0. 자연어 분석 4단계
- 어휘분석 : 입력된 문자열을 분석하여 형태소로 분리하는 단계 (품사 태깅 포함)
- 구문분석 : 문장의 구조를 분석하여 파싱(Parsing) 하는 단계
- 의미분석 : 구문 분석 결과 생성된 통사 구조에 의해 문장의 의미를 밝혀내는 작업을 수행하는 단계
- 화용분석 : 언어의 사용에 관련된 지식을 통해 문장을 해석함으로써 화자의 의도를 파악하는 작업
- 예시)
- 어휘분석을 진행하기 전에 텍스트 전처리 진행
1. 텍스트 전처리 (Text preprocessing)
0)비정형 데이터란?
💡 비정형 데이터 : 일정한 규격이나 형태를 지닌 숫자 데이터(Numeric data)와 달리 그림이나 영상, 문서처럼 형태와 구조가 다른 구조화 되지 않은 데이터
비정형 데이터 예시) 음성정보, 동영상정보, 시각 정보 등
- 텍스트는 대표적인 비정형 데이터
- 비정형 데이터인 텍스트(raw data)를 정형화된 Feature 형태로 나타내도록 전처리 해줘야 함
1) Text Cleaning(정제) and 정규화(Normalization)
Text Cleaning(정제)
- 불필요한 기호, 숫자, 영어 등 필요하지 않은 언어를 제거하고, 띄어쓰기, 맞춤법 검사 등의 기초적인 전처리 과정
정규화(Normalization)
: 표기가 다른 단어들을 하나의 단어로 표현하는 방법으로 대소문자 통합, 동사원형 복원 등이 있음.
1-1) 노이즈 제거
(1) 대소문자 통합
- 영어권 언어에서 유리
- 보통 대문자 → 소문자
- 하지만 모든 단어를 소문자로 바꾸면 안됨
- e.g. 미국의 US vs 우리를 뜻하는 us
(2) 불필요한 단어 제거
- 등장 빈도가 적은 단어
- 텍스트 데이터에서 너무 적게 등장해서 자연어 처리에 도움이 되지 않는 단어들
- 길이가 짧은 단어
- 영어
- 길이가 짧은 단어를 삭제하는 것만으로도 의미없는 단어를 제거하는 효과가 있음
- e.g. 영어의 경우 단어길이가 1인 단어를 제거하면 → 관사 ‘a’와 주어 ‘I’가 제거됨
- 한국어
- 한국어의 경우 단어에 한자어가 많고 한 글자만으로도 의미를 가진 경우가 많음
- → 지울 때 주의가 필요
- 영어
1-2) 불용어(stopwords) 처리
- 불용어(Stopword)란?e.g. 관사, 조사, 접미사 등
- 자주 등장하지만 분석을 하는 것에 있어서는 큰 도움이 되지 않는 단어들
- 영어
- 주로 nltk 안에 있는 stopwords 이용
from nltk.corpus import stopwords stop_words_list = stopwords.words('english') print('불용어 개수 :', len(stop_words_list)) print('불용어 10개 출력 :',stop_words_list[:10]) # 불용어 개수 : 179 # 불용어 10개 출력 : ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're"]
- 한국어
- 보통 불용어 리스트를 직접 정의
- 은,는,이,가 등의 조사, 어미 등등
- https://www.ranks.nl/stopwords/korean → 링크에 잘 정리되어 있음
- 보통 불용어 리스트를 직접 정의
stopword_list = ['은', '는', '이', '가', '에서', '어요', '으로', '네요', '지만', '에게', '습니다', '는데', '지만', '한다', '다는', '라고', '다고', '라는', '면서', '까지',
'입니다', '아니', '보다', '그리고', '합니다', '면서', '아요', '해서', '는다', '해서', '으면', '세요', '처럼', '된다', '나오', '다고', '다는',
'으면', '다가', '라고', '라는', '부터', '네요', '없이', '그리고', '려고', '입니다', '아서', '는지', '인데', '어떻게', '에게', '너무']
# 불용어 제거 함수
def remove_stopwords(words):
result = []
for w in words:
if w not in stopword_list:
result.append(w)
return result
1-3) 정규표현식
- 불필요한 특수기호, 숫자 등을 지우는 방법
특수문자 | 설명 | 예시 |
. | 한 개의 임의의 문자를 나타냅니다. (줄바꿈 문자인 \n는 제외) |
"a.c” : “abc” or “akc” → a와 c 사이에 임의의 문자 가능 |
? | 앞의 문자가 존재할 수도 있고, 존재하지 않을 수도 있습니다. (문자가 0개 또는 1개) | “ab?c” : ? 앞의 문자(b)가 존재 O/X → abc or ac 가능 |
* | 앞의 문자가 무한개로 존재할 수도 있고, 존재하지 않을 수도 있습니다. (문자가 0개 이상) | “ab*c” : * 바로 앞의 문자(b)가 존재 X or 여러개 존재 가능 → ac, abc, abbc, ,... 무수히 가능 |
+ | 앞의 문자가 최소 한 개 이상 존재합니다. (문자가 1개 이상) |
“ab+c” : + 바로 앞의 문자(b)가 1개 이상 존재 가능 → ac, abc, abbc, ,... 무수히 가능 |
^ | 뒤의 문자열로 문자열이 시작됩니다. | “^ab” : ab로 시작되는 문자열 → abc, abcd 등 |
$ | 앞의 문자열로 문자열이 끝납니다. | “ab$” : ab로 끝나는 문자열 → cab, dab 등 |
{숫자} 기호 | 숫자만큼 반복합니다. | "ab{2}c” : a와 c 사이에 b 2개 있는 문자열 → abbc |
{숫자1, 숫자2} 기호 | 숫자1 이상 숫자2 이하만큼 반복합니다. ?, *, +를 이것으로 대체할 수 있습니다. |
"ab{2,8}c” : a와 c 사이에 b가 2이상 8이하인 문자열 |
[ ] | 대괄호 안의 문자들 중 한 개의 문자와 매치합니다. [a-zA-Z]는 알파벳 전체를 의미하는 범위이며, 문자열에 알파벳이 존재하면 매치를 의미합니다. | [abc] : a or b or c 만 매치 |
[^문자] | 해당 문자를 제외한 문자를 매치합니다. | "[^abc]” : a or b or c가 아닌 문자열만 매칭 |
| | AlB와 같이 쓰이며 A 또는 B의 의미를 가집니다. | “a|b” : a or b |
- 예시)
r = re.compile("[a-z]") # 컴파일 지정
# 아무런 결과도 출력되지 않는다.
r.search("AAA") # 대문자 X
r.search("111") # 숫자 X
r.search("aBC")
# <_sre.SRE_Match object; span=(0, 1), match='a'>
정규표현식 문자규칙
문자규칙 | 설명 |
\ | 역 슬래쉬 문자 자체를 의미합니다 |
\d | 모든 숫자를 의미합니다. [0-9]와 의미가 동일합니다. |
\D | 숫자를 제외한 모든 문자를 의미합니다. [^0-9]와 의미가 동일합니다. |
\s | 공백을 의미합니다. [ \t\n\r\f\v]와 의미가 동일합니다. |
\S | 공백을 제외한 문자를 의미합니다. [^ \t\n\r\f\v]와 의미가 동일합니다. |
\w | 문자 또는 숫자를 의미합니다. [a-zA-Z0-9]와 의미가 동일합니다 |
\W | 문자 또는 숫자가 아닌 문자를 의미합니다. [^a-zA-Z0-9]와 의미가 동일합니다. |
정규표현식 모듈 함수
모듈 함수 | 설명 |
re.compile() | 정규표현식을 컴파일하는 함수. 다시 말해, 파이썬에게 전해주는 역할 찾고자 하는 패턴이 빈번한 경우에는 미리 컴파일해놓고 사용하면 속도와 편의성면에서 유리 |
re.search() | 문자열 전체에 대해서 정규표현식과 매치되는지를 검색 |
re.match() | 문자열의 처음이 정규표현식과 매치되는지를 검색 |
re.split() | 정규 표현식을 기준으로 문자열을 분리하여 리스트로 리턴 |
re.findall() | 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열을 찾아서 리스트로 리턴 만약, 매치되는 문자열이 없다면 빈 리스트가 리턴 |
re.finditer() | 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열에 대한 이터레이터 객체를 리턴 |
re.sub() | 문자열에서 정규 표현식과 일치하는 부분에 대해서 다른 문자열로 대체 |
예시)
- re.split()
# 공백 기준 분리
text = "사과 딸기 수박 메론 바나나"
re.split(" ", text)
# ['사과', '딸기', '수박', '메론', '바나나']
- re.findall()
text = """이름 : 김철수
전화번호 : 010 - 1234 - 1234
나이 : 30
성별 : 남"""
re.findall("\\d+", text) # 문자열 중 숫자가 최소 1개 이상인 경우
# ['010', '1234', '1234', '30']
- re.sub()
text = "This is Lab Semina!! I'm 25 years old."
preprocessed_text = re.sub('[^a-zA-Z]', ' ', text) # 영어 제외하고 다 " " 로 대체
print(preprocessed_text)
# "This is Lab Semina I m years old "
- Text Cleaning 예시
import re
def clean_text(texts):
corpus = []
for i in range(0, len(texts)):
review = re.sub(r'[@%\\\\*=()/~#&\\+á?\\xc3\\xa1\\-\\|\\.\\:\\;\\!\\-\\,\\_\\~\\$\\'\\"]', '',str(texts[i])) # 특수문자 제거
review = re.sub(r'\\d+','', review)# 숫자 제거
review = review.lower() #소문자 변환
review = re.sub(r'\\s+', ' ', review) # extra 띄어쓰기 제거
review = re.sub(r'<[^>]+>','',review) #html 태그 제거 , '<h1>hello!</h1>'
review = re.sub(r'\\s+', '', review) # 띄어쓰기 제거
review = re.sub(r"^\\s+", '', review) # 앞 space 제거
review = re.sub(r'\\s+$', '', review) # 뒤 space 제거
corpus.append(review)
return corpus
1-4) 띄어쓰기 교정
자연어처리에서의 띄어쓰기
- 자연어처리에서는 텍스트를 “토큰" 단위로 구분하여 다룬다 (*토큰 설명은 뒤에 나옴)
- 쉽게 토크나이징 하는 방법은 띄어 쓴 단어를 구분하는 것.
- 한국어의 경우, 띄어쓰기가 텍스트의 의미를 구분하는데 큰 영향을 미친다.
- e.g. 아버지 가방에 들어가신다 vs 아버지가 방에 들어가신다.
규칙기반 띄어쓰기 교정기법
: 형태소 분석기를 사용하는 규칙기반의 분석적인 방법
장점
- 특정한 상황 규칙에서는 정확한 답변
단점
- 모든 상황에 적용하려면 모든 규칙을 모두 손수 제작해야함
- 분석과정이 복잡하고 어휘지식 구축관리에 비용이 큼 → 시스템 유지보수가 어렵다.
예시) 어절 블록 양방향 알고리즘
correction rule : 들어 가셨다.
(1) 어절 블록 인식 e.g. 들어가셨다
(2) 어절 블록 내의 어절 인식 e.g. 들어 / 가셨다
(3) 어절 인식 오류 교정 e.g. 들어 가셨다
한계)
- 두 correction rule 중에 뭐가 맞는지 판단할 수 없음.
통계확률 기반 띄어쓰기 교정 기법
장점
- 자동 추출된 음절 n-gram 정보를 기반으로 기 계적인 계산 과정을 거쳐 띄어쓰기 오류를 교정하므로 구현이 더 용이
- 어휘 지식 구축관리 및 미 등록어에 대해서도 견고한 분석 가능
단점
- 한국어의 경우, 신뢰할만한 n-gram 정보를 얻기 위 해 띄어쓰기가 올바른 대용량의 학습 데이터, 즉 학습 말뭉치를 구하기 어려움
→ 세계 비주류 언어라면 데이터 부족의 문제가 큼
예시)
한국어 패키지 예시 - PyKoSpacing
Convolution based Neural N-gram Detector(NND) model → 확률기반 띄어쓰기 교정 기법 포함
! pip install git+https://github.com/haven-jeon/PyKoSpacing.git
from pykospacing import Spacing
spacing = Spacing()
print(spacing("아버지가방에들어가신다."))
# 아버지가 방에 들어가신다.
1-5) 맞춤법 교정
맞춤법 교정
정확한 의미전송 및 정보교환을 위해 반드시 필요 → 의미혼용의 방지 및 정보전달의 실패를 방지
- 텍스트 내 오류 감지 → 오류 수정
철자 오류의 종류
- 삽입(Insertion) : “the”를 “ther”처럼 추가적으로 문자를 입력하는 오류
- 생략(Deletion) : “the”를 “th”처럼 본래 있어야 하는 문자를 생략하는 오류
- 대체(Substitution) : “the”를 “thw”처럼 본래 넣어야 할 문자 대신 타 문자를 대입하는 오류
- 순열(Transposition) : “the”를 “hte”처럼 철자 순서를 뒤바뀌어져 있는 오류
규칙기반 맞춤법 교정 기법
→ 형태소 분석을 통해 맞춤법 교정을 진행함
통계, 확률 기반 맞춤법 교정 기법
- 가장 간단한 모델 : Bayesian inference model
- e.g. “acress”
한국에 패키지 예시 - https://github.com/ssut/py-hanspell
- py-hanspell : 네이버 맞춤법 검사기와 동일
from hanspell import spell_checker
result = spell_checker.check(u'안녕허세요. 저는 한국인입니다. 이문장은 한글로 작성됬습니다.')
result.as_dict() # dict로 출력
# {'checked': '안녕하세요. 저는 한국인입니다. 이 문장은 한글로 작성됐습니다.',
# 'errors': 3,
# 'original': '안녕허세요. 저는 한국인입니다. 이문장은 한글로 작성됬습니다.',
# 'result': True,
# 'time': 0.2873954772949219,
# 'words': OrderedDict([('안녕하세요.', 1),
# ('저는', 0),
# ('한국인입니다.', 0),
# ('이', 2),
# ('문장은', 2),
# ('한글로', 0),
# ('작성됐습니다.', 1)])}
1-6) Stemming/Lemmatization
(1) 어간 추출(Stemming)
- 단어를 축약형으로 바꿔줌→ 검색엔진이나 tf-idf 등 빈도수 계산 할 때 도움이 많이 돼서 사용한다!
- → 단어의 다양성을 축소하고 싶을 때 (정규화) 시 이용
from nltk import PorterStemmer, LancasterStemmer, word_tokenize
porter = PorterStemmer()
print(porter.stem('plays'),porter.stem('played'),porter.stem('playing'))
print(porter.stem('laughs'),porter.stem('laughed'),porter.stem('laughing'))
print(porter.stem('happier'),porter.stem('happiest'))
print(porter.stem('the going'))
# play play play
# laugh laugh laugh
# happier happiest
# the go
(2) 표제어 추출(Lemmatization)
- 품사정보가 보존된 형태의 기본형으로 변환한다. → 그 단어가 문장 속에서 어떤 품사인지 판단
from nltk import PorterStemmer, WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
print(lemmatizer.lemmatize('flying', pos='v')) # fly
print(lemmatizer.lemmatize('flies', pos='n')) # fly
Stemming vs Lemmatization
2. 어휘분석 (Lexical Analysis)
0) 어휘분석의 종류
- 포스 태깅(Part of Speech): 단어의 품사 정보를 결정하는 절차
- 개체명 인식(Named Entity Recognition) : 인명, 지명 등의 고유명사를 분류하는 방법론
- 상호참조(Co-Reference) : 선행단어(구)를 현재 단어(구)와 비교해 같은 개체인지 결정하는 문제
- 의존관계분석(Basic Dependencies) : 성분에 따라 문장 구조를 정의하는 구구조 문법과 달리, 단어와 다른 단어가 가지는 의존관계를 중시해 문장 구조를 분석하는 방법
1) 어휘분석 절차
1) 문장 분리 (Sentence Splitting)
- 말뭉치(corpus)를 문장 단위로 끊어서 입력해야 함
- 보통 마침표, 물음표, 느낌표 등으로 문장 분리 가능
- → 하지만 이가 없는 경우 어떤 방식으로 문장 분리?
문장 분리 방법
(1) 형태소 분석으로 종결어미를 구분하는 경우
(2) 문장의 CRF(Conditional Random Field) 결과로 판단하는 방법
한국어 문장 분리 예시 - kss
- 패턴 기반의 문장 분리기
- 종결형에 사용되는 음절: 다/요/.!?
- (1)종결형에 사용되는 음절을 골라내어 이전/이후 음절을 매칭하여 문장을 구분
# ! pip install kss
import kss
s = "제 이름은 김수완입니다 저는 지금 랩세미나를 준비중이에요 너무 졸려요"
for sent in kss.split_sentences(s):
print(sent)
#제 이름은 김수완입니다
#저는 지금 랩세미나를 준비중이에요
#너무 졸려요
2) 토큰화(Tokenization)
- 토큰(Token) : 문법적으로 더 이상 나눌 수 없는 언어 요소로 기본적으로 띄어쓰기를 기준으로 함
- 토크나이징(Tokenizing) : 문서나 문장을 분석하기 쉽도록 토큰 단위로 나눠주는 과정
영어 vs 한국어
- 영어: 대부분 공백으로 토큰을 나눌 수 있음
- 한국어 : 한국어는 조사가 존재하여 띄어쓰기 단위로 나누기만 하면 X → 조사를 분리해줘야함
- → 형태소 분석을 실시!
3) 형태소 분석 (Morphological Analysis)
형태소란?
- 자립 형태소
- 접사, 어미, 조사와 상관없이 자립하여 사용할 수 있는 형태소
- 그 자체로 단어
- 체언(명사, 대명사, 수사), 수식언(관형사, 부사), 감탄사 등
- 의존 형태소
- 다른 형태소와 결합하여 사용되는 형태소
- 접사, 어미, 조사, 어간 등
형태소 분석 의미
- 최소한의 의미를 갖는 단위인 형태소를 사용해 단어가 어떻게 형성되는지에 대해 자연어의 제약 조건과 문법 규칙에 맞춰 분석하는 것
형태소 분석 절차
(1) 단어에서 최소 의미를 포함 하는 형태소를 분리
(2) 형태론적 변형이 일어난 형태소의 원형 찾기
(3) 단어와 사전들 사이의 결합 조건에 따라 옳은 분석 후보를 선택
e.g. “나는" 의 의미
나는 = 나(대명사) + 는(조사)
나는 = 날(동사) + 는(어미)
→ 제대로된 형태소 분석을 하기 위해서는 품사 태깅을 활용해야함
한국어 형태소 분석 - KoNLPy
import konlpy.tag
okt = konlpy.tag.Okt()
print('OKT 형태소 분석 :',okt.morphs("다음 주에 스키장을 갑니다."))
# OKT 형태소 분석 : ['다음', '주', '에', '스키장', '을', '갑니다', '.']
- konlpy 안에 있는 다양한 오픈 라이브러리마다 각자 기준이 조금씩 다르며 학습 속도도 다르다
영어 형태소 분석
- 영어의 최소한의 의미를 갖는 기본 단위 = 단어
- 그러므로 또 다른 분석 없이, Stemming/Lemmatization 을 사용하여 형태소 분석
예시) nltk
# 단어 단위로 나누기
from nltk.tokenize import word_tokenize
print(word_tokenize("I am a student, I'm 25 years old."))
#['I', 'am', 'a', 'student', ',', 'I', "'m", '25', 'years', 'old', '.']
4) 품사 태깅 (Pos-Tag)
품사태깅(Pos Tagging) 이란?
문서 또는 문장을 이루고 있는 각 단어에 정확한 하나의 품사를 부여하는 것
역할
- 형태론적 중의성 해결
- 나(대명사) + 는 (조사)
- 날(동사) + 는 (어미)
- e.g. 위의 예시 나는
영어 품사 태깅
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
text = "My name is suwan. I'm 25 years old."
tokenized_sentence = word_tokenize(text)
print('단어 토큰화 :',tokenized_sentence)
print('품사 태깅 :',pos_tag(tokenized_sentence))
# 품사 태깅 : [('My', 'PRP'), ('name', 'NN'), ('is', 'VBZ'), ('suwan', 'JJ'),
#('.', '.'), ('I', 'PRP'), ("'m", 'VBP'), ('25', 'CD'), ('years', 'NNS'),
#('old', 'JJ')]
한국어 품사 태깅
import konlpy.tag
okt = konlpy.tag.Okt()
print('OKT 형태소 분석 :',okt.morphs("다음 주에 스키장을 갑니다."))
print('OKT 품사 태깅 :',okt.pos("다음 주에 스키장을 갑니다."))
#OKT 형태소 분석 : ['다음', '주', '에', '스키장', '을', '갑니다', '.']
#OKT 품사 태깅 : [('다음', 'Noun'), ('주', 'Noun'), ('에', 'Josa'),
# ('스키장', 'Noun'), ('을', 'Josa'), ('갑니다', 'Verb'), ('.', 'Punctuation')]
Reference
https://ratsgo.github.io/natural%20language%20processing/2017/03/22/lexicon/
https://bab2min.tistory.com/669
https://ebbnflow.tistory.com/246
https://marketingscribbler.tistory.com/4
자연어처리 바이블