時間序列分類04:如何開發LSTMs模型實現人類活動識別(CNN-LSTM、ConvLSTM)

本文介紹了三種用於時間序列分類任務的網絡架構,包括:LSTM、CNN-LSTM、ConvLSTM,並使用這些網絡架構應用於業內標準的數據集UCI-HAR-Dataset進行人類活動識別。



1. LSTM 模型

在本節中,我們將爲人類活動識別數據集開發一個LSTM模型。 LSTM 是一種遞歸神經網絡,能夠學習和記住長序列的輸入數據。它們旨在與包含較長數據序列(最長200到400個時間步長)的數據一起使用,它們可能很適合此問題。該模型可以支持輸入數據的多個並行序列,例如加速度計和陀螺儀數據的每個軸。該模型學習從觀測序列中提取特徵,以及將內部特徵映射到不同的活動類型。

使用LSTM進行序列分類的好處是可以直接從原始時間序列數據中學習,而無需學習專業知識來做特徵工程。該模型可以學習時間序列數據的內部表示,並且理想情況下可以達到與經過特徵工程處理的數據集版本匹配的模型可比的性能。

1.1 模型定義

我們將模型定義爲具有單隱層的LSTM模型。接下來是一個dropout層,旨在減少模型對訓練數據的過擬合。最後在使用輸出層進行預測之前,使用全連接層來解釋由LSTM隱藏層提取的特徵。對於多類分類任務,使用隨機梯度下降Adam來優化網絡,並使用分類交叉熵損失函數。模型定義如下:

