はじめに
前回の記事に続いて、今回は、MVTec AD の Capsule に挑戦します。
https://qiita.com/tetz1/items/6f92928d482534f7d2c4
※コードは準備中です。
[](
※全体のコードはこちらに置いています。
(Google colab 上で実行するためのコードを含んでいます。)
https://github.com/nshikimi/MVTecAD/tree/master/Capsule
)
データセット(MVTec AD)について(前回の繰り返し)
様々な製品(物体)について、正常な個体の画像と異常な個体の画像が含まれています。
また、異常な個体の画像には、多様な異常の種類が、きちんと分類されて含まれています。
さらに、素晴らしいのは、異常な部分を特定できるようなマスクがはじめから含まれていることです。(これは、本当に有り難いです。)
ただ、実際の現場で、このようなマスクを作るのは極めて手間がかかると思われるので、これに頼ると、その手法が実際の現場で使えない、ということが発生するかも知れません。
製品(物体)の種類は、bottle、cable、capsule、carpet、grid、hazelnut、leather、metal_nut、pill、screw、tile、toothbrush、transistor、wood、zipper の15種類です。
下記にて、入手できます。
https://www.mvtec.com/company/research/datasets/mvtec-ad/
データのサンプル
正常データ
![]() |
![]() |
![]() |
![]() |
![]() |
---|
異常(crack)
![]() |
![]() |
![]() |
![]() |
![]() |
---|
異常(faulty_imprint)
![]() |
![]() |
![]() |
![]() |
![]() |
---|
異常(poke)
![]() |
![]() |
![]() |
![]() |
![]() |
---|
異常(scratch)
![]() |
![]() |
![]() |
![]() |
![]() |
---|
異常(squeeze)
![]() |
![]() |
![]() |
![]() |
![]() |
---|
検証概要
今回の検証の概要は下記の通りです。
-
今回は、MVTecAD の Capsuleデータセットを使用
-
MVTecAD のフォルダ構成
MVTecAD
|--- capsule
|--- train
| |--- good
|--- Test
|--- good
|--- crack
|--- faulty_print
|--- poke
|--- scratch
|--- squeeze -
下記のパターンを実施
- 1000x1000の元画像を、512x512に resize して、正常と異常の二値分類問題として実施
- 1000x1000の元画像を、512x512に resize して、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、正常と異常との二値分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)に、人工的に異常(複数色の直線)を加え、異常画像を増やして、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)に、人工的に異常(白色のみの直線)を加え、異常画像を増やして、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、画像のシャープネスを加工し、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、画像の値を逆転させて(0〜1)の値を1-元の値(0〜1)に変換)し、各異常モードごとの多クラス分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)の画像を部分的に入れ替えることで、異常画像を増やして、正常と異常の二値分類として実施
- 1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)の画像を部分的に入れ替えることで、異常画像を増やして、各異常モードごとの多クラス分類として実施
-
学習には、正常だけで構成される trainフォルダのデータだけでなく、testフォルダの異常データも使用
-
学習、予測に使用した TrainデータとTestデータは、 trainフォルダ、testフォルダの正常データと異常データを、各サブフォルダごとに8:2に分割して、作成、使用
-
NNには、ResNet18を使用
-
学習には、Metric Learning/AdaCos を使用
-
Epoch数は100〜700/900 (学習が遅いもの、学習を進めて見たら変化がありそうなもの(無根拠)は900です。)
-
lr はすべて 0.1 で実施
検証結果
モデルの学習と特徴空間へのプロット
エポック数100〜700で学習したモデルから、Metric Learningの特徴(512次元)を抽出し、UMAPで2次元に可視化したものが下記のとおり。
1000x1000の元画像を、512x512に resize して、正常と異常の二値分類問題として実施
データ
使用したデータは、オリジナルデータ(10001000)を512512にresizeしたもので、見た目は上記のオリジナルデータと同じ(正方形)
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
800 | ![]() |
![]() |
![]() |
900 | ![]() |
![]() |
![]() |
※ 学習は二値分類問題として実施しましたが、各異常モードごとの差異がわかるように、推論とプロットは多クラスで行っています。
考察
学習には結構時間がかかっています。
エポック500あたりで落ち着いて来てますが、エポック500ではかなりきれいに分離・予測できています。
何も手を加えていないのに、ここまで分離・予測できるのは驚きです。
1000x1000の元画像を、512x512に resize して、各異常モードごとの多クラス分類として実施
データ
使用したデータは、オリジナルデータ(10001000)を512512にresizeしたもので、見た目は上記のオリジナルデータと同じ(正方形)
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
800 | ![]() |
![]() |
![]() |
900 | ![]() |
![]() |
![]() |
考察
二値分類に比べ、早く学習が進んでいます。(エポック200あたりで落ち着いて来ています。)
エポック500が最もよさそうです。
多クラス分類にした方が、それぞれの異常モードの特徴を分けて捉えることができるかと思ったのですが、課題が複雑になってしまったのか、サンプル数が足りなかったのか、二値分類より精度が低下してしまいました。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、正常と異常との二値分類として実施
データ
正常 | crack | imprint |
---|---|---|
![]() |
![]() |
![]() |
poke | scratch | squeeze |
---|---|---|
![]() |
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
※ 学習は二値分類問題として実施しましたが、各異常モードごとの差異がわかるように、推論とプロットは多クラスで行っています。
考察
かなり早く学習が進んでいます。(エポック100ですでに落ち着いて来ています。)
また、サイズを落としたことで、各エポックの学習にかかる時間も大幅に短縮されています。
エポック500では、異常データが一点だけ正常のエリアにプロットされている以外は、非常にきれいに分離・予測できています。
このモデルが、今回のすべてのモデル中で最も精度がいいという結果になりました。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、各異常モードごとの多クラス分類として実施
データ
正常 | crack | imprint |
---|---|---|
![]() |
![]() |
![]() |
poke | scratch | squeeze |
---|---|---|
![]() |
![]() |
![]() |
※ ラベル以外は、上記の二値分類と同じデータです。
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
考察
こちらも学習は早く進んでいます。
こちらも、エポック500が最も優れているようです。
ただし、二値分類と比較して、わずかに劣化しているようです。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)に、人工的に異常(複数色の直線)を加え、異常画像を増やして、各異常モードごとの多クラス分類として実施
データ
生成異常① | 生成異常② | 生成異常③ | 生成異常④ |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
考察
生成した異常データは、正常データに加工をしたもののみ(異常データを加工したものはない)です。
異常データを生成してデータ数を増やしているので、エポックあたりの学習時間が増加し、学習の進む速度も遅くなっています。(エポック400あたりで落ち着いて来ています。)
エポック400が最も精度が良さそうです。
ただし、他の手法に比べて、特に優れているとは言えないようです。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)に、人工的に異常(白色のみの直線)を加え、異常画像を増やして、各異常モードごとの多クラス分類として実施
データ
生成異常① | 生成異常② | 生成異常③ | 生成異常④ |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
800 | ![]() |
![]() |
![]() |
900 | ![]() |
![]() |
![]() |
考察
上と同じく、生成した異常データは、正常データに加工をしたもののみ(異常データを加工したものはない)です。
今回は、追加する異常は、白い線のみとしました。(実際の異常でも、異常個所はほとんどが白い(中身の白い粉末の色)ため)。また、少し、線を細くしました。
上と同じく、異常データを生成してデータ数を増やしているので、エポックあたりの学習時間が増加し、学習の進む速度も遅くなっています。(エポック300あたりで落ち着いて来ています。)
エポック700あたりが最も良さそうです。
上の複数の色で異常を生成したものよりはよさそうですが、他の手法に比べて、特に優れているとは言えないようです。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、画像のシャープネスを加工し、各異常モードごとの多クラス分類として実施
正常 | crack | imprint |
---|---|---|
![]() |
![]() |
![]() |
poke | scratch | squeeze |
---|---|---|
![]() |
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
考察
画像を加工して、異常が(人間の目で見て)目立つようにしてみたのですが、ほとんど効果はありませんでした。
(この手の加工(処理)は、基本的にはニューーラルネットワークの中で行う処理と重複しているらしくです。)
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、画像の値を逆転させて(0〜1の値を1-元の値(0〜1)に変換)し、各異常モードごとの多クラス分類として実施
正常 | crack | imprint |
---|---|---|
![]() |
![]() |
![]() |
poke | scratch | squeeze |
---|---|---|
![]() |
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
4400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
800 | ![]() |
![]() |
![]() |
900 | ![]() |
![]() |
![]() |
考察
学習・推論には不要な背景が白く、カプセルの一部が黒いので、学習に必要のない背景を0にして、学習に必要なカプセル本体を1に近づけることで何かが変わるかもと思ったのですが、何の効果もないどころか、精度が低下してしまいました。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)の画像を部分的に入れ替えることで、異常画像を増やして、正常と異常の二値分類として実施
データ
元画像 | 加工後 |
---|---|
![]() |
![]() |
![]() |
![]() |
緑の枠線で囲った部分を左右入れ替えて、異常データを水増ししています。
crack | imprint | poke |
---|---|---|
![]() |
![]() |
![]() |
scratch | squeeze |
---|---|
![]() |
![]() |
※コードの一部に間違いがあったので、下記のプロット図は間もなく修正します。申し訳ありません。
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
800 | ![]() |
![]() |
![]() |
900 | ![]() |
![]() |
![]() |
※ 学習は二値分類問題として実施しましたが、各異常モードごとの差異がわかるように、推論とプロットは多クラスで行っています。
考察
異常データを生成してデータ数を増やしているので、エポックあたりの学習時間も増加し、学習の進行も遅くなっています。(エポック400あたりで落ち着いて来ています。)
エポック900が最も精度がよさそうですが、特に他の手法と比べて優れているというわけでもなさそうです。
もう少し、学習を進めてみると面白いかも知れません。
1000x1000の元画像を、400x900にクロップし、200x450に resize して、さらに、対象物(カプセル)の画像を部分的に入れ替えることで、異常画像を増やして、各異常モードごとの多クラス分類として実施
データ
元画像 | 加工後 |
---|---|
![]() |
![]() |
![]() |
![]() |
緑の枠線で囲った部分を左右入れ替えて、異常データを水増ししています。
crack | imprint | poke |
---|---|---|
![]() |
![]() |
![]() |
scratch | squeeze |
---|---|
![]() |
![]() |
結果
エポック数 | trainデータ | testデータ | train+testデータ |
---|---|---|---|
100 | ![]() |
![]() |
![]() |
200 | ![]() |
![]() |
![]() |
300 | ![]() |
![]() |
![]() |
400 | ![]() |
![]() |
![]() |
500 | ![]() |
![]() |
![]() |
600 | ![]() |
![]() |
![]() |
700 | ![]() |
![]() |
![]() |
考察
異常データを生成してデータ数を増やしているので、エポックあたりの学習時間は増加していますが、学習の進行はかなり早く、エポック100ですでに落ち着きはじめています。
二値分類と多クラス分類で、どうしてこんなに差が出たのか分かりませんが、考えられるのは、二値分類としては、異常データに多様性がありすぎるが、多クラス分類としては、各異常モードごとの異常データが増えたために、異常を捉えやすくなったのかも知れません。
また、エポック500、600、700あたりでは、なかなかいい結果を出しています。
総論
capsuleは、なかなか難しいデータセットでした。(それでも、カプセルの向き等が揃っているので、MVTec ADのなかでは、まだ簡単な方だという気がしますが。)
結果として、カプセル本体のある部分だけにトリミングをするというのが、最も効果的な前処理だと言うことが分かりました。
また、エポックが進むにつれて、学習がどのように進むのかを理解する非常にいい実験になりました。