オッサンはDesktopが好き

自作PCや機械学習、自転車のことを脈絡無く書きます

一枚の異常画像からディープ・ラーニングを組んでみる

 こんにちは. 今回は異常画像が少ない場合のディープ・ラーニングの組み方について考えてみます.

f:id:changlikesdesktop:20200630072031p:plain:w300

0. 背景

 以前に,AutoEncoderを用いた異常検知を紹介しました*1 *2 *3. そのとき,異常(傷)有り画像だけを集めて学習をする方が検出精度が高くなると書きました*4. それはつまり,AutoEncoderを用いた異常検知には,大量の異常有り画像を集める必要があるということです.

 しかし,現実のデータから異常有り画像を大量に集めることは難しい(場合が多い)です. 滅多に起こらないから異常なわけですから. 「数年に一回しか異常が発生しない」なんてことも珍しく無いと思います.

 改善アルゴリズム*5や画像の水増し*6が提案されています. GANで作るというのも,流行りですね. 異常画像の少なさというのは,ディープ・ラーニングに限らず,画像処理分野の根源的な問題であるように思います. そこで今回は,たった一枚の異常画像からディープ・ラーニングを組むことにトライしてみます.

1. 学習画像

 「空飛ぶ円盤」が写ったUFO画像を題材にします. Wikipedia*7に載っていた有名な写真を異常画像に選びました.

f:id:changlikesdesktop:20200630060320j:plain:w200

 Googleで「空飛ぶ円盤」を検索すると,同様の画像が沢山見つかります. それらを見ていくと,「空飛ぶ円盤」という位なので,多くの写真が空を背景に円盤状の物体が宙を舞っている構図になっています. この構図を,意図的に作ってみます.

 先ず,空飛ぶ円盤部分を切り出してみます. 赤く塗っているのは,円盤部分と背景を区別するためです.

f:id:changlikesdesktop:20200630061040j:plain:w200

 次に,円盤を背景となる空に重ねます. 空の画像は,個人ブログ等で紹介されているものをお借りしました. ポイントは,空だけでなく,ビルや山などが一緒に写っている画像を選ぶことです. こうすることで,既存のUFO写真に近づけることができます. また,一部の領域をくり抜いて使うため,大きめの画像を選んでいます.

f:id:changlikesdesktop:20200630063408p:plain:w600
出典*8 *9 *10 *11 *12 *13 *14

 空画像から一部をくり抜きます. くり抜く大きさは,U-Netに入力する256の0.5〜2.0倍の間でランダムに決めています.

configurate_data.py

size = random.randint(int(c.IMG_SIZE/2.0), int(c.IMG_SIZE*2)) #image size
if(max_x > size):
    org_x = random.randint(0, max_x - size)
    size_x = size
else:
    org_x = 0
    size_x = max_x
if(max_y > size):
    org_y = random.randint(0, max_y - size)
    size_y = size
else:
    org_y = 0
    size_y = max_y
cropped512 = sky.crop([org_x, org_y, org_x + size_x, org_y + size_y])
cropped512 = cropped512.resize([c.IMG_SIZE*2, c.IMG_SIZE*2])
cropped512 = np.array(cropped512)

 円盤画像を空に重ねます. 円盤のサイズ,配置位置,回転角度を乱数で与えています. 背景になじませるために輝度を少し調整し,最後に,画像全体を1/2にリサイズしています.

configurate_data.py

# add ufo
ufo = ufo_org.copy() # copy original image
rate = random.uniform(0.2, 1.0) # size of 0.5 to 1.0 times            
ufo = ufo.resize([int(ufo.size[0]*rate), int(ufo.size[1]*rate)])
ufo_gray = np.array(ufo.convert("L"))# gray scale            
ufo = np.array(ufo) # tp array
bright = np.mean(cropped512)/np.mean(ufo)
ufo = ufo*bright # adjust brightness

x = random.randint(0, c.IMG_SIZE*2)
y = random.randint(0, c.IMG_SIZE*2)
rot = deg2rad(random.uniform(-30, 30)) # rotation angle 
label512 = np.zeros([c.IMG_SIZE*2, c.IMG_SIZE*2])
for i in range(ufo.shape[0]):
    for j in range(ufo.shape[1]):
        if ufo[i, j, 0] != 255 and ufo[i, j, 1] != 0 and ufo[i, j, 2] != 2:
            rot_i = int(i*math.cos(rot) - j*math.sin(rot) + 0.5)
            rot_j = int(i*math.sin(rot) + j*math.cos(rot) + 0.5)
            if 0 <= x + rot_i and x + rot_i < c.IMG_SIZE*2 and 0 <= y + rot_j and y + rot_j < c.IMG_SIZE*2:
                cropped512[x + rot_i, y + rot_j] = ufo_gray[i, j]
                label512[x + rot_i, y + rot_j] = 255
