'전체 글'에 해당하는 글 2175건

토픽 모델링 알고리즘인 LSA 의 단점을 보완한 대표적인 알고리즘이 잠재 디리클레 할당(LDA) 이다. 문서들에서 발견된 단어의 빈도수를 분석하여, 해당 문서가 어떤 주제를 다루고 있을지 예측할 수 있다. LDA 를 사용하여 토픽을 몇개 얻을 것인지 사용자가 지정해야 하는데, 이 하이퍼 파라미터로부터 결과가 달라질 수 있으므로 올바른 토픽을 얻기 위한 테스트가 필요하다.

 

LDA 는 각 문서의 토픽 분포와 각 토픽의 단어 분포를 도출하고 분석하여, 해당 문서가 어떤 주제들을 함께 다루고 있을지를 예측할 수 있다.


LDA 수행과정

 

  1. 문서 빈도수 기반의 표현 방법인, DTM 이나 TF-IDF 행렬을 입력으로 한다. (단어의 순서가 중요치 않음)
  2. 사용자가 LDA 에 하이퍼 파라미터인 토픽 개수(k) 를 전달한다.
  3. 문서에 사용할 토픽의 혼합을 확률 분포에 기반하여 결정한다.
  4. 랜덤으로 할당된 단어에 잘못 할당된 토픽을 각 단어들은 P(t|d) 와 P(w|t) 작업을 반복(역공학) 하며 토픽을 재할당한다.
     - 각 문서의 토픽 분포 P(t|d) : 해당 문서 d 에 나타난 토픽 t 의 비율을 보고 단어에 토픽 할당
     - 각 토픽의 단어 분포 P(w|t) : 는 전체 문서에서 단어 w 에 할당된 토픽 t 의 비율을 보고 단어에 토픽 할당

 

아래는 뉴스 기사의 제목을 모아놓은 약 100만개의 영어 데이터로부터 gensim 과 sklearn 을 사용하여 토픽을 추출하는 예제이다.

 

 

LDA with gensim

import gensim 
from gensim import corpora 

""" 전처리 결과가 아래와 같다고 할 때 
print(tokenized_doc[:5]) 
0    [well, sure, about, story, seem, biased, what,... 
1    [yeah, expect, people, read, actually, accept,... 
2    [although, realize, that, principle, your, str... 
3    [notwithstanding, legitimate, fuss, about, thi... 
4    [well, will, have, change, scoring, playoff, p... 
Name: clean_doc, dtype: object 
"""

# 각 단어에 정수 인코딩으로 (word_id, word_frequency) 의 형태로 변환 
dictionary = corpora.Dictionary(tokenized_doc) 
corpus = [dictionary.doc2bow(text) for text in tokenized_doc]  # [[(52, 1), (55, 1), ... , (88, 1), (89, 1)]] 

