1. 유사도 측정 실습
◼ 데이터
sen_1 = '오늘 점심에 배가 너무 고파서 밥을 너무 많이 먹었다'
sen_2 = '오늘 점심에 배가 고파서 밥을 많이 먹었다'
sen_3 = '오늘 배가 너무 고파서 점심에 밥을 너무 많이 먹었다'
sen_4 = '오늘 점심에 배가 고파서 지하철을 많이 먹었다'
sen_5 = '어제 저녁에 밥을 너무 많이 먹었더니 배가 부르다'
sen_6 = '이따가 오후 6시에 출발하는 비행기가 3시간 연착 되었다고 하네요'
training_documents = [sen_1, sen_2, sen_3, sen_4, sen_5, sen_6]
for text in training_documents:
print(text)
|
오늘 점심에 배가 너무 고파서 밥을 너무 많이 먹었다 오늘 점심에 배가 고파서 밥을 많이 먹었다 오늘 배가 너무 고파서 점심에 밥을 너무 많이 먹었다 오늘 점심에 배가 고파서 지하철을 많이 먹었다 어제 저녁에 밥을 너무 많이 먹었더니 배가 부르다 이따가 오후 6시에 출발하는 비행기가 3시간 연착 되었다고 하네요 |
◼ 백터 생성
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
vectorizer.fit(training_documents)
word_idx = vectorizer.vocabulary_
word_idx
|
{'오늘': 14, '점심에': 18, '배가': 9, '너무': 3, '고파서': 2, '밥을': 8, '많이': 5, '먹었다': 6, '지하철을': 19, '어제': 12, '저녁에': 17, '먹었더니': 7, '부르다': 10, '이따가': 16, '오후': 15, '6시에': 1, '출발하는': 20, '비행기가': 11, '3시간': 0, '연착': 13, '되었다고': 4, '하네요': 21} |
◼ word_idx를 idx 순서대로 정렬
for key, idx in sorted(word_idx.items()):
print(f'{key}: {idx}')
|
3시간: 0 6시에: 1 고차서: 2 고파서: 3 너무: 4 되었다고: 5 많이: 6 먹었다: 7 먹었더니: 8 밥을: 9 배가: 10 부르다: 11 비행기가: 12 어제: 13 연착: 14 오늘: 15 오후: 16 이따가: 17 저녁에: 18 점심에: 19 지하철을: 20 출발하는: 21 하네요: 22 |
◼ word_idx에 따라 dataframe을 생성
# word_idx에 따라 dataframe을 생성
# 컬럼 : key, 인덱스 : 문장idx, 값: 빈도수
# 오늘 점심에 배가 너무 고파서...
# 0 1 1 1 2 1
import pandas as pd
result = []
vocab = list(word_idx.keys())
for i in range(len(training_documents)):
result.append([])
d = training_documents[i]
for j in range(len(vocab)):
target = vocab[j]
result[-1].append(d.count(target))
tf = pd.DataFrame(result, columns = vocab)
tf
|
◼ 유사도를 측정할 문장들을 문장 - 단어 행렬 기반 임베딩으로 변환
vector_sen_1 = vectorizer.transform([sen_1]).toarray()[0]
vector_sen_2 = vectorizer.transform([sen_2]).toarray()[0]
vector_sen_3 = vectorizer.transform([sen_3]).toarray()[0]
vector_sen_4 = vectorizer.transform([sen_4]).toarray()[0]
vector_sen_5 = vectorizer.transform([sen_5]).toarray()[0]
vector_sen_6 = vectorizer.transform([sen_6]).toarray()[0]
print(vector_sen_1)
print(vector_sen_2)
print(vector_sen_3)
print(vector_sen_4)
print(vector_sen_5)
print(vector_sen_6)
|
[0 0 1 2 0 1 1 0 1 1 0 0 0 0 1 0 0 0 1 0 0 0] [0 0 1 0 0 1 1 0 1 1 0 0 0 0 1 0 0 0 1 0 0 0] [0 0 1 2 0 1 1 0 1 1 0 0 0 0 1 0 0 0 1 0 0 0] [0 0 1 0 0 1 1 0 0 1 0 0 0 0 1 0 0 0 1 1 0 0] [0 0 0 1 0 1 0 1 1 1 1 0 1 0 0 0 0 1 0 0 0 0] [1 1 0 0 1 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 1 1] |
◼ 코사인 기반 유사도 계산
import numpy as np
from numpy import dot
from numpy.linalg import norm
def cos_sim(A, B):
return dot(A, B) / (norm(A) * norm(B))
|
이 코드는 두 벡터 A와 B 사이의 코사인 유사도를 계산하는 함수를 정의합니다. 코사인 유사도는 두 벡터 간의 각도를 측정하여 벡터 간의 유사성을 나타내는 지표로, 1에 가까울수록 두 벡터가 유사하고, -1에 가까울수록 두 벡터가 반대 방향을 가리킴을 의미합니다. |
- sen_1, sen_2 : 의미가 유사한 문장 간 유사도 계산 (조사 생략)
print(f'sen_1, sen_2: {cos_sim(vector_sen_1, vector_sen_2)}')
|
sen_1, sen_2: 0.7977240352174656 |
- sen_1, sen_3: 의미가 유사한 문장 간 유사도 계산 (순서 변경)
print(f'sen_1, sen_3: {cos_sim(vector_sen_1, vector_sen_3)}')
|
sen_1, sen_3: 1.0 |
- sen_2, sen_4: 문자내 단어를 임의의 단어로 치환한 문장과 원 문장 간 유사도 계산
print(f'sen_2, sen_4: {cos_sim(vector_sen_2, vector_sen_4)}')
|
sen_2, sen_4: 0.857142857142857 |
- sen_1, sen_5: 의미는 다르지만 비슷한 주제를 가지는 문장 간 유사도 계산
print(f'sen_1, sen_5: {cos_sim(vector_sen_1, vector_sen_5)}')
|
sen_1, sen_5: 0.5330017908890261 |
- sen_1, sen_6: 의미가 서로 다른 문장 간 유사도 계산
print(f'sen_1, sen_6: {cos_sim(vector_sen_1, vector_sen_6)}')
|
sen_1, sen_6: 0.0 |
sen_1 = '오늘 점심에 배가 너무 고파서 밥을 너무 많이 먹었다' sen_2 = '오늘 점심에 배가 고파서 밥을 많이 먹었다' sen_3 = '오늘 배가 너무 고파서 점심에 밥을 너무 많이 먹었다' sen_4 = '오늘 점심에 배가 고파서 지하철을 많이 먹었다' sen_5 = '어제 저녁에 밥을 너무 많이 먹었더니 배가 부르다' sen_6 = '이따가 오후 6시에 출발하는 비행기가 3시간 연착 되었다고 하네요' |
* sen_1, sen_2 : 의미가 유사한 문장 간 유사도 계산 (조사 생략) -> 0.7977240352174656 * sen_1, sen_3: 의미가 유사한 문장 간 유사도 계산 (순서 변경) -> 1.0 * sen_2, sen_4: 문자내 단어를 임의의 단어로 치환한 문장과 원 문장 간 유사도 계산 -> 0.857142857142857 * sen_1, sen_5: 의미는 다르지만 비슷한 주제를 가지는 문장 간 유사도 계산 -> 0.5330017908890261 * sen_1, sen_6: 의미가 서로 다른 문장 간 유사도 계산 -> 0.0 |
◼ TF-IDF 기반의 문서-단어 행렬을 사용하여 각 단어의 인덱스를 출력
# TF-IDF기반 문서 - 단어 행렬을 활용한 문장 간 유사도 측정
from sklearn.feature_extraction.text import TfidfVectorizer
tfidfv = TfidfVectorizer().fit(training_documents)
for key, idx in sorted(tfidfv.vocabulary_.items()):
print(f'{key}: {idx}')
|
3시간: 0 6시에: 1 고파서: 2 너무: 3 되었다고: 4 많이: 5 먹었다: 6 먹었더니: 7 밥을: 8 배가: 9 부르다: 10 비행기가: 11 어제: 12 연착: 13 오늘: 14 오후: 15 이따가: 16 저녁에: 17 점심에: 18 지하철을: 19 출발하는: 20 하네요: 21 |
◼ TF-IDF (Term Frequency-Inverse Document Frequency) 변환을 수행하고 그 결과를 배열 형태로 출력
tf_idf = tfidfv.transform(training_documents).toarray()
print(tf_idf)
|
[[0. 0. 0.28941449 0.67547293 0. 0.24993256 0.28941449 0. 0.28941449 0.24993256 0. 0. 0. 0. 0.28941449 0. 0. 0. 0.28941449 0. 0. 0. ] [0. 0. 0.39248775 0. 0. 0.33894457 0.39248775 0. 0.39248775 0.33894457 0. 0. 0. 0. 0.39248775 0. 0. 0. 0.39248775 0. 0. 0. ] [0. 0. 0.28941449 0.67547293 0. 0.24993256 0.28941449 0. 0.28941449 0.24993256 0. 0. 0. 0. 0.28941449 0. 0. 0. 0.28941449 0. 0. 0. ] [0. 0. 0.34642121 0. 0. 0.29916243 0.34642121 0. 0. 0.29916243 0. 0. 0. 0. 0.34642121 0. 0. 0. 0.34642121 0.58392899 0. 0. ] [0. 0. 0. 0.29913919 0. 0.22136971 0. 0.43208699 0.25633956 0.22136971 0.43208699 0. 0.43208699 0. 0. 0. 0. 0.43208699 0. 0. 0. 0. ] [0.33333333 0.33333333 0. 0. 0.33333333 0. 0. 0. 0. 0. 0. 0.33333333 0. 0.33333333 0. 0.33333333 0.33333333 0. 0. 0. 0.33333333 0.33333333]] |
◼ TF-IDF 행렬에서 얻어지는 유사도 값을 0에서 1 사이로 스케일링하기 위해
L1 정규화(L1 normalization)를 진행
# TF-IDF 행렬에서 얻어지는 유사도의 값을 0~1로 스케일하기 위해 L1정규화를 진행
def l1_normalize(v):
norm = np.sum(v)
return v/norm
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix_l1 = tfidf_vectorizer.fit_transform(training_documents)
tfidf_norm_l1 = l1_normalize(tfidf_matrix_l1)
tf_sen_1 = tfidf_norm_l1[0:1]
tf_sen_2 = tfidf_norm_l1[1:2]
tf_sen_3 = tfidf_norm_l1[2:3]
tf_sen_4 = tfidf_norm_l1[3:4]
tf_sen_5 = tfidf_norm_l1[4:5]
tf_sen_6 = tfidf_norm_l1[5:6]
tf_sen_1.toarray()
|
array([[0. , 0. , 0.01788756, 0.04174829, 0. , 0.01544734, 0.01788756, 0. , 0.01788756, 0.01544734, 0. , 0. , 0. , 0. , 0.01788756, 0. , 0. , 0. , 0.01788756, 0. , 0. , 0. ]]) |
◼ 유클리디안 거리 기반 유사도 측정
# 유클리디안 거리 기반 유사도 측정
from sklearn.metrics.pairwise import euclidean_distances
def euclidean_distances_value(vec_1, vec_2):
return round(euclidean_distances(vec_1, vec_2)[0][0], 3)
print(f'sen_1,sen_2:{euclidean_distances_value(tf_sen_1, tf_sen_2)}')
print(f'sen_1,sen_3:{euclidean_distances_value(tf_sen_1, tf_sen_3)}')
print(f'sen_2,sen_4:{euclidean_distances_value(tf_sen_2, tf_sen_4)}')
print(f'sen_1,sen_5:{euclidean_distances_value(tf_sen_1, tf_sen_5)}')
print(f'sen_1,sen_6:{euclidean_distances_value(tf_sen_1, tf_sen_6)}')
|
sen_1,sen_2:0.045 _ 글자하나 유사한 단어로 바뀐 경우(유사) sen_1,sen_3:0.0 (일치) sen_2,sen_4:0.044 _ 글자하나 임의의 단어로 바뀐 경우(제일 유사) sen_1,sen_5:0.068 _ 주제가 같은 문장 (보통) sen_1,sen_6:0.087 (불일치) |
◼ 맨해튼 거리 기반 유사도 측정
# 맨해튼 거리 기반 유사도 측정
from sklearn.metrics.pairwise import manhattan_distances, cosine_similarity
def manhattan_distances_value(vec_1, vec_2):
return round(manhattan_distances(vec_1, vec_2)[0][0], 2)
print(f'sen_1,sen_2:{manhattan_distances_value(tf_sen_1, tf_sen_2)}')
print(f'sen_1,sen_3:{manhattan_distances_value(tf_sen_1, tf_sen_3)}')
print(f'sen_2,sen_4:{manhattan_distances_value(tf_sen_2, tf_sen_4)}')
print(f'sen_1,sen_5:{manhattan_distances_value(tf_sen_1, tf_sen_5)}')
print(f'sen_1,sen_6:{manhattan_distances_value(tf_sen_1, tf_sen_6)}')
|
sen_1,sen_2:0.08 _ 글자하나 유사한 단어로 바뀐 경우(유사) sen_1,sen_3:0.0 (일치) sen_2,sen_4:0.08 _ 글자하나 임의의 단어로 바뀐 경우(유사) sen_1,sen_5:0.21 _ 주제가 같은 문장 (불일치) sen_1,sen_6:0.35 (불일치) |
◼ 코사인 유사도 측정
def cosine_similarity_value(vec_1, vec_2):
return round(cosine_similarity(vec_1, vec_2)[0][0], 2)
print(f'sen_1,sen_2:{cosine_similarity_value(tf_sen_1, tf_sen_2)}')
print(f'sen_1,sen_3:{cosine_similarity_value(tf_sen_1, tf_sen_3)}')
print(f'sen_2,sen_4:{cosine_similarity_value(tf_sen_2, tf_sen_4)}')
print(f'sen_1,sen_5:{cosine_similarity_value(tf_sen_1, tf_sen_5)}')
print(f'sen_1,sen_6:{cosine_similarity_value(tf_sen_1, tf_sen_6)}')
|
sen_1,sen_2:0.74 sen_1,sen_3:1.0 (일치) sen_2,sen_4:0.75 sen_1,sen_5:0.39 sen_1,sen_6:0.0 (불일치) |
◼ 언어 모델을 확용한 문장 간 유사도 측정
- transformers 모듈 설치
!pip install transformers
|
- BERT 모델과 토크나이저를 불러오기
from transformers import AutoModel, AutoTokenizer, BertTokenizer
MODEL_NAME = 'bert-base-multilingual-cased'
model = AutoModel.from_pretrained(MODEL_NAME)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
|
◼ 문장을 토큰화 , 각 문장을 PyTorch 텐서로 반환
bert_sen_1 = tokenizer(sen_1, return_tensors='pt')
bert_sen_2 = tokenizer(sen_2, return_tensors='pt')
bert_sen_3 = tokenizer(sen_3, return_tensors='pt')
bert_sen_4 = tokenizer(sen_4, return_tensors='pt')
bert_sen_5 = tokenizer(sen_5, return_tensors='pt')
bert_sen_6 = tokenizer(sen_6, return_tensors='pt')
bert_sen_1
|
{'input_ids': tensor([[ 101, 9580, 118762, 9668, 71013, 10530, 9330, 11287, 9004, 32537, 8888, 46150, 12424, 9327, 10622, 9004, 32537, 47058, 9266, 17706, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])} |
|
◼ BERT 모델을 사용하여 여러 문장에 대한 출력을 계산하고, 각 문장의 마지막 풀링(pooling) 출력을 추출
sen_1_outputs = model(**bert_sen_1)
sen_2_outputs = model(**bert_sen_2)
sen_3_outputs = model(**bert_sen_3)
sen_4_outputs = model(**bert_sen_4)
sen_5_outputs = model(**bert_sen_5)
sen_6_outputs = model(**bert_sen_6)
sen_1_pooler_output = sen_1_outputs.pooler_output
sen_2_pooler_output = sen_2_outputs.pooler_output
sen_3_pooler_output = sen_3_outputs.pooler_output
sen_4_pooler_output = sen_4_outputs.pooler_output
sen_5_pooler_output = sen_5_outputs.pooler_output
sen_6_pooler_output = sen_6_outputs.pooler_output
|
◼ PyTorch의 nn.CosineSimilarity를 사용하여
BERT 모델에서 추출한 각 문장의 풀링(pooling) 출력 간의 코사인 유사도를 계산하는 과정
from torch import nn
cos_sim = nn.CosineSimilarity(dim = 1, eps = 1e-6)
print(f'sen_1,sen_2:{cos_sim(sen_1_pooler_output, sen_2_pooler_output)}')
print(f'sen_1,sen_3:{cos_sim(sen_1_pooler_output, sen_3_pooler_output)}')
print(f'sen_2,sen_4:{cos_sim(sen_2_pooler_output, sen_4_pooler_output)}')
print(f'sen_1,sen_5:{cos_sim(sen_1_pooler_output, sen_5_pooler_output)}')
print(f'sen_1,sen_6:{cos_sim(sen_1_pooler_output, sen_6_pooler_output)}')
|
sen_1,sen_2:tensor([0.9920], grad_fn=<SumBackward1>) sen_1,sen_3:tensor([0.9959], grad_fn=<SumBackward1>) sen_2,sen_4:tensor([0.9840], grad_fn=<SumBackward1>) sen_1,sen_5:tensor([0.9608], grad_fn=<SumBackward1>) sen_1,sen_6:tensor([0.9402], grad_fn=<SumBackward1>) 1에 가까울 수록 유사하다! (편차는 크지 않음) |
'AI > 자연어처리' 카테고리의 다른 글
07. 워드 임베딩 시각화 (0) | 2024.06.27 |
---|---|
06. 자연어처리 - 워드 임베딩 (0) | 2024.06.25 |
04. 자연어처리 - 임베딩 (1) | 2024.06.25 |
03. 자연어처리 - 전처리 실습 (0) | 2024.06.24 |
02. 자연어처리 - 진행순서 (0) | 2024.06.24 |