如何基于TensorFlow使用LSTM和CNN實現(xiàn)時序分類任務(wù)
時序數(shù)據(jù)經(jīng)常出現(xiàn)在很多領(lǐng)域中,如金融、信號處理、語音識別和醫(yī)藥。傳統(tǒng)的時序問題通常首先需要人力進(jìn)行特征工程,才能將預(yù)處理的數(shù)據(jù)輸入到機(jī)器學(xué)習(xí)算法中。并且這種特征工程通常需要一些特定領(lǐng)域內(nèi)的專業(yè)知識,因此也就更進(jìn)一步加大了預(yù)處理成本。例如信號處理(即 EEG 信號分類),特征工程可能就涉及到各種頻帶的功率譜(power spectra)、Hjorth 參數(shù)和其他一些特定的統(tǒng)計學(xué)特征。本文簡要地介紹了使用 CNN 和 LSTM 實現(xiàn)序列分類的方法,詳細(xì)代碼請查看 Github。
Github 項目地址:https://github.com/healthDataScience/deep-learning-HAR
傳統(tǒng)圖像分類中也是采用的手動特征工程,然而隨著深度學(xué)習(xí)的出現(xiàn),卷積神經(jīng)網(wǎng)絡(luò)已經(jīng)可以較為***地處理計算機(jī)視覺任務(wù)。使用 CNN 處理圖像不需要任何手動特征工程,網(wǎng)絡(luò)會一層層自動從最基本的特征組合成更加高級和抽象的特征,從而完成計算機(jī)視覺任務(wù)。
在本文中,我們將討論如何使用深度學(xué)習(xí)方法對時序數(shù)據(jù)進(jìn)行分類。我們使用的案例是 UCI 項目中的人體活動識別(HAR)數(shù)據(jù)集。該數(shù)據(jù)集包含原始的時序數(shù)據(jù)和經(jīng)預(yù)處理的數(shù)據(jù)(包含 561 個特征)。本文將對比用特征工程的機(jī)器學(xué)習(xí)算法和兩種深度學(xué)習(xí)方法(卷積神經(jīng)網(wǎng)絡(luò)和循環(huán)神經(jīng)網(wǎng)絡(luò)),試驗***表明深度學(xué)習(xí)方法超越了傳統(tǒng)使用特征工程的方法。
作者使用 TensorFlow 和實現(xiàn)并訓(xùn)練模型,文中只展示了部分代碼,更詳細(xì)的代碼請查看 Github。
卷積神經(jīng)網(wǎng)絡(luò)(CNN)
首先***步就是將數(shù)據(jù)饋送到 Numpy 中的數(shù)組,且數(shù)組的維度為 (batch_size, seq_len, n_channels),其中 batch_size 為模型在執(zhí)行 SGD 時每一次迭代需要的數(shù)據(jù)量,seq_len 為時序序列的長度(本文中為 128),n_channels 為執(zhí)行檢測(measurement)的通道數(shù)。本文案例中通道數(shù)為 9,即 3 個坐標(biāo)軸每一個有 3 個不同的加速檢測
(acceleration measurement)。我們有六個活動標(biāo)簽,即每一個樣本屬于 LAYING、STANDING、SITTING、WALKING_DOWNSTAIRS、WALKING_UPSTAIRS 或 WALKING。
下面,我們首先構(gòu)建計算圖,其中我們使用占位符為輸入數(shù)據(jù)做準(zhǔn)備:
- graph = tf.Graph()
- with graph.as_default():
- inputs_ = tf.placeholder(tf.float32, [None, seq_len, n_channels],
- name = 'inputs')
- labels_ = tf.placeholder(tf.float32, [None, n_classes], name = 'labels')
- keep_prob_ = tf.placeholder(tf.float32, name = 'keep')
- learning_rate_ = tf.placeholder(tf.float32, name = 'l
其中 inputs_是饋送到計算圖中的輸入張量,***個參數(shù)設(shè)置為「None」可以確保占位符***個維度可以根據(jù)不同的批量大小而適當(dāng)調(diào)整。labels_是需要預(yù)測的 one-hot 編碼標(biāo)簽,keep_prob_為用于 dropout 正則化的保持概率,learning_rate_ 為用于 Adam 優(yōu)化器的學(xué)習(xí)率。
我們使用在序列上移動的 1 維卷積核構(gòu)建卷積層,圖像一般使用的是 2 維卷積核。序列任務(wù)中的卷積核可以充當(dāng)為訓(xùn)練中的濾波器。在許多 CNN 架構(gòu)中,層級的深度越大,濾波器的數(shù)量就越多。每一個卷積操作后面都跟隨著池化層以減少序列的長度。下面是我們可以使用的簡單 CNN 架構(gòu)。
上圖描述的卷積層可用以下代碼實現(xiàn):
- with graph.as_default():
- # (batch, 128, 9) -> (batch, 32, 18)
- conv1 = tf.layers.conv1d(inputs=inputs_, filters=18, kernel_size=2, strides=1,
- padding='same', activation = tf.nn.relu)
- max_pool_1 = tf.layers.max_pooling1d(inputs=conv1, pool_size=4, strides=4, padding='same')
- # (batch, 32, 18) -> (batch, 8, 36)
- conv2 = tf.layers.conv1d(inputs=max_pool_1, filters=36, kernel_size=2, strides=1,
- padding='same', activation = tf.nn.relu)
- max_pool_2 = tf.layers.max_pooling1d(inputs=conv2, pool_size=4, strides=4, padding='same')
- # (batch, 8, 36) -> (batch, 2, 72)
- conv3 = tf.layers.conv1d(inputs=max_pool_2, filters=72, kernel_size=2, strides=1,
- padding='same', activation = tf.nn.relu)
- max_pool_3 = tf.layers.max_pooling1d(inputs=conv3, po
一旦到達(dá)了***一層,我們需要 flatten 張量并投入到有適當(dāng)神經(jīng)元數(shù)的分類器中,在上圖中為 144 個神經(jīng)元。隨后分類器輸出 logits,并用于以下兩種案例:
- 計算 softmax 交叉熵函數(shù),該損失函數(shù)在多類別問題中是標(biāo)準(zhǔn)的損失度量。
- 在***化概率和準(zhǔn)確度的情況下預(yù)測類別標(biāo)簽。
下面是上述過程的實現(xiàn):
- with graph.as_default():
- # Flatten and add dropout
- flat = tf.reshape(max_pool_3, (-1, 2*72))
- flat = tf.nn.dropout(flat, keep_prob=keep_prob_)
- # Predictions
- logits = tf.layers.dense(flat, n_classes)
- # Cost function and optimizer
- cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logitslogits=logits,
- labels=labels_)) optimizer = tf.train.AdamOptimizer(learning_rate_).minimize(cost)
- # Accuracy
- correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax
剩下的實現(xiàn)部分就比較典型了,讀者可查看 GitHub 中的完整代碼和過程。前面我們已經(jīng)構(gòu)建了計算圖,后面就需要將批量訓(xùn)練數(shù)據(jù)饋送到計算圖進(jìn)行訓(xùn)練,同時我們還要使用驗證集來評估訓(xùn)練結(jié)果。***,完成訓(xùn)練的模型將在測試集上進(jìn)行評估。我們在該實驗中 batch_siza 使用的是 600、learning_rate 使用的是 0.001、keep_prob 為 0.5。在 500 個 epoch 后,我們得到的測試精度為 98%。下圖顯示了訓(xùn)練準(zhǔn)確度和驗證準(zhǔn)確度隨 epoch 的增加而顯示的變化:
長短期記憶網(wǎng)絡(luò)(LSTM)
LSTM 在處理文本數(shù)據(jù)上十分流行,它在情感分析、機(jī)器翻譯、和文本生成等方面取得了十分顯著的成果。因為本問題涉及相似分類的序列,所以 LSTM 是比較優(yōu)秀的方法。
下面是能用于該問題的神經(jīng)網(wǎng)絡(luò)架構(gòu):
為了將數(shù)據(jù)饋送到網(wǎng)絡(luò)中,我們需要將數(shù)組分割為 128 塊(序列中的每一塊都會進(jìn)入一個 LSTM 單元),每一塊的維度為(batch_size, n_channels)。隨后單層神經(jīng)元將轉(zhuǎn)換這些輸入并饋送到 LSTM 單元中,每一個 LSTM 單元的維度為 lstm_size,一般該參數(shù)需要選定為大于通道數(shù)量。這種方式很像文本應(yīng)用中的嵌入層,其中詞匯從給定的詞匯表中嵌入為一個向量。后面我們需要選擇 LSTM 層的數(shù)量(lstm_layers),我們可以設(shè)定為 2。
對于這一個實現(xiàn),占位符的設(shè)定可以和上面一樣。下面的代碼段實現(xiàn)了 LSTM 層級:
- with graph.as_default():
- # Construct the LSTM inputs and LSTM cells
- lstm_in = tf.transpose(inputs_, [1,0,2]) # reshape into (seq_len, N, channels)
- lstm_in = tf.reshape(lstm_in, [-1, n_channels]) # Now (seq_len*N, n_channels)
- # To cells
- lstm_in = tf.layers.dense(lstm_in, lstm_size, activation=None)
- # Open up the tensor into a list of seq_len pieces
- lstm_in = tf.split(lstm_in, seq_len, 0)
- # Add LSTM layers
- lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)
- drop = tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob_)
- cell = tf.contrib.rnn.MultiRNNCell([drop] * lstm_layers)
- initial_state = cell.zero_state(batch_size, tf.floa
上面的代碼段是十分重要的技術(shù)細(xì)節(jié)。我們首先需要將數(shù)組從 (batch_size, seq_len, n_channels) 重建維度為 (seq_len, batch_size, n_channels),因此 tf.split 將在每一步適當(dāng)?shù)胤指顢?shù)據(jù)(根據(jù)第 0 個索引)為一系列 (batch_size, lstm_size) 數(shù)組。剩下的部分就是標(biāo)準(zhǔn)的 LSTM 實現(xiàn)了,包括構(gòu)建層級和初始狀態(tài)。
下一步就是實現(xiàn)網(wǎng)絡(luò)的前向傳播和成本函數(shù)。比較重要的技術(shù)點是我們引入了梯度截斷,因為梯度截斷可以在反向傳播中防止梯度爆炸而提升訓(xùn)練效果。
下面是我們定義前向傳播和成本函數(shù)的代碼:
- with graph.as_default():
- outputs, final_state = tf.contrib.rnn.static_rnn(cell, lstm_in, dtype=tf.float32,
- initial_stateinitial_state = initial_state)
- # We only need the last output tensor to pass into a classifier
- logits = tf.layers.dense(outputs[-1], n_classes, name='logits')
- # Cost function and optimizer
- cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logitslogits=logits, labels=labels_))
- # Grad clipping
- train_op = tf.train.AdamOptimizer(learning_rate_)
- gradients = train_op.compute_gradients(cost) capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients] optimizer = train_op.apply_gradients(capped_gradients)
- # Accuracy
- correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax
注意我們只使用了 LSTM 頂層輸出序列的***一個元素,因為我們每個序列只是嘗試預(yù)測一個分類概率。剩下的部分和前面我們訓(xùn)練 CNN 的過程相似,我們只需要將數(shù)據(jù)饋送到計算圖中進(jìn)行訓(xùn)練。其中超參數(shù)可選擇為 lstm_size=27、lstm_layers=2、batch_size=600、learning_rate=0.0005 和 keep_prob=0.5,我們在測試集中可獲得大約 95% 的準(zhǔn)確度。這一結(jié)果要比 CNN 還差一些,但仍然十分優(yōu)秀??赡苓x擇其它超參數(shù)能產(chǎn)生更好的結(jié)果,讀者朋友也可以在 Github 中獲取源代碼并進(jìn)一步調(diào)試。
對比傳統(tǒng)方法
前面作者已經(jīng)使用帶 561 個特征的數(shù)據(jù)集測試了一些機(jī)器學(xué)習(xí)方法,性能***的方法是梯度提升樹,如下梯度提升樹的準(zhǔn)確度能到達(dá) 96%。雖然 CNN、LSTM 架構(gòu)與經(jīng)過特征工程的梯度提升樹的精度差不多,但 CNN 和 LSTM 的人工工作量要少得多。
HAR 任務(wù)經(jīng)典機(jī)器學(xué)習(xí)方法:
https://github.com/bhimmetoglu/talks-and-lectures/tree/master/MachineLearning/HAR
梯度提升樹:https://rpubs.com/burakh/har_xgb
結(jié)語
在本文中,我們試驗了使用 CNN 和 LSTM 進(jìn)行時序數(shù)據(jù)的分類,這兩種方法在性能上都有十分優(yōu)秀的表現(xiàn),并且最重要的是它們在訓(xùn)練中會一層層學(xué)習(xí)獨(dú)特的特征,它們不需要成本昂貴的特征工程。
本文所使用的序列還是比較小的,只有 128 步??赡軙凶x者懷疑如果序列變得更長(甚至大于 1000),是不是訓(xùn)練就會變得十分困難。其實我們可以結(jié)合 LSTM 和 CNN 在這種長序列任務(wù)中表現(xiàn)得更好??偟膩碚f,深度學(xué)習(xí)方法相對于傳統(tǒng)方法有非常明顯的優(yōu)勢。
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】