LoginSignup
0
0

More than 3 years have passed since last update.

4.円のパラメータをニューラルネットワークで!

Posted at

はじめに

シリーズ第4回です。

これまで、与えたランダムな数値データから平均値と標準偏差を出力するNN(第2回)、与えた正規分布波形データから、正規分布波形データを作成するのに使用した3つのパラメーターを出力するNN(第3回)を、作成してきました。

今回は、円を描いた二次元の画像データから、円を描くのに使用した4つのパラメーター(円の中心のx座標、y座標、半径、ペンの線の太さ)を出力する畳み込みNN(CNN)を作成します。

画像データは、Objective-Cで作成しました。

画像データの作成

まずは画像データの作成です。50ピクセルx50ピクセルの円を描いた画像を作成し、ここからピクセルデータを取得し、円を描くのに使用した4つのパラメーターと併せてファイルに書き出します。1件のデータは、カンマ区切りの2504個のデータよりなります。最初の2500個は、0から1までの数値、残り4個は、円の中心のx座標、y座標、半径、円の線の太さです。データの間は改行(\n)で区切ります。

データの作成はObjective-CでNSImageクラスを使用しました。

4-001.c
// 画像サイズは 50ピクセル x 50ピクセル
// 中心座標、半径、線の太さを乱数で決める。
// 半径は5から25
// 中心座標は、円が画像内に含まれるようにする。
// 線の太さは、0.2から5
#import <Cocoa/Cocoa.h>
-(uint8_t *)pixelDataFromImage(NSImage *image);//main()内で使う関数のプロトタイプ宣言

int main(int argc, const char * argv[]) {
    srand((unsigned int)time(NULL));//乱数の初期化
    //書き出し先のファイルを開く
    char *fileName = "~/imageLearningData.txt";
    FILE *fp = fopen(fileName, "w");

    //50000枚作成
    for(int mmm = 0; mmm < 50000;mmm++){
        double radius = (double)rand()/RAND_MAX*20 + 5;
        double x = (double)rand()/RAND_MAX*(50 - radius * 2) + radius;
        double y = (double)rand()/RAND_MAX*(50 - radius * 2) + radius;
        double lineWidth = (double)rand()/RAND_MAX*4.8 + 0.2;
        [NSBezierPath setDefaultLineWidth:lineWidth];

        NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(50, 50)];    
        NSBezierPath *bezierPath = [NSBezierPath bezierPath];
        //イメージの描画
        [image lockFocus];
        [bezierPath appendBezierPathWithOvalInRect:NSMakeRect(x - radius, y - radius, radius * 2, radius * 2)];
        [bezierPath stroke];
        [image unlockFocus];

        uint8_t *pixels = pixelDataFromImage(image);

        //ピクセルデータを出力
        NSSize size = [image size];
        uint32_t width = (uint32_t) size.width;
        uint32_t height = (uint32_t) size.height;
        int components = 4;
        for(int iii = 0; iii < height ;iii ++){
            for(int kkk = 0; kkk < width ; kkk++){
                double value = 0;
                value += pixels[( width * iii + kkk )*4     ]/255.0;
                value += pixels[( width * iii + kkk )*4 + 1 ]/255.0;
                value += pixels[( width * iii + kkk )*4 + 2 ]/255.0;
                //以下は出力ファイルサイズを抑えるための工夫。「1.0000000」の代わりに「1」を出力する。
                value /= 3;
                if(value == 1){
                    fprintf(fp,"%d,",1);
                }else{
                    fprintf(fp,"%f,",value);
                }
            }
        }

        fprintf(fp,"%f,%f,%f,%f\n",x,y,radius,lineWidth);
        free(pixels);
    }
    fclose(fp);
}

上のコードにある メソッドpixelDataFromImage についてはしまぴょんさん@shimacpyonコード をすこしだけ改変して以下のように作成しました。しまぴょんさん、ありがとうございます。

4-002.c
-(uint8_t *)pixelDataFromImage(NSImage *image){
    /* NSBitmapImageRepのインスタンスを作成します */
    NSBitmapImageRep*bitmapRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
    /* JPEGで保存する場合は、アルファチャンネルを削除します */
    [bitmapRep setAlpha:NO];
    /* 保存のための品質を取得します */
    float quality = 1.0;
    /* プロパティを作成します */
    NSDictionary* properties = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:quality] forKey:NSImageCompressionFactor];
    /* JPEGデータを作成します */
    NSData *data = [bitmapRep representationUsingType:NSJPEGFileType properties:properties];

    //NSDataからNSImageを改めて作成
    NSImage *newImage = [[NSImage alloc] initWithData:data];

    if (newImage != nil) {
        NSSize size = [newImage size];
        uint32_t width = (uint32_t) size.width, height = (uint32_t) size.height, components = 4;
        uint8_t *pixels = (uint8_t *) malloc(size.width * size.height * components);//0から255
        if (pixels) {
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGContextRef bitmapContext = CGBitmapContextCreate(pixels, width, height, 8, components * width, colorSpace, kCGImageAlphaPremultipliedLast);
            NSRect rect = NSMakeRect(0, 0, width, height);
            NSGraphicsContext *graphicsContext = (NSGraphicsContext *) [[NSGraphicsContext currentContext] graphicsPort];
            CGImageRef cgImage = [newImage CGImageForProposedRect:&rect context:graphicsContext hints:nil];
            CGContextDrawImage(bitmapContext, NSRectToCGRect(rect), cgImage);
            CGContextRelease(bitmapContext);
            CGColorSpaceRelease(colorSpace);
            return pixels;
        }
    }
    return nil;
}

