📱

Kaggle - スマホのセンサーを使用した人物の特徴推定

2024/04/13に公開

概要

Kaggle にアップロードされているスマートフォンによるモーションデータを使用してスマートフォンを持っている人物の特徴を調べる.

使用したデータ
https://www.kaggle.com/datasets/malekzadeh/motionsense-dataset/data

内容としては スマートフォンに搭載したIMU を使用して取得したデータが含まれており,
立ったり座ったりの動作に対してその動作をした人物の体重, 身長, 年齢などの情報が含まれている.

それをnn に入れて学習させて分類をさせる

コードはこちら
https://github.com/nagato0614/kaggle_smart_phone_har

ダウンロードしたデータセットは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

体重の差が大きいのと性別はほとんどあっていない, というか逆の性別ですいろんしていることがわかる.

参考

https://note.com/le92gisd2u/n/n0e90dabe5491

Discussion