时间序列预测-基于LSTM-CNN的人体活动识别

发布时间 2023-06-30 19:36:00作者: 哎呦哎(iui)

本文主要利用LSTM和CNN来处理移动传感器产生的数据识别人类活动。

传感器数据集

数据组成

这个项目使用了 WISDM (Wireless Sensor Data Mining) Lab 实验室公开的 Actitracker 的数据集
image
其中数据:
测试记录:1,098,207 条
行为类型:6 种

  • 走路
  • 慢跑
  • 上楼梯
  • 下楼梯
  • 站立

传感器类型:加速度
测试场景:手机放在衣兜里面

WISDM 公开了两个数据集,一个是在实验室环境采集的;另一个是在真实使用场景中采集的,这里使用的是实验室环境采集的数据。

数据分析

其中数据又6中数据行为:
Walking -> 2,082 -> 38.4%,
Jogging -> 1,626 -> 30.0%,
Upstairs -> 633 -> 11.7%,
Downstairs -> 529 -> 9.8%,
Sitting -> 307 -> 5.7%,
Standing -> 247 -> 4.6%,
数据组成:
image
Raw.txt遵循以下格式:
[user-id],[activity],[timestamp],[x-acceleration],[y-accel],[z-accel]

  • user-id 是用户的id:数据范围是[1..36]
  • activity 是用户的行为,包括[Walking,Jogging,Upstairs,Downstairs,Sitting,Standing]
  • timestamp 是用户手机里面的时间戳
  • x-acceleration 是手机x轴的加速度,数值介于[-20,20]的浮点数
  • y-accel 类似于x-accel
  • z-accel 类似于z-accel

读取数据

column_names =['user-id', 'activity', 'timestamp', 'X', 'Y', 'Z']
df = pd.read_csv('Dataset/WISDM_ar_v1.1/WISDM_ar_v1.1_raw.txt',header=None, names=column_names) 
df.head()

我们发现如果用上面的代码处理的话,会出现最后一行"Z"多了一个';'.
image
所以我们读取完了之后要处理一下

#raw.txt follows this format:
#[user],[activity],[timestamp],[x-accel],[y-accel],[z-accel];

def read_data(filepath): 
    df = read_csv(filepath,header=None, names=['user-id', 
                                               'activity', 
                                               'timestamp', 
                                               'X', 
                                               'Y', 
                                               'Z']) 
    ## removing ';' from last column and converting it to float 
    df['Z'].replace(regex=True, inplace=True, to_replace=r';', value=r'') 
    df['Z'] = df['Z'].apply(convert_to_float) 
    return df 
 
def convert_to_float(x): 
    try: 
        return np.float64(x) 
    except: 
        return np.nan 
 
df = read_data('Dataset/WISDM_ar_v1.1/WISDM_ar_v1.1_raw.txt') 
df.head()

image

数据可视化分析

plt.figure(figsize=(15, 5)) 
 
plt.xlabel('Activity Type') 
plt.ylabel('Training examples') 
df['activity'].value_counts().plot(kind='bar', 
                                  title='Training examples by Activity Types') 
plt.show() 
 
plt.figure(figsize=(15, 5)) 
plt.xlabel('User') 
plt.ylabel('Training examples') 
df['user-id'].value_counts().plot(kind='bar',  
                                 title='Training examples by user') 
plt.show()

image
下面是我们将收集的三个轴上的加速度计数据进行可视化。

def axis_plot(ax, x, y, title): 
    ax.plot(x, y, 'r') 
    ax.set_title(title) 
    ax.xaxis.set_visible(False) 
    ax.set_ylim([min(y) - np.std(y), max(y) + np.std(y)]) 
    ax.set_xlim([min(x), max(x)]) 
    ax.grid(True) 
 
for activity in df['activity'].unique(): 
    limit = df[df['activity'] == activity][:180] 
    fig, (ax0, ax1, ax2) = plt.subplots(nrows=3, sharex=True, figsize=(15, 10)) 
    axis_plot(ax0, limit['timestamp'], limit['X'], 'x-axis') 
    axis_plot(ax1, limit['timestamp'], limit['Y'], 'y-axis') 
    axis_plot(ax2, limit['timestamp'], limit['Z'], 'z-axis') 
    plt.subplots_adjust(hspace=0.2) 
    fig.suptitle(activity) 
    plt.subplots_adjust(top=0.9) 
    plt.show()

image
image
image
image
image
image

数据预处理

其中包括:

  • 标签编码
  • 线性插值
  • 数据分割
  • 归一化
  • 时间序列分割
  • 独热编码

标签编码