# LDA 토픽 모델링 (num_topics: 토픽 수, passes: 반복훈련횟수)
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics=20, id2word=dictionary, passes=15)
topics = ldamodel.print_topics(num_words=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"') 
(2, '0.012*"colorado" + 0.010*"david" + 0.006*"decenso" + 0.005*"tyre"') 
(3, '0.020*"number" + 0.018*"wire" + 0.013*"bits" + 0.013*"filename"') 
(4, '0.038*"space" + 0.013*"nasa" + 0.011*"research" + 0.010*"medical"') 
(5, '0.014*"price" + 0.010*"sale" + 0.009*"good" + 0.008*"shipping"') 
(6, '0.012*"available" + 0.009*"file" + 0.009*"information" + 0.008*"version"') 
(7, '0.021*"would" + 0.013*"think" + 0.012*"people" + 0.011*"like"') 
(8, '0.035*"window" + 0.021*"display" + 0.017*"widget" + 0.013*"application"') 
(9, '0.012*"people" + 0.010*"jesus" + 0.007*"armenian" + 0.007*"israel"') 
(10, '0.008*"government" + 0.007*"system" + 0.006*"public" + 0.006*"encryption"') 
(11, '0.013*"germany" + 0.008*"sweden" + 0.008*"switzerland" + 0.007*"gaza"') 
(12, '0.020*"game" + 0.018*"team" + 0.015*"games" + 0.013*"play"') 
(13, '0.024*"apple" + 0.014*"water" + 0.013*"ground" + 0.011*"cable"') 
(14, '0.011*"evidence" + 0.010*"believe" + 0.010*"truth" + 0.010*"church"') 
(15, '0.016*"president" + 0.010*"states" + 0.007*"united" + 0.007*"year"') 
(16, '0.047*"file" + 0.035*"output" + 0.033*"entry" + 0.021*"program"') 
(17, '0.008*"dept" + 0.008*"devils" + 0.007*"caps" + 0.007*"john"') 
(18, '0.011*"year" + 0.009*"last" + 0.007*"first" + 0.006*"runs"') 
(19, '0.013*"outlets" + 0.013*"norton" + 0.012*"quantum" + 0.008*"neck"') 
""" 

# 문서 별 토픽 분포 
for i, topic_list in enumerate(ldamodel[corpus]): 
    if i==5:  # 상위 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)] 
""" 



LDA with sklearn

 

from sklearn.feature_extraction.text import TfidfVectorizer 
from sklearn.decomposition import LatentDirichletAllocation

""" 전처리 결과가 아래와 같다고 할 때 
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 
Name: headline_text, dtype: object 
""" 

# TF-IDF 행렬 만들기 
vectorizer = TfidfVectorizer(stop_words='english', max_features= 1000)  # 상위 1,000개의 단어로 제한 
X = vectorizer.fit_transform(tokenized_doc) 

# LDA 토픽 모델링 
lda_model = LatentDirichletAllocation(n_components=10, learning_method='online', random_state=777, max_iter=1) 
lda_top = lda_model.fit_transform(X) 

terms = vectorizer.get_feature_names() # 단어 집합 

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)] 
Topic 2: [('australia', 13691.08), ('australian', 11088.95), ('melbourne', 7528.43), ('world', 6707.7), ('south', 6677.03)] 
Topic 3: [('death', 5935.06), ('interview', 5924.98), ('kill', 5851.6), ('jail', 4632.85), ('life', 4275.27)] 
Topic 4: [('house', 6113.49), ('2016', 5488.19), ('state', 4923.41), ('brisbane', 4857.21), ('tasmania', 4610.97)] 
Topic 5: [('court', 7542.74), ('attack', 6959.64), ('open', 5663.0), ('face', 5193.63), ('warn', 5115.01)] 
Topic 6: [('market', 5545.86), ('rural', 5502.89), ('plan', 4828.71), ('indigenous', 4223.4), ('power', 3968.26)] 
Topic 7: [('charge', 8428.8), ('election', 7561.63), ('adelaide', 6758.36), ('make', 5658.99), ('test', 5062.69)] 
Topic 8: [('police', 12092.44), ('crash', 5281.14), ('drug', 4290.87), ('beat', 3257.58), ('rise', 2934.92)] 
Topic 9: [('fund', 4693.03), ('labor', 4047.69), ('national', 4038.68), ('council', 4006.62), ('claim', 3604.75)] 
Topic 10: [('trump', 11966.41), ('perth', 6456.53), ('report', 5611.33), ('school', 5465.06), ('woman', 5456.76)] 
""" 

 


WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

2020년 12월 31일 월미도

 

 

스피드하게 2020 결산을 하긴 했지만, 반성할 게 별로 없다고는 썼지만 한 달 전인 그때만 해도 정신머리가 어디 고여 있었던 것 같다. 전 세계가 코로나니까, 모두가 힘드니까, 나도 그냥 이렇게... 한 해를 보냈던 것 같다. 그렇게 살아서 뭐 문제 있어? 라고 묻는다면 문제는 없지만 발전 없는 하루하루가 좋니? 라고 말하고 싶다. 근데 왜 그러구 있니? 라는 물음에는 할 말이 없어 이렇게 또 반성문을 써본다. 12월 31일에 월미도에서 석양을 보며 정신 차리려고 했는데 그날부터 시작된 음주가 오늘에서야 글을 쓰게 만들었다.ㅋ

 

그 힘든 1년간의 코로나 시대에, 집값은 2배가 됐고, 코스피는 3천을 넘었는데, 월급은 그대로고, 현금을 보유하고 있는 자들은 오히려 바보가 됐다. 딱 1년만을 보자면 그렇다. 내 노력으로 생긴 보상 보다, 운으로 생긴 보상이 더 크게 되니, 피땀의 가치가 이렇게 초라할 수 없다. 이렇게 신분 상승의 기회를 주는데도 제대로 못 받아 먹은 게 아쉬울 따름. 뭐 이런 얘기를 하려는 건 아닌데, 아무튼... 

 

코로나로 인한 재택근무가 많아지고 개인 시간도 많았었는데, 열정적으로 아무것도 하지 않은 시간이 너무 많다. 결과적으로 많이 실망스럽다. 흔히 사람들이 시간을 돈에 비유한다. 이 시간이란 돈은 쓰려고 하지 않아도 계속 돌아가고 쓰이고 있다. 그런데 이 소중한 시간을 한순간이라도 아무 생각 없이 보낸다는 게 과연 정상적인 삶일까. 다음 주면 싱글라이프도 끝나고... 지금이 딱 재정비하기 좋은 시기다. 각자 삶의 방식은 모두 다르지만, 올해 나는 이 시간을 의미 있게, 집중적으로, 효과적으로 쓰는 것을 목표로 하고 정리 한번 들어간다.

 

 

1. 잠은 최고의 보약.

 

하루에 사용할 에너지를 충전하는 시간. 규칙적인 시간에 잠들기. 5시간을 자도 일어나기 힘들고 6시간을 자도 일어나기 힘들면 5시간만 자는 게 맞다. 8시간쯤 자면 쉽게 일어나지만, 그럼 아무것도 못 하고 맨날 회사<->집만 반복하다가 하루가 끝날 것이다. 4시간을 자면 가끔 알람 소리가 안 들릴 때도 있으니 난 5시간만 자는 것이 맞는 것 같다. 점심시간 쪽잠은 안 자는 걸로... 점심 먹고 몇 분 자는 게 꿀이긴 하지만 점심 먹고 바로 잠이 들면 속도 편치 않고, 정작 밤에는 제시간에 못 잘 수도 있다.

 

 

2. 출퇴근 시간 활용.

 

자가 이용 시는 아무것도 할 수 없고, 대중교통 이용 시는 이게 참... 사람 많을 때는 낑겨서 아무것도 못 하는데 그렇다고 왕복 세시간을 포기하는 것도 아깝고... 갈아타느라 잠도 제대로 못 자고. 약 20년간을 돌이켜 보면 뭘 하던 결국 몽롱하게 있다가 잠으로 이어졌음. 어쨌든 잠들면 어쩔 수 없는 거고, 핸드폰/태블릿 활용하는 게 최선이겠지.

 

 

3. 업무 및 자기계발

 

사실 이거때매 작년에 망했다고 내가 지금 이 글을 쓰는거다. 하루 중 시간을 가장 많이 할애하는 업무시간과 자기계발시간. 이 시간에 얼마나 집중할 수 있느냐에 따라서 더 많은 시간을 확보하거나 잃을 수 있다. 재택 하면서 생긴 심각한 버릇이 일 하다 말고 자꾸 딴짓하게 된다. 감시자들이 없어서 그런 건지. TV 틀어놓고, 음악 틀어놓고 그러니 1시간이면 끝낼 일을 2시간, 3시간이 넘도록 하는 경우가 많다. 그렇다고 전기 충격 같은 걸 설치할 수도 없고, 이건 그냥 다 끄고 다시 집중하도록 노력하는 수밖에. 뽀모도로 앱도 일단 깔아봤다.ㅋ

 

 

4. 헬스

 

한 달이 넘게 헬스를 못 했고, 안 했다. 못한 이유는 헬스장 셧다운 때문이고, 집에서조차 안 한 이유는 이거 뭐 무게도 칠 것도 없고 시간만 낭비하는 거 같아서 아예 아무것도 안 했다. 그런데 코로나뿐 아니라 언제라도 전염병이 돌면 또 같은 상황이 반복될 것이 뻔하다. 이제는 자의로 헬스장을 꾸준히 다닐수 없는 세상이 왔고 나는 홈 짐을 선택했다. 이것도 방 하나를 다 차지 하니 쉽게 결정할 수는 없다. 이제 나이도 계속 들어가고 하니 중량도 필요 없고 내가 평생 무리 없이 할 수 만큼만 해서 최소한으로 준비해 보려 한다.

 

 

5. 음주

 

고질병... 시간 빨아먹는 주요인.ㅋ 사실 한 2년간 혼자 있으면서 술을 진짜 적당히 잘 마셨다. 혼술할 때는 마실만큼 마시지만, 오래 마시지도 않고, 많이 마시지도 않고, 흔히 말하는 반주로 기분 좋게 마셨지. 이렇게만 먹으면 문제없는데, 날 잡고 지인들과 만날 때가 문제. 과할 때는 다음 날 저녁쯤이 돼서야 움직여지니 만 하루를 버리는 셈인데. 이거 안 만날 수도 없고, 분위기 깨지게 덜 먹을 수도 없고. 여우 같은 마누라가 있나 토끼 같은 자식이 있나 핑계거리도 없고. 안타깝지만 간을 새 걸루 바꾸지 않는 한 이것도 방법이 없다. 술 먹자고 선창만 날리지 말아야지.

 

 

6. 효도

 

올해로 부모님이 70세를 넘기셨다. 또한 손자들로부터도 해방이 되셨다. 그리고 내가 합가를 선언했다. 아버지께서 아직 일하시고 두 분 다 아프지 않으시니, 내가 모시고 산다고는 볼 수 없다. 사실 혼자 살면서 내가 이렇게 싱글라이프에 최적화 되어 있는지 몰랐다. 내가 평생 자신 있는게 외롭지 않고 심심하지 않는거다.ㅋㅋ 부모님 잔소리나 심부름이 없어진 것은 덤. 뭐 암튼 그럼에도 불구하고 합가를 결심한 것은 모든 가족의 바램이기도 하고, '가정도 못 꾸렸는데 부모님이라도 책임져야지'라는 생각이 들었다. 아직은 먼 얘기지만, 한 분만 남게 되었을 때 모시려면 그 또한 서로 쉽지 않을 것 같았다. 그냥 일찍부터 함께 살고 있는 것이 자연스러울터. 부모님 돌아가시면 어차피 죽을 때까지 혼자 살 거. 운세에 나온 45살까지 밖에서 버텨보려 했는데... 부질없다.ㅋ 우리 세 식구 살면 큰 소리 날 일도 없고, 나만 잘하면 평생 행복하게 산다. 매일 저녁 함께 하면서 얘기 나누고, 주말도 되도록 함께 보내는 착한 아들 돼야지. 45살 전에 며느리 얻으면 더 착한 아들인데...

 

 

작년 한 해는 이 부모님을 모시는 문제 때문에 정말 고민이 깊었다. 마지막 순간에 내가 원하는 집을 찾고도 맘 편히 계약하지 못하는 나를 보면서 합가를 결심하게 됐다. 물론 지금은 맘이 엄청 편하다. 후련하고. 앞으로 남양주에서의 출퇴근이 걱정이지. 출퇴근하고 운동할 시간만 잘 짜낸다면, 남은 인생 어찌되든 상관읍따! 내년 이맘때 이 글을 보고 부끄럽지 않도록 실천 잘하자. 신축년 새해에도 아프지 않고 소처럼 열심히 일하기! (1월 1일에 눈다래끼 시전으로 액땜 끝!)

 

 


WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  2개가 달렸습니다.
  1. 구구절절 옳은 말들을.. 여전히 효자입니다. 다만 아쉬운 건 새해 시작은 입춘 기준이므로 눈다래끼는 庚子年 뒤끝입니다. (욕 같아 한자로 바꿨음) 어차피 가톨릭 신자는 열외일 겁니다.^^
secret

토픽 모델링(Topic Modeling) 이란 문서집합에서 추상적인 주제를 발견하기 위한 통계적 모델로, 잠재 의미 분석(Latent Semantic Analysis, LSA) / 잠재 디리클레 할당(Latent Dirichlet Allocation, LDA) 등의 알고리즘이 있다. LSA 는 기본적으로 DTM 이나 TF-IDF 행렬에 절단된 특이값 분해(truncated SVD) 를 사용하여 차원을 축소시키고, 단어들의 잠재적인 의미를 끌어내지만, SVD 의 특성상 새로운 데이터를 업데이트 하려면 처음부터 다시 계산해야 하는 단점이 있다.


특이값 분해(SVD) 는 A 가 m × n 행렬일 때,

3개의 행렬(U:m×m 직교행렬, VT:n×n 직교행렬, S:m×n 직사각 대각행렬) 의 곱으로 분해(decomposition) 하는 것이다.


(9 x 4) 행렬의 DTM 으로 절단된 특이값 분해(truncated SVD) 를 구하기.


import numpy as np
 
# 아래와 같은 DTM 이 있다고 할 때,
= [
    [000101100],
    [000110100],
    [011020000],
    [100000011]
]
 
# (4 x 9) 행렬에서
# 일단 특이값 분해 full SVD 구하기 : U x s x VT
# U : m×m 직교행렬,
# s : m×n 직사각 대각행렬,
# VT : n×n 직교행렬 이라 할 때,
U, s, VT = np.linalg.svd(A, full_matrices=True)
 
# 4 x 4 직교행렬 확인
print(U.round(2))
# [[ 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.  ]]
 
# 특이값 s 를 대각행렬로 바꾸고 직교행렬 구하기
print(s.round(2))
# [2.69 2.05 1.73 0.77]
= np.zeros((49))
S[:4, :4= np.diag(s)  # 특이값 s 를 대각행렬에 삽입
print(S.round(2))
# [[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.  ]]
 
# 9 x 9 직교행렬 확인
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.  ]
#  [ 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]]
cs


여기까지 구해본 full SVD 를 역으로 계산해 보면 U x S x VT = A 와 같음을 알 수 있다.

이제 3개 행렬을 축소시킨 truncated SVD 를 구하여 다른 문서나 단어의 유사도를 구할 수 있다.


# Truncated SVD 구하기
# 특이값 상위 2개만 남기기 (t = 2)
= U[:, :2]
= S[:2, :2]
VT = VT[:2, :]
print(np.dot(np.dot(U,S), VT).round(2))
# [[ 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.  ]]
cs


위와 같이 전체 코퍼스에서 절단된 특이값 분해를 구해야 하므로, 데이터를 추가하게 되면 전과정을 처음부터 다시 실행해야 하는 단점이 있다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

문서들 간에 유사도를 구하기 위해서는 문서마다 동일한 단어나 비슷한 단어가 얼마나 사용되었는지를 파악할 수 있다. BoW, DTM, TF-IDF, Word2Vec 등의 방법으로 단어를 수치화 했다면, 유사도 기법을 사용하여 문서의 유사도를 구하는 게 가능하다.


  1. 코사인 유사도(Cosine similarity) 는 두 벡터의 방향에 따라 1~ -1 의 값을 가지며 1에 가까울수록 유사하다.
  2. 유클리드 거리(Euclidean distance) 는 두 점 사이의 직선 거리를 구하여 거리의 값이 작을수록 유사하다.
  3. 자카드 유사도(Jaccard similarity) 는 두개의 집합이 있을 때 합집합에서 교집합의 비율을 구하며, 1에 가까울 수록 유사하다.


문서1 : 저는 사과 좋아요

문서2 : 저는 바나나 좋아요

문서3 : 저는 바나나 좋아요 저는 바나나 좋아요


위 문서간의 유사도 구하기


from sklearn.feature_extraction.text import CountVectorizer
from numpy import dot
from numpy.linalg import norm
import numpy as np
 
corpus = [
    "저는 사과 좋아요",
    "저는 바나나 좋아요",
    "저는 바나나 좋아요 저는 바나나 좋아요",
]
vector = CountVectorizer()
dtm = vector.fit_transform(corpus).toarray()
print(dtm)
# [[0 1 1 1]
#  [1 0 1 1]
#  [2 0 2 2]]
 
def cos_sim(A, B):
    return dot(A, B) / (norm(A)*norm(B))
 
print(cos_sim(dtm[0], dtm[1])) #문서1과 문서2의 코사인 유사도 0.6666666666666667
print(cos_sim(dtm[0], dtm[2])) #문서1과 문서3의 코사인 유사도 0.6666666666666667
print(cos_sim(dtm[1], dtm[2])) #문서2과 문서3의 코사인 유사도 1.0000000000000002
 
def dist(A, B):
    return np.sqrt(np.sum((A-B)**2))
 
print(dist(dtm[0], dtm[1])) #문서1과 문서2의 유클리드 거리 1.4142135623730951
print(dist(dtm[0], dtm[2])) #문서1과 문서3의 유클리드 거리 2.6457513110645907
print(dist(dtm[1], dtm[2])) #문서2과 문서3의 유클리드 거리 1.7320508075688772
 
corp1 = corpus[0].split()
corp2 = corpus[1].split()
corp3 = corpus[2].split()
 
def jaccard(A, B):
    union = set(A).union(set(B))  # 합집합
    intersection = set(A).intersection(set(B))  # 교집합
    return len(intersection) / len(union)
 
print(jaccard(dtm[0], dtm[1])) #문서1과 문서2의 자카드 유사도 1.0
print(jaccard(dtm[0], dtm[2])) #문서1과 문서3의 자카드 유사도 0.3333333333333333
print(jaccard(dtm[1], dtm[2])) #문서2과 문서3의 자카드 유사도 0.3333333333333333
cs


위 코드에서 처럼 문서의 유사도의 성능은 각 문서의 단어들의 수치화 방법, 유사도 방법에 따라 다르다는 것을 알 수 있다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

문서 단어 행렬(DTM) 의 단점, 중요한 단어에 대해서 가중치를 주지 못하는 단점을 보완한 방법이 단어 빈도-역 문서 빈도(TF-IDF: Term Frequency-Inverse Document Frequency) 이다. 문서의 빈도에 특정 식을 취하여 DTM 내의 각 단어들에 가중치를 주는 방법이다. TF-IDF 의 값이 높을수록 중요도가 높다. TF-IDF 의 특징은 모든 문서에서 자주 등장하는 a 나 the 같은 단어는 중요도가 낮다고 판단하며, 특정 문서에서만 자주 등장하는 단어를 중요도가 높다고 판단한다.



IF-IDF 에 적용되는 특정 식은 TF 값 과 IDF 값을 곱하는 것이다. (IDF 값은 DF 값의 역수이다.)

  • tf(d,t) : 특정 문서 d 에서의 특정 단어 t 의 등장 횟수.
  • df(t) : 특정 단어 t가 등장한 문서의 수.
  • idf(d,t) : df(t)에 반비례하는 수. n 을 총 문서 개수라고 할 때, 자연로그 ln(n/(1+df(t))) 


import pandas as pd
from math import log
 
docs = [
  '먹고 싶은 사과',
  '먹고 싶은 바나나',
  '길고 노란 바나나 바나나',
  '저는 과일이 좋아요'
]
 
vocab = list(set(w for doc in docs for w in doc.split()))  # 중복 제거, 단어 토큰화
vocab.sort()  # 오름차순 정렬 : ['과일이', '길고', '노란', '먹고', '바나나', '사과', '싶은', '저는', '좋아요']
 
= len(docs)  # 총 문서 수
 
def tf(t, d):
    return d.count(t)
 
def idf(t):
    df = 0
    for doc in docs:
        df += t in doc  # in 연산. True / False... True = 1
    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 : 문서별 단어 빈도수 구하기
        # [[0, 0, 0, 1, 0, 1, 1, 0, 0], [0, 0, 0, 1, ...
 
tf_ = pd.DataFrame(result, columns = vocab)
""" DTM
   과일이  길고  노란  먹고  바나나  사과  싶은  저는  좋아요
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
"""
 
result = []
for j in range(len(vocab)):
    t = vocab[j]
    result.append(idf(t))
 
idf_ = pd.DataFrame(result, index = vocab, columns = ["IDF"])
"""
          IDF
과일이  0.693147
길고   0.693147
노란   0.693147
먹고   0.287682
바나나  0.287682
사과   0.693147
싶은   0.287682
저는   0.693147
좋아요  0.693147
"""
 
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)
"""
        과일이        길고        노란  ...        싶은        저는       좋아요
0  0.000000  0.000000  0.000000  ...  0.287682  0.000000  0.000000
1  0.000000  0.000000  0.000000  ...  0.287682  0.000000  0.000000
2  0.000000  0.693147  0.693147  ...  0.000000  0.000000  0.000000
3  0.693147  0.000000  0.000000  ...  0.000000  0.693147  0.693147
"""
cs


공교롭게도 바나나가 잘렸지만; 문서2 에서의 바나나 tfidf(0.28) 보다, 문서3 에서의 바나나 tfidf(0.57) 가 높은 것으로 보아 문서3 에서의 바나나가 더 중요하다는 것을 인식해야 한다.



TfidfVectorizer 를 이용한 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}
"""
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret