본문 바로가기
AI/자연어처리

05. 자연어처리 - 임베딩 실습

by 사라리24 2024. 6. 25.



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)


  1. 모델 및 토크나이저 설정:
    • MODEL_NAME = 'bert-base-multilingual-cased': 사용할 BERT 모델의 이름을 지정합니다.
      여기서는 bert-base-multilingual-cased를 사용하겠다고 선언합니다.
      이 모델은 다국적(cased) 다국어(BERT)를 의미합니다.
  2. 모델 및 토크나이저 불러오기:
    • model = AutoModel.from_pretrained(MODEL_NAME):
      지정된 MODEL_NAME에 해당하는 사전 훈련된 BERT 모델을 불러옵니다.
      AutoModel.from_pretrained 메서드는 Hugging Face의 transformers 라이브러리에서 모델을 자동으로 불러옵니다.
    • tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME):
      지정된 MODEL_NAME에 해당하는 사전 훈련된 BERT 토크나이저를 불러옵니다.
      마찬가지로 AutoTokenizer.from_pretrained 메서드를 사용하여 자동으로 토크나이저를 불러옵니다.
이 코드는 bert-base-multilingual-cased라는 모델과 해당 모델에 대한 토크나이저를 불러와서 model tokenizer 변수에 할당하는 과정을 나타내며, 이후 이 모델과 토크나이저를 사용하여 텍스트를 처리하거나 다른 자연어 처리 작업을 수행할 수 있습니다.
 

 

 

문장을 토큰화 , 각 문장을 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]])}
  • input_ids:
    • tensor([[ 101, 9580, 118762, 9668, 71013, 10530, 9330, 11287, 9004, 32537, 8888, 46150, 12424, 9327, 10622, 9004, 32537, 47058, 9266, 17706, 102]])
    • input_ids는 문장을 BERT 모델이 이해할 수 있는 정수 시퀀스로 변환한 것입니다. 여기서 각 정수는 토큰의 인덱스를 나타냅니다. 101은 [CLS] 토큰을 나타내고, 102는 [SEP] 토큰을 나타냅니다. 나머지 숫자들은 각각 단어 또는 특정 토큰에 해당하는 인덱스입니다.
  • 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]])
    • token_type_ids는 BERT 모델이 입력 시퀀스에서 각 토큰이 어느 문장에 속하는지를 구분하는 역할을 합니다. 일반적으로 문장이 두 개일 때 (예: 문장1과 문장2), 첫 번째 문장의 토큰은 0으로, 두 번째 문장의 토큰은 1로 마킹됩니다. 여기서는 단일 문장을 처리하므로 모든 값이 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]])
    • attention_mask는 입력 시퀀스에서 어텐션(attention)을 수행할 위치를 나타냅니다. `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