使用 LSTM 进行新冠病例预测(Pytorch)
The following article is from 机器学习研习院 Author 小猴子
长短期记忆网络(LSTM)等高级深度学习模型能够捕捉时间序列数据中的模式,因此可用于对数据的未来趋势进行预测。在本文中,我们将一起学习如何使用 LSTM 算法使用时间序列数据进行未来预测。
本案例并不是试图建立一个模型,并以尽可能最好的方式预测Covid-19。而这也仅仅是一个示例,说明如何使用 PyTorch 在一些真实数据的时间序列数据上使用循环神经网络LSTM。当然,如果你有更好的模型来预测每日确诊病例的数量,欢迎联系小猴子,一起研习呀。
写在前面
什么是 LSTM
为什么选用LSTM处理数据序列
PyTorch 中的 LSTM
nn.LSTM 中每一维代表什么意思
nn.LSTM参数详解
input_size
– 输入数据的大小,就是你输入x的向量大小(x向量里有多少个元素)hidden_size
– 隐藏层的大小(即隐藏层节点数量),输出向量的维度等于隐藏节点数。就是LSTM在运行时里面的维度。隐藏层状态的维数,即隐藏层节点的个数,这个和单层感知器的结构是类似的。num_layers
– LSTM 堆叠的层数,默认值是1层,如果设置为2,第二个LSTM接收第一个LSTM的计算结果。bias
– 隐层状态是否带bias,默认为true。bias是偏置值,或者偏移值。没有偏置值就是以0为中轴,或以0为起点。偏置值的作用请参考单层感知器相关结构。batch_first
– 判断输入输出的第一维是否为 batch_size,默认值 False。故此参数设置可以将 batch_size 放在第一维度。因为 Torch 中,人们习惯使用Torch中带有的dataset,dataloader向神经网络模型连续输入数据,这里面就有一个 batch_size 的参数,表示一次输入多少个数据。在 LSTM 模型中,输入数据必须是一批数据,为了区分LSTM中的批量数据和dataloader中的批量数据是否相同意义,LSTM 模型就通过这个参数的设定来区分。如果是相同意义的,就设置为True,如果不同意义的,设置为False。torch.LSTM 中 batch_size 维度默认是放在第二维度,故此参数设置可以将 batch_size 放在第一维度。 dropout
– 默认值0。是否在除最后一个 RNN 层外的其他 RNN 层后面加 dropout 层。输入值是 0-1 之间的小数,表示概率。0表示0概率dripout,即不dropout 。bidirectional
– 是否是双向 RNN,默认为:false,若为 true,则:num_directions=2,否则为1。
(batch_first=False)
。输入数据需要按如下形式传入input, (h_0,c_0)
input
: 输入数据,即上面例子中的一个句子(或者一个batch的句子),其维度形状为 (seq_len, batch, input_size)
seq_len: 句子长度,即单词数量,这个是需要固定的。当然假如你的一个句子中只有2个单词,但是要求输入10个单词,这个时候可以用torch.nn.utils.rnn.pack_padded_sequence(),或者torch.nn.utils.rnn.pack_sequence()来对句子进行填充或者截断。 batch:就是你一次传入的句子的数量 input_size: 每个单词向量的长度,这个必须和你前面定义的网络结构保持一致 h_0
:维度形状为 (num_layers * num_directions, batch, hidden_size)。
结合下图应该比较好理解第一个参数的含义num_layers * num_directions,即LSTM的层数乘以方向数量。这个方向数量是由前面介绍的bidirectional决定,如果为False,则等于1;反之等于2。 batch:同上 hidden_size: 隐藏层节点数 c_0
:维度形状为 (num_layers * num_directions, batch, hidden_size),各参数含义和h_0类似。
(h_0, c_0)
,那么这两个参数会默认设置为0。output
:维度和输入数据类似,只不过最后的feature部分会有点不同,即 (seq_len, batch, num_directions * hidden_size)。这个输出tensor包含了LSTM模型最后一层每个time step的输出特征,比如说LSTM有两层,那么最后输出的是[h10,h11,...,h1l],表示第二层LSTM每个time step对应的输出。h_n
:(num_layers * num_directions, batch, hidden_size), 只会输出最后一个time step的隐状态结果。c_n
:(num_layers * num_directions, batch, hidden_size),只会输出最后个time step的cell状态结果。
导入相关模块
import os
import numpy as np
import pandas as pd
from tqdm import tqdm
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc
from sklearn.preprocessing import MinMaxScaler
from pandas.plotting import register_matplotlib_converters
from torch import nn, optim
%matplotlib inline
%config InlineBackend.figure_format='retina'
sns.set(style='whitegrid', palette='muted', font_scale=1.2)
HAPPY_COLORS_PALETTE = ["#01BEFE", "#FFDD00", "#FF7D00", "#FF006D", "#93D30C", "#8F00FF"]
sns.set_palette(sns.color_palette(HAPPY_COLORS_PALETTE))
rcParams['figure.figsize'] = 14, 6
register_matplotlib_converters()
RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)
每日数据集
探索性数据分析
df.head()
数据包含省、国家/地区、纬度和经度。但我们不需要这些数据。 病例数是累积的。我们需要撤消累积。
daily_cases.index = pd.to_datetime(daily_cases.index)
daily_cases.head()
2020-01-23 655
2020-01-24 941
2020-01-25 1434
2020-01-26 2118
dtype: int64
plt.title("Cumulative daily cases");
daily_cases.head()
2020-01-23 98
2020-01-24 286
2020-01-25 493
2020-01-26 684
dtype: int64
plt.title("Daily cases");
数据预处理
train_data = daily_cases[:-test_data_size]
test_data = daily_cases[-test_data_size:]
train_data.shape
scikit-learn
的MinMaxScaler
。scaler = scaler.fit(np.expand_dims(train_data, axis=1))
train_data = scaler.transform(np.expand_dims(train_data, axis=1))
test_data = scaler.transform(np.expand_dims(test_data, axis=1))
xs = []
ys = []
for i in range(len(data)-seq_length-1):
x = data[i:(i+seq_length)]
y = data[i+seq_length]
xs.append(x)
ys.append(y)
return np.array(xs), np.array(ys)
seq_length = 7
X_train, y_train = create_sequences(train_data, seq_length)
X_test, y_test = create_sequences(test_data, seq_length)
X_train = torch.from_numpy(X_train).float()
y_train = torch.from_numpy(y_train).float()
X_test = torch.from_numpy(X_test).float()
y_test = torch.from_numpy(y_test).float()
[0.0000],
[0.0001],
[0.0003],
[0.0004],
[0.0005],
[0.0017]],
[[0.0000],
[0.0001],
[0.0003],
[0.0004],
[0.0005],
[0.0017],
[0.0003]]])
[0.0013]])
[0. ],
[0.00012527],
[0.0002632 ],
[0.00039047],
[0.00047377],
[0.00170116],
[0.00032717],
[0.00131268],
[0.00106214]])
建立模型
torch.nn.Module
的类中。def __init__(self, n_features, n_hidden, seq_len, n_layers=2):
super(CoronaVirusPredictor, self).__init__()
self.n_hidden = n_hidden
self.seq_len = seq_len
self.n_layers = n_layers
self.lstm = nn.LSTM(
input_size=n_features,
hidden_size=n_hidden,
num_layers=n_layers,
dropout=0.5
)
self.linear = nn.Linear(in_features=n_hidden, out_features=1)
def reset_hidden_state(self):
self.hidden = (
torch.zeros(self.n_layers, self.seq_len, self.n_hidden),
torch.zeros(self.n_layers, self.seq_len, self.n_hidden)
)
def forward(self, sequences):
lstm_out, self.hidden = self.lstm(
sequences.view(len(sequences), self.seq_len, -1),
self.hidden
)
last_time_step = \
lstm_out.view(self.seq_len, len(sequences), self.n_hidden)[-1]
y_pred = self.linear(last_time_step)
return y_pred
CoronaVirusPredictor
包含 3 个方法:构造函数 - 初始化所有辅助数据并创建层。 reset_hidden_state
- 我们将使用无状态 LSTM,因此我们需要在每个示例之后重置状态。forward
- 获取序列,一次将所有序列通过 LSTM 层。我们采用最后一个时间步的输出并将其传递给我们的线性层以获得预测。
训练模型
model,
train_data,
train_labels,
test_data=None,
test_labels=None
):
loss_fn = torch.nn.MSELoss(reduction='sum')
optimiser = torch.optim.Adam(model.parameters(), lr=1e-3)
num_epochs = 100
train_hist = np.zeros(num_epochs)
test_hist = np.zeros(num_epochs)
for t in range(num_epochs):
model.reset_hidden_state()
y_pred = model(X_train)
loss = loss_fn(y_pred.float(), y_train)
if test_data is not None:
with torch.no_grad():
y_test_pred = model(X_test)
test_loss = loss_fn(y_test_pred.float(), y_test)
test_hist[t] = test_loss.item()
if t % 10 == 0:
print(f'Epoch {t} train loss: {loss.item()} test loss: {test_loss.item()}')
elif t % 10 == 0:
print(f'Epoch {t} train loss: {loss.item()}')
train_hist[t] = loss.item()
optimiser.zero_grad()
loss.backward()
optimiser.step()
return model.eval(), train_hist, test_hist
n_features=1,
n_hidden=512,
seq_len=seq_length,
n_layers=2
)
model, train_hist, test_hist = train_model(
model,
X_train,
y_train,
X_test,
y_test
)
Epoch 10 train loss: 15.204809188842773 test loss: 1.5947879552841187
Epoch 20 train loss: 12.796365737915039 test loss: 2.8455893993377686
Epoch 30 train loss: 13.453448295593262 test loss: 5.292770862579346
Epoch 40 train loss: 13.023208618164062 test loss: 2.4893276691436768
Epoch 50 train loss: 12.724889755249023 test loss: 4.096951007843018
Epoch 60 train loss: 12.620814323425293 test loss: 3.4106807708740234
Epoch 70 train loss: 12.63078498840332 test loss: 3.245408296585083
Epoch 80 train loss: 12.615142822265625 test loss: 3.529395341873169
Epoch 90 train loss: 12.61486530303955 test loss: 3.571239948272705
如何解决神经网络训练时loss不下降的问题?
训练集loss不下降 1.模型结构和特征工程存在问题
2.权重初始化方案有问题
3.正则化过度
4.选择合适的激活函数、损失函数
5.选择合适的优化器和学习速率
6.训练时间不足
7.模型训练遇到瓶颈:梯度消失、大量神经元失活、梯度爆炸和弥散、学习率过大或过小等
8.batch size过大
9.数据集未打乱
10.数据集有问题
11.未进行归一化
12.特征工程中对数据特征的选取有问题验证集loss不下降 1.适当的正则化和降维
2.适当降低模型的规模
3.获取更多的数据集
plt.plot(test_hist, label="Test loss")
plt.ylim((0, 100))
plt.legend();
预测未来几天的病例
test_seq = X_test[:1]
preds = []
for _ in range(len(X_test)):
y_test_pred = model(test_seq)
pred = torch.flatten(y_test_pred).item()
preds.append(pred)
new_seq = test_seq.numpy().flatten()
new_seq = np.append(new_seq, [pred])
new_seq = new_seq[1:]
test_seq = torch.as_tensor(new_seq
).view(1, seq_length, 1).float()
np.expand_dims(y_test.flatten().numpy(), axis=0)
).flatten()
predicted_cases = scaler.inverse_transform(
np.expand_dims(preds, axis=0)
).flatten()
daily_cases.index[:len(train_data)],
scaler.inverse_transform(train_data).flatten(),
label='Historical Daily Cases')
plt.plot(
daily_cases.index[len(train_data):len(train_data) + len(true_cases)],
true_cases,
label='Real Daily Cases')
plt.plot(
daily_cases.index[len(train_data):len(train_data) + len(true_cases)],
predicted_cases,
label='Predicted Daily Cases')
plt.legend();
使用所有数据来训练
scaler = scaler.fit(np.expand_dims(daily_cases, axis=1))
all_data = scaler.transform(np.expand_dims(daily_cases, axis=1))
all_data.shape
X_all = torch.from_numpy(X_all).float()
y_all = torch.from_numpy(y_all).float()
model = CoronaVirusPredictor(
n_features=1,
n_hidden=512,
seq_len=seq_length,
n_layers=2
)
model, train_hist, _ = train_model(model, X_all, y_all)
Epoch 10 train loss: 12.115002632141113
Epoch 20 train loss: 10.47011661529541
Epoch 30 train loss: 18.45709991455078
Epoch 40 train loss: 14.793025016784668
Epoch 50 train loss: 12.061325073242188
Epoch 60 train loss: 11.918513298034668
Epoch 70 train loss: 11.55040168762207
Epoch 80 train loss: 10.834881782531738
Epoch 90 train loss: 15.602020263671875
预测未来病例
with torch.no_grad():
test_seq = X_all[:1]
preds = []
for _ in range(DAYS_TO_PREDICT):
y_test_pred = model(test_seq)
pred = torch.flatten(y_test_pred).item()
preds.append(pred)
new_seq = test_seq.numpy().flatten()
new_seq = np.append(new_seq, [pred])
new_seq = new_seq[1:]
test_seq = torch.as_tensor(new_seq).view(1, seq_length, 1).float()
np.expand_dims(preds, axis=0)
).flatten()
start=daily_cases.index[-1],
periods=DAYS_TO_PREDICT + 1,
closed='right'
)
predicted_cases = pd.Series(
data=predicted_cases,
index=predicted_index
)
plt.plot(predicted_cases, label='Predicted Daily Cases')
plt.legend();
plt.plot(predicted_cases, label='Predicted Daily Cases')
plt.legend();
写在最后
参考资料
参考原文: https://curiousily.com/posts/time-series-forecasting-with-lstm-for-daily-coronavirus-cases/
[2]Long Short Term Memory Networks (LSTM): https://en.wikipedia.org/wiki/Long_short-term_memory
[3]Chris Olah的博客: http://colah.github.io/posts/2015-08-Understanding-LSTMs/
[4]参数详解: https://blog.csdn.net/wangwangstone/article/details/90296461
[5]数据集可在GitHub上获得: https://github.com/CSSEGISandData/COVID-19
[6]如何解决神经网络训练时loss不下降的问题: https://blog.ailemon.net/2019/02/26/solution-to-loss-doesnt-drop-in-nn-train/
- EOF -
觉得本文对你有帮助?请分享给更多人
推荐关注「Python开发者」,提升Python技能
点赞和在看就是最大的支持❤️