AI TECH

[실습] 전처리 Corpus cleaning

prefer_all 2022. 10. 10. 20:50
<한국어 말뭉치 전처리 모듈>
1. KoNLPy : 품사분석
2. Khaiii : 형태소 분석
3. PyKoSpacing : 띄어쓰기
4. Py-Hanspell: 한국어 맞춤법 교정
5. 정규표현식
6. 크롤링 실습

 

KoNLPy

KoNLPy는 품사 분석을 위한 다음과 같은 모듈을 제공합니다. 

  • Kkma
  • Komoran
  • Hannanum
  • Okt
  • Mecab

KoNLPy에서 제공되는 모듈들은 대부분 통계 기반 형태소 분석기 입니다. 각 모듈들은 비슷한 기능을 제공하지만, 사용하는 품사 태그나 실행시간, 그리고 그 정확성에 다소 차이가 있습니다.

 

예제) Hannanum을 사용한 한국어 문장의 품사 분석

from konlpy.tag import Hannanum
hannanum = Hannanum()
text = '환영합니다! 자연어 처리 수업은 재미있게 듣고 계신가요?'
print(hannanum.morphs(text))  # 형태소 단위로 나누기 
print(hannanum.nouns(text))   # 명사만 뽑아내기
print(hannanum.pos(text))     # 품사 태깅
''' 출력 결과
['환영', '하', 'ㅂ니다', '!', '자연어', '처리', '수업', '은', '재미있', '게', '듣', '고', '계시', 'ㄴ가', '요', '?']
['환영', '자연어', '처리', '수업']
[('환영', 'N'), ('하', 'X'), ('ㅂ니다', 'E'), ('!', 'S'), ('자연어', 'N'), ('처리', 'N'), ('수업', 'N'), ('은', 'J'), ('재미있', 'P'), ('게', 'E'), ('듣', 'P'), ('고', 'E'), ('계시', 'P'), ('ㄴ가', 'E'), ('요', 'J'), ('?', 'S')]
'''

Khaiii

 

최근에는 심층 학습(Deep Learning, 딥러닝)을 활용하여 형태소 분석을 진행되기도 합니다.

 

다운로드

!git clone https://github.com/kakao/khaiii.git
!pip install cmake
!mkdir build
!cd build && cmake ../khaiii
!cd build && make all
!cd build && make resource
!cd build && make install
!cd build && make package_python
!pip install build/package_python

 

from khaiii import KhaiiiApi
khaiiApi = KhaiiiApi()
tokenized = khaiiApi.analyze('환영합니다! 자연어 처리 수업은 재미있게 듣고 계신가요?')
tokens = []
for word in tokenized:
    tokens.extend([str(m).split('/')[0] for m in word.morphs])

print(tokens)
# ['환영', '하', 'ㅂ니다', '!', '자연어', '처리', '수업', '은', '재미있', '게', '듣', '고', '계시', 'ㄴ가요', '?']

PyKoSpacing

 

부적절한 띄어쓰기가 포함된 문장을 전처리하기 위하여 많은 모델로,  RNN (Recurrent Neural Network) 중 하나인 Gated Recurrent Unit (GRU)와 CNN을 다음과 같이 사용하여 모델을 구성 및 학습하였습니다.

 

다운로드

!pip install git+https://github.com/haven-jeon/PyKoSpacing.git

 

from pykospacing import Spacing
spacing = Spacing()
kospacing_sent = spacing(new_sent) 

print('띄어쓰기가 없는 문장 :\n', new_sent) 
print('정답 문장:\n', sent) 
print('띄어쓰기 교정 후:\n', kospacing_sent)
'''
띄어쓰기가 없는 문장 :
 환영합니다!자연어처리수업은재미있게듣고계신가요?
정답 문장:
 환영합니다! 자연어 처리 수업은 재미있게 듣고 계신가요?
띄어쓰기 교정 후:
 환영합니다! 자연어 처리 수업은 재미있게 듣고 계신 가요?
'''

Py-Hanspell

네이버 한국어 맞춤법 검사기를 기반으로 하여 제작되었습니다.

 

다운로드

!pip install git+https://github.com/ssut/py-hanspell.git

 

from hanspell import spell_checker

sent = "맞춤법 틀리면 외 않되? 쓰고싶은대로쓰면돼지 "
spelled_sent = spell_checker.check(sent)

hanspell_sent = spelled_sent.checked
print(hanspell_sent)
# 맞춤법 틀리면 왜 안돼? 쓰고 싶은 대로 쓰면 되지

 


정규 표현식(Regular Expression)

 

연습할 수 있는 사이트: https://regexr.com/

import re

 

pattern 찾기

역슬래시 "\" (한국어 인코딩에서는 ) 를 활용하면 특정 집합에 속한 문자 "하나"를 지칭할 수 있습니다. 사용 가능한 대표적인 특수 글자는 다음과 같습니다

