頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


機械学習で画像認識ができていなかった

KerasのImageDataGeneratorで画像データの水増し
↑この続き。
結局4chで画像データを水増しして学習をやり直すことにした。

Kerasで再度学習し直し、macOSアプリのMetal Performance Shadersで同じ入力なら同じ計算結果になることを確認した。

次に動画ファイルで”Excellent!”の文字がありそうな付近で予測結果がどのようになるか確認する。

↓その結果がこちら。動画から1/30秒ごとに中心付近の画像を得てMPSに渡して予測した。

予測結果1

↑画像下の数字が”Excellent!”の文字がある確率(Softmax関数で「ある」の場合の出力値なので)。
この値を見ると、うっすら表示している場合は値が低く、はっきり見えるのは1.0となっている。
6コマ目が豪快に間違えているが、まあなんとか使えるかな、、、

と思っていたのだが、他の動画で確認してみると、、、

予測結果2

予測結果3

↑どちらも全く"Excellent!"が出ていないのに1に近い値を出してる。なんか違うところで判定してそう。
これは使えない。

冷静に考えれば、二択とは言え学習用の画像データは1,302枚だもの、、、少ない。元はCreateML(転移学習)用で準備してたからな。うーん、考え直さねば。
スポンサーサイト



KerasのImageDataGeneratorで画像データの水増し

3チャンネル画像だとKerasとMPSの計算が合わない
↑この続き。
Kerasで学習のやり直しをすることに。せっかくなので画像データの水増しを試してみた。
TensorFlowでも画像データを増やせる関数などがあったがKerasにもImageDataGeneratorというものがあるようで取り入れてみた。

画像の前処理 - Keras Documentation
↑公式はこちら。

修正部分はこんな感じ↓
from keras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(
                             rotation_range = 3 , 
                             width_shift_range = 0.1,
                             height_shift_range = 0.1,
                             zoom_range = 0.02,
                             channel_shift_range = 10.0, 
                             )#—(1)

datagen.fit(x_train)#—(2)


#history = model.fit(x_train, y_train, batch_size=batch_size, … #—(3)
history = model.fit_generator(
                              datagen.flow(x_train, y_train, batch_size=batch_size),…#—(4)

↑(1)ImageDataGeneratorで目的に合致するもので使えそうなのを選んで設定。
どのように画像が変化するのかは参考ページなどに。
(2)は内容によっては不要かも。
ImageDataGeneratorを使う前は(3)のfit関数で訓練を実行していたのだけど、(4)の形式に変更。

調子に乗って盛り込みすぎると学習が進まなくなってしまったので、ほどほどがよさそう、、、ほどほどってなんやねん!?将来はそういうのもKerasが良きに計らってくれたらいいのにな。


参考ページ
Kerasによるデータ拡張 - 人工知能に関する断創録
Kerasでデータ拡張(Data Augmentation)後の画像を表示する - Qiita





3チャンネル画像だとKerasとMPSの計算が合わない

画像の判別がCreateMLでダメだったのでKerasでやってみた
↑この続き。
Kerasで画像分類をさせて、その学習結果(重み、バイアス)を出力。それをmacOSアプリのMetal Performance Shadersで同じ計算をさせる。

計算量削減のために画像のRGBの3チャンネル分のデータを使う。するとKerasとMPSで同じ画像を与えても出てくる結果が違ってしまう。

ちなみに4チャンネルの画像を使い、同じようにKerasで学習させて係数をmacOSで使いMPSで計算させると同じ値になる。
よって、学習結果の係数の受け渡し方に問題はないと考えている。

MPSで画像データを与える場合に
- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow;
↑このメソッドを使うとする(※1)。
//(1)4チャンネルの場合

{
    MTLRegion region = MTLRegionMake2D(0, 0, Width, Height);

    [srcImage.texture replaceRegion:region 
        mipmapLevel:0 
        withBytes:inputs 
        bytesPerRow:sizeof(unsigned char)*Width*NofChannel];
}
↑MPSではこんな感じでデータ列(ここではunsigned char型)をMPSImageのsrcImageのテクスチャに書き込む。
その範囲をMTLRegionで指定したり、1行あたりのバイト数を指定したりする。
これは意図通りに動いた。

問題は次の3チャンネルの場合だ。
//(2)3チャンネルの場合
{
    MTLRegion region = MTLRegionMake2D(0, 0, Width*3/4, Height);

    [srcImage.texture replaceRegion:region 
        mipmapLevel:0 
        withBytes:inputs 
        bytesPerRow:sizeof(unsigned char)*Width*NofChannel];
}
↑今回のケースではMPSImageは4チャンネルが基本(※2)なのだが、データは3チャンネルなのでbytesPerRowは1バイトx3チャンネルx横幅。
これに合わせてコピーする範囲の横幅をピクセル数の3/4倍としている(画像サイズは96x96なので横幅は4の倍数)。

これが、なぜかKerasと計算結果が全然合わない、、、😥

regionの設定が間違っているのか?と思いsrcImage.textureから再度データを取り出して画像を作成して元画像になってるのを確認したり、RGBが入れ替わっているのか?と思いチェックするなど、まあいろいろと調べたのだが、、、降参!

回避策は4チャンネルで学習、予測を行う、、、だ、ださいがまあ動くからいいか、、、なにこの敗北感。
何かあればお教えください。


※1
- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level slice:(NSUInteger)slice withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow bytesPerImage:(NSUInteger)bytesPerImage;
↑こちらのメソッドもあるが、今回は3チャンネルか4チャンネルなのでスライスは不要。

※2 これはMPSImageオブジェクトのpixel format次第。これはread only。MPSImageオブジェクトを作る時に使うMPSImageDescriptorオブジェクトの設定により自動的に決まるのだと思う。
MPSImageDescriptor *sourceImageDiscriptor = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatUnorm8 
        width:Width 
        height:Height 
        featureChannels:NofChannel];
