佐藤さとる

佐藤さとる

梶研 [LSTMで行動認識をする]

2024年02月27日

LSTMで行動認識をする

出席率

スケジュール

短期的な予定

長期的な予定

進捗報告

データセットを探す

国際会議CVPR2021にて住空間行動認識コンペティションを開催します

http://ec2-52-25-205-214.us-west-2.compute.amazonaws.com/files/activity_net.v1-3.min.json

---

私のブックマーク「センサを用いた行動認識」 bookmark_vol36-no2/

---

料理中の料理動画再生制御に向けた 料理工程推定手法の評価

Moreaux, M., Ortiz, M. G., Ferran´e, I. and Lerasle, F.: Benchmark for kitchen20, a daily life dataset for audiobased human action recognition, Int. Conf. ContentBased Multimedia Indexing (CBMI), IEEE, pp. 1–6 (2019).

>

https://ieeexplore.ieee.org/document/8877429

とても良さそうだったが論文が見れなかった...

モデル作成に用いる kitchen20 データセット [11] の各音源の長さは 5 秒である 音声だったかも

---

探すの飽きたので一旦適当なデータセットで試す

好きな物を題材にするのはやる気が出て良いと思います

時系列データではないため LSTM では使えなかった

https://allisonhorst.github.io/palmerpenguins/

---

行動認識してる研究があった

加速度データからの機械学習による行動認識
福井大学 大学院工学研究科 研究報告 第68巻2020年3月

https://www.eng.u-fukui.ac.jp/wp-content/uploads/vol68_59-66.pdf

4.1 用いるデータセット

この節では、使用するデータセットの説明をする。

データセットは、スマートフォンによって収集された3 軸加速度センサを用いたデータである HASC コーパスを用る。このデータセットの対象としている行動は、停止 (stay )、歩行 ( walk )、ジョギング ( jog )、スキップ (skip )、階段を上がる ( stUp )、階段を下る ( stDown )の 6 種類であり、それぞれの 3 軸加速度信号データ (csv )、メタデータ ( meta )、ラベルデータ ( label ) の3 種類のデータ形式が記録されている。また、データセットは、大きく分けてセグメントデータとシーケンスデータに分けることができる。

梶先生らの HASC コーパス を使っていた

LSTM をしてみる

流れを確認する

簡単なLSTMをしている記事があった

https://qiita.com/sloth-hobby/items/93982c79a70b452b2e0a

流れ

  1. (三角関数のパラメーラを設定)
  2. データセットを生成
    • 値 と ラベル (と 時間)
  3. データセットを学習用とテスト用に分ける
    • sklearn.modelselection.**traintest_split** を使っている
  4. 値 と ラベル をシャッフルする
    • sklearn.utils.shuffle
  5. Tensor型 にする
    • torch.Tensor
  6. 損失を計算する?
  7. 損失に関する各パラメータの勾配を計算する?
  8. 予測する

より詳しい説明

https://hilinker.hatenablog.com/entry/2018/06/23/204910

解きたいタスクによって大きく変わるらしい

三角関数の連続する3点から続きの1点を出す → many to one

分類問題は many to one

参考: https://qiita.com/m__k/items/841950a57a0d7ff05506

書く

データセットを扱いやすい形にする

BasicActivity/{index}_{label}/{PersonID}/HASC*-acc.csv - time x y z

複数のファイルに分かれているためひとつのファイルにまとめる

BasicActivity/{PersonID}/{PersonID}-acc.csv - time x y z label

面倒だったので 加速度だけでやる

<details>

<summary>ソースコード</summary>

    def __init__(self, person_id: str, path: str) -> None:
        labels = ['stay', 'walk', 'jog', 'skip', 'stUp', 'stDown']

        files = [
            {
                "label": l,
                "acc": str(list(
                    pathlib
                    .Path(f"{path}/{i+1}_{l}/{person_id}")
                    .glob("*-acc.csv")
                )[0]),
                "gyro": str(list(
                    pathlib
                    .Path(f"{path}/{i+1}_{l}/{person_id}")
                    .glob("*-gyro.csv")
                )[0]),
                "pressure": str(list(
                    pathlib
                    .Path(f"{path}/{i+1}_{l}/{person_id}")
                    .glob("*-pressure.csv")
                )[0]),
            }
            for i, l in enumerate(labels)
        ]

        df_acc = pd.concat([
            pd.read_csv(f["acc"], header=None, names=["time", "x", "y", "z"])
            .assign(label=f["label"])
            for f in files
        ])
        df_gyro = pd.concat([
            pd.read_csv(f["gyro"], header=None, names=["time", "pressure"])
            .assign(label=f["label"])
            for f in files
        ])
        df_pressure = pd.concat([
            pd.read_csv(f["pressure"], header=None, names=["time", "x", "y", "z"])
            .assign(label=f["label"])
            for f in files
        ])

        self.df_acc = df_acc
        self.df_gyro = df_gyro
        self.df_pressure = df_pressure

    def make_dataset(self, sequence_length: int):
        dataset_inputs = []
        dataset_labels = []
        dataset_times = []

        # self.df_acc を時系列データに変換
        for i, row in enumerate(self.df_acc.itertuples()):
            # dataframe の長さを超える場合は終了
            if i+sequence_length > len(self.df_acc):
                break
            # ラベルが変わる場合はスキップ
            if i > 0 and self.df_acc.iloc[i-1].label != row.label:
                continue
            dataset_inputs.append(r for r in self.df_acc[i:i+sequence_length])
            dataset_labels.append(row.label)
            dataset_times.append(row.time)

        return dataset_inputs, dataset_labels, dataset_times

