-
[HandsOn] 16. RNN과 어텐션을 사용한 자연어 처리 - 내용 정리1[도서완독]Hands On Machine Learning 2022. 8. 16. 20:45
요즘처럼 열심히 일한 적이 없다....😂
그래도 공부해야지....
RNN (순환 신경망) 은 자연어 문제를 위해 많이 사용되는 방법.
16.1 Char-RNN을 사용해 셰익스피어 문체 생성하기
Char-RNN을 사용해 한 번에 한 글자씩 새로운 텍스트를 생성 가능.
16.1.1 훈련 데이터셋 만들기
shakespeare_url="https://homl.info/shakespeare" filepath=keras.utils.get_file("shakespeare.txt",shakespeare_url) with open(filepath) as f: shakespeare_text=f.read()
경로에서 셰익스피어 작품을 모두 다운로드.
그 다음 모든 글자를 정수로 인코딩 해야 함. 케라스의 Tokenizer 클래스를 사용하자.
객체를 텍스트로 훈련해야 함. 텍스트에서 사용되는 모든 글자를 각기 다른 글자 id에 매핑.
id는 1부터 고유한 글자 개수까지 만들어짐.
tokenizer=keras.preprocessing.text.Tokenizer(char_level=True) #단어 수준 레벨이 아닌 글자 수준 인코딩을 만듬 \ tokenizer.fit_on_texts(shakespeare_text) tokenizer.texts_to_sequences(['First'])
[[20, 6, 9, 8, 3]]
max_id=len(tokenizer.word_index) #고유 글자 개수 dataset_size=tokenizer.document_count #전체 글자 개수
[encoded]=np.array(tokenizer.texts_to_sequences([shakespeare_text]))-1
전체 텍스트를 인코딩하여 각 글자를 id로 나타냄(0~38까지 id를 얻기 위해 1을 뺌)
데이터셋을 훈련 세트, 검증 세트, 테스트 세트로 나누어야 함. 텍스트에 있는 글자를 섞으면 안 됨.
순차 데이터셋을 어떻게 나누어야 할까?
16.1.2 순차 데이터셋을 나누는 방법
훈련, 검증, 테스트 세트가 중복되지 않도록 만드는 것이 매우 중요
시계열을 다룰 때는 보통 시간에 따라 나눔.
텍스트의 처음 90%를 훈련 세트로 사용.(나머지는 검증, 테스트 세트)
이 세트에서 한 번에 한 글자씩 반환하는 tf.data.Dataset 객체를 만든다.
train_size=dataset_size*90//100 dataset=tf.data.Dataset.from_tensor_slices(encoded[:train_size]) for item in dataset.take(3): print(item)
tf.Tensor(19, shape=(), dtype=int32)
tf.Tensor(5, shape=(), dtype=int32)
tf.Tensor(8, shape=(), dtype=int32)
tf.data.Dataset.from_tensor_slices 함수는 tf.data.Dataset 를 생성하는 함수로 입력된 텐서로부터 slices를 생성합니다. 예를 들어 MNIST의 학습데이터 (60000, 28, 28)가 입력되면, 60000개의 slices로 만들고 각각의 slice는 28×28의 이미지 크기를 갖게 됩니다.
출처: https://hiseon.me/data-analytics/tensorflow/tensorflow-dataset/
16.1.3 순차 데이터를 윈도 여러 개로 자르기
훈련 세트는 백만 개 이상의 글자로 이루어진 시퀀스 하나. 여기에 신경망을 직접 훈련시킬 수 없음.
이 RNN은 매우 긴 샘플 하나로 훈련하는 셈...(RNN에서는 하나의 시퀀스가 모두 처리된 후 가중치가 업데이트됨)
데이터셋의 window() 메서드를 사용해 이 긴 시퀀스를 작은 많은 텍스트 윈도로 변환하자.
RNN은 이 부분 문자열 길이만큼만 역전파를 위해 펼쳐지는데, 이를 TBPTT( truncated backpropagetion through time) 이라고 함.
n_steps=100 window_length=n_steps+1 #target=1글자 앞의 input dataset=dataset.window(window_length,shift=1,drop_remainder=True)
shift의 기본값은 윈도우 크기인데, shift=1로 지정하면 가장 큰 훈련 세트를 만들 수 있음.
첫번째 윈도는 0~100 번째 글자를 포함하고, 두 번째 윈도는 1~101번째 글자를 포함하는 식.
패딩 없이 배치 데이터를 만들기 위해, 모든 윈도가 동일하게 101개의 글자를 포함하도록 drop_reminder=True로 지정
window() 메서드는 각각 하나의 데이터셋으로 표현되는 윈도를 포함하는 데이터셋을 만듬. 중첩 데이터셋!
이런 구조는 데이터셋 메서드를 호출하여 뭐... 섞거나 배치를 만드는 듯 각 윈도를 변환할 때 유용하지만, 모델은 데이터셋이 아니라 텐서를 기대하기 때문에 훈련에는 중첩 데이터셋을 바로 사용할 수 없음.
-> 중첩 데이터셋을 플랫 데이터셋으로 변환해야 함.
dataset=dataset.flat_map(lambda window: window.batch(window_length)) for item in dataset.take(3): print(item)
tf.Tensor( [19 5 8 7 2 0 18 5 2 5 35 1 9 23 10 21 1 19 3 8 1 0 16 1 0 22 8 3 18 1 1 12 0 4 9 15 0 19 13 8 2 6 1 8 17 0 6 1 4 8 0 14 1 0 7 22 1 4 24 26 10 10 4 11 11 23 10 7 22 1 4 24 17 0 7 22 1 4 24 26 10 10 19 5 8 7 2 0 18 5 2 5 35 1 9 23 10 15 3 13 0], shape=(101,), dtype=int32)
tf.Tensor( [ 5 8 7 2 0 18 5 2 5 35 1 9 23 10 21 1 19 3 8 1 0 16 1 0 22 8 3 18 1 1 12 0 4 9 15 0 19 13 8 2 6 1 8 17 0 6 1 4 8 0 14 1 0 7 22 1 4 24 26 10 10 4 11 11 23 10 7 22 1 4 24 17 0 7 22 1 4 24 26 10 10 19 5 8 7 2 0 18 5 2 5 35 1 9 23 10 15 3 13 0 4], shape=(101,), dtype=int32)
tf.Tensor( [ 8 7 2 0 18 5 2 5 35 1 9 23 10 21 1 19 3 8 1 0 16 1 0 22 8 3 18 1 1 12 0 4 9 15 0 19 13 8 2 6 1 8 17 0 6 1 4 8 0 14 1 0 7 22 1 4 24 26 10 10 4 11 11 23 10 7 22 1 4 24 17 0 7 22 1 4 24 26 10 10 19 5 8 7 2 0 18 5 2 5 35 1 9 23 10 15 3 13 0 4 8], shape=(101,), dtype=int32)
경사 하강법은 훈련 세트가 iid할때 가장 잘 작동하기 때문에 이 윈도를 섞어야 함. 그 다음 윈도를 배치로 만들고 입력(~100번째) 과 타깃(101)번째를 분리하겠음.
batch_size=32 dataset=dataset.shuffle(10000).batch(batch_size) for item in dataset.take(1): print(item) dataset=dataset.map(lambda windows: (windows[:,:-1],windows[:,1:])) for item in dataset.take(1): print(item)
tf.Tensor( [[ 5 1 12 ... 0 15 3] [ 7 23 10 ... 4 9 7] [ 7 2 3 ... 7 3 14] ... [ 5 8 0 ... 4 24 1] [ 0 5 27 ... 1 9 1] [12 0 15 ... 12 26 0]], shape=(32, 101), dtype=int32)
(<tf.Tensor: shape=(32, 100), dtype=int32, numpy= array([[ 5, 8, 7, ..., 16, 27, 2], [ 1, 2, 5, ..., 1, 15, 0], [ 3, 9, 12, ..., 4, 9, 7], ..., [ 5, 9, 7, ..., 14, 5, 9], [ 8, 25, 1, ..., 2, 0, 2], [ 4, 22, 7, ..., 7, 0, 20]])>,
<tf.Tensor: shape=(32, 100), dtype=int32, numpy= array([[ 8, 7, 2, ..., 27, 2, 26], [ 2, 5, 3, ..., 15, 0, 4], [ 9, 12, 0, ..., 9, 7, 0], ..., [ 9, 7, 0, ..., 5, 9, 12], [25, 1, 7, ..., 0, 2, 6], [22, 7, 10, ..., 0, 20, 8]])>)
하 드디어 이해했다... 머리 터짐...ㅎㅎ....빨리 넘어가!!!
범주형 입력 특성은 원-핫 벡터나 임베딩으로 인코딩되어야 하는데, 고유한 글자 수가 적기 때문에(39개) 원-핫 벡터를 사용해 글자를 인코딩.
dataset=dataset.map(lambda X_batch,Y_batch:(tf.one_hot(X_batch,depth=max_id),Y_batch)) for item in dataset.take(1): print(item) dataset=dataset.prefetch(1)
(<tf.Tensor: shape=(32, 100, 39), dtype=float32, numpy=
array([[[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[1., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 1., 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.],
[0., 0., 0., ..., 0., 0., 0.]],
[[0., 0., 0., ..., 0., 0., 0.],
[1., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 1., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[1., 0., 0., ..., 0., 0., 0.]]], dtype=float32)>,<tf.Tensor: shape=(32, 100), dtype=int32, numpy=
array([[13, 9, 5, ..., 19, 1, 11],
[ 0, 18, 5, ..., 8, 0, 5],
[ 1, 1, 11, ..., 21, 3, 12],
...,
[ 4, 5, 9, ..., 10, 18, 3],
[ 9, 17, 10, ..., 8, 4, 2],
[ 0, 5, 19, ..., 12, 0, 13]])>)데이터셋 만들기까지... 힘들었다.. 하다보니 뭘 하려던 것인지 기억도 안나지만... 해보도록하자...
16.1.4 Char-RNN 모델 만들고 훈련하기
model = keras.models.Sequential([ keras.layers.GRU(128, return_sequences=True, input_shape=[None, max_id], #dropout=0.2, recurrent_dropout=0.2), dropout=0.2), keras.layers.GRU(128, return_sequences=True, #dropout=0.2, recurrent_dropout=0.2), dropout=0.2), keras.layers.TimeDistributed(keras.layers.Dense(max_id, activation="softmax")) ]) model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") history = model.fit(dataset, epochs=10)
경고!! 이 코드는 하드웨어에 따라 실행하는데 24시간이 걸릴 수 있다. GPU를 사용하면 1~2시간 정도 걸릴 수 있음...
씁.... 나는 안함....
(GRU 클래스는 다음 매개변수에서 기본값을 사용할 때에만 GPU를 사용.
activation, recurrent_activation, recurrent_dropout, unroll, use_bias reset_after)
16.1.5 Char-RNN 모델 사용하기
이제 모델을 만듬! (그렇다구치자^^)
여기에 새로운 텍스트를 주입하려면 앞에서처럼 전처리를 해야 함.
def preprocess(texts): X = np.array(tokenizer.texts_to_sequences(texts)) - 1 return tf.one_hot(X, max_id)
경고: predict_classes() 메서드는 deprecated 되었습니다. 대신 np.argmax(model(X_new), axis=-1)를 사용합니다.
X_new = preprocess(["How are yo"]) #Y_pred = model.predict_classes(X_new) Y_pred = np.argmax(model(X_new), axis=-1) tokenizer.sequences_to_texts(Y_pred + 1)[0][-1] # 1st sentence, last char
'u'
이 모델로 새로운 텍스트를 생성해보자.
(pass)
이 모델은 n_steps보다 긴 패턴을 학습할 수 없음. 이 윈도를 크게 할 수 있지만 훈련이 더 어려워짐. LSTM과 GRU 셀이라도 매우 긴 시퀀스는 다룰 수 없다.
아니면 상태가 있는 RNN(stateful RNN)을 사용한다고 한다.
'[도서완독]Hands On Machine Learning' 카테고리의 다른 글
[HandsOn] 16. RNN과 어텐션을 사용한 자연어 처리 - 내용 정리3 (0) 2022.08.19 [HandsOn] 16. RNN과 어텐션을 사용한 자연어 처리 - 내용 정리2 (0) 2022.08.18 [HandsOn]15. RNN과 CNN을 사용해 시퀀스 처리하기 - 내용 정리2 (0) 2022.08.13 [HandsOn]15. RNN과 CNN을 사용해 시퀀스 처리하기 - 내용 정리1 (0) 2022.08.08 [HandsOn]14. 합성곱 신경망을 사용한 컴퓨터 비전 - 내용 정리1 (0) 2022.08.01