こういうの↓を作ってみようと思います
mnist問題をやっと動かせるようになったレベルの僕には
敷居が高そうですが、、、
画像のダウンロード
上記の画像は10年位前に行われたコンペティションで使われたもので、
ここ*2からダウンロード出来ます
コンペは種類の異なる6クラスの画像全てを使って行われた様ですが、
今回は上記で示されているClass 1のみを使うことにします
基本方針
mnistは、モデルに画像を入力して分類結果を出力する動きでした
正確に言うと、ニューラルネットワークが出力するのは
分類項目についての確率密度です

傷位置検出も同じ考え方で構築します
つまり、画像の座標情報に対する確率密度を出力します
傷がある場所(ある確率の高い場所)の数値を大きくするという事ですね

損失関数
mnistのような画像分類と傷位置検出の大きな違いの一つが、
損失関数の立て方だと思います
画像分類で一般的に使われてるcross entropyは次の数式で表されます
正解ラベルを表すは、正解ならば1、不正解ならば0となるone_hot表現です
色々と試行錯誤をする中で、このone_hot表現が傷位置検出には
合わないことが解ってきました
one_hot表現では、正解の場所(1が掛けられる)以外の出力値には
0が掛けられて無視されます
つまり、↓の2つの出力は同じ意味になるのです

画像分類であればこれで構わないのですが、
傷位置検出では傷が無い場所が等しく低い確率になることも重要です
傷がない場所は、等しく無いわけですから
この問題に対する答えが論文*3の中にありました

