- 자연어는 일상생활에서 사용하는 언어
- 컴퓨터가 인간의 언어를 학습하는 방법에는 무엇이 있을까
텍스트분류, 감성분석, 문서요약, 번역, 질의 응답, 음성인식, 챗봇으로 응용이 될 수 있다.
강의를 들은 사람들이라고 가정하고 시작하겠습니다.
split()의 기능중.. 생소했던 부분
s = 'No pain no gain'
s.split().index('gain')
>>> 3
s.split()[2][::-1]
>>> on
정규표현식
- 정규표현식은 특정문자들을 편리하게 지정하고 추가, 삭제가 가능합니다.
- 전처리에서 아주 많이 사용됨.
- re패키치(regular expression)
문법
. | 앞의 문자 1개를 표현 |
? | 문자 한개를 표현하나 존재할 수도, 존재하지 않을 수도 있음(0개 또는 1개) |
* | 앞의 문자가 0개 이상 |
+ | 앞의 문자가 최소 1개 이상 |
^ | 뒤의 문자로 문자열이 시작 |
\$ | 앞의 문자로 문자열이 끝남 |
\{n\} | n번만큼 반복 |
\{n1, n2\} | n1 이상, n2 이하만큼 반복, n2를 지정하지 않으면 n1 이상만 반복 |
\[ abc \] | 안에 문자들 중 한 개의 문자와 매치, a-z처럼 범위도 지정 가능 |
\[ ^a \] | 해당 문자를 제외하고 매치 |
a|b | a 또는 b를 나타냄 |
자주 사용되는 역슬래시응 이용한 문자규칙
\\ | 역슬래시 자체를 의미 |
\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]와 동일 |
match
match(pattern, string) : 문자열의 시작부분부터 매칭이 되는지 검색
import re
check = 'ab.'
print(re.match(check, 'abc'))
>>> #ab다음에 하나만 올 수 있다. 매치됨
print(re.match(check, 'c'))
>>> #매칭안됨
print(re.match(check, 'ab'))
>>> #매칭안됨
compile사용시 주의 사항
★ r = re.compile('ab.')
r.match(check)
-> r을 지정해줘야 한다.
많이 실수하고 놓치는 부분!!!
match와 search
어려웠던 점
match(pattern, string) : 문자열의 시작부분부터 매칭이 되는지 검색
search(pattern, string) : 문자열 전체를 검사
###match와 search의 차이
##match
print(re.match('a', 'ab'))
>>> 매칭 성공
print(re.match('b', 'ab'))
>>> 매칭 실패
##search
print(re.search('a', 'ab'))
>>> 매칭 성공
print(re.search('b', 'ab'))
>>> 매칭 성공
match는 시작부분부터 pattern과 비교를 시작
search는 문장내에서 pattern과 맞는 부분을 다 찾아냄.
split
split사용시 생소한 예시
r = re.compile('[1-9]')
print(r.split('s1abc 2v3s 4sss 5a'))
>>> ['s', 'abc ', 'v', 's ', 'sss ', 'a']
##예제
r = re.compile('[ㄱ-ㅎ]')
print(r.split('123ㄱ456ㅈ45648ㄴ84ㅇ'))
>>> ?
sub
정규표현식과 일치하는 부분을 다른 문자열로 교체
토큰화(Tokenization)
문장을 최소 의미 단위로 잘라서 컴퓨터가 인식하도록 돕는 방법이다.
전처리의 필수적인 작업중 하나!!
조심해야함.
고려해야 할 사항
1. 특수문자를 그냥 제외하면 안됨.
그것만의 의미가 있을수도..
- 예) 마침표는 문장의 결계를 알 수 있게함.
- 예) m.p.h나 Ph.D나 AT&T 같은 경우 : 단어자체에 의미가 있음.
2. 표준 토큰화
Penn Treebank Tokenization의 규칙
1.) 하이푼으로 구성된 단어는 하나로 유지한다. (-)
2.) doesn't와 같이 아포스트로피로 '접어'가 함께하는 단어는 분리해준다. -> "does", "n't"
단어 토큰화
- "I am student" -> "I", "am", "student" :
>>> nltk패키지의 'punkt'다운로드 -> word_tokenize
import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize
sentence = "Time is gold"
tokens = word_tokenize(sentence)
tokens
>>> ['Time', 'is', 'gold']
문장 토큰화
- 문장 토큰화는 주로 줄바꿈 문자('\n')을 기준으로 문장을 분리
sent_tokenize(sentences)
sentences = 'The world is a beautiful book.\n But of little use to him who cannot read it'
from nltk.tokenize import sent_tokenize
tokens = sent_tokenize(sentences)
tokens
>>> ['The world is a beautiful book.',
>>> 'But of little use to him who cannot read it']
토크나이저
- WhiteSpaceTokenizer: 공백을 기준으로 토큰화
- WordPunktTokenizer: 텍스트를 알파벳 문자, 숫자, ㅇ라파벳 이외의 문자 리스트로 토큰화
- MWETokenizer: MWE는 Multi-Word Expression의 약자로 'republic of korea'와 같이 여러 단어로 이뤄진 특정 그룹을 한 개체로 취급
- TweetTokenizer: 트위터에서 사용되는 문장의 토큰화를 위해서 만들어졌으며, 문장 속 감성의 표현과 감정을 다룸
n-gram 추출
n-gram은 word2vec에서 예측모델에 많이 쓰이는 개념입니다.
훗날 window라는 개념으로 쓰입니다.
from nltk import ngrams
sentence = 'There is no rayal road to learning'
bigram = list(ngrams(sentence.split(), 2))
print(bigram)
>>>[('There', 'is'), ('is', 'no'), ('no', 'rayal'), ('rayal', 'road'), ('road', 'to'), ('to', 'learning')]
TextBlob을 이용하는 방법
from textblob import TextBlob
blob = TextBlob(sentence)
blob.ngrams(n=2)
>>[WordList(['There', 'is']),
>> WordList(['is', 'no']),
>> WordList(['no', 'rayal']),
>> WordList(['rayal', 'road']),
>> WordList(['road', 'to']),
>> WordList(['to', 'learning'])]
POS태킹(품사태킹)
불용어제거 - 영어의 전치사(on, in), 한국어의 조사(을, 를) 등은 분석에 필요하지 않은단어들을 빼주기
import nltk
nltk.download('punkt')
from nltk import word_tokenize
nltk.download('averaged_perceptron_tagger')
nltk.pos_tag(word_tokenize("A rolling stone gathers no moss"))
>> [('A', 'DT'),
>> ('rolling', 'VBG'),
>> ('stone', 'NN'),
>> ('gathers', 'NNS'),
>> ('no', 'DT'),
>> ('moss', 'NN')]
철자교정(Speller)
- 텍스트에 오탈자가 존재하는 경우가 있음
- 예를 들어, 단어 'apple'을 'aplpe'과 같이 철자 순서가 바뀌거나 spple 같이 철자가 틀릴 수 있음
- 사람이 적절한 추정을 통해 이해하는데는 문제가 없지만, 컴퓨터는 이러한 단어를 그대로 받아들여 처리가 필요
- 철자 교정 알고리즘은 이미 개발되어 워드 프로세서나 다양한 서비스에서 많이 적용됨
spell = Speller('en')
print(spell('people'))
print(spell('peope'))
print(spell('peopae'))
어간추출(Stemming) 추출 / 표제어(Lemmatization) 추출
어간 : application -> applic 같이 단어의 의미를 담고 있는 단어의 핵심 부분.
표제오 : 삼인칭 단수 동사에는 -s/-es가 붙는다. 이럴때, 동사원형으로 바꿔준다.
단어중의성(Lexical Ambiguity)
import nltk
from nltk.wsd import lesk
s = "I saw bats."
print(word_tokenize(s))
>> ['I', 'saw', 'bats', '.']
print(lesk(word_tokenize(s), 'saw'))
>> Synset('saw.v.01')
print(lesk(word_tokenize(s), 'bats'))
>> Synset('squash_racket.n.01')
##나는 야구방망이를 보았다.
Bag Of Words(BOW)
- 단어집합
from sklearn.feature_extraction.text import CountVectorizer
corpus = ["Think like a man of actioin and act like man of thought."]
vector = CountVectorizer()
bow = vector.fit_transform(corpus)
print(bow.toarray()) #인덱스에서 차지하고 있는 부분
>>> [[1 1 1 2 2 2 1 1]]
print(vector.vocabulary_) #인덱스
>>> {'think': 6, 'like': 3, 'man': 4, 'of': 5, 'actioin': 1, 'and': 2, 'act': 0, 'thought': 7}
문서 단어 행렬(DTM)
- Document-Term Matrix
문서에 등장하는 여러 단어들의 빈도를 행렬로 표현
한 문서에 대한 BoW를 하나의 행렬로 표현한것
from sklearn.feature_extraction.text import CountVectorizer
corpus = ["Think like a man of action and act like man of thought.",
"Try not to become a man of success but rather try to become a man of value.",
"Give me liberty, of give me death"]
vector = CountVectorizer(stop_words='english')
bow = vector.fit_transform(corpus)
print(bow.toarray())
>>>[[1 1 0 0 2 2 0 1 1 0 0]
>>> [0 0 0 0 0 2 1 0 0 2 1]
>>> [0 0 1 1 0 0 0 0 0 0 0]]
print(vector.vocabulary_)
>>> {'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'success': 6, 'value': 10, 'liberty': 3, 'death': 2}
어휘 빈도-문서 역빈도(TF-IDF) 분석
Term Frequency - Inverse Document Frequency
- 특정문서에 집중적으로 등장할 때, 해단 단어가 문서의 주제를 잘 담고 있는 핵심어라고 가정
-> 단순히 빈도수가 높은 단어가 핵심어가 아님!!
주로
1. 문서의 유사도를 구하는 작업
2. 검색 시스템에서 검색 결과의 중요도를 정하는 작업
3. 문서 내에서 특정 단어의 중요도를 구하는 작업
- 특정 문서에서만!! 등장하는 단어들을 해당 문서의 핵심어로 간주!!
계산 : 어휘빈도(TF) X 역문서 빈도(IDF)
-> 어휘 빈도 : 특정 문서(x)에서 특정 단어(y)의 등장 횟수
$$tf_{x,y}$$
-> 역문서 빈도 : 다른문서에 등장하지 않는 단어 빈도
$$ log(N/df_{x}) $$
=> 특정 문서(x)에 특정 단어가 등장한 문서의 수, Not 단어의 수
$$df_{y}$$
=> 위에것에 반비례하고, 전체 문서의 수를 곱하고, log를 취함.
예를들어 생각해보자
여러문서에 한가지 단어의 TF-IDF분석
문서A,B,C가 있다. '박지훈'이란 단어가 각각의 문서에 2번, 7번, 0번 등장했다고 가정하자.
- 어휘빈도(TF)
$$tf_{A,박지훈} = 2 $$
$$tf_{B,박지훈} = 7 $$
$$tf_{C,박지훈} = 0 $$
- $$df_{박지훈} = 2 $$
# B,C문서 총 2문서에만 '박지훈'이 등장하기 때문
- 역문서빈도(IDF)
$$ log(N/df_{x}) = log(frac_{3}{2}) = $$
$$TF-IDF(A, '박지훈') = 2 \times \log{\frac{3}{2}}$$
$$TF-IDF(B, '박지훈') = 7 \times \log{\frac{3}{2}}$$
$$TF-IDF(C, '박지훈') = 0 \times \log{\frac{3}{2}}$$
문서도 여러개, 단어도 여러개
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ["Think like a man of action and act like man of thought.", #문서1
"Try not to become a man of success but rather try to become a man of value.", #문서2
"Give me liberty, of give me death"] #문서3
tfidf = TfidfVectorizer(stop_words='english').fit(corpus)
print(tfidf.vocabulary_)
>>> {'think': 7, 'like': 4, 'man': 5, 'action': 1, 'act': 0, 'thought': 8, 'try': 9, 'success': 6, 'value': 10, 'liberty': 3, 'death': 2}
#BoW생성
print(pd.DataFrame(tfidf.transform(corpus).toarray()))
0 1 2 3 4 5 6
0 0.311383 0.311383 0.000000 0.000000 0.622766 0.473630 0.000000
1 0.000000 0.000000 0.000000 0.000000 0.000000 0.527533 0.346821
2 0.000000 0.000000 0.707107 0.707107 0.000000 0.000000 0.000000
7 8 9 10
0 0.311383 0.311383 0.000000 0.000000
1 0.000000 0.000000 0.693642 0.346821
2 0.000000 0.000000 0.000000 0.000000
또한 다음과 같이 문장간의 유사도 또한 구할 수 있다.
from sklearn.metrics.pairwise import linear_kernel
tfidf_matrix = tfidf.fit_transform(corpus)
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
cosine_sim
>>>array([[1. , 0.24985513, 0. ],
>>> [0.24985513, 1. , 0. ],
>>> [0. , 0. , 1. ]])