model = Sequential()
model.add(LSTM(100, input_shape=(n_timesteps,n_features)))
model.add(Dropout(0.5))
model.add(Dense(100, activation='relu'))
model.add(Dense(n_outputs, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

在這種情況下,合適的epoch爲15,batch_size=64,在更新模型權重之前將64個數據窗口輸入模型。模型擬合後,在測試數據集上對其進行評估,並返回測試數據集上的擬合模型的準確性。注意,在擬合LSTM時通常不對序列數據進行 shuffle 操作。但在此模型中,我們會在訓練期間隨機調整輸入數據的窗口(默認設置)。在這個問題中,我們感興趣的是利用LSTMs的能力來學習和提取一個窗口中跨時間步長的特性,而不是跨窗口的特性。


1.2 模型評估

我們不能從單一的評價來判斷模型的技能。這是因爲神經網絡是隨機的,這意味着在相同的數據上訓練相同的模型配置時,會產生不同的模型。這是網絡的一個特徵,因爲它賦予了模型自適應能力,但需要對模型進行稍微複雜的評估。我們將多次重複對模型的評估,然後總結每次運行時模型的性能。代碼實現:

def summarize_results(scores, params):
    print(scores, params)
    # 總結均值和標準差
    for i in range(len(scores)):
        m, s = np.mean(scores[i]), np.std(scores[i])
        print('Param=%s: %.3f%% (+/-%.3f)' % (params[i], m, s))

def run_experiment(trainX, trainy, testX, testy, repeats=10):
    # one-hot編碼
    # 這個之前的文章中提到了,因爲原數據集標籤從1開始,而one-hot編碼從0開始,所以要先減去1
    trainy = to_categorical(trainy-1)
    testy = to_categorical(testy-1)
    
    scores = []
    for r in range(repeats):
        score = evaluate_model(trainX, trainy, testX, testy)
        score = score * 100.0
        print('>#%d: %.3f' % (r+1, score))
        scores.append(score)
        print(scores)
    
    mean_scores, std_scores = np.mean(scores), np.std(scores)
    print('Accuracy: %.3f%% (+/-%.3f)' % (mean_scores, std_scores))

1.3 完整代碼:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.layers import LSTM
from tensorflow.keras.utils import to_categorical

def load_file(filepath):
    dataframe = pd.read_csv(filepath, header=None, delim_whitespace=True)
    return dataframe.values

def load_dataset(data_rootdir, dirname, group):
    '''
    該函數實現將訓練數據或測試數據文件列表堆疊爲三維數組
    '''
    filename_list = []
    filepath_list = []
    X = []
    
    # os.walk() 方法是一個簡單易用的文件、目錄遍歷器,可以高效的處理文件、目錄。
    for rootdir, dirnames, filenames in os.walk(data_rootdir + dirname):
        for filename in filenames:
            filename_list.append(filename)
            filepath_list.append(os.path.join(rootdir, filename))
        #print(filename_list)
        #print(filepath_list)
    
    # 遍歷根目錄下的文件,並讀取爲DataFrame格式;
    for filepath in filepath_list:
        X.append(load_file(filepath))
    
    X = np.dstack(X) # dstack沿第三個維度疊加,兩個二維數組疊加後,前兩個維度尺寸不變,第三個維度增加;
    y = load_file(data_rootdir+'/y_'+group+'.txt')
    # one-hot編碼。這個之前的文章中提到了,因爲原數據集標籤從1開始,而one-hot編碼從0開始,所以要先減去1
    y = to_categorical(y-1)
    print('{}_X.shape:{},{}_y.shape:{}\n'.format(group,X.shape,group,y.shape))
    return X, y


def evaluate_model(trainX, trainy, testX, testy):
    verbose, epochs, batch_size = 0, 15, 64
    n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1]
    
    model = Sequential()
    model.add(LSTM(100, input_shape=(n_timesteps,n_features)))
    model.add(Dropout(0.5))
    model.add(Dense(100, activation='relu'))
    model.add(Dense(n_outputs, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose)

    _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0)
    return accuracy
        

def run_experiment(trainX, trainy, testX, testy, repeats=10):

    scores = list()
    for r in range(repeats):
        score = evaluate_model(trainX, trainy, testX, testy)
        score = score * 100.0
        print('>#%d: %.3f' % (r+1, score))
        scores.append(score)
    
    m, s = np.mean(scores), np.std(scores)
    print('Accuracy: %.3f%% (+/-%.3f)' % (m, s))

if __name__ == '__main__':
	train_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/train/'
	test_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/test/'
	dirname = '/Inertial Signals/'
	trainX, trainy = load_dataset(train_dir, dirname, 'train')
	testX, testy = load_dataset(test_dir, dirname, 'test')

	run_experiment(trainX, trainy, testX, testy, repeats=10)

首先運行示例將加載數據集。將創建並評估模型,併爲每個模型打印調試信息。最後,打印分數樣本,然後打印均值和標準差。可以看到該模型運行良好,在原始數據集上訓練的分類精度約爲89.7%,標準偏差約爲1.3。考慮到原始論文發表了89%的結果,該結果是在具有特定領域特徵的重型工程數據集而不是原始數據集上進行訓練的,因此,這是一個很好的結果。

train_X.shape:(7352, 128, 9),train_y.shape:(7352, 6)

test_X.shape:(2947, 128, 9),test_y.shape:(2947, 6)
>#1: 90.770
>#2: 90.804
>#3: 88.768
>#4: 88.870
>#5: 90.227
>#6: 87.615
>#7: 91.313
>#8: 87.479
>#9: 91.144
>#10: 90.092
>Accuracy: 89.708% (+/-1.354)

2. CNN-LSTM Model

2.1 數據輸入shape

CNN-LSTM體系結構涉及使用卷積神經網絡(CNN)層對LSTM的輸入數據進行特徵提取,以支持序列預測。關於使用CNN-LSTM模型進行時間序列預測的內容,在之前的文章中已經介紹過了,此處不再贅述。CNN-LSTM模型將以塊的形式讀取主序列的子序列,從每個塊中提取特徵,然後允許LSTM解釋特徵從每個塊中提取。一種實現此模型的方法是將128個時間步長的每個窗口劃分爲子序列,以供CNN模型處理。例如,每個窗口中的128個時間步長可以分爲時間步長爲32的四個子序列。 代碼實現:

# 將數據重塑爲子序列
n_steps, n_length = 4, 32
trainX = trainX.reshape((trainX.shape[0], n_steps, n_length, n_features))
testX = testX.reshape((testX.shape[0], n_steps, n_length, n_features)

2.2 模型定義

然後,我們可以定義一個CNN模型,該模型期望以32個時間步長和9個特徵的長度順序讀取。整個CNN模型可以包裝在TimeDistributed層中,以允許在窗口的四個子序列中的每一箇中讀取相同的CNN模型。然後將提取的特徵展平並提供給LSTM模型以進行讀取,在對活動進行最終分類之前提取其自身的特徵。模型定義如下:

model = Sequential()
model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'),
input_shape=(None,n_length,n_features)))
model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu')))
model.add(TimeDistributed(Dropout(0.5)))
model.add(TimeDistributed(MaxPooling1D(pool_size=2)))
model.add(TimeDistributed(Flatten()))
model.add(LSTM(100))
model.add(Dropout(0.5))
model.add(Dense(100, activation='relu'))
model.add(Dense(n_outputs, activation='softmax'))

2.3 完整代碼

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Conv1D, MaxPooling1D
from tensorflow.keras.layers import LSTM, TimeDistributed, ConvLSTM2D
from tensorflow.keras.utils import to_categorical

def load_file(filepath):
    dataframe = pd.read_csv(filepath, header=None, delim_whitespace=True)
    return dataframe.values