因为我们的模型不接收数字标签,作为输入。由于我们是对这些行文进行预测,我们的预测结果可以是这些行为对应的数字标签。所以我们在原始数据中添加一列'activityEncode',来对应我们的每个行为。便于预测。这里有点类似于哈希,就是把每一个类别哈希成一个数字
Downstairs [0]
Jogging [1]
Sitting [2]
Standing [3]
Upstairs [4]
Walking [5]
这里遇到的API:是sklearn.preprocessing中的fit_transfrom ,官网

from sklearn.preprocessing import LabelEncoder
la = LabelEncoder()
la.fit([,,,])

Fit label encoder and return encoded labels.
Parameters:
yarray-like of shape (n_san_samples,)
Target values.
Returns:
yarray-like of shape (n_samples,)
Encoded labels.

例如:

from sklearn import preprocessing
le = preprocessing.LabelEncoder()
le.fit_transform(["tokyo", "tokyo", "paris"])

image

label_encode = LabelEncoder()
df['activityEncode'] = label_encode.fit_transform(df['activity'])
df

image

线性插值

首先查看是否有缺失值
利用线性插值可以避免采集过程中出现NaN的数据丢失的问题。它将通过插值法填充缺失的值。虽然在这个数据集中只有一个NaN值。
查看是否有缺失值:

#查看是否有缺失值
df.isnull().any(axis=0)
#定位缺失值所在的行
df.loc[df.isnull().any(axis=1)]
#统计各变量中的缺失值的数量
df.isnull().sum(axis=0)

image
线性插值,或者我们可以直接把这一行的数据删除掉,因为我我们的数据很多

interpolation_fn = interp1d(df['activityEncode'] ,df['Z'], kind='linear')
null_list = df[df['Z'].isnull()].index.tolist() 
null_list

image

for i in null_list: 
    y = df['activityEncode'][i] 
    value = interpolation_fn(y) 
    df['Z']=df['Z'].fillna(value) 
    print(value)

image

数据分割

这里我们可以用sklearn进行分割,或者我们可以直接根据user-id进行分割。

#根据用户id进行数据分割,避免数据分割错误。我们在训练集中使用id小于或等于27的用户,其余的在测试集中使用。
df_test = df[df['user-id'] > 27]
df_train = df[df['user-id'] <= 27]
df_train.shape,df_test.shape

image

数据无量纲化,归一化

我们这里用到的是:
\(X_i\)=\(\frac{X_i-X_{min}}{X_{max}-X_{min}}\)

#在训练之前,需要将数据特征归一化到0到1的范围内。我们用的方法是
df_train['X'] = (df_train['X']-df_train['X'].min())/(df_train['X'].max()-df_train['X'].min()) 
df_train['Y'] = (df_train['Y']-df_train['Y'].min())/(df_train['Y'].max()-df_train['Y'].min()) 
df_train['Z'] = (df_train['Z']-df_train['Z'].min())/(df_train['Z'].max()-df_train['Z'].min()) 
df_train

image

时间序列分割

先说说我自己的理解,首先我们需要用到的数据是'X','Y','Z'和'activityEncode'这四个数据。然后我们training的肯定是'X','Y','Z'上的数据。我们预测行为的时候不是一个时刻的行为,而是一段时间序列的行为。也就是根据一段序列(比如说3秒或者5个数据点)的'X','Y','Z'数据来预测这个一段序列的行为,所以我们需要进行时间序列分割。然后我们每个序列中的标签就是这个一段序列中的行为的众数。也就是这个一段序列中最多的一个行为,就作为这一段序列的行为。
这个我们分割完是一个[b,time_steps,n_features],其中这个n_features是'X','Y','Z'三个特征。
这里我们在处理的时候用到了滑动窗口的知识。我们这里在划分每个序列的时侯,这个序列和下一个序列会有重复的,就像下面一样。
image
我们最重要得到的是[b,time_steps,n_features],这个b是一共有多少个。time_steps是处理好的序列中有多少个时间点,n_features是一共有多少个特征。
我们还需要设置一个值就是step,就是我们每隔几个点来取一个值,也就是上一个和下一个重复的有time_steps-step个时间点。然后这一段序列中的行为就用这一段的行为标签中的众数来代替。

比如我们用time_steps=5,step=2。我们总的数据有9个[0,9)。
第一个就是[0,5):0,1,2,3,4
第二个就是[2,7): 2, 3, 4, 5, 6
第三个就是[4,9): 4, 5, 6, 7, 8

测试:

