Hatena::ブログ(Diary)

ぱたへね

2017-02-19

[] kerasで処理の途中の値を取り出す

俺俺DLフレームワークを作るにはKeras一択のような気がしてきました。

KerasでNNに特定のデータを入れた時に、狙った層の出力をnumpy形式で出力する方法です。やりかたはKeras FAQをみました。簡単です。

https://keras.io/ja/getting-started/faq/#_1

やってること

http://d.hatena.ne.jp/natsutan/20170212/1486862458

ここで作ったNNに一枚絵を入れて、その途中の計算結果を取り出しました。入力データはひらがなデータを入れていますが、やっていることはNNも含めてMNISTと完全に同じです。

モデルと学習結果を読み込む

学習後に保存したjsonファイルとhd5ファイルを読み込みます。ファイルの作り方はここを参照。

http://d.hatena.ne.jp/natsutan/20170212/1486862458

json_string = open('output/cnn.json', 'r').read()
model = model_from_json(json_string)
model.load_weights('output/cnn.h5')

画像の読み込み

「い」が入ったデータを読み込みます。行列の形を合わせる必要があります。255から引いている所はネガポジ反転なので、データがすでに直接入力できる形式になっていれば不要です。

# load image
images = np.empty([0, 28, 28], np.float32)
img_ori = Image.open('data/I.png')
img_gray = ImageOps.grayscale(img_ori)

img_ary = np.asarray(img_gray)
img_ary = 255 - img_ary
images = np.append(images, [img_ary], axis=0)

images = images.reshape(1, 28, 28, 1)
一枚の識別

model.predictで一枚だけ識別します。

ret = model.predict(images, 1, 1)
print(ret)

実行結果

1/1 [==============================] - 0s
[[  9.66260362e-18   1.00000000e+00   1.46393750e-16   3.83880911e-21
    1.73906418e-14   2.77851445e-13   2.60705761e-21   2.42816259e-22
    9.78005010e-10   9.94229803e-16   2.92022889e-13   2.50225903e-12
    1.87606130e-20   9.82728684e-17   4.47857970e-22   6.36410264e-16
    2.89718408e-16   9.22273868e-15   1.46329236e-20   1.10161611e-16
    3.02622735e-19   4.96133791e-18   4.64741527e-13   2.02620950e-12
    5.23028010e-11   2.72302085e-14   3.31416961e-09   8.62797342e-16
    3.77434917e-15   3.23395878e-13   8.63702926e-16   1.50429401e-18
    1.54558563e-13   6.33530894e-10   1.18054563e-24   1.71215462e-14
    1.62872005e-12   8.14889678e-20   4.88564665e-19   8.62093701e-15
    4.40518906e-17   6.88677597e-16   4.34340205e-16   1.31322590e-17
    4.82810310e-18   1.39269949e-18]]

入力した文字が「い」に対して、softamx出力の2番目が最大値になっているので識別できてますね。

中間層の出力を取り出す。

https://keras.io/ja/getting-started/faq/#_1 を参考に。

K.functionを使って、特定の層(例えばConvolution2D)の入出力を取り出す関数を作ります。その関数に対して、入力データを指定すると、それを処理した出力を受け取ることができます。numpy形式なので、そのままファイルに落とせます。

# output
get_1st_layer_output = K.function([model.layers[0].input],
                                  [model.layers[0].output])
layer_output = get_1st_layer_output([images,])
print(layer_output[0].shape)
np.save('output/convolution2d_out.npy', layer_output[0], allow_pickle=False)

全ソースコード

import numpy as np
from PIL import Image
from PIL import ImageOps
from keras.models import model_from_json
from keras import backend as K

json_string = open('output/cnn.json', 'r').read()
model = model_from_json(json_string)
model.load_weights('output/cnn.h5')

# load image
images = np.empty([0, 28, 28], np.float32)
img_ori = Image.open('data/I.png')
img_gray = ImageOps.grayscale(img_ori)

img_ary = np.asarray(img_gray)
img_ary = 255 - img_ary
images = np.append(images, [img_ary], axis=0)

