2021. 9. 27. 13:15ㆍAI/Machine learning
- 목차
Stanford university 강의 자료
https://web.stanford.edu/~jurafsky/slp3/3.pdf
뉴욕대학교 강의 자료 : https://cs.nyu.edu/courses/spring17/CSCI-UA.0480-009/lecture3-and-half-n-grams.pdf
이해를 돕는 추천 자료 : https://www.edwith.org/deepnlp/lecture/29214/
조경현 교수님이 n-gram 언어 모델에 대해서 강의하시는 파트입니다.
용어정리
자소 (= 음소) = phoneme
음소를 표시하는 최소의 변별적 단위
초/중/종성
예를 들어 음소 /p/를 표시하는 데 사용되는 pin의 p, hopping의 pp, hiccough의 gh는 모두 한 자소의 구성원
음소
더 이상 작게 나눌 수 없는 음운론상의 최소 단위.
하나 이상의 음소가 모여서 음절을 이룬다.
즉, 하나의 '소리'로 인식되는 최소 단위
ex. '강', ㄱ, ㅏ, ㅅ,...
음절
한번에 낼 수 있는 소리 마디를 나타내는 문법 단위
하나의 종합된 음의 느낌을 주는 말소리의 단위
음절은 소리의 단위
형태소는 의미 단위
모음은 단독으로 한 음절이 되기도 한다
ex. 아침의 '아', '침'
ex. 좋다
운소
분절음: 소리의 경계를 나눌 수 있음
비분절음: .. 나눌수 없음
단독으로 사용 되지 않음
운소는 비분절음운
음운
단어의 뜻의 최소 단위
음소와 운소를 포함한 개념
단어의 의미를 변별하는 소리의 최소단위.
뜻이 있는 소리의 기본 단위
초성, 중성, 종성
초성
ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ (19개)
중성
ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ (21개)
종성(종성이 없는 경우 존재)
ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ (27개)
위 초성, 중성, 종성을 조합하여 만들 수 있는 글자: 19 * 21 * (27 + 1) = 11172
이 11172 개의 한글 음절들은 위키피디아 홈페이지에서 확인 가능
현대 한글 음절 목록
가각갂...
언어모델 (Language Model)
단어 sequence(or 문장)에 확률을 할당하는 모델
= 단어 sequence에 확률을 할당하는 일을 수행하는 모델
-> 가장 자연스러운 단어 sequence를 찾아내는 모델
예측 접근
1) "이전 단어들"에 대한 "다음 단어" 예측
2) 양쪽의 단어들로 가운데 단어를 예측
BERT
언어 모델 생성 방법
1) 통계적 방법
2) 인공 신경망 사용 방법
GPT, BERT
어떤 문장이 있을때 기계가
이 문장이 적절한지, 부적절한지 판단할 수 있다면,
기계가 자연어 처리를 잘 하는 것
즉, 적절한 문장을 판단 (생성)하는 모델
Language Modeling
주어진 단어들로 아직 모르는 단어를 예측하는 작업
이전 단어들로부터 다음 단어를 예측하는 일이 언어 모델링
Standford University에서는
언어모델을 문법(grammar)라고 비유하기도 함
언어 모델의 조합이 적절한지 여부가 마치 문법이 하는 일과 유사
전통적인 언어 모델(SLM: Statistical Language Model)
통계에 기반한 언어 모델은 실제 사용되는 자연어를 근사하기에는 한계가 있음
요즘은 인공 신경망을 사용해 이 한계를 뛰어넘고 있음
그럼에도, 여전히 통계 기반 언어 모델에서 배우게 될
N-gram은 자연어 처리 분야에서 활발히 활용되고 있음 (Word2Vec)
통계 기반 방법론에 대한 이해는 언어 모델에 대한 전체적인 시야를 갖는데 도움을 줌
단어 sequence의 확률 할당
자연어 처리에서 단어 sequence에 확률을 할당
P: prob.
Machine Translation (기계 번역)
P("나는 버스를 탔다") > P("나는 버스를 태운다")
언어 모델은 두 문장을 비교하여 좌측의 문장의 확률이 더 높다고 판단
Spell Correction (오타 교정)
선생님이 교실로 부리나케
P("달려갔다") > P("잘려갔다")
언어 모델은 두 문장을 비교하여 좌측의 문장의 확률이 더 높다고 판단
speech recognition (음성 인식)
P("나는 메롱을 먹는다") < P("나는 메론을 먹는다")
언어 모델은 우측 문장의 확률이 높다고 판단.
즉, 확률을 통해 보다 적절한 문장을 판단.
주어진 이전 단어들로부터 다음 단어 예측
언어모델
단어 sequence에 확률을 할당하는 모델
가장 보편적인 확률할당 방법
이전 단어(들)에 다음 단어에 대한 확률 할당
이를 조건부 확률로 표현
단어 시퀀스의 확률
w: 단어
W: 단어 sequence
n개의 단어가 등장하는 단어 sequence W의 확률
P(W) = P(w1,w2,w3,...,wn)
다음 단어 등장 확률
P(wn|w1,...wn-1)
| 은 conditional probability
conditional probability
P(E,F) = F(E|F)P(F)
P(E,F)는 E와 F의 교집합의 확률
P(E,F) = P(E|F)P(F)
E와 F가 서로 독립적으로 발생하는 확률이라면,
P(E,F) = P(E)P(F)
P(E|F) = P(E,F)/P(F)
= P(E)
P(w5|w1,w2,w3,w4)
전체 단어 W의 확률은 모든 단어가 예측된 후 알 수 있음
P(W)=P(w1,w2,w3,w4,w5,...wn) = ∏i=1~n P(wn|w1,...,wn−1)
언어 모델의 간단한 직관
비행기를 타려고 공항에 갔는데 지각을 하는 바람에 비행기를 [?]
'비행기를' 다음에 어떤 단어가 오게될지 ?
'놓쳤다' 라고 예상할 수 있음
기계에게 위 문장을 주고,
앞에 어떤 단어들이 나왔는지 고려하고 후보 중 확률이 가장 높은 것을 선택
검색 엔진에서의 언어 모델 예
딥 러닝을 이용한 ... <-- 미리 예측해서 아래 N-best를 보여줌
통계적 언어 모델 (SLM: Statistical Language Model)
p(B|A) = P(A,B)/P(A)
P(A,B) = P(A) P(B|A)
조건부 확률의 연쇄 법칙(chain rule)
P(A,B,C,D) = P(A) P(B|A) P(C|A,B) P(D|A,B,C)
n개
P(x1,x2,x3...xn) =
P(x1)P(x2|x1)P(x3|x1,x2)...P(xn|x1...xn−1)
문장에 대한 확률
P(An adorable little boy is spreading smiles)
각 단어는 문맥이라는 관계로 인해 이전 단어의 영향을 받아 나온 단어
문장의 확률은 각 단어들이 이전 단어가 주어졌을 때
다음 단어로 등장할 확률의 곱
P(w1,w2,w3,w4,w5,...wn) = ∏ n=1~n P(wn|w1,...,wn−1)
위의 문장에 해당 식을 적용
P(An adorable little boy is spreading smiles)=
P(An) × P(adorable|An) × P(little|An adorable) ×
P(boy|An adorable little) ×
P(is|An adorable little boy) ×
P(spreading|An adorable little boy is) ×
P(smiles|An adorable little boy is spreading)
문장의 확률을 구하기 위해서 다음 단어에 대한 예측 확률을 모두 곱
카운트 기반의 접근
SLM은 이전 단어로부터 다음 단어에 대해
카운트에 기반하여 확률을 계산
An adorable little boy
이후, is가 나올 확률인 P(is|An adorable little boy)
P(is|An adorable little boy) =
count(An adorable little boy is) /
count(An adorable little boy )
예를 들어 기계가 학습한 코퍼스 데이터에서 An adorable little boy가 100번 등장
그 다음에 is가 등장한 경우는 30번
30/100 = 0.3 (즉, 30%)
카운트 기반 접근의 한계 - 희소 문제(Sparsity Problem)
An adorable little boy가 나왔을 때 is가 나올 확률
확률 분포
기계에게 많은 코퍼스를 훈련시켜서 언어 모델을 통해 현실에서의 확률 분포를 근사하는 것이 언어 모델의 목표
기계가 훈련하는 데이터는 정말 방대한 양이 필요
sparsity problem (희소 문제)
P(is|An adorable little boy)
An adorable little boy is라는 단어 시퀀스가 없었다면
이 단어 시퀀스에 대한 확률은 0
또는 An adorable little boy라는 단어 시퀀스가 없었다면 분모가 0이 되어 확률은 정의되지 않음
이와 같이 충분한 데이터를 관측하지 못하여 언어를 정확히 모델링하지 못하는 문제를 희소 문제(sparsity problem)
위 문제를 완화하는 방법
일반화(generalization) 기법
n-gram
스무딩
백오프
하지만 희소 문제에 대한 근본적인 해결책은 되지 못함
러한 한계로 인해 언어 모델의 트렌드는 통계적 언어 모델에서 인공 신경망 언어 모델로 넘어가게 됨
N-gram 언어 모델(N-gram Language Model)
count 통계에 기반한 SLM의 일종
단, 이전의 모든 단어를 고려하는 것이 아니라 일부 단어만 고려하는 접근 방법을 사용
N은 몇 개의 단어까지만 고려할 것이냐는 의미
Copus에서 count하지 못하는 경우의 감소
SLM의 한계
copus에 확률을 계산하고 싶은 문장이나 단어가 없을 수 있다는 점
(sparse problem)
다음과 같이 참고하는 단어를 줄이면 count를 할 수 있을 가능성이 높아짐
P(is|An adorable little boy)≈ P(is|boy)
지나친 일반화로 느껴진다면 2-gram으로,
P(is|An adorable little boy)≈ P(is|little boy)
N-gram
n-gram은 n개의 연속적인 단어 나열
코퍼스에서 n개의 단어 뭉치 단위를 하나의 토큰으로 간주
An adorable little boy is spreading smiles
unigrams : an, adorable, little, boy, is, spreading, smiles
bigrams : an adorable, adorable little, little boy, boy is, is spreading, spreading smiles
trigrams : an adorable little, adorable little boy, little boy is, boy is spreading, is spreading smiles
4-grams : an adorable little boy, adorable little boy is, little boy is spreading, boy is spreading smiles
n-gram을 통한 언어 모델
다음에 나올 단어의 예측은 오직 n-1개의 단어에만 의존
4-gram의 경우
An adorable little boy is spreading
spreading 다음에 올 단어를 예측하는 것은 n-1에 해당되는 앞의 3개의 단어만을 고려
P(w|boy is spreading) =
count(boy is spreading w) /
count(boy is spreading)
copus에서
boy is spreading가 1,000번 등장
boy is spreading insults가 500번 등장
boy is spreading smiles가 200번 등장
boy is spreading 다음에
insults가 등장할 확률은 50%
smiles가 등장할 확률은 20%
P(insults|boy is spreading)=0.500
P(smiles|boy is spreading)=0.200
N-gram Language Model의 한계
An adorable little boy is spreading에서
4-gram에서도
an adorable little)'이라는 수식어를 제거하고, 반영하지 않았음
작고 사랑스러운 소년이' '모욕을 퍼트렸다'라는 부정적인 내용이 '웃음 지었다'라는 긍정적인 내용 대신 선택되었을까?
즉, 전체 문맥을 고려하지 않기에,
생뚱맞은 단어를 선택할 수 있음
n-gram 모델 한계
1) 희소 문제(Sparsity Problem)
n-gram 언어 모델도 여전히 희소 문제가 존재
2) n을 선택하는 trade-off 문제
n을 1보다는 2로 선택하는 것은 거의 대부분의 경우에서 언어 모델의 성능을 높일 수 있음
n을 크게 선택하면
희소 문제는 점점 심각해짐
모델 사이즈가 커짐
n을 작게 선택하면
정확도는 현실의 확률분포와 멀어짐
n은 최대 5를 넘게 잡아서는 안 된다고 권장
월스트리트 저널에서 3,800만 개의 단어 토큰에 대하여 n-gram 언어 모델을 학습
1,500만 개의 테스트 데이터에 대해서 테스트를 했을 때
- Unigram Bigram Trigram
Perplexity 962 170 109
perplexity
낮을수록 더 좋은 성능
적용 분야(Domain)에 맞는 코퍼스의 수집
어떤 어플리케이션인지에 따라서 특정 단어들의 확률 분포는 다름
마케팅 분야
마케팅 단어가 빈번하게 등장
의료 분야
의료 관련 단어가 당연히 빈번하게 등
언어 모델의 약점
훈련에 사용된 domain copus에 따라 성능이 달라짐
언어 모델에 사용하는 코퍼스를 해당 도메인의 코퍼스를 사용한다면 당연히 언어 모델이 제대로 된 언어 생성을 할 가능성이 높아짐
인공 신경망을 이용한 언어 모델(Neural Network Based Language Model)
N-gram Language Model의 한계점을 극복하기 위한 방법도 있으나,
분모, 분자에 숫자를 더해서 카운트했을 때 0이 되는 것을 방지하는 등
N-gram 언어 모델의 취약점을 완전히 해결하지는 못함
그래서
N-gram Language Model 보다 대체로 성능이 우수한 '인공 신경망을 이용한 언어 모델'이 많이 사용되고 있음
한국어에서의 언어 모델(Language Model for Korean Sentences)
한국어는 언어 모델로 다음 단어를 예측하기가 훨씬 까다로움
why?
한국어는 어순이 중요하지 않음
① 나는 운동을 합니다 체육관에서.
② 나는 체육관에서 운동을 합니다.
③ 체육관에서 운동을 합니다.
④ 나는 운동을 체육관에서 합니다.
교착어임
띄어쓰기 단위로 token화 하기 어려움
띄어쓰기 단위인 '어절'로 token 화 시,
발생 가능한 단어의 수가 굉장히 많음
왜?
조사 등이 있어서
'그녀'
그녀를
그녀의
그녀와
그녀로
그녀께서
그녀처럼
...
이런 다양한 경우가 존재함
굴절어인 영어에서는
'she' 하나로 처리될 것을 위와 같이 교착어인 한국어에서는 수 많은 경우들이 존재함
그래서 한국어에서는 토큰화를 통해 접사, 조사 등을
분리하는 것이 중요한 작업
접사, 조사 등이 '접합 - 교착' 되는 언어
영어인 굴절어는
context 별로 단어 형태가 달라짐
(뭔가 붙지 않음)
띄어쓰기가 안 지켜짐
띄어쓰기가 잘 안된 경우
이를 통해 훈련하면 언어 모델이 제대로 동작하지 않음
5) 퍼플렉서티(Perplexity)
언어모델의 성능 측정 방식
'헷갈리는'이라는 의미
즉, 낮은 수치일 수록 좋은 모델
extrinsic evaluation
외부 평가
부정확하나 어느정도 test data에 대해서는 빠르게 식으로 평가
모델 내에서 자신의 성능을 수치화하여 결과를 내놓는 내부평가에 해당하는 방법이 퍼플렉시티
언어 모델의 평가 방법 (evaluation metric): PPL
perplexity: PPL
PPL은 단어의 수로 정규화(normalization) 된 테스트 데이터에 대한 확률의 역수
PPL을 최소화
문장의 확률을 최대화
PPL(W) =
P(w1,w2,w3,...,wN)&(−1N) =
N sqrt(1/P(w1,w2,w3,...,wN))
분기 계수(Branching factor)
선택할 수 있는 가능한 경우의 수
PPL
특정 시점에서 평균적으로 몇 개의 선택지를 가지고 고민하고 있는지를 의미
ex.
언어 모델에 어떤 테스트 데이터을 주고 측정했더니 PPL이 10
해당 언어 모델은
테스트 데이터에 대해서 다음 단어를 예측하는 모든 시점(time-step)마다 평균적으로 10개의 단어를 가지고 고려
당연히 PPL이 더 낮은 언어 모델의 성능이 더 좋다
PPL(W) = P(w1,w2,w3,...,wN)^(1/N)
= ((1/10)^N)^(-1/N)
= (1/10)^(-1)
= 10
PPL의 값이 낮다는 것은 테스트 데이터 상에서 높은 정확도를 보인다는 것
사람이 직접 느끼기에 좋은 언어 모델이라는 것을 반드시 의미하진 않는다는 점
정량적으로 양이 많고, 또한 도메인에 알맞은 동일한 테스트 데이터를 사용해야 신뢰도가 높다
기존 언어 모델 Vs. 인공 신경망을 이용한 언어 모델.
페이스북 AI 연구팀이 평가한 PPL
Model PPL
Interpolated Kneser-Ney 5-gram 67.6
RNN-1024 + MaxEnt 9-gram 51.3
RNN-2048 + BlackOut sampling 68.3
Sparse Non-negative Matrix facto.. 52.9
LSTM-2048 43.7
2-layer LSTM-8192 30
Ours small (LSTM-2048) 43.9
Ours large (2-layer LSTM-2048) 39.8
Interpolated Kneser-Ney 5-gram이 N-gram 언어 모델
가장 PPL이 높음
Neural Network를 사용한 언어 모델들 대부분이 N-gram 언어 모델보다 더 높은 성능을 보이고 있음
https://towardsdatascience.com/perplexity-intuition-and-derivation-105dd481c8f3
https://www.slideshare.net/shkulathilake/nlpkashkevaluating-language-model
http://www.statmt.org/book/slides/07-language-models.pdf
https://github.com/kobikun/wiki/wiki/Language-Models
04. 카운트 기반의 단어 표현(Count based word Representation)
자연어 처리를 위해서는 문자를 숫자로 수치화 할 필요가 있음
1) 다양한 단어의 표현 방법
1) Local representation (국소 표현) = discrete 표현
해당 단어 그 자체만가지고 특정 값으로 mappping
2) Distributed Representation (분산 표현) = continuous 표현
해당 단어와 주변 단어를 참고하여 값으로 mapping
단어 표현의 category
word representation
+- local representation
| +- One-hot vector
| +- N-gram
| +- Count Based
| +- Bag of Words (DTM)
|
+- continuous representation
+- Prediction based -----+
| +- Word2Vec (FastText) |
+- Count based |
+- Full Document |
| +- LSA |
+- Windows |
+- Glove <-------+
One-hot vector
국소 표현
N-gram
국소 표현
Bag of Words(DTM)
국소 count 기반 표현
단어의 빈도수를 count하여 단어를 수치화하는 단어 표현 방법
DTM은 BoW의 확장
TF-IDF
이런 빈도수 기반의 단어 표현에 가중치를 부여하는 방법
Word2Vec
예측 기반
LSA
full document의 count 기반
Glove
windows의 count 기반
2) Bag of Words(BoW)
단어의 등장 순서를 고려하지 않는 빈도수 기반의 단어 표현 방법
BoW를 만드는 과정
각 단어에 고유한 정수 인덱스를 부여
각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 벡터를 만듦
ex.
정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다.
입력된 문서에 대해서 단어 집합 (vocaburary) 생성 w/ index
from konlpy.tag import Okt
import re
import collections
okt = Okt()
in_str = re.sub("(\.)","","정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다.")
tokens = okt.morphs(in_str) # tokenization
freqs = collections.Counter(tokens)
{'가': 2, '물가상승률': 2, '정부': 1, '발표': 1, '하는': 1, '과': 1, '소비자': 1, '느끼는': 1, '은': 1, '다르다': 1}
Bag of Words의 다른 예제들
BoW에 있어서 중요한 것은 단어의 등장 빈도
BoW는 각 단어가 등장한 횟수를 수치화하는 텍스트 표현 방법이기 때문에
단어의 등장 빈도를 통해 문서의 성격을 판단할때 사용됨
분류 문제나 문서 간 유사도를 구하는 문제에 주로 사용됨
CountVectorizer 클래스로 BoW 만들기
CountVectorizer
사이킷 런에서는 단어의 빈도를 Count하여 Vector로 만듦
from sklearn.feature_extraction.text import CountVectorizer
corpus = ['you know I want your love. because I love you.']
vector = CountVectorizer()
print(vector.fit_transform(corpus).toarray()) # 코퍼스로부터 각 단어의 빈도 수를 기록한다.
print(vector.vocabulary_)
[[1 1 2 1 2 1]]
{'you': 4, 'know': 1, 'want': 3, 'your': 5, 'love': 2, 'because': 0}
CounterVectorizer는 길이가 2 이상인 문자만 처리하여 'I'는 무시되었음
Cleaning 과정에서 (정제)
이런 짧은 문자를 제거하는 전처리를 수행하기도 함
CountVectorizer는 단지 띄어쓰기만을 기준으로 단어를 자르는 낮은 수준의 토큰화
-> 한국어에 CountVectorizer를 적용하면, 조사 등의 이유로 제대로 BoW가 만들어지지 않음
'정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다.'
'물가상승률' 이라는 단어를 인식하지 못함
이런 tokenization 시, '물가상승률과'와 '물가상승률은'은 서로 조사가 다르기에 다른 단어로 판단함
불용어(stopword)를 제거한 BoW 만들기
불용어는 자연어 처리에서 별로 의미를 갖지 않는 단어들
BoW를 사용한다는 것은 그 문서에서 각 단어가 얼마나 자주 등장했는지를 보겠다는 것
각 단어에 대해 빈도수를 수치화 하여 text 내 어떤 단어들이 중요한지에 대한 의미를 함축
즉, BoW 생성 시, 불용어의 제거는 자연어 처리의 정확도를 높이기 위해 필요한 전처리 기법
CounterVectorizer
영어의 경우, 불용어를 지정하면 불용어를 제외하고 BoW를 생성함
from sklearn.feature_extraction.text import CountVectorizer
text = ["Family is not an important thing. It's everything."]
vect = CountVectorizer(stop_words=["the", "a", "an", "is", "not"])
print(vect.fit_transform(text).toarray())
print(vect.vocabulary_)
[[1 1 1 1 1]]
{'family': 1, 'important': 2, 'thing': 4, 'it': 3, 'everything': 0}
CounterVectorizer에서 제공하는 제체 불용어 사용
from sklearn.feature_extraction.text import CountVectorizer
text = ["Family is not an important thing. It's everything."]
vect = CountVectorizer(stop_words="english")
print(vect.fit_transform(text).toarray())
print(vect.vocabulary_)
[[1 1 1]]
{'family': 0, 'important': 1, 'thing': 2}
NLTK에서 지원하는 불용어 사용
from sklearn.feature_extraction.text import CountVectorizer
from nltk.corpus import stopwords
text = ["Family is not an important thing. It's everything."]
sw = stopwords.words("english")
vect = CountVectorizer(stop_words=sw)
print(vect.fit_transform(text).toarray())
print(vect.vocabulary_)
[[1 1 1 1]]
{'family': 1, 'important': 2, 'thing': 3, 'everything': 0}
3) 문서 단어 행렬(Document-Term Matrix, DTM)
서로 다른 문서들의 BoW들을 결합한 표현 방법
DTM 혹은 TDM으로 부름
문서 단어 행렬(Document-Term Matrix, DTM)의 표기법
다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현
각 문서에 대한 BoW를 하나의 행렬로 만든 것
BoW 표현을 다수의 문서에 대해서 행렬로 표현하고 부르는 용어
ex.
문서1 : 먹고 싶은 사과
문서2 : 먹고 싶은 바나나
문서3 : 길고 노란 바나나 바나나
문서4 : 저는 과일이 좋아요
각 문서에서 등장한 단어의 빈도를 행렬의 값으로 표기
- 과일이 길고 노란 먹고 바나나 사과 싶은 저는 좋아요
문서1 0 0 0 1 0 1 1 0 0
문서2 0 0 0 1 1 0 1 0 0
문서3 0 1 1 0 2 0 0 0 0
문서4 1 0 0 0 0 0 0 1 1
문서 단어 행렬은 문서들을 서로 비교할 수 있도록 수치화할 수 있다
cf. 원한다면 한국어에서 불용어에 해당되는 조사들 또한 제거하여 더 정제된 DTM을 만들 수도 있을 것
문서 단어 행렬(Document-Term Matrix)의 한계
1) 희소 표현(Sparse representation)
one-hot vector 처럼,
전체 단어의 수가 차원의 수가 되기에,
대부분 0이 되는 (copus가 크면 클수록) 큰 차원을 지님
원-핫 벡터
단어 집합의 크기가 벡터의 차원
대부분의 값이 0
공간적 낭비와 계산 리소스를 증가
2) 단순 빈도 수 기반 접근
각 문서에는 중요한 단어와 불필요한 단어들이 혼재
TF-IDF
DTM에 불용어와 중요한 단어에 대해서 가중치를 줄 수 있는 방법
4) TF-IDF(Term Frequency-Inverse Document Frequency)
DTM 내에 있는 각 단어에 대한 중요도를 계산
기존의 DTM을 사용하는 것보다 보다 더 많은 정보를 고려하여 문서들을 비교할 수 있음
* TF-IDF가 DTM보다 항상 성능이 뛰어나진 않음
TF-IDF(단어 빈도-역 문서 빈도, Term Frequency-Inverse Document Frequency)
단어의 빈도와 역 문서 빈도로 DTM 내 각 단어들마다 중요한 정보를 가중치로 주는 방법
1) DTM을 만듦
2) TF-IDF 가중치를 부여
모든 문서에서 등장하는 단어는 중요도가 낮다고 판단
특정 문서에서만 자주 등장하는 단어는 중요도가 높다고 판단
the, a등의 불용어(stopword)는 모든 문서에서 자주 등장하기에,
중요도를 낮게 보겠다는 것
TF-IDF 사용 예
문서의 유사도를 구하는 작업
검색 시스템에서 검색 결과의 중요도를 정하는 작업
문서 내에서 특정 단어의 중요도 구하기
-> 즉, '중요도'를 찾는데 사용될 수 있음
d: 문서
t: 단어
n: 문서의 총 개수
tf(d, t):
특정 문서 d 내 특정 단어 t의 등장 빈도
df(t):
특정 단어 t가 등장한 문서의 수
idf(d, t):
df(t)에 반비례
idf(d, t) = log(n / (1 + df(t)))
IDF는 DF에 역수
단, log와 분모에 1을 더함
log를 사용하지 않으면,
총 문서의 수 n이 커질수록 IDF의 값은 linear하게 커짐
그래서 log를 사용하여 작게 커지게 함
ex. n = 1,000,000
단어 t df(t) idf(d,t)
word1 1 6
word2 100 4
word3 1,000 3
word4 10,000 2
word5 100,000 1
word6 1,000,000 0
log를 사용하지 않는 경우
단어 t df(t) idf(d,t)
word1 1 1,000,000
word2 100 10,000
word3 1,000 1,000
word4 10,000 100
word5 100,000 10
word6 1,000,000 1
불용어 등과 같이 자주 쓰이는 단어들은 비교적 자주 쓰이지 않는 단어들보다 최소 수십 배 자주 등장
즉, 희귀 단어일 수록 'IDF'의 값이 (문서 수에 비례해) 커짐
-> 희귀 단어의 IDF 값도 적절히 낮추기 위해 log를 사용
분모에 1을 더한것은 분모가 0이 되는 것을 막기 위함
파이썬으로 TF-IDF 직접 구현하기
import pandas as pd
from math import log
docs = [
'먹고 싶은 사과',
'먹고 싶은 바나나',
'길고 노란 바나나 바나나',
'저는 과일이 좋아요'
]
vocab = list(set(w for doc in docs for w in doc.split()))
vocab.sort()
N = len(docs)
def tf(t, d):
return d.count(t)
def idf(t):
df = 0
for doc in docs:
df += t in doc
return log(N/(df + 1))
def tfidf(t, d):
return tf(t,d)*idf(t)
result = []
for i in range(N):
result.append([])
d = docs[i]
for j in range(len(vocab)):
t = vocab[j]
result[-1].append(tf(t, d))
tf_ = pd.DataFrame(result, columns = vocab)
tf_
과일이 길고 노란 먹고 바나나 사과 싶은 저는 좋아요
0 0 0 0 1 0 1 1 0 0
1 0 0 0 1 1 0 1 0 0
2 0 1 1 0 2 0 0 0 0
3 1 0 0 0 0 0 0 1 1
이제 각 단어에 대한 IDF 값
result = []
for j in range(len(vocab)):
t = vocab[j]
result.append(idf(t))
idf_ = pd.DataFrame(result, index = vocab, columns = ["IDF"])
idf_
IDF
과일이 0.693147
길고 0.693147
노란 0.693147
먹고 0.287682
바나나 0.287682
사과 0.693147
싶은 0.287682
저는 0.693147
좋아요 0.693147
특정 단어의 문서 집중도를 값으로 표현
이제 TF-IDF 행렬을 출력
result = []
for i in range(N):
result.append([])
d = docs[i]
for j in range(len(vocab)):
t = vocab[j]
result[-1].append(tfidf(t,d))
tfidf_ = pd.DataFrame(result, columns = vocab)
tfidf_
과일이 길고 노란 먹고 바나나 사과 싶은 저는 좋아요
0 0.000000 0.000000 0.000000 0.287682 0.000000 0.693147 0.287682 0.000000 0.000000
1 0.000000 0.000000 0.000000 0.287682 0.287682 0.000000 0.287682 0.000000 0.000000
2 0.000000 0.693147 0.693147 0.000000 0.575364 0.000000 0.000000 0.000000 0.000000
3 0.693147 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.693147 0.693147
단어의 발생 '1' 대신 문서 집중도의 값(IDF)으로 치환
IDF는 log( n / DF(T))임
즉, DF를 구해야 IDF를 구할 수 있음
많은 패키지들은 위 식을 조정해서 사용
만약 n = 4인데, df(t) = 3인 경우
idf(d,t) = log(n/(df(t) + 1))
= log(4/(3 + 1)) = 0
log의 진수값이 1이 되어 idf(d, t)의 값이 0이 됨
IDF가 0이 되면 가중치 역할을 수행할 수 없음
그래서 실제 구현에서는
log(n/(df(t) + 1)) + 1
log항에 1을 더해서 최소 1의 값을 갖도록 함
사이킷런도 이 방식을 사용
사이킷런을 이용한 DTM과 TF-IDF 실습
사이킷런을 통해 DTM과 TF-IDF를 만들기
CountVectorizer를 사용
1) DTM 만들기
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
'you know I want your love',
'I like you',
'what should I do ',
]
vector = CountVectorizer()
# corpus에서 각 단어의 frequency를 기록
print(vector.fit_transform(corpus).toarray())
print(vector.vocabulary_) # 각 단어의 index
[[0 1 0 1 0 1 0 1 1]
[0 0 1 0 0 0 0 1 0]
[1 0 0 0 1 0 1 0 0]]
{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}
do는 0의 index 이며 1회 발생 (3번째 문서에서)
3번째 행에서만 1의 값
know는 1의 index (첫 번째 문서에서만 등장)
첫 번째 행에서만 1의 값
2) TF-IDF 계산
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'you know I want your love',
'I like you',
'what should I do ',
]
tfidfv = TfidfVectorizer().fit(corpus)
print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)
[[0. 0.46735098 0. 0.46735098 0. 0.46735098 0. 0.35543247 0.46735098]
[0. 0. 0.79596054 0. 0. 0. 0. 0.60534851 0. ]
[0.57735027 0. 0. 0. 0.57735027 0. 0.57735027 0. 0. ]]
{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}
문서 집중도를 vector화
idf(d,t) = log(n/(1 + df(t))
이제 이를 통해 문서의 유사도를 구할 수 있음
cf.
http://www.cs.pomona.edu/~dkauchak/classes/f09/cs160-f09/lectures/lecture5-tfidf.pdf
05. 문서 유사도(Document Similarity)
문서의 유사도
동일한 단어 또는 비슷한 단어가 얼마나 공통적으로 많이 사용되었는지
기계의 경우
위를 DTM, Word2Vec 등으로 수치화 하여 표현
문서 간의 단어들이 차이 계산은
Eclid, cosing similarity 등을 활용
https://www.kernix.com/blog/similarity-measure-of-textual-documents_p12
코사인 유사도(Cosine Similarity)
BoW, DTM, TF-IDF, Word2Vec을 통해 단어를 수치화 한 후,
이를 가지고 코사인 유사도를 구할 수 있음
두 vector간 cosine 각도(방향)로 유사도를 구함
-1 ~ 1
1에 가까울 수록 높은 유사도
동일 방향: 1
90도의 각: 0
180도의 각: -1
similarity = cos(theta) = A ⋅ B
-----
||A|| |B||
= ∑ i=1~n Ai x Bi
----------------
sqrt(∑ i=1~n Ai^2) x sqrt(∑ i=1~n Bi^2)
TF-IDF 행렬이 특징 vector A, B가 됨
ex.
문서1 : 저는 사과 좋아요
문서2 : 저는 바나나 좋아요
문서3 : 저는 바나나 좋아요 저는 바나나 좋아요
DTM (Document-Term Matrix)
- 바나나 사과 저는 좋아요
문서1 0 1 1 1
문서2 1 0 1 1
문서3 2 0 2 2
파이썬에서 코사인 유사도를 구하기
from numpy import dot
from numpy.linalg import norm
import numpy as np
def cos_sim(A, B):
return dot(A, B)/(norm(A)*norm(B))
doc1 = np.array([0,1,1,1])
doc2 = np.array([1,0,1,1])
doc3 = np.array([2,0,2,2])
print(cos_sim(doc1, doc2)) #문서1과 문서2의 코사인 유사도
print(cos_sim(doc1, doc3)) #문서1과 문서3의 코사인 유사도
print(cos_sim(doc2, doc3)) #문서2과 문서3의 코사인 유사도
0.67
0.67
1.00
문서3은 문서2에서 단지 모든 단어의 빈도수가 1씩 증가했을 뿐
한 문서 내의 모든 단어의 빈도수가 동일하게 증가하는 경우에는 기존의 문서와 코사인 유사도의 값이 1
코사인 유사도를 사용하지 않는다고 가정하였을 때
문서 A에 대해서 모든 문서와의 유사도를 구한다고 가정
패턴이 동일하나, 원문 길이가 긴 문서라는 이유로
다른 문서들보다 유사도가 더 높게 나오면 안 됨
ex. abstract와 내용은 유사 문서로 처리되어야 함
abstract의 짧고 길고에 따라 유사도가 달라져서는 안 됨
코사인 유사도는 유사도를 구할 때, 벡터의 크기가 아니라 벡터의 방향(패턴)에 초점을 두기 때문
유사도를 이용한 추천 시스템 구현하기
영화 데이터셋을 가지고 영화 추천 시스템
TF-IDF와 코사인 유사도를 사용해 구현
dataset: https://www.kaggle.com/rounakbanik/the-movies-dataset
movies_metadata.csv를 다운
data input
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel
data = pd.read_csv('현재 movies_metadata.csv의 파일 경로', low_memory=False)
data.head(2)
- ... original_title overview ... title video vote_average ...
0 ... Toy Story ... ... Toy Story 7.7 ...
1 ... Jumanji ... ... Jumanji 6.9 ...
24개의 열을 지님
좋아하는 영화를 입력하면, 해당 영화의 줄거리와 유사한 영화를 추천
data = data.head(20000)
훈련 데이터의 양을 줄이고 학습을 진행하고자 한다면, 이와 같이 데이터를 줄여서 재저장할 수 있음
Null 값 제거
data['overview'].isnull().sum()
135
data['overview'] = data['overview'].fillna('')
data['overview'].isnull().sum()
0
TF-IDF 수행
tfidf = TfidfVectorizer(stop_words='english')
# overview에 대해서 tf-idf 수행
tfidf_matrix = tfidf.fit_transform(data['overview'])
print(tfidf_matrix.shape)
(20000, 47487)
20,000개의 영화를 표현하기위해 총 47,487개의 단어가 사용
cosine similarity 계산
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
indices = pd.Series(data.index,
index=data['title']).drop_duplicates()
print(indices.head())
title
Toy Story 0
Jumanji 1
Grumpier Old Men 2
Waiting to Exhale 3
Father of the Bride Part II 4
dtype: int64
이 테이블의 용도는 영화의 타이틀을 입력하면 인덱스를 리턴
idx = indices['Father of the Bride Part II']
# 4
overview가 유사한 10개의 영화를 찾아내는 함수
def get_recommendations(title, cosine_sim=cosine_sim):
idx = indices[title] # get index for the title
# calc. cosine similarity of all the movies for the give title
sim_scores = list(enumerate(cosine_sim[idx]))
# sort in descending order
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
sim_scores = sim_scores[1:11]
# tilte index
movie_indices = [i[0] for i in sim_scores]
# title
return data['title'].iloc[movie_indices]
영화 다크 나이트 라이즈와 overview가 유사한 영화
get_recommendations('The Dark Knight Rises')
12481 The Dark Knight
150 Batman Forever
1328 Batman Returns
15511 Batman: Under the Red Hood
585 Batman
9230 Batman Beyond: Return of the Joker
18035 Batman: Year One
19792 Batman: The Dark Knight Returns, Part 1
3095 Batman: Mask of the Phantasm
10122 Batman Begins
Name: title, dtype: object
여러가지 유사도 기법
유클리드 거리(Euclidean distance)
자카드 유사도나 코사인 유사도만큼, 유용한 방법은 아님
p=(p1,p2,p3,...,pn)
q=(q1,q2,q3,...,qn)
sqrt((q1−p1)^2 + (q2−p2)^2 + ... + (qn−pn)^2)
= sqrt(∑i=1~n (qi − pi)^2)
2 차원이라면 두 직선 사이의 거리 구히기 임
- 바나나 사과 저는 좋아요
문서1 2 3 0 1
문서2 1 2 3 1
문서3 2 1 2 2
위 DTM은 4차원
- 바나나 사과 저는 좋아요
문서Q 1 1 0 1
가 위 문서1~3 중 어느것과 가장 유사한지 찾기
4차원 공간에서의 각각의 문서들과의 유클리드 거리를 구하면 됨
import numpy as np
def dist(x,y):
return np.sqrt(np.sum((x-y)**2))
doc1 = np.array((2,3,0,1))
doc2 = np.array((1,2,3,1))
doc3 = np.array((2,1,2,2))
docQ = np.array((1,1,0,1))
print(dist(doc1,docQ)) # 2.23606797749979 <- 가장 가까움
print(dist(doc2,docQ)) # 3.1622776601683795
print(dist(doc3,docQ)) # 2.449489742783178
자카드 유사도(Jaccard similarity)
두 집합의 교집합의 비율로 유사도를 구함
0 ~ 1 사이의 값
J(A,B) = |A∩B|/|A∪B| = |A∩B| / (|A|+|B|−|A∩B|)
두 문서에서 공통적으로 등장한 단어의 비율
doc1 = "apple banana everyone like likey watch card holder"
doc2 = "apple banana coupon passport love you"
# tokenization
tokenized_doc1 = doc1.split()
tokenized_doc2 = doc2.split()
union = set(tokenized_doc1) | set(tokenized_doc2)
intersection = set(tokenized_doc1) & set(tokenized_doc2)
print(len(intersection)/len(union)) # 0.16666...
06. 토픽 모델링(Topic Modeling)
문서 집합의 추상적인 주제를 발견하기 위한 통계적 모델
텍스트 본문의 숨겨진 의미 구조를 발견하기 위해 사용되는 텍스트 마이닝 기법
잠재 의미 분석(Latent Semantic Analysis, LSA)
= Latent Semantic Indexing, LSI
토픽 모델링이라는 분야에 아이디어를 제공한 알고리즘
BoW에 기반한 DTM이나 TF-IDF는 기본적으로 단어의 빈도 수를 이용한 수치화 방법이기 때문에 단어의 의미를 고려하지 못한다
단어의 토픽을 고려하지 못한다
LSA = DTM의 잠재된(Latent) 의미를 이끌어내는 방법
선형대수학의 특이값 분해(Singular Value Decomposition, SVD)를 사용
특이값 분해(Singular Value Decomposition, SVD)
A가 m × n 행렬
다음과 같이 3개의 행렬의 곱으로 분해(decomposition)
A = UΣV^T
U: m x m 직교행렬 (AA^T = U(ΣΣ^T)U^T)
V: n x n 직교행렬 (A^TA = V(Σ^TΣ)V^T)
Σ: m x n 직사각 대각행렬
orthogonal matrix
자신과 자신의 전치 행렬(transpose matrix)의 곱 (혹은 반대가) identity matrix(단위행렬)이 되는 행렬
diagonal matrix(대각 행렬)
주대각선을 제외한 곳의 원소가 모두 0인 행렬
SVD로 나온 대각 행렬의 대각 원소의 값을 행렬 A의 특이값(singular value)
Matrix types
전치 행렬(Transposed Matrix)
원래의 행렬에서 행과 열을 바꾼 행렬
주대각선을 축으로 반사 대칭을 하여 얻는 행렬
M = 1 2 M^T = 1 3 5
3 4 2 4 6
5 6
Identity Matrix (단위 행렬)
I = 1 0
0 1
Inverse Matrix (역행렬)
A matrix와 B matrix의 곱이 I matrix
B를 A의 역행렬이라고 표현
A^-1
A x A^-1 = I
Orthogonal matrix (직교 행렬)
A x A^T = I
A^T x A = I
둘을 만족하는 행렬 A를 직교 행렬
이 때 직교행렬인 A는
A^-1 = A^T 임
Diagonal matrix (대각 행렬)
Σ = a 0 0
0 a 0
0 0 a
Σ = a 0 0
0 a 0
0 0 a
0 0 0
Σ = a 0 0 0
0 a 0 0
0 0 a 0
SVD를 통해 나온 대각 행렬 Σ
Σ의 주대각원소를 행렬 A의 특이값(singular value)
sigma1, sigma2, ... sigmar
내림차순 정렬 특징
ex.
Σ = 12.4 0 0
0 9.5 0
0 0 1.3
절단된 SVD(Truncated SVD)
위에서 설명한 SVD를 풀 SVD(full SVD)
LSA의 경우 풀 SVD에서 나온 3개의 행렬에서 일부 벡터들을 삭제시킨 절단된 SVD(truncated SVD)를 사용
Full SVD
A U Σ V^T
+----+ +----+ +--+ +----+
| | = | | | | | |
| | | | | | +----+
| | | | | |
+----+ +----+ +--+
Truncated SVD
A' Ut Σt V^Tt
+----+ +--+ +-+ +----+
| | = | | | | +----+
| | | | +-+
| | | |
+----+ +--+
절단된 SVD (데이터의 차원을 줄임)
대각 행렬 Σ의 대각 원소의 값 중에서 상위값 t개만 남음
절단된 SVD를 수행하면 값의 손실로 행렬 A를 복구할 수 없음
t = 찾고자하는 토픽의 수를 반영한 하이퍼파라미터값
t를 선택하는 것은 쉽지 않은 일
t를 크게 잡으면
기존의 행렬 A로부터 다양한 의미를 가져갈 수 있음
t를 작게 잡으면
노이즈를 제거할 수 있음
설명력이 낮은 정보를 삭제하고 설명력이 높은 정보를 남긴다는 의미
-> 심층적인 의미를 확인
계산 비용이 낮아짐
즉, overfitting 문제와 동일
잠재 의미 분석(Latent Semantic Analysis, LSA)
DTM이나 TF-IDF 행렬에 절단된 SVD(truncated SVD)를 사용하여 차원을 축소하여 단어들의 잠재적인 의미를 끌어냄
DTM
- 과일이 길고 노란 먹고 바나나 사과 싶은 저는 좋아요
문서1 0 0 0 1 0 1 1 0 0
문서2 0 0 0 1 1 0 1 0 0
문서3 0 1 1 0 2 0 0 0 0
문서4 1 0 0 0 0 0 0 1 1
import numpy as np
A = np.array([[0,0,0,1,0,1,1,0,0],
[0,0,0,1,1,0,1,0,0],
[0,1,1,0,2,0,0,0,0],
[1,0,0,0,0,0,0,1,1]])
np.shape(A) # (4, 9)
full SVD
# s = Σ
U, s, VT = np.linalg.svd(A, full_matrices = True)
print(U.round(2)) # 소수점 두 번째 자리까지만 출력
np.shape(U) # (4, 4)
[[-0.24 0.75 0. -0.62]
[-0.51 0.44 -0. 0.74]
[-0.83 -0.49 -0. -0.27]
[-0. -0. 1. 0. ]]
(4, 4)
4 × 4의 크기를 가지는 직교 행렬 U가 생성
대각 행렬 S를 확인
print(s.round(2))
np.shape(s)
[2.69 2.05 1.73 0.77]
(4,)
Numpy의 linalg.svd()
특이값의 리스트를 반환
그러므로 앞서 본 수식의 형식으로 보려면 이를 다시 대각 행렬로 바꾸어 주어야 함
우선 특이값을 s에 저장
대각 행렬 크기의 행렬을 생성한 후에 그 행렬에 특이값을 삽입
S = np.zeros((4, 9)) # 대각 행렬 크기 4 x 9의 행렬 생성
S[:4, :4] = np.diag(s) # 특이값을 대각행렬에 삽입
print(S.round(2))
np.shape(S)
[[2.69 0. 0. 0. 0. 0. 0. 0. 0. ]
[0. 2.05 0. 0. 0. 0. 0. 0. 0. ]
[0. 0. 1.73 0. 0. 0. 0. 0. 0. ]
[0. 0. 0. 0.77 0. 0. 0. 0. 0. ]]
(4, 9)
2.69 > 2.05 > 1.73 > 0.77 순으로 값이 내림차순
print(VT.round(2))
np.shape(VT)
9 × 9의 크기를 가지는 직교 행렬 VT(V의 전치 행렬)가 생성
[[-0. -0.31 -0.31 -0.28 -0.8 -0.09 -0.28 -0. -0. ]
[ 0. -0.24 -0.24 0.58 -0.26 0.37 0.58 -0. -0. ]
[ 0.58 -0. 0. 0. -0. 0. -0. 0.58 0.58]
[ 0. -0.35 -0.35 0.16 0.25 -0.8 0.16 -0. -0. ]
[-0. -0.78 -0.01 -0.2 0.4 0.4 -0.2 0. 0. ]
[-0.29 0.31 -0.78 -0.24 0.23 0.23 0.01 0.14 0.14]
[-0.29 -0.1 0.26 -0.59 -0.08 -0.08 0.66 0.14 0.14]
[-0.5 -0.06 0.15 0.24 -0.05 -0.05 -0.19 0.75 -0.25]
[-0.5 -0.06 0.15 0.24 -0.05 -0.05 -0.19 -0.25 0.75]]
(9, 9)
U × S × VT를 하면 기존의 행렬 A가 나와야 함
Numpy의 allclose()는 2개의 행렬이 동일하면 True를 리턴
이를 사용하여 정말로 기존의 행렬 A와 동일한지 확인
np.allclose(A, np.dot(np.dot(U,S), VT).round(2)) # True
Truncated SVD 수행
대각 행렬 S 내의 특이값 중에서 상위 2개만 남기고 제거
S=S[:2,:2]
print(S.round(2))
[[2.69 0. ]
[0. 2.05]]
직교 행렬 U에 대해서도 2개의 열만 남기고 제거
U=U[:,:2]
print(U.round(2))
[[-0.24 0.75]
[-0.51 0.44]
[-0.83 -0.49]
[-0. -0. ]]
행렬 V의 전치 행렬인 VT에 대해서 2개의 행만 남기고 제거
이는 V관점에서는 2개의 열만 남기고 제거한 것
VT=VT[:2,:]
print(VT.round(2))
[[-0. -0.31 -0.31 -0.28 -0.8 -0.09 -0.28 -0. -0. ]
[ 0. -0.24 -0.24 0.58 -0.26 0.37 0.58 -0. -0. ]]
축소된 행렬 U, S, VT에 대해서 다시 U × S × VT연산을 하면 기존의 A와는 다른 결과
A_prime = np.dot(np.dot(U,S), VT) # 원복
A와 truncated A의 값 비교
print(A)
print(A_prime.round(2))
[[0 0 0 1 0 1 1 0 0]
[0 0 0 1 1 0 1 0 0]
[0 1 1 0 2 0 0 0 0]
[1 0 0 0 0 0 0 1 1]]
[[ 0. -0.17 -0.17 1.08 0.12 0.62 1.08 -0. -0. ]
[ 0. 0.2 0.2 0.91 0.86 0.45 0.91 0. 0. ]
[ 0. 0.93 0.93 0.03 2.05 -0.17 0.03 0. 0. ]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. ]]
대체적으로 기존에 0인 값들은 0에 가가운 값이 나오고,
1인 값들은 1에 가까운 값이 나오는 것
제대로 복구되지 않은 구간도 존재
의미
축소된 U는 4 × 2의 크기
문서의 개수 × 토픽의 수 t의 크기
단어의 개수인 9는 유지하지 않고,
문서의 개수인 4의 크기가 유지
4개의 문서 각각을 2개의 값으로 표현
즉, U의 각 행은 잠재 의미를 표현하기 위한 수치화 된 각각의 문서 벡터
축소된 VT는 2 × 9의 크기
토픽의 수 t × 단어의 개수
VT의 각 열은 잠재 의미를 표현하기 위해 수치화된 각각의 단어 벡터
이 문서 벡터들과 단어 벡터들을 통해 다른 문서의 유사도, 다른 단어의 유사도, 단어(쿼리)로부터 문서의 유사도를 구하는 것들이 가능
실습을 통한 이해
사이킷런에 Twenty Newsgroups이라고 불리는 20개의 다른 주제를 가진 뉴스그룹 데이터
문서의 수를 원하는 토픽의 수로 압축한 뒤에 각 토픽당 가장 중요한 단어 5개를 출력하는 실습
import pandas as pd
from sklearn.datasets import fetch_20newsgroups
dataset = fetch_20newsgroups(shuffle=True,
random_state=1,
remove=('headers', 'footers', 'quotes'))
documents = dataset.data
len(documents) # 11314
documents[1]
"\n\n\n\n\n\n\nYeah, do you expect people to read the FAQ, etc. and actually accept hard\natheism? No, you need a little leap of faith, Jimmy. Your logic runs out\nof steam!\n\n\n\n\n\n\n\nJim,\n\nSorry I can't pity you, Jim. And I'm sorry that you have these feelings of\ndenial about the faith you need to get by. Oh well, just pretend that it will\nall end happily ever after anyway. Maybe if you start a new newsgroup,\nalt.atheist.hard, you won't be bummin' so much?\n\n\n\n\n\n\nBye-Bye, Big Jim. Don't forget your Flintstone's Chewables! :) \n--\nBake Timmons, III"
target_name에는 본래 이 뉴스그룹 데이터가 지닌 20개의 category 저장
print(dataset.target_names)
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
텍스트 전처리
정제 과정
구두점, 숫자, 특수 문자를 제거
정규 표현식을 통해서 해결
길이가 짧은 단어도 제거
짧은 단어는 유용한 정보를 담고있지 않다고 가정
모든 알파벳을 소문자로 변환
news_df = pd.DataFrame({'document':documents})
# 특수 문자 제거
news_df['clean_doc'] = news_df['document'].str.replace("[^a-zA-Z]", " ")
# 길이가 3이하인 단어는 제거 (길이가 짧은 단어 제거)
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: ' '.join([w for w in x.split() if len(w)> 3 ]))
# 전체 단어에 대한 소문자 변환
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: x.lower())
정제 확인
news_df['clean_doc'][1]
'yeah expect people read actually accept hard atheism need little leap faith jimmy your logic runs steam sorry pity sorry that have these feelings denial about faith need well just pretend that will happily ever after anyway maybe start newsgroup atheist hard bummin much forget your flintstone chewables bake timmons'
불용어를 제거
불용어를 제거하기 위해서 토큰화를 우선 수행
from nltk.corpus import stopwords
import nltk
nltk.download('stopwords')
stop_words = stopwords.words('english') # NLTK의 불용어 list
# 토큰화
tokenized_doc = news_df['clean_doc'].apply(lambda x: x.split())
# 불용어 제거
tokenized_doc = tokenized_doc.apply(lambda x: [item for item in x if item not in stop_words])
print(tokenized_doc[1])
['yeah', 'expect', 'people', 'read', 'actually', 'accept', 'hard', 'atheism', 'need', 'little', 'leap', 'faith', 'jimmy', 'logic', 'runs', 'steam', 'sorry', 'pity', 'sorry', 'feelings', 'denial', 'faith', 'need', 'well', 'pretend', 'happily', 'ever', 'anyway', 'maybe', 'start', 'newsgroup', 'atheist', 'hard', 'bummin', 'much', 'forget', 'flintstone', 'chewables', 'bake', 'timmons']
your, about, just, that, will, after 단어들이 제거되었음
TF-IDF 행렬 만들기
# Detokenization
detokenized_doc = []
for i in range(len(news_df)):
t = ' '.join(tokenized_doc[i])
detokenized_doc.append(t)
news_df['clean_doc'] = detokenized_doc
news_df['clean_doc'][1]
'yeah expect people read actually accept hard atheism need little leap faith jimmy logic runs steam sorry pity sorry feelings denial faith need well pretend happily ever anyway maybe start newsgroup atheist hard bummin much forget flintstone chewables bake timmons'
TfidfVectorizer로 단어 1,000개(제한)에 대한 TF-IDF 행렬 생성
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(stop_words='english',
max_features= 1000, # 상위 1,000개의 단어를 보존
max_df = 0.5,
smooth_idf=True)
X = vectorizer.fit_transform(news_df['clean_doc'])
X.shape # TF-IDF 행렬의 크기 11,314 × 1,000
토픽 모델링(Topic Modeling)
TF-IDF 행렬을 다수의 행렬로 분해
사이킷런의 절단된 SVD(Truncated SVD)를 사용
20개의 토픽을 가졌다고 가정하고 토픽 모델링
원래 기존 뉴스그룹 데이터가 20개의 카테고리를 갖고있었음
from sklearn.decomposition import TruncatedSVD
import numpy as np
svd_model = TruncatedSVD(n_components=20, algorithm='randomized', n_iter=100, random_state=122)
svd_model.fit(X)
len(svd_model.components_) # 20
np.shape(svd_model.components_) # VT는 (20, 1000)
# 정확하게 토픽의 수 t × 단어의 수의 크기
terms = vectorizer.get_feature_names() # 단어 집합. 1,000개의 단어가 저장됨.
def get_topics(components, feature_names, n=5):
for idx, topic in enumerate(components):
print("Topic %d:" % (idx+1), [(feature_names[i], topic[i].round(5)) for i in topic.argsort()[:-n - 1:-1]])
get_topics(svd_model.components_,terms)
각 20개의 행의 각 1,000개의 열 중 가장 값이 큰 5개의 값을 찾아서 단어로 출력
Topic 1: [('like', 0.2138), ('know', 0.20031), ('people', 0.19334), ('think', 0.17802), ('good', 0.15105)]
Topic 2: [('thanks', 0.32918), ('windows', 0.29093), ('card', 0.18016), ('drive', 0.1739), ('mail', 0.15131)]
...
Topic 20: [('problem', 0.32797), ('file', 0.26268), ('thanks', 0.23414), ('used', 0.19339), ('space', 0.13861)]
LSA의 장단점(Pros and Cons of LSA)
쉽고 빠르게 구현이 가능
단어의 잠재적인 의미를 이끌어낼 수 있어 문서의 유사도 계산 등에서 좋은 성능을 보여줌
하지만
이미 계산된 LSA에 새로운 데이터를 추가하여 계산하려고하면 보통 처음부터 다시 계산해야 함
즉, 새로운 정보에 대해 업데이트가 어려움
이는 최근 LSA 대신 Word2Vec 등 단어의 의미를 벡터화할 수 있는 또 다른 방법론인 인공 신경망 기반의 방법론이 각광받는 이유이기도 함
cf.
https://sragent.tistory.com/entry/Latent-Semantic-AnalysisLSA
https://simonpaarlberg.com/post/latent-semantic-analyses/
LSA로 텍스트 분류하기 :
잠재 디리클레 할당(Latent Dirichlet Allocation, LDA)
토픽 모델링의 대표적인 알고리즘
토픽 모델링은 문서의 집합에서 토픽을 찾아내는 프로세스
문서의 주제를 알아내는 일이 중요한 곳에서 사용
검색 엔진
고객 민원 시스템
여러 topic들이 혼재되어 있으며,
topic들은 확률 분포에 기반하여 단어를 생성한다고 가정
LDA는 data에 대해 문서 생성 과정을 역추적
test site
https://lettier.com/projects/lda-topic-modeling/
잠재 디리클레 할당(Latent Dirichlet Allocation, LDA) 개요
문서1 : 저는 사과랑 바나나를 먹어요
문서2 : 우리는 귀여운 강아지가 좋아요
문서3 : 저의 깜찍하고 귀여운 강아지가 바나나를 먹어요
hyperparameter
k = topic 수: 사용자가 설정
ex. 2개의 토픽을 찾으라고 요청
LDA 입력 전에 정제, 불용어 (주어, 조사 등) 제거
이후 DTM 하여 LDA에 입력
LDA는 각 문서의 토픽 분포와 각 토픽 내의 단어 분포를 추정
<각 문서의 토픽 분포>
문서1 : 토픽 A 100%
문서2 : 토픽 B 100%
문서3 : 토픽 B 60%, 토픽 A 40%
<각 토픽의 단어 분포>
토픽A : 사과 20%, 바나나 40%, 먹어요 40%, 귀여운 0%, 강아지 0%, 깜찍하고 0%, 좋아요 0%
토픽B : 사과 0%, 바나나 0%, 먹어요 0%, 귀여운 33%, 강아지 33%, 깜찍하고 16%, 좋아요 16%
LDA는 토픽의 이름을 지정하지 않으나,
위 토픽 내 단어 분포를 보면,
토픽A는 과일에 대한 토픽
토픽B는 강아지에 대한 토픽
즉, 문서가 어떤 토픽에 해당하는지 분류 (%로)
LDA의 가정
LDA는 문서의 집합에 어떤 토픽이 존재하는지를 알아내기 위한 알고리즘
LDA 입력
bOW의 행렬 DTM
or
TF-IDF 행렬
단어의 순서는 상관 없음
문서 별
주제가 있고,
이 주제에 연관된 단어들이 있음
문서에 사용된 단어가 N개
ex. N = 5
문서에 사용할 토픽의 혼합을 확률 분포에 기반하여 결정
토픽이 2개라고 하였을 때
강아지 토픽을 60%, 과일 토픽을 40%와 같이 선택할 수 있음
문서에 사용할 각 단어를 (아래와 같이) 정함
1. 토픽 분포에서 토픽 T를 확률적으로 선택
2. 선택한 토픽 T에서 단어의 출현 확률 분포에 기반해 문서에 사용할 단어를 선택
강아지 토픽을 선택하였다면, 33% 확률로 강아지란 단어를 선택할 수 있음
3. 1~2 반복
ref. 아래 내용을 가지고 문서를 생성하는 로직을 설명한 것임
토픽A : 사과 20%, 바나나 40%, 먹어요 40%, 귀여운 0%, 강아지 0%, 깜찍하고 0%, 좋아요 0%
토픽B : 사과 0%, 바나나 0%, 먹어요 0%, 귀여운 33%, 강아지 33%, 깜찍하고 16%, 좋아요 16%
이러한 과정을 통해 문서가 작성되었다는 가정 하에 LDA는 토픽을 뽑아내기 위하여 위 과정을 역으로 추적하는 역공학(reverse engneering)을 수행
LDA의 수행하기
k: topic number 결정
k개의 topic이 M개의 문서에 분포되어 있다고 가정
모든 단어를 k 개 topic 중 하나에 할당
단어별로 k개 topic 중 random하게 할당
이후,
각 문서는 topic을 지님
topic은 단어 분포를 지님
random한 것이니 전부 틀린 상태
같은 단어가 서로 다른 topic에 할당 되었을 수 있음
모든 문서에 대해
각 단어 w
자신만 잘못된 topic에 할당 되어 있다고 가정
단어 w는 두 가지 기준에 따라서 토픽 재할당
p(topic t | document d)
문서 d의 단어들 중 토픽 t에 해당하는 단어들의 비율
p(word w | topic t)
단어 w를 갖고 있는 모든 문서들 중 토픽 t가 할당된 비율
이를 반복하면 모든 할당이 완료된 수렴 상태가 됨
ex.
doc1
word apple banana apple dog dog
topic B B ? A A
doc2
word cute book king apple apple
topic B B B B B
doc1의 세 번째 단어 apple의 topic을 결정하자
doc1
word apple banana apple dog dog
topic B B ? A A
-- -- -- --
doc2
word cute book king apple apple
topic B B B B B
첫 번째 기준
doc1의 단어들이 어떤 topic에 할당되어 있는지 여부
doc1의 모든 단어들은 topic A와 B에 각각 50% 비율로 할당
단어 apple은 topic A와 B 중 어디에도 속할 가능성이 있음
doc1
word apple banana apple dog dog
topic B B ? A A
-- --
doc2
word cute book king apple apple
topic B B B B B
-- --
두 번째 기준
apple이 전체 문서에서 어떤 topic에 할당 되어 있는지 여부
apple은 topic B에 할당될 가능성이 높음
잠재 디리클레 할당과 잠재 의미 분석의 차이
LSA
DTM을 차원 축소 하여 축소 차원에서 근접 단어들을 토픽으로 묶음
LDA
단어가 어떤 topic에 존재할 확률 (한 문서 내에서)
and
문서에 어떤 topic이 존재할 확률
이를 결합확률로 추정하여 topic을 추출
실습
사이킷런으로 LDA 실습 : https://wikidocs.net/40710
gensim으로 LDA 실습
정수 인코딩과 단어 집합 만들기
Twenty Newsgroups
20개의 다른 주제를 가진 뉴스 데이터
전처리 과정은 생략
tokenized_doc[:5]
0 [well, sure, about, story, seem, biased, what,...
...
4 [well, will, have, change, scoring, playoff, p...
각 단어에 정수 인코딩 + 각 뉴스에서의 단어의 빈도수를 기록
from gensim import corpora
dictionary = corpora.Dictionary(tokenized_doc)
corpus = [dictionary.doc2bow(text) for text in tokenized_doc]
print(corpus[1]) # 두번째 뉴스 출력
[(52, 1), (55, 1), (56, 1), ... (66, 2), ... ]
# (word_id as integer, occurance frequency)
(66, 2)
66으로 할당된 단어가 2번째 뉴스에서 두 번 등장
dictionary[66] # 총 65284개 단어
"faith"라는 단어임
LDA 모델 훈련시키기
import gensim
NUM_TOPICS = 20 # k = 20
ldamodel = gensim.models.ldamodel.LdaModel(
corpus,
num_topics = NUM_TOPICS,
id2word=dictionary,
passes=15 # 알고리즘 동작 횟수
)
topics = ldamodel.print_topics(
num_words=4 # 4개 단어만 출력
)
for topic in topics:
print(topic)
(0, '0.015*"drive" + 0.014*"thanks" + 0.012*"card" + 0.012*"system"')
(1, '0.009*"back" + 0.009*"like" + 0.009*"time" + 0.008*"went"')
...
(19, '0.013*"outlets" + 0.013*"norton" + 0.012*"quantum" + 0.008*"neck"')
(토픽번호, ...)
각 단어 앞에 붙은 수치는 단어의 해당 토픽에 대한 기여도
LDA 시각화 하기
토픽 별 단어 분포 시각화
pip install pyLDAvis
import pyLDAvis.gensim
pyLDAvis.enable_notebook()
vis = pyLDAvis.gensim.prepare(ldamodel, corpus, dictionary)
pyLDAvis.display(vis)
원들로 구성된 diagram과 막대 그래프 diagram이 표시됨
원의 의미: 각각이 topic임
원이 겹치는 경우 이 topic들은 유사 topic
문서 별 토픽 분포 보기
이미 훈련된 LDA 모델인 ldamodel에 전체 데이터가 정수 인코딩 된 결과를 넣은 후에 확인이 가능
상위 5개의 문서에 대해서만 토픽 분포를 확인
for i, topic_list in enumerate(ldamodel[corpus]):
if i == 5:
break
print(i,'번째 문서의 topic 비율은',topic_list)
0 번째 문서의 topic 비율은 [(7, 0.3050222), (9, 0.5070568), (11, 0.1319604), (18, 0.042834017)]
1 번째 문서의 topic 비율은 [(0, 0.031606797), (7, 0.7529218), (13, 0.02924682), (14, 0.12861845), (17, 0.037851967)]
2 번째 문서의 topic 비율은 [(7, 0.52241164), (9, 0.36602455), (16, 0.09760969)]
3 번째 문서의 topic 비율은 [(1, 0.16926806), (5, 0.04912094), (6, 0.04034211), (7, 0.11710636), (10, 0.5854137), (15, 0.02776434)]
4 번째 문서의 topic 비율은 [(7, 0.42152268), (12, 0.21917087), (17, 0.32781804)]
출력 결과에서 (숫자, 확률)은 각각 토픽 번호와 해당 토픽이 해당 문서에서 차지하는 분포도를 의미
예를 들어 0번째 문서의 토픽 비율에서 (7, 0.3050222)은 7번 토픽이 30%의 분포도를 가지는 것을 의미
데이터프레임 형식으로 출력
def make_topictable_per_doc(ldamodel, corpus):
topic_table = pd.DataFrame()
for i, topic_list in enumerate(ldamodel[corpus]):
doc = topic_list[0] if ldamodel.per_word_topics else topic_list
# 문서 내 topic 의 percentage로 정렬
doc = sorted(doc, key=lambda x: (x[1]), reverse=True)
for j, (topic_num, prop_topic) in enumerate(doc):
if j == 0:
topic_table = topic_table.append(pd.Series([int(topic_num), round(prop_topic,4), topic_list]), ignore_index=True)
# 가장 비중이 높은 토픽의 비중과, 전체 토픽의 비중을 저장한다.
else:
break
return (topic_table)
topictable = make_topictable_per_doc(ldamodel, corpus)
topictable = topictable.reset_index() # 문서 번호 열(column) 생성
topictable.columns = ['문서 번호', '가장 비중이 높은 토픽', '가장 높은 토픽의 비중', '각 토픽의 비중']
topictable[:10]
문서 번호 가장 비중이 높은 토픽 가장 높은 토픽의 비중 각 토픽의 비중
0 0 8.0 0.7045 [(4, 0.13990301), (8, 0.7044836), (11, 0.12481...
1 1 8.0 0.5561 [(3, 0.056908607), (5, 0.06901159), (7, 0.1808...
2 2 8.0 0.7365 [(4, 0.24897447), (8, 0.7365094)]
3 3 8.0 0.4147 [(5, 0.09874551), (6, 0.021120021), (8, 0.4147...
4 4 6.0 0.3877 [(6, 0.38773236), (8, 0.29062006), (9, 0.07729...
5 5 8.0 0.5379 [(4, 0.045204345), (8, 0.53792506), (17, 0.150...
6 6 10.0 0.6530 [(1, 0.14797193), (8, 0.18229719), (10, 0.6529...
7 7 8.0 0.5836 [(4, 0.34252658), (8, 0.5835517), (17, 0.03465...
8 8 15.0 0.4458 [(3, 0.1362022), (8, 0.39497805), (15, 0.44584...
9 9 8.0 0.6196 [(5, 0.30197588), (8, 0.61962634), (14, 0.0667...
ref.
http://s-space.snu.ac.kr/bitstream/10371/95582/1/22_1_%EB%82%A8%EC%B6%98%ED%98%B8.pdf
https://bab2min.tistory.com/568
https://annalyzin.wordpress.com/2015/06/21/laymans-explanation-of-topic-modeling-with-lda-2/
https://towardsdatascience.com/latent-dirichlet-allocation-15800c852699
https://radimrehurek.com/gensim/wiki.html#latent-dirichlet-allocation
https://www.machinelearningplus.com/nlp/topic-modeling-gensim-python/
모델 저장 및 로드 하기 : https://stackabuse.com/python-for-nlp-working-with-the-gensim-library-part-2/
전반적으로 참고하기 좋은 글 : https://shichaoji.com/2017/04/25/topicmodeling/
동영상 강의 : https://blog.naver.com/chunjein/220946362463
뉴스를 가지고 할 수 있는 다양한 일들 : https://www.slideshare.net/koorukuroo/20160813-pycon2016apac
잠재 디리클레 할당(LDA) 실습2
뉴스 기사 제목 데이터에 대한 이해
약 15년 동안 발행되었던 뉴스 기사 제목을 모아놓은 영어 데이터
https://www.kaggle.com/therohk/million-headlines
import pandas as pd
import urllib.request
urllib.request.urlretrieve("https://raw.githubusercontent.com/franciscadias/data/master/abcnews-date-text.csv", filename="abcnews-date-text.csv")
data = pd.read_csv('abcnews-date-text.csv', error_bad_lines=False)
print(len(data)) # 1082168
print(data.head(5))
publish_date headline_text
0 20030219 aba decides against community broadcasting lic...
1 20030219 act fire witnesses must be aware of defamation
2 20030219 a g calls for infrastructure protection summit
3 20030219 air nz staff in aust strike for pay rise
4 20030219 air nz strike to affect australian travellers
headline_text만 별도 저장
text = data[['headline_text']]
text.head(5)
headline_text
0 aba decides against community broadcasting lic...
1 act fire witnesses must be aware of defamation
2 a g calls for infrastructure protection summit
3 air nz staff in aust strike for pay rise
4 air nz strike to affect australian travellers
텍스트 전처리
불용어 제거, 표제어 추출, 길이가 짧은 단어 제거
import nltk
text['headline_text'] = text.apply(lambda row: nltk.word_tokenize(row['headline_text']), axis=1)
print(text.head(5))
headline_text
0 [aba, decides, against, community, broadcastin...
1 [act, fire, witnesses, must, be, aware, of, de...
2 [a, g, calls, for, infrastructure, protection,...
3 [air, nz, staff, in, aust, strike, for, pay, r...
4 [air, nz, strike, to, affect, australian, trav...
불용어를 제거
from nltk.corpus import stopwords
stop = stopwords.words('english')
text['headline_text'] =
text['headline_text'].apply(
lambda x: [word for word in x if word not in (stop)]
)
print(text.head(5))
headline_text
0 [aba, decides, community, broadcasting, licence]
1 [act, fire, witnesses, must, aware, defamation]
2 [g, calls, infrastructure, protection, summit]
3 [air, nz, staff, aust, strike, pay, rise]
4 [air, nz, strike, affect, australian, travellers]
against, be, of, a, in, to 등의 단어가 제거되었음
표제어 추출
3인칭 단수 표현을 1인칭으로 바꾸고, 과거 현재형 동사를 현재형으로 변경
해당 문서의 대표 문서화
ex. decides -> decide
from nltk.stem import WordNetLemmatizer
text['headline_text'] = text['headline_text'].apply(lambda x: [WordNetLemmatizer().lemmatize(word, pos='v') for word in x])
print(text.head(5))
headline_text
0 [aba, decide, community, broadcast, licence]
1 [act, fire, witness, must, aware, defamation]
2 [g, call, infrastructure, protection, summit]
3 [air, nz, staff, aust, strike, pay, rise]
4 [air, nz, strike, affect, australian, travellers]
길이가 3이하인 단어에 대해서 제거
tokenized_doc = text['headline_text'].apply(lambda x: [word for word in x if len(word) > 3])
print(tokenized_doc[:5])
0 [decide, community, broadcast, licence]
1 [fire, witness, must, aware, defamation]
2 [call, infrastructure, protection, summit]
3 [staff, aust, strike, rise]
4 [strike, affect, australian, travellers]
TF-IDF 행렬 만들기
detokenized_doc = []
for i in range(len(text)):
t = ' '.join(tokenized_doc[i])
detokenized_doc.append(t)
text['headline_text'] = detokenized_doc
text['headline_text'][:5]
0 decide community broadcast licence
1 fire witness must aware defamation
2 call infrastructure protection summit
3 staff aust strike rise
4 strike affect australian travellers
Name: headline_text, dtype: object
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(
stop_words='english',
max_features= 1000) # 상위 1,000개의 단어를 보존
X = vectorizer.fit_transform(text['headline_text'])
X.shape # TF-IDF 행렬의 크기 (1082168, 1000)
토픽 모델링
from sklearn.decomposition import LatentDirichletAllocation
lda_model=LatentDirichletAllocation(n_components=10,learning_method='online',random_state=777,max_iter=1)
lda_top=lda_model.fit_transform(X)
print(lda_model.components_)
print(lda_model.components_.shape)
[[1.00001533e-01 1.00001269e-01 1.00004179e-01 ... 1.00006124e-01
1.00003111e-01 1.00003064e-01]
...
terms = vectorizer.get_feature_names() # 단어 집합. 1,000개의 단어가 저장됨.
def get_topics(components, feature_names, n=5):
for idx, topic in enumerate(components):
print("Topic %d:" % (idx+1), [(feature_names[i], topic[i].round(2)) for i in topic.argsort()[:-n - 1:-1]])
get_topics(lda_model.components_,terms)
Topic 1: [('government', 8725.19), ('sydney', 8393.29), ('queensland', 7720.12), ('change', 5874.27), ('home', 5674.38)]
...
07. 머신 러닝(Machine Learning) 개요
응용
블랙박스 영상의 영상 분류
상황별 조건별 영상 분류
영상 상세 검색 및 랭킹 가능
음악 추천
artist, genre, year,
코드 유사도 분석
코드의 유사도, 중복 분석
코드 성격 분류
코드의 성격 분류
챗 봇
주식 투자
특허
차량 저장 영상 검색
차량 충돌 정보
일/시
날씨, 온도
광량
위치
주행속도
영상 내 주변 정보
주변 차량
위 정보들을 조합하여 저장
검색방법
"최근 1일 내 정차 중 발생한 충격 영상 검색"
코퍼스 데이터
Corpus
말뭉치
: 여러 단어로 이뤄진 문장
언어 데이터를 한데 모아둔 것
실제로 사용되고,
대표성을 지녀야 함
Tokenization
token의 단위는 상황에 따라 다름
보통 의미 있는 단위를 token으로 결정
word tokenization
token unit 이 word
ex.
Time is an illusion. Lunchtime double so!
-> [Time, is, an, illusion, Lunchtime, double, so]
Don't be fooled by .....Jone's ...
Don't 와 Jone's의 token화
word_tokenize의 경우
Don't -> [Do, n't]
wordPuncTokenizer
[Don, ', t]
Keras의 text_to_word_sequence
[don't]
token화 시 고려 사항
구도점 및 특수 문자를 단순 제외하면 안 됨
줄임말과 단어 내 띄어쓰기
rock 'n' roll 의 경우 단어 사이에 띄어쓰기가 있어도 하나의 단어임
표준 tokenization ex.
표준으로 쓰이고 있는 토큰화 방법 중 하나인 Penn Treebank Tokenization
규칙 1. 하이푼으로 구성된 단어는 하나로 유지한다.
규칙 2. doesn't와 같이 아포스트로피로 '접어'가 함께하는 단어는 분리해준다.
문장 tokenization
이진 분류기(Binary Classifier)
한국어에서의 토큰화의 어려움.
한국어의 경우에는 띄어쓰기 단위가 되는 단위를 '어절'
어절 토큰화는 한국어 NLP에서 지양되고 있음
한국어는 교착어이다.
교착어란 조사, 어미 등을 붙여서 말을 만드는 언어
RNN
Recurernt Neural Network
sequence data를 modeling 하기 위함
CNN등은 image등 고정 길이 데이터로 학습
가변 크기의 data 학습에는 적합하지 않음
RNN은
'hidden state'를 (기억) 지니고 있다는 차이점
지금까지의 입력 데이터를 요약한 정보
모든 입력 처리 후 network에 hidden state는
sequence 전체를 요약하는 정보가 됨
이전까지의 기억을 바탕으로 새로운 단어를 이해
새로운 단어마다 이 과정이 반복
그래서 recurrent (순환적)이라고 함
응용
image caption 생성
CNN과 RNN을 결합하여 image를 text로 설명
자동번역
구글번역기, 네이버 파파고 등은 모두 RNN을 으용ㅇ
RNN은 sequence 입/출력 구조
- encoder-decoder model
sequence-to-sequence
번역기에서 대표적으로 사용되는 모델
Chatbot, 기계번역등에서 응용
입력 sequence와 출력 sequence를 각각 질문과 대답으로 구성하여
챗봇, 기계 번역을 만듦
Text summarization
STT등에서도 사용
RNN을 어떻게 조립했느냐로 seq2seq를 만듦
seq2seq 구성
encoder와 decoder로 구성
encoder
입력의 모든 단어들을 순차적으로 입력 후 마지막에 이 모든 단어 정보를 압축하여 하나의 vector로 만듦
-> context vector
입력 문장 정보가 하나의 context vector로 압축
이후, 이 context vector를 decoder로 전송
decoder
decoder는 context vector를 받아 번역된 단어를 한 개씩 순차적으로 출력함
context vector
보통 수백이상의 차원을 지님
seq2seq encoder 구조
+-------------------------------+
| LSTM -> LSTM -> LSTM -> LSTM ------> context
+-------------------------------+
I am a student
je suis etudiant <eos>
^
|
+------------------------------+
context ---> | LSTM -> LSTM -> LSTM -> LSTM |
+------------------------------+
^
|
<sos> je suis etudiant
decoder는 < sos> 이후
다음에 등장할 확률이 높은 단어를 예측
첫 time step에서 je를 예측
이를 다음 RNN cell에 입력
두 번째 시점에서는 je로 부터 suis를 예측..
이렇게 반복 <eos>까지
두 개의 RNN
바닐라 RNN이 아니라 LSTM 셀 또는 GRU 셀들로 구성
입력 문장은
단어 tokenization을 통해 단어 단위로 쪼개짐
단어 token 각각은 RNN 셀의 각 시점의 입력이 됨
모든 단어 입력 후 은닉 상태를 context vector로서 decoder어 넘김
context vector는 디코더 RNN 셀의 첫번째 은닉 상태로 사용됨
decoder
RNNLM(RNN Language Model)
'AI > Machine learning' 카테고리의 다른 글
로지스틱 회귀 logistic regression (0) | 2022.03.06 |
---|---|
Modeling (모델링) (0) | 2022.03.06 |
JPype1 설치 (0) | 2021.09.10 |
XGBoost (0) | 2021.08.17 |
K-means clustering (0) | 2021.08.16 |