#这里我们设置time_steps=5,step=2,测试一下
def segments(df, time_steps, step, label_name): 
    N_FEATURES = 3 
    segments = [] 
    labels = []  
    for i in range(0, len(df) - time_steps, step):
        xs = df['X'].values[i:i+time_steps] 
        ys = df['Y'].values[i:i+time_steps] 
        zs = df['Z'].values[i:i+time_steps]
        label = mode(df[label_name][i:i+time_steps])[0][0]
        segments.append([xs, ys, zs])
        print(i,i+time_steps)
        labels.append(label)
 
    reshaped_segments = np.asarray(segments, dtype=np.float32).reshape(-1, time_steps, N_FEATURES)
    labels = np.asarray(labels) 
     
    return reshaped_segments, labels 

TIME_PERIOD = 5
STEP_DISTANCE = 2
LABEL = 'activityEncode'

image
image
image

然后我们的处理就是:

def segments(df, time_steps, step, label_name): 
    N_FEATURES = 3 
    segments = [] 
    labels = [] 
    for i in range(0, len(df) - time_steps, step): 
        xs = df['X'].values[i:i+time_steps] 
        ys = df['Y'].values[i:i+time_steps] 
        zs = df['Z'].values[i:i+time_steps] 
 
        label = mode(df[label_name][i:i+time_steps])[0][0] 
        segments.append([xs, ys, zs]) 
        labels.append(label) 
 
    reshaped_segments = np.asarray(segments, dtype=np.float32).reshape(-1, time_steps, N_FEATURES) 
    labels = np.asarray(labels) 
 
    return reshaped_segments, labels 
 
TIME_PERIOD = 80 
STEP_DISTANCE = 40 
LABEL = 'activityEncode' 
x_train, y_train = segments(df_train, TIME_PERIOD, STEP_DISTANCE, LABEL)
print('x_train shape:', x_train.shape)
print('Training samples:', x_train.shape[0])
print('y_train shape:', y_train.shape)

image

这里还存储了一些后面用到的数据:时间段(time_period),传感器数(sensors)和类(num_classes)的数量。
这里的time_period是80,传感器个数就是3(X,Y,Z),类的数量就是:['Downstairs', 'Jogging', 'Sitting', 'Standing', 'Upstairs', 'Walking']。

# 这里还存储了一些后面用到的数据:时间段(time_period),传感器数(sensors)和类(num_classes)的数量。
time_period, sensors = x_train.shape[1], x_train.shape[2]
print(time_period,sensors)
num_classes = label_encode.classes_.size 
print(list(label_encode.classes_)) 

image
最后需要使用Reshape将其转换为列表,作为keras的输入

#最后需要使用Reshape将其转换为列表,作为keras的输入
input_shape = time_period * sensors 
x_train = x_train.reshape(x_train.shape[0], input_shape) 
print("Input Shape: ", input_shape) 
print("Input Data Shape: ", x_train.shape)

image
最后需要将所有数据转换为float32。

# 最后需要将所有数据转换为float32。
x_train = x_train.astype('float32')
y_train = y_train.astype('float32')

独热编码

#这是数据预处理的最后一步,我们将通过编码标签并将其存储到y_train_hot中来执行。
y_train_hot = to_categorical(y_train, num_classes)
print("y_train shape: ", y_train_hot.shape)

image

模型预测

image

model = Sequential() 
model.add(LSTM(32, return_sequences=True, input_shape=(input_shape,1), activation='relu')) 
model.add(LSTM(32,return_sequences=True, activation='relu')) 
model.add(Reshape((1, 240, 32))) 
model.add(Conv1D(filters=64,kernel_size=2, activation='relu', strides=2)) 
model.add(Reshape((120, 64))) 
model.add(MaxPool1D(pool_size=4, padding='same')) 
model.add(Conv1D(filters=192, kernel_size=2, activation='relu', strides=1)) 
model.add(Reshape((29, 192))) 
model.add(GlobalAveragePooling1D()) 
model.add(BatchNormalization(epsilon=1e-06)) 
model.add(Dense(6)) 
model.add(Activation('softmax')) 
 
print(model.summary())

image

training

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) 
history = model.fit(x_train, 
                    y_train_hot,  
                    batch_size= 192,  
                    epochs=100 
                   )

image

作图

plt.figure(figsize=(6, 4)) 
plt.plot(history.history['accuracy'], 'r', label='Accuracy of training data') 
plt.plot(history.history['loss'], 'r--', label='Loss of training data') 
plt.title('Model Accuracy and Loss') 
plt.ylabel('Accuracy and Loss') 
plt.xlabel('Training Epoch') 
plt.ylim(0) 
plt.legend() 
plt.show() 
 
y_pred_train = model.predict(x_train) 
max_y_pred_train = np.argmax(y_pred_train, axis=1) 
print(classification_report(y_train, max_y_pred_train))

image
image