images = images.reshape(1, 28, 28, 1)

# predict
ret = model.predict(images, 1, 1)
print(ret)

# output
get_1st_layer_output = K.function([model.layers[0].input],
                                  [model.layers[0].output])
layer_output = get_1st_layer_output([images,])
print(layer_output[0].shape)
np.save('output/convolution2d_out.npy', layer_output[0], allow_pickle=False)

2017-02-15

[][] kerasの学習データをCで読み込む

kerasのネットワーク情報と学習データを保存する方法はこちら

http://d.hatena.ne.jp/natsutan/20170212

この続きで保存した学習データをCで読み込んでみます。

hd5形式をネットワーク名に対応させてnumpy形式で書き出す。

ファイル構成を調べても、どのデータがどの重みに対応するのかがよくわかりませんでした。結局、kerasのコードから必要なところを引き抜いてきました。

参考:https://github.com/fchollet/keras/blob/master/keras/engine/topology.py

# https://github.com/fchollet/keras/blob/master/keras/engine/topology.py
# https://github.com/fchollet/keras/blob/master/LICENSE
# Kerasの学習データ抜き出しスクリプト
import h5py
import numpy as np

input_hd5 = 'cnn.h5'

def save_weights(filepath, by_name=False):
    f = h5py.File(filepath, mode='r')
    if 'layer_names' not in f.attrs and 'model_weights' in f:
        f = f['model_weights']

    save_weights_from_hdf5_group(f)

    if hasattr(f, 'close'):
        f.close()

def save_weights_from_hdf5_group(f):

    layer_names = [n.decode('utf8') for n in f.attrs['layer_names']]
    print(layer_names)

    filtered_layer_names = []
    for name in layer_names:
        g = f[name]
        weight_names = [n.decode('utf8') for n in g.attrs['weight_names']]
        if len(weight_names):
            filtered_layer_names.append(name)

    layer_names = filtered_layer_names

    print(layer_names)

    for k, name in enumerate(layer_names):
        g = f[name]
        weight_names = [n.decode('utf8') for n in g.attrs['weight_names']]
        weight_values = [g[weight_name] for weight_name in weight_names]
        print('')
        print(name)
        print(weight_names)
        for weight_name in weight_names:
            data = g[weight_name].value
            print(data.shape)

            filename = weight_name.replace(':0', '_z') + '.npy'
            np.save(filename, data, allow_pickle=False)
            print("save %s to %s" % (weight_name, filename))

save_weights(input_hd5)

これをhd5のあるディレクトリで実行すると、kerasのネットワークに対応した名前のnpy形式で保存します。ポイントは、np.save()を使ってnumpyを保存するときに、allow_pickleにFalseを指定することです。こうすることで、Pickle形式ではなく、連続したCの配列になります。

以下、実行結果(抜粋)。kerasのネットワークに対応したファイル名で保存しています。

convolution2d_1
['convolution2d_1_W:0', 'convolution2d_1_b:0']
(3, 3, 1, 32)
save convolution2d_1_W:0 to convolution2d_1_W_z.npy
(32,)
save convolution2d_1_b:0 to convolution2d_1_b_z.npy

numpy形式をCで読み出す。

numpyのフォーマットはこちらを参照。

https://docs.scipy.org/doc/numpy/neps/npy-format.html

先頭から、8byte, 9byteにヘッダー長があるので、その長さ+8以降が実際のデータです。先にpythonで値を確認します。

import numpy as np
data = np.load('dense_1_b_z.npy')
print(data)

これを実行すると、このように出力されます。