def load_dataset(data_rootdir, dirname, group):
    '''
    該函數實現將訓練數據或測試數據文件列表堆疊爲三維數組
    '''
    filename_list = []
    filepath_list = []
    X = []
    
    # os.walk() 方法是一個簡單易用的文件、目錄遍歷器,可以高效的處理文件、目錄。
    for rootdir, dirnames, filenames in os.walk(data_rootdir + dirname):
        for filename in filenames:
            filename_list.append(filename)
            filepath_list.append(os.path.join(rootdir, filename))
        #print(filename_list)
        #print(filepath_list)
    
    # 遍歷根目錄下的文件,並讀取爲DataFrame格式;
    for filepath in filepath_list:
        X.append(load_file(filepath))
    
    X = np.dstack(X) # dstack沿第三個維度疊加,兩個二維數組疊加後,前兩個維度尺寸不變,第三個維度增加;
    y = load_file(data_rootdir+'/y_'+group+'.txt')
    # one-hot編碼。這個之前的文章中提到了,因爲原數據集標籤從1開始,而one-hot編碼從0開始,所以要先減去1
    y = to_categorical(y-1)
    print('{}_X.shape:{},{}_y.shape:{}\n'.format(group,X.shape,group,y.shape))
    return X, y


def evaluate_model(trainX, trainy, testX, testy):
    verbose, epochs, batch_size = 0, 25, 64
    n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1]

    n_steps, n_length = 4, 32
    trainX = trainX.reshape((trainX.shape[0], n_steps, n_length, n_features))
    testX = testX.reshape((testX.shape[0], n_steps, n_length, n_features))

    model = Sequential()
    model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu'), 
                              input_shape=(None, n_length, n_features)))
    model.add(TimeDistributed(Conv1D(filters=64, kernel_size=3, activation='relu')))
    model.add(TimeDistributed(Dropout(0.5)))
    model.add(TimeDistributed(MaxPooling1D(pool_size=2)))
    model.add(TimeDistributed(Flatten()))
    model.add(LSTM(100))
    model.add(Dropout(0.5))
    model.add(Dense(100, activation='relu'))
    model.add(Dense(n_outputs, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose)

    _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0)
    return accuracy
        

def run_experiment(trainX, trainy, testX, testy, repeats=10):

    scores = list()
    for r in range(repeats):
        score = evaluate_model(trainX, trainy, testX, testy)
        score = score * 100.0
        print('>#%d: %.3f' % (r+1, score))
        scores.append(score)
    
    m, s = np.mean(scores), np.std(scores)
    print('Accuracy: %.3f%% (+/-%.3f)' % (m, s))

if __name__ == '__main__':
	train_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/train/'
	test_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/test/'
	dirname = '/Inertial Signals/'
	trainX, trainy = load_dataset(train_dir, dirname, 'train')
	testX, testy = load_dataset(test_dir, dirname, 'test')

	run_experiment(trainX, trainy, testX, testy, repeats=2)

運行示例將對2個運行中的每個運行的模型性能進行彙總,然後再報告測試集上模型性能的最終摘要。我們可以看到,該模型的性能約爲90.5%,標準偏差約爲0.37%。

train_X.shape:(7352, 128, 9),train_y.shape:(7352, 6)

test_X.shape:(2947, 128, 9),test_y.shape:(2947, 6)

>#1: 90.126
>#2: 90.872
Accuracy: 90.499% (+/-0.373)

3. ConvLSTM 模型

CNN-LSTM思想的進一步擴展是執行CNN的卷積(例如CNN如何讀取輸入序列數據)作爲LSTM的一部分。這種組合稱爲卷積LSTM,簡稱ConvLSTM,像CNN-LSTM一樣也用於時空數據。默認情況下,ConvLSTM2D 類要求數據輸入的shape爲:[樣本,時間步長,行,列,通道]([samples, time, rows, cols, channels])。其中數據的每個時間步均定義爲 (行×列) 數據點的圖像。


3.1 數據輸入shape

在上一節中,將給定的數據窗口(128個時間步長)分爲時間步長爲32的四個子序列。可以使用相同的子序列方法來定義ConvLSTM2D輸入,其中時間步是窗口中子序列的數目,在處理一維數據時,行數是1,而列數表示子序列中的時間步數,在這種情況下爲32。對於該問題框架,ConvLSTM2D的輸入爲:

  • 樣本(samples):n,表示數據集中的窗口數。
  • 時間(time):4,將一個有128個時間步的窗口分成四個子序列。
  • 行(rows):1,表示每個子序列的一維形狀。
  • 列(columns):32,輸入子序列中的32個時間步。
  • 通道(channels):9,九個輸入變量(特徵,九軸傳感數據)。

代碼實現:

n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1]
# 重塑爲子序列 (samples, timesteps, rows, cols, channels)
n_steps, n_length = 4, 32
trainX = trainX.reshape((trainX.shape[0], n_steps, 1, n_length, n_features))
testX = testX.reshape((testX.shape[0], n_steps, 1, n_length, n_features))