특수 글자 설명
\w 영숫자 + 언더스코어("_")
\W (영숫자 + "_")를 제외한 문자
\d 숫자
\d 숫자가 아닌 문자
\s 공백 문자
\S 공백이 아닌 문자
\b 단어 경계
pattern = r's\wa'  
# \는 python에서 escape 문자로 쓰이기 때문에 정규표현식을 올바르게 사용하기 위해선 
# r''형태의 raw string을 사용해야합니다.
for match in re.finditer(pattern, text):
    print(match.span(), match.group(0))     
    # (시작 index, 끝 index + 1)과 들어맞는 패턴을 출력합니다.

sha, sta 등의 문자가 출력됨

 

 

정규 표현식에는 아래의 메타 문자가 있으며, 이 문자들은 특수한 의미를 가진 문법의 역할을 하기 때문에 사용이 불가능합니다.

. ^ $ * + ? { } [ ] \ | ( )

해당 문제를 그대로 매칭하고 싶다면 Escape 문자인 \를 붙여 사용해야합니다.

pattern = r"\(\)" 
re.findall(pattern, "abc()de") # text에서 pattern 찾기
# 출력값: ['()']

 

메타 문자

. .은 아무 문자 하나를 지칭합니다.
공백은 포함하나, 줄 바꿈 문자 
\n은 제외합니다.

ex) pattern = r's.a' 의 경우, s a, saa, sba, sca.. 등이 출력됨
[] [ ] 안에 있는 문자들 중에서 하나의 문자와 매치합니다.
범위를 지정할 수도 있습니다. Ex) A-Z, a-z, 0-9

ex) pattern = r'[a-di]s'        # a, b, c, d, i
      as,bs,cd,ds,is 가 출력됨
[^] [^ ] 안에 있는 문자를 제외한 문자들 중에서 하나의 문자와 매치합니다.

ex) pattern = r'[^a-di]s' 
      es, fs, gs.... 등이 출력됨
* * 는 바로 앞의 문자가 0개 이상일 경우를 나타냅니다.

ex)
pattern = r'om*.n'   # m이 없거나 한 개 이상 존재
      ogn, oun, ommon, ommmen... 등이 출력됨
+ + 는 바로 앞의 문자가 최소 1개 이상일 경우를 나타냅니다.

ex)
pattern = r'\w+s'  #s로 끝나는 단어

 

이러한 반복관련 기능들은 가능한 조합 중에서 가장 긴 것을 매칭합니다.

html = '<div> text1 </div> text2 <div> text3 </div>'        
# <div>와 같은 HTML 태그를 뽑아내고 싶다고 가정합니다.
pattern = r'<.+>'
for match in re.finditer(pattern, html):
    print(match.span(), match.group(0))
# (0, 43) <div> text1 </div> text2 <div> text3 </div>

 

 

반대로 가장 짧은 것을 매칭하기 위해선 반복관련 기호 뒤에 ?를 붙이면 됩니다.

pattern = r'<.+?>'
for match in re.finditer(pattern, html):
    print(match.span(), match.group(0))
    
'''
(0, 5) <div>
(12, 18) </div>
(25, 30) <div>
(37, 43) </div>
'''

다만 ?를 붙어 가장 짧은 것을 매칭하기보다 명시적으로 매칭되면 안되는 문자를 지정하는 것을 권장합니다.

pattern = r'<[^>]+>'
for match in re.finditer(pattern, html):
    print(match.span(), match.group(0))

 

? ? 는 바로 앞의 문자가 있을 수도 없을 수도 있는 경우를 나타냅니다.
{} { }는 {숫자} 형태로 사용되며, 바로 앞의 문자가 해당 숫자만큼 반복되는 경우를 나타냅니다.

ex)
pattern = r'om{2}.n' #ommn
^ 글의 시작을 의미합니다
pattern = r'^\w+'
text2 = """\
But recognition of the inherent dignity and of the equal and inalienable rights of all members of the human family is the foundation of freedom, justice and peace in the world,
Whereas disregard and contempt for human rights have resulted in barbarous acts which have outraged the conscience of mankind, and the advent of a world in which human beings shall enjoy freedom of speech and belief and freedom from fear and want has been proclaimed as the highest aspiration of the common people,
Whereas it is essential, if man is not to be compelled to have recourse, as a last resort, to rebellion against tyranny and oppression, that human rights should be protected by the rule of law,
""" 
for match in re.finditer(pattern, text2):
    print(match.span(), match.group(0))
# (0, 3) But

 

줄이 나눠졌을 때 각 줄의 시작을 의미하고 싶다면 Multi-line flag를 지정해야합니다.

pattern = r'^\w+'
for match in re.finditer(pattern, text2, re.MULTILINE):
    print(match.span(), match.group(0))
'''
(0, 3) But
(177, 184) Whereas
(492, 499) Whereas
'''

 