[ 0.01447727 -0.00560358 -0.03917709 -0.00773129  0.01669481  0.02331016
 -0.02222279 -0.00773809  0.0162074  -0.00154851 -0.04478939 -0.00506137
  0.01831469 -0.02160945  0.01513562 -0.00983882 -0.02593831 -0.02709689
  0.00020504 -0.00142339 -0.00577859 -0.00883115  0.00912447  0.03082606
  0.01143611 -0.00278293 -0.00554265 -0.02509919 -0.00772283 -0.02985987
 -0.02390517 -0.00474763 -0.01730801  0.00120485 -0.01142981 -0.00061552
 -0.02196323  0.00577068 -0.00911395  0.01392506  0.02327628  0.00369775
 -0.00224362  0.0179715  -0.04719299  0.00875815 -0.00452969 -0.03214104
 -0.0069066  -0.01714456  0.01036448  0.0048428  -0.01533855 -0.00589305
  0.00368132  0.00494579  0.03383888 -0.01036347 -0.01587362  0.00544504
  0.00351622  0.00747294  0.04023237 -0.00225322  0.03317111 -0.04557553
 -0.05552437 -0.00583258  0.01372788  0.00907361  0.02187419  0.0295448
 -0.00099583  0.00802352  0.00391109  0.00583726 -0.03709769  0.00090473
 -0.01646602 -0.01568777 -0.01240169 -0.00157236 -0.01959496 -0.00049798
 -0.03411916  0.02637957  0.02369151 -0.01003799 -0.01855751  0.00707838
  0.04559748 -0.01478013  0.00100191  0.01219569 -0.00181059 -0.01096813
  0.01167649 -0.00252321 -0.02898367  0.01881598  0.01765522 -0.0138432
 -0.00060348 -0.00303171  0.00660704  0.00888676  0.0260018  -0.01976014
 -0.01559355 -0.00344168  0.02282669 -0.0136997   0.00279327 -0.02522321
 -0.00378602 -0.02168743 -0.01284252 -0.00368761  0.0025697   0.00357005
  0.01638989  0.00504998 -0.00064822  0.00188594 -0.02876503 -0.02664481
  0.02615403 -0.00238846]

読みだすCのソースです。

#include <stdio.h>
#include <stdlib.h>

#define DATA_NUM (128)

float data[DATA_NUM];

int main(void)
{
	
	FILE *fp;
	const char *fname = "dense_1_b_z.npy";
	int hsize;
	int i;
	int cnt;
	
	fp = fopen(fname, "rb");
	if(fp == NULL) {
		printf("error can't open %s\n", fname);
		exit(1);
	}
	
	//get header size
	fseek(fp, 8, 0);
	fread(&hsize, 2, 1, fp);
	
	//skip header 
	printf("hsize = 0x%x(%d)\n", hsize, hsize);
	fseek(fp, 8+2+hsize, 0);
	
	//read data
	cnt = fread(&data, 4, DATA_NUM, fp);
	printf("read cnt = %d\n", cnt);
	
	//print
	for(i=0;i<DATA_NUM;i++) {
		printf("%f\n", data[i]);
	}
	
	
	fclose(fp);
	
	return 0;
}

実行するとこのように表示されて、先頭からcの配列に浮動小数点数として読み込めているのがわかります。

hsize = 0x46(70)
read cnt = 128
0.014477
-0.005604
-0.039177
-0.007731
0.016695
0.023310
-0.022223
-0.007738
0.016207
-0.001549
-0.044789
-0.005061
0.018315
-0.021609
0.015136
-0.009839
-0.025938
-0.027097
0.000205
-0.001423
-0.005779
-0.008831
0.009124
0.030826
0.011436
-0.002783
-0.005543
-0.025099
-0.007723
-0.029860
-0.023905
-0.004748
-0.017308
0.001205
-0.011430
-0.000616
-0.021963
0.005771
-0.009114
0.013925
0.023276
0.003698
-0.002244
0.017971
-0.047193
0.008758
-0.004530
-0.032141
-0.006907
-0.017145
0.010364
0.004843
-0.015339
-0.005893
0.003681
0.004946
0.033839
-0.010363
-0.015874
0.005445
0.003516
0.007473
0.040232
-0.002253
0.033171
-0.045576
-0.055524
-0.005833
0.013728
0.009074
0.021874
0.029545
-0.000996
0.008024
0.003911
0.005837
-0.037098
0.000905
-0.016466
-0.015688
-0.012402
-0.001572
-0.019595
-0.000498
-0.034119
0.026380
0.023692
-0.010038
-0.018558
0.007078
0.045597
-0.014780
0.001002
0.012196
-0.001811
-0.010968
0.011676
-0.002523
-0.028984
0.018816
0.017655
-0.013843
-0.000603
-0.003032
0.006607
0.008887
0.026002
-0.019760
-0.015594
-0.003442
0.022827
-0.013700
0.002793
-0.025223
-0.003786
-0.021687
-0.012843
-0.003688
0.002570
0.003570
0.016390
0.005050
-0.000648
0.001886
-0.028765
-0.026645
0.026154
-0.002388