ConvLSTM2D類需要在CNN和LSTM方面進行配置。這包括指定過濾器的數量(例如64),在這種情況下(子序列時間步長的1行和3列)的二維核大小,relu激活函數。與CNN或LSTM模型一樣,必須先將輸出展平爲一個長向量,然後才能通過全連接層對其進行解釋。


3.2 完整代碼

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.layers import LSTM, TimeDistributed, ConvLSTM2D
from tensorflow.keras.utils import to_categorical

def load_file(filepath):
    dataframe = pd.read_csv(filepath, header=None, delim_whitespace=True)
    return dataframe.values

def load_dataset(data_rootdir, dirname, group):
    '''
    該函數實現將訓練數據或測試數據文件列表堆疊爲三維數組
    '''
    filename_list = []
    filepath_list = []
    X = []
    
    # os.walk() 方法是一個簡單易用的文件、目錄遍歷器,可以高效的處理文件、目錄。
    for rootdir, dirnames, filenames in os.walk(data_rootdir + dirname):
        for filename in filenames:
            filename_list.append(filename)
            filepath_list.append(os.path.join(rootdir, filename))
        #print(filename_list)
        #print(filepath_list)
    
    # 遍歷根目錄下的文件,並讀取爲DataFrame格式;
    for filepath in filepath_list:
        X.append(load_file(filepath))
    
    X = np.dstack(X) # dstack沿第三個維度疊加,兩個二維數組疊加後,前兩個維度尺寸不變,第三個維度增加;
    y = load_file(data_rootdir+'/y_'+group+'.txt')
    # one-hot編碼。這個之前的文章中提到了,因爲原數據集標籤從1開始,而one-hot編碼從0開始,所以要先減去1
    y = to_categorical(y-1)
    print('{}_X.shape:{},{}_y.shape:{}\n'.format(group,X.shape,group,y.shape))
    return X, y


def evaluate_model(trainX, trainy, testX, testy):
    verbose, epochs, batch_size = 0, 25, 64
    n_timesteps, n_features, n_outputs = trainX.shape[1], trainX.shape[2], trainy.shape[1]

    n_steps, n_length = 4, 32

    trainX = trainX.reshape((trainX.shape[0], n_steps, 1, n_length, n_features))
    testX = testX.reshape((testX.shape[0], n_steps, 1, n_length, n_features))

    model = Sequential()
    model.add(ConvLSTM2D(filters=64, kernel_size=(1,3), activation='relu', input_shape=(n_steps, 1, n_length, n_features)))
    model.add(Dropout(0.5))
    model.add(Flatten())
    model.add(Dense(100, activation='relu'))
    model.add(Dense(n_outputs, activation='softmax'))
    
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    model.fit(trainX, trainy, epochs=epochs, batch_size=batch_size, verbose=verbose)

    _, accuracy = model.evaluate(testX, testy, batch_size=batch_size, verbose=0)
    return accuracy
        

def run_experiment(trainX, trainy, testX, testy, repeats=10):

    scores = list()
    for r in range(repeats):
        score = evaluate_model(trainX, trainy, testX, testy)
        score = score * 100.0
        print('>#%d: %.3f' % (r+1, score))
        scores.append(score)
    
    m, s = np.mean(scores), np.std(scores)
    print('Accuracy: %.3f%% (+/-%.3f)' % (m, s))

if __name__ == '__main__':
	train_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/train/'
	test_dir = 'D:/GraduationCode/01 Datasets/UCI HAR Dataset/test/'
	dirname = '/Inertial Signals/'
	trainX, trainy = load_dataset(train_dir, dirname, 'train')
	testX, testy = load_dataset(test_dir, dirname, 'test')

	run_experiment(trainX, trainy, testX, testy, repeats=2)

與先前的實驗一樣,運行模型會在每次擬合和評估時打印出模型的性能。運行結束時將提供最終模型性能的摘要。可以看到,該模型在問題上始終表現良好,可達到約90.7%的準確度,而且可能比CNN-LSTM模型消耗的資源更少。

train_X.shape:(7352, 128, 9),train_y.shape:(7352, 6)

test_X.shape:(2947, 128, 9),test_y.shape:(2947, 6)

>#1: 90.159
>#2: 91.347
Accuracy: 90.753% (+/-0.594)

4. 拓展

針對時間序列分類任務,還可以做如下探索:

  • 數據準備。考慮研究簡單的數據縮放方案是否可以進一步提高模型性能,例如歸一化,標準化。
  • LSTM變體。 LSTM體系結構的各種變體可以在此問題上獲得更好的性能,例如堆疊的LSTM和雙向LSTM。
  • 超參數調整。考慮研究模型超參數的調整,例如units,epochs,batch_size等。

參考:
參考1
參考2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章