Python : tf-idf-cosine : 문서 유사성 찾기


93

Part 1 & Part 2 에서 사용할 수있는 튜토리얼을 따르고있었습니다 . 불행히도 저자는 실제로 두 문서 사이의 거리를 찾기 위해 코사인 유사성을 사용하는 마지막 섹션에 대한 시간이 없었습니다. 나는 stackoverflow 의 다음 링크의 도움으로 기사의 예제를 따랐 습니다. 위 링크에 언급 된 코드가 포함되어 있습니다 (삶을 더 쉽게 만들기 위해)

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from nltk.corpus import stopwords
import numpy as np
import numpy.linalg as LA

train_set = ["The sky is blue.", "The sun is bright."]  # Documents
test_set = ["The sun in the sky is bright."]  # Query
stopWords = stopwords.words('english')

vectorizer = CountVectorizer(stop_words = stopWords)
#print vectorizer
transformer = TfidfTransformer()
#print transformer

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray()
testVectorizerArray = vectorizer.transform(test_set).toarray()
print 'Fit Vectorizer to train set', trainVectorizerArray
print 'Transform Vectorizer to test set', testVectorizerArray

transformer.fit(trainVectorizerArray)
print
print transformer.transform(trainVectorizerArray).toarray()

transformer.fit(testVectorizerArray)
print 
tfidf = transformer.transform(testVectorizerArray)
print tfidf.todense()

위의 코드의 결과로 다음과 같은 매트릭스가 있습니다.

Fit Vectorizer to train set [[1 0 1 0]
 [0 1 0 1]]
Transform Vectorizer to test set [[0 1 1 1]]

[[ 0.70710678  0.          0.70710678  0.        ]
 [ 0.          0.70710678  0.          0.70710678]]

[[ 0.          0.57735027  0.57735027  0.57735027]]

코사인 유사성을 계산하기 위해이 출력을 사용하는 방법을 잘 모르겠습니다. 길이가 비슷한 두 벡터에 대해 코사인 유사성을 구현하는 방법을 알고 있지만 여기서는 두 벡터를 식별하는 방법을 잘 모르겠습니다.


3
trainVectorizerArray의 각 벡터에 대해 testVectorizerArray의 벡터와 코사인 유사성을 찾아야합니다.
-08-25

@excray 고마워요, 당신의 도움이 된 포인트로 내가 그것을 알아 냈습니다. 대답을 넣어야합니까?
추가 세미콜론

@excray하지만 작은 질문이 있습니다. actuall tf * idf 계산은 행렬에 표시된 최종 결과를 사용하지 않기 때문에 이것을 사용할 수 없습니다.
추가 세미콜론

4
다음은 질문에 대한 자세한 답변을 인용 한 튜토리얼의 세 번째 부분입니다. pyevolve.sourceforge.net/wordpress/?p=2497
Clément Renaud

@ ClémentRenaud 나는 당신이 제공 한 링크를 따랐지만 내 문서가 커질수록 MemoryError를 던지기 시작합니다. 어떻게 처리 할 수 ​​있습니까?
ashim888

답변:


172

먼저 카운트 기능을 추출하고 TF-IDF 정규화 및 행 단위 유클리드 정규화를 적용하려면 다음을 사용하여 한 번의 작업으로 수행 할 수 있습니다 TfidfVectorizer.

>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> from sklearn.datasets import fetch_20newsgroups
>>> twenty = fetch_20newsgroups()

>>> tfidf = TfidfVectorizer().fit_transform(twenty.data)
>>> tfidf
<11314x130088 sparse matrix of type '<type 'numpy.float64'>'
    with 1787553 stored elements in Compressed Sparse Row format>

이제 한 문서 (예 : 데이터 세트의 첫 번째 문서)와 다른 모든 문서의 코사인 거리를 찾으려면 tfidf 벡터가 이미 행 정규화되어 있으므로 첫 번째 벡터의 내적을 다른 모든 문서와 계산하면됩니다.

Chris Clark이 주석과 여기 에서 설명했듯이 코사인 유사성은 벡터의 크기를 고려하지 않습니다. 행 정규화의 크기는 1이므로 선형 커널은 유사성 값을 계산하기에 충분합니다.

scipy 희소 행렬 API는 약간 이상합니다 (밀도 N 차원 numpy 배열만큼 유연하지 않음). 첫 번째 벡터를 얻으려면 행 단위로 행렬을 슬라이스하여 단일 행이있는 부분 행렬을 얻어야합니다.

