음성학/음운론 연구를 위한 Python 맛보기
음운론학회 연구방법론 워크숍
2018년 12월 8일 (토)

음성학/음운론 연구를 위한 Python 맛보기 Part 3

Phonotactics

  • Part 1
    • Python 프로그래밍을 위한 variables 및 data type의 이해
    • control structure 소개
    • function과 class 소개
  • Part 2
    • 문자열
    • file input 및 file ouput
    • packages (예, NLTK) 사용 소개
  • [x] Part 3
    • Regular Expression
    • Unicode와 text encoding
    • Error 발생시 대처법

1. Regular expression

re (regular expression) 모듈 사용방법

  • regular expression은 특정한 규칙을 가진 문자열의 패턴을 표현하는 데 사용
  • text에서 특정 문자열을 검색(search)하거나 치환(substitute)할 때 사용
  • re 모듈의 compile 함수
    • 정규식 패턴을 입력으로 받아들여 정규식 객체를 return함.
    • 정규식 객체로부터 실제 결과 문자열을 얻기 위해서는 group() method를 사용함.
    • 정규 표현식에서 ()는 group을 의미.
    • 첫번째 group은 group(1)으로, 두번째 group은 group(2)로, 그리고 전체는 group() 혹은 group(0)을 사용
  • search() 메소드: 문자열에서 처음 matching되는 문자열만 return
  • match() 메소드: 문자열의 처음부터 정규식과 match되는 지 조사
  • findall() 메소드: matching되는 문자열을 모두 return
In [ ]:
import re
m = re.match('([0-9]+) ([0-9]+)', '10 295')
print(m)
In [ ]:
print(m.group())
print(m.group(1))
print(m.group(2))

<a href="c:\Python34\Koala.jpg">그림</a><font size = "10">

이 코드에서 그림의 경로인 c:\Python34\Koala.jpg 부문만 꺼내오고 싶다면, 어떻게 해야할까?

In [ ]:
import re
url = '<a href="c:\Python34\Koala.jpg">그림</a><font size = "10">'
print(re.search('href="(.*?)">', url).group(1))
In [ ]:
p = re.compile(r'(\b\w+)\s+\1')                     # \s (공백문자), \w (영문자+숫자), \b (단어경계)
p.search("Paris in the the spring").group()    # group() (RE에 일치된 문자열을 반환)

