Keras LSTM 이해

저는 LSTM에 대한 저의 이해를 조정하려고 노력하고 있으며, 여기 크리스토퍼 올라가 작성한 이 게시물에서 지적한 내용을 Keras로 구현했습니다. 저는 제이슨 브라운리가 작성한 블로그의 Keras 튜토리얼을 따르고 있습니다. 제가 주로 혼란스러워하는 것은 다음과 같습니다,

  1. 데이터 계열을 샘플, 시간 단계, 기능으로 재구성하는 것과,
  2. 상태 저장 LSTM

아래에 붙여넣은 코드를 참조하여 위의 두 가지 질문에 집중해 보겠습니다:

# reshape into X=t and Y=t+1
look_back = 3
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

# reshape input to be [samples, time steps, features]
trainX = numpy.reshape(trainX, (trainX.shape[0], look_back, 1))
testX = numpy.reshape(testX, (testX.shape[0], look_back, 1))
########################
# The IMPORTANT BIT
##########################
# create and fit the LSTM network
batch_size = 1
model = Sequential()
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
for i in range(100):
    model.fit(trainX, trainY, nb_epoch=1, batch_size=batch_size, verbose=2, shuffle=False)
    model.reset_states()

참고: create_dataset은 길이 N의 시퀀스를 받아 각 요소가 look_back 길이의 시퀀스인 N-look_back 배열을 반환합니다.

시간 단계와 특징이란 무엇인가요?

보시다시피 TrainX는 3차원 배열이며 Time_steps와 Feature는 각각 마지막 두 차원입니다(이 특정 코드에서는 3과 1). 아래 이미지와 관련하여 분홍색 상자의 수가 3인 '다대일'의 경우를 고려하고 있다는 의미일까요? 아니면 문자 그대로 체인 길이가 3이라는 의미인가요(즉, 녹색 상자 3개만 고려됨)? ![여기에 이미지 설명 입력]]3

다변량 계열을 고려할 때 특징 인수가 관련성이 있나요? 예를 들어 두 개의 금융 주식을 동시에 모델링하는 경우?

상태 저장 LSTM

상태 저장 LSTM은 배치 실행 사이에 셀 메모리 값을 저장하는 것을 의미합니까? 그렇다면 batch_size는 하나이고 훈련 실행 사이에 메모리가 재설정되므로 상태 저장형이라는 말이 무슨 의미가 있을까요? 훈련 데이터가 셔플되지 않는다는 사실과 관련이 있다고 생각하지만 그 방법은 잘 모르겠습니다.

어떤 생각 있으신가요? 이미지 참조: http://karpathy.github.io/2015/05/21/rnn-effectiveness/

수정 1:

빨간색과 녹색 상자가 같다는 @van의 의견에 대해 약간 혼란스러웠습니다. 확인을 위해 다음 API 호출이 펼쳐진 다이어그램과 일치하나요? 특히 두 번째 다이어그램에 주목합니다(batch_size는 임의로 선택했습니다.): ![여기에 이미지 설명 입력]][4]

수정 2:

유다시티의 딥러닝 강좌를 수강하고도 time_step 인자에 대해 혼란스러우신 분들은 다음 토론을 참고하시기 바랍니다: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169.

업데이트:

제가 찾던 것은 model.add(TimeDistributed(Dense(vocab_len)))였습니다. 다음은 예제입니다: https://github.com/sachinruk/ShakespeareBot

업데이트2:

LSTM에 대한 이해의 대부분을 여기에 요약했습니다: https://www.youtube.com/watch?v=ywinX5wgdEU

질문에 대한 의견 (9)
해결책

우선, 훌륭한 튜토리얼(1,2)을 선택하여 시작합니다.

시간 단계의 의미: X.shape(데이터 모양 설명)에서 '시간 단계==3'은 분홍색 상자 3개가 있음을 의미합니다. Keras에서는 각 단계마다 입력이 필요하므로 일반적으로 녹색 상자의 수는 빨간색 상자의 수와 같아야 합니다. 구조를 해킹하지 않는 한 말입니다.