>>> tfidf[0:1]
<1x130088 sparse matrix of type '<type 'numpy.float64'>'
    with 89 stored elements in Compressed Sparse Row format>

scikit-learn은 이미 벡터 컬렉션의 조밀 한 표현과 희소 표현 모두에 대해 작동하는 쌍 단위 메트릭 (머신 학습 용어로 커널)을 제공합니다. 이 경우 선형 커널이라고도하는 내적이 필요합니다.

>>> from sklearn.metrics.pairwise import linear_kernel
>>> cosine_similarities = linear_kernel(tfidf[0:1], tfidf).flatten()
>>> cosine_similarities
array([ 1.        ,  0.04405952,  0.11016969, ...,  0.04433602,
    0.04457106,  0.03293218])

따라서 상위 5 개의 관련 문서를 찾기 위해 argsort몇 가지 음의 배열 슬라이싱을 사용할 수 있습니다 (대부분의 관련 문서는 가장 높은 코사인 유사성 값을 가지므로 정렬 된 인덱스 배열의 끝에 있음).

>>> related_docs_indices = cosine_similarities.argsort()[:-5:-1]
>>> related_docs_indices
array([    0,   958, 10576,  3277])
>>> cosine_similarities[related_docs_indices]
array([ 1.        ,  0.54967926,  0.32902194,  0.2825788 ])

첫 번째 결과는 온 전성 검사입니다. 다음 텍스트가있는 코사인 유사성 점수가 1 인 가장 유사한 문서 인 쿼리 문서를 찾습니다.

>>> print twenty.data[0]
From: lerxst@wam.umd.edu (where's my thing)
Subject: WHAT car is this!?
Nntp-Posting-Host: rac3.wam.umd.edu
Organization: University of Maryland, College Park
Lines: 15

 I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.

Thanks,
- IL
   ---- brought to you by your neighborhood Lerxst ----

두 번째로 가장 유사한 문서는 원본 메시지를 인용하는 회신이므로 많은 공통 단어가 있습니다.

>>> print twenty.data[958]
From: rseymour@reed.edu (Robert Seymour)
Subject: Re: WHAT car is this!?
Article-I.D.: reed.1993Apr21.032905.29286
Reply-To: rseymour@reed.edu
Organization: Reed College, Portland, OR
Lines: 26

In article <1993Apr20.174246.14375@wam.umd.edu> lerxst@wam.umd.edu (where's my
thing) writes:
>
>  I was wondering if anyone out there could enlighten me on this car I saw
> the other day. It was a 2-door sports car, looked to be from the late 60s/
> early 70s. It was called a Bricklin. The doors were really small. In
addition,
> the front bumper was separate from the rest of the body. This is
> all I know. If anyone can tellme a model name, engine specs, years
> of production, where this car is made, history, or whatever info you
> have on this funky looking car, please e-mail.

Bricklins were manufactured in the 70s with engines from Ford. They are rather
odd looking with the encased front bumper. There aren't a lot of them around,
but Hemmings (Motor News) ususally has ten or so listed. Basically, they are a
performance Ford with new styling slapped on top.

>    ---- brought to you by your neighborhood Lerxst ----

Rush fan?

--
Robert Seymour              rseymour@reed.edu
Physics and Philosophy, Reed College    (NeXTmail accepted)
Artificial Life Project         Reed College
Reed Solar Energy Project (SolTrain)    Portland, OR

후속 질문 : 문서 수가 매우 많은 경우 2 단계의 linear_kernel 함수는 행 수에 선형이기 때문에 성능 병목 현상이 될 수 있습니다. 그것을 하위 선형으로 줄이는 방법에 대한 생각이 있습니까?
Shuo 2014

하위 선형 확장 성 프로필로 대략적인 답변을 산출해야하는 Elastic Search 및 Solr의 "보다 유사한"쿼리를 사용할 수 있습니다.
ogrisel

7
이렇게하면 첫 번째 문서가 아닌 다른 모든 문서와 각 문서의 코사인 유사성이 제공 cosine_similarities = linear_kernel(tfidf, tfidf)됩니까?
ionox0

2
예, 이것은 쌍대 유사성의 정사각형 행렬을 제공합니다.
ogrisel

10
다른 사람들이 저처럼 궁금해하는 경우,이 경우 linear_kernel은 TfidfVectorizer가 정규화 된 벡터를 생성하기 때문에 cosine_similarity와 동일합니다. 워드 프로세서의 참고 사항을 참조하십시오 : scikit-learn.org/stable/modules/metrics.html#cosine-similarity
크리스 클라크

22

@excray의 의견을 통해 답을 찾을 수있었습니다. 우리가해야 할 일은 실제로 기차 데이터와 테스트 데이터를 나타내는 두 배열을 반복하는 간단한 for 루프를 작성하는 것입니다.

먼저 코사인 계산을위한 공식을 유지하는 간단한 람다 함수를 구현합니다.

cosine_function = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3)