</details>

訓練用とテスト用に分割する

inputs, labels, _ = train.make_dataset(sequence_length)
train_inputs, test_inputs, train_labels, test_labels = train_test_split(inputs, labels, test_size=test_size, shuffle=False)

評価関数を宣言

評価関数とは

評価関数とは学習させたモデルの良さを測る指標を指します。
criterion = nn.CrossEntropyLoss()

二乗平均誤差(RMSE) みたいな役割ぽい

参考: https://qiita.com/monda00/items/a2ee8e0da51953c24da8

最適化関数を宣言

最適化関数とは

損失関数を用いて正解値と予測値の差を微分することで、勾配を求めることができます。

この勾配を利用してどのくらいの強度でパラメータを更新するかを決めているのが最適化関数です。

とにかくパラメータの方向性をいい感じにしてくれるらしい

参考: https://qiita.com/Lapinna/items/d5c331b74c99767ca04a

なんやかんやする

ephoc数: 訓練データを何回繰り返して学習させるかの回数

batch数: データセットを分割する. 1度に処理を行う数

for ephoch in ephoc数:
    for batch in batch数:
        学習の処理

<details>

<summary>ソースコード</summary>

    def train(
        self,
        train_inputs: list,
        train_labels: list,
        test_inputs: list,
        test_labels: list,
        epoch_num: int,
        batch_size: int,
        sequence_length: int,
    ):
        train_batch_num = len(train_inputs) // batch_size
        test_batch_num = len(test_inputs) // batch_size

        for epoch in range(epoch_num):
            print("-" * 20)
            print(f"Epoch {epoch+1}/{epoch_num}")
            train_loss = 0.0
            test_loss = 0.0
            shuffled_train_inputs, shuffled_train_labels = shuffle(
                train_inputs, train_labels
            )

            np.savetxt(
                "shuffled_train_inputs.csv",
                np.array(shuffled_train_inputs).reshape(
                    -1, np.array(shuffled_train_inputs).shape[-1]
                ),
                delimiter=",",
            )

            for batch in range(train_batch_num):
                start = batch * batch_size
                end = start + batch_size

                np_train_inputs = np.array(shuffled_train_inputs[start:end]).astype(
                    np.float64
                )
                np_train_labels = np.array(shuffled_train_labels[start:end]).astype(
                    np.int64
                )
                loss, _ = self.train_step(np_train_inputs, np_train_labels)
                train_loss += loss.item()

            for batch in range(test_batch_num):
                start = batch * batch_size
                end = start + batch_size

                loss, _ = self.train_step(
                    np.array(test_inputs[start:end]).astype(np.float64),
                    np.array(test_labels[start:end]).astype(np.int64),
                )
                test_loss += loss.item()

            print(f"loss: {train_loss / train_batch_num}")
            print(f'test_loss: {test_loss / test_batch_num}')

    def train_step(self, inputs, labels):
        inputs_tensor = torch.tensor(inputs, dtype=torch.float32).to(self.device)
        labels_tensor = torch.tensor(labels).to(self.device)

        self.model.eval()
        preds = self.model(inputs_tensor)
        loss = self.criterion(preds, labels_tensor)

        return loss, preds

</details>

実行結果

device: cpu
--------------------
Epoch 1/100
loss: 1.80343093945269
test_loss: 1.7852103145498979
--------------------
Epoch 2/100
loss: 1.8034178761013768
test_loss: 1.7852103145498979
--------------------
Epoch 3/100
loss: 1.8034281317602123
test_loss: 1.7852103145498979
--------------------
Epoch 4/100
loss: 1.8034172628009528
test_loss: 1.7852103145498979
--------------------
Epoch 5/100
loss: 1.8034309844175975
test_loss: 1.7852103145498979
--------------------
Epoch 6/100
loss: 1.8034331908351497
test_loss: 1.7852103145498979
...
loss: 1.803422716103102
test_loss: 1.7852103145498979
--------------------
Epoch 12/100

loss が減らない.