다대다 대 다대일: keras에서는 LSTM이나 GRU, SimpleRNN을 초기화할 때 return_sequences 파라미터가 있습니다. 반환값이 False(기본값)이면 그림과 같이 다수 대 1이 됩니다. 반환 형태는 마지막 상태를 나타내는 (batch_size, hidden_unit_length)입니다. return_sequencesTrue이면 **다대다**입니다. 반환 형태는(batch_size, time_step, hidden_unit_length)`입니다.

특징 인자가 관련성이 있는가: 특징 인수는 '빨간색 상자가 얼마나 큰지' 또는 각 단계의 입력 차원을 의미합니다. 예를 들어 8가지의 시장 정보로부터 예측하고 싶다면 feature==8로 데이터를 생성할 수 있습니다.

스테이트풀: 소스 코드]3를 조회할 수 있습니다. 상태를 초기화할 때 stateful==True이면 마지막 학습의 상태를 초기 상태로 사용하고, 그렇지 않으면 새로운 상태를 생성합니다. 아직 stateful을 켜지 않았습니다. 하지만 batch_sizestateful==True일 때만 1이 될 수 있다는 것에 동의하지 않습니다.

현재 수집된 데이터로 데이터를 생성하고 있습니다. 주식 정보가 스트림으로 들어온다고 가정하고, 하루를 기다렸다가 순차적으로 모두 수집하는 것이 아니라 네트워크에서 학습/예측하는 동안 온라인으로 입력 데이터를 생성하고 싶습니다. 만약 같은 네트워크를 공유하는 주식이 400개라면 batch_size==400을 설정하면 됩니다.

해설 (10)

정답을 보완하기 위해 이 답변은 케라스의 행동과 각 그림을 달성하는 방법을 보여줍니다.

일반 케라스 동작

표준 케라스 내부 처리는 다음 그림과 같이 항상 다대다입니다(예제로서 features=2, 압력 및 온도를 사용했습니다): 이 이미지에서는 다른 차원과 혼동을 피하기 위해 단계 수를 5로 늘렸습니다. 이 예제에서는

  • N개의 기름 탱크가 있습니다.
  • 5시간 동안 시간당(시간 단계) 측정을 수행했습니다.
  • 두 가지 특징을 측정했습니다:
    • 압력 P
    • 온도 T 그러면 입력 배열은 (N,5,2)의 형태가 되어야 합니다:
        [     Step1      Step2      Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2], [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2], [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....
Tank N:    [[Pn1,Tn1], [Pn2,Tn2], [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]

슬라이딩 창에 대한 입력

종종 LSTM 레이어는 전체 시퀀스를 처리해야 합니다. 창을 나누는 것이 최선의 아이디어가 아닐 수도 있습니다. 레이어에는 시퀀스가 앞으로 나아갈 때 어떻게 진화하고 있는지에 대한 내부 상태가 있습니다. 윈도우는 모든 시퀀스를 윈도우 크기로 제한하여 긴 시퀀스를 학습할 가능성을 제거합니다. 윈도우에서는 각 윈도우가 긴 원본 시퀀스의 일부이지만 Keras에서는 각각 독립적인 시퀀스로 간주됩니다:

        [     Step1    Step2    Step3    Step4    Step5
Window  A:  [[P1,T1], [P2,T2], [P3,T3], [P4,T4], [P5,T5]],
Window  B:  [[P2,T2], [P3,T3], [P4,T4], [P5,T5], [P6,T6]],
Window  C:  [[P3,T3], [P4,T4], [P5,T5], [P6,T6], [P7,T7]],
  ....
        ]

이 경우 처음에는 하나의 시퀀스만 있지만 여러 시퀀스로 나누어 창을 생성한다는 점에 유의하세요. '시퀀스란 무엇인가'라는 개념은 추상적입니다. 중요한 부분은

  • 많은 개별 시퀀스가 있는 배치를 가질 수 있다.
  • 시퀀스를 시퀀스로 만드는 것은 시퀀스가 단계적으로 진화한다는 것입니다(일반적으로 시간 단계).

    '단일 레이어'로 각 사례 달성하기;

    표준 다대다를 달성합니다:

    반환_시퀀스=True`를 사용하여 간단한 LSTM 레이어로 다대다를 달성할 수 있습니다:

outputs = LSTM(units, return_sequences=True)(inputs)

#output_shape -> (batch_size, steps, units)

다대다 달성하기:

정확히 동일한 레이어를 사용하면 keras는 정확히 동일한 내부 전처리를 수행하지만, return_sequences=False를 사용하면(또는 단순히 이 인수를 무시하면) keras는 마지막 단계 이전의 단계를 자동으로 버립니다:

outputs = LSTM(units)(inputs)

#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned

1 대 다수 달성하기