그런 다음 to 벡터를 반복하는 간단한 for 루프를 작성하면 논리는 모든 "trainVectorizerArray의 각 벡터에 대해 testVectorizerArray의 벡터와 코사인 유사성을 찾아야합니다."

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from nltk.corpus import stopwords
import numpy as np
import numpy.linalg as LA

train_set = ["The sky is blue.", "The sun is bright."] #Documents
test_set = ["The sun in the sky is bright."] #Query
stopWords = stopwords.words('english')

vectorizer = CountVectorizer(stop_words = stopWords)
#print vectorizer
transformer = TfidfTransformer()
#print transformer

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray()
testVectorizerArray = vectorizer.transform(test_set).toarray()
print 'Fit Vectorizer to train set', trainVectorizerArray
print 'Transform Vectorizer to test set', testVectorizerArray
cx = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3)

for vector in trainVectorizerArray:
    print vector
    for testV in testVectorizerArray:
        print testV
        cosine = cx(vector, testV)
        print cosine

transformer.fit(trainVectorizerArray)
print
print transformer.transform(trainVectorizerArray).toarray()

transformer.fit(testVectorizerArray)
print 
tfidf = transformer.transform(testVectorizerArray)
print tfidf.todense()

다음은 출력입니다.

Fit Vectorizer to train set [[1 0 1 0]
 [0 1 0 1]]
Transform Vectorizer to test set [[0 1 1 1]]
[1 0 1 0]
[0 1 1 1]
0.408
[0 1 0 1]
[0 1 1 1]
0.816

[[ 0.70710678  0.          0.70710678  0.        ]
 [ 0.          0.70710678  0.          0.70710678]]

[[ 0.          0.57735027  0.57735027  0.57735027]]

1
nice .. 나도 처음부터 배우고 있으며 귀하의 질문과 답변은 따라 가기 가장 쉽습니다. 롤-자신의 방법 대신 np.corrcoef ()를 사용할 수 있다고 생각합니다.
wbg

transformer.fit운영 의 목적은 무엇 이며 tfidf.todense()? 루프에서 유사성 값을 얻은 다음 계속 tfidf를 수행합니까? 계산 된 코사인 값은 어디에 사용됩니까? 귀하의 예는 혼란 스럽습니다.
미네랄

설명하는 데 신경 쓰지 않으면 코사인이 정확히 무엇을 반환합니까? 귀하의 예에서 0.4080.816, 이러한 값은 무엇입니까?
buydadip

20

나는 그것의 오래된 포스트를 안다. 하지만 http://scikit-learn.sourceforge.net/stable/ 패키지를 시도했습니다 . 여기에 코사인 유사성을 찾는 코드가 있습니다. 문제는이 패키지와의 코사인 유사성을 어떻게 계산할 것인가였으며 여기에 대한 내 코드가 있습니다.

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

f = open("/root/Myfolder/scoringDocuments/doc1")
doc1 = str.decode(f.read(), "UTF-8", "ignore")
f = open("/root/Myfolder/scoringDocuments/doc2")
doc2 = str.decode(f.read(), "UTF-8", "ignore")
f = open("/root/Myfolder/scoringDocuments/doc3")
doc3 = str.decode(f.read(), "UTF-8", "ignore")

train_set = ["president of India",doc1, doc2, doc3]

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix_train = tfidf_vectorizer.fit_transform(train_set)  #finds the tfidf score with normalization
print "cosine scores ==> ",cosine_similarity(tfidf_matrix_train[0:1], tfidf_matrix_train)  #here the first element of tfidf_matrix_train is matched with other three elements

여기에서 쿼리가 train_set의 첫 번째 요소이고 doc1, doc2 및 doc3이 코사인 유사성의 도움으로 순위를 매기려는 문서라고 가정합니다. 이 코드를 사용할 수 있습니다.

또한 질문에 제공된 자습서는 매우 유용했습니다. 여기에 대한 모든 부분이 있습니다 part-I , part-II , part-III

출력은 다음과 같습니다.