2017-02-12

[][] Kerasを使ってみた

Kerasを使ってひらがな認識のCNNを動かしてみました。情報を取り出すのが素のTensorflow, Caffe, Darknetに比べて非常に楽でした。

ひらがな認識をkerasで動かす

意外に簡単にできました。

https://github.com/natsutan/NPU/blob/master/example/keres_cnn/keras/chihay_cnn.py

ネットワークの構築はこう。MNISTのチュートリアルそのままです。

model = Sequential()

model.add(Convolution2D(nb_filters, kernel_size[0], kernel_size[1],
                        border_mode='valid',
                        input_shape=input_shape))
model.add(Activation('relu'))
model.add(Convolution2D(nb_filters, kernel_size[0], kernel_size[1]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=pool_size))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(128))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(kana_num))
model.add(Activation('softmax'))

ネットワーク、学習結果の出力

Kerasはネットワークの情報を持ち出すのが、Kaffe, Tensorflowに比べてわかりやすい。ちなみにDarknetはさっぱりわからない。

モデルのJSON出力

to_json()を使う。

with open('output/cnn.json', 'w') as fp:
    json_string = model.to_json()
    fp.write(json_string)

改行がほしいが、プログラムで読む分は問題ない。

{"class_name": "Sequential", "config": [{"class_name": "Convolution2D", "config": {"subsample": [1, 1], "nb_filter": 32, "nb_col": 3, "trainable": true, "init": "glorot_uniform", "W_regularizer": null, "b_regularizer": null, "bias": true, "input_dtype": "float32", "W_constraint": null, "nb_row": 3, "name": "convolution2d_1", "activation": "linear", "activity_regularizer": null, "border_mode": "valid", "dim_ordering": "tf", "batch_input_shape": [null, 28, 28, 1], "b_constraint": null}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_1"}}, {"class_name": "Convolution2D", "config": {"subsample": [1, 1], "nb_filter": 32, "nb_col": 3, "trainable": true, "init": "glorot_uniform", "W_regularizer": null, "b_regularizer": null, "W_constraint": null, "nb_row": 3, "name": "convolution2d_2", "activation": "linear", "activity_regularizer": null, "bias": true, "dim_ordering": "tf", "b_constraint": null, "border_mode": "valid"}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_2"}}, {"class_name": "MaxPooling2D", "config": {"strides": [2, 2], "trainable": true, "name": "maxpooling2d_1", "pool_size": [2, 2], "dim_ordering": "tf", "border_mode": "valid"}}, {"class_name": "Dropout", "config": {"p": 0.25, "trainable": true, "name": "dropout_1"}}, {"class_name": "Flatten", "config": {"trainable": true, "name": "flatten_1"}}, {"class_name": "Dense", "config": {"output_dim": 128, "activation": "linear", "trainable": true, "b_regularizer": null, "init": "glorot_uniform", "W_regularizer": null, "input_dim": 4608, "b_constraint": null, "name": "dense_1", "W_constraint": null, "activity_regularizer": null, "bias": true}}, {"class_name": "Activation", "config": {"activation": "relu", "trainable": true, "name": "activation_3"}}, {"class_name": "Dropout", "config": {"p": 0.5, "trainable": true, "name": "dropout_2"}}, {"class_name": "Dense", "config": {"output_dim": 46, "activation": "linear", "trainable": true, "b_regularizer": null, "init": "glorot_uniform", "W_regularizer": null, "input_dim": 128, "b_constraint": null, "name": "dense_2", "W_constraint": null, "activity_regularizer": null, "bias": true}}, {"class_name": "Activation", "config": {"activation": "softmax", "trainable": true, "name": "activation_4"}}], "keras_version": "1.2.0"}

