目次
こんにちは.高山です.
これまで様々な手話認識入門記事を紹介してきて,「一体どこまでが入門レベルなのだろう...?」と思ってしまい (^^;),タイトルを変えることにしました.
というわけで今回からは「実践手話認識 - モデル開発編」と題して記事を執筆していきたいと思います.
「実践」と言っても手話認識は技術として確立されているわけではないので,"より高度な技術や最新の研究に基づいた話題" を格好良く (^^;) 言ってみたかった,くらいにとらえてください.
さて,今回は Macaron Net [Lu'19] を用いた孤立手話単語認識モデルを実装する方法を紹介します.
Macaron Net は Transformer の構造を改良した手法で,簡単な改変で実装することができます.
実はMacaron Net の面白い点は実装ではなくて,Transformer の処理を "特徴空間内における多粒子の運動現象 (移流拡散現象) と見なして,近似精度の高いアーキテクチャを採用する"というアイデアにあります.
が,これを説明するのはかなり大変なので (というより数学部分を完全には理解できてないので(^^;)) 今回は実装と実験結果に注力して紹介します.
今回解説するスクリプトはGitHub上に公開しています.
複数の実験を行っている都合で,CPUで動かした場合は結構時間がかるのでご注意ください.
- [Lu'19]: Y. Lu, et al., "Understanding and Improving Transformer From a Multi-Particle Dynamic System Point of View," arXiv:1906.02762, available here, 2019.
更新履歴 (大きな変更のみ記載しています)
- 2024/09/17: タグを更新しました
1. 実験概要
図1に今回の実験内容を示します.
手話認識入門-第九回で紹介したPre-LN 構成の Transformer をベースとして,アーキテクチャを Macaron Net に変更して認識性能を比較します.
Transformer から Macaron Net への変更点は下記のとおりです.
- Multi-Head Self-Attention (MHSA) \(\rightarrow\) Position-wise Feed Forward Network (PFFN) というブロック構成から,PFFN \(\rightarrow\) MHSA \(\rightarrow\) PFFN というブロック構成に変更する.
- PFFN の Residual Connection (分岐との和) において,PFFN の出力に \(1/2\) をかけて足し合わせる.
その他の構成は Transformer と同様です.
2. 実験結果
次節以降では,実装の紹介をしていきます.
コード紹介記事の方針として記事単体で全処理が分かるように書いており,少し長いので結果を先にお見せしたいと思います.
2.1 頻度の多い10単語を学習させた結果
図2は,アーキテクチャ毎のValidation Lossと認識率の推移を示しています.
横軸は学習・評価ループの繰り返し数 (Epoch) を示します.
縦軸はそれぞれの評価指標を示します.
各線の色と実験条件の関係は次のとおりです.
- 青線 (Transformer): Pre-LN構成のTransformer
- 橙線 (Macaron-Net): Macaron Net
ロスの安定性,および認識率共に Macaron Net を用いた場合の方が良い結果になっています.
2.2 250単語を学習させた結果
データが少なくて学習が不安定になっている可能性がありますので,全データ (250単語) を学習させた場合の挙動を図5に示します.
なお,こちらの実験はメモリや処理時間の都合でColab上では実行が難しいので,ローカル環境で行いました.
データの分割方法やパラメータは10単語のときと同じです.
ただし,学習時間を短縮するためにバッチ数は256に設定しています.
また,手話認識入門の補足記事10と同様に,正規化層を切り替えたモデルも評価しました.
各線の色と実験条件の関係は次のとおりです.
- 青線 (Transformer-L): Pre-LN構成のTransformer
- 橙線 (Macaron-Net-L): Macaron Net
- 緑線 (Transformer-B): Transformerの正規化層を Batch Normalization (BN) 層に変更
- 赤線 (Macaron-Net-B): Macaron Netの正規化層を BN層に変更
- 紫線 (Macaron-Net-B(MHSA)): Macaron Net で MHSA ブロックの正規化層を BN層に変更
まずデフォルト設定の青線と橙線に着目すると,全データを学習させた結果ではMacaron Net を用いた場合の方が早く収束し,認識性能も僅かに良いようです.
次に,正規化層を BN層に変更したケース (緑線と赤線) では,Transformer は認識性能が向上しているのに対して,Macaron Net は劣化しています.
正規化層に対する挙動が大きく変わるのは面白いですね.
Macaron-Net-B(MHSA) は,Macaron Net の MHSA ブロックだけをBN層に変更したケースです.
Macaron-Net に対して正規化層の組み合わせを試した限りでは,この設定が一番良い性能になりました.
ただし,認識性能は Transformer-B と同程度なので,Macaron Net が明確に優れていると言うにはもう少し実験をする必要がありそうです.
なお,今回の実験では話を簡単にするために,実験条件以外のパラメータは固定にし,乱数の制御もしていません.
必ずしも同様の結果になるわけではないので,ご了承ください.
3. 前準備
3.1 データセットのダウンロード
ここからは実装方法の説明をしていきます.
まずは,前準備としてGoogle Colabにデータセットをアップロードします.
ここの工程はこれまでの記事と同じですので,既に行ったことのある方は第3.3項まで飛ばしていただいて構いません.
まず最初に,データセットの格納先からデータをダウンロードし,ご自分のGoogle driveへアップロードしてください.
次のコードでGoogle driveをColabへマウントします.
Google Driveのマウント方法については,補足記事にも記載してあります.
1 2 3 |
|
ドライブ内のファイルをColabへコピーします.
パスはアップロード先を設定する必要があります.
# Copy to local.
!cp [path_to_dataset]/gislr_dataset_top10.zip gislr_top10.zip
データセットはZIP形式になっているので unzip
コマンドで解凍します.
!unzip gislr_top10.zip
Archive: gislr_top10.zip
creating: dataset_top10/
inflating: dataset_top10/16069.hdf5
...
inflating: dataset_top10/sign_to_prediction_index_map.json
成功すると dataset_top10
以下にデータが解凍されます.
HDF5ファイルはデータ本体で,手話者毎にファイルが別れています.
JSONファイルは辞書ファイルで,TXTファイルは本データセットのライセンスです.
!ls dataset_top10
16069.hdf5 25571.hdf5 29302.hdf5 36257.hdf5 49445.hdf5 62590.hdf5
18796.hdf5 26734.hdf5 30680.hdf5 37055.hdf5 53618.hdf5 LICENSE.txt
2044.hdf5 27610.hdf5 32319.hdf5 37779.hdf5 55372.hdf5 sign_to_prediction_index_map.json
22343.hdf5 28656.hdf5 34503.hdf5 4718.hdf5 61333.hdf5
単語辞書には単語名と数値の関係が10単語分定義されています.
!cat dataset_top10/sign_to_prediction_index_map.json
{
"listen": 0,
"look": 1,
"shhh": 2,
"donkey": 3,
"mouse": 4,
"duck": 5,
"uncle": 6,
"hear": 7,
"pretend": 8,
"cow": 9
}
ライセンスはオリジナルと同様に,CC-BY 4.0 としています.
!cat dataset_top10/LICENSE.txt
The dataset provided by Natsuki Takayama (Takayama Research and Development Office) is licensed under CC-BY 4.0.
Author: Copyright 2024 Natsuki Takayama
Title: GISLR Top 10 dataset
Original licenser: Deaf Professional Arts Network and the Georgia Institute of Technology
Modification
- Extract 10 most frequent words.
- Packaged into HDF5 format.
次のコードでサンプルを確認します.
サンプルは辞書型のようにキーバリュー形式で保存されており,下記のように階層化されています.
- サンプルID (トップ階層のKey)
|- feature: 入力特徴量で `[C(=3), T, J(=543)]` 形状.C,T,Jは,それぞれ特徴次元,フレーム数,追跡点数です.
|- token: 単語ラベル値で `[1]` 形状.0から9の数値です.
1 2 3 4 5 6 7 8 9 |
|
['1109479272', '11121526', ..., '976754415']
<KeysViewHDF5 ['feature', 'token']>
(3, 23, 543)
[1]
3.2 モジュールのダウンロード
次に,過去の記事で実装したコードをダウンロードします.
本項は前回までに紹介した内容と同じですので,飛ばしていただいても構いません.
コードはGithubのsrc/modules_gislr
にアップしてあります (今後の記事で使用するコードも含まれています).
まず,下記のコマンドでレポジトリをダウンロードします.
!wget https://github.com/takayama-rado/trado_samples/archive/refs/tags/v0.2.zip -O master.zip
--2024-08-15 02:45:40-- https://github.com/takayama-rado/trado_samples/archive/refs/tags/v0.2.zip
...
2024-08-15 02:45:47 (12.8 MB/s) - ‘master.zip’ saved [79912224]
ダウンロードしたリポジトリを解凍します.
!unzip -o master.zip -d master
Archive: master.zip
4907710e233440b5197bbae5462c3e135f8f8711
creating: master/trado_samples-0.2/
inflating: master/trado_samples-0.2/.gitignore
...
モジュールのディレクトリをカレントディレクトリに移動します.
!mv master/trado_samples-0.2/src/modules_gislr .
他のファイルは不要なので削除します.
!rm -rf master master.zip gislr_top10.zip
!ls
dataset_top10 drive modules_gislr sample_data
3.3 モジュールのロード
主要な処理の実装に先立って,下記のコードでモジュールをロードします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
【コード解説】
- 標準モジュール
- copy: データコピーライブラリ.Macaron Netブロック内でEncoder層をコピーするために使用します.
- json: JSONファイル制御ライブラリ.辞書ファイルのロードに使用します.
- os: システム処理ライブラリ
- sys: Pythonインタプリタの制御ライブラリ.
今回はローカルモジュールに対してパスを通すために使用します.
- functools: 関数オブジェクトを操作するためのライブラリ.
今回はDataLoaderクラスに渡すパディング関数に対して設定値をセットするために使用します.
- inspect.signature: オブジェクトの情報取得ライブラリ.
- pathlib.Path: オブジェクト指向のファイルシステム機能.
主にファイルアクセスに使います.osモジュールを使っても同様の処理は可能です.
高山の好みでこちらのモジュールを使っています(^^;).
- 3rdパーティモジュール
- numpy: 行列演算ライブラリ
- torch: ニューラルネットワークライブラリ
- torchvision: PyTorchと親和性が高い画像処理ライブラリ.
今回はDatasetクラスに与える前処理をパッケージするために用います.
- ローカルモジュール: sys.pathにパスを追加することでロード可能
- dataset: データセット操作用モジュール
- defines: 各部位の追跡点,追跡点間の接続関係,およびそれらへのアクセス処理を
定義したモジュール
- layers: ニューラルネットワークのモデルやレイヤモジュール
- transforms: 入出力変換処理モジュール
- train_functions: 学習・評価処理モジュール
- utils: 汎用処理関数モジュール
4. 認識モデルの実装
4.1 ユーティリティ関数
ここから先は,認識モデルを実装していきます.
まず最初に,正規化層の切り替えが簡単になるようにユーティリティ関数を追加します.
下記の関数でインスタンス化する正規化層を選択できるようにします.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
BN層と LN層では,想定している入力系列の形状が異なります.
下記の関数で正規化層の適用処理を整理しておくことで,余計な分岐を減らすことができます.
1 2 3 4 5 6 7 8 9 10 11 |
|
ここでは BN層と LN層の実装例を示しましたが,分岐を追加していけば様々な正規化層を選択できるようになります.
4.2 Macaron Net encoder layer
次に下記のコードで,encoder層を実装します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
|
【コード解説】
- 引数
- dim_model: 入力特徴量の次元数
- num_heads: MHAのヘッド数
- dim_ffw: PFFNの内部特徴次元数
- dropout: Dropout層の欠落率
- activation: 活性化関数の種別を指定 [relu/gelu/swish/silu/mish]
- norm_type_sattn: MHSAブロックの正規化層種別を指定 [batch/layer]
- norm_type_ffw: PFFNブロックの正規化層種別を指定 [batch/layer]
- norm_eps: 正規化層内で0除算を避けるための定数
- add_bias: Trueの場合,線形変換層と正規化層にバイアス項を適用.
ただし,LN層がバイアス項に対応していない場合 (古いPyTorch) は無視します.
- 12-52行目: 初期化処理.
- 14行目: Macaron Net では,PFFN ブロックで Residual Connection を足し合わせる
際に係数をかけるので,ここで固定値を定義しています.
- 19-52行目: 基本的には各層をインスタンス化するだけですが,`create_norm()`
関数を用いて正規化層の種別を切り替えれるようにしています.
- 58-97行目: 推論処理.
- 58-64行目: MHSA用のマスキング配列を作成
- 70-73行目: 1段階目の PFFN ブロック適用処理.
`apply_norm()` 関数を用いて正規化層の処理を抽象化しています.
73行目で `fc_factor` がかけられている点に注意してください.
- 79-86行目: MHSA ブロック 適用処理
- 92-95行目: 2段階目の PFFN ブロック適用処理.
4.3 Macaron Net encoder block
次のコードで,Transformer-Encoderブロックを実装します.
Macaron Net は引数で渡すので,基本的には Transformer の実装がそのまま使えます.
(なので名前も変えていません)
最終段の正規化層の種別を指定する norm_type_tail
引数が加わっていること以外は,手話認識入門第九回と同様 (第5.5項をご参照ください) ですので,説明は割愛させていただきます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
4.4 認識モデル
次のコードで,認識モデル全体を実装します.
基本的には手話認識入門第九回と同様 (第5.6項をご参照ください) です.
ただし,正規化層の種別を指定する tren_norm_type_sattn, tren_norm_type_ffw, tren_norm_type_tail
が引数に加わり,tren_norm_first
は引数から除外されています (Pre-LN構成固定なので).
また,39-48行目で MacaronNetEncoderLayer
を呼び出している点に注意してください.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|
4.5 動作チェック
認識モデルの実装ができましたので,動作確認をしていきます.
次のコードでデータセットからHDF5ファイルとJSONファイルのパスを読み込みます.
1 2 3 4 5 6 7 8 |
|
dataset_top10/sign_to_prediction_index_map.json
[PosixPath('dataset_top10/2044.hdf5'), PosixPath('dataset_top10/32319.hdf5'), PosixPath('dataset_top10/18796.hdf5'), PosixPath('dataset_top10/36257.hdf5'), PosixPath('dataset_top10/62590.hdf5'), PosixPath('dataset_top10/16069.hdf5'), PosixPath('dataset_top10/29302.hdf5'), PosixPath('dataset_top10/34503.hdf5'), PosixPath('dataset_top10/37055.hdf5'), PosixPath('dataset_top10/37779.hdf5'), PosixPath('dataset_top10/27610.hdf5'), PosixPath('dataset_top10/53618.hdf5'), PosixPath('dataset_top10/49445.hdf5'), PosixPath('dataset_top10/30680.hdf5'), PosixPath('dataset_top10/22343.hdf5'), PosixPath('dataset_top10/55372.hdf5'), PosixPath('dataset_top10/26734.hdf5'), PosixPath('dataset_top10/28656.hdf5'), PosixPath('dataset_top10/61333.hdf5'), PosixPath('dataset_top10/4718.hdf5'), PosixPath('dataset_top10/25571.hdf5')]
次のコードで辞書ファイルをロードして,認識対象の単語数を格納します.
1 2 3 4 5 |
|
次のコードで前処理を定義します.
今回は,手話認識入門記事で説明した追跡点の選定と,追跡点の正規化を前処理として適用して実験を行います.
1 2 3 4 5 6 7 8 9 10 |
|
次のコードで,前処理を適用したHDF5DatasetとDataLoaderをインスタンス化し,データを取り出します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
torch.Size([2, 2, 24, 130])
次のコードでモデルをインスタンス化して,動作チェックをします.
追跡点抽出の結果,入力追跡点数は130で,各追跡点はXY座標値を持っていますので,入力次元数は260になります.
出力次元数は単語数なので10になります.
また,Macaron Net層の入力次元数は64に設定し,PFFN内部の拡張次元数は256に設定しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
MacaronNetEnISLR(
(linear): Linear(in_features=260, out_features=64, bias=True)
(activation): ReLU()
(pooling): Identity()
(tr_encoder): TransformerEncoder(
(pos_encoder): PositionalEncoding(
(dropout): Dropout(p=0.1, inplace=False)
)
(layers): ModuleList(
(0-1): 2 x MacaronNetEncoderLayer(
(norm_ffw1): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(ffw1): PositionwiseFeedForward(
(w_1): Linear(in_features=64, out_features=256, bias=True)
(w_2): Linear(in_features=256, out_features=64, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
(activation): ReLU()
)
(norm_sattn): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(self_attn): MultiheadAttention(
(w_key): Linear(in_features=64, out_features=64, bias=True)
(w_value): Linear(in_features=64, out_features=64, bias=True)
(w_query): Linear(in_features=64, out_features=64, bias=True)
(w_out): Linear(in_features=64, out_features=64, bias=True)
(dropout_attn): Dropout(p=0.1, inplace=False)
)
(norm_ffw2): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(ffw2): PositionwiseFeedForward(
(w_1): Linear(in_features=64, out_features=256, bias=True)
(w_2): Linear(in_features=256, out_features=64, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
(activation): ReLU()
)
(dropout): Dropout(p=0.1, inplace=False)
)
)
(norm_tail): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
)
(head): GPoolRecognitionHead(
(head): Linear(in_features=64, out_features=10, bias=True)
)
)
torch.Size([2, 10])
(2, 2, 24, 24) (2, 2, 24, 24)
5. 学習と評価
5.1 共通設定
では,実際に学習・評価を行います.
まずは,実験全体で共通して用いる設定値を次のコードで実装します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Using 2 cores for data loading.
Using cpu for computation.
次のコードで学習・バリデーション・評価処理それぞれのためのDataLoaderクラスを作成します.
1 2 3 4 5 6 7 8 9 10 11 |
|
5.2 学習・評価の実行
次のコードでモデルをインスタンス化します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
MacaronNetEnISLR(
(linear): Linear(in_features=260, out_features=64, bias=True)
(activation): ReLU()
(pooling): Identity()
(tr_encoder): TransformerEncoder(
(pos_encoder): PositionalEncoding(
(dropout): Dropout(p=0.1, inplace=False)
)
(layers): ModuleList(
(0-1): 2 x MacaronNetEncoderLayer(
(norm_ffw1): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(ffw1): PositionwiseFeedForward(
(w_1): Linear(in_features=64, out_features=256, bias=True)
(w_2): Linear(in_features=256, out_features=64, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
(activation): ReLU()
)
(norm_sattn): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(self_attn): MultiheadAttention(
(w_key): Linear(in_features=64, out_features=64, bias=True)
(w_value): Linear(in_features=64, out_features=64, bias=True)
(w_query): Linear(in_features=64, out_features=64, bias=True)
(w_out): Linear(in_features=64, out_features=64, bias=True)
(dropout_attn): Dropout(p=0.1, inplace=False)
)
(norm_ffw2): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
(ffw2): PositionwiseFeedForward(
(w_1): Linear(in_features=64, out_features=256, bias=True)
(w_2): Linear(in_features=256, out_features=64, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
(activation): ReLU()
)
(dropout): Dropout(p=0.1, inplace=False)
)
)
(norm_tail): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
)
(head): GPoolRecognitionHead(
(head): Linear(in_features=64, out_features=10, bias=True)
)
)
次のコードで学習・評価処理を行います.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Start training.
--------------------------------------------------------------------------------
Epoch 1
Start training.
loss:3.077432 [ 0/ 3881]
loss:2.068993 [ 3200/ 3881]
Done. Time:4.522592245999988
Training performance:
Avg loss:2.044730
Start validation.
Done. Time:0.25345869099999163
Validation performance:
Avg loss:2.031111
Start evaluation.
Done. Time:1.2937657340000044
Test performance:
Accuracy:29.5%
--------------------------------------------------------------------------------
...
--------------------------------------------------------------------------------
Epoch 50
Start training.
loss:0.105367 [ 0/ 3881]
loss:0.137955 [ 3200/ 3881]
Done. Time:2.9604755469999873
Training performance:
Avg loss:0.173550
Start validation.
Done. Time:0.2568066980000481
Validation performance:
Avg loss:0.736782
Start evaluation.
Done. Time:1.2513119850000294
Test performance:
Accuracy:78.5%
Minimum validation loss:0.6523600816726685 at 27 epoch.
Maximum accuracy:82.5 at 40 epoch.
以後,同様の処理をアーキテクチャ毎に繰り返します.
コード構成は同じですので,ここでは説明を割愛させていただきます.
また,この後グラフ等の描画も行っておりますが,本記事の主要点ではないため説明を割愛させていただきます.
今回は Macaron Net を用いた孤立手話単語認識モデルを紹介しましたが,如何でしたでしょうか?
記事冒頭で述べたとおり,Macaron Net の面白い点はアーキテクチャの改良内容よりもそこに至る考え方にあります.
今回は説明を割愛させていただきましたが,もう少し理解が進んだら (大学数学を勉強し直さなければなりませんが...(^^;)) そこも記事にできたらいいなと思います.
今回紹介した話が,これから手話認識や深層学習を勉強してみようとお考えの方に何か参考になれば幸いです.