[[ 1.          0.07102631  0.02731343  0.06348799]]

여기서 1은 쿼리가 자체와 일치 함을 나타내고 나머지 3 개는 쿼리를 각 문서와 일치시키는 점수입니다.


1
cosine_similarity (tfidf_matrix_train [0 : 1], tfidf_matrix_train) 1이 수천 이상으로 변경되면 어떨까요? 어떻게 처리 할 수 ​​있습니까?
ashim888 2014 년

1
처리하는 방법ValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2
pyd를

17

제가 작성한 또 다른 튜토리얼을 알려 드리겠습니다. 그것은 귀하의 질문에 대한 답을 제공하지만 우리가 왜 일부 일을하고 있는지 설명합니다. 나는 또한 그것을 간결하게 만들려고 노력했다.

그래서 당신 list_of_documents은 단지 문자열의 배열이고 다른 document하나는 단지 문자열입니다. list_of_documents에서 가장 유사한 문서를 찾아야 합니다 document.

함께 결합 해 보겠습니다. documents = list_of_documents + [document]

종속성부터 시작하겠습니다. 우리가 각각을 사용하는 이유가 분명해질 것입니다.

from nltk.corpus import stopwords
import string
from nltk.tokenize import wordpunct_tokenize as tokenize
from nltk.stem.porter import PorterStemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy.spatial.distance import cosine

사용할 수있는 접근 방식 중 하나는 bag-of-words 접근 방식입니다. 여기서 문서의 각 단어를 다른 단어와 독립적으로 취급하고 모든 단어를 큰 가방에 함께 넣습니다. 한 관점에서는 단어가 연결되는 방식과 같은 많은 정보를 잃어 버리지 만 다른 관점에서는 모델을 단순하게 만듭니다.

영어와 다른 인간의 언어에는 'a', 'the', 'in'과 같은 "쓸모없는"단어가 너무나 흔해서 의미가 많지 않습니다. 그들 불리는 중지 단어를 그리고 그들을 제거하는 좋은 아이디어이다. 또 하나 눈에 띄는 것은 '분석', '분석자', '분석'과 같은 단어가 정말 비슷하다는 것입니다. 그것들은 공통된 어근을 가지고 있으며 모두 하나의 단어로 변환 될 수 있습니다. 이 프로세스를 형태소 분석 이라고 하며 속도, 공격성 등이 다른 여러 형태소 분석기가 있습니다. 그래서 우리는 각 문서를 불용어가없는 단어의 어간 목록으로 변환합니다. 또한 모든 구두점을 삭제합니다.

porter = PorterStemmer()
stop_words = set(stopwords.words('english'))

modified_arr = [[porter.stem(i.lower()) for i in tokenize(d.translate(None, string.punctuation)) if i.lower() not in stop_words] for d in documents]

그렇다면이 단어들이 우리에게 어떻게 도움이 될까요? 우리가 3 봉지를 상상해 [a, b, c], [a, c, a][b, c, d]. 기본적 으로 벡터 로 변환 할 수 있습니다. [a, b, c, d] . 그래서 우리는 벡터로 끝납니다 : [1, 1, 1, 0], [2, 0, 1, 0]그리고 [0, 1, 1, 1]. 비슷한 것은 우리의 문서에서도 마찬가지입니다 (벡터 만 더 길어질 것입니다). 이제 우리는 많은 단어를 제거하고 벡터의 차원을 줄이기 위해 다른 단어도 제거했음을 알 수 있습니다. 여기에 흥미로운 관찰이 있습니다. 긴 문서는 짧은 것보다 더 많은 양의 요소를 가지므로 벡터를 정규화하는 것이 좋습니다. 이를 빈도 TF라는 용어라고하며, 사람들은 다른 문서에서 단어가 얼마나 자주 사용되는지에 대한 추가 정보 (역 문서 빈도 IDF)를 사용했습니다. 함께 우리는 몇 가지 특징을 가진 메트릭 TF-IDF를 가지고 있습니다.. 이것은 sklearn에서 한 줄로 달성 할 수 있습니다 :-)

modified_doc = [' '.join(i) for i in modified_arr] # this is only to convert our list of lists to list of strings that vectorizer uses.
tf_idf = TfidfVectorizer().fit_transform(modified_doc)

실제로 벡터 라이저 사용하면 제거 및 소문자와 같은 많은 작업을 수행 할 수 있습니다 . sklearn에는 영어가 아닌 불용어가 없기 때문에 별도의 단계로 수행했지만 nltk에는 있습니다.