이제 이것은 keras LSTM 레이어만으로는 지원되지 않습니다. 단계를 곱하기 위해 자신만의 전략을 만들어야 합니다. 두 가지 좋은 접근 방식이 있습니다:

  • 텐서를 반복하여 일정한 다중 단계 입력을 생성합니다.
  • stateful=True를 사용하여 한 단계의 출력을 반복적으로 가져와서 다음 단계의 입력으로 제공합니다(output_features == input_features` 필요).

    반복 벡터를 사용한 일대다 변환

    케라스의 표준 동작에 맞추기 위해서는 단계별 입력이 필요하므로 원하는 길이만큼 입력을 반복하기만 하면 됩니다:

outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)

#output_shape -> (batch_size, steps, units)

스테이트풀 이해 = True

이제 컴퓨터 메모리에 맞지 않는 데이터를 한 번에 로드하는 것을 피하는 것 외에 stateful=True의 가능한 용도 중 하나를 소개합니다. Stateful을 사용하면 시퀀스의 '부분'을 단계적으로 입력할 수 있습니다. 차이점은 다음과 같습니다:

  • stateful=False`에서 두 번째 배치에는 첫 번째 배치와 독립적으로 완전히 새로운 시퀀스가 포함됩니다.
  • stateful=True`에서는 두 번째 배치가 첫 번째 배치를 이어서 동일한 시퀀스를 확장합니다.
    이 두 가지 주요 차이점을 제외하고는 시퀀스를 창으로 나누는 것과 같습니다:
  • 이 창들은 겹치지 않습니다!!
  • stateful=True에서는 이러한 창이 하나의 긴 시퀀스로 연결됩니다. stateful=True에서는 모든 새 배치가 이전 배치를 계속하는 것으로 해석됩니다(model.reset_states()를 호출할 때까지).
  • 배치 2의 시퀀스 1은 배치 1의 시퀀스 1을 계속합니다.
  • 배치 2의 시퀀스 2는 배치 1의 시퀀스 2를 계속합니다.
  • 배치 2의 시퀀스 n은 배치 1의 시퀀스 n을 계속합니다. 입력의 예, 배치 1에는 1단계와 2단계가 포함되고 배치 2에는 3~5단계가 포함됩니다:
                   BATCH 1                           BATCH 2
        [     Step1      Step2        |    [    Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2],     |       [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2],     |       [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....                                |
Tank N:    [[Pn1,Tn1], [Pn2,Tn2],     |       [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]                                  ]

배치 1과 배치 2에서 탱크가 정렬된 것을 확인하세요! 그렇기 때문에 shuffle=False가 필요합니다(물론 하나의 시퀀스만 사용하는 경우가 아니라면). 배치의 개수는 무한대로 가질 수 있습니다. (각 배치에 가변 길이를 사용하려면 input_shape=(None,features)를 사용합니다.

stateful=True를 사용한 일대다 배치

여기서는 하나의 출력 스텝을 가져와 입력으로 만들고자 하므로 배치당 하나의 스텝만 사용하겠습니다. 그림의 동작은 'stateful=True'에 의한 동작이 아니라는 점에 유의하세요. 아래 수동 루프에서 해당 동작을 강제할 것입니다. 이 예제에서 stateful=True는 시퀀스를 멈추고 원하는 것을 조작한 후 멈춘 지점에서 계속할 수 있도록 해줍니다. 솔직히 이 경우에는 반복 접근 방식이 더 나은 선택일 수 있습니다. 하지만 stateful=True를 살펴보고 있기 때문에 이것은 좋은 예입니다. 이를 사용하는 가장 좋은 방법은 다음 '다대다'의 경우입니다. Layer:

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, #just to keep a nice output shape even with length 1
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

이제 예측을 위한 수동 루프가 필요합니다:

input_data = someDataWithShape((batch, 1, features))

#important, we're starting new sequences, not continuing old ones:
model.reset_states()

output_sequence = []
last_step = input_data
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

stateful=True를 사용한 다대다 예측

이제 입력 시퀀스가 주어졌을 때 미래의 알려지지 않은 단계를 예측하는 아주 멋진 애플리케이션이 생겼습니다. 위의 '일대다'에서와 동일한 방법을 사용하지만 한 가지 차이점이 있습니다:

  • 한 단계 더 나아가 시퀀스 자체를 대상 데이터로 사용합니다.
  • 시퀀스의 일부를 알고 있으므로 결과에서 이 부분을 버립니다.
    레이어(위와 동일):
outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, 
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

훈련: 시퀀스의 다음 단계를 예측하도록 모델을 훈련하겠습니다:

totalSequences = someSequencesShaped((batch, steps, features))
    #batch size is usually 1 in these cases (often you have only one Tank in the example)

X = totalSequences[:,:-1] #the entire known sequence, except the last step
Y = totalSequences[:,1:] #one step ahead of X

#loop for resetting states at the start/end of the sequences:
for epoch in range(epochs):
    model.reset_states()
    model.train_on_batch(X,Y)

예측: 예측의 첫 번째 단계는 '상태 조정'을 포함합니다. 그렇기 때문에 이미 이 부분을 알고 있더라도 전체 시퀀스를 다시 예측할 것입니다:

model.reset_states() #starting a new sequence
predicted = model.predict(totalSequences)
firstNewStep = predicted[:,-1:] #the last step of the predictions is the first future step

이제 일대다 사례에서와 같이 루프로 이동합니다. 하지만 여기서 상태를 초기화하지 마세요!**. 우리는 모델이 시퀀스의 어느 단계에 있는지 알기를 원합니다(그리고 위에서 방금 한 예측으로 인해 첫 번째 새로운 단계에 있다는 것을 알고 있습니다).

output_sequence = [firstNewStep]
last_step = firstNewStep
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

이 접근 방식은 이 답변과 파일에 사용되었습니다:

inputs = Input((steps,features))

#a few many to many layers:
outputs = LSTM(hidden1,return_sequences=True)(inputs)
outputs = LSTM(hidden2,return_sequences=True)(outputs)    

#many to one layer:
outputs = LSTM(hidden3)(outputs)

encoder = Model(inputs,outputs)

디코더: '반복' 메서드 사용;

inputs = Input((hidden3,))

#repeat to make one to many:
outputs = RepeatVector(steps)(inputs)

#a few many to many layers:
outputs = LSTM(hidden4,return_sequences=True)(outputs)

#last layer
outputs = LSTM(features,return_sequences=True)(outputs)

decoder = Model(inputs,outputs)

자동 인코더:

inputs = Input((steps,features))
outputs = encoder(inputs)
outputs = decoder(outputs)

autoencoder = Model(inputs,outputs)

fit(X,X)`로 훈련하기

추가 설명

LSTM에서 스텝이 계산되는 방식에 대한 자세한 내용이나 위의 'stateful=True' 사례에 대한 자세한 내용은 이 답변에서 확인할 수 있습니다: https://stackoverflow.com/questions/53955093/doubts-regarding-understanding-keras-lstms.

해설 (21)

RNN의 마지막 레이어에 반환 시퀀스가 있는 경우 단순한 Dense 레이어를 사용할 수 없으며 대신 TimeDistributed를 사용합니다.

다음은 다른 사람들에게 도움이 될 수 있는 코드 예제입니다.

words = keras.layers.Input(batch_shape=(None, self.maxSequenceLength), name = "input")

    # Build a matrix of size vocabularySize x EmbeddingDimension 
    # where each row corresponds to a "word embedding" vector.
    # This layer will convert replace each word-id with a word-vector of size Embedding Dimension.
    embeddings = keras.layers.embeddings.Embedding(self.vocabularySize, self.EmbeddingDimension,
        name = "embeddings")(words)
    # Pass the word-vectors to the LSTM layer.
    # We are setting the hidden-state size to 512.
    # The output will be batchSize x maxSequenceLength x hiddenStateSize
    hiddenStates = keras.layers.GRU(512, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength,
                                        self.EmbeddingDimension),
                                        name = "rnn")(embeddings)
    hiddenStates2 = keras.layers.GRU(128, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength, self.EmbeddingDimension),
                                        name = "rnn2")(hiddenStates)

    denseOutput = TimeDistributed(keras.layers.Dense(self.vocabularySize), 
        name = "linear")(hiddenStates2)
    predictions = TimeDistributed(keras.layers.Activation("softmax"), 
        name = "softmax")(denseOutput)  

    # Build the computational graph by specifying the input, and output of the network.
    model = keras.models.Model(input = words, output = predictions)
    # model.compile(loss='kullback_leibler_divergence', \
    model.compile(loss='sparse_categorical_crossentropy', \
        optimizer = keras.optimizers.Adam(lr=0.009, \
            beta_1=0.9,\
            beta_2=0.999, \
            epsilon=None, \
            decay=0.01, \
            amsgrad=False))
해설 (0)