정규식 패턴 표현

  • . \n (=newline)을 제외한 모든 문자
  • * 앞 pattern이 0개 이상
  • + 앞 pattern이 하나 이상이어야 함.
  • ? 앞 pattern이 없어가 하나 (optional pattern
  • \b 단어(word boundary)경계
  • \w 문자(character)의미
  • \s 공백문자(white space)를 의미 (== [\t\n\r\f])
  • \1 backrereference로서 처음 match된 group을 지칭

Seoul Corpus 등에서 vowel harmony에 관심이 있다면...

In [ ]:
regex = re.compile('[^aeiou]')
lists = regex.split('iip0aarriissiissii')   # SeoulCorpus의 전사 예
new_lists = []
for l in lists:
    if l != "": 
        new_lists.append(l)
print(new_lists)

for l in range(len(new_lists)-1):
    print("Check harmony for: ", new_lists[l], new_lists[int(l)+1])
    if new_lists[l] == new_lists[l+1]: print('\t[', new_lists[l], '] [', new_lists[l+1], '] are in harmony')
    else: print('\tno harmony')

문자열 치환하기

  • re.sub() 함수는 문자열에서 매치된 텍스트를 다른 텍스트로 치환
    • ‘sub’는 치환을 뜻하는 ‘substitution’의 줄임말
    • 패턴이 여러 번 matching하면 matching한 텍스트를 모두 치환
In [ ]:
text = '즐거운 perl 프로그래밍'
pattern = re.compile(r'perl')
output = re.sub(pattern, 'Python', '즐거운 perl 프로그래밍')
print('text:', text)
print('output:', output)

전방 탐방(look ahead) 혹은 후방 탐방(look behind)

  • (?=...)(?!...): looka head & negative look ahead
  • (?<=...)(?<!...): look behind & negative look behind

  • [문자들 ] 가능한 문자들의 집합을 정의하며, 문자들 중의 하나면 matching

  • [^문자들] 가능하지 않는 문자들의 집합, 이 문자들이 아니면 matching
In [ ]:
# look ahead and look forward
import re
place = re.compile('(?<=m)[^pbmw]')
m = re.search(place, 'amta')
if m:
    print('[', m.group(), '] occurs after [m], which violates place assimilation')
else: print('찾는 문자열이 없습니다.')

check pronunciation in a pronunication dictionary

  • data 디렉토리에 english_dict.txt라는 파일이 있는 데, 이 파일의 내용을 살펴보면 단어 발음의 순으로 발음사전이 구성되어 있다는 것을 알 수 있다.
  • 이 파일을 읽어서, 단어는 사전의 key에, 그리고 발음은 value에 저장한 후, 단어를 입력받으면 발음을 찾아서 출력해 주는 프로그램을 만들어 보자.
In [ ]:
%ls data/*
In [ ]:
eng_dict = open('data/english_dict.txt', "r")
lines = eng_dict.readlines()
eng_dict.close()
In [ ]:
print(lines[:10])
In [ ]:
eng_dict = {}
for line in lines:
    line = line.strip().split('\t')
    ortho = line[0]
    pron = line[1]
    #print(ortho, pron)
    eng_dict[ortho] = pron
    
In [ ]:
word = input('Type a word to check the pronunciation:')
print(eng_dict.get(word, 'N/A'))
In [ ]:
#define vowels
vowels = 'aeiouɜɛɑʊ'
#get the word from the user
word = input("Type a word: ")
#proceed as before...

eng_dict = {}
for line in lines:
    line = line.strip().split()
    ortho = line[0]
    pron = line[1]
    #print(ortho, pron)
    eng_dict[ortho] = pron
    
word = eng_dict.get(word, 'N/A')
counter = 0
vowelcount = 0
while counter < len(word):
    if word[counter] in vowels:
        vowelcount += 1
    counter += 1
else:
    print('There are',vowelcount, 'vowels in this word: ', word)
    print("   NOTE: The diphthongs are not treated properly. I will leave this as a work to do")

glob() 모듈 소개

glob() 함수는 복잡한 정규표현식이 아닌, 유닉스 쉘 규칙을 사용하여 일치하는 파일(file)이나 디렉토리(directory)의 이름을 검색한다. 규칙은 다음과 같다:

  • 모든 것에 일치: * (re 모듈에서 .*와 같다)
  • 한 문자에 일치: ?
  • a, b, 혹은 c문자에 일치: [abc]
  • a, b, 혹은 c를 제외한 문자에 일치: [!abc]
In [ ]:
import glob
In [ ]:
files = glob.glob('data/*.txt')
for file in files:
    print(file)
In [ ]:
files = glob.glob('data/Seoul*[234]*.txt')
for file in files:
    print(file)
In [ ]:
! head data/SeoulCorpus4PCT_s02_results.txt
In [ ]:
# Error will occur

for file in files:
    print(file)
    fin = open(file, 'r')
    lines = fin.readlines()
    fin.close()
    
    for line in lines:
        line = line.strip().split()
        print(line)
 

2 Encoding & Unicode

In [ ]:
import sys
sys.getdefaultencoding()
In [ ]:
import io, re
In [ ]:
fout = open('data/test.txt', 'w')
for file in files:
    print(file)
    fin = io.open(file, 'r', encoding="utf-16")
    lines = fin.readlines()
    fin.close()

fout.close()            
        

단어 초의 자음군

In [ ]:
vowels = 'aeiou'
word = 'sthetic'
counter = 0            # 문자 수를 세기 위하여
cluster=[]                # 빈 리스트에 모음을 만나기 전까지의 자음들을 저장하기
for i in range(len(word)):       # 단어의 개수까지를 레인지로 잡아서 
    if word[i] in vowels:           
        break
    cluster.append(word[i])
    counter += 1
cluster_string = ''.join(cluster)
print("The word begins with", counter, "consonant letters")
print("The consonant letters are [", cluster_string, "]")
In [ ]:
print(range(7))

simple entropy

In [ ]:
word_dict = {'a': 100, 'b': 0.00000001}
total_freq = sum(word_dict.values())
print(total_freq)

from math import log2
def entropy(word, freq):
    h = -(freq/total_freq)*log2(freq/total_freq)
    return (word, h)

total_H= []
for wrd, freq in word_dict.items(): 
    total_H.append(entropy(wrd, freq)[1])
    print(entropy(wrd, freq))
    
print(total_H)
print("Total Entropy is: ", sum(total_H))
In [ ]:
 

3. Alice in the Wonderland에서 initial consonant cluster 추출하기

In [ ]:
# open the file
fin = open('data/alice.txt', 'r')
# read it all in 
text = fin.read()
# close the file stream
fin.close()
In [ ]:
# convert to lowercase and split into words
words = text.lower().split()
print(words[:50])
In [ ]:
words_str = ' '.join(words)
import re
# punctuation 
punct = re.compile('[\.\?\-!\*,"\(\):\'\[\];_/~]')

new_words = re.sub(punct, ' ', words_str)
new_words_list = new_words.split()
print(new_words_list[:50])
In [ ]:
worddict = {}
for w in new_words_list:
    if w in worddict:
        worddict[w] += 1
    else: 
        worddict[w] = 1


        
for wrd, freq in sorted(worddict.items(), key=lambda t : t[1], reverse=True): 
    print(wrd, '\t', freq)


#  lambda 함수에 t와 t[1]을 사용하였는데, 
# 여기에서 t는 worddict.items()에서 얻은 목록에 들어 있는 항목을 의미하며,
# t 항목은 ('key', value) 튜플로 되어 있습니다. 
# 그렇기 때문에 t[1]은 value를 의미하여 정렬하는 키를 value로 하라는 의미입니다
In [ ]:
total_freq = sum(worddict.values())

from math import log2
def word_entropy(word, freq):
    h = -(freq/total_freq)*log2(freq/total_freq)
    return (word, h)

total_H= []
for wrd, freq in sorted(worddict.items(), key=lambda t : t[1], reverse=True): 
    total_H.append(word_entropy(wrd, freq)[1])
    print(word_entropy(wrd, freq)[0], '\t', word_entropy(wrd, freq)[1], '\t', freq)

print("Total Entropy is: ", sum(total_H))
In [ ]:
 
In [ ]:
clusters = []

for w in new_words_list:
    m = re.search('^[^aeiou]*', w)
    if m:
        onset = w[0:m.end()]
        clusters.append(onset)
print(clusters[:40])
In [ ]:
# eliminate duplicate onsets
clusters = sorted(set(clusters))
print(clusters)
In [ ]:
# Refinement
# words that contain a vowel and that have non-word-initial y
clusters = []
for w in words:
    m = re.search('^([^aeiouy]*)[aeiouy]', w)
    if m:
        if m.end(1) == 0 and w[0] == 'y':
            onset = 'y'
        else:
            onset = w[0:m.end(1)]
        clusters.append(onset)
# eliminate duplicate onsets
clusters = sorted(set(clusters))
print(clusters)
In [ ]:
# count all clusters
clusters = {}
for w in words:
    m = re.search('^([^aeiouy]*)[aeiouy]', w)
    if m:
        if m.end(1) == 0 and w[0] == 'y':
            onset = 'y'
        else:
            onset = w[0:m.end(1)]
        if onset in clusters:
            clusters[onset] = clusters[onset] + 1
        else:
            clusters[onset] = 1

# print onset counts
keys = sorted(clusters.keys())

for c in keys:
    print(c, ":", clusters[c], sep="")

The End of the Workshop!