针对序列数据的LSTM模型
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了针对序列数据的LSTM模型相关的知识,希望对你有一定的参考价值。
参考技术A 在实际项目中,我们能接触到很多序列数据,比如用户的行为序列。在构建模型中,这些行为序列能为我们的模型提供更多维度的特征,同时行为序列本身也包含了用户的各种习惯。因为序列数据本身有先后关系,为了更好的建模这种先后关系,我们尝试用 LSTM 模型。
LSTM (Long Short Term Memory network),一种特殊的RNN网络,可以很好的学习长期依赖关系。LSTM 由 Hochreiter & Schmidhuber (1997) 提出,并在近期被 Alex Graves 进行了改良和推广。
其结构如下:
图中主要的计算如下
将行为序列用 id 编码,并填充至固定长度。并切分好训练集、验证集和测试集。
将序列数据用 embedding 转换,用 LSTM 来提取数据,后接 Dense 层,最后输出结果。
这里简要提一下 LSTM 的参数计算:
其中, , , ,所以最终参数为 。
LSTM时间序列预测
关于时间序列预测
你可能经常会遇到这样的问题,给你一个数据集,要你预测下一个时刻的值是多少?如下图所示,这种数据往往并没有规律可言,也不可能用一个简单的n阶模型去拟合。老实说,以前我遇到这种问题都是直接上灰色模型,但是用的多了就感觉会有点问题。其它还有一些模型比方说ARAM、ARIRM我没有试过。这篇文章主要讲解用LSTM如何进行时间序列预测
数据
数据直接放在代码里,省去了下载文件并读取的麻烦。这组数据是首都国际机场1949-01~1960-12这12年来的客流量,一共12*12=144个月。我们选取前9年的数据(75%)作为Train Data,后3年的数据(25%)作为Test Data。本来还需要Val Data的,但由于数据量比较少,而且不用搞得那么麻烦,所以就不做Validation了。由于我观察到的客流量变化周期是一年的,因为我为客流数据加上了年、月这两个维度的标记
def load_data():
# passengers number of international airline, 1949-01~1960-12 per month
seq_number = np.array(
[112., 118., 132., 129., 121., 135., 148., 148., 136., 119., 104.,
118., 115., 126., 141., 135., 125., 149., 170., 170., 158., 133.,
114., 140., 145., 150., 178., 163., 172., 178., 199., 199., 184.,
162., 146., 166., 171., 180., 193., 181., 183., 218., 230., 242.,
209., 191., 172., 194., 196., 196., 236., 235., 229., 243., 264.,
272., 237., 211., 180., 201., 204., 188., 235., 227., 234., 264.,
302., 293., 259., 229., 203., 229., 242., 233., 267., 269., 270.,
315., 364., 347., 312., 274., 237., 278., 284., 277., 317., 313.,
318., 374., 413., 405., 355., 306., 271., 306., 315., 301., 356.,
348., 355., 422., 465., 467., 404., 347., 305., 336., 340., 318.,
362., 348., 363., 435., 491., 505., 404., 359., 310., 337., 360.,
342., 406., 396., 420., 472., 548., 559., 463., 407., 362., 405.,
417., 391., 419., 461., 472., 535., 622., 606., 508., 461., 390.,
432.], dtype=np.float32)
# plt.plot(seq_number)
# plt.ion()
seq_number = seq_number[:, np.newaxis] # add a new dimension
# 12 years
seq_year = np.arange(12)
# 12 month
seq_month = np.arange(12)
seq_year_month = np.transpose(
[np.repeat(seq_year, len(seq_month)),
np.tile(seq_month, len(seq_year))],
)
seq = np.concatenate((seq_number, seq_year_month), axis=1)
# normalization
seq = (seq - seq.mean(axis=0)) / seq.std(axis=0)
return seq
总的数据维度是(144, 3),即144个月的[客流量,年份,月份]这3个维度的数据。并且我对数据进行了归一化处理
模型
我们希望输入前9年的数据,让LSTM预测后3年的客流,那么我们可以先用前9年中每个月的数据训练LSTM,让它根据前几个月预测下一个月的客流。等训练完成后,我们让LSTM根据前9年的数据预测出下一个月的客流,把刚刚输出的预测客流作为输入,迭代求得后3年的客流
请注意,通常情况下Tensor的第一个维度是批次大小batch size,但是PyTorch建议我们输入循环神经网络的时候,Tensor的第一个维度是序列长度seq len,第二个维度才是batch size
对于这个客流数据,seq_len
指的是时间序列的长度,这里前9年,共108个月,则seq_len=108
。input_dim
指的是输入的维度,这里输入数据由三个维度构成,则input_dim=3
接下来我们就可以确定LSTM的结构了
lstm = nn.LSTM(input_dim, mid_dim, mid_layers)
# input_dim 指的是LSTM输入Tensor的维度,根据我们的数据已经确定了这个值是3
# mid_dim 指的是LSTM三个门(gaee)的网络宽度,也是LSTM输出Tensor的维度
# mid_layers 指的是LSTM内部各个gate使用的全连接层的数量,一般设为1或2
x = torch.randn(seq_len, batch_size, input_dim)
output = lstm(x)
assert output.size() == (seq_len, batch_size, mid_dim)
mid_layers一般设置为1或者2:理论上足够宽(神经元个数足够多),并且至少存在一层具有任何一种"挤压"性质的激活函数的2层全连接层就能拟合任何的连续函数
为了进行时间序列预测,我们在LSTM后面街上两层全连接层(1层也行),用于改变最终LSTM输出Tensor的维度。我们只需要预测客流量这一个值,因此out_dim=1
fc = nn.Sequential(
nn.Linear(mid_dim, mid_dim)
nn.ReLU(),
nn.Linear(mid_dim, out_dim),
)
x = output_of_LSTM
seq_len, batch_size, mid_dim = x.shape
x = x.view(-1, mid_dim)
output_of_fc = fc(x)
output_of_fc = output_of_fc.view(seq_len, batch_size, out_dim)
训练
同一批次中序列长度不同,需要使用from torch.nn.utils.rnn import pad_sequence
我们只有一组训练数据,即前9年的客流量。我们可以在同一批次中,训练LSTM预测不同月份的客流量。1~t月的输入对应了t+1月的客流量。由于同一批次里面训练序列长度不统一,直接在末尾补0的操作不优雅,所以我们需要借助torch 自带的工具 pad_sequence的协助,具体如下
var_x = torch.tensor(train_x, dtype=torch.float32, device=device)
var_y = torch.tensor(train_y, dtype=torch.float32, device=device)
batch_var_x = list()
batch_var_y = list()
for i in range(batch_size):
j = train_size - i
batch_var_x.append(var_x[j:])
batch_var_y.append(var_y[j:])
from torch.nn.utils.rnn import pad_sequence
batch_var_x = pad_sequence(batch_var_x)
batch_var_y = pad_sequence(batch_var_y)
放入pad_sequence的序列必须从长到短放置,随着反向传播的进行,PyTorch会逐步忽略完成梯度计算的短序列,具体解释请看PyTorch官网
criterion = nn.MSELoss() # L2_loss
for e in range(epochs):
out = net(batch_var_x)
loss = criterion(out, batch_var_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
评估
就像前面所说的:使用前9年的数据作为输入,预测得到下一个与的客流,并将此预测结果加到输入序列中,从而逐步预测后3年的客流。就像修路一样,走在刚刚铺好的路面往前推进
最终完整代码
import numpy as np
import torch
from torch import nn
import matplotlib.pyplot as plt
def run_train_lstm():
inp_dim = 3
out_dim = 1
mid_dim = 8
mid_layers = 1
batch_size = 12 * 4
mod_dir = '.'
'''load data'''
data = load_data()
data_x = data[:-1, :] # 0~142
data_y = data[1:, 0] # 1~143
assert data_x.shape[1] == inp_dim
train_size = int(len(data_x) * 0.75)
train_x = data_x[:train_size]
train_y = data_y[:train_size]
train_x = train_x.reshape((train_size, inp_dim))
train_y = train_y.reshape((train_size, out_dim))
'''build model'''
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = RegLSTM(inp_dim, out_dim, mid_dim, mid_layers).to(device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)
'''train'''
var_x = torch.tensor(train_x, dtype=torch.float32).to(device)
var_y = torch.tensor(train_y, dtype=torch.float32).to(device)
batch_var_x = list()
batch_var_y = list()
for i in range(batch_size):
j = train_size - i
batch_var_x.append(var_x[j:])
batch_var_y.append(var_y[j:])
from torch.nn.utils.rnn import pad_sequence
batch_var_x = pad_sequence(batch_var_x)
batch_var_y = pad_sequence(batch_var_y)
print("Training Start")
for e in range(500):
out = net(batch_var_x)
loss = criterion(out, batch_var_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if e % 64 == 0:
print('Epoch: {:4}, Loss: {:.5f}'.format(e, loss.item()))
torch.save(net.state_dict(), '{}/net.pth'.format(mod_dir))
print("Save in:", '{}/net.pth'.format(mod_dir))
'''eval'''
net.load_state_dict(torch.load('{}/net.pth'.format(mod_dir), map_location=lambda storage, loc: storage))
net = net.eval()
test_x = data_x.copy()
test_x[train_size:, 0] = 0
test_x = test_x[:, np.newaxis, :]
test_x = torch.tensor(test_x, dtype=torch.float32, device=device)
eval_size = 1
zero_ten = torch.zeros((mid_layers, eval_size, mid_dim), dtype=torch.float32, device=device)
test_y, hc = net.output_y_hc(test_x[:train_size], (zero_ten, zero_ten))
test_x[train_size + 1, 0, 0] = test_y[-1]
for i in range(train_size + 1, len(data) - 2):
test_y, hc = net.output_y_hc(test_x[i:i + 1], hc)
test_x[i + 1, 0, 0] = test_y[-1]
pred_y = test_x[1:, 0, 0]
pred_y = pred_y.cpu().data.numpy()
diff_y = pred_y[train_size:] - data_y[train_size:-1]
l1_loss = np.mean(np.abs(diff_y))
l2_loss = np.mean(diff_y ** 2)
print("L1: {:.3f} L2: {:.3f}".format(l1_loss, l2_loss))
plt.plot(pred_y, 'r', label='pred')
plt.plot(data_y, 'b', label='real', alpha=0.3)
plt.plot([train_size, train_size], [-1, 2], color='k', label='train | pred')
plt.legend(loc='best')
plt.savefig('lstm_reg.png')
class RegLSTM(nn.Module):
def __init__(self, inp_dim, out_dim, mid_dim, mid_layers):
super(RegLSTM, self).__init__()
self.lstm = nn.LSTM(inp_dim, mid_dim, mid_layers) # lstm
self.fc = nn.Sequential(
nn.Linear(mid_dim, mid_dim),
nn.ReLU(),
nn.Linear(mid_dim, out_dim),
)
def forward(self, x):
y = self.lstm(x)[0] # y, (h, c) = self.lstm(x)
seq_len, batch_size, hid_dim = y.shape
y = y.view(-1, hid_dim)
y = self.fc(y)
y = y.view(seq_len, batch_size, -1)
return y
"""
Examples::
>>> rnn = nn.LSTM(10, 20, 2)
>>> input = torch.randn(5, 3, 10)
>>> h0 = torch.randn(2, 3, 20)
>>> c0 = torch.randn(2, 3, 20)
>>> output, (hn, cn) = rnn(input, (h0, c0))
"""
def output_y_hc(self, x, hc):
y, hc = self.lstm(x, hc) # y, (h, c) = self.lstm(x)
seq_len, batch_size, hid_dim = y.shape
y = y.view(-1, hid_dim)
y = self.fc(y)
y = y.view(seq_len, batch_size, -1)
return y, hc
def load_data():
# passengers number of international airline, 1949-01~1960-12 per month
seq_number = np.array(
[112., 118., 132., 129., 121., 135., 148., 148., 136., 119., 104.,
118., 115., 126., 141., 135., 125., 149., 170., 170., 158., 133.,
114., 140., 145., 150., 178., 163., 172., 178., 199., 199., 184.,
162., 146., 166., 171., 180., 193., 181., 183., 218., 230., 242.,
209., 191., 172., 194., 196., 196., 236., 235., 229., 243., 264.,
272., 237., 211., 180., 201., 204., 188., 235., 227., 234., 264.,
302., 293., 259., 229., 203., 229., 242., 233., 267., 269., 270.,
315., 364., 347., 312., 274., 237., 278., 284., 277., 317., 313.,
318., 374., 413., 405., 355., 306., 271., 306., 315., 301., 356.,
348., 355., 422., 465., 467., 404., 347., 305., 336., 340., 318.,
362., 348., 363., 435., 491., 505., 404., 359., 310., 337., 360.,
342., 406., 396., 420., 472., 548., 559., 463., 407., 362., 405.,
417., 391., 419., 461., 472., 535., 622., 606., 508., 461., 390.,
432.], dtype=np.float32)
# plt.plot(seq_number)
# plt.ion()
seq_number = seq_number[:, np.newaxis] # add a new dimension
# 12 years
seq_year = np.arange(12)
# 12 month
seq_month = np.arange(12)
seq_year_month = np.transpose(
[np.repeat(seq_year, len(seq_month)),
np.tile(seq_month, len(seq_year))],
)
seq = np.concatenate((seq_number, seq_year_month), axis=1)
# normalization
seq = (seq - seq.mean(axis=0)) / seq.std(axis=0)
return seq
if __name__ == '__main__':
torch.manual_seed(1)
run_train_lstm()
以上是关于针对序列数据的LSTM模型的主要内容,如果未能解决你的问题,请参考以下文章