Goolge Text-to-Speech "gTTS" を試す
※ この記事は、 クリエイティブ・コモンズ・表示・継承ライセンス3.0 のもとで公表されたウィキペディアの項目 「ディープラーニング - Wikipedia」 を素材として二次利用しています。
シンプルにテキストを音声にしたい、というモチベーションからgTTS(Google Text-to-Speech)を試してみた。 Web環境を用意して、gTTSをインストールすればOK。
$ pip install gTTS
ディープラーニング - Wikipedia のテキストを音声変換するテストコードを書いてみた。
from gtts import gTTS def main(): txt_01 = """ ディープラーニングまたは深層学習とは、対象の全体像から細部までの各々の粒度の概念を階層構造として関連させて学習する手法のことである。 深層学習として最も普及した手法は、多層の人工ニューラルネットワークによる機械学習手法である。 多層ニューラルネットワークについては、ジェフリー・ヒントンの研究チームが2006年に考案したスタックドオートエンコーダが直接の起源となった。 """ txt_02 = """ 要素技術としてはバックプロパゲーションなど、20世紀のうちに開発されていたものの、4層以上の深層ニューラルネットについて、 局所最適解や勾配消失などの技術的な問題によって十分学習させられず、性能も芳しくなかった。 しかし、21世紀に入って、スタックドオートエンコーダを始めとするヒントンらによる多層ニューラルネットワークの学習の研究や、 学習に必要な計算機の能力向上、および、インターネットの発展による学習データの流通により、十分に学習させられるようになった。 その結果、音声・画像・自然言語を対象とする諸問題に対し、他の手法を圧倒する高い性能を示し、2010年代に普及した。 学界では更に抽象化された数学的概念によるディープラーニングが研究されている。 """ txt_03 = """ ディープラーニングは、学習に用いる具体的な数学的概念はどうであれ、対象の全体像から細部までの各々の粒度の概念を階層構造として関連させて学習する手法を指す。 21世紀に入って、オートエンコーダを始めとするジェフリー・ヒントンらによる多層ニューラルネットワークによる学習の研究や、学習に必要な計算機の能力向上、 および、インターネットの発展による学習データの流通により、多層ニューラルネットによる手法が最初に確立された。 その結果、音声・画像・自然言語を対象とする諸問題に対し、他の手法を圧倒する高い性能を示し、2010年代に普及した。 結果として多層の人工ニューラルネットワークによる機械学習手法が広く知られるようになったが、ニューラルネットワーク以外でも深層学習は構成可能であり、 現在はニューラルネットワークよりも抽象的な深層学習の数学的概念が模索されている最中にある。 ビジネスの現場では多層ニューラルネットワークの応用が盛んであり、「ディープラーニング=ニューラルネットワーク」などと解釈される事が多いが、 学界ではニューラルネットワーク以外の手法も含めた抽象的な概念として説明される。 """ t2s(txt_01, "audio_001.mp3") t2s(txt_01+txt_02, "audio_002.mp3") t2s(txt_01+txt_02+txt_03, "audio_003.mp3") def t2s(txt, audio_file_name): print('len(text):', len(txt)) tts = gTTS(text=txt, lang='ja') tts.save(audio_file_name) print(f"Saved {audio_file_name}") print('') if __name__ == "__main__": main()
コア部分はt2s()。 テキストと音声ファイル名を渡すだけ。
音声は女性の声で多少の癖はあるが聞きやすい方だと思う。 ただし、話す速度はもっと速くてもいいかな、というレベル。 音声はこちら にあるのでダウンロードして聞いてみて下さい。
gTTSのドキュメント
を見たけど、slow
パラメーターはあるが、fast
というパラメーターはなかった。
なので、速くしたいなら自分で調整する処理を実装しないといけない。
あと制限については Google Cloud Text-to-Speechの「割り当てと上限」 に書いてあった。 ただし、Cloudの仕様なので、gTTSと同じとは限らないが、無料ならまあ十分と言える仕様である。 1分あたりのリクエスト数は1,000回なので、リクエストを数回叩けば、数万文字を音声にできるので、 この辺も自分で音声を繋げる処理を書けばなんとかなりそう。
音声合成の上限 | 値 |
---|---|
1リクエストあたりの合計文字数 | 5,000 |
上限のタイプ | 使用量上限 |
---|---|
1 分あたりのリクエスト数 | 1,000 |
1 分あたりの文字数 | 500,000 |
上記のコードでは3種類の文字数のリクエストを投げているが、 いずれの文字数も問題なく10秒ほどで処理できていた。
$ python main.py len(text): 190 Saved audio_001.mp3 len(text): 526 Saved audio_002.mp3 len(text): 1033 Saved audio_003.mp3
参考文献
とにかくDjangoでAjaxのPOSTを機能させる近道
【Blender 2.8 x Python API】ボールで壁を破壊する物理演算
ボールで壁を破壊する物理演算をPython APIだけで実現する。 前準備として、Blenderを起動して、 LayoutタブからScriptingタブに変えて、新規をクリックしておく。
Python API 実装解説
BlenderのPython API bpy
をimport。
Blender 2.8からかなり仕様が変わっている。
bpy
を操作に調べると分かるが、
ブログや記事に書いてあるコードが動かないことが多い。
# import libraries import bpy
操作は何度か繰り替えすことを想定している。 なので、繰り返す度に環境をリフレッシュさせるため、全選択&削除を実行。
# Reset objects. bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(True)
light_add()
でライトをセット。
以下のパラメーターは、配置するボールと壁に当たるように調整した結果。
と言いつつ、上図はソリッドモードなので関係ないが。
ライトの当たり具合を確認するには、レンダープレビューにする必要がある。
# Add light bpy.ops.object.light_add(type='AREA', radius=10, location=(0, -20, 20)) bpy.context.object.data.energy = 10000 bpy.context.object.rotation_euler[0] = 0.52
primitive_plane_add()
で平面を設置。
object_add(type='PASSIVE')
で崩壊する壁を受け止めるようになる。
# Add plane. bpy.ops.mesh.primitive_plane_add(enter_editmode=False, location=(0, 0, 0)) bpy.ops.transform.resize(value=(10, 40, 1)) bpy.ops.rigidbody.object_add(type='PASSIVE')
primitive_cube_add()
でキューブを積み重ねて壁を作る。
object_add(type='ACTIVE')
とする事で、平面とは異なり、ボールの衝突によってキューブが物理演算に従って移動する。
# Add wall. idx = 0 for x in range(-5, 5): for y in range(-1, 0): for z in range(0, 5): bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, location=(x+0.5,y+0.5,z+0.5)) bpy.ops.rigidbody.object_add(type='ACTIVE')
primitive_uv_sphere_add()
で球をセット。
キューブと同様、object_add(type='ACTIVE')
とし、ボールを壁へ衝突させる物理演算を実現する。
# Add ball. bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, location=(0, -15, 2)) bpy.ops.rigidbody.object_add(type='ACTIVE')
ボールが動き出すための時間をセット。 30フレームで1秒。 これが0フレーム(0秒)だと、開始時から壁が破壊され始めて、何が起きたかよく分からなくなる。
# Set animation and physical calculation n_stay = 30
n_stay後のボールの動きをセットする。
まずは加速をアニメーションで作成。
(1)frame_set()
でフレームを移動
(2) ボールのIDであるSphere
を選択、このIDは球のデフォルトの名前
(3) keyframe_insert()
でボールをセットした位置をキーフレームに登録する位置としてセット
(4) アニメーションをONに
(5) keyframe_insert(data_path='kinematic')
で上記の内容をキーフレームに登録
bpy.context.scene.frame_set(n_stay+1) # (1) obj = bpy.context.scene.objects.get('Sphere') # (2) obj.keyframe_insert(data_path='location') # (3) bpy.context.object.rigid_body.kinematic = True # (4) Animation ON obj.rigid_body.keyframe_insert(data_path='kinematic') # (5)
ボールの加速の終わりをセット。
設定方法は上記とほぼ同じ。
frame_set()でスタート位置より+4のフレームに移動し、
Sphere
を選択して、
Y軸だけ-15mから12mに移動して、
keyframe_insert(data_path='location')
でキーフレームに登録する。
4フレームで27mを進んだことになるので、
この時点で秒速202.5m(= 27m / (4/30)s)出ていることになる。
bpy.context.scene.frame_set(n_stay+5) obj = bpy.context.scene.objects.get('Sphere') obj.location = (0, 12, 2) obj.keyframe_insert(data_path='location')
最後に物理演算の設定。
202.5mになった次のフレームで、アニメーションを解除する。
キーフレームを移動して、ボールを選択するまでは一緒。
その後、kinematicをFalseにした状態で、
keyframe_insert(data_path='kinematic')
でキーフレームを登録することで、
アニメーションが解除になり、202.5m/sで放たれたボールが物理演算に従ってシミュレートされる。
bpy.context.scene.frame_set(n_stay+6) obj = bpy.context.scene.objects.get('Sphere') bpy.context.object.rigid_body.kinematic = False # (1) obj.rigid_body.keyframe_insert(data_path='kinematic') # Animation OFF (2)
フレームを1に戻す。 Layoutタブに戻して、タイムラインウィンドウで再生を押すだけでシミュレートできる。
bpy.context.scene.frame_set(1)
全コード
# import libraries import bpy # Reset objects. bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(True) # Add light bpy.ops.object.light_add(type='AREA', radius=10, location=(0, -20, 20)) bpy.context.object.data.energy = 10000 bpy.context.object.rotation_euler[0] = 0.52 # Add plane. bpy.ops.mesh.primitive_plane_add(enter_editmode=False, location=(0, 0, 0)) bpy.ops.transform.resize(value=(10, 40, 1)) bpy.ops.rigidbody.object_add(type='PASSIVE') # Add wall. idx = 0 for x in range(-5, 5): for y in range(-1, 0): for z in range(0, 5): bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, location=(x+0.5,y+0.5,z+0.5)) bpy.ops.rigidbody.object_add(type='ACTIVE') # Add ball. bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, location=(0, -15, 2)) bpy.ops.rigidbody.object_add(type='ACTIVE') # Set animation and physical calculation n_stay = 30 bpy.context.scene.frame_set(n_stay+1) obj = bpy.context.scene.objects.get('Sphere') obj.keyframe_insert(data_path='location') bpy.context.object.rigid_body.kinematic = True # Animation ON obj.rigid_body.keyframe_insert(data_path='kinematic') bpy.context.scene.frame_set(n_stay+5) obj = bpy.context.scene.objects.get('Sphere') obj.location = (0, 12, 2) obj.keyframe_insert(data_path='location') bpy.context.scene.frame_set(n_stay+6) obj = bpy.context.scene.objects.get('Sphere') bpy.context.object.rigid_body.kinematic = False # Animation OFF obj.rigid_body.keyframe_insert(data_path='kinematic') bpy.context.scene.frame_set(1)
参考文献
Ubuntu18.04をインストールしてPyTorchでGPUが使えるかを確認するまでの設定
Nvidia GeForce RTX 2080 Tiを搭載したOSなしのPCを買ってライブUSBを指すも、バグった画面が出てインストール画面にすら行かないという状況だったが、 なんとかNvidia Driver/CUDA/cuDNNを入れて、PyTorchでGPUを認識するに至った過程を残しておく。
1. インストールメディアの作成
ライブUSBでもライブDVDでもいいが、Ubuntu18.04のISOファイルを書き込んだメディアを作成する。 簡単だがそれぞれ作り方が異なるので この辺とか ネットで検索してから実施した。 自分は最初、手持ちの古いUSBでライブUSBを作成したがバグ画面が出たので、古いからダメなのかなと思い、 DVDを買ってきてライブDVDを作成して再度インストールを試みたが同じ画面が出た感じ。 結局どっちでもいいと分かったので、その後はDVDで進めている。 今回の件とは無関係かもしれないが、今回インストールしたUbuntu18.04は日本語翻訳版で、 それが悪さしているという噂 も散見されるので、英語版でやった方がすんなり行くかも。
2. インストール画面に行けない問題の解決
メディアを読み込むとGNU GRUB画面が出る。
デフォルトのTry Ubuntu without installing
か、その下のInstall Ubuntu
に進むと、
Ubuntu18.04のインストール画面に行く。
自分はWi-Fiが使えるのかを確認してからインストールしたかったので前者を選択したが、
どちらもその先はバグった画面にメッセージが書かれたまま進まない状況となる。
解決策としては、
ここ
にも書かれている通り、以下の手順を踏むと上手く行く。
- GNU GRUB画面で
Try Ubuntu without installing
かInstall Ubuntu
を選択した状態にする - e キーを押して起動コマンドを編集する画面を開く
- 起動コマンドの
quiet splash
の箇所をnomodeset
に書き換える F10
で起動
3の手順は最後に追加すればOKとかいうサイトもあるけど、quiet splash
をnomodeset
に置き換える必要がある。
あとnomodeset
のスペルミスに気を付けて入力すること。
上手く行けばインストール画面に進める。
3. ログイン画面に行かない問題の解決
無事インストールできたと思っても、再起動後にログイン画面が表示されず、またバグった画面が出るという状態になった。
問題はWayland
という未完成のディスプレイサーバーを利用しているかららしい。
なので、これをオフにすると上手く行った。
本記事には詳しく書かないが、
ここ
を参考に進めたら上手く行った。1度だけ。。
ちなみに、Advanced option for Ubuntu
を選択する画面だが、
自分の環境ではShift
ではなくEsc
を連打したら行くことができた。
あと、自分はviではなくemacs派だったのでviを使えないのだが、
手順の例に挙げられているnanoは普通のエディタなので、vi使えない人はnanoがいいと思う。
4. ログイン画面に行かない問題の解決 その2
再起動したもの再びログイン画面は表示されなかった。
上記と同じ手順に従うと行けるはずと思ったが、もうconfファイルの編集は必要ないので、
[Advanced option for Ubuntu
] > [Ubuntu, with Linux ... (recovery mode)
] > [resume
] > [Ok
]
の手順でログイン画面の表示に成功した。
5. Nvidia Driverを入れて問題解決
様々な参考文献を読んでると、どうやら諸悪の根源はNvidia Driverのような感じがする。 と言うわけで、Nvidia Driverを入れれば全部解決するっぽい。 ただし、自分はNvidia Driverだけでなく、 CUDA/cuDNNまで入れてPyTorchが動くことまで確認したら、 ログイン画面が表示されるようになった。 ただし、Ubuntu18.04とPyTorch1.4では、この辺はほぼ全自動で簡単にできる。
5.1. Secure BootをDisableにしてNvidia Driverを入れる
まずPCを再起動して、F11を連打して、UEFI画面を開いて、セキュアブートを無効にする。 次にログイン画面して、以下のコマンドを入力して各種ドライバーをインストールする。 詳しくは ここや ここを参照。 なお、これだけでもログイン画面問題は解決するかも。
$ ubuntu-drivers devices $ sudo ubuntu-drivers autoinstall
5.2 Anacondaを入れて仮想環境にPyTrochを入れる
ここを参考にAnacondaを入れたあと、 仮想環境を作って、そのにPyTorchを入れる。 PyTorchをインストールする際は、以下のようにcudatoolkitも入るので、 これでCUDAとcuDNNもバージョンを気にせずインストールできる。
conda install pytorch torchvision cudatoolkit=10.1 -c pytorch
すべて上手くいくと以下のように環境構築できたことが確認できる。
$ nvidia-smi Sun Apr 5 12:31:15 2020 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 435.21 Driver Version: 435.21 CUDA Version: 10.1 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce RTX 208... Off | 00000000:01:00.0 On | N/A | | 35% 30C P8 12W / 260W | 395MiB / 11011MiB | 8% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | 0 1169 G /usr/lib/xorg/Xorg 18MiB | | 0 1263 G /usr/bin/gnome-shell 58MiB | | 0 1572 G /usr/lib/xorg/Xorg 157MiB | | 0 1705 G /usr/bin/gnome-shell 113MiB | | 0 2938 G ...quest-channel-token=6894681319300380423 46MiB | +-----------------------------------------------------------------------------+ $ python Python 3.6.10 |Anaconda, Inc.| (default, Mar 25 2020, 23:51:54) [GCC 7.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import torch >>> print(torch.cuda.is_available()) True
参考文献
- Ubuntu 18.04 LTSインストールガイド【スクリーンショットつき解説】
- NVIDIA搭載機にUbuntuをインストールできない問題の解決 - 回れ右の内輪差
- Ubuntu 18.04 LTSが起動しない?Waylandを無効にすれば起動するかも! - LFI
- 18.04 LTS インストールできましたが、起動途中でフリーズしてしまいます - Ubuntu日本語フォーラム
- 【Ubnutu】リカバリーモードで起動する方法【18.04】 - taketiyo.log
- 【初心者向け】Ubuntu18.04にAnacondaを導入しよう! - Samurai Works
- Anaconda installer archive
- 簡単にUbuntuにVSCode (Visual Studio Code) をインストールする方法 - karelie
- Ubuntu 18.04の右クリックメニューから「空のドキュメント」を作成する - Sickly Life Blog
- Ubuntuでホームディレクトリの中身を英語にする - Qiita
- PyTorch
- UbuntuにRTX2080Tiドライバーのインストール - 日本に西の端に棲息する研究者見習いの備忘録
- Ubuntu18.04にRTX 2080のドライバーをインストールする
- Ubuntu18.04にCUDAをインストールする - Qiita
- 開発メモ その114 Ubuntu 18.04でNvidia Driverをインストールする - A certain engineer "COMPLEX"
【論文解説】Real-world Anomaly Detection in Surveillance Videos
監視カメラ映像の異常検知に関する論文。 内容が弱ラベル訓練データをmultiple instance learning (MIL) で訓練するという、 個人的にノータッチの手法だったので読んでみたが、中身は非常に分かりやすかった。
- Title: Real-world Anomaly Detection in Surveillance Videos
- Authors: Waqas Sultani, Chen Chen, Mubarak Shah
- Conference: CVPR2018
1. 概要
監視カメラ映像における実世界異常検知のデータと手法に関する研究。 1900本128時間の映像からなる大規模データセットをYouTube等から選別して異常・正常映像を同数用意。 multiple instance learning(MIL)により、異常を含む映像か否かのみを付与した弱ラベル訓練データでの学習を可能にしている。 映像から異常セグメントを見つけるだけなく、そのセグメントにおける異常行動認識も行う。
2. 先行研究との差異
異常検知は、映像を複数分割したセグメントの異常度をランキングするタスクとして考えられる。 近年のランキング手法は、Deep Learningがコンピュータービジョンの様々な分野でSOTAを達成しているが、 正常と異常のアノテーションデータが大量に必要となる。
提案手法ではアノテーション作業を緩和するために、弱ラベル訓練データを利用している。 これにより、セグメントレベルではなく映像レベルで異常をラベリングすることができる。 つまり、ある映像に異常なセグメントがあれば、その映像は異常とラベリングされる。 弱ラベル訓練データの学習は、教師あり学習の一種であるmultiple instance learningにより可能になる。 ある映像の複数セグメントを1つのバッグと考え、異常ラベルの映像はどのセグメントが異常かを学習して、 異常度に応じたランキングを行う。
また異常検知手法として、近年、正常な振舞いを学習したオートエンコーダーの再構築ロスを利用した研究がある。 提案手法でもこの手法を利用するが、正常だけでなく異常な振舞いも考慮している。
3. 手法
3.1 アルゴリズム
手法の全体像を図1に示す。異常映像と正常映像を入力として与え、時間軸を保持したまま32個のセグメントに分割し、それぞれ陽性バッグと陰性バッグに入れる。各セグメントは動画分類器の一種であるC3Dによって特徴抽出されたのち全結合層を通り、陽性バッグと陰性バッグ各々の最大スコアを利用してランキングロスを計算する。
図1 手法の全体像
ランキングロスは式(6)のように3つのロスの和となる。が陽性バッグ、が陰性バッグ、が両者のロス、が異常映像の各セグメントの異常スコア、が正常映像の各セグメントの異常スコア、が2つ目のロスの重み、が3つ目のロスの重みとなる。
1つ目のロスは、図1に示す通り陽性バッグと陰性バッグの最大ロスを利用したもので、両者の差が大きくなるように学習する。陰性バッグの最大スコアとなるセグメントはいわゆるHard NegativeであるためFalse Positiveとならないよう、このロスによってスコアが低くなるよう学習が行われる。
2つ目のロスは、映像はセグメントの連続であるため、隣合うセグメントのスコアの変化はスムーズである必要があるという制約となる。3つ目のロスは、異常は突発的に起こるためスコアがまばらに(小数のセグメントのスコアが高く)なるようにという制約となる。
3.2 データセット
既存の異常検知向けの映像データセットは、全体的に映像の数が少なく長さも短い。また異常のバリエーションが限られており、そのうち幾つかは異常とは呼べないものもある。
提案手法で作成したデータセットは、トリミングしていない比較的長い監視カメラ映像となる。公共の安全において重要な13個の実世界の異常な振舞い(虐待、逮捕、放火、突撃、事故、強盗、爆発、喧嘩、強盗、射撃、窃盗、万引き、破壊行為)が含まれている。10人のアノテーターを訓練して、YouTubeとLiveLeakから"car crash"や"load accident"など多言語でテキストサーチして収集。編集動画、いたずら動画、手持ちカメラ、不明瞭な異常などは含めない。正常・異常各々950本の計1900本の映像からなる。データセットはここからダウンロードできる。
なお訓練だけなら映像レベルのアノテーションで問題ないが、テストには異常なフレームのアノテーションが必要になるため、複数人のアノテーターが付けた平均を異常なフレームとしてアノテーションしている。訓練・テストに利用した正常・異常映像の数を表1に示す。
表1 訓練・テストに利用した正常・異常映像の数
normal | anomaly | |
---|---|---|
train | 800 | 810 |
test | 150 | 140 |
3.3 実装
映像のフレームレートは30FPSに固定して、各セグメントを240x320x16にして、正常・異常共に30バッチずつ入力。 フレームベースで描いたROCのAUCで評価を行う。
4. 評価
4.1 異常検知
表2に既存手法と提案手法のAUCによる評価結果を示す。 先に述べたようにほとんどの異常な振舞いは短時間で発生する。Binary classifier(バイナリ分類器)は、提案手法のように映像をセグメント分割しないため、多くの正常セグメントを含む異常とラベリングされた映像の異常検知には利用できない。Hasan et al.[18]は、正常映像で訓練したオートエンコーダーである。正常セグメントはよく分類できるが、未知の正常セグメントで高い異常スコアが出る傾向にある。Lu[28] et al.は辞書学習で、[18]に比べて大幅に精度は高くなるが、ロバストでない。提案手法は、既存手法のすべての課題を解決できており、[28]に比べて精度が10ポイント程上昇している。2つ目と3つ目のロスで制約を加えた効果も見られる。
表2 既存手法と提案手法のAUC
図2にテストデータの定性評価を示す。赤い窓が異常範囲のGround Truth、縦軸が異常スコア、横軸が時間を示す。適切に異常検知できたケースでは、Ground Truthと予測した異常スコアの高い部分が重なっていることが分かる。一方、暗闇の侵入を正常と判断したり、集団の出現を異常と判断するなどの誤判定も見られる。
図2 テストデータの定性評価
表3に正常映像のみで評価したアラーム間違い率を示す。正常と異常が両方含まれる訓練データで学習しているため、アラーム間違い率が1.9と低い。
表3 正常映像のアラーム間違い率
4.2 異常行動認識
異常行動認識のために、TCNN(Tube Convolutional Neural Network)を利用している。TCNNでは、C3Dの一部をToI(tube of interest)プーリング層に置き換えている。ToIプーリング層は、全セグメントの特徴を統合して1つの映像の特徴とする。これによりend-to-endな異常行動認識が可能になる。表4に異常行動認識の精度比較を示す。C3Dの一部をToIプーリング層に置き換えることにより、5ポイント程精度が向上していることが分かる。
表4 異常行動認識の精度比較
まとめ
正常映像と異常映像を同数含む1900本128時間という膨大な映像データセットを作成し、映像の異常フレームのラベリングという非常にコストが高いアノテーション作業を、弱ラベル訓練データでも学習可能なMILを利用することで映像単位まで簡略化し、映像の異常検知において既存手法を10ポイントも上回り、さらにTCNNによって異常行動認識まで可能にした。
所感
まとめに書いたようにすごく良くできた論文だった。ただし少しできすぎている感がある。 例えば、セグメントごとに異常スコアを算出している認識だったが、評価指標のAUCを算出するために描いたROCが何故かフレームベースだった。あとTCNN(ToIプーリング層)の部分もあっさりしていて、もう少し詳細な説明が欲しい。あとロス関数の2つの制約のうち、どちらの効果が大きいのかablation testの結果が見たいところ。 また自分の理解力の問題でもあるが、32個のセグメント分割と30FPSのフレームレートと16フレームの入力の関係が結び付かなかった。これはコードを読めば分かるかも知れないが、MATLABで実装っていう。。。 ただし、非常に納得できる内容だったし、今後の映像異常検知のベースとなる研究であると思う(というかなってきている)。 特に、データは正常映像だけでは限界があるので異常映像も同時に学習させるべきというのと、異常部分にフォーカスするために映像はセグメント分割すべきというのがポイントだと思った。
Torchreid入門
Person ReIDライブラリーのTorchreid がいい感じだったので簡単まとめておく。 チュートリアル に色々な使い方が記載されているが、ここでは以下の3つについてまとめている。
- 訓練
README.mdのGet started: 30 seconds to Torchreid
。 Market1501で距離学習モデルを訓練する。 - テスト
チュートリアルのTest a trained model
。 1で訓練でしたモデルの精度を算出する。 - ランキング結果の可視化
チュートリアルのVisualize ranking results
。 1で訓練したモデルでqueryに対するgalleryの検索結果を可視化する。
コードはこちら。
1. 訓練
データ、モデル定義、最適化関数は、訓練だけでなくテストとランキング結果の可視化でも必要になるので、
これらを取得する共通メソッドget_items()
を定義しておく。
また訓練ではschedulerも必要なので、それもリターンする。
これらをtrain()
に渡してモデルを訓練する。
# Training
datamanager, model, optimizer, scheduler = get_items()
train(datamanager, model, optimizer, scheduler)
データはdatamanager
、モデル定義はmodel
、最適化関数はoptimizer
、
スケジューラーはscheduler
に格納する。
データ
今回は訓練データとテストデータ共にmarket1501を利用する。 各々sourcesとtargetsに指定するが、他のデータ(との組合せ)を記載することも可能。モデル定義
ベースモデルはResNet50を利用。 メトリックの指定方法など、具体的な設定方法はまだ勉強中。最適化関数
無難にAdamを利用。pytorch.optim
に実装されているのなら利用できる予感。スケジューラー
シングルステップを指定。pytorch.optim.lr_scheduler
に実装されているのなら利用できる予感。
def get_items(): # Step 2: construct data manager datamanager = torchreid.data.ImageDataManager( root='D:/workspace/datasets', sources='market1501', targets='market1501', height=256, width=128, batch_size_train=32, batch_size_test=100, transforms=['random_flip', 'random_crop'] ) # Step 3: construct CNN model model = torchreid.models.build_model( name='resnet50', num_classes=datamanager.num_train_pids, loss='softmax', pretrained=True ) model = model.cuda() # Step 4: initialise optimiser and learning rate scheduler optimizer = torchreid.optim.build_optimizer( model, optim='adam', lr=0.0003 ) scheduler = torchreid.optim.build_lr_scheduler( optimizer, lr_scheduler='single_step', stepsize=20 ) return datamanager, model, optimizer, scheduler
上で定義した4つ変数を引数として訓練を実施。
engine
を作ってrun
を実行する。
チェックポイントをsave_dir
に保存してくれる。
また訓練時はtest_only
をFalse
にする。
def train(datamanager, model, optimizer, scheduler): # Step 5: construct engine engine = torchreid.engine.ImageSoftmaxEngine( datamanager, model, optimizer=optimizer, scheduler=scheduler, label_smooth=True ) # Step 6: run model training and test engine.run( save_dir='../experiments/models/checkpoints', max_epoch=60, eval_freq=10, print_freq=10, test_only=False )
訓練が開始されると次のように標準出力される。 GeForce GTX 1080だと、60エポックは1時間46分かかり、モデル精度はRank-1で85.2%となった。
> python .\main.py Building train transforms ... + resize to 256x128 + random flip + random crop (enlarge to 288x144 and crop 256x128) + to torch tensor of range [0, 1] + normalization (mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) Building test transforms ... + resize to 256x128 + to torch tensor of range [0, 1] + normalization (mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) => Loading train (source) dataset => Loaded Market1501 ---------------------------------------- subset | # ids | # images | # cameras ---------------------------------------- train | 751 | 12936 | 6 query | 750 | 3368 | 6 gallery | 751 | 15913 | 6 ---------------------------------------- => Loading test (target) dataset => Loaded Market1501 ---------------------------------------- subset | # ids | # images | # cameras ---------------------------------------- train | 751 | 12936 | 6 query | 750 | 3368 | 6 gallery | 751 | 15913 | 6 ---------------------------------------- **************** Summary **************** source : ['market1501'] # source datasets : 1 # source ids : 751 # source images : 12936 # source cameras : 6 target : ['market1501'] ***************************************** => Start training Epoch: [1/1][10/404] Time 0.188 (0.833) Data 0.000 (0.505) Loss 6.7096 (6.7458) Acc 0.00 (0.62) Lr 0.000300 eta 0:05:28 Epoch: [1/1][20/404] Time 0.190 (0.511) Data 0.001 (0.253) Loss 6.7111 (6.8223) Acc 0.00 (0.94) Lr 0.000300 eta 0:03:16 Epoch: [1/1][30/404] Time 0.187 (0.404) Data 0.001 (0.169) Loss 6.6187 (6.7905) Acc 0.00 (1.15) Lr 0.000300 eta 0:02:31 ... Epoch: [60/60][380/404] Time 0.188 (0.202) Data 0.000 (0.013) Loss 1.0709 (1.0722) Acc 100.00 (99.99) Lr 0.000003 eta 0:00:04 Epoch: [60/60][390/404] Time 0.196 (0.202) Data 0.001 (0.013) Loss 1.0650 (1.0722) Acc 100.00 (99.98) Lr 0.000003 eta 0:00:02 Epoch: [60/60][400/404] Time 0.194 (0.202) Data 0.000 (0.012) Loss 1.0691 (1.0722) Acc 100.00 (99.98) Lr 0.000003 eta 0:00:00 => Final test ##### Evaluating market1501 (source) ##### Extracting features from query set ... Done, obtained 3368-by-2048 matrix Extracting features from gallery set ... Done, obtained 15913-by-2048 matrix Speed: 0.0236 sec/batch Computing distance matrix with metric=euclidean ... Computing CMC and mAP ... ** Results ** mAP: 68.4% CMC curve Rank-1 : 85.2% Rank-5 : 93.6% Rank-10 : 95.9% Rank-20 : 97.3% Checkpoint saved to "../experiments/models/checkpoints/model.pth.tar-60" Elapsed 1:45:48
2. テスト
テストとランキング結果の可視化のコードはほどんど同じ。 ロードした学習済みモデルの重みについて、精度評価するか距離計算するかの違い。 先に述べたようにデータ、モデル定義、最適化関数を引数として渡す。
# Test weight_path = '../experiments/models/model_market1501_resnet50.pth.tar-60' torchreid.utils.load_pretrained_weights(model, weight_path) test(datamanager, model, optimizer) vis_rank(datamanager, model, optimizer)
engine.run()
でtest_only
をTrue
にするとテスト(精度評価)になる。
def test(datamanager, model, optimizer): # Step 5: construct engine engine = torchreid.engine.ImageSoftmaxEngine( datamanager, model, optimizer=optimizer ) # Step 6: run model training and test engine.run( test_only=True
訓練時、最後に保存したチェックポイントをロードしているので精度が同じになる。
Successfully loaded pretrained weights from "../experiments/models/model_market1501_resnet50.pth.tar-60" ##### Evaluating market1501 (source) ##### Extracting features from query set ... Done, obtained 3368-by-2048 matrix Extracting features from gallery set ... Done, obtained 15913-by-2048 matrix Speed: 0.0317 sec/batch Computing distance matrix with metric=euclidean ... Computing CMC and mAP ... ** Results ** mAP: 68.4% CMC curve Rank-1 : 85.2% Rank-5 : 93.6% Rank-10 : 95.9% Rank-20 : 97.3%
3. ランキング結果の可視化
ランキング結果の可視化でも2と同様に精度算出してくれる。
さらに画像検索の結果も保存してくれる。
engine.run()
のsave_dir
に画像検索結果の保存先を指定して、
visrank
をTrue
にする。
def vis_rank(datamanager, model, optimizer): #torchreid.utils.load_pretrained_weights(model, weight_path) # Step 5: construct engine engine = torchreid.engine.ImageSoftmaxEngine( datamanager, model, optimizer=optimizer ) # Step 6: run model training and test engine.run( save_dir='../experiments/', test_only=True, visrank=True )
標準出力は次のようになる。
Successfully loaded pretrained weights from "../experiments/models/model_market1501_resnet50.pth.tar-60" ##### Evaluating market1501 (source) ##### Extracting features from query set ... Done, obtained 3368-by-2048 matrix Extracting features from gallery set ... Done, obtained 15913-by-2048 matrix Speed: 0.0242 sec/batch Computing distance matrix with metric=euclidean ... Computing CMC and mAP ... ** Results ** mAP: 68.4% CMC curve Rank-1 : 85.2% Rank-5 : 93.6% Rank-10 : 95.9% Rank-20 : 97.3% # query: 3368 # gallery 15913 Visualizing top-10 ranks ... - done 100/3368 - done 200/3368 - done 300/3368 - done 400/3368 - done 500/3368 ...
検索結果は次の通り。 1列目のように得意なqueryは全正解だが、 2列目のように画像の一部がqueryになると著しく正解率が下がる。 また3列目のようにリュックと服の色の区別ができている訳でもないみたい。
まとめ
様々なデータやモデルが気軽に非常に便利。
ただし用意されたものだけでは痒い所に手は届かないので、
チュートリアルの
Use your own dataset
とDesign your own Engine
を参考に改良する必要がある。
PyTorch物体検出チュートリアルを魔改造
PyTorchの物体検出チュートリアルが、 個人的にいじりたい場所だらけだったので、色々と魔改造してみた。 コードはこちら。
概要
チュートリアルではTrainingだけだが、今回はTestに関するコードも実装している。 それを含めて以下が今回魔改造した点。 TrainingとTestで各々3つずつポイントがある。
1. Training
1.1. データのCSV化
チュートリアルではデータセットをPyTorchの手続きに従ってクラス化していたが、 自分はほとんどのデータセットを一旦CSVファイルにまとめてからクラス化している。 CSV化することでPandasのDataFrameにできるので、色々な実験をしたい時に便利。1.2. モデルをFaster RCNNに変更
ホントは様々なモデルが扱えるMMDetection が良かったが、自分にはちょっとハードルが高かった。 魔改造第2弾があれば使ってみたい。今回はTorchVision。 チュートリアルではMask RCNNを使っていたが、特にマスクは不要なのでFaster RCNNに変更した。 ただし、これはチュートリアルにも例として呼び出し方が載っている。 あとMask RCNNは、今回は関係ないけど、人のマスクでは頭が真っ平になるのが気に食わない っていうのもあって敬遠している。1.3. チェックポイントを保存
チュートリアルではモデル自体保存していないが、テストしたいのでチェックポイントを保存できるようにした。
2. Test
2.1. 物体検出
保存したモデルをロードして物体検出を実行。 検出結果は例によってCSV化している。2.2. スコアをVOC方式で算出
cocoapiにより、訓練中にCOCO方式で算出されたmAPが出力されるが、 出力結果を変数として簡単には取得できなかったので、Cartucho/mAPを利用した。 これにより、mAPを変数として取得できるようになり、またTP/FP/Recall/Precisionなども取得できるようになっている。2.3. Ground Truthと検出結果の描画
タイトルの通り。 スコアを見るだけは分からない具体的な誤検出や未検出の原因を画像で確認する。
1. Training
まずはTrainingから。
train.py
に実装。
訓練自体についてはほとんどタッチしていないが、その前後をいじっている。
1.1 データのCSV化
コードは外部ファイル化している。
他のデータセットも増やせるよう、データセット用のディレクトリーを用意して、./datasets/penn_fudan_ped.py
に実装している。
train.py
のmain()
では以下のように呼び出している。
最終的には、trainとtestのDataLoaderを返しているが、それらを取得するためのget_dataset()
の引数はCSVファイルのパスになる。
CSVファイルはmake_csv()
にデータセットパスとCSV出力パスを渡すことで作成できる。
from datasets import penn_fudan_ped def main(): # Get data. if not os.path.exists(args.anno_path): penn_fudan_ped.make_csv(args.data_dir, args.anno_path) train_data_loader, test_data_loader = penn_fudan_ped.get_dataset(args.anno_path)
./datasets/penn_fudan_ped.py
の中身については詳解しないが、
get_dataset()
はどのデータにも横展開できるコードになっているが、
make_csv()
はチュートリアルと同様、データセットごとに対応したコードを実装してCSVファイルに落とし込む必要がある。
なおCSVファイルにすることにより、Train時は学習データのフィルタリング、
Test時はスコア算出やバウンディングボックス描画などで利便性が出てくる。
1.2. モデルをFaster RCNNに変更
データとモデルは別ファイルにした方がよいというのが経験から得られているので、
./src/models.py
に実装している。
ただし、ほとんどチュートリアルに載ってるのをそのまま。
クラス数はTrainingとTestで共通なので、引数として渡すようにしている。
def get_fasterrcnn_resnet50(num_classes, pretrained=False): model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=pretrained) in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) return model
1.3. チェックポイントを保存
モデルの重みを保存しないと、テストだけしたいという時に困るので。
./src/train.py
のmain()
に以下のように実装している。
# Save a model checkpoint. model_ckpt_path = args.model_ckpt_path_temp.format(args.dataset_name, args.model_name, epoch+1) torch.save(model.state_dict(), model_ckpt_path) print('Saved a model checkpoint at {}'.format(model_ckpt_path))
訓練中は以下のように標準出力され、 最後にcocoapiのスコアとチェックポイントを保存した旨が表示される。
$ python .\train.py Loading a model... Epoch: [0] [ 0/60] eta: 0:05:40 lr: 0.000090 loss: 0.9656 (0.9656) loss_classifier: 0.7472 (0.7472) loss_box_reg: 0.1980 (0.1980) loss_objectness: 0.0093 (0.0093) loss_rpn_box_reg: 0.0111 (0.0111) time: 5.6684 data: 3.8234 max mem: 3548 Epoch: [0] [10/60] eta: 0:00:47 lr: 0.000936 loss: 0.7725 (0.6863) loss_classifier: 0.4681 (0.4597) loss_box_reg: 0.1726 (0.2008) loss_objectness: 0.0127 (0.0163) loss_rpn_box_reg: 0.0070 (0.0095) time: 0.9561 data: 0.3484 max mem: 4261 Epoch: [0] [20/60] eta: 0:00:28 lr: 0.001783 loss: 0.4149 (0.5500) loss_classifier: 0.2203 (0.3413) loss_box_reg: 0.1567 (0.1806) loss_objectness: 0.0127 (0.0188) loss_rpn_box_reg: 0.0060 (0.0093) time: 0.4775 data: 0.0011 max mem: 4261 Epoch: [0] [30/60] eta: 0:00:19 lr: 0.002629 loss: 0.3370 (0.4652) loss_classifier: 0.1338 (0.2607) loss_box_reg: 0.1504 (0.1787) loss_objectness: 0.0094 (0.0152) loss_rpn_box_reg: 0.0084 (0.0106) time: 0.4745 data: 0.0011 max mem: 4261 Epoch: [0] [40/60] eta: 0:00:12 lr: 0.003476 loss: 0.2195 (0.3939) loss_classifier: 0.0534 (0.2088) loss_box_reg: 0.1345 (0.1620) loss_objectness: 0.0063 (0.0129) loss_rpn_box_reg: 0.0094 (0.0102) time: 0.4814 data: 0.0011 max mem: 4458 Epoch: [0] [50/60] eta: 0:00:05 lr: 0.004323 loss: 0.1478 (0.3427) loss_classifier: 0.0442 (0.1765) loss_box_reg: 0.0842 (0.1432) loss_objectness: 0.0021 (0.0118) loss_rpn_box_reg: 0.0091 (0.0112) time: 0.5083 data: 0.0014 max mem: 4623 Epoch: [0] [59/60] eta: 0:00:00 lr: 0.005000 loss: 0.1176 (0.3073) loss_classifier: 0.0433 (0.1562) loss_box_reg: 0.0561 (0.1293) loss_objectness: 0.0009 (0.0102) loss_rpn_box_reg: 0.0117 (0.0117) time: 0.5165 data: 0.0013 max mem: 4623 Epoch: [0] Total time: 0:00:34 (0.5814 s / it) creating index... index created! Test: [ 0/50] eta: 0:03:14 model_time: 0.1120 (0.1120) evaluator_time: 0.0010 (0.0010) time: 3.8935 data: 3.7785 max mem: 4623 Test: [49/50] eta: 0:00:00 model_time: 0.0900 (0.0962) evaluator_time: 0.0010 (0.0012) time: 0.0994 data: 0.0006 max mem: 4623 Test: Total time: 0:00:08 (0.1791 s / it) Averaged stats: model_time: 0.0900 (0.0962) evaluator_time: 0.0010 (0.0012) Accumulating evaluation results... DONE (t=0.01s). IoU metric: bbox Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.641 Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.987 Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.859 Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000 Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.325 Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.644 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.286 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.706 Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.706 Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000 Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.700 Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.706 Saved a model checkpoint at ../experiments/models/checkpoints/PennFudanPed_FasterRCNN-ResNet50_epoch=1.pth
なお、cocoapiをWindows10で利用する場合、インストールに失敗する場合がある。 その際はClone of COCO APIを利用すると上手くいった。
2. Test
Trainingで保存したモデルの重みをロードして、 物体検出とそのスコアの算出および検出結果の画像描画をTestとして行う。
2.1. 物体検出
まずはシンプルに物体検出を実施。
コードは./src/test.py
のdetect_objects()
を参照。
以下のように検出結果が標準出力される。
これは例によってCSVファイルに保存される。
$ python .\test.py Loading a model from ../experiments/models/PennFudanPed_FasterRCNN-ResNet50_epoch=10.pth Detecting objects... 100% ========================== DETECTION RESULTS ========================== label score xmin ymin xmax ymax image_path 0 1 0.999 294 129 447 419 D:/workspace/datasets/PennFudanPed/PNGImages/F... 1 1 0.999 361 135 456 399 D:/workspace/datasets/PennFudanPed/PNGImages/F... 2 1 0.999 207 100 350 382 D:/workspace/datasets/PennFudanPed/PNGImages/F... 3 1 0.999 0 111 88 383 D:/workspace/datasets/PennFudanPed/PNGImages/F... 4 1 0.999 37 100 97 362 D:/workspace/datasets/PennFudanPed/PNGImages/F... 5 1 0.999 40 106 87 268 D:/workspace/datasets/PennFudanPed/PNGImages/F... 6 1 0.998 268 92 397 374 D:/workspace/datasets/PennFudanPed/PNGImages/F... 7 1 0.998 260 191 294 345 D:/workspace/datasets/PennFudanPed/PNGImages/F... 8 1 0.998 262 97 338 357 D:/workspace/datasets/PennFudanPed/PNGImages/F... 9 1 0.999 384 192 551 482 D:/workspace/datasets/PennFudanPed/PNGImages/F... Detection results saved to ../experiments/results/tables/dets.csv
2.2. スコアをVOC方式で算出
1.1 データのCSV化
で保存したGround TruthのCSVファイルと
2.1. 物体検出
で保存した検出結果のCSVファイルを照合して、VOC方式のmAPを算出する。
Cartucho/mAPを利用しているが、
以下のようなmAP以外のスコアを得るためにmain.py
を改修したmain_ex.py
を
./src/test.py
のdetect_objects()
から呼び出している。
以下が実行結果。
Making gt text files: 100%|██████████████████████████████████████████████████████████████████████████████| 50/50 [00:00<00:00, 769.32it/s] Making det text files: 100%|█████████████████████████████████████████████████████████████████████████████| 50/50 [00:00<00:00, 735.01it/s] 65.53% = 1 AP mAP = 65.53% class_name ap recall precision gt n_det tp fp 0 1 0.655343 1.0 0.581395 125 215 125 90 Score saved to ../experiments/results/tables/score.csv
結果を見るとrecall(=tp/gt)は1.0だが、precision(=tp/n_det)は0.58と低くなっている。
つまり誤検出が多いということだが、具体的には何を誤検出しているのか?
それを2.3. Ground Truthと検出結果の描画
で確認する。
2.3. Ground Truthと検出結果の描画
./src/test.py
のdraw_gt_n_det()
を実行すると、
テスト画像の上にGround Truthと検出結果が描画される。
以下のように描画結果(青がGround Truth、黄緑が検出結果)を見てみると、
自転車に乗っている人、小さく写る人、オクルージョンのある人が
アノテーションされておらず、それらを検出しているため誤検出となり、
Precisionの低下を導いていることが分かる。
あと、non-maximum suppressionが上手くできてないというのも原因の1つ。
まとめ
チュートリアルだと、 テストがほとんどなかったが、説明したコードを利用すれば基礎な分析はできるようになった。 あとは、訓練時にTorchVisionに依存しているので、 MMDetectionを利用して多数のモデルが使えるようにしたり、 Albumentationsを用いたData Augmentationや、 Schedulerの充実などを行いたい。