ディープラーニングを使った画像の異常検知について、研究が盛んに行われています。
工業製品を対象に、正常データのみを使って異常品を検出するアプローチとしては、
今のところ、@daisukelab さんの自己教師あり学習が一番有力です。こちらの研究では、
課題として、一部の画像で精度が伸び悩み、異常クラスのデータの作り方を試行錯誤した
とのコメントがありました。
そこで、本稿では、多様な異常画像の自動生成方法について模索してみたいと思います。
※こちらはPythonデータ分析勉強会#16の発表資料です。
※コード全体はこちらに置きました。
#結論から
- 先行研究の自己教師あり学習では、「正常」、「線を描画した異常」の2クラスに対して、本稿では「歪みを加えた異常」を追加して3クラスの深層距離学習(下図参照)で学習させた。
- 3クラスになったことにより、深層距離学習の最新手法「AdaCos」の使用も可能となった。
- 本手法は、一部データでは精度が良くなったものの、悪くなるデータもあり、諸刃の剣となった。
#画像の考察
先行研究は、非常にシンプルな方法でありながら、多くの欠陥を捉えることに成功しています。
さらに、@daisukelab さんのfasi.aiのコードは、非常にすっきりしていて可読性も高いです。
画像の異常検知に興味がある方は、是非ご一読されることをお勧めます。
先行研究では、MVtech ADのScrew、Transistor、Grid、Pillの4つのデータでAUCが0.9を下回る
結果となり、試行錯誤をしたとの内容でした。
今回はPill以外のデータを対象にしたいと思います。
Pill以外の3つのデータは以下の通りです。
異常画像を見ると、傷や汚れというより、「歪み」を生じているものが多く、この検出が
難しいため、AUCが伸びにくいものと推察されます。
そこで、正常データから**「歪みデータ」を自動的に生成し**、異常検知性能を押し上げる方法を模索します。
#「歪みデータ」の自動生成
歪みデータを作るには、色々な手法があると思われますが、本稿では画像の一部を回転する方法を
採用しました。
これはKerasのDataGeneratorを使えば簡単に実行可能です。
from keras.preprocessing.image import ImageDataGenerator
small_fig = fig[begin1:end1,begin2:end2]
datagen = ImageDataGenerator(
rotation_range=30,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=False,
vertical_flip=False)
for d in datagen.flow(np.expand_dims(small_fig, axis=0), batch_size=1):
# datagen.flowは無限ループするためループを抜ける
break
fig[begin1:end1,begin2:end2] = d[0]
##意味のある場所に適用
ただし、背景画像の一部を回転させても意味はありません。
そこで対象物を自動検知するアルゴリズムを実装しました。
Screwなどでは、学習データにおいて試験片の方向や位置が多少ズレています。
それらに対し、学習データを平均化すると試験片が消えたような画像が得られます
(2.Training data average)。
その後、平均化した画像とターゲットとなる画像の差分をとり、二値化(Binary map)
することで試験片の範囲を自動検知することができます。自動検知を実行しながら、
画像の一部を回転させると、以下のような画像が得られます。
歪んでいるような画像が得られました。
#本手法の手順
- 正常画像を用意する。
- 正常画像に細い線を描き、「線を描いた異常」クラスの画像を生成する。(実験では、OpenCVを用いて、下図のような画像を生成)
- 提案手法を正常画像に適用し、「歪みを加えた異常」クラスの画像を生成する。
- 用意した3クラス(下図参照)の分類問題を深層距離学習で学習させる。
- 学習後、LOFやコサイン類似度で異常度を算出する。
#実験
MVtech ADを使って実験を行いました。
コード全体はこちらに置きました。Colabで実行可能です。
##条件
- 深層距離学習は「L2-SoftmaxLoss」、「ArcFace」、「AdaCos」(クラス数が3以上になったことにより適用可能に!)を使用。
- 「L2-SoftmaxLoss」はLOFにて異常度を算出
- 「ArcFace」と「AdaCos」は全ての学習(正常)データに対し、コサイン類似度を計算し、一番大きい値を採用する(「L2-SoftmaxLoss」「ArcFace」については、こちらの記事を参考にしてください)
- epochは10、最適化手法はAdam
- バッチサイズは32
- ベースモデルはMobileNet V2(学習済モデルを使用、つまり転移学習)
- 異常クラス(線、歪み)はバッチ毎にランダムに描画するため、学習に使う関数はKerasのtrain_on_batchを使用
- 10回試行してAUCを算出
- データはScrew、Transistor、Gridを使用
##AUCの結果
実験結果では、class2(「正常」と「線を描いた異常」の2クラス)がベースライン、
class3(class2のクラス+「歪みを加えた異常」)が提案手法になります。
- Screw
Screwでは、class2に対し、提案手法(class3)を適用するとAUCが低下して
しまいました。低下した理由は後で、考察で検討します。
- Grid
Gridでは、期待とおり、ベースライン(class2)よりも、提案手法(class3)の方が
AUCが上昇しました。狙い通り、歪みが検出できたかと思います。異常部分が検出
できているかは可視化で検証します。
- Transistor
Transistorでは、Gridと同様に提案手法(class3)を適用するとAUCが上昇しました。
ArcFaceに至っては0.1以上の向上が確認できます。
##可視化の結果
先行研究にあるように、Grad-CAMによる異常部分の可視化も行ってみました。
ただし、「AdaCos」をベースにしています。そのため、異常クラスが二つあり、
それぞれのクラスでGrad-CAMを実行しています。
まずは、Gridのデータです。
「歪みによる異常(Distortion)」の可視化が全てのデータに対し、うまく適合しています。
ただし、異常が複数ある場合、一個しか検出できない場合もあるようです。
次にTransistorです。
Transistorでは、「線による異常(Slight line)」の可視化がほとんどのデータに対し、うまく適合しています。
Gridとは反対の結果になりました。ここら辺はどちらのクラスの異常を見れば良いのか、データの種類によって
変わるかもしれません。
#実験(番外編)
最後に、ベースモデルの変更(MobileNetV2→EfficentNetB0)を行ってみます。
##条件
先の実験とほとんど同じ条件です。違う点は以下のとおりです。
- ベースモデルはEfficentNetB0(学習済モデルを使用、つまり転移学習)
- 5回試行してAUCを算出
##AUCの結果
※中央値が見えづらいですが、後で表にまとめて記載しております。
- Screw
Screwでは、class2であってもAUCが0.5を下回る結果に。
- Grid
Gridでは、MobileNetV2と同じような結果に。
やはり、class3にした方が良いスコアが出ます。
- Transistor
Transistorでも、MobileNetV2と同じような結果に。
やはり、class3にした方が良いスコアが出ます。
#実験結果のまとめ
ここまでの結果を中央値で比較してみます。
Screwを除くと、ベースライン(class2)に対し、提案手法(class3)を適用するとAUCの上昇が
確認できます。また、距離学習の種類については、どれもそんなに差がついているようには
見えませんが、ArcFaceが割と良いスコアを示しています。
CNNモデルによるAUCの差は、AUC0.9超えを達成しているEfficientNetB0が良さそうに見えます。
EfficientNetB7とかを適用すると、より顕著にAUCが上昇するかもしれません(ただし、画像の
最小サイズは600×600らしいです。重い。ちなみに、B0は224×224。)
#考察(なぜScrewのAUCは低下したのか?)
ScrewのAUC低下の原因を考えてみます。
端的にいって、「歪みを加えた異常」クラスを追加することにより**「大きな」歪みは異常として
検出できるようになったが、「小さな」歪みは逆に正常に近いものとして**認識されるように
なったと思われます。
先行研究の2クラス分類では、細い線を描くことにより、傷や変色はもちろん、Screw先端に
線を描くことで「小さな」歪みを疑似的に再現することができました。ところが、3クラス目を
導入することにより、CNNのタスクがより難しくなります。
今まで、2クラス分類では、「小さな」歪みは、どちらかというと細い線のクラスだった
のに対し、3クラス分類になったことにより、CNNの特量抽出が線検出と回転検出に特化し、
「小さな」歪みは正常クラスに近くなってしまったと思われます。その結果、AUCの低下を
招いたものと推察されます。
上の図はScrewで学習させたAdaCos(class3)にGrad-CAMを適用した図です。画像は全て
異常画像です。ご覧のとおり、Screw先端の小さな歪みは全然検出できていないことが分かります。
さらに、MobileNetV2→EfficientNetB0に変更すると、class2であってもAUCが0.5を
下回る結果になりました。これはEfficientNetB0の方が、より特徴を明確に抽出できるため
「細い線」の特徴抽出がうまくいき過ぎる結果となり、「小さな」歪みは正常に近づいて
しまったものと思われます。
では、一部回転させる範囲を小さくすれば「小さな」歪みを疑似的に再現できるのではないか?
という疑問が生まれますが、範囲を小さくし過ぎるとCNNでは検出できず、学習が全然進まなく
なります。
これを改善するには画像の解像度を上げたり、あるいは試験品の方向を揃えるなど
画像に特化した工夫が必要になります。先行研究でも言及されているように、
異常データが入手可能であれば、その画像をしっかり見て分析してみることも、
有効だと思われます。
おススメの手順としては、まずは、自己教師あり学習で2クラス分類。その後、
3クラス分類を試してAUCが悪化するようなら、画像をしっかり分析するという
プロセスになります。
#まとめ
- 「歪みを加えた異常」クラスを追加して、3クラスの深層距離学習+自己教師あり学習で学習を行った。
- TransistorやGridでは、**AUCの上昇(最大でAUC:0.75→0.87)が確認できた。**一方、Screwでは、逆にAUCの低下を招いた。
- そのため、本手法を適用するかどうかは、データを見て慎重に判断しなければならない。
- Pillは歪みというよりは変色異常の画像が多いため、本手法は適さないため試していない。
- 深層距離学習の種類については、ほぼ同等の精度。ただ、「AdaCos」はパラメータチューニングが不要なため非常におススメ。