NSImageから直接ピクセルデータを取り出すことはできず、NSImageからJPEG形式のデータ(NSData)を取得し、これをもとにあらためてNSImageを作成してそこからピクセルデータを取り出しました。もう少しスマートな方法があるのかもしれませんが、先に進めたいと思います。

学習データの分割

作成した学習データ(2504データ x 50000行)を4つ、すなわち、
1. 訓練用の入力データ(2500データx40000行)、
2. 訓練用の正解データ(4データ x 40000行)、
3. 評価用の入力データ(2500データx10000行)、
4. 評価用の正解データ(4データx10000行)、

に分割します。

4-003.py
import numpy as np
d = np.loadtxt('./imageLearningData.txt', delimiter=',')
#-4:は後ろから4つ目から最後まで。
d_training_x = d[:40000,:-4]
d_training_y = d[:40000,-4:]
d_test_x = d[40000:,:-4]
d_test_y = d[40000:,-4:]

#データの形の変更
d_training_x = d_training_x.reshape(40000,50,50,1)
d_test_x = d_test_x.reshape(10000,50,50,1)

CNNのデザイン

CNNをデザインします。二次元画像を学習させるので畳み込みニューラルネットワークを使用します。CNNのデザインは勘に基づいて、適当にデザインしました。考慮したのは以下の点です。
1. 畳み込み層をいくつかおく。
2. Maxプーリング層でデータ数を減らす。
3. 全結合層をいくつか結合して徐々にデータ数を減らして4にする。
4. 損失関数は、2乗和誤差を使用。
5. 最初の層の入力shapeは(50,50,1)。
6. 最後の層の出力数は4。

4-004.py
import keras
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.optimizers import Adam
from keras.layers.core import Dense, Activation, Dropout, Flatten

# モデルの定義
model = Sequential()
model.add(Conv2D(32,5,input_shape=(50,50,1)))
model.add(Activation('tanh'))
model.add(Conv2D(32,3))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Conv2D(64,3))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(1024))
model.add(Activation('relu'))
model.add(Dense(128))
model.add(Activation('relu'))
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dense(4, activation='linear'))

adam = Adam(lr=1e-4)

model.compile(optimizer=adam, loss='mean_squared_error', metrics=["accuracy"])
model.summary()

学習の開始

パラメータの数は6,722,916個です。結構時間がかかりそうですね.....。学習を開始します。

4-005.py
batch_size = 128  # 128データをまとめて放りこむ
epochs = 20

history = model.fit(d_training_x, d_training_y,
batch_size=batch_size,
epochs=20,
verbose=1,
validation_data=(d_test_x, d_test_y))

1エポックあたり107秒かかりました。
学習が進行する様子をグラフにします。lossは訓練データから計算された損失値、val_lossは評価用データから計算された損失値です。

4-006.py
#グラフの描画
import matplotlib.pyplot as plt
plt.plot(history.history['loss'],label="loss")
plt.plot(history.history['val_loss'],label="val_loss")
plt.legend() # 凡例を表示
plt.title("Can CNN learn to predict 4 parameters used to draw a circle?")
plt.xlabel("epoch")
plt.ylabel("Loss")
plt.show()

Figure_4-1.png
順調に学習できたように見えます。

CNNの評価

どの程度精度良く予測できているでしょうか?評価用データの先頭200データを訓練後のCNNに放り込んでみます。

4-007.py
inp = d_test_x[:200,:]
out = d_test_y[:200,:]
pred = model.predict(inp, batch_size=1)

#グラフにします。
plt.title("Can NN deduce circle parameters?")

plt.scatter(out[:,0], pred[:,0],label = "x",marker='.', s=20,alpha=0.7)
plt.scatter(out[:,1], pred[:,1],label = "y",marker='.', s=20,color="green",alpha=0.7)
plt.scatter(out[:,2], pred[:,2],label = "r",marker='.', s=20,color="red",alpha=0.7)
plt.scatter(out[:,3], pred[:,3],label = "line width",marker='.', s=20,color="black",alpha=0.7)

plt.legend(fontsize=14) # 凡例を表示
plt.xlabel("expected value")
plt.ylabel("prediction")
#見にくいのでx=yのラインは省略
#x = np.arange(-1, 41, 0.01)  
#y = x
#plt.plot(x, y,color="black")
plt.show()

Figure_4-3.png

横軸は円データを作成するときに使ったパラメーターの値、縦軸は、画像データをもとにCNNが出力した値です。

左下から右上に引いた$x=y$の線に乗れば、うまく出力できたことになります。

完璧とは言えないものの、概ね良く学習できたようです。ネットワーク構成などを変えたらもう少し良くなるでしょうか?

まとめ

画像の中にどこに、どれくらいの大きさの円があるかを、円を書くためのパラメーターとして出力できるようになりました。

これでシリーズ第4回は終了です!

シリーズ第1回 準備編
シリーズ第2回 平均と標準偏差
シリーズ第3回 正規分布
シリーズ第4回 円

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0