image = np.zeros([c.IMG_SIZE, c.IMG_SIZE])
label = np.zeros([c.IMG_SIZE, c.IMG_SIZE])
for i in range(c.IMG_SIZE):
    for j in range(c.IMG_SIZE):
        image[i, j] = (float(cropped512[2*i, 2*j]) + float(cropped512[2*i + 1, 2*j]) + float(cropped512[2*i, 2*j + 1]) + float(cropped512[2*i + 1, 2*j + 1]))/4.0
        label[i, j] = (label512[2*i, 2*j] + label512[2*i + 1, 2*j] + label512[2*i, 2*j + 1] + label512[2*i + 1, 2*j + 1])/4.0

 空飛ぶ円盤の学習画像とラベルの出来上がりです. やろうと思えば,無限に作れます. 円盤が大きく写った画像は,かなり違和感がありますね.

f:id:changlikesdesktop:20200630065505p:plain:w400 f:id:changlikesdesktop:20200630065516p:plain:w400 f:id:changlikesdesktop:20200630065526p:plain:w400 f:id:changlikesdesktop:20200630065538p:plain:w400
左: 学習画像,右: ラベル

2. 学習

 学習画像とラベルを1000枚づつ作り,U-Netで学習させました. 学習済みネットワークを,Google検索で見つかった本物(?)の空飛ぶ円盤画像に当ててみます. 異常(=空飛ぶ円盤)である確率が高い領域を,赤枠で囲っています.

f:id:changlikesdesktop:20200630071421p:plain:w600 f:id:changlikesdesktop:20200630071432p:plain:w600 f:id:changlikesdesktop:20200630071443p:plain:w600 f:id:changlikesdesktop:20200630071455p:plain:w600 f:id:changlikesdesktop:20200630071506p:plain:w600 f:id:changlikesdesktop:20200630071517p:plain:w600
左: 入力画像,中: 推論結果,右: 入力画像に異常領域(赤枠)を付加

 成功しているものもあれば,失敗している例もありました. 傾向としては,画像全体に広がるほどの大きな円盤と,薄く(或いは白く)写っている円盤の検出に失敗していました.

3. 考察

 これインチキじゃね?って,思われるかも知れません. やっていることは合成写真の学習ですからね. 僕自身も,わざわざ合成写真を作ってまでディープ・ラーニングを使う意味があるのか,と半信半疑です.

 ディープ・ラーニングのメリットの一つに,ヒトの判断や巧みさを学習できることがあります. 異常検知で言えば,ヒトが画像を見てラベル付けをすれば,ヒトの判断を模倣できます. 合成写真から自動でラベルを作る今回のアプローチは,このメリットを度外視しています.

 しかし,ディープ・ラーニングは一つの画像処理アルゴリズムとしても強力です. 従来のパターンマッチングよりも柔軟かつロバストパターン認識できると感じています. これを活用するだけでも,大きな意味があると思うのです.

 また,学習画像とラベルを自らで作ることで,ネットワークの振る舞いをコントロールできる可能性もあります. 一般的に,ニューラルネットワークは学習データに対して「できたなり」になります. どういう動きをするか(明確には)予想できませんし,意図した動きに調整することも出来ません.

 今回,「大きく写った円盤と白い円盤が検出出来ない」という結果になりました. この理由は明快で,そうした特徴の学習画像を作っていなかったからです. つまり,検出精度を改善しようと思ったら,円盤のサイズをもっと大きなものまで許容しつつ,円盤の輝度にバリエーションを持たせれば良いわけです.

 異常を検知したいと思っている方は,その異常がどんな大きさで,どんな色味かを知っている筈です. 検出したい異常を巧く網羅するような学習画像を作れるケースも在るのではないでしょうか. 現実のデータは常に偏りを持ち,母集団には成り得ません. 合成写真を学習させる方が,効率的な可能性さえあると思います.

 少ない画像からディープ・ラーニングを組めることは,大きなインパクトがあります. また「この顔みたら110番」の話になりますが,監視カメラに映る犯人の顔は限られたアングルになることが多いでしょう. 表情も固定です. iphone 10に顔を覚えさせるとき,表情を変えずに上下左右に顔を動かされますよね. こうした理想的な条件で画像を集めることは難しいでしょう. 限られた画像からディープ・ラーニングを組めれば,それこそ,指名手配書なんて不要になります. ニューラル・ネットワークに学習させて,全国の監視カメラに仕込めば良いわけです. (大手の監視カメラで人物を追尾する機能がありますが,似たような事をしているかもですね.主には服装等の特徴を使っていると思いますが). こんな世の中が訪れるのも,遠くなさそうです.

 便利さの反面にプライバシーの問題があります. ショップはカメラに人物を判別させ,購入記録を作ったり,レコメンドを出したりするでしょう. 店員に話しかけられるだけで鬱陶しく感じる僕には,住みにくい部分もありそうです.

4. むすび

 「ネットワークは組めるようになったけど適当なデータが無い」とヤキモキしていた時期もあったのですが,逆転の発送で楽しめました. 機会があれば,考察で書いたアプローチで円盤の検出精度を向上してみようとおもいます. 余談ですが,世の中に存在するUFO画像は構図がやたらと似ています. このことも,異常検知がそこそこ成立した要因の一つだと思います(笑)

 今回書いたソースはここ*15です.