サマリー出力

summary()で簡単。

print(model.summary())

実行時にはこのように表示される。

____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
convolution2d_1 (Convolution2D)  (None, 26, 26, 32)    320         convolution2d_input_1[0][0]      
____________________________________________________________________________________________________
activation_1 (Activation)        (None, 26, 26, 32)    0           convolution2d_1[0][0]            
____________________________________________________________________________________________________
convolution2d_2 (Convolution2D)  (None, 24, 24, 32)    9248        activation_1[0][0]               
____________________________________________________________________________________________________
activation_2 (Activation)        (None, 24, 24, 32)    0           convolution2d_2[0][0]            
____________________________________________________________________________________________________
maxpooling2d_1 (MaxPooling2D)    (None, 12, 12, 32)    0           activation_2[0][0]               
____________________________________________________________________________________________________
dropout_1 (Dropout)              (None, 12, 12, 32)    0           maxpooling2d_1[0][0]             
____________________________________________________________________________________________________
flatten_1 (Flatten)              (None, 4608)          0           dropout_1[0][0]                  
____________________________________________________________________________________________________
dense_1 (Dense)                  (None, 128)           589952      flatten_1[0][0]                  
____________________________________________________________________________________________________
activation_3 (Activation)        (None, 128)           0           dense_1[0][0]                    
____________________________________________________________________________________________________
dropout_2 (Dropout)              (None, 128)           0           activation_3[0][0]               
____________________________________________________________________________________________________
dense_2 (Dense)                  (None, 46)            5934        dropout_2[0][0]                  
____________________________________________________________________________________________________
activation_4 (Activation)        (None, 46)            0           dense_2[0][0]                    
====================================================================================================
Total params: 605,454
Trainable params: 605,454
Non-trainable params: 0

Pythonからの取得

get_config()でlistとして情報を取得できる。

config = model.get_config()
for l in config:
    print(l)

上のサマリーよりは詳しい情報が取れる。json出力と情報量は同じっぽい。

{'class_name': 'Convolution2D', 'config': {'subsample': (1, 1), 'nb_filter': 32, 'nb_col': 3, 'trainable': True, 'init': 'glorot_uniform', 'W_regularizer': None, 'b_regularizer': None, 'bias': True, 'input_dtype': 'float32', 'W_constraint': None, 'nb_row': 3, 'name': 'convolution2d_1', 'activation': 'linear', 'activity_regularizer': None, 'border_mode': 'valid', 'dim_ordering': 'tf', 'batch_input_shape': (None, 28, 28, 1), 'b_constraint': None}}
{'class_name': 'Activation', 'config': {'activation': 'relu', 'trainable': True, 'name': 'activation_1'}}
{'class_name': 'Convolution2D', 'config': {'subsample': (1, 1), 'nb_filter': 32, 'nb_col': 3, 'trainable': True, 'init': 'glorot_uniform', 'W_regularizer': None, 'b_regularizer': None, 'W_constraint': None, 'nb_row': 3, 'name': 'convolution2d_2', 'activation': 'linear', 'activity_regularizer': None, 'bias': True, 'dim_ordering': 'tf', 'b_constraint': None, 'border_mode': 'valid'}}
{'class_name': 'Activation', 'config': {'activation': 'relu', 'trainable': True, 'name': 'activation_2'}}
{'class_name': 'MaxPooling2D', 'config': {'strides': (2, 2), 'trainable': True, 'name': 'maxpooling2d_1', 'pool_size': (2, 2), 'dim_ordering': 'tf', 'border_mode': 'valid'}}
{'class_name': 'Dropout', 'config': {'p': 0.25, 'trainable': True, 'name': 'dropout_1'}}
{'class_name': 'Flatten', 'config': {'trainable': True, 'name': 'flatten_1'}}
{'class_name': 'Dense', 'config': {'output_dim': 128, 'activation': 'linear', 'trainable': True, 'b_regularizer': None, 'init': 'glorot_uniform', 'W_regularizer': None, 'input_dim': 4608, 'b_constraint': None, 'name': 'dense_1', 'W_constraint': None, 'activity_regularizer': None, 'bias': True}}
{'class_name': 'Activation', 'config': {'activation': 'relu', 'trainable': True, 'name': 'activation_3'}}
{'class_name': 'Dropout', 'config': {'p': 0.5, 'trainable': True, 'name': 'dropout_2'}}
{'class_name': 'Dense', 'config': {'output_dim': 46, 'activation': 'linear', 'trainable': True, 'b_regularizer': None, 'init': 'glorot_uniform', 'W_regularizer': None, 'input_dim': 128, 'b_constraint': None, 'name': 'dense_2', 'W_constraint': None, 'activity_regularizer': None, 'bias': True}}
{'class_name': 'Activation', 'config': {'activation': 'softmax', 'trainable': True, 'name': 'activation_4'}}
重みの出力