読み解くのが大変なんですが、
要は傷がある場所には0.8の、傷が無い場所には0.2の重みを掛けろ、
と言っていると思います
傷の検出を重視しつつ、傷が無い場所は一様に、
という意味ですね
コーディング
import math
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
from PIL import Image
TRAIN_DATA_SIZE = 1000
TRAIN_DATA_SIZE_WITH_DEFECT = 100
VALID_DATA_SIZE = 50
TEST_DATA_SIZE = 50
IMG_SIZE = 64
if __name__ == "__main__":
init = tf.global_variables_initializer()
sess = tf.Session()
with sess.as_default():
# remove old file
if(os.path.exists('./data/trainImage64.txt')):
os.remove('./data/trainImage64.txt')
if(os.path.exists('./data/validationImage64.txt')):
os.remove('./data/validationImage64.txt')
if(os.path.exists('./data/testImage64.txt')):
os.remove('./data/testImage64.txt')
if(os.path.exists('./data/trainLABEL.txt')):
os.remove('./data/trainLABEL.txt')
if(os.path.exists('./data/trainWEIGHT.txt')):
os.remove('./data/trainWEIGHT.txt')
if(os.path.exists('./data/validationLABEL.txt')):
os.remove('./data/validationLABEL.txt')
if(os.path.exists('./data/validationWEIGHT.txt')):
os.remove('./data/validationWEIGHT.txt')
if(os.path.exists('./data/testLABEL.txt')):
os.remove('./data/testLABEL.txt')
if(os.path.exists('./data/testWEIGHT.txt')):
os.remove('./data/testWEIGHT.txt')
# without detection
for k in range(TRAIN_DATA_SIZE):
filename = './data/Class1/' + str(k + 1) + '.png'
print(filename)
imgtf = tf.read_file(filename)
img = tf.image.decode_png(imgtf, channels=1)
resized = tf.image.resize_images(img, [IMG_SIZE, IMG_SIZE], method=tf.image.ResizeMethod.BILINEAR)
array = resized.eval()
line = str(k)
for i in range(IMG_SIZE):
for j in range(IMG_SIZE):
line = line + ',' + str(array[i, j, 0])
line = line + '\n'
file = open('./data/trainImage64.txt', 'a')
file.write(line)
file.close()
# # detection data
for k in range(TRAIN_DATA_SIZE_WITH_DEFECT + VALID_DATA_SIZE):
filename = './data/Class1_def/' + str(k + 1) + '.png'
print(filename)
imgtf = tf.read_file(filename)
img = tf.image.decode_png(imgtf, channels=1)
resized = tf.image.resize_images(img, [IMG_SIZE, IMG_SIZE], method=tf.image.ResizeMethod.BILINEAR)
array = resized.eval()
line = str(k + TRAIN_DATA_SIZE)
for i in range(IMG_SIZE):
for j in range(IMG_SIZE):
line = line + ',' + str(array[i, j, 0])
line = line + '\n'
if(k < TRAIN_DATA_SIZE_WITH_DEFECT):
file = open('./data/trainImage64.txt', 'a')
file.write(line)
file.close()
else:
file = open('./data/validationImage64.txt', 'a')
file.write(line)
file.close()
file = open('./data/testImage64.txt', 'a')
file.write(line)
file.close()
# label #
trnLABEL = []
trnWEIGHT = []
valLABEL = []
valWEIGHT = []
tstLABEL = []
tstWEIGHT = []
# no defection data
for k in range(1000):
label = np.zeros([16*16 + 1])
label[0] = k
trnLABEL.append(label)
weight = np.zeros([16*16 + 1])
weight[0] = k
weight[1:16*16 + 1] = 0.2
trnWEIGHT.append(weight)
# defection data
x = np.linspace(15.5, 495.5, 16)
y = np.linspace(15.5, 495.5, 16)
print('reading Class1_def')
label1 = open('./data/Class1_def/labels.txt', 'r')
for k in range(150):
line = label1.readline()
val = line.split('\t')
num = int(val[0]) - 1
mjr = float(val[1])
mnr = float(val[2])
rot = float(val[3])
cnx = float(val[4])
cny = float(val[5])
# inverse rotate pixels
label = np.zeros([16*16 + 1])
weight = np.zeros([16*16 + 1])
label[0] = num + 1000 # index
weight[0] = num + 1000 # index
for i in range(16):
for j in range(16):
dist = math.sqrt((x[i] - cnx)**2 + (y[j] - cny)**2)
xTmp = (x[i] - cnx) * math.cos(-rot) - (y[j] - cny) * math.sin(-rot)
yTmp = (x[i] - cnx) * math.sin(-rot) + (y[j] - cny) * math.cos(-rot)
ang = math.atan(yTmp/xTmp)
distToEllipse = math.sqrt((mjr * math.cos(ang))**2 + (mnr * math.sin(ang))**2)
if(dist < distToEllipse):
label[i*16 + j + 1] = 1 # defection
weight[i*16 + j + 1] = 0.9
else:
label[i*16 + j + 1] = 0
weight[i*16 + j + 1] = 0.1
# plot test
if(k == 1):
for i in range(16):
for j in range(16):
if(label[i*16 + j + 1] == 1):
plt.plot(i*32, j*32, '.', color='white')
else:
plt.plot(i*32, j*32, '.', color='black')
plt.xlim(0, 512)
plt.ylim(512, 0)
plt.show()
if(k < 100):
trnLABEL.append(label)
trnWEIGHT.append(weight)
else:
valLABEL.append(label)
valWEIGHT.append(weight)
tstLABEL.append(label)
tstWEIGHT.append(weight)
# normalize
w_array = np.array(trnWEIGHT)
for k in range(1100):
s = sum(w_array[k, 1:16*16 + 1])
w_array[k, 1:16*16 + 1] = w_array[k, 1:16*16 + 1]/s
trnWEIGHT = w_array.tolist()
w_val_array = np.array(valWEIGHT)
w_tst_array = np.array(tstWEIGHT)
for k in range(50):
s = sum(w_val_array[k, 1:16*16 + 1])
w_val_array[k, 1:16*16 + 1] = w_val_array[k, 1:16*16 + 1]/s
s = sum(w_tst_array[k, 1:16*16 + 1])
w_tst_array[k, 1:16*16 + 1] = w_tst_array[k, 1:16*16 + 1]/s
valWEIGHT = w_val_array.tolist()
tstWEIGHT = w_tst_array.tolist()
np.savetxt('./data/trainLABEL.txt', trnLABEL, fmt='%d')
np.savetxt('./data/trainWEIGHT.txt', trnWEIGHT, fmt='%.5f')
np.savetxt('./data/validationLABEL.txt', valLABEL, fmt='%d')
np.savetxt('./data/validationWEIGHT.txt', valWEIGHT, fmt='%.5f')
np.savetxt('./data/testLABEL.txt', tstLABEL, fmt='%d')
np.savetxt('./data/testWEIGHT.txt', tstWEIGHT, fmt='%.5f')
sess.close()
重み付きのラベルを、trnWEIGHT、valWEIGHT、tstWEIGHTに格納し、
ファイルに出力しています
傷がある場所を0.9、傷が無い場所を0.1としているのがポイントです
ソース内で# normalizeと書いているのも重要です
tensorflowの損失関数に適用するラベルは、
確率密度関数である必要がある*5ためです
動かしてみる
損失関数のソースです
def loss(output, y, weight):
xentropy = tf.nn.softmax_cross_entropy_with_logits(logits=output, labels=weight)
loss = tf.reduce_mean(xentropy)
return loss
効果を確かめるために、one_hot表現のラベルと、
重み付きのラベルの両方を損失関数に渡せるようにしました
labels=の後をyにすればone_hotに、weightにすれば重み付きになります

この結果だけを見るとone_hot表現の方が効率的に学習が進んで行くように見えます
しかし、100回程度計算を繰り返すと、one_hot表現の損失関数は発散します
重み付きの損失関数に、一定の効果があったと考えています
(確率密度関数にならない(=1が複数ある)one_hotラベルは、
そもそも間違っているのですが、、、)
ただ、学習速度は恐ろしく遅いです
一体何回計算を回せば良いことやら、、、(汗)
学習が進まない原因として、 ↓↓↓を予想しています
1. グラボの容量の関係で画像をresizeしている
2. ニューラルネットワークがmnistと同じで単純すぎる
3. そもそも何かが根本的に間違っている
亀の歩みで、また少しずつ調べて行きます
*1:tao2018:, https://www.mdpi.com/2076-3417/8/9/1575
*2:http://resources.mpi-inf.mpg.de/conferences/dagm/2007/prizes.html
*3:tao2018:, https://www.mdpi.com/2076-3417/8/9/1575
*4:mnistのjpeg画像をtensorflowに入力する、Part 02 - オッサンはDesktopが好き
*5:https://www.tensorflow.org/api_docs/python/tf/nn/softmax_cross_entropy_with_logits
