【深度学习】在PyTorch中使用 LSTM 进行新冠病例预测
時間序列數據,顧名思義是一種隨時間變化的數據。例如,24 小時時間段內的溫度,一個月內各種產品的價格,特定公司一年內的股票價格。長短期記憶網絡(LSTM)等高級深度學習模型能夠捕捉時間序列數據中的模式,因此可用于對數據的未來趨勢進行預測。在本文中,我們將一起學習如何使用 LSTM 算法使用時間序列數據進行未來預測。
本案例并不是試圖建立一個模型,并以盡可能最好的方式預測Covid-19。而這也僅僅是一個示例,說明如何使用 PyTorch 在一些真實數據的時間序列數據上使用循環神經網絡LSTM。當然,如果你有更好的模型來預測每日確診病例的數量,歡迎聯系小猴子,一起研習呀。
寫在前面
什么是 LSTM
長短期記憶網絡Long Short Term Memory Networks (LSTM)[2]是一種循環神經網絡,旨在克服基本 RNN 的問題,因此網絡可以學習長期依賴關系。具體來說,它解決了梯度消失和爆炸的問題——當你通過時間反向傳播太多時間步長時,梯度要么消失(變為零)要么爆炸(變得非常大),因為它變成了數字的乘積。
循環神經網絡都具有神經網絡重復模塊鏈的形式,LSTM 也有這種類似鏈的結構,但重復模塊與標準 RNN 相比有不同的結構。不是只有一個神經網絡層,而是有四個,以一種非常特殊的方式進行交互。典型的圖如下所示。
可以從Chris Olah的博客[3]中了解有關 LSTM 的更多信息。
為什么選用LSTM處理數據序列
許多經典方法(例如 ARIMA)嘗試以不同的成功率處理時間序列數據(并不是說它們不擅長)。在過去幾年中,長短期記憶網絡模型在處理這些類型的數據時已成為一種非常有用的方法。
循環神經網絡(LSTM 是其中一種)非常擅長處理數據序列。他們可以“回憶”過去(或未來)數據中的模式。在本次案例中,將學習如何基于真實世界的數據使用 LSTM 來預測未來的冠狀病毒病例。
PyTorch 中的 LSTM
nn.LSTM 中每一維代表什么意思
Pytorch 中的 LSTM 期望其所有輸入都是三維張量。這些張量的軸的語義很重要。第一個軸是序列本身,第二個索引是小批量中的實例,第三個索引是輸入的元素。具體來講:
第一維體現的是序列(sequence)結構,也就是序列的個數,用文章來說,就是每個句子的長度,因為是喂給網絡模型,一般都設定為確定的長度,也就是我們喂給LSTM神經元的每個句子的長度,當然,如果是其他的帶有序列形式的數據,則表示一個明確分割單位長度,
例如是如果是股票數據內,這表示特定時間單位內,有多少條數據。這個參數也就是明確這個層中有多少個確定的單元來處理輸入的數據。
第二維度體現的是batch_size,也就是一次性喂給網絡多少條句子,或者股票數據中的,一次性喂給模型多少是個時間單位的數據,具體到每個時刻,也就是一次性喂給特定時刻處理的單元的單詞數或者該時刻應該喂給的股票數據的條數。
第三位體現的是輸入的元素(elements of input),也就是,每個具體的單詞用多少維向量來表示,或者股票數據中 每一個具體的時刻的采集多少具體的值,比如最低價,最高價,均價,5日均價,10均價,等等。
nn.LSTM參數詳解
通過源代碼中可以看到nn.LSTM繼承自nn.RNNBase,其需要關注的參數以及其含義解釋如下:
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?torchimport?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)<torch._C.Generator at 0x7fcc1072d2b0>每日數據集
這些數據由約翰·霍普金斯大學系統科學與工程中心(JHU CSSE)提供,包含按國家/地區報告的每日病例數。數據集可在GitHub上獲得[5],并定期更新。
我們將只使用確診病例的時間序列數據(也提供死亡和康復病例數):
探索性數據分析
首先導入數據集
df?=?pd.read_csv('./data/time_series_covid19_confirmed_global.csv') df.head()這里要注意兩點:
數據包含省、國家/地區、緯度和經度。但我們不需要這些數據。
病例數是累積的。我們需要撤消累積。
從去掉前四列我們不需要的數據,可以通過切片直接獲取第五列之后的數據。
df?=?df.iloc[:,?4:]df.head()接下來檢查缺失值。
df.isnull().sum().sum()0一切都已就位。我們對所有行求和,這樣就得到了每日累計案例:
daily_cases?=?df.sum(axis=0) daily_cases.index?=?pd.to_datetime(daily_cases.index) daily_cases.head()2020-01-22 557 2020-01-23 655 2020-01-24 941 2020-01-25 1434 2020-01-26 2118 dtype: int64plt.plot(daily_cases) plt.title("Cumulative?daily?cases");我們將通過從前一個值中減去當前值來撤消累加。我們將保留序列的第一個值。
daily_cases?=?daily_cases.diff().fillna(daily_cases[0]).astype(np.int64) daily_cases.head()2020-01-22 557 2020-01-23 98 2020-01-24 286 2020-01-25 493 2020-01-26 684 dtype: int64plt.plot(daily_cases) plt.title("Daily?cases");從圖中可以看出有一個巨大的峰值,這主要是由于中國檢測患者的標準發生了變化。這對我們的模型來說肯定是一個挑戰。
讓我們檢查一下我們擁有的數據量。
daily_cases.shape(622,)我們有 622 天的數據。讓我們看看我們能用它做什么。
數據預處理
我們將保留前 467 天用于訓練,其余時間用于測試。
test_data_size?=?155train_data?=?daily_cases[:-test_data_size] test_data?=?daily_cases[-test_data_size:]train_data.shape(467,)如果我們想提高模型的訓練速度和性能,我們必須縮放數據(將值按比例壓縮在 0 和 1 之間)。我們將使用來自?scikit-learn?的MinMaxScaler。
scaler?=?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))這份數據中,每天都有大量的病例。可以將把它轉換成更小的條數。
def?create_sequences(data,?seq_length):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()每個訓練示例包含7個歷史數據點序列和一個標簽,該標簽表示我們的模型需要預測的真實值。接下來看看我們轉換后的數據的樣貌。
X_train.shapetorch.Size([459, 7, 1])X_train[:2]tensor([[[0.0003],[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]]])y_train.shapetorch.Size([459, 1])y_train[:2]tensor([[0.0003],[0.0013]])train_data[:10]array([[0.00030585],[0. ],[0.00012527],[0.0002632 ],[0.00039047],[0.00047377],[0.00170116],[0.00032717],[0.00131268],[0.00106214]])建立模型
我們將把模型封裝到一個自torch.nn.Module的類中。
class?CoronaVirusPredictor(nn.Module):def?__init__(self,?n_features,?n_hidden,?seq_len,?n_layers=2):super(CoronaVirusPredictor,?self).__init__()self.n_hidden?=?n_hiddenself.seq_len?=?seq_lenself.n_layers?=?n_layersself.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 層。我們采用最后一個時間步的輸出并將其傳遞給我們的線性層以獲得預測。
訓練模型
為模型訓練構建一個輔助函數。
def?train_model(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?=?100train_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注意,隱藏層的狀態在每個epoch開始時被重置。我們不使用批量的數據,模型可以一次看到每個樣本。將使用均方誤差來測量我們的訓練和測試誤差,并將兩者都記錄下來。
接下來創建一個模型的實例并訓練它。
model?=?CoronaVirusPredictor(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 0 train loss: 36.648155212402344 test loss: 19.638214111328125 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不下降的問題[6]
訓練集loss不下降
1.模型結構和特征工程存在問題
2.權重初始化方案有問題
3.正則化過度
4.選擇合適的激活函數、損失函數
5.選擇合適的優化器和學習速率
6.訓練時間不足
7.模型訓練遇到瓶頸:梯度消失、大量神經元失活、梯度爆炸和彌散、學習率過大或過小等
8.batch size過大
9.數據集未打亂
10.數據集有問題
11.未進行歸一化
12.特征工程中對數據特征的選取有問題
驗證集loss不下降
1.適當的正則化和降維
2.適當降低模型的規模
3.獲取更多的數據集
來看看訓練和測試損失。
plt.plot(train_hist,?label="Training?loss") plt.plot(test_hist,?label="Test?loss") plt.ylim((0,?100)) plt.legend();我們的模型在50個時代之后并沒有改善。回想一下,我們只有很少的數據,并且模型損失并未優化至最佳。也許我們不該那么相信我們的模型?
預測未來幾天的病例
我們的所建立的模型(由于訓練它的方式)只能預測未來的某一天。我們將采用一個簡單的策略來克服這個限制。使用預測值作為預測未來幾天的輸入。
with?torch.no_grad():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()這里需要撤銷測試數據和模型預測的縮放轉換比例,即使用逆縮放器變換,已得到原始數據。
true_cases?=?scaler.inverse_transform(np.expand_dims(y_test.flatten().numpy(),?axis=0) ).flatten()predicted_cases?=?scaler.inverse_transform(np.expand_dims(preds,?axis=0) ).flatten()一起看看結果,將訓練數據、測試數據及預測數據繪制在同一張畫布上,一起比較下預測結果。
plt.plot(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?=?MinMaxScaler()scaler?=?scaler.fit(np.expand_dims(daily_cases,?axis=1)) all_data?=?scaler.transform(np.expand_dims(daily_cases,?axis=1)) all_data.shape(622, 1)預處理和訓練步驟相同。
X_all,?y_all?=?create_sequences(all_data,?seq_length)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 0 train loss: 28.981904983520508 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預測未來病例
使用“完全訓練”的模型來預測未來 12 天的確診病例。
DAYS_TO_PREDICT?=?12with?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()和以前一樣,我們將逆縮放器變換。
predicted_cases?=?scaler.inverse_transform(np.expand_dims(preds,?axis=0) ).flatten()要使用歷史和預測案例創建一個很酷的圖表,我們需要擴展數據框的日期索引。
daily_cases.index[-1]Timestamp('2020-03-02 00:00:00')predicted_index?=?pd.date_range(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(daily_cases,?label='Historical?Daily?Cases') plt.plot(predicted_cases,?label='Predicted?Daily?Cases') plt.legend();從這里也能看,本次模型仍然有待優化。至于如何優化模型,待后續更新。
寫在最后
我們學習了如何使用 PyTorch 創建處理時間序列數據的循環神經網絡。模型性能不是很好,但考慮到數據量很少,這是可以預期的。
預測每日 Covid-19 病例的問題確實是一個難題。同樣希望一段時間后一切都會恢復正常。
參考資料
[1]?
參考原文:?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/
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載黃海廣老師《機器學習課程》視頻課黃海廣老師《機器學習課程》711頁完整版課件本站qq群955171419,加入微信群請掃碼:
總結
以上是生活随笔為你收集整理的【深度学习】在PyTorch中使用 LSTM 进行新冠病例预测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js获取 jquery获取页面shu
- 下一篇: 【机器学习】为什么GBDT可以超越深度学