Python/자연어처리

2024-06-25 3. 임베딩, 임베딩 실습

nomad06 2024. 6. 25. 14:55


1. 자연어의 특성

 

  • 자연어를 기계가 처리하도록 하기 위해서 먼저 자연어를 기계가 이해할 수 있는 언어로 바꾸는 방법을 알아야 함
  • 토큰화 작업의 결과인 단어 사전을 기계가 이해할 수 있는 언어로 표현하는 과정이고,
    단어 사전 내 단어 하나를 어떻게 표현할까의 문제로 볼 수 있음 

 

1. 단어의 유사성과 모호성

  • 단어의 의미는 유사성과 모호성을 가지고 있는데 단어는 겉으로 보이는 형태인 표제어안에 여러가지 의미를 담고 있음 
  • 사람은 주변 정보에 따라 숨겨진 의미를 파악하고 이해할 수 있으나, 
    기계는 학습의 부재 또는 잘못된 데이터로 의미를 파악하지 못하는 경우가 있음 
  • 한 가지 형태의 단어에 여러 의미가 포함되어 생기는 중의성 문제는 자연어 처리에서 매우 중요
    • 동형어: 형태는 같으나 뜻이 서로 다른 단어 _예) 배
    • 다의어: 하나의 형태가 여러 의미를 지니면서도 그 의미들이 서로 관련이 있는 단어_ 예) 머리
    • 동의어 : 서로 다른 형태의 단어들이 동일한 의미를 가지는 단어 _예)춘추 - 나이
    • 상의어 : 상위 개념을 가지키는 단어 _예)동물
    • 하의어 : 하위 개념을 가리키는 단어 _예)강아지

2. 언어의 모호성을 해소


  • 동형어나 다의어처럼 여러 의미를 가지는 단어들이 하나의 형태로 공유, 
    동의어처럼 하나의 형태를 가지는 단어들이 서로 같은 의미를 공유
  • 단어의 중위성 해소(WSD) 알고리즘 방법을 통헤 단어의 의미를 명확히 함 
    • 지식 기반 단어 중요성 해소
      • 컴퓨터가 읽을수 있는 사전이나 어휘집 등을 바탕으로 단어의 의미를 추론하는 접근 방식 
      • 사람이 직접 선별해서 데이터를 넣으므로 노이지가 작음 
      • 구축에 많은 리소스가 필요함 
      • 데이터 평향이 생길 수 있음 
      • 영어 자연어처리 분야에서 가장 유명한 WordNet이 있음 
    • 지도 학습 기반 단어 중요성 해소
      • 지도 학습은 데이터에 정답이 있다는 의미로, 각종 기계 학습 알고리즘을 통해 단어 의미를 분류하는 방법
      • WSD라고 하면 보통 단어의 세부 의미가 부착된 코퍼스를 학습 데이터로 사용하여
        학습에 쓰이지 않았던 새로운 문장에서 단어 의미를 판별해내는 경우
      • 좋은 성능을 위해서는 질 높은 레이블을 가진 많은 데이터가 필요 
      • 데이터가 충분할 경우 일반화된 환경에서 좋은 성능을 낼 수 있음 
    • 비지도 학습 기반 단어 중요성 해소
      • 비지도 학습 는 단어 의미 추론 작업인 WSI를 가리키는 경우가 많음 
      • 문장에 등장하는 각 단어의 의미를 사전적인 의미에 연결하지 않고, 세부 의미가 같은 맥락을 군집화하는 데에 초점을 맞춤
      • 대규모 자연어 코퍼스로부터 추가 작업없이 자동적으로 학습을 수행할 수 있어서 활용 가능성이 높음
      • 사람이 직접 제작한 학습 데이터를 사용하지 않기 때문에 성능을 내기 어려움

 



 

 

2. 임베딩 구축 방법

 

