📱
Kaggle - スマホのセンサーを使用した人物の特徴推定
概要
Kaggle にアップロードされているスマートフォンによるモーションデータを使用してスマートフォンを持っている人物の特徴を調べる.
使用したデータ
内容としては スマートフォンに搭載したIMU を使用して取得したデータが含まれており,
立ったり座ったりの動作に対してその動作をした人物の体重, 身長, 年齢などの情報が含まれている.
それをnn に入れて学習させて分類をさせる
コードはこちら
ダウンロードしたデータセットはmain.ipynbと同じディレクトリに配置していると想定している.
データセット
以下の図のように体重, 年齢, 身長, 性別が含まれている.
コード
import と定数
定数と必要なファイル, ラベルを定義
import os
import tensorflow as tf
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from tensorflow import keras
import matplotlib.pyplot as plt
data_dir = 'archive/A_DeviceMotion_data/A_DeviceMotion_data'
data_subjects_info = 'archive/data_subjects_info.csv'
class_names = ['dws', 'ups', 'sit', 'std', 'wlk', 'jog']
class_full_names = ['Downstairs', 'Upstairs', 'Sitting', 'Standing', 'Walking', 'Jogging']
csv_file_base = 'sub_'
num_of_user = 24
divided_num = 200
データの確認
# data_subjects_info の内容を表示
df = pd.read_csv(data_subjects_info)
print(df)
ラベルデータを表示する.
code weight height age gender
0 1 102 188 46 1
1 2 72 180 28 1
2 3 48 161 28 0
3 4 90 176 31 1
4 5 48 164 23 0
5 6 76 180 28 1
6 7 62 175 30 0
7 8 52 161 24 0
8 9 93 190 32 1
9 10 72 164 31 0
10 11 70 178 24 1
11 12 60 167 33 1
12 13 60 178 33 1
13 14 70 180 35 1
14 15 70 185 33 1
15 16 96 172 29 0
16 17 76 180 26 1
17 18 54 164 26 0
18 19 78 164 28 0
19 20 88 180 25 1
20 21 52 165 24 1
21 22 100 186 31 1
22 23 68 170 25 0
23 24 74 173 18 0
18 ~ 46歳で比較的30前後が多い
ディレクトリを取得
# 指定したディレクトリいかにあるのディレクトリ名を表示
dir_list = []
for root, dirs, files in os.walk(data_dir):
for dir in dirs:
dir_list.append(dir)
print(dir_list)
取得したディレクトリ
先頭三文字が動作になっている
['dws_11', 'ups_12', 'wlk_7', 'std_14', 'wlk_15', 'wlk_8', 'dws_2', 'sit_13', 'jog_9', 'ups_3', 'ups_4', 'jog_16', 'dws_1', 'sit_5', 'std_6']
各ユーザーごとの動作を取得して結合
user_data = []
for i in range(1, num_of_user + 1):
user_data.append({})
for d in dir_list:
# ユーザーに対応する番号のcsv を開きdf として読み込む
file_name = data_dir + '/' + d + '/' + csv_file_base + str(i) + '.csv'
df = pd.read_csv(file_name)
user_data[i - 1][d] = df
ユーザーをdfとして保存する
# ユーザーごとに読み込んだデータを動作ごとに結合する
for i in range(1, num_of_user + 1):
for class_name in class_names:
df = pd.DataFrame()
for d in dir_list:
if class_name in d:
df = pd.concat([df, user_data[i - 1][d]])
# 一列目を削除
df = df.iloc[:, 1:]
# インデックスを振り直す
df = df.reset_index(drop=True)
user_data[i - 1][class_name] = df
結合したデータは以下のような感じ
データセットの作成
# データセットを作成する
# 1つのデータセットは200個のデータを持つ
# 1つのデータセットは4つの出力 (体重, 身長, 年齢, 性別) を持つ
# 学習用の人数
test_user = 3
train_user = num_of_user - test_user
# 最後の一人はテスト用に使用する
input_x = []
output_classification = []
output_regression = []
for i in range(train_user):
print(f'User {i + 1}')
for class_name in class_names:
for data in user_array[i][class_name]:
# データは一つのnumpy配列にまとめる
data = data.flatten()
input_x.append(data)
output_regression.append(user_info[i][:3])
output_classification.append(user_info[i][3])
print("テストデータ作成")
# テスト用データを作成
input_x_test = []
output_classification_test = []
output_regression_test = []
for i in range(train_user, num_of_user):
print(f'User {i + 1}')
for class_name in class_names:
for data in user_array[i][class_name]:
data = data.flatten()
input_x_test.append(data)
output_regression_test.append(user_info[i][:3])
output_classification_test.append(user_info[i][3])
# データオーグメンテーション
# 入力のデータにノイズを加えたものを追加する
# ノイズの強さ
noise_scale = 0.01
for i in range(len(input_x)):
noise = np.random.normal(0, noise_scale, input_x[i].shape)
input_x.append(input_x[i] + noise)
output_regression.append(output_regression[i])
output_classification.append(output_classification[i])
# tf.data.Datasetを作成
dataset = tf.data.Dataset.from_tensor_slices(({'input_x': input_x},
{'regression': output_regression,
'classification': output_classification}))
# テスト用データセット
test_dataset = tf.data.Dataset.from_tensor_slices(({'input_x': input_x_test},
{'regression': output_regression_test,
'classification': output_classification_test}))
# 各データをテスト用, 検証用, 学習用に分割
train_rate = 0.8
val_rate = 0.2
dataset_size = len(input_x)
train_size = int(train_rate * dataset_size)
val_size = int(val_rate * dataset_size)
train_dataset = dataset.take(train_size)
remaining_dataset = dataset.skip(train_size)
val_dataset = remaining_dataset.take(val_size)
# データセットをシャッフルしてバッチ化
batch_size = 1000
train_dataset = train_dataset.shuffle(buffer_size=train_size).batch(batch_size)
val_dataset = val_dataset.batch(batch_size)
test_dataset = test_dataset.batch(batch_size)
最後の数人をテストデータように確保しておいて残りを学習に使用する.
データオーグメンテーションとしてノイズをふくんだデータを追加する.
これをすることで制度がすこし上がった
モデルの定義
input_shape = (divided_num * 12,) # 入力の形状に合わせて適切な値を設定してください
output_shape_regression = (3,) # 体重、身長、年齢の3つの連続値
output_shape_classification = (1,) # 性別の2値分類
hidden_units = 5000
l2_regularization = 0.001 # L2正則化の強度
# 入力層
inputs = keras.Input(shape=input_shape, name='input_x')
# 共通の中間層
x = keras.layers.Dense(hidden_units, activation='relu', kernel_regularizer=keras.regularizers.l2(l2_regularization))(inputs)
x = keras.layers.BatchNormalization()(x)
x = keras.layers.Dense(hidden_units, activation='relu', kernel_regularizer=keras.regularizers.l2(l2_regularization))(x)
x = keras.layers.BatchNormalization()(x)
# 回帰用の出力層
output_regression = keras.layers.Dense(output_shape_regression[0], activation='linear',
name='regression')(x)
# 分類用の出力層
output_classification = keras.layers.Dense(output_shape_classification[0], activation='sigmoid',
name='classification')(x)
# モデルの定義
model = keras.Model(inputs=inputs, outputs=[output_regression, output_classification])
# モデルのコンパイル
model.compile(optimizer='adam',
loss={'regression': 'mse', 'classification': 'binary_crossentropy'},
metrics={'regression': 'mse', 'classification': 'accuracy'})
model.summary()
性別だけは分類問題なので出力を2つに分けている.
そして学習
# モデルの学習
epochs = 200
history = model.fit(train_dataset,
epochs=epochs,
validation_data=val_dataset)
エポック数は適宜変更させる.
学習時は以下のように感じになった.
テスト
# 学習結果の評価
loss, classification_accuracy, regression_mse = model.evaluate(test_dataset)
print(f'Validation loss: {loss}')
print(f'Validation regression MSE: {regression_mse}')
print(f'Validation classification accuracy: {classification_accuracy}')
実際にテストしてみると結構誤差が大きい
Validation loss: 164.17987060546875
Validation regression MSE: 155.07366943359375
Validation classification accuracy: 0.33500000834465027
for test_data in test_dataset.unbatch().shuffle(2000).take(10).batch(1):
# 選択したデータを使って予測
predicted_regression, predicted_classification = model.predict(test_data[0])
# 入力データをプロット
plt.plot(test_data[0]['input_x'].numpy().flatten())
plt.title('Input Data')
plt.show()
# 実際の値と予測値を表示
print("[実際の値]体重: {:.2f}, 身長: {:.2f}, 年齢: {:.2f}, 性別: {}".format(
test_data[1]['regression'][0][0],
test_data[1]['regression'][0][1],
test_data[1]['regression'][0][2],
test_data[1]['classification'][0]
))
print("[予測値]体重: {:.2f}, 身長: {:.2f}, 年齢: {:.2f}, 性別: {}".format(
predicted_regression[0][0],
predicted_regression[0][1],
predicted_regression[0][2],
predicted_classification[0][0]
))
それぞれ比較して見ると
[実際の値]体重: 68.00, 身長: 170.00, 年齢: 25.00, 性別: 0
[予測値]体重: 85.21, 身長: 182.18, 年齢: 29.68, 性別: 0.9992871880531311
体重の差が大きいのと性別はほとんどあっていない, というか逆の性別ですいろんしていることがわかる.
参考
Discussion