ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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/

     

    텐서플로우 tf.data.Dataset 사용 방법 - HiSEON

    텐서플로우 tf.data.Dataset 사용 방법 텐서플로우 dataset 만들기 Estimator 모델에서 사용되는 데이터 입력 파이프라인인 tf.data.Dataset 예제 형태에 대해서 설명드리도록 하겠습니다. tf.data.Dataset은 사용

    hiseon.me

     

    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)을 사용한다고 한다.

Designed by Tistory.