그래서 우리는 모든 벡터를 계산했습니다. 마지막 단계는 마지막 단계와 가장 유사한 것을 찾는 것입니다. 이를 달성하는 데는 여러 가지 방법이 있습니다. 그중 하나는 여기에서 논의한 이유로 그리 크지 않은 유클리드 거리입니다 . 또 다른 접근 방식은 코사인 유사성 입니다. 모든 문서를 반복하고 문서와 마지막 문서 간의 코사인 유사성을 계산합니다.

l = len(documents) - 1
for i in xrange(l):
    minimum = (1, None)
    minimum = min((cosine(tf_idf[i].todense(), tf_idf[l + 1].todense()), i), minimum)
print minimum

이제 최소는 최고의 문서와 점수에 대한 정보를 갖게됩니다.


3
서명, 이것은 op가 요구 한 것이 아닙니다 : 말뭉치에서 "최고의 문서"가 아닌 질의가 주어진 최고의 문서를 검색하는 것입니다. 제발 그렇게하지 마십시오. 나 같은 ppl은 op 작업에 대한 예제를 사용하는 데 시간을 낭비하고 매트릭스 크기 조정 광기로 끌려 갈 것입니다.
미네랄

그리고 어떻게 다른가요? 아이디어는 완전히 동일합니다. 특징을 추출하고 쿼리와 문서 사이의 코사인 거리를 계산합니다.
Salvador Dali

동일한 모양의 행렬에서 이것을 계산하고 다른 예제를 시도해보십시오. 여기서 다른 크기의 쿼리 행렬, 연산의 기차 집합 및 테스트 집합이 있습니다. 코드가 작동하도록 수정할 수 없었습니다.
미네랄

@SalvadorDali 지적했듯이 위의 내용은 다른 질문에 대한 답입니다. 쿼리와 문서가 동일한 말뭉치의 일부라고 가정하고 있는데 이는 잘못된 것입니다. 이로 인해 동일한 말뭉치 (동일한 차원)에서 파생 된 벡터의 거리를 사용하는 잘못된 접근 방식이 발생하며 일반적으로 그럴 필요가 없습니다. 질의와 문서가 서로 다른 말뭉치에 속하는 경우, 생성 된 벡터가 동일한 공간에 있지 않을 수 있으며 위에서 수행 한대로 거리를 계산하는 것은 의미가 없습니다 (동일한 수의 차원도 갖지 않음).
gented apr

12

이것은 당신을 도울 것입니다.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity  

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(train_set)
print tfidf_matrix
cosine = cosine_similarity(tfidf_matrix[length-1], tfidf_matrix)
print cosine

출력은 다음과 같습니다.

[[ 0.34949812  0.81649658  1.        ]]

9
길이는 어떻게 얻습니까?
gogasca

3

다음은 훈련 데이터에 맞는 Tf-Idf 변환기를 사용하여 테스트 데이터를 훈련 데이터와 비교하는 함수입니다. 장점은 n 개의 가장 가까운 요소를 찾기 위해 빠르게 피벗하거나 그룹화 할 수 있고 계산이 행렬 방식으로 내려 간다는 것입니다.

def create_tokenizer_score(new_series, train_series, tokenizer):
    """
    return the tf idf score of each possible pairs of documents
    Args:
        new_series (pd.Series): new data (To compare against train data)
        train_series (pd.Series): train data (To fit the tf-idf transformer)
    Returns:
        pd.DataFrame
    """

    train_tfidf = tokenizer.fit_transform(train_series)
    new_tfidf = tokenizer.transform(new_series)
    X = pd.DataFrame(cosine_similarity(new_tfidf, train_tfidf), columns=train_series.index)
    X['ix_new'] = new_series.index
    score = pd.melt(
        X,
        id_vars='ix_new',
        var_name='ix_train',
        value_name='score'
    )
    return score

train_set = pd.Series(["The sky is blue.", "The sun is bright."])
test_set = pd.Series(["The sun in the sky is bright."])
tokenizer = TfidfVectorizer() # initiate here your own tokenizer (TfidfVectorizer, CountVectorizer, with stopwords...)
score = create_tokenizer_score(train_series=train_set, new_series=test_set, tokenizer=tokenizer)
score

   ix_new   ix_train    score
0   0       0       0.617034
1   0       1       0.862012


np.arange (0, len (score))의 인덱스 : value = score.loc [index, 'score']
Golden Lion
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.