피드포워드 신경망은 입력의 길이가 고정되어 있었다.
이번 장에서는 다양한 길이의 입력 시퀀스를 처리할 수 있는 RNN, 이를 개선한 LSTM, GRU에 대해 학습한다.
# 08-01 순환 신경망(Recurrent Neural Network, RNN)
1. RNN이란?
-시퀀스 모델: 입력과 출력을 시퀀스 단위로 처리. 따라서 입력벡터, 출력벡터, 은닉상태라는 표현 사용
-메모리셀/RNN셀(cell): RNN의 은닉층에서 이전값을 기억하는 메모리 역할을 수행하는 노드
-은닉상태(hidden state): 메모리셀이 출력층 혹은 다음시점인 t+1의 자신에게 보내는 값
-FFNN과 달리 입출력길이를 다양하게 설계 가능 (일대다, 다대일, 다대다)
ex) 일대다: 이미지캡셔닝(하나의 이미지에 대해 다양한 제목 출력)
ex) 다대일: 감성분류/스팸메일분류
ex) 다대다:번역기, 개체명인식, 품사태깅
-재귀활동: 은닉층의 메모리셀은 각시점(t)에서 이전시점(t-1)으로부터 온 값을 자신의 입력으로 사용. 아래 그림들 참고
Step1) 은닉층: 현재시점의 입력값 Xt(d차원)과 가중치 Wx가 곱해지고, 이전시점으로부터의 은닉상태값 ht-1(D차원)과 가중치 Wh가 곱해지고, 편향 b가 더해져서 결과인 ht를 출력
Step2) 출력층: Step1에서 출력된 ht에 가중치 Wy가 곱해져 yt를 출력
* 가중지 Wx, Wh, Wy는 같은 층에서 동일한 값 공유, 은닉층 2개 이상일 경우 서로 다른 층 간에는 다름
2. Keras로 RNN 구현하기
(1) 입력값: (hidden_units, input_length, input_dim)
from tensorflow.keras.layers import SimpleRNN
# 기본 RNN 층 추가
model.add(SimpleRNN(hidden_units))
# 추가 인자 설정하는 법
model.add(SimpleRNN(hidden_units, input_shape=(timesteps, input_dim)))
# 또 다른 표기
model.add(SimpleRNN(hidden_units, input_length=M, input_dim=N))
hidden_units = 은닉상태의 크기(= 다음시점으로 내보낼 값의 크기, output_dim). 중소형모델은 보통 128, 256, 512, 1024
batch_size = 한번에 학습하는 데이터의 개수
timesteps(input_length) = 시점의 수(문장의 길이)
input_dim = 입력의 크기(단어벡터의 차원)
(2) 출력값: (batch_size, input_length, output_dim)
return_sequences=True/False(디폴트)
: True - 각 시점의 은닉상태를 모두 모아 전체시퀀스(3D 텐서)를 리턴(batch_size, input_length, output_dim) -> 다대다
: False - 메모리셀의 최종시점의 은닉상태(2D 텐서)를 리턴(batch_size, output_dim) -> 다대일
* return_sequences=False(디폴트) 이고 batch_size를 정의하지 않았을 때
: Output Shape은 2D텐서 (batch_size, output_dim) 이므로 (None, 3)
model = Sequential()
model.add(SimpleRNN(3, input_shape=(2,10)))
# model.add(SimpleRNN(3, input_length=2, input_dim=10))와 동일함.
model.summary()
* return_sequences=False(디폴트) 이고 batch_size를 정의했을 때
: Output Shape은 2D텐서 (batch_size, output_dim) 이므로 (8, 3)
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8,2,10)))
model.summary()
* return_sequences=True 이고 batch_size를 정의했을 때
: Output Shape은 3D텐서 (batch_size, input_length, output_dim) 이므로 (8, 2, 3)
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8,2,10), return_sequences=True))
model.summary()
3. 파이썬으로 RNN 구현하기
Step1) 가상코드(pseudocode, 동장하지 않는 코드)로 긱본구조 살펴보기
hidden_state_t = 0 # t시점의 은닉상태
for input_t in input_length: # t시점의 입력값
output_t = tanh(input_t, hidden_state_t) # t시점의 입력값과 t-1시점의 은닉상태를 입력으로 t시점의 은닉상태 계산
hidden_state_t = output_t # 계산한 t시점의 은닉상태를 출력값으로 내보냄
Step2) 초기 은닉상태 출력
timesteps = 10
input_dim = 4
hidden_units = 8
# 입력에 해당되는 2D 텐서
inputs = np.random.random((timesteps, input_dim))
# 초기 은닉 상태는 0(벡터)로 초기화
hidden_state_t = np.zeros((hidden_units,))
print(hidden_state_t) # [0. 0. 0. 0. 0. 0. 0. 0.]
-> hidden_units(=output_dim)을 8로 설정했으므로 8차원의 0으로 구성된 벡터 출력
Step3) 가중치와 편향 출력
Wx = np.random.random((hidden_units, input_dim)) # 입력에 대한 가중치. (8, 4)크기의 2D텐서
Wh = np.random.random((hidden_units, hidden_units)) # 은닉상태에 대한 가중치. (8, 8)크기의 2D 텐서
b = np.random.random((hidden_units,)) # 편향. (8,)크기의 1D텐서
print('가중치 Wx의 크기(shape) :',np.shape(Wx)) # (8,4)
print('가중치 Wh의 크기(shape) :',np.shape(Wh)) # (8,8)
print('편향의 크기(shape) :',np.shape(b)) # (8,)
Step4) 모든 시점의 은닉상태 출력
total_hidden_states = []
for input_t in inputs:
# Wx * Xt + Wh * Ht-1 + b(bias)
output_t = np.tanh(np.dot(Wx,input_t) + np.dot(Wh,hidden_state_t) + b)
# 각 시점 t별 출력의 크기는 (timestep t, output_dim)
# 각 시점의 은닉 상태의 값을 계속해서 누적
total_hidden_states.append(list(output_t))
hidden_state_t = output_t
# 출력 시 값을 깔끔하게 해주는 용도.
total_hidden_states = np.stack(total_hidden_states, axis = 0)
# (timesteps, output_dim)
print(total_hidden_states)
4. 깊은 순환신경망(Deep RNN)
은닉층이 2개 이상인 RNN
은닉층을 2개 추가하는 코드
model = Sequential()
model.add(SimpleRNN(hidden_units, input_length=10, input_dim=5, return_sequences=True))
model.add(SimpleRNN(hidden_units, return_sequences=True))
첫번째 은닉층에서 return_sequences=True를 설정해 다음 은닉층으로 모든 시점의 은닉상태값을 보내줌
5. 양방향 순환신경먕(Bidirectional RNN)
-t시점에서 t-1시점(Forward States)뿐 아닌 t+1시점(Backward States)의 은닉상태값도 사용하는 것
-기본적으로 두 개의 메모리셀을 사용
from tensorflow.keras.layers import Bidirectional
timesteps = 10
input_dim = 5
model = Sequential()
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True),
input_shape=(timesteps, input_dim)))
-양방향 RNN이면서 은닉층이 여러개인 깊은 양방향 순환신경망(Deep Bidirectional RNN) 가능
# 은닉층이 4개인 양방향 RNN 구현코드
model = Sequential()
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True),
input_shape=(timesteps, input_dim)))
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True)))
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True)))
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True)))
# 08-02 장단기 메모리(LSTM)
1. 바닐라 RNN의 한계
: 장기 의존성 문제, 시점이 길어질수록 앞의 정보가 뒤로 충분히 전달되지 못해, 비교적 짧은 시퀀스에 대해서만 효과적
2. 바닐라 RNN의 내부구조
3. LSTM(Long Short-Term Memory)의 내부구조
은닉층의 메모리셀에 입력게이트, 망각게이트, 출력게이트를 추가해 불필요한 기억을 지우고 기억해야할 것만 남김
즉, 은닉상태 계산식이 복잡해졌고 셀 상태(cell state)라는 값이 추가됨(t시점의 셀 상태 = Ct)
(1) 입력게이트(현재시점 정보량 결정)
i: 시그모이드 함수를 지나 0과 1사이 값 가짐
g: 하이퍼볼릭탄젠트 함수를 지나 -1과 1 사이 값 가짐
기억할 정보의 양 = 입력게이트에서 구한 i와 g에 원소별 곱을 진행한 것
*원소별 곱(extrywise product): 같은 위치의 성분끼리 곱하는 것
(2) 삭제게이트(이전시점 정보량 결정)
f: 시그모이드 함수를 지나며 삭제 과정을 거쳐 0과 1 사이 값 가짐, 많이 삭제될수록 0, 온전할수록 1에 가까움
f 값을 가지고 셀 상태를 구함
(3) 셀 상태(총합 정보량 결정)
t시점의 셀상태=입력게이트에서 선택된 t시점의 기억 + 삭제게이트의 결과값인 t-1시점의 기억(f)-> t+1시점의 셀로 넘겨짐
f=0(삭제게이트 닫힘)이면 입력게이트 결과값만이 셀상태를 결정
i=0(입력게이트 닫힘)이면 삭제게이트 결과값만이 셀상태를 결정
(4) 출력게이트, 은닉상태
(3)에서 구한 셀상태(ct)는 tanh를 지나 -1과 1 사이 값이 됨(=tanh(ct))
t시점의 x와 t-1시점의 은닉상태는 시그모이드함수를 지나 0에서 1 사이 값이 됨(=출력게이트값, o)
o와 tanh(ct)에 원소별 곱을 진행한 값이 최종 출력게이트값 (ht)
# 08-03 게이트 순환 유닛(Gated Recurrent Unit, GRU)
-LSTM과는 달리 업데이트게이트, 리셋게이트의 2가지 게이트만이 존재
-성능은 LSTM과 유사하면서 보다 간단한 구조로 학습속도 향상
-데이터가 적을 때는 GRU, 많을때는 LSTM이 나음
-Keras로 GRU 구현 코드
model.add(GRU(hidden_size, input_shape=(timesteps, input_dim)))
# 08-04 Keras의 SimpleRNN, LSTM
1. 임의의 입력 생성
배치크기는 1, 4번의 시점(timesteps), 각 시점마다 5차원의 단어벡터가 입력으로 사용되는 예시 생성
import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import SimpleRNN, LSTM, Bidirectional
train_X = [[[0.1, 4.2, 1.5, 1.1, 2.8], [1.0, 3.1, 2.5, 0.7, 1.1], [0.3, 2.1, 1.5, 2.1, 0.1], [2.2, 1.4, 0.5, 0.9, 1.1]]]
train_X = np.array(train_X, dtype=np.float32)
print(train_X.shape) # (1, 4, 5)
2. SimpleRNN
return_sequences=True일 경우 모든시점의 은닉상태 값 출력(디폴트 False)
return_state=True일 경우 return_sequences의 여부와 상관없이 마지막시점의 은닉상태 값 출력(디폴트 False)
rnn = SimpleRNN(3, return_sequences=True, return_state=True)
hidden_states, last_state = rnn(train_X)
print(hidden_states.shape) # (1, 4, 3)
print(last_state.shape) # (1, 3)
-> 둘 다 True면 모든시점의 은닉상태, 마지막시점의 은닉상태라는 두 개의 출력 리턴
rnn = SimpleRNN(3, return_sequences=False, return_state=True)
hidden_state, last_state = rnn(train_X)
print(hidden_state.shape) # (1, 3)
print(last_state.shape) # (1, 3)
-> return_sequences=False이고 return_state=True면 마지막시점의 은닉상태 두 번 출력
3. LSTM
SimpleRNN과의 차이: return_state=True일 경우 마지막시점의 은닉상태뿐 아닌 셀상태까지 반환
lstm = LSTM(3, return_sequences=False, return_state=True)
hidden_state, last_state, last_cell_state = lstm(train_X)
print(hidden_state.shape) # (1, 3)
print(last_state.shape) # (1, 3)
print(last_cell_state.shape) # (1, 3)
4. Bidirectional LSTM
(1) return_sequences=False, return_state=True인 경우
bilstm = Bidirectional(LSTM(3, return_sequences=False, return_state=True, \
kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_states, forward_h, forward_c, backward_h, backward_c = bilstm(train_X) # 5개의 값 반환
print(hidden_states.shape) # (1, 6)
print(forward_h.shape) # (1, 3)
print(backward_h.shape) # (1, 3)
-> return_sequences=False이므로 정방향 LSTM의 마지막시점의 은닉상태와 역방향 LSTM의 첫번째시점의 은닉상태가 연결된채 반환 (=hidden_states)
-> return_state=True이므로 정방향 LSTM의 은닉상태(=forward_h)와 셀상태(forward_c), 역방향 LSTM의 은닉상태(backward_h)와 셀상태(bacward_c)의 4가지를 반환
(2) return_sequences=True, return_state=True인 경우
bilstm = Bidirectional(LSTM(3, return_sequences=True, return_state=True, \
kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_states, forward_h, forward_c, backward_h, backward_c = bilstm(train_X)
-> return_sequences=True이므로 hidden states의 출력값에는 모든 시점의 은닉상태가 출력됨.
즉, 역방향 LSTM의 첫번째시점의 은닉상태는 정방향 LSTM의 첫번째시점의 은닉상태와 연결됨
# 08-05 RNN 언어모델(RNNLM)
1. RNNLM이란?
-RNN으로 만든 언어모델로, NNLM과 달리 입력의 길이를 고정하지 않아도 됨
-교사 강요(teacher forcing): t시점의 예측값이 아닌 t시점의 레이블(실제정답)을 t+1시점의 입력으로 사용하도록 강요하여 훈련시킴으로써, 잘못된 예측이 뒤의 예측까지 영향을 주지 못하도록 하는 효율적인 훈련방법. '훈련방법'이므로, '테스트 과정'에서는 원래대로 t시점의 출력(예측값)이 t+1시점의 입력으로 사용됨
ex) 'what will the fat cat sit on'
-> 훈련: what will the fat cat sit 을 입력으로 넣으면 will the fat cat sit on 을 예측하도록 강요
-> 테스트: what 을 입력으로 will 을 예측, 이 will 을 다시 입력으로 the 를 예측, ...
즉, cat 은 what, will, the, fat 이라는 앞서 나온 시퀀스로 인해 결정된 단어
2. RNNLM의 구조
아래 그림과 같이 4개의 층으로 이루어져 있다.
(1) 입력층
현재시점(timestep)=4 라고 할 때, 입력 x 는 4번째 입력단어인 fat 의 원핫벡터
(2) 임베딩층
입력받은 단어에 V(단어집합크기) X M(임베딩벡터크기) 임베딩행렬 E 를 곱해 임베딩벡터를 얻는 투사층
* 투사층(projection layer): NNLM에서 룩업테이블을 수행하는 층으로, 그 결과로 얻는 벡터가 임베딩벡터
(3) 은닉층
현재시점의 입력인 임베딩벡터와 이전시점의 은닉상태인 ht-1 를 연산해 현재시점의 은닉상태 ht 계산
(4) 출력층
V차원의 벡터가 소프트맥스 함수를 지나 0과 1 사이의 실수값을 가지며 총합이 1인 벡터 yt 로 변환됨
-손실함수로 크로스엔트로피를 사용해 역전파 수행(업데이트되는 가중치행렬: E, Wx, Wh, Wy 의 4개)
# 08-06 RNN으로 텍스트생성(Text Generation)
1. SimpleRNN 으로 텍스트생성
(1) 데이터 전처리
3개의 문장 ' 경마장에 있는 말이 뛰고 있다' '그의 말이 법이다' '가는 말이 고와야 오는 말이 곱다' 이 있을 때, 모델이 학습하게 될 데이터를 다음과 같이 재구성해보자.
# 정수 인코딩
import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
text = """경마장에 있는 말이 뛰고 있다\n
그의 말이 법이다\n
가는 말이 고와야 오는 말이 곱다\n"""
tokenizer = Tokenizer()
tokenizer.fit_on_texts([text])
vocab_size = len(tokenizer.word_index) + 1 # 패딩을 고려해 1 더해줌
print(vocab_size) # 단어집합크기 V
print(tokenizer.word_index) # 각 단어에 부여된 정수인덱스
{'말이': 1, '경마장에': 2, '있는': 3, '뛰고': 4, '있다': 5, '그의': 6, '법이다': 7, '가는': 8, '고와야': 9, '오는': 10, '곱다': 11}
# 훈련 데이터 생성
# 문장 토큰화
sequences = list()
for line in text.split('\n'):
encoded = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(encoded)):
sequence = encoded[:i+1]
sequences.append(sequence)
print(len(sequences)) # 훈련 데이터 개수, 11
# 데이터 길이 일치시키기
# 가장 긴 샘플의 길이
max_len = max(len(l) for l in sequences) # 6
# 전체 샘플 길이 6으로 패딩
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
# 각 데이터의 마지막 단어를 레이블로 지정
sequences = np.array(sequences)
X = sequences[:,:-1] # 마지막 이전까지의 값
y = sequences[:,-1] # 마지막 값(레이블)
# 레이블에 원핫인코딩 수행
y = to_categorical(y, num_classes=vocab_size)
(2) 모델 설계
# 모델 생성 및 학습
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, SimpleRNN
embedding_dim = 10 # 임베딩벡터크기 M
hidden_units = 32 # 은닉상태크기
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim)) # 임베딩층 추가
model.add(SimpleRNN(hidden_units)) # 은닉층 추가
model.add(Dense(vocab_size, activation='softmax')) # 출력층 추가
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 다중클래스분류
model.fit(X, y, epochs=200, verbose=2) # 에포크 200 으로 학습
# 모델이 정확하게 예측하는지 확인하는 함수 정의
def sentence_generation(model, tokenizer, current_word, n):
init_word = current_word # 현재단어
sentence = ''
# n번 반복
for _ in range(n):
# 현재단어에 대해 정수인코딩 및 패딩
encoded = tokenizer.texts_to_sequences([current_word])[0] # 정수인코딩
encoded = pad_sequences([encoded], maxlen=5, padding='pre') # 패딩
# 예측단어를 result에 저장
result = model.predict(encoded, verbose=0) # 예측
# 만약 여기서 주어진 입력 다음에 나오는게 무엇인지 배운적 없다면 모델은 임의예측을 수행
result = np.argmax(result, axis=1) # 저장
# 갖고있는 단어(훈련시켜둔 단어들) 중에 예측단어(result)와 인덱스가 동일한 단어(word) 찾기
for word, index in tokenizer.word_index.items():
if index == result: # 만약 찾으면
break # 그 단어(word)에서 멈춤(word가 result와 동일해짐)
# 다음 입력값을 업데이트
current_word = current_word + ' ' + word
# 예측단어를 문장에 추가 (최종문장으로 출력하기 위해)
sentence = sentence + ' ' + word
sentence = init_word + sentence
return sentence
# 사용해보기
print(sentence_generation(model, tokenizer, '경마장에', 4)) # 경마장에 있는 말이 뛰고 있다
'경마장에' 뒤에 4개의 단어가 있으므로, 4번보다 큰 숫자를 주면 모델은 임의예측을 수행
2. LSTM 으로 텍스트생성
LSTM을 사용하면 더 많은 데이터로 모델을 학습할 수 있다.
(1) 뉴욕타임즈 기사제목( https://www.kaggle.com/aashita/nyt-comments )
# 데이터 확인
import pandas as pd
import numpy as np
from string import punctuation
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
df = pd.read_csv('ArticlesApril2018.csv')
print(len(df.columns)) # 15
print(df.columns) # Index(['articleID', 'articleWordCount', 'byline', 'documentType',
'headline', 'keywords', 'multimedia', 'newDesk', 'printPage',
'pubDate', 'sectionName', 'snippet', 'source', 'typeOfMaterial',
'webURL'], dtype='object')
print(df['headline'].isnull().values.any()) # False (결측값 없음)
# 데이터 전처리
# 신문제목만 리스트로 저장
headline = []
headline.extend(list(df.headline.values))
# 'Unknown' 이라는 제목 삭제
headline = [word for word in headline if word != "Unknown"]
# 구두점제거, 소문자화
def repreprocessing(raw_sentence):
preproceseed_sentence = raw_sentence.encode("utf8").decode("ascii",'ignore')
return ''.join(word for word in preproceseed_sentence if word not in punctuation).lower()
preprocessed_headline = [repreprocessing(x) for x in headline]
(2) 훈련 데이터 생성
# 단어집합 생성
tokenizer = Tokenizer()
tokenizer.fit_on_texts(preprocessed_headline)
vocab_size = len(tokenizer.word_index) + 1 # 단어집합크기 V, 패딩 고려해 1 추가
# 문장 토큰화
sequences = list()
for sentence in preprocessed_headline:
encoded = tokenizer.texts_to_sequences([sentence])[0] # 정수인코딩
for i in range(1, len(encoded)):
sequence = encoded[:i+1]
sequences.append(sequence)
# 패딩
# max_len = max(len(l) for l in sequences) 으로 최대문장길이 구함: 24
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre') # 'pre': 앞을 0으로 패딩
# 맨 오른쪽 단어를 레이블로 분리
sequences = np.array(sequences)
X = sequences[:,:-1]
y = sequences[:,-1]
# 레이블에 원핫인코딩 수행
y = to_categorical(y, num_classes=vocab_size)
(3) 모델 설계
# 모델 생성 및 학습
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, LSTM
embedding_dim = 10 # 임베딩벡터크기 M
hidden_units = 128 # 은닉상태크기
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim)) # 임베딩층 추가
model.add(LSTM(hidden_units)) # LSTM 은닉층 추가
model.add(Dense(vocab_size, activation='softmax')) # 출력층 추가
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 다중클래스분류
model.fit(X, y, epochs=200, verbose=2) # 학습
# 모델 정확도 확인 함수 정의
def sentence_generation(model, tokenizer, current_word, n): # 모델, 토크나이저, 현재 단어, 반복할 횟수
init_word = current_word
sentence = ''
# n번 반복
for _ in range(n):
encoded = tokenizer.texts_to_sequences([current_word])[0]
encoded = pad_sequences([encoded], maxlen=max_len-1, padding='pre')
# 예측단어를 result에 저장
result = model.predict(encoded, verbose=0)
result = np.argmax(result, axis=1)
# 갖고있느 단어 중 예측단어와 같은 것에서 멈춤(=word)
for word, index in tokenizer.word_index.items():
if index == result:
break
# 다음 입력값 업데이트
current_word = current_word + ' ' + word
# 최종출력할 문장 업데이트
sentence = sentence + ' ' + word
sentence = init_word + sentence
return sentence
# 사용해보기
print(sentence_generation(model, tokenizer, 'i', 10))
# i disapprove of school vouchers can i still apply for them
print(sentence_generation(model, tokenizer, 'how', 10))
# how to make facebook more accountable will so your neighbor chasing
# 08-07 문자단위 RNN (Char RNN)
지금까지 입력과 출력의 단위가 단어레벨이었다면, 이번에는 문자레벨로 RNN을 구현해보자.
단어단위가 아니기 때문에 임베딩층이 필요 없다.
1. Char RNNLM으로 다대일 LSTM 생성
(1) 데이터 전처리
데이터: 소설 이상한 나라의 앨리스 http://www.gutenberg.org/files/11/11-0.txt
import numpy as np
import urllib.request
from tensorflow.keras.utils import to_categorical
# 데이터 로드
urllib.request.urlretrieve("http://www.gutenberg.org/files/11/11-0.txt", filename="11-0.txt")
# 데이터 전처리
f = open('11-0.txt', 'rb')
sentences = []
for sentence in f:
sentence = sentence.strip()
sentence = sentence.lower()
sentence = sentence.decode('ascii', 'ignore') # \xe2\x80\x99 등과 같은 바이트 열 제거
if len(sentence) > 0:
sentences.append(sentence)
f.close()
# 하나의 문자열로 붙여버리기(15만 9천자짜리 소설)
total_data = ' '.join(sentences)
# 문자집합 생성
char_vocab = sorted(list(set(total_data)))
vocab_size = len(char_vocab) # 56
# 추후 디코딩을 위한 작업
# 문자에 정수 부여
char_to_index = dict((char, index) for index, char in enumerate(char_vocab))
# 정수로 문자 찾는 딕셔너리 생성
index_to_char = {}
for key, value in char_to_index.items():
index_to_char[value] = key
(2) 훈련 데이터 생성
seq_length = 60 # 한 샘플당 길이 설정
n_samples = int(np.floor((len(total_data) - 1) / seq_length)) # 샘플 개수
train_X = []
train_y = []
# 루프를 돌며 문장 추출(0:60 -> 60:120 -> 120:180 ...)
for i in range(n_samples):
X_sample = total_data[i * seq_length: (i + 1) * seq_length] # 문장 추출
X_encoded = [char_to_index[c] for c in X_sample] # 정수인코딩
train_X.append(X_encoded)
# 레이블 y = 다음에 올 문자(오른쪽으로 1칸)
y_sample = total_data[i * seq_length + 1: (i + 1) * seq_length + 1]
y_encoded = [char_to_index[c] for c in y_sample] # 정수인코딩
train_y.append(y_encoded)
# 샘플출력 및 디코딩해보기
print(train_X[0]) # 첫번째 샘플
print(train_y[0]) # 첫번째 레이블
print([index_to_char[i] for i in train_X[0]]) # 첫번째 샘플 디코딩
print([index_to_char[i] for i in train_y[0]]) # 첫번째 레이블 디코딩
# 원핫인코딩
train_X = to_categorical(train_X) # 임베딩층을 사용하지 않으므로 X에도 원핫인코딩
train_y = to_categorical(train_y)
print(train_X.shape) # (2658, 60, 56)
print(train_y.shape) # (2658, 60, 56)
(3) 모델 설계
# 모델 생성 및 학습
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, TimeDistributed
hidden_units = 256 # 은닉상태 크기
model = Sequential()
model.add(LSTM(hidden_units, input_shape=(None, train_X.shape[2]), return_sequences=True)) # 은닉층1
model.add(LSTM(hidden_units, return_sequences=True)) # 은닉층2
model.add(TimeDistributed(Dense(vocab_size, activation='softmax'))) # 전결합층
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 다중클래스분류
model.fit(train_X, train_y, epochs=80, verbose=2) # 모델 학습
# 문장 생성 함수 정의
def sentence_generation(model, length):
# 문자에 대한 랜덤한 정수 생성
ix = [np.random.randint(vocab_size)]
# 랜덤한 정수로부터 맵핑되는 문자 생성
y_char = [index_to_char[ix[-1]]]
print(ix[-1],'번 문자',y_char[-1],'로 예측을 시작!')
# (1, length, 55) 크기의 X 생성. 즉, LSTM의 입력 시퀀스 생성
X = np.zeros((1, length, vocab_size))
for i in range(length):
# X[0][i][예측한 문자의 인덱스] = 1, 즉, 예측 문자를 다음 입력 시퀀스에 추가
X[0][i][ix[-1]] = 1
print(index_to_char[ix[-1]], end="")
ix = np.argmax(model.predict(X[:, :i+1, :])[0], 1)
y_char.append(index_to_char[ix[-1]])
return ('').join(y_char)
result = sentence_generation(model, 100)
print(result)
# ury-men would have done just as well. the twelve jurors were to say in that dide. he went on in a di'
2. Char RNNLM 으로 다대다 LSTM 생성
(1) 데이터 전처리
import numpy as np
from tensorflow.keras.utils import to_categorical
raw_text = 임의의 텍스트
tokens = raw_text.split() # 단락구분 없애고
raw_text = ' '.join(tokens) # 한 문장으로 합침
# 중복을 제거한 문자 집합 생성
char_vocab = sorted(list(set(raw_text))) # 문자 집합
vocab_size = len(char_vocab) # 문자 집합의 크기
# 디코딩을 위한 정수인코딩
char_to_index = dict((char, index) for index, char in enumerate(char_vocab))
(2) 훈련데이터 생성
# 샘플 길이 설정 및 샘플추출
length = 11
sequences = []
for i in range(length, len(raw_text)):
seq = raw_text[i-length:i] # 길이 11의 문자열을 지속적으로 만든다.
sequences.append(seq)
print(len(sequences)) # 426개의 샘플
# 정수인코딩
encoded_sequences = []
for sequence in sequences: # 전체 데이터에서 문장 샘플을 1개씩 꺼내
encoded_sequence = [char_to_index[char] for char in sequence] # 각 문자에 대해 정수인코딩
encoded_sequences.append(encoded_sequence)
# 레이블 추출
encoded_sequences = np.array(encoded_sequences)
X_data = encoded_sequences[:,:-1] # 맨 마지막 위치의 문자를
y_data = encoded_sequences[:,-1] # 레이블로 저장
# 원핫잇코딩
X_data_one_hot = [to_categorical(encoded, num_classes=vocab_size) for encoded in X_data]
X_data_one_hot = np.array(X_data_one_hot)
y_data_one_hot = to_categorical(y_data, num_classes=vocab_size)
print(X_data_one_hot.shape) # (426, 10, 33)
(3) 모델 설계
# 모델 생성 및 학습
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM
from tensorflow.keras.preprocessing.sequence import pad_sequences
hidden_units = 64 # 은닉상태 크기
model = Sequential()
model.add(LSTM(hidden_units, input_shape=(X_data_one_hot.shape[1], X_data_one_hot.shape[2]))) # 은닉층
model.add(Dense(vocab_size, activation='softmax')) # 출력층 추가(전결합층)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 다중클래스분류
model.fit(X_data_one_hot, y_data_one_hot, epochs=100, verbose=2) # 모델 학습
# 문장 생성 함수 정의
def sentence_generation(model, char_to_index, seq_length, seed_text, n):
# 초기 시퀀스
init_text = seed_text
sentence = ''
for _ in range(n):
encoded = [char_to_index[char] for char in seed_text] # 정수인코딩
encoded = pad_sequences([encoded], maxlen=seq_length, padding='pre') # 패딩
encoded = to_categorical(encoded, num_classes=len(char_to_index))
# 입력한 X(현재 시퀀스)에 대해서 y를 예측하고 y(예측한 문자)를 result에 저장.
result = model.predict(encoded, verbose=0)
result = np.argmax(result, axis=1)
for char, index in char_to_index.items():
if index == result:
break
# 다음 입력값 업데이트
seed_text = seed_text + char
# 최종 출력 문장 업데이트
sentence = sentence + char
# n번의 문자예측이 끝나면 최종 완성된 문장을 리턴
sentence = init_text + sentence
return sentence
print(sentence_generation(model, char_to_index, 10, 'I get on w', 80))
# I get on with life as a programmer, I like to hang out with programming and deep learning.
'딥러닝 > 딥러닝을 이용한 자연어처리 입문' 카테고리의 다른 글
[딥러닝 NLP] 10. Text Classification 실습(RNN, LSTM, GRU, Naive Bayes, BiLSTM) (1) | 2024.01.31 |
---|---|
[딥러닝 NLP] 09. 워드 임베딩(Word2Vec, SGNS, GloVe, FastText, ELMo, Doc2Vec) (1) | 2024.01.30 |
[딥러닝 NLP] 07. 딥러닝(2) Keras, NNLM (0) | 2024.01.16 |
[딥러닝 NLP] 07. 딥러닝(1) 활성화함수,손실함수,옵티마이저,역전파, 과적합, 기울기소실, 기울기폭주 (2) | 2024.01.11 |
[딥러닝 NLP] 06. 머신러닝(Linear, Logistic, Softmax Regression) (2) | 2023.12.06 |