본문 바로가기
Programming/Python

파이썬 한글 자음 모음 조합하기 (NLP)

by 코딩하는 금융인 2021. 7. 17.

안녕하세요.

오늘은 과거 진행했던 NLP 프로젝트에서 유용했던 파이썬 프로그래밍 작업에 대해 리뷰해보겠습니다.


프로젝트 목표 : 한글 검색어에 대한 다양한 모음 조합 찾기

- 샘플 단어 : 텔레비젼

프로그래밍 작업의 목적은 자동으로 어떤 단어에 대한 모음을 재조합하여 다양한 경우의 수 찾기였습니다.

 

예를 들면, 텔레비젼 -> 텔레비젼, 텔래비젼, 탤레비젼, 탤래비젼

이런 식으로 'ㅔ' 를 'ㅐ'로, 'ㅐ'를 'ㅔ'로 같은 발음을 가졌지만, 모음 하나에서 차이가 나는 경우의 수를 조합하여 데이터를 가공해봤습니다.

 

똑같은 단어라도 사람마다 말하는 방식(발음)이 다르기에 'ㅔ'와 'ㅐ'를 조합하여 다양한 발음을 고려할 수 있는 검색어 범위를 만들어주고 싶었습니다.

 

따라서, 다양한 조합을 찾아 회사가 갖고 있는 음성 인식 알고리즘에 제가 찾거나 가공한 데이터를 넣어주어 학습되는 데이터의 다양성을 넓혀주는 방향으로 프로젝트에 임했습니다.

 

먼저, 목표를 주석으로 달아주고 라이브러리를 import 해주겠습니다.

## 목표 : 한글 단어 모음 ㅔ를 ㅐ로, ㅐ를 ㅔ로 변환하기
# 라이브러리 장착
from jamo import j2h
from jamo import j2hcj
from itertools import product

패키지는 한글 자음 모음 결합 및 분리에 많이 쓰이는 jamo를 사용했습니다.

 

▣ 한글 초성, 중성, 종성 리스트

####################################################################################
# 초성 리스트. 19개
CHOSUNG_LIST = ['ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ',
				'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']
# 중성 리스트. 21개
JUNGSUNG_LIST = ['ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ',
				 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ']
# 종성 리스트. 28개
JONGSUNG_LIST = [' ', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ',
				 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ',
                 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']
####################################################################################

 

아래의 유니코드를 참고하면, 초중종성의 인덱스 계산식을 도출 가능합니다.

초성 인덱스 (음절코드 - 44032)//588
중성 인덱스 (음절코드 - 44032 - (초성 인덱스*588))/28
종성 인덱스 (음절코드 - 44032 - (초성 인덱스*588) - (중성 인덱스 * 28))

※ 44032는 '가'의 유니코드, 588는 중성(21)과 종성(28)이 다 돌고 난 후의 개수

 

▶ 참고자료

유니코드 : https://www.unicode.org/charts/PDF/UAC00.pdf


초성/중성/종성 구분하기

def korean_jamo(korean_word):
    """
    한글 단어를 입력받아서 초성/중성/종성을 구분하여 리턴해줍니다.
    """
    r_lst = []
    for w in list(korean_word.strip()):
        if '가'<=w<='힣':
            ch1 = (ord(w) - ord('가'))//588
            ch2 = ((ord(w) - ord('가')) - (588*ch1)) // 28
            ch3 = (ord(w) - ord('가')) - (588*ch1) - 28*ch2
            r_lst.append([CHOSUNG_LIST[ch1], JUNGSUNG_LIST[ch2], JONGSUNG_LIST[ch3]])
        else:
            r_lst.append([w])
    return r_lst

 

위에서 말한 계산식에 따라 초성/중성/종성을 구분한 리스트를 리턴하는 함수

 

'ㅔ'<->'ㅐ' / 'ㅖ' <-> 'ㅒ' 변환하기

def change(a):
	"""
    ㅔ와 ㅐ / ㅒ와 ㅖ 바꿔주기
    """
    if a[1] == "ㅔ" :
        return [[a[0],"ㅔ",a[2]],[a[0],"ㅐ",a[2]]]
    if a[1] == "ㅐ" :
        return [[a[0],"ㅔ",a[2]],[a[0],"ㅐ",a[2]]]
    if a[1] == "ㅖ" :
        return  [[a[0],"ㅖ",a[2]],[a[0],"ㅒ",a[2]]]
    if a[1] == "ㅒ" :
        return  [[a[0],"ㅖ",a[2]],[a[0],"ㅒ",a[2]]]
    else : return [a]

모음인 중성에서 같은 발음을 가진 'ㅔ'와 'ㅐ, 'ㅖ'와 'ㅒ'를 리스트화 시켜주는 함수

 

 종성 유무에 따른 결합

def doSomeThing( data , length ) :
    """
    종성 유무에 따른 결합
    """
    if data[2] ==' ' :
        return j2h( data[0] , data[1] )
    else :
        return j2h( data[0] , data[1] , data[2] )

종성 유무에 따라 자음, 모음을 결합해주는 함수 (j2h)

 

실제 테스트 해보기 ( 예시 : 텔레비젼 )

a = korean_jamo("텔레비젼")
"""
print(a) : [['ㅌ', 'ㅔ', 'ㄹ'], ['ㄹ', 'ㅔ', ' '], 
			['ㅂ', 'ㅣ', ' '], ['ㅈ', 'ㅕ', 'ㄴ']]
"""

data =[]
w2=[]

for i in range(0,len(a)):
    w = change(a[i])
    w2.append(w)

w3=list(product(*w2))
"""
print(w3) : [(['ㅌ', 'ㅔ', 'ㄹ'], ['ㄹ', 'ㅔ', ' '], ['ㅂ', 'ㅣ', ' '], ['ㅈ', 'ㅕ', 'ㄴ']),
			 (['ㅌ', 'ㅔ', 'ㄹ'], ['ㄹ', 'ㅐ', ' '], ['ㅂ', 'ㅣ', ' '], ['ㅈ', 'ㅕ', 'ㄴ']), 
             (['ㅌ', 'ㅐ', 'ㄹ'], ['ㄹ', 'ㅔ', ' '], ['ㅂ', 'ㅣ', ' '], ['ㅈ', 'ㅕ', 'ㄴ']), 
             (['ㅌ', 'ㅐ', 'ㄹ'], ['ㄹ', 'ㅐ', ' '], ['ㅂ', 'ㅣ', ' '], ['ㅈ', 'ㅕ', 'ㄴ'])]
"""

for i in w3:
    lk = []
    for kk in i:
        ll = ''.join(kk)
        lk.append(doSomeThing(j2hcj(ll), len(j2hcj(ll))))
    data.append(''.join(lk))

## 결과 ##
print(data)
['텔레비젼', '텔래비젼', '탤레비젼', '탤래비젼']

중간에 product를 사용한 이유는 'ㅔ'와 'ㅐ'로 이루어진 모든 조합을 구하기 위해서입니다.

모든 조합의 리스트 w3를 하나씩 j2hcj를 활용하여 결합하고 리스트화 -> 최종 결합해서 data에 넣어주기

 

 

 

반응형

댓글