1. 임베딩이란?

  • 자연어 처리 작업에서 특징 추출을 통해 자연어를 수치화하는 과정이 필요하고 이런 백터화의 과정이자 결과
  • 토큰화 작업의 목표 임베딩을 만들기 위한 단어 사전을 구축하는 것 

2. 임베딩의 역할 


  • 자연어의 의미적인 정보를 함축
    • 자연어의 중요한 특징들을 추출하여 백터로 압축하는 과정
    • 임베딩으로 표현된 문장은 실제 자연어의 주요 정보들을 포함하고 있음
    • 백터인 만큼 사칙연산이 가능하여 단어 백터 간 덧셈/뺄셈을 통해 단어들 사이의 의미적 문법적 관계를 도출
    • 임베딩의 품질을 평가하기 위해 사용되는 단어 유출 평가가 존재함 
      * 사이트: https://word2vec.kr/search/
  • 자연어 간의 유사도 계산 
    • 자연어를 백터로 표현하면 코사인 유사도를 활용하여 두 백터 간 유사도를 계산할 수 있음
    • 코사인 유사도는 -1이상 1이하의 값을 가지며, 값이 1에 가까울수록 유사도가 높다고 판단함 
  • 전이 학습 
    • 이미 만들어진 임베딩을 다른 작업을 학습하기 위한 입력값으로 쓰임
    • 품질 좋은 임베딩을 사용할수록 목표로하는 자연어처리 작업의 학습속도와 성능이 향상됨
    • 매번 새로운 것을 배울 때 scratch 부터 시작한자면 매 학습이 오래 걸림 
    • 파인 튜닝: 학습하는데 전이학습에 의한 임베딩을 초기화하여 사용한 편,새로운 작업을 학습함에도 빠르게 학습할 수 있고 성능도 좋아짐

3. 단어 출현 빈도의 기반한 임베딩 추축 방법 

  • 원 핫 인코딩 
    • 자연어를 0과 1로 구별하겠다는 인코딩 방법
    •  표현하고 싶은 단어의 인덱스에 1의 값을 부여하고, 나머지 인덱스에는 0을 부여하는 백터 표현 방식
      • 예)"...오늘 날씨가 참 좋다.."
        -> {...5:"좋다",6:"날씨", ...13:"오늘". 256:"참", 257:"가"...}
      • 일반적으로 빈도수를 정렬하고 사용, 인덱스의 순서가 의미가 없음
      • 순서가 없는 카테코리컬 피쳐인 경우 클래스 갯수가 3개 이상일 때 원핫 인코딩을 함
      • 단어 사전의 크기가 10000개 하면, 총 10000개 중 현재 내가 원하는 단어를 표현하는 1개의 차원에만 1을 ,
        나머지 9999개의 차원는 0으로 표현하는 방법 
      • 대부분의 값들이 0인 행렬을 희소행렬이라하는데
        크기는 계속 중가하나 증가하는 크기에 비해 표현의 효율성을 떨어짐 단어의 유사도를 표현하지 못함 
  • Bag of Words 
    • 단어들의 순서는 전혀 고려하지 않고 단어들의 출현빈도에 집중하는 자연어 코퍼스의 데이터 수치화 방법
    • 각 단어의 고유한 정수 인덱스를 부여하여 단어 집합을 생성하고
      각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 백터를 만듦
    • 단어 단위의 압축 방식이기 때문에 희소 문제와 단어 순서를 반영하지 못함
      • 문장을 표현하는 방법 ->  원 핫 인코딩을 모두 더함




  • TF-IDF
    • 단어의 빈도와 역문서 빈도를 사용하여 문서-단어 행렬 내 각 단어들의 중요한 정도를 가중치로 주는 표현방법 
    • 문서의 유사도를 구하는 작업, 검색 시스템에서 검색 결과의 중요도를 정하는 작업,
      문서 내에서 특정 단어의 중요도를 구하는 작업 둥에서 효과적으로 쓰일 수 있음
    • 단어의 중요도를 고려해도 여전히 단어의 빈도로 판단하는 표현방식이기 때문에 맥락적 유사도를 반영하지 못함 

4. 단어의 순서 

  • 통계 기반 언어 모델
    • 단어가 n개 주어졌을 때 언어 모델은 n개의 단어가 동시에 나타날 확률을 반환 
    • 문장은 어순을 고려하여 여러 단어로 이루어진 단어 시퀀스라고 부르며 n개의 단어로 구성된 시퀀스를 확률적으로 표현 
  • 딥러닝 모델
    • 통계 기반 언어 모델에서는 비도라는 통계량을 활용하여 확률을 추산했지만, 딥러닝 기반 언어 모델들이 등장하면서 입력과 출력사이의 관계를 유연하게 정의할 수 있게 되고 그 자체로 확률 모델로 동작할 수 있음 
    • MLM(Masked Language Modeling)
      • 문장 중간에 마스크를 씌워서 해당 마스크에 어떤 단어가 올지 예측하는 과정에서 학습을 진행 
      • 문장 전체를 다 보고 중간에 있는 단어를 예측하기 때문에 양방향 학습이 가능 
      • 대표적인 BERT 모델이 있음
    • Next Token Prediction
      • 주어진 단어 시퀀스를 가지고 다음 단어로 어떤 단어가 올지 예측하는 과정에서 학습 
      • 단어를 순차적으로 입력 받은 뒤 다음 단어를 맞춰야하기 때문에 한방향 학습을 함 
      • 대표적으로 GPT, ELMo

 

 



3. 텍스트 유사도

 

  • 두 개의 자연어 텍스트가 얼마나 유사한지를 나타내는 방법 
  • 유사도를 정의하거나 판단하는 척도가 주관적이기 떄문에, 최대한 정량화하는 방법을 찾는 것이 중요함

 

1. 유클리디안 거리 기반 유사도

  • 두 점 사이의 거리를 측정하는 유클리디안 거기 공식을 사용하여 문서의 유사도를 구하는 방법으로 거리가 가까울수록 유사도가 높다고 판단함
  • 자연어처리 분야 뿐 아니라 다른 분야에서도 범용적으로 사용되는 거리 측정 기법

2. 맨해튼 거리 기반 유사도

  • 맨해튼 거리를 사용하여 문서의 유사도를 구하는 방법
  • 유클리드 거리 공식과 유사하나, 각차원의 차를 곱해서 사용하는 대신, 절대값을 바로 합산함 
  • 유클리드 거리 공식보다 값이 크거나 같음 
  • 다차원 공간 상에서 두 좌표 간 최단거리를 구하는 방법이 아니다 보니 특별한 상황이 아니면 잘 사용되지 않음

3. 코사인 유사도

  • 두 개의 백터값에서 코사인 각도를 이용하여 구할 수 있는 두 백터의 유사도를 의미 
  • 두 백터의 방향이 완전히 동일한 경우는 1의 값을 가지며, 90도의 각을 이루면 0, 180도로 반대의 방향을 가지면 -1의 값을 가짐 
  • -1이상 1이하의 값을 가지며, 값이 1에 가까울수록 유사하다는 것을 의미 
  • 두 백터가 가리키는 방향이 얼마나 유사한가를 의미하기 때문에 자연어 내 유사도 계산에 더 적합함

4. 자카드 유사도

  • 두 문장을 각각 단어의 집합으로 만든 뒤 두 집합을 통해 유사도를 측정하는 방식
  • 수치화된 백터 없이 단어 집합만으로 계산할 수 있음
  • 두 집합의 교지합인 공통된 단어의 개수를 두 집합의 합집합을 전체 단어의 개수로 나누는 것 
  • 전체 합집합 중 공통의 단어의 개수에 따라 0과 1사이의 값을 가지며, 1에 가까울수록 유사도가 높음 

 

 

 

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에 가까울 수록 유사하다! (편차는 크지 않음)