ChatGPT に聞いてみた

訓練がうまくいかない理由の一つは、学習率 (lr) の値が適切でない可能性があります。学習率はモデルの訓練中に重要な役割を果たし、適切な値を見つけることが重要です。学習率が高すぎると、訓練が収束せずに振動する可能性があります。逆に、学習率が低すぎると、訓練が収束するまでに非常に長い時間がかかる可能性があります。

なるほど

0.0001 から 1 にしてみた

device: cpu
--------------------
Epoch 1/100
loss: 1.7967431503429747
test_loss: 1.7872365692205596
--------------------
Epoch 2/100
loss: 1.796769555723458
test_loss: 1.7872365692205596
--------------------
Epoch 3/100
loss: 1.796803759901147
test_loss: 1.7872365692205596
--------------------
Epoch 4/100
loss: 1.7967799776478817
test_loss: 1.7872365692205596
--------------------
Epoch 5/100
loss: 1.7967207206968676
test_loss: 1.7872365692205596
--------------------
Epoch 6/100
loss: 1.79678154043984
test_loss: 1.7872365692205596
...
--------------------
Epoch 100/100
loss: 1.7967986835722338
test_loss: 1.7872365692205596

変わらない. 0.01 100 なども試したが大差なかった

原因候補

最適化関数を変えてみる

optim.Adam から optim.SGD にした

torch-optim_02.png (17.2 kB)

参考: Pytorchの様々な最適化手法

device: cpu
--------------------
Epoch 1/100
loss: 1.809930482977315
test_loss: 1.721430358133818
--------------------
Epoch 2/100
loss: 1.8099145392576854
test_loss: 1.721430358133818
--------------------
Epoch 3/100
loss: 1.8099606868467832
test_loss: 1.721430358133818
--------------------
Epoch 4/100
loss: 1.8100076438042156
test_loss: 1.721430358133818
--------------------
Epoch 5/100
loss: 1.8099237277842404
test_loss: 1.721430358133818
--------------------
Epoch 6/100
loss: 1.8099288961343598
test_loss: 1.721430358133818
...
--------------------
Epoch 100/100
loss: 1.8099035244239003
test_loss: 1.721430358133818

変わらなかった. そもそも最適化関数が使えてる?

optimizer.step() を入れてなかった

結果

device: cpu
--------------------
Epoch 1/100
loss: 1.4727532474095362
test_loss: 2.458821142043775
--------------------
Epoch 2/100
loss: 3.9363968978848374
test_loss: 47.90312148220191
--------------------
Epoch 3/100
loss: 22.853781702225668
test_loss: 350.9249111309386
--------------------
Epoch 4/100
loss: 43.75640468430101
test_loss: 69.1852414315207
--------------------
Epoch 5/100
loss: 172.5352889696757
test_loss: 838.7717777720669
--------------------
Epoch 6/100
loss: 140.68897648024978
test_loss: 245.76162933885004
...
loss: 222.82399281284265
test_loss: 668.8828098230194
--------------------
Epoch 10/100

変化したが爆上がりした

0.0001 にしてみた

device: cpu
--------------------
Epoch 1/1000
loss: 1.7136963245115782
test_loss: 2.322023772356803
--------------------
Epoch 2/1000
loss: 1.564926897224627
test_loss: 2.430806122328106
--------------------
Epoch 3/1000
loss: 1.6254029938003474
test_loss: 1.955993468301338
--------------------
...
loss: 0.7991769483737778
test_loss: 3.40365913876316
--------------------
Epoch 104/1000
device: cpu
--------------------
Epoch 1/1000
loss: 1.630137525629579
test_loss: 2.442890736094692
--------------------
Epoch 2/1000
loss: 1.7019225055711311
test_loss: 1.8877409361956412
--------------------
Epoch 3/1000
loss: 1.540266997458642
test_loss: 1.795748932319775
--------------------
...
loss: 0.31551139612208334
test_loss: 0.7359745272092129
--------------------
Epoch 101/1000
...
loss: 1.1991943183698153
test_loss: 1.0757994573612355
--------------------
Epoch 167/1000

良い感じになってきた.

Epoch数 を上げすぎると過学習になった

パラメータを変えて精度を上げる必要がありそう

(主成分分析は)

これをどう使えばいいかは分からない

ある程度分かってくると機械学習も面白い

メモ

私のブックマーク「センサを用いた行動認識」

https://www.ai-gakkai.or.jp/resource/my-bookmark/my-bookmark_vol36-no2/

進路関係

なし

余談

関ヶ原に行ってきた

道中の犬山城

IMG_6079.jpg (753.2 kB)

道中の大垣城

IMG_6082.jpg (2.2 MB)

関ヶ原

IMG_6101.jpg (2.0 MB)

道中の墨俣一夜城

IMG_6108.jpg (1.1 MB)