자연어 처리

2021. 9. 27. 13:15AI/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://bskyvision.com/251

https://simonpaarlberg.com/post/latent-semantic-analyses/

 

LSA로 텍스트 분류하기 :

https://seantrottcom.wordpress.com/2018/08/20/tutorial-document-classification-with-latent-semantic-analysis-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