저는 LSTM에 대한 저의 이해를 조정하려고 노력하고 있으며, 여기 크리스토퍼 올라가 작성한 이 게시물에서 지적한 내용을 Keras로 구현했습니다. 저는 제이슨 브라운리가 작성한 블로그의 Keras 튜토리얼을 따르고 있습니다. 제가 주로 혼란스러워하는 것은 다음과 같습니다,
샘플, 시간 단계, 기능
으로 재구성하는 것과,아래에 붙여넣은 코드를 참조하여 위의 두 가지 질문에 집중해 보겠습니다:
# 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은 배치 실행 사이에 셀 메모리 값을 저장하는 것을 의미합니까? 그렇다면 batch_size
는 하나이고 훈련 실행 사이에 메모리가 재설정되므로 상태 저장형이라는 말이 무슨 의미가 있을까요? 훈련 데이터가 셔플되지 않는다는 사실과 관련이 있다고 생각하지만 그 방법은 잘 모르겠습니다.
어떤 생각 있으신가요? 이미지 참조: http://karpathy.github.io/2015/05/21/rnn-effectiveness/
빨간색과 녹색 상자가 같다는 @van의 의견에 대해 약간 혼란스러웠습니다. 확인을 위해 다음 API 호출이 펼쳐진 다이어그램과 일치하나요? 특히 두 번째 다이어그램에 주목합니다(batch_size
는 임의로 선택했습니다.):
![여기에 이미지 설명 입력]][4]
유다시티의 딥러닝 강좌를 수강하고도 time_step 인자에 대해 혼란스러우신 분들은 다음 토론을 참고하시기 바랍니다: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169.
제가 찾던 것은 model.add(TimeDistributed(Dense(vocab_len)))
였습니다. 다음은 예제입니다: https://github.com/sachinruk/ShakespeareBot
LSTM에 대한 이해의 대부분을 여기에 요약했습니다:
우선, 훌륭한 튜토리얼(1,2)을 선택하여 시작합니다.
시간 단계의 의미: X.shape(데이터 모양 설명)에서 '시간 단계==3'은 분홍색 상자 3개가 있음을 의미합니다. Keras에서는 각 단계마다 입력이 필요하므로 일반적으로 녹색 상자의 수는 빨간색 상자의 수와 같아야 합니다. 구조를 해킹하지 않는 한 말입니다.
다대다 대 다대일: keras에서는 LSTM
이나 GRU
, SimpleRNN
을 초기화할 때 return_sequences
파라미터가 있습니다. 반환값이 False
(기본값)이면 그림과 같이 다수 대 1이 됩니다. 반환 형태는 마지막 상태를 나타내는 (batch_size, hidden_unit_length)
입니다. return_sequences가
True이면 **다대다**입니다. 반환 형태는
(batch_size, time_step, hidden_unit_length)`입니다.
특징 인자가 관련성이 있는가: 특징 인수는 '빨간색 상자가 얼마나 큰지' 또는 각 단계의 입력 차원을 의미합니다. 예를 들어 8가지의 시장 정보로부터 예측하고 싶다면 feature==8
로 데이터를 생성할 수 있습니다.
스테이트풀: 소스 코드]3를 조회할 수 있습니다. 상태를 초기화할 때 stateful==True
이면 마지막 학습의 상태를 초기 상태로 사용하고, 그렇지 않으면 새로운 상태를 생성합니다. 아직 stateful
을 켜지 않았습니다. 하지만 batch_size
가 stateful==True
일 때만 1이 될 수 있다는 것에 동의하지 않습니다.
현재 수집된 데이터로 데이터를 생성하고 있습니다. 주식 정보가 스트림으로 들어온다고 가정하고, 하루를 기다렸다가 순차적으로 모두 수집하는 것이 아니라 네트워크에서 학습/예측하는 동안 온라인으로 입력 데이터를 생성하고 싶습니다. 만약 같은 네트워크를 공유하는 주식이 400개라면 batch_size==400
을 설정하면 됩니다.
정답을 보완하기 위해 이 답변은 케라스의 행동과 각 그림을 달성하는 방법을 보여줍니다.
표준 케라스 내부 처리는 다음 그림과 같이 항상 다대다입니다(예제로서 features=2
, 압력 및 온도를 사용했습니다):
이 이미지에서는 다른 차원과 혼동을 피하기 위해 단계 수를 5로 늘렸습니다.
이 예제에서는
(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]],
....
]
이 경우 처음에는 하나의 시퀀스만 있지만 여러 시퀀스로 나누어 창을 생성한다는 점에 유의하세요. '시퀀스란 무엇인가'라는 개념은 추상적입니다. 중요한 부분은
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
이제 이것은 keras LSTM 레이어만으로는 지원되지 않습니다. 단계를 곱하기 위해 자신만의 전략을 만들어야 합니다. 두 가지 좋은 접근 방식이 있습니다:
를 사용하여 한 단계의 출력을 반복적으로 가져와서 다음 단계의 입력으로 제공합니다(
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)
이제 컴퓨터 메모리에 맞지 않는 데이터를 한 번에 로드하는 것을 피하는 것 외에 stateful=True
의 가능한 용도 중 하나를 소개합니다.
Stateful을 사용하면 시퀀스의 '부분'을 단계적으로 입력할 수 있습니다. 차이점은 다음과 같습니다:
에서는 이러한 창이 하나의 긴 시퀀스로 연결됩니다. stateful=True
에서는 모든 새 배치가 이전 배치를 계속하는 것으로 해석됩니다(model.reset_states()
를 호출할 때까지). 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
를 살펴보고 있기 때문에 이것은 좋은 예입니다. 이를 사용하는 가장 좋은 방법은 다음 '다대다'의 경우입니다.
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()
이제 입력 시퀀스가 주어졌을 때 미래의 알려지지 않은 단계를 예측하는 아주 멋진 애플리케이션이 생겼습니다. 위의 '일대다'에서와 동일한 방법을 사용하지만 한 가지 차이점이 있습니다:
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.
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))