[딥러닝] Transformer : 소개와 동작 원리 (+ 간단한 챗봇 만들기)
이번 포스팅에서는 Transformer 아키텍처에 대해서 알아보려고 한다.
Transformer는 2020년대에 들어서서 매우 핫한 딥러닝 기반 모델이며, 뛰어난 퍼포먼스를 보이고 있다. ChatGPT를 만들 수 있었던 근간이기도 하며 특히 자연어 처리(NLP) 분야에서 혁신을 가져왔다.
Attention 메커니즘은 Transformer의 중요한 알고리즘으로서 사용된다. 해당 메커니즘과 Seq2Seq에 대한 이해가 있어야 Transformer를 이해할 수 있어서 간단하게 설명해 두었지만, 혹시 이해가 안된다면 아래의 포스팅을 참고하면 좋을 것 같다.
https://sjh9708.tistory.com/230
Seq2Seq (Sequence-to-Sequence)
Seq2Seq는 Attention 메커니즘이 도입되기 이전 RNN 기반 자연어 처리에서 뛰어난 성능을 발휘하였다.
- 인코더(Encoder): 입력 시퀀스를 처리하고 이를 고정된 크기의 Context Vector로 변환한다.
- 컨텍스트 벡터(Context Vector): Encoder가 순차적으로 입력 시퀀스를 처리한 후의 마지막 Hidden State로서, 입력 시퀀스의 단어들의 의미와 순서를 요약한 정보이다.
- 디코더(Decoder): 인코더에서 생성된 Context Vector를 초기 State로 사용하여 출력 시퀀스를 생성한다.
RNN(LSTM/GRU) 기반 모델의 한계
1. 장기 의존성 문제(Long-term dependency problem)
- LSTM과 GRU의 등장으로 장기 의존성 문제가 많이 완화되었지만 구조적으로 완전히 해결할 수 없다.
- 여전히 긴 문장들에 대해서 Gradient 소실과 폭주가 발생할 수 있다.
2. Seq2Seq
- 입출력의 크기의 제약 완화 : LSTM의 동일한 입출력의 크기의 제약을 해결하였다.
- 고정된 Context Vector에 소스 문장의 정보 압축 : 병목 발생 : 고정된 길이에 입력 시퀀스의 모든 정보를 담지 못하고 손실될 수 있다. 특히 긴 입력 시퀀스의 경우 정보 손실이 더 심각하다. 이는 모델의 성능 하락으로 이어진다.
이러한 한계점으로 인해 Attention 메커니즘과 같은 기술이 도입되었다.
Attention은 디코더가 인코더의 각 입력 단계에 주의를 기울일 수 있도록 하는 것으로, 더 많은 정보를 고려할 수 있도록 돕는다. 이는 2024년 기점으로 NLP에서 뛰어난 성능을 보이는 Transformer의 "Attention is all you need"의 근간이 되었다.
Attention
아이디어
1. 고정된 Context Vector를 사용하는 대신 Decoder에서 출력 단어를 예측하는 Time step마다 인코더에서의 전체 입력 문장을 다시 참고한다.
- 기존 seq2seq 모델에서 하나의 고정된 Context vector(= Encoder의 마지막 Hidden state)를 사용했다.
- 각 디코더의 Time step마다 인코더의 모든 Hidden state를 참고하도록 한다.
2. 모든 입력 토큰을 모두 동일한 비율로 참고하지 않고 예측해야 할 단어와 연관이 있는 입력을 집중(Attention)하여 참고하게 된다.
- 연관이 있는 입력 토큰에 더 큰 가중치를 부여하는 메커니즘을 사용한다.
- 각 디코더 타임 스텝에서 인코더의 각 출력(Hidden state)에 대해 가중치(Attention score)를 계산
- 중요한 Hidden state에 더 많은 주의를 기울여서 context vector를 동적으로 생성한다.
Attention(Q, K, V) = Attention Value
Q = Query : t 시점의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들
이를 LSTM 기반 Encoder & Decoder 아키텍처에서 전체적인 도식으로 보면 다음과 같다.
1. Decoder은 현재 예측해야 하는 단어에 대해서 Encoder에 Query를 보낸다.
2. Encoder은 Query에 대해서 모든 Key와의 유사도를 각각 구한다.
3. 유사도를 Key와 매핑된 각각의 Value에 반영한다.
- 소프트맥스 함수를 통해 나온 결과값은 I, am, a, student 단어 각각이 현재 출력 단어를 예측할 때 얼마나 도움이 되는지의 정도를 수치화한 값이다.
4. Attention Value 출력 : 유사도가 반영된 Value을 모두 취합해서 Decoder에게 반환한다.
- 각 입력 단어가 디코더의 예측에 도움이 되는 정도가 수치화하여 측정되면 이를 하나의 정보(Attention Value)로 담아서 디코더로 전송한다.
Transformer
Attention is All You Need!
지금까지의 특징을 전달하는(transduction) 모델은 주로 복잡한 순환신경망이나 인코더-디코더같은 합성곱 신경망 방식이 우세하지만, 우리는 어텐션을 사용한 '트랜스포머'라는 간단한 모델을 제안한다. 두 가지의 기계 번역 문제에 대해 실험했고, 학습 시간이 매우 적어야 한다는 것과 병렬적이여야 한다는 조건 하에서 상기된 모델이 질적으로 우수함을 보였다. 상기된 모델은 WMT 2014 영어-독일어 번역 문제를 앙상블이 포함된 기존에 존재했던 결과보다 2 BLEU 향상시킨 28.4 BLEU를 달성했다. 영어-프랑스어 번역에서는, 기존 최상 모델들의 학습 비용의 작은 부분 정도인, 8개의 GPU를 활용해 3일 12시간에 걸쳤던 학습을 마친 후 41.8 BLEU의 신규 모델 상태를 지정했다. 또한 한정된 학습 데이터와 방대한 학습 데이터와 함께 영어 파싱(parsing)에 성공적으로 어텐션 방식을 적용함으로써 트랜스포머가 다른 문제들에도 이를 일반화한 것을 보였다.
Transformer는 "Attention is All You Need" 논문의 이름과 같이 RNN과 CNN을 사용하지 않고 전적으로 Attention 메커니즘에 기반을 둔 모델이다.
- 시퀀스 간의 복잡한 의존성을 효과적으로 학습하고, 훈련 시간을 단축시키기 위해 설계
- RNN을 사용하는 이전 모델들과 달리, Transformer는 병렬 처리가 가능하여 대규모 데이터셋에서 효율적인 처리가 가능하다.
- Self Attention : Transformer의 핵심적인 메커니즘으로 자신을 구성하는 부분끼리 연관성을 찾고자 할 때 사용됨
Transformer는 RNN 기반 seq2seq와 유사하게 입력 시퀀스에 따른 출력 시퀀스를 내보내는 Encoder-Decoder 구조를 사용한다.
- seq2seq에서 Encoder, Decoder의 각각 하나의 RNN이 t개의 시점(time step)을 가지는 구조였다.
- Transformer에는 RNN이 없이 인코더와 디코더라는 단위가 N개로 구성되는 구조이다.
Self Attention
Self-Attention : Transformer의 핵심 메커니즘. 자신을 구성하는 부분끼리 연관성을 찾고자 할 때 사용된다.
- 위 사진을 보면 "I"와 "student"의 연관도가 높은 것을 알 수 있다. (I = student 이기 때문)
Self-Attention의 특징
- 병렬 처리: 각 단어가 다른 단어들과 동시에 상호작용하므로 병렬 처리가 가능
- 긴 종속성: 멀리 떨어진 단어들 사이의 관계도 쉽게 포착, 긴 문맥 정보의 처리에 유리하다.
- 위치 불변성: 포지셔널 인코딩(Positional Encoding)을 통해 각 단어의 위치 정보를 제공해야 한다.
이제 Self-Attention의 과정을 파악해보자.
Self Attention의 과정
1. Query, Key, Value(Q, K, V) 벡터 얻기
Self Attention | 기존의 Attention | |
Q = Query | 입력 문장의 모든 Word Vector | t 시점의 디코더 셀에서의 은닉 상태 |
K = Keys | 입력 문장의 모든 Word Vector | 모든 시점의 인코더 셀의 은닉 상태들 |
V = Values | 입력 문장의 모든 Word Vector | 모든 시점의 인코더 셀의 은닉 상태들 |
(Self) Attention(Q, K, V)
Query 벡터 (Q): 입력 벡터가 다른 단어와 얼마나 관련이 있는지를 평가하는 데 사용
Key 벡터 (K): Query 벡터와 상호작용하여 주의할 단어를 결정
Value 벡터 (V): 실제로 네트워크가 최종적으로 사용하게 되는 정보
1. 각 입력 단어를 고차원 벡터로 Word Embedding한다.
2. 입력 임베딩 벡터를 Q, K, V로 변환하기 위해 세 개의 가중치 벡터 \(W_Q\) 를 사용한다.
3. 가중치 행렬들은 학습 가능한 파라미터들로 모델의 학습 과정에서 최적화된다.
4. 각 단어에 대한 임베딩 벡터와 가중치 벡터를 내적계산하여 Query, Key, Value 벡터를 얻어낸다.
2. Attention Value 계산하기
1. Attention Score : Query 벡터와 Key 벡터의 내적을 계산 -> 유사도 측정
2. Attention Distribute : 유사도 -> Softmax 함수 -> 각 단어가 얼마나 중요한지에 대한 분포를 얻어낸다.
3. Attention Value : 해당 중요도에 대한 벡터를 Value 벡터에 가중합하여 최종적인 Attention Value를 출력한다.
Transformer의 메커니즘
Transformer는 Encoder와 Decoder로 구성되고, Self-Attention 메커니즘을 통해 각 단어가 시퀀스 내 다른 모든 단어들과 상호작용하여 관계를 학습한다는 것 까지 알아보았다. 이제 Transformer의 구조에 대해서 살펴보도록 하자.
Encoder : Input에 대한 처리
1. 입력 시퀀스의 각 단어를 고차원 벡터로 Embedding한다.
2. 단어 순서를 반영하기 위해 Positional Encoding을 임베딩에 추가한다.
Positional Encoding
트랜스포머는 RNN과 달리 단어 입력을 순차적으로 받는 방식이 아니므로 단어의 위치 정보를 알 수 없다(위치 불변성) 따라서 단어의 위치 정보를 얻기 위해서 각 단어의 임베딩 벡터에 위치 정보들을 더하여 모델의 입력으로 사용해야 한다.
해당 과정을 포지셔널 인코딩(Positional Encoding) 이라고 한다.
위치 \(pos\)와 임베딩 벡터의 차원 \(i\)에 대해 다음과 같은 식을 사용하여 상대적인 위치에 대한 계산을 수행한다.
Encoder : Self Attention Layer
- 입력 문장을 구성하는 단어들 간의 문장 내에서의 연관관계를 파악하는 단계
- 각 입력 벡터에 대해 Query, Key, Value 벡터를 생성하고 Self-Attention을 수행하여 출력한다.
- 이 과정을 여러 Self-Attention Head에서 병렬로 수행하는데 이를 Multi-Head Attention이라고 한다.
Encoder : 출력
Fully Connected Layers로 구성된 신경망을 통해 비선형 변환을 수행한다.
Layer의 Input에 대해 정규화를 통해서 학습 속도를 향상시키고 안정되게 한다.
Residual Connection(잔차 연결)을 통해 입력을 출력에 더해주면서 모델이 원래 입력을 참조할 수 있게 한다.
Encoder의 출력은 입력 시퀀스의 각 단어에 대한 다양한 요소와 관계에 대한 표현을 포함한다.
- 단어 간의 상호 작용, 유사성, 구문 정보, 문맥 정보
Decoder : Input에 대한 처리
- Decoder Input에 대해 마찬가지로 Embedding-vector로 변환하고 Positional Encoding을 통해 위치 정보를 달아준다.
- Decoder의 입력은 현재까지 생성된 단어들을 의미한다.
Decoder : Self Attention Layer
- Decoder Input에 대해서도 마찬가지로 Self-Attention을 수행한다. 현재 단어가 이전 단어들과 어떻게 상호작용하는지를 학습하는 것이다.
- Masked Multi-head Self-Attention : . 디코더가 현재 시점 이전의 단어들만을 참고하고 미래 단어를 참조하지 않도록 Self-Attention에서 마스킹을 사용한다.
- Multi-head : Self-Attention Head에서 병렬로 수행된다는 것을 의미한다.
Decoder : Encoder-Decoder Attention Layer
출력의 각 단어가 입력 소스의 어떤 부분과 연관이 있는지를 파악하는 과정. Self Attention이 아니다!.
지금까지 우리는 Self-Attention을 통해서 입력 단어 간의 상호관계, 출력 단어 간의 상호관계를 계산하였다. 이를 기반으로 나온 Encoder의 Self-Attention value와 Decoder의 Self-Attention value를 이용하여 입력과 출력 단어 간의 상호관계를 파악하는 과정이다.
이번에는 Self Attention이 아니다!
Encoder-Decoder Attention Value의 생성 과정
그림을 자세히 보면 Attention으로 들어오는 화살표가 세 개가 있고, Encoder로부터 2개, Decoder로부터 1개가 온다.
- Query Vector은 Query는 디코더의 첫번째 서브층(Self-attention Layer)의 결과 행렬로부터 얻는다.
- Key Vector와 Value Vector은 Encoder의 최종 출력으로부터 얻는다.
- Encoder의 출력은 입력 시퀀스의 각 단어에 대한 다양한 요소와 관계에 대한 표현을 포함한다.
Query (Decoder) -> Key, Value(Encoder) 대응으로 유사도 매칭
- Decoder가 Encoder에게 다음에 올 단어에 대한 원래 문장에서의 관계에 대해서 Query(질의)를 하면 Encoder가 입력 문장과의 연관관계(Key, Value)를 응답해주는 관계이다.
- Softmax를 통해 Attention Weights를 생성 -> Value 벡터의 가중합을 계산하여 Attention value(Context Vector) 생성
- 해당 Context vector(Encoder-Decoder Attention Value)은 다음 단어를 예측하는 데 사용된다.
Decoder : 출력
1. Feed Forward : Fully Connected Layers로 구성된 신경망을 통해 비선형 변환을 수행한다.
- Layer의 Input에 대해 정규화를 통해서 학습 속도를 향상시키고 안정되게 한다.
- Residual Connection(잔차 연결)을 통해 입력을 출력에 더해주면서 모델이 원래 입력을 참조할 수 있게 한다.
2. Linear Transformation : 선형 변환을 통해 차원을 줄여서 출력 벡터의 크기를 줄여준다.
3. Softmax 활성화 함수 : 출력 벡터를 확률 분포로 변환한다. Decoder는 다음 단어를 선택하기 위해 Softmax 출력 중 확률이 가장 높은 단어를 선택하여 최종적으로 출력한다.
Transformer 정리
Attention
여러 개의 인코더와 디코더
앞에서 잠시 언급하였지만 Transformer는 이렇게 만들어진 Encoder와 Decoder를 여러 Layer로 쌓아서 구성된다.
다양한 정보의 획득: 인코더는 여러 관점에서의 임베딩을 수행할 수 있고, 디코더 또한 다양한 관점에서의 출력 시퀀스를 내보낼 수 있다.
Multi-Head Attention :여러 Self-Attention Head에서 병렬로 수행하기 위해서 Multi-Head Attention을 사용한다고 하였다.
.
Transformer 모델의 확장
BERT (2018)(Pretraining of Deep Bidirectional Transformers for Language Understanding)
Masked Language Model : 입력 텍스트의 15% 단어를 랜덤으로 MASKING 후 예측하도록 학습
Next Sentence Prediction: 두 문장을 주고 이 문장이 이어지는 문장인지 아닌지 판별
GPT(Generative Pre-trained Transformer)
1. GPT-1 (2018)
1억 1천만 개의 파라미터를 갖고 있음, Self-supervised learning 으로 만든 언어 모델, Pretraining-finetuning의 대표적인 모델
간단한 질의응답, 번역, 요약 등 다양한 NLP 작업등을 수행할 수 있다.
2. GPT-2 (2019)
15억 개의 파라미터를 갖고 있음, 더 다양하고 방대한 데이터셋으로 사전 학습
생성된 텍스트의 질이 높아져, 실제와 구분하기, 어려운 수준의 자연스러운 텍스트를 생성
3. GPT-3 (2020)
약 1750억 개의 파라미터를 갖고 있음, 더 다양하고 방대한 데이터셋으로 사전 학습
사전 학습만으로도 여러 NLP 작업에서 높은 성능을 보여주며, 별도의 미세 조정 없이도 사용가능한 일반화 모델
3. GPT-4 (2023)
약 1조 7천억개의 파라미터를 가진다고 추정된다(미공개). 한 번에 처리할 수 있는 단어량을 3,000개에서 25,000개로 8배 이상 확대, 이미지와 동영상을 인식할 수 있다. AI가 사실과 다른 것을 마치 진실인 것처럼 강한 확신을 담아 답변하는 할루시네이션을 상당 부분 줄이는 데 성공했다.
논란(2024.06 기준) : 학계에서 매개변수가 월등히 늘었음에도 불구하고 비례적인 성능 향상이 이루어지지 않았다, 오히려 성능이 나빠졌다는 의견이 있다. MoE 구조가 오히려 영향을 주었다는 학계의 GPT와 트랜스포머의 한계점을 시사하는 일부의견이 등장하였다.
간단한 챗봇 만들기 : Transformer 모델 구성 및 학습
코랩 GPU를 다썼다.. 자고 일어나면 학습되어 있으려나..
from IPython.core.display import display, HTML
display(HTML("<style>.container {width:90% !important;}</style>"))
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
import os
os.chdir('drive/MyDrive/DL2024_201810776/week12')
%load_ext autoreload
%autoreload 2
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
Positional Encoding 준비¶
class PositionalEncoding(tf.keras.layers.Layer):
def __init__(self, position, d_model):
super(PositionalEncoding, self).__init__()
self.pos_encoding = self.positional_encoding(position, d_model)
def get_angles(self, position, i, d_model):
angles = 1 / tf.pow(10000, (2 * (i // 2)) / tf.cast(d_model, tf.float32))
return position * angles
def positional_encoding(self, position, d_model):
angle_rads = self.get_angles(
position=tf.range(position, dtype=tf.float32)[:, tf.newaxis],
i=tf.range(d_model, dtype=tf.float32)[tf.newaxis, :],
d_model=d_model)
# 배열의 짝수 인덱스(2i)에는 사인 함수 적용
sines = tf.math.sin(angle_rads[:, 0::2])
# 배열의 홀수 인덱스(2i+1)에는 코사인 함수 적용
cosines = tf.math.cos(angle_rads[:, 1::2])
angle_rads = np.zeros(angle_rads.shape)
angle_rads[:, 0::2] = sines
angle_rads[:, 1::2] = cosines
pos_encoding = tf.constant(angle_rads)
pos_encoding = pos_encoding[tf.newaxis, ...]
print(pos_encoding.shape)
return tf.cast(pos_encoding, tf.float32)
def call(self, inputs):
return inputs + self.pos_encoding[:, :tf.shape(inputs)[1], :]
sample_pos_encoding = PositionalEncoding(50, 128)
plt.pcolormesh(sample_pos_encoding.pos_encoding.numpy()[0], cmap='RdBu')
plt.xlabel('Depth')
plt.xlim((0, 128))
plt.ylabel('Position')
plt.colorbar()
plt.show()
(1, 50, 128)
MultiHeadAttention 준비¶
def scaled_dot_product_attention(query, key, value, mask):
# query 크기 : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
# key 크기 : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
# value 크기 : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
# padding_mask : (batch_size, 1, 1, key의 문장 길이)
# Q와 K의 곱. 어텐션 스코어 행렬.
matmul_qk = tf.matmul(query, key, transpose_b=True)
# 스케일링
# dk의 루트값으로 나눠준다.
depth = tf.cast(tf.shape(key)[-1], tf.float32)
logits = matmul_qk / tf.math.sqrt(depth)
# 마스킹. 어텐션 스코어 행렬의 마스킹 할 위치에 매우 작은 음수값을 넣는다.
# 매우 작은 값이므로 소프트맥스 함수를 지나면 행렬의 해당 위치의 값은 0이 된다.
if mask is not None:
logits += (mask * -1e9)
# 소프트맥스 함수는 마지막 차원인 key의 문장 길이 방향으로 수행된다.
# attention weight : (batch_size, num_heads, query의 문장 길이, key의 문장 길이)
attention_weights = tf.nn.softmax(logits, axis=-1)
# output : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
output = tf.matmul(attention_weights, value)
return output, attention_weights
class MultiHeadAttention(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, name="multi_head_attention"):
super(MultiHeadAttention, self).__init__(name=name)
self.num_heads = num_heads
self.d_model = d_model
assert d_model % self.num_heads == 0
# d_model을 num_heads로 나눈 값.
# 논문 기준 : 64
self.depth = d_model // self.num_heads
# WQ, WK, WV에 해당하는 밀집층 정의
self.query_dense = tf.keras.layers.Dense(units=d_model)
self.key_dense = tf.keras.layers.Dense(units=d_model)
self.value_dense = tf.keras.layers.Dense(units=d_model)
# WO에 해당하는 밀집층 정의
self.dense = tf.keras.layers.Dense(units=d_model)
# num_heads 개수만큼 q, k, v를 split하는 함수
def split_heads(self, inputs, batch_size):
inputs = tf.reshape(
inputs, shape=(batch_size, -1, self.num_heads, self.depth))
return tf.transpose(inputs, perm=[0, 2, 1, 3])
def call(self, inputs):
query, key, value, mask = inputs['query'], inputs['key'], inputs[
'value'], inputs['mask']
batch_size = tf.shape(query)[0]
# 1. WQ, WK, WV에 해당하는 밀집층 지나기
# q : (batch_size, query의 문장 길이, d_model)
# k : (batch_size, key의 문장 길이, d_model)
# v : (batch_size, value의 문장 길이, d_model)
# 참고) 인코더(k, v)-디코더(q) 어텐션에서는 query 길이와 key, value의 길이는 다를 수 있다.
query = self.query_dense(query)
key = self.key_dense(key)
value = self.value_dense(value)
# 2. 헤드 나누기
# q : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
# k : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
# v : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
query = self.split_heads(query, batch_size)
key = self.split_heads(key, batch_size)
value = self.split_heads(value, batch_size)
# 3. 스케일드 닷 프로덕트 어텐션. 앞서 구현한 함수 사용.
# (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
scaled_attention, _ = scaled_dot_product_attention(query, key, value, mask)
# (batch_size, query의 문장 길이, num_heads, d_model/num_heads)
scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])
# 4. 헤드 연결(concatenate)하기
# (batch_size, query의 문장 길이, d_model)
concat_attention = tf.reshape(scaled_attention,
(batch_size, -1, self.d_model))
# 5. WO에 해당하는 밀집층 지나기
# (batch_size, query의 문장 길이, d_model)
outputs = self.dense(concat_attention)
return outputs
Encoder Layer 정의¶
1. 입력¶
- inputs: 입력 시퀀스
- padding_mask: 시퀀스의 패딩 마스크
2. Self-Attention (첫 번째 Sub layer)¶
- 어텐션 메커니즘은 쿼리, 키, 밸류의 세 가지 입력을 사용
- 같은 (inputs)으로 Multi-head Attention을 수행
- 패딩 마스크 : 입력 시퀀스의 패딩 토큰을 마스킹
- 입력 시퀀스의 각 단어에 대한 어텐션 가중치를 계산
3. Feed-forwarding (두 번째 Sub layer)¶
- 피드 포워드 신경을 적용
- ReLU 활성화 함수가 있는 두 개의 Layer
4. 드롭아웃 및 잔차 연결 및 층 정규화¶
- 과적합을 방지하기 위해 어텐션 출력에 드롭아웃이 적용
- 결과를 입력 시퀀스에 추가하고 정규화
5. 출력¶
- 인코더 레이어의 최종 출력 반환
def create_padding_mask(x):
mask = tf.cast(tf.math.equal(x, 0), tf.float32)
# (batch_size, 1, 1, key의 문장 길이)
return mask[:, tf.newaxis, tf.newaxis, :]
def encoder_layer(dff, d_model, num_heads, dropout, name="encoder_layer"):
inputs = tf.keras.Input(shape=(None, d_model), name="inputs")
# 인코더는 패딩 마스크 사용
padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")
# 멀티-헤드 어텐션 (첫번째 서브층 / 셀프 어텐션)
attention = MultiHeadAttention(
d_model, num_heads, name="attention")({
'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
'mask': padding_mask # 패딩 마스크 사용
})
# 드롭아웃 + 잔차 연결과 층 정규화
attention = tf.keras.layers.Dropout(rate=dropout)(attention)
attention = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(inputs + attention)
# 포지션 와이즈 피드 포워드 신경망 (두번째 서브층)
outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
outputs = tf.keras.layers.Dense(units=d_model)(outputs)
# 드롭아웃 + 잔차 연결과 층 정규화
outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
outputs = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(attention + outputs)
return tf.keras.Model(
inputs=[inputs, padding_mask], outputs=outputs, name=name)
def encoder(vocab_size, num_layers, dff,
d_model, num_heads, dropout,
name="encoder"):
inputs = tf.keras.Input(shape=(None,), name="inputs")
# 인코더는 패딩 마스크 사용
padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")
# 포지셔널 인코딩 + 드롭아웃
embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)
# 인코더를 num_layers개 쌓기
for i in range(num_layers):
outputs = encoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
dropout=dropout, name="encoder_layer_{}".format(i),
)([outputs, padding_mask])
return tf.keras.Model(
inputs=[inputs, padding_mask], outputs=outputs, name=name)
Decoder Layer 정의¶
1. 입력¶
- inputs: Decoder의 입력 시퀀스
- enc_outputs: Encoder의 출력을 나타내는 입력
2. Masked self-attention (첫 번째 Sub layer):¶
- 입력에 대한 Multi-head Attention
- self-attention이므로 (Q = K = V).
- Look-ahead mask : 현재 위치 이후의 토큰에 대한 정보를 참조하지 않음
3. 잔차 연결과 정규화¶
- 어텐션 결과와 입력을 더한 후 잔차 연결, 정규화 수행
4. Decoder-Encoder Attention (두 번째 Sub layer):¶
- 쿼리(Q)는 이전 Sub layer의 결과인 attention1
- 키와 값(K, V)는 Encoder의 출력인 enc_outputs
5. Feed-fowarding (세 번째 Sub layer):¶
- 피드 포워드 신경망 적용
- ReLU 활성화 함수가 있는 두 개의 Layer
6. 출력:¶
- 최종 출력은 잔차 연결 및 정규화 후에 반환
def create_look_ahead_mask(x):
seq_len = tf.shape(x)[1]
look_ahead_mask = 1 - tf.linalg.band_part(tf.ones((seq_len, seq_len)), -1, 0)
padding_mask = create_padding_mask(x) # 패딩 마스크도 포함
return tf.maximum(look_ahead_mask, padding_mask)
def decoder_layer(dff, d_model, num_heads, dropout, name="decoder_layer"):
inputs = tf.keras.Input(shape=(None, d_model), name="inputs")
enc_outputs = tf.keras.Input(shape=(None, d_model), name="encoder_outputs")
# 디코더는 룩어헤드 마스크(첫번째 서브층)와 패딩 마스크(두번째 서브층) 둘 다 사용.
look_ahead_mask = tf.keras.Input(
shape=(1, None, None), name="look_ahead_mask")
padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')
# 멀티-헤드 어텐션 (첫번째 서브층 / 마스크드 셀프 어텐션)
attention1 = MultiHeadAttention(
d_model, num_heads, name="attention_1")(inputs={
'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
'mask': look_ahead_mask # 룩어헤드 마스크
})
# 잔차 연결과 층 정규화
attention1 = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(attention1 + inputs)
# 멀티-헤드 어텐션 (두번째 서브층 / 디코더-인코더 어텐션)
attention2 = MultiHeadAttention(
d_model, num_heads, name="attention_2")(inputs={
'query': attention1, 'key': enc_outputs, 'value': enc_outputs, # Q != K = V
'mask': padding_mask # 패딩 마스크
})
# 드롭아웃 + 잔차 연결과 층 정규화
attention2 = tf.keras.layers.Dropout(rate=dropout)(attention2)
attention2 = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(attention2 + attention1)
# 포지션 와이즈 피드 포워드 신경망 (세번째 서브층)
outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention2)
outputs = tf.keras.layers.Dense(units=d_model)(outputs)
# 드롭아웃 + 잔차 연결과 층 정규화
outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
outputs = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(outputs + attention2)
return tf.keras.Model(
inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
outputs=outputs,
name=name)
def decoder(vocab_size, num_layers, dff,
d_model, num_heads, dropout,
name='decoder'):
inputs = tf.keras.Input(shape=(None,), name='inputs')
enc_outputs = tf.keras.Input(shape=(None, d_model), name='encoder_outputs')
# 디코더는 룩어헤드 마스크(첫번째 서브층)와 패딩 마스크(두번째 서브층) 둘 다 사용.
look_ahead_mask = tf.keras.Input(
shape=(1, None, None), name='look_ahead_mask')
padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')
# 포지셔널 인코딩 + 드롭아웃
embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)
# 디코더를 num_layers개 쌓기
for i in range(num_layers):
outputs = decoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
dropout=dropout, name='decoder_layer_{}'.format(i),
)(inputs=[outputs, enc_outputs, look_ahead_mask, padding_mask])
return tf.keras.Model(
inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
outputs=outputs,
name=name)
def transformer(vocab_size, num_layers, dff,
d_model, num_heads, dropout,
name="transformer"):
# 인코더의 입력
inputs = tf.keras.Input(shape=(None,), name="inputs")
# 디코더의 입력
dec_inputs = tf.keras.Input(shape=(None,), name="dec_inputs")
# 인코더의 패딩 마스크
enc_padding_mask = tf.keras.layers.Lambda(
create_padding_mask, output_shape=(1, 1, None),
name='enc_padding_mask')(inputs)
# 디코더의 룩어헤드 마스크(첫번째 서브층)
look_ahead_mask = tf.keras.layers.Lambda(
create_look_ahead_mask, output_shape=(1, None, None),
name='look_ahead_mask')(dec_inputs)
# 디코더의 패딩 마스크(두번째 서브층)
dec_padding_mask = tf.keras.layers.Lambda(
create_padding_mask, output_shape=(1, 1, None),
name='dec_padding_mask')(inputs)
# 인코더의 출력은 enc_outputs. 디코더로 전달
enc_outputs = encoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
d_model=d_model, num_heads=num_heads, dropout=dropout,
)(inputs=[inputs, enc_padding_mask]) # 인코더의 입력은 입력 문장과 패딩 마스크
# 디코더의 출력은 dec_outputs. 출력층으로 전달
dec_outputs = decoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
d_model=d_model, num_heads=num_heads, dropout=dropout,
)(inputs=[dec_inputs, enc_outputs, look_ahead_mask, dec_padding_mask])
# 다음 단어 예측을 위한 출력층
outputs = tf.keras.layers.Dense(units=vocab_size, name="outputs")(dec_outputs)
return tf.keras.Model(inputs=[inputs, dec_inputs], outputs=outputs, name=name)
모델 아키텍쳐 보기¶
small_transformer = transformer(
vocab_size = 9000,
num_layers = 4,
dff = 512,
d_model = 128,
num_heads = 4,
dropout = 0.3,
name="small_transformer")
tf.keras.utils.plot_model(
small_transformer, to_file='small_transformer.png', show_shapes=True)
(1, 9000, 128) (1, 9000, 128)
Loss Function¶
- NLP는 다중 클래스 분류 문제이므로 Categorical Cross Entropy를 Loss Function으로 사용
def loss_function(y_true, y_pred):
y_true = tf.reshape(y_true, shape=(-1, MAX_LENGTH - 1))
loss = tf.keras.losses.SparseCategoricalCrossentropy(
from_logits=True, reduction='none')(y_true, y_pred)
mask = tf.cast(tf.not_equal(y_true, 0), tf.float32)
loss = tf.multiply(loss, mask)
return tf.reduce_mean(loss)
Transformer의 학습률 스케줄링 정의¶
- 초기에는 학습률을 증가시키고 점차 감소시키는 방식으로 학습
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
def __init__(self, d_model, warmup_steps=4000):
super(CustomSchedule, self).__init__()
self.d_model = d_model
self.warmup_steps = warmup_steps
def __call__(self, step):
step = tf.cast(step, tf.float32) # Explicitly cast step to float32
arg1 = tf.math.rsqrt(step)
arg2 = step * (self.warmup_steps**-1.5)
return tf.math.minimum(arg1, arg2) * self.d_model**-0.5
def get_config(self):
config = {
"d_model": self.d_model,
"warmup_steps": self.warmup_steps,
}
return config
sample_learning_rate = CustomSchedule(d_model=128)
plt.plot(sample_learning_rate(tf.range(200000, dtype=tf.float32)))
plt.ylabel("Learning Rate")
plt.xlabel("Train Step")
Text(0.5, 0, 'Train Step')
1. 데이터 로드¶
- https://github.com/songys/Chatbot_data
- 한국어 채팅에 관한 데이터셋
import pandas as pd
import urllib.request
import tensorflow_datasets as tfds
import tensorflow as tf
import time
import numpy as np
import matplotlib.pyplot as plt
import re
train_data = pd.read_csv('dataset/ChatBotData.csv')
train_data.head()
Q | A | label | |
---|---|---|---|
0 | 12시 땡! | 하루가 또 가네요. | 0 |
1 | 1지망 학교 떨어졌어 | 위로해 드립니다. | 0 |
2 | 3박4일 놀러가고 싶다 | 여행은 언제나 좋죠. | 0 |
3 | 3박4일 정도 놀러가고 싶다 | 여행은 언제나 좋죠. | 0 |
4 | PPL 심하네 | 눈살이 찌푸려지죠. | 0 |
print('챗봇 샘플의 개수 :', len(train_data))
챗봇 샘플의 개수 : 11823
2. 데이터 전처리 및 토큰화, 임베딩 벡터로 변환¶
questions = []
for sentence in train_data['Q']:
# 구두점에 대해서 띄어쓰기
# ex) 12시 땡! -> 12시 땡 !
sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
sentence = sentence.strip()
questions.append(sentence)
answers = []
for sentence in train_data['A']:
# 구두점에 대해서 띄어쓰기
# ex) 12시 땡! -> 12시 땡 !
sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
sentence = sentence.strip()
answers.append(sentence)
print(questions[:5])
print(answers[:5])
['12시 땡 !', '1지망 학교 떨어졌어', '3박4일 놀러가고 싶다', '3박4일 정도 놀러가고 싶다', 'PPL 심하네'] ['하루가 또 가네요 .', '위로해 드립니다 .', '여행은 언제나 좋죠 .', '여행은 언제나 좋죠 .', '눈살이 찌푸려지죠 .']
서브워드 텍스트 인코더¶
- 텍스트 데이터를 토큰화하고, 단어나 문자 단위로 나누는 대신 하위 단어(subword)로 분할
# 서브워드텍스트인코더를 사용하여 질문과 답변을 모두 포함한 단어 집합(Vocabulary) 생성
tokenizer = tfds.deprecated.text.SubwordTextEncoder.build_from_corpus(
questions + answers, target_vocab_size=2**13)
# 시작 토큰과 종료 토큰에 대한 정수 부여.
START_TOKEN, END_TOKEN = [tokenizer.vocab_size], [tokenizer.vocab_size + 1]
# 시작 토큰과 종료 토큰을 고려하여 단어 집합의 크기를 + 2
VOCAB_SIZE = tokenizer.vocab_size + 2
print('시작 토큰 번호 :',START_TOKEN)
print('종료 토큰 번호 :',END_TOKEN)
print('단어 집합의 크기 :',VOCAB_SIZE)
시작 토큰 번호 : [8178] 종료 토큰 번호 : [8179] 단어 집합의 크기 : 8180
# 서브워드텍스트인코더 토크나이저의 .encode()를 사용하여 텍스트 시퀀스를 정수 시퀀스로 변환.
print('Tokenized sample question: {}'.format(tokenizer.encode(questions[20])))
Tokenized sample question: [5766, 611, 3509, 141, 685, 3747, 849]
# 서브워드텍스트인코더 토크나이저의 .encode()와 decode() 테스트해보기
# 임의의 입력 문장을 sample_string에 저장
sample_string = questions[20]
# encode() : 텍스트 시퀀스 --> 정수 시퀀스
tokenized_string = tokenizer.encode(sample_string)
print ('정수 인코딩 후의 문장 {}'.format(tokenized_string))
# decode() : 정수 시퀀스 --> 텍스트 시퀀스
original_string = tokenizer.decode(tokenized_string)
print ('기존 문장: {}'.format(original_string))
정수 인코딩 후의 문장 [5766, 611, 3509, 141, 685, 3747, 849] 기존 문장: 가스비 비싼데 감기 걸리겠어
# 각 정수는 각 단어와 어떻게 mapping되는지 병렬로 출력
# 서브워드텍스트인코더는 의미있는 단위의 서브워드로 토크나이징한다. 띄어쓰기 단위 X 형태소 분석 단위 X
for ts in tokenized_string:
print ('{} ----> {}'.format(ts, tokenizer.decode([ts])))
5766 ----> 가스 611 ----> 비 3509 ----> 비싼 141 ----> 데 685 ----> 감기 3747 ----> 걸리 849 ----> 겠어
# 최대 길이를 40으로 정의
MAX_LENGTH = 40
# 토큰화 / 정수 인코딩 / 시작 토큰과 종료 토큰 추가 / 패딩
def tokenize_and_filter(inputs, outputs):
tokenized_inputs, tokenized_outputs = [], []
for (sentence1, sentence2) in zip(inputs, outputs):
# encode(토큰화 + 정수 인코딩), 시작 토큰과 종료 토큰 추가
sentence1 = START_TOKEN + tokenizer.encode(sentence1) + END_TOKEN
sentence2 = START_TOKEN + tokenizer.encode(sentence2) + END_TOKEN
tokenized_inputs.append(sentence1)
tokenized_outputs.append(sentence2)
# 패딩
tokenized_inputs = tf.keras.preprocessing.sequence.pad_sequences(
tokenized_inputs, maxlen=MAX_LENGTH, padding='post')
tokenized_outputs = tf.keras.preprocessing.sequence.pad_sequences(
tokenized_outputs, maxlen=MAX_LENGTH, padding='post')
return tokenized_inputs, tokenized_outputs
questions, answers = tokenize_and_filter(questions, answers)
print('질문 데이터의 크기(shape) :', questions.shape)
print('답변 데이터의 크기(shape) :', answers.shape)
질문 데이터의 크기(shape) : (11823, 40) 답변 데이터의 크기(shape) : (11823, 40)
임베딩 벡터 출력¶
idx = 9
print(questions[idx])
print(answers[idx])
[8178 2161 782 7612 204 84 183 352 1258 8179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [8178 350 3985 16 32 1 8179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
print('단어 집합의 크기(Vocab size): {}'.format(VOCAB_SIZE))
print('전체 샘플의 수(Number of samples): {}'.format(len(questions)))
단어 집합의 크기(Vocab size): 8180 전체 샘플의 수(Number of samples): 11823
# 텐서플로우 dataset을 이용하여 셔플(shuffle)을 수행하되, 배치 크기로 데이터를 묶는다.
# 또한 이 과정에서 교사 강요(teacher forcing)을 사용하기 위해서 디코더의 입력과 실제값 시퀀스를 구성한다.
BATCH_SIZE = 64
BUFFER_SIZE = 20000
# 디코더의 실제값 시퀀스에서는 시작 토큰을 제거해야 한다.
dataset = tf.data.Dataset.from_tensor_slices((
{
'inputs': questions,
'dec_inputs': answers[:, :-1] # 디코더의 입력. 마지막 패딩 토큰이 제거된다.
},
{
'outputs': answers[:, 1:] # 맨 처음 토큰이 제거된다. 다시 말해 시작 토큰이 제거된다.
},
))
dataset = dataset.cache()
dataset = dataset.shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
# 임의의 샘플에 대해서 [:, :-1]과 [:, 1:]이 어떤 의미를 가지는지 테스트해본다.
print(answers[0]) # 기존 샘플
print(answers[:1][:, :-1]) # 마지막 패딩 토큰 제거하면서 길이가 39가 된다.
print(answers[:1][:, 1:]) # 맨 처음 토큰이 제거된다. 다시 말해 시작 토큰이 제거된다. 길이는 역시 39가 된다.
[8178 3844 74 7894 1 8179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [[8178 3844 74 7894 1 8179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]] [[3844 74 7894 1 8179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
tf.keras.backend.clear_session()
# Hyper-parameters
NUM_LAYERS = 2
D_MODEL = 256
NUM_HEADS = 8
DFF = 512
DROPOUT = 0.1
model = transformer(
vocab_size=VOCAB_SIZE,
num_layers=NUM_LAYERS,
dff=DFF,
d_model=D_MODEL,
num_heads=NUM_HEADS,
dropout=DROPOUT)
(1, 8180, 256) (1, 8180, 256)
MAX_LENGTH = 40
learning_rate = CustomSchedule(D_MODEL)
optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.9, beta_2=0.98, epsilon=1e-9)
def accuracy(y_true, y_pred):
# ensure labels have shape (batch_size, MAX_LENGTH - 1)
y_true = tf.reshape(y_true, shape=(-1, MAX_LENGTH - 1))
return tf.keras.metrics.sparse_categorical_accuracy(y_true, y_pred)
model.compile(optimizer=optimizer, loss=loss_function, metrics=[accuracy])
EPOCHS = 50
model.fit(dataset, epochs=EPOCHS)
Epoch 1/50 185/185 [==============================] - 45s 121ms/step - loss: 1.4468 - accuracy: 0.0261 Epoch 2/50 185/185 [==============================] - 14s 75ms/step - loss: 1.1741 - accuracy: 0.0494 Epoch 3/50 185/185 [==============================] - 12s 65ms/step - loss: 1.0053 - accuracy: 0.0506 Epoch 4/50 185/185 [==============================] - 12s 65ms/step - loss: 0.9293 - accuracy: 0.0543 Epoch 5/50 185/185 [==============================] - 12s 63ms/step - loss: 0.8713 - accuracy: 0.0574 Epoch 6/50 185/185 [==============================] - 12s 64ms/step - loss: 0.8116 - accuracy: 0.0618 Epoch 7/50 185/185 [==============================] - 11s 62ms/step - loss: 0.7450 - accuracy: 0.0681 Epoch 8/50 185/185 [==============================] - 11s 60ms/step - loss: 0.6709 - accuracy: 0.0759 Epoch 9/50 185/185 [==============================] - 11s 61ms/step - loss: 0.5922 - accuracy: 0.0845 Epoch 10/50 185/185 [==============================] - 11s 62ms/step - loss: 0.5098 - accuracy: 0.0940 Epoch 11/50 185/185 [==============================] - 12s 63ms/step - loss: 0.4269 - accuracy: 0.1039 Epoch 12/50 185/185 [==============================] - 11s 61ms/step - loss: 0.3459 - accuracy: 0.1152 Epoch 13/50 185/185 [==============================] - 11s 61ms/step - loss: 0.2710 - accuracy: 0.1259 Epoch 14/50 185/185 [==============================] - 12s 62ms/step - loss: 0.2056 - accuracy: 0.1363 Epoch 15/50 185/185 [==============================] - 11s 61ms/step - loss: 0.1513 - accuracy: 0.1454 Epoch 16/50 185/185 [==============================] - 11s 61ms/step - loss: 0.1096 - accuracy: 0.1529 Epoch 17/50 185/185 [==============================] - 12s 64ms/step - loss: 0.0796 - accuracy: 0.1587 Epoch 18/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0618 - accuracy: 0.1620 Epoch 19/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0515 - accuracy: 0.1635 Epoch 20/50 185/185 [==============================] - 12s 62ms/step - loss: 0.0453 - accuracy: 0.1646 Epoch 21/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0428 - accuracy: 0.1649 Epoch 22/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0410 - accuracy: 0.1651 Epoch 23/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0369 - accuracy: 0.1660 Epoch 24/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0323 - accuracy: 0.1671 Epoch 25/50 185/185 [==============================] - 11s 62ms/step - loss: 0.0287 - accuracy: 0.1680 Epoch 26/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0249 - accuracy: 0.1688 Epoch 27/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0229 - accuracy: 0.1694 Epoch 28/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0201 - accuracy: 0.1701 Epoch 29/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0184 - accuracy: 0.1706 Epoch 30/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0167 - accuracy: 0.1709 Epoch 31/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0155 - accuracy: 0.1713 Epoch 32/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0143 - accuracy: 0.1716 Epoch 33/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0129 - accuracy: 0.1719 Epoch 34/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0126 - accuracy: 0.1719 Epoch 35/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0110 - accuracy: 0.1725 Epoch 36/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0114 - accuracy: 0.1723 Epoch 37/50 185/185 [==============================] - 12s 63ms/step - loss: 0.0099 - accuracy: 0.1727 Epoch 38/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0098 - accuracy: 0.1727 Epoch 39/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0093 - accuracy: 0.1729 Epoch 40/50 185/185 [==============================] - 12s 63ms/step - loss: 0.0089 - accuracy: 0.1729 Epoch 41/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0081 - accuracy: 0.1732 Epoch 42/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0079 - accuracy: 0.1731 Epoch 43/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0078 - accuracy: 0.1732 Epoch 44/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0070 - accuracy: 0.1734 Epoch 45/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0069 - accuracy: 0.1735 Epoch 46/50 185/185 [==============================] - 11s 61ms/step - loss: 0.0069 - accuracy: 0.1734 Epoch 47/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0064 - accuracy: 0.1736 Epoch 48/50 185/185 [==============================] - 11s 60ms/step - loss: 0.0061 - accuracy: 0.1736 Epoch 49/50 185/185 [==============================] - 11s 58ms/step - loss: 0.0060 - accuracy: 0.1736 Epoch 50/50 185/185 [==============================] - 11s 59ms/step - loss: 0.0056 - accuracy: 0.1737
<keras.src.callbacks.History at 0x7bf9ebd1a110>
model.save_weights('tansformer_weight')
check_model = transformer(
vocab_size=VOCAB_SIZE,
num_layers=NUM_LAYERS,
dff=DFF,
d_model=D_MODEL,
num_heads=NUM_HEADS,
dropout=DROPOUT)
check_model.load_weights('tansformer_weight')
(1, 8180, 256) (1, 8180, 256)
<tensorflow.python.checkpoint.checkpoint.CheckpointLoadStatus at 0x7bf9eaef4b80>
4. 결과 확인¶
- 답변이 신뢰감은 안가지만 챗봇의 역할을 하는 모델을 훈련해보았다.
def preprocess_sentence(sentence):
sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
sentence = sentence.strip()
return sentence
def evaluate(sentence):
sentence = preprocess_sentence(sentence)
sentence = tf.expand_dims(
START_TOKEN + tokenizer.encode(sentence) + END_TOKEN, axis=0)
output = tf.expand_dims(START_TOKEN, 0)
# 디코더의 예측 시작
for i in range(MAX_LENGTH):
predictions = model(inputs=[sentence, output], training=False)
# 현재(마지막) 시점의 예측 단어를 받아온다.
predictions = predictions[:, -1:, :]
predicted_id = tf.cast(tf.argmax(predictions, axis=-1), tf.int32)
# 만약 마지막 시점의 예측 단어가 종료 토큰이라면 예측을 중단
if tf.equal(predicted_id, END_TOKEN[0]):
break
# 마지막 시점의 예측 단어를 출력에 연결한다.
# 이는 for문을 통해서 디코더의 입력으로 사용될 예정이다.
output = tf.concat([output, predicted_id], axis=-1)
return tf.squeeze(output, axis=0)
def predict(sentence):
prediction = evaluate(sentence)
predicted_sentence = tokenizer.decode(
[i for i in prediction if i < tokenizer.vocab_size])
print('Input: {}'.format(sentence))
print('Output: {}'.format(predicted_sentence))
return predicted_sentence
output = predict("사랑해")
Input: 사랑해 Output: 하늘 만큼 땅 만큼 사랑해요 .
output = predict("밥 먹었어?")
Input: 밥 먹었어? Output: 저는 배터리가 밥이예요 .
output = predict("나랑 쇼핑하러 가자")
Input: 나랑 쇼핑하러 가자 Output: 좋아요 !
output = predict("바다로 여행가고 싶어")
Input: 바다로 여행가고 싶어 Output: 여행 좋죠 !
<References>
https://wikidocs.net/22893
https://blog.promedius.ai/transformer/
해당 포스팅의 내용은 "상명대학교 민경하 교수님 "인공지능" 수업, 상명대학교 김승현 교수님 "딥러닝"수업을 기반으로 작성하였으며, 포스팅 자료는 해당 내용을 기반으로 재구성하여 만들어 사용하였습니다.
'Data Science > 머신러닝 & 딥러닝' 카테고리의 다른 글
[딥러닝] 생성형 모델 : VAE / GAN / Diffusion (+ 이미지 생성 및 복원해보기) (0) | 2024.06.17 |
---|---|
[딥러닝] 기계 번역 : Seq2Seq와 Attention (+ 모델 학습시켜 다국어 번역해보기) (0) | 2024.06.11 |
[딥러닝] NLP : 자연어 처리 기본 (+ 영화 리뷰글 긍정/부정 판단해보기) (0) | 2024.06.09 |
[딥러닝] 기억하는 신경망 : RNN, 그리고 개선 모델 (LSTM, GRU) (0) | 2024.06.08 |
[딥러닝] CNN : ResNet 모델로 동물 이미지 분류하기(CIFAR 이미지셋) (0) | 2024.06.08 |