Multi-line flag외에도 다양한 Flag들이 존재합니다. 대표적으로 대소문자 미구분을 뜻하는 Ignore case flag가 있습니다.

pattern = 'human'
for match in re.finditer(pattern, text, re.IGNORECASE):
    print(match.span(), match.group(0))
# Human도 포함

 

$ 글의 끝을 의미합니다.

|

일종의 or 기호로서 여러 패턴 중 하나랑 매칭합니다.

ex)
pattern = r'[ai]s|in|of'  
       as, is, in, of 
() 텍스트을 캡쳐합니다.
캡쳐된 텍스트은 캡쳐된 순서대로 
\숫자 형태로 불러오는 것이 가능합니다.
일종의 괄호이므로 우선 순위가 있습니다.
(?: ) 일반 괄호입니다. 캡쳐를 하지 않습니다.
(?= ) 뒷 패턴을 확인합니다. D(?=R) 형태로 사용합니다. R이 바로 뒤에 있는 D를 매칭하며, R부분은 포함하지 않습니다.

ex) pattern = r'\w+(?=ing)'            # ing가 뒤에 있는 텍스트를 매칭합니다.
       text가 eating이면 eat를 print
(?<=) 앞 패턴을 확인합니다. (?<=R)D 형태로 사용합니다. R이 바로 앞에 있는 D를 매칭하며, R부분은 포함하지 않습니다.

ex)
pattern = r'(?<=all )\w+' # 'all `이 앞에 있는 텍스트를 매칭합니다.
      text가 all want for christmas이면 all을 print

 

 

()  앞에서 (), (), () 썼으면 \1, \2, \3 로 불러옴

numbers = """\
010-1234-5678
010-4321-4321
051-9876-5432
010-1010-1010 
02-0101-1010
02-1111-1111\
"""                                                         
# 중간 번호와 끝 번호가 같은 전화번호를 뽑아봅시다.

pattern = r'^\d{2,3}-(\d{4})-\1$'                           
# 캡쳐된 텍스트는 1번부터 사용이 가능합니다.
for match in re.finditer(pattern, numbers, re.MULTILINE):
    print(match.span(), match.group(0), match.group(1))     
    # Group의 숫자를 지정하여 캡쳐된 텍스트를 출력. 0번 Group는 텍스트 전체를 의미합니다.
'''
(14, 27) 010-4321-4321 4321
(70, 82) 02-1111-1111 1111
'''

 

(?: )

numbers = """\
010-1234-5678
+82-10-4321-4321
+82-51-9876-5432
010-1010-1010 
02-0101-1010
02-1111-1111\
"""

pattern = r'^(?:0|\+82-)\d{1,2}-(\d{4})-\1$'                
# 캡쳐된 텍스트는 1번부터 사용이 가능합니다.

for match in re.finditer(pattern, numbers, re.MULTILINE):
    print(match.span(), match.group(0), match.group(1))     
# Group의 숫자를 지정하여 캡쳐된 텍스트를 출력. 0번 Group는 텍스트 전체를 의미합니다.
'''
(14, 30) +82-10-4321-4321 4321
(76, 88) 02-1111-1111 1111
'''

크롤링 후 텍스트 전처리 실습

# 네이버 영화 한줄평 크롤링
from urllib.request import urlopen # 웹서버에 접근 모듈 
from bs4 import BeautifulSoup # 웹페이지 내용구조 분석 모듈
from time import sleep

reviews = []
for j in range(1, 11):
    sleep(0.5)                                                           
    # 시간차를 두지 않고 웹페이지 접속시 DDOS 공격으로 분류될 수 있음
    url='https://movie.naver.com/movie/bi/mi/pointWriteFormList.naver?code=187348&type=after&isActualPointWriteExecute=false&isMileageSubscriptionAlready=false&isMileageSubscriptionReject=false&page='+str(j)
    html=urlopen(url)
    html_source = BeautifulSoup(html,'html.parser',from_encoding='utf-8') 
    # 댓글 페이지를 utf-8형식으로 html 소스가져오기

    for i in range(10):
        html_reviews = html_source.find('span',{'id': '_filtered_ment_'+str(i)}) 
        reviews.append(html_reviews.text.strip())
import re

# 1. 한글만 남기고 다른 글자 제거
for index, review in enumerate(reviews):
    reviews[index] = re.sub('[^ 가-힣]', '', review)        
    # sub 함수를 사용하면 해당 패턴을 다른 텍스트로 대체가 가능합니다.


# 2. 중복 공백 제거
for index, review in enumerate(reviews):
    reviews[index] = re.sub(' +', ' ', review) 
   
# 3. 한국어 띄어쓰기 및 맞춤법 교정   
from pykospacing import Spacing
from hanspell import spell_checker

spacing = Spacing()

for index, review in enumerate(reviews):
    review = spacing(review)
    reviews[index] = spell_checker.check(review).checked

print(reviews)