↑このようにして作成するとsourceImageDiscriptor.pixelFormatは
MTLPixelFormatRGBA8Unorm
となり、
Ordinary format with four 8-bit normalized unsigned integer components in RGBA order
↑とリファレンスにあるように8ビット符号なし整数のRGBAの値を持つ(4バイト)。

ちなみにMNISTでは白黒画像を使っていたが
MPSImageDescriptor *sourceImageDiscriptor = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatUnorm8 
width:28 
height:28 
featureChannels:1];
↑このように画像を設定した場合だとpixel formatは
MTLPixelFormatR8Unorm
となり1ピクセルに1バイトの値しか持たない。





NSImage画像をRGBAのデータに展開する

カラー(※1)のNSImage画像(RGBAの4チャンネルのデータを持つ)をMetal Performance Shadersで使うためにデータ化する、というのをしたことがなかったのでデータ化するクラスメソッドを作った。

この手のメソッドは数ヶ月後か数年後かに参考にしたり使い回ししたくなるものなので可能ならクラスメソッドにして自分がすぐにわかるような置き場に置いておくようにしたい(自分への戒め)。

あらかじめその分のメモリは確保しておく必要がある。
// 呼び出し元
{
    unsigned char *toData = calloc(width*height*4, sizeof(unsigned char));
    [MyUtility storeColorImage:image to:toData];

    // (toDataを使う処理)

    free(toData);//—(1)
}

// MyUtilityクラスの実装ファイル
+(void)storeColorImage:(NSImage*)image to:(unsigned char *)toData
{
    size_t width = image.size.width;
    size_t height = image.size.height;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = width*4;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageAlphaInfo bitmapInfo = kCGImageAlphaNoneSkipLast;
    
    CGContextRef context = CGBitmapContextCreate(nil,
                                                 width,
                                                 height,
                                                 bitsPerComponent,
                                                 bytesPerRow,
                                                 colorSpace,
                                                 bitmapInfo);
    
    NSRect imageRect = NSMakeRect(0, 0, width,height);
    NSGraphicsContext *gctx = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
    [NSGraphicsContext setCurrentContext:gctx];
    [image drawInRect:imageRect];
    
    void *data = CGBitmapContextGetData (context);
    
    memcpy(toData, data, width*height*4);
    
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
}
(1)使用後はメモリの解放が必要。そういう事情もあるので
+(unsigned char *)getDataForImage:(NSImage *)image;
という形式にできない(※2)のが少し残念、というかコードの可読性が低下するが仕方ない。


※1 わざわざ”カラー”としているのは、白黒も扱うことがあるのであえてメソッド名などにColorと入れてるだけ
※2 できなくもないけどメモリ解放を忘れてしまう可能性大なので避けてる


画像の判別がCreateMLでダメだったのでKerasでやってみた

CreateMLで2種類の画像を見分けられなかった話
↑ここでCreateMLだと2種類の画像の分類がうまくいかなかったことを書いた。
転移学習を利用しているCreateMLだと動物の分類などでは枚数がすくなくてもうまくいくが、今回のような課題には向かないのだろう、と思いCreateMLでやるのはあきらめた。

ただ、機械学習で見分けられない課題ではないだろう?と思ったのでKeras(TensorFlowバックエンド)で分類できるか学習させてみた。

NNの構成はCNNを使うMNISTと同じで、二層の畳み込み、ドロップアウトもありのまあサンプルコード通りのものだ。
実際に動かしてみたら(私のMacには)300x300の画像は大きすぎたようで、あらかじめ全ての画像を96x96にリサイズしておいた。

判定対象画像1

判定対象画像2

↑このサイズだとこのような感じ。Excellent!の文字の有無を判定する。

その結果のログがこちら↓

Kerasでの学習結果

Train 99.23%
Test 100.0%

いけるやんかー!!!そうだよな、CNN、お前はできる子や!
5epochだけやったようなログになっているが、この前にもやっているので実際には合計で30epoch弱程度やっている。
次はこの学習結果をmacOSアプリで利用できるようにせねば。








Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.

FC2Ad