hd5形式で出力できる。

# save model
model.save('output/cnn.h5')

HD5のViewerで見るといい感じ

f:id:natsutan:20170212101613p:image

2017-02-01

なにわTECH道 Presents エンジニアによるエンジニアにためのセミナーパソナテック Deep Learning実装祭り!! いよいよ開催

https://naniwa-tech-dou.connpass.com/event/49177/

僕も2枠発表します。

前半が組み込み実装の話で、途中から一気に農業+AI+IoTの話になります。

僕の発表自体は

https://tfug-tokyo.connpass.com/event/49668/?utm_campaign=recent_events&utm_medium=atom&utm_source=feed

こっちでも聞けます。

2017-01-03

[][][] 行列プログラマ

2ヶ月かけて読んだ感想を。

全体的には、悪い本ではないのですが、僕には合わなかったです。

本を読み始めた理由

Computer VisionやDeep Learningで出てくる行列式がすらすら分かり、Pythonで簡単に動作を確認できるようになりたくて読んでみました。

内容について

まず、Pythonのコードはそれほど出てこないです。出てきたとしても、俺俺行列実装を使うので実践では役に立たない。みんな実際にプログラミングするときにはnumpy使うよね。最初からnumpyで説明しても良いんじゃないかと思いました。最初の方に出てくる漫画は面白いのですが、途中からほとんど出てきません。残念。基本的にはラムダ内包表記で行けるところまで行くので、最初は面白かったのですが、途中からわけわからなくなって結局numpyで少しずつ動かしてました。

扱うトピックとしてデータの圧縮や、暗号化があるのですが、各章でちょっとずつ勉強するのでもやもやしたまま勉強を続けないといけないのが僕には辛かったです。

勉強の効率の問題なのですが、例えばComputer Visionでの行列についてはComputer Visionの良い本を読んだほうが、数学も含めて詳しく書いてあり最初からその分野の良い本にあたった方が良いです。

参考までに僕が良かったと思う本です。

  • これなら分かる応用数学教室―最小二乗法からウェーブレットまで― 
  • Multiple View Geometry in Computer Vision
  • 最小二乗法と測量網平均の基礎

この本の良かったところ。

得たものとしては、GF(2)、三角化、Ker、Im等の考え方がわかったこと。翻訳に関しては特に詰まることもなく、読みやすかったです。原書は安いのでそっちと合わせて読んでも良いと思います。行列の応用についても、広く浅くカバーされていてどういう使いみちがあるのかわからない人には役に立つと思います。上に書いたとおり、ちゃんと勉強するならそのジャンルの良い本を読んだほうが理解が深まると思います。

ごく一部の人向けですが、最後のほうでノイマンのちょっと良い話が乗っているのがよかったです。