hyeonzzz's Tech Blog

[딥러닝 파이토치 교과서] 10장. 임베딩 -(1) 본문

Deep Learning/Pytorch

[딥러닝 파이토치 교과서] 10장. 임베딩 -(1)

hyeonzzz 2024. 5. 31. 15:41

10.1 임베딩

임베딩(embedding)

: 자연어를 벡터로 변환한 결과 혹은 일련의 과정

 

임베딩 역할

  • 단어 및 문장 간 관련성 계산
  • 의미적 혹은 문법적 정보의 함축

임베딩 방법에 따라 희소 표현 기반 임베딩, 횟수 기반 임베딩, 예측 기반 임베딩, 횟수/예측 기반 임베딩이 있다.

 

임베딩과 인코딩
인코딩 : 범주형 데이터를 숫자형 데이터로 변환하는 기본 방법. 라벨 인코딩과 원핫 인코딩을 포함함.
임베딩 : 데이터를 고차원 실수 벡터로 변환하여 의미와 유사성을 반영하는 방법. 임베딩 과정에서 초기 단계에 인코딩이 사용될 수 있음.

 

10.1.1 희소 표현 기반 임베딩

희소 표현(sparse representation)

: 대부분의 값이 0으로 채워져 있는 경우

대표적으로 원-핫 인코딩이 있다.

 

원-핫 인코딩(one-hot encoding)

: 주어진 텍스트를 벡터로 변환해 주는 것

단어가 포함되어 있는 위치에 1을 넣고 나머지에는 0 값을 채운다.

 

원-핫 인코딩의 단점

  1. 하나의 요소만 1 값을 갖고 나머지는 모두 0인 희소 벡터를 갖는다. 이때 두 단어에 대한 벡터의 내적을 구해 보면 0값을 갖게 되므로 직교를 이룬다. 즉, 단어끼리 관계성없이 서로 독립적인 관계가 된다.
  2. 차원의 저주 문제가 발생한다. 하나의 단어를 표현하는 데 말뭉치에 있는 수만큼 차원이 존재한다. 예를 들어 단어 10만 개를 포함한 데이터셋에 원-핫 인코딩 배열을 구성한다면 차원 개수는 10만 개에 이른다.

대안으로 신경망에 기반하여 단어를 벡터로 바꾸는 Word2Vec, GloVe, FastText 등이 주목을 받고 있다.

 

 

라벨 인코더 vs 원-핫 인코더

라벨 인코더 : 범주형 데이터를 고유한 정수로 변환

  • ['사과', '바나나', '오렌지'] -> [0, 1, 2]
  • 간단하고 빠르며 범주의 순서를 유지할 수 있다.
  • 단점은 범주 간에 순서가 있는 것처럼 오해할 수 있다는 것이다. 이는 선형 회귀나 거리 기반 알고리즘에서 문제를 일으킬 수 있다.

원핫 인코더 : 범주형 데이터를 이진 벡터로 변환

  • ['사과', '바나나', '오렌지'] -> [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
  • 범주 간에 순서가 없음을 명확히 할 수 있으며 거리 기반 알고리즘에서 잘 동작한다.
  • 단점은 데이터의 차원이 증가한다는 것이다. 메모리 사용량이 크게 증가할 수 있다.
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
import pandas as pd

# 라벨 인코더
label_encoder = LabelEncoder()
categories = ['사과', '바나나', '오렌지']
labels = label_encoder.fit_transform(categories)
print("라벨 인코딩 결과:", labels)

# 원핫 인코더
onehot_encoder = OneHotEncoder(sparse=False)
onehot_encoded = onehot_encoder.fit_transform(pd.DataFrame(categories))
print("원핫 인코딩 결과:\n", onehot_encoded)

 

※ OneHotEncoder의 경우 입력 값으로 2차원 데이터를 요구한다. (DataFrame은 2차원 데이터 구조)

  OneHotEncoder는 여러 제약으로 인해 많이 사용되지 않는다.

  그 대신, pandas의 get_dummies()를 더 많이 활용

 

10.1.2 횟수 기반 임베딩

횟수 기반

: 단어가 출현한 빈도를 고려하여 임베딩

카운터 벡터와 TF-IDF가 있다.

 

카운터 벡터(counter vector)

: 문서 집합에서 단어를 토큰으로 생성하고 각 단어의 출현 빈도수를 이용하여 인코딩해서 벡터를 만드는 방법

즉, 토크나이징과 벡터화가 동시에 가능한 방법

 

CountVectorizer()

  1. 문서를 토큰 리스트로 변환
  2. 토큰의 출현 빈도를 센다
  3. 문서를 인코딩하고 벡터로 변환한다
from sklearn.feature_extraction.text import CountVectorizer

# 텍스트 데이터
texts = ["I love machine learning", "I love learning"]

# 카운트 벡터화 객체 생성
vectorizer = CountVectorizer()

# 텍스트 데이터를 카운트 벡터화
X = vectorizer.fit_transform(texts)

# 벡터화된 결과를 배열 형태로 변환
X_array = X.toarray()

# 결과 출력
print("어휘 사전:", vectorizer.get_feature_names_out())
print("벡터화된 데이터:\n", X_array)
어휘 사전: ['i' 'learning' 'love' 'machine']
벡터화된 데이터:
 [[1 1 1 1]
  [1 1 1 0]]

 

 

TF-IDF(Term Frequency-Inverse Docuent Frequency)

: 정보 검색론(IR)에서 가중치를 구할 때 사용되는 알고리즘

 

TF(Term Frequency)(단어 빈도) : 문서 내에서 특정 단어가 출현한 빈도

특정 문서 d에서 특정 단어 t의 등장 횟수

혹은

1을 더하는 이유 : 로그 스케일링 때문

1. 빈도수의 0값 방지
2. 적절한 스케일링
: 예를 들어, 원래 빈도수가 1과 10인 단어가 있다면, 로그 스케일에서는 각각 log(1+1)와 log(10+1)로 변환되어 0과 1이라는 큰 차이에서 0.69와 2.39라는 상대적으로 작은 차이로 줄어든다.
3. 중요도 조정
문서 내에서 많이 등장하는 단어의 영향력을 줄여준다.

 

DF(Document Frequency)(문서 빈도) : 한 단어가 전체 문서에서 얼마나 공통적으로 많이 등장하는지

즉, 특정 단어가 나타난 문서 개수

 

특정 단어 t가 모든 문서에 등장하는 일반적인 단어(a, the)라면 TF-IDF 가중치를 낮추어 줄 필요가 있다. 따라서 DF 값이 클수록  TF-IDF 가중치 값을 낮추기 위해 DF값에 역수를 취하는데 이 값이 IDF이다. 역수를 취하면 전체 문서 개수가 많아질수록 IDF값도 커지므로 IDF는 log를 취해야 한다.

 

특정 단어가 발생하는 빈도가 0이라면 분모가 0이 되는 상황이 발생한다. 이를 방지하고자 분모에 1을 더해 주는 것스무딩(smoothing)이라고 한다.

 

TF-IDF가 사용되는 상황

  • 키워드 검색을 기반으로 하는 검색 엔진
  • 중요 키워드 분석
  • 검색 엔진에서 검색 결과의 순위를 결정
from sklearn.feature_extraction.text import TfidfVectorizer

# 분석할 문서들을 리스트로 정의
doc = ['I like machine learning', 'I love deep learning', 'I run everyday']

# TF-IDF 벡터화를 위한 TfidfVectorizer 객체 생성
# min_df=1은 최소 문서 빈도를 1로 설정하여 모든 단어를 포함함
tfidf_vectorizer = TfidfVectorizer(min_df=1)

# 문서를 TF-IDF 벡터로 변환
tfidf_matrix = tfidf_vectorizer.fit_transform(doc)

# TF-IDF 행렬을 전치하여 문서 간 유사도 계산
doc_distance = (tfidf_matrix * tfidf_matrix.T)

# 유사도를 위한 행렬의 크기 출력
print ('유사도를 위한', str(doc_distance.get_shape()[0]), 'x', str(doc_distance.get_shape()[1]), 'matrix를 만들었습니다.')

# 문서 간 유사도 행렬 출력
print(doc_distance.toarray())
유사도를 위한 3 x 3 matrix를 만들었습니다.
[[1.       0.224325 0.      ]
 [0.224325 1.       0.      ]
 [0.       0.       1.      ]]

 

TF-IDF 값은 특정 문서 내에서 단어의 출현 빈도가 높거나 전체 문서에서 특정 단어가 포함된 문서가 적을수록 TF-IDF 값이 높다. 따라서 이 값을 사용하여 문서에 나타나는 흔한 단어(a, the)들을 걸러 내거나 특정 단어에 대한 중요도를 찾을 수 있다. 

 

10.1.3 예측 기반 임베딩

예측 기반 임베딩

: 신경망 구조 혹은 모델을 이용하여 특정 문맥에서 어떤 단어가 나올지 예측하면서 단어를 벡터로 만드는 방식

대표적으로 Word2Vec이 있다.

 

Word2Vec(워드투벡터)

: 신경망 알고리즘. 주어진 텍스트에서 텍스트의 각 단어마다 하나씩 일련의 벡터를 출력한다. 

단어의 의미적 유사성을 반영한 고정된 크기의 벡터를 생성한다.

  • 출력 벡터가 2차원 그래프에 표시될 때, 의미론적으로 유사한 단어의 벡터는 서로 가깝게 표현된다.
  • 서로 가깝다는 의미는 코사인 유사도를 이용하여 단어 간의 거리를 측정한 결과로 나타나는 관계성을 의미한다.
  • 즉, 특정 단어의 동의어를 찾을 수 있다.

 

수행 과정

  1. 일정한 크기의 윈도우로 분할된 텍스트를 입력으로 사용한다.
  2. 분할된 텍스트는 한 쌍의 대상 단어와 컨텍스트로 네트워크에 공급된다.
  3. 예를 들어 대상 단어는 'one'이고 컨텍스트는 and, I, love, this 단어로 구성된다.
  4. 네트워크의 은닉층에는 각 단어에 대한 가중치가 포함되어 있다.

 

이제 CBOW와 skip-gram을 이용하여 단어 간 유사성을 살펴보겠다

 

CBOW(Continuous Bag Of Words) - (주변 단어 -> 관련 단어 추정)

: 단어를 여러 개 나열한 후 이와 관련된 단어를 추정하는 방식

문장에서 등장하는 n개의 단어 열에서 다음에 등장할 단어를 예측한다.

 

CBOW 구조와 예시

가중치 행렬은 모든 단어에서 공통으로 사용된다.

 

  • 은닉층 크기 N은 입력 텍스트를 임베딩한 벡터 크기이다.
  • 위 그림에서 N=5이기 때문에 해당 CBOW를 수행한 후 벡터 크기는 5가 된다.
  • 입력층과 은닉층 사이의 가중치 W는 VxN 행렬
  • 은닉층과 출력층 사이의 가중치 W'는 NxV 행렬 (V는 단어 집합의 크기)
model1 = gensim.models.Word2Vec(data, min_count = 1,  
                              vector_size = 100, window = 5, sg=0) # sg 0:CBOW, 1:skip-gram
print("Cosine similarity between 'peter' " + "wendy' - CBOW : ", 
      model1.wv.similarity('peter', 'wendy')) # 결과 출력

 

 

skip-gram - (중심 단어 -> 주변 단어 예측)

: CBOW 방식과 반대로 특정한 단어에서 문맥이 될 수 있는 단어를 예측한다.

보통 입력 단어 주변의 단어 k개를 문맥으로 보고 예측 모형을 만든다. (k = 윈도우 크기)

model2 = gensim.models.Word2Vec(data, min_count = 1, vector_size = 100, 
                                             window = 5, sg = 1) # skip-gram 모델 사용
print("Cosine similarity between 'peter' " +
          "wendy' - Skip Gram : ", 
    model2.wv.similarity('peter', 'wendy')) # 결과 출력


데이터 성격, 분석에 대한 접근 방법 및 도출하고자 하는 결론 등을 종합적으로 고려하여 CBOW와 skip-gram 중 필요한 라이브러리를 사용할 수 있어야 한다.

 

 

FastText(패스트텍스트)

: 워드투벡터의 단점을 보완하고자 페이스북에서 개발한 임베딩 알고리즘

예를 들어 apple이라는 단어를 app, ppl, ple와 같은 여러 n-grams로 분해해 학습하므로 서브워크 정보도 학습한다.

 

기존 Word2Vec

  • 분산 표현을 이용하여 단어의 분산 분포가 유사한 단어들에 비슷한 벡터 값을 할당하여 표현한다.
  • 사전에 없는 단어에 대해서는 벡터 값을 얻을 수 없다.
  • 자주 사용되지 않는 단어에 대해서는 학습이 불안정하다.

FastText

  • 단어 표현(word representation) 방법을 사용한다.
  • 노이즈에 강하며, 새로운 단어에 대해서는 형태적 유사성을 고려한 벡터값을 얻기 때문에 자연어 처리 분야에서 많이 사용된다.
from gensim.test.utils import common_texts
from gensim.models import FastText

model = FastText('./peter.txt', vector_size=4, window=3, min_count=1, epochs=10)

 

 

패스트텍스트가 워드투벡터 단점을 극복하는 방법

 

1. 사전에 없는 단어에 벡터 값을 부여하는 방법

패스트텍스트는 단어를 n-gram으로 표현한다. n의 설정에 따라 단어의 분리 수준이 결정된다. n을 3으로 설정하면 This is Deep Learning Book은 다음과 같이 분리된다.

n 값에 따른 부분 단어는 다음과 같다.

  • n=1 : unigram
  • n=2 : bigram
  • n=3 : trigram

 

사전에 없는 단어가 등장한다면 n-gram으로 분리된 부분 단어와 유사도를 계산하여 의미를 유추할 수 있다.

 

 

2. 자주 사용되지 않는 단어에 학습 안정성을 확보하는 방법

 

기존 Word2Vec

  • 단어의 출현 빈도가 적으면 임베딩의 정확도가 낮다.
  • 참고할 수 있는 경우의 수가 적기 때문에 상대적으로 정확도가 낮아 임베딩되지 않는다.

FastText

  • 등장 빈도수가 적더라도, n-gram으로 임베딩하기 때문에 참고할 수 있는 경우의 수가 많다.
  • 자주 사용되지 않는 단어에서도 정확도가 높다.

 

10.1.4 횟수/예측 기반 임베딩

GloVe(글로브)

: 횟수 기반의 LSA(Latent Semantic Analysis)와 예측 기반의 Word2Vec 단점을 보완하기 위한 모델

LSA(Latent Semantic Analysis)
: 텍스트 데이터에서 숨겨진 의미적 구조를 발견하기 위한 통계적 기법

LSA는 단어와 문서 간의 관계를 분석하여 고차원의 희소 행렬을 낮은 차원의 의미 공간으로 변환한다. 이를 통해 단어 간의 유사성을 파악하고 문서의 주제를 식별할 수 있다.

 

  • 단어에 대한 글로벌 동시 발생 활률(global co-occurrence statistics) 정보를 포함하는 단어 임베딩 방법
  • 단어에 대한 통계 정보와 skip-gram을 합친 방식
  • skip-gram을 사용하되 통계적 기법이 추가된 것
  • 단어 간 관련성을 통계적 방법으로 표현해 준다

Word2Vec와 GloVe 비교

Word2Vec

  • 훈련 방식: Word2Vec는 예측 기반 모델로, CBOW(Continuous Bag of Words)와 Skip-gram 두 가지 알고리즘을 사용하여 단어의 문맥을 예측하면서 벡터를 학습한다.
  • 지역적 정보: Word2Vec는 문맥 창 내의 단어들에 집중하여 로컬 정보를 캡처한다.
  • 단어 간의 관계: 단어의 의미적 유사성과 관계를 벡터 공간에서 잘 나타낸다.

GloVe

  • 훈련 방식: GloVe는 카운트 기반 모델로, 전체 코퍼스의 전역적인 동시 출현 확률을 사용하여 단어 벡터를 학습한다.
  • 전역적 정보: GloVe는 전체 코퍼스의 전역적인 단어 동시 출현 행렬을 사용하여 글로벌한 정보를 캡처한다.
  • 단어 간의 관계: 전역적 통계를 반영하여 단어 벡터를 생성하므로, 특정 단어 쌍 간의 관계를 명확하게 학습할 수 있다.

GloVe를 Word2Vec 포맷으로 변환하는 이유

GloVe를 이용하는데 Word2Vec 포맷으로 변환하는 주된 이유는 다음과 같습니다:

 

라이브러리 지원:

  • gensim과 같은 라이브러리는 Word2Vec 포맷을 기본적으로 지원합니다. 따라서 GloVe 벡터를 gensim에서 사용하려면 Word2Vec 포맷으로 변환해야 합니다.
  • 많은 자연어 처리 라이브러리와 도구들이 Word2Vec 포맷을 표준으로 사용합니다.
from gensim.scripts.glove2word2vec import glove2word2vec
from gensim.models import KeyedVectors
from gensim.test.utils import datapath, get_tmpfile

# GloVe 파일의 경로를 설정합니다.
glove_file = datapath('glove.6B.100d.txt')

# 변환된 Word2Vec 포맷 파일을 저장할 임시 파일 경로를 설정합니다.
word2vec_glove_file = get_tmpfile("glove.6B.100d.word2vec.txt")

# GloVe 파일을 Word2Vec 포맷으로 변환합니다.
glove2word2vec(glove_file, word2vec_glove_file)

# 변환된 Word2Vec 포맷의 파일을 로드합니다.
model = KeyedVectors.load_word2vec_format(word2vec_glove_file)

# 'king' 단어의 벡터를 출력합니다.
print(model['king'])

# 'king'과 유사한 단어들을 출력합니다.
print(model.most_similar('king'))

 

 

10.2 트랜스포머 어텐션

어텐션(attention)

  • 언어 번역에서 사용되기 때문에 인코더디코더 네트워크를 사용한다.
  • 인코더(Encoder): 입력 시퀀스를 받아들여 일련의 은닉 상태(hidden states)를 생성합니다. 이 은닉 상태들은 입력 시퀀스의 각 단어에 대한 정보가 담겨 있습니다.
  • 디코더(Decoder): 인코더의 은닉 상태와 이전의 디코더 은닉 상태를 사용하여 출력 시퀀스를 생성합니다.

인코더

  • 입력에 대한 벡터 변환을 인코더에서 처리하고 모든 벡터를 디코더로 보낸다.
  • 모든 벡터를 전달하는 이유 : 시간이 흐를수록 초기 정보를 잃어버리는 기울기 소멸 문제를 해결하기 위해서
  • 단점 : 모든 벡터가 전달되기 때문에 행렬 크기가 굉장히 커진다.
  • 해결 : 소프트맥스 함수를 사용하여 가중합을 구하고 그 값을 디코더에 전달한다.

디코더

  • 가중합만 전달되었더라도 부담이다.
  • 은닉 상태에 대해 중점적으로 집중해서 보아야 할 벡터를 소프트맥스 함수로 점수를 매긴 후 각각을 은닉 상태의 벡터들과 곱한다.
  • 이 은닉 상태를 모두 더해서 하나의 값으로 만든다.
  • 즉, 모든 벡터 중에서 꼭 살펴보아야 할 벡터들에 집중하겠다는 의미이다.

 

 

 

트랜스포머(transformer)

: 어텐션을 극대화하는 방법

  • "Attention is All You Need" 논문에서 발표된 것
  • 인코더와 디코더를 여러 개 중첩시킨 구조
  • 이때, 각각의 인코더와 디코더를 블록(block)이라고 한다.
  • 논문에서는 인코더 블록과 디코더 블록을 6개씩 중첩시킨 구조를 사용한다.

 

인코더 블록 구조

  • 단어를 벡터로 임베딩하고, 이를 셀프 어텐션과 전방향 신경망으로 전달한다.
  • 셀프 어텐션문장 안에서 단어 간 관계를 파악한다. 
  • 셀프 어텐션에서 파악된 단어 간 관계는 전방향 신경망으로 전달된다.

 

디코더 블록 구조

  • 셀프 어텐션 + 인코더-디코더 어텐션 + 전방향 신경망
  • 셀프 어텐션 층 : 인코더와 동일하다 (문장에서 단어 간 관계 파악)
  • 인코더-디코더 어텐션 : 인코더가 처리한 정보를 받아 어텐션 메커니즘을 수행하고, 전방향 신경망으로 데이터를 전달한다.

 

<어텐션 매커니즘>

1. 어텐션 스코어 구하기

: 현재 디코더의 시점 i에서 단어를 예측하기 위해, 인코더의 모든 은닉 상태 값(h_j)디코더의 이전 시점의 은닉 상태(s_i-1)와 얼마나 관련이 있는지를 판단하는 값

  • 인코더의 모든 은닉 상태의 값(h_j)과 디코더에서의 이전 시점의 은닉 상태(s_i-1)값을 이용하여 구할 수 있다.

 

2. softmax 함수에 적용하여 확률로 변환하기

  • 이렇게 계산된 0~1 사이의 값들이 특정 시점에 대한 가중치, 즉 시간의 가중치가 된다.

 

3. 컨텍스트 벡터 구하기

: 시간의 가중치(a_ij)와 은닉 상태(h_j)의 가중합을 계산하면 하나의 벡터가 계산된다.

 

4. 디코더의 은닉 상태(s_t) 구하기

  • 컨텍스트 벡터(c_t)와 디코더 이전 시점의 은닉 상태(s_t-1)와 출력(y_t-1)이 필요하다.

  • 어텐션이 적용된 인코더-디코더의 수식에서는 컨텍스트 벡터(c_i)가 계속 변하고 있다.
  • 어텐션이 적용되지 않은 인코더-디코더 수식에서는 컨텍스트 벡터(c)가 고정되어 있다.

 

순전파 과정을 통해 정리

  1. 어텐션 스코어 계산 : 'They are cats'라는 입력 시퀀스에 대해 먼저 s1(디코더의 이전 시점 은닉 상태)과 모든 인코더 은닉 상태에 대한 어텐션 스코어(e21, e22, e23)를 계산한다.
  2. 소프트맥스 함수 적용 : 어텐션 스코어(e21, e22, e23)를 구했으면 소프트맥스 함수를 적용하여 시간의 가중치(a21, a22, a23)를 구한다.
  3. 컨텍스트 벡터(c2) 계산 : '시간의 가중치'와 인코더의 은닉 상태 값들을 이용하여 가중합을 계산함으로써 컨텍스트 벡터(c2)를 구한다.
  4. 다음 디코더의 은닉 상태(s2) 출력 : 앞에서 구한 컨텍스트 벡터와 디코더 이전 시점의 은닉 상태와 출력을 이용하여 최종적으로 다음 디코더의 은닉 상태(s2)를 출력한다.

디코더의 은닉 상태의 역할
: 다음에 어떤 단어를 출력할지를 결정하는 데 필요한 모든 문맥 정보를 담고 있다.

1. 문맥 정보 저장
- 디코더의 은닉 상태는 이전에 생성된 모든 단어와 입력 시퀀스에 대한 정보를 요약한다. 이는 다음 단어를 예측할 때 필요한 문맥을 제공한다.
2. 출력 단어 예측
- 디코더의 은닉 상태는 다음 단어를 예측하는 데 사용된다. 이는 디코더의 현재 은닉 상태와 어텐션 메커니즘에서 계산된 컨텍스트 벡터를 사용하여 출력 단어를 결정한다.
3. 상호작용
- 디코더는 인코더의 은닉 상태와 어텐션 메커니즘을 통해 상호작용하여, 입력 시퀀스의 중요한 정보를 반영한다. 이는 디코더가 다음에 출력할 단어를 예측하는 데 도움을 준다.

 

10.2.1 seq2seq

 

seq2seq

: 입력 시퀀스에 대한 출력 시퀀스를 만들기 위한 모델

시퀀스 레이블링 vs 시퀀스투시퀀스

시퀀스 레이블링 (sequence labeling)
: 입력 단어가 x1, x2, ..., xn이라면 출력은 y1, y2, ..., yn이 되는 형태
입력과 출력에 대한 문자열이 같다.
품사 판별이 주 목적

시퀀스투시퀀스 (seq2seq)
: 입력 시퀀스와 의미가 동일한 출력 시퀀스를 만드는 것
x, y 간의 관계는 중요하지 않다.
각 시퀀스의 길이도 서로 다를 수 있다.
번역에 초점을 둔 모델

 

 

<seq2seq를 파이토치로 구현 (영어 -> 프랑스어)>

1. 라이브러리 호출

 

2. 데이터 준비

re 모듈

: 정규표현식을 사용하고자 할 때 사용

(정규표현식 : 특정한 규칙을 갖는 문자열의 집합을 표현하기 위한 형식)

import re
com = re.compile('[cats]')
com.findall('I love cats.')
['c', 'a', 't', 's']

cats를 하나의 단어로 인식하지 않고 문자열을 하나씩 매핑하여 결과가 출력된다.

 

3. 데이터 정규화

df = pd.read_csv(loc, delimiter='\t', header=None, names=[lang1, lang2])
  • loc : 예제에서 사용할 데이터셋
  • delimiter : CSV 파일의 데이터가 어떤 형태로 나뉘었는지. 하나의 문장을 분할하지 않고 그대로 사용하고 싶을 때 유용
  • header : 데이터셋의 첫번째 행을 열이름(header)으로 지정
  • names : 열이름을 리스트 형태로 입력 

 

4. 텐서로 변환

파이토치의 네트워크는 텐서 유형의 데이터만 인식하기 때문에 매우 중요한 작업이다. 

데이터셋이 문장이기 때문에 문장의 끝에 입력이 완료되었음을 네트워크에 알려 주어야 한다. (토큰)

 

seq2seq 모델을 사용하기 위해서는 인코더와 디코더를 정의해야 한다. 

  • 입력 문장이 인코더로 주입되면 디코더로 번역되어 출력된다.
  • 인코더와 디코더를 이용하면 문장의 번역뿐만 아니라 다음 입력을 예측하는 것도 가능하다.
  • 각 입력 문장의 끝에는 문장의 끝을 알리는 토큰이 할당된다.

 

5. 인코더 네트워크

  • 입력 문장을 단어별로 순서대로 인코딩한다.
  • 문장의 끝을 표시하는 토큰이 붙는다.

  • 임베딩 계층과 GRU 계층으로 구성된다.
  • 임베딩 계층 - 입력에 대한 임베딩 결과가 저장되어 있는 딕셔너리를 조회하는 테이블
  • GRU 계층 - 연속하여 들어오는 입력을 계산. 이전 계층의 은닉 상태를 계산한 후 망각 게이트와 업데이트 게이트를 갱신한다.

 

6. 디코더 네트워크

  • 인코더 출력을 디코딩하여 다음 출력을 예측한다.

  • 임베딩 계층, GRU 계층, Linear 계층으로 구성된다.
  • 임베딩 계층 - 출력을 위해 딕셔너리를 조회할 테이블을 만든다.
  • GRU 계층 - 다음 단어를 예측하기 위한 확률을 계산한다.
  • Linear 계층 - 계산된 확률 값 중 최적의 값을 선택하기 위해 softmax 활성화 함수를 사용한다.

 

7. seq2seq 네트워크

앞에서 정의한 인코더와 디코더를 이용하여 seq2seq 모델을 정의한다.

teacher_force = random.random() < teacher_forcing_ratio
  • 티처포스(teacher_force) : 디코더의 입력으로 실제 타겟 시퀀스를 사용할지, 모델이 예측한 출력을 사용할지
  • 번역하려는 목표 단어를 디코더의 다음 입력으로 넣어 주는 기법
    1. 만약 random.random()이 teacher_forcing_ratio보다 작으면 teacher_force는 True가 된다.
      • 이 경우, 실제 타겟 시퀀스를 다음 디코더 입력으로 사용한다.
    2. 반대로 random.random()이 teacher_forcing_ratio보다 크면 teacher_force는 False가 된다.
      • 이 경우, 디코더의 출력을 다음 디코더 입력으로 사용한다.
  • 장점 - 학습 초기에 안정적인 훈련이 가능하며, 기울기를 계산할 때 빠른 수렴이 가능하다.
  • 단점 - 네트워크가 불안정해질 수 있다.

 

8. 모델의 오차 계산 함수 정의

 

9. 모델의 훈련 함수 정의

NLLLoss vs CrossEntropyLoss

NLLLoss CrossEntropyLoss
- Softmax를 사용할 것임을 명시해야 한다. - LogSoftmax + NLLLoss
- Softmax를 명시하지 않아도 된다.

 

10. 모델 평가

 

11. 모델 훈련

 

12. 임의의 문장에 대한 평가 결과

 

seq2seq 모델의 단점 (a)

  • 입력 문장이 긴 시퀀스일 경우 정확한 처리가 어렵다.
  • 인코더에서 사용하는 RNN(LSTM, GRU)의 마지막 은닉 상태만 디코더로 전달되기 때문이다.
  • 하나의 고정된 크기의 벡터에 모든 정보를 담다 보니 정보의 손실 발생
  • RNN에서 발생할 수 있는 기울기 소멸 문제 발생

 

어텐션 메커니즘의 장점 (b)

  • 디코딩동안 입력 시퀀스의 모든 숨겨진 상태를 유지하고 활용하기 때문에 정보의 손실과 기울기 소멸 문제가 발생하지 않는다.
  • 컨텍스트 벡터는 인코더의 전체 은닉 상태들과 디코더의 이전 은닉 상태를 바탕으로 만들어진다.
  • 특정 시점마다 다른 컨텍스트 벡터를 사용한다.

 

13. 어텐션이 적용된 디코더

self.attn = nn.Linear(self.hidden_size * 2, self.max_length)
  • 디코더의 현재 은닉 상태와 인코더 출력의 특정 타임스텝의 은닉 상태를 결합하여 입력 시퀀스의 모든 타임스텝에 대한 어텐션 가중치를 계산하는 선형 레이어를 정의
  • 입력 : hidden_size * 2 크기의 벡터
  • 출력 : max_length 크기의 벡터
attn_applied = torch.bmm(attn_weights.unsqueeze(0),
                                 encoder_outputs.unsqueeze(0))
  • torch.bmm : 배치 행렬 곱(BMM) 수행
  • 뒤의 2개 차원에 대해 행렬 곱 수행
  • (BN, A, B) x (BN, B, C) -> (BN, A, C)

  • 따라서 가중치와 인코더의 출력 벡터를 곱하겠다는 의미
  • attn_applied는 입력 시퀀스의 특정 부분에 관한 정보를 포함하고 있기 때문에 디코더가 적절한 출력 단어를 선택하도록 도와준다.

 

14. 어텐션 디코더 모델 학습을 위한 함수

 

15. 어텐션 디코더 모델 훈련

어텐션 메커니즘의 성능 향상 효과를 보려면 시퀀스가 길수록 좋다

 

 

10.2.2 버트(BERT)

BERT

: 기존의 단방향 자연어 처리 모델들의 단점을 보완한 양방향 자연어 처리 모델

  • 검색 문장의 단어를 입력된 순서대로 하나씩 처리하는 것이 아니라, 트랜스포머를 이용하여 구현되었다.
  • 방대한 양의 텍스트 데이터로 사전 훈련된 언어 모델이다.

 

구조

  • transformer라는 인코더를 쌓아 올린 구조이다.
  • 문장 예측을 할 때 사용한다.
  • 사전 학습된 버트 위에 분류를 위한 신경망을 한층 추가한다.
  • 즉, 트랜스포머와 사전 학습을 사용하여 성능을 향상시킨 모델이다.

 

학습 절차

  1. 그림과 같이 문장을 버트의 입력 형식에 맞게 변환한다. 이때 문장의 시작은 [CLS], 문장의 끝은 [SEP]로 표시한다.
  2. 한 문장의 단어들에 대해 토큰화(tokenization)를 진행한다. 예를 들어 ‘고양이’라는 단어의 경우 ‘고##’, ‘양##’, ‘##이’로 토큰화한다.
  3. 마지막으로 각 토큰들에 대해 고유의 아이디를 부여한다. 토큰이 존재하지 않는 자리는 0으로 채운다.

 

특징

  • 전이 학습을 기반으로 한다.
  • 인코더-디코더 중 인코더만 사용한다.
  • BERT-base, BERT-large 두 가지 버전이 있다.

 

<BERT를 파이토치로 구현>

1. 라이브러리 호출

 

2. 데이터셋 불러오기

 

3. 불러온 데이터셋 중 일부만 사용

 

4. 데이터셋 생성

 

5. 데이터셋의 데이터를 데이터로더로 전달

 

6. 버트 토크나이저 내려받기

 

7. 최적화 모델 저장최적화 모델 저장

 

8. 모델 훈련 함수 정의

# 원래 코드
labels = torch.tensor(label)

# 수정된 코드
labels = label.clone().detach()
  • label.clone()는 텐서 label의 복사본을 생성한다.
  • detach()는 이 복사된 텐서가 계산 그래프에서 분리되어 더 이상 그래디언트를 추적하지 않도록 한다. 이는 모델의 역전파 과정에서 불필요한 메모리 사용을 방지하고, torch.tensor()를 사용할 때 발생하는 경고를 피할 수 있다.

 

9. 모델의 파라미터 미세 조정 및 모델 훈련

 

10. 오차 정보를 그래프로 확인

 

11. 모델 평가 함수 정의

cm = confusion_matrix(y_true, y_pred, labels=[1,0])
  • 사이킷런은 정밀도, 재현율, F1-스코어를 구하기 위해 classification_report 제공
  • 3개의 평균값으로 전체 모델 성능을 평가
  • y_true : 정답
  • y_pred : 모델이 예측한 값
  • labels : 출력에 대한 순서 변경
sns.heatmap(cm, annot=True, ax=ax, cmap='Blues', fmt="d")
  • heatmap : 데이터의 배열을 색상으로 표현하고자 할 때 사용
  • cm : heatmap으로 표현할 데이터셋
  • annot : heatmap의 각 셀에 숫자를 입력
  • ax : 플롯에 대한 축
  • cmap : heatmap의 색상
  • fmt : annot의 숫자의 형태를 정수로 표현

 

12. 모델 평가