どうも!いつもはビーチリゾートについての情報を主に扱っていますが、今回は趣向を大きく変えて、Pythonのディープラーニングのフレームワーク、Tensorflowを使って、世界のビーチを分類してみよう!ということをやっていこうと思います!
なぜこれをやろうと思ったか、そして記事にしようとしたかというと、
理由
- 会社でTensorflow(ディープラーニングのフレームワーク)の話があがった。
- もともと大学院ではプログラムを書いてて、Pythonとかも多少は読める(決して書けると言えるようなレベルではない)ので、なんかできそうだなと思った。
- ヨーロッパのビーチとアジアのビーチって結構違うなあと思っていたので、それをなんとなく具現化したかった。
- 記事にした理由は、備忘録ぐらいの意味
です!
基本的にディープラーニングの詳しい知識やTensorflowの知識はありません!いろんなサイトのコードを参考に作成しております!それを統合するスキルはないので、不細工なフローになっています!すいません!
参考にさせていただいた先人の方々は本当に偉大です。すごいの一言です。ありがとうございました!
それでは早速やっていきたいと思います!どうやってやるかに興味がない人は結果だけ見てもらえれば大丈夫です!
Info.
環境
環境
- OS:mac OS X EI Capitan ver. 10.11.6
- Python:2.7.3
- フレームワーク:Tensorflow(インストール済み)
まあ、ネットサーフィンが主なユーザーには一般的な環境ですよね。
ゴール
ゴールは、ヨーロッパとアジアのビーチ画像を学習させ、それぞれ実際にテストして分類できるか見てみる!と設定します。
例えば、下記画像はアジアのビーチなのですが、これをテストした際に、”アジア”に分類されれば成功!
ゴールまでのフロー
フローとしては、
Point!
- WEB上の画像を取得して
- その画像をリサイズして(このフローはいらないかもです)
- 画像データを学習データに変換して
- 学習データを使ってモデル作成して
- 実際にテスト
という感じでやっていきます!
Tutorial.1 画像取得
まず、WEBからヨーロッパ、アジアの画像を取得する必要があります。コードに関しては、下記URLを参考にさせていただきました!プログラマーの味方Qiita、そして投稿者の方、ありがとうございます!
実際には自分のディレクトリとかに合わせて、ほんの少しだけいじりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# -*- coding: utf-8 -*- import urllib import urllib2 import os.path import sys from HTMLParser import HTMLParser def download(url): img = urllib.urlopen(url) print(os.path.basename(url)) localfile = open('./web_image/'+os.path.basename(url), 'wb') localfile.write(img.read()) img.close() localfile.close() def get_url_root(url): if("http://" in url): url_delet_http = url.lstrip("http://") if("/" in url_delet_http): url_root = "http://" + url_delet_http[0:url_delet_http.find("/")] return url_root elif("https://" in url): url_delet_http = url.lstrip("https://") if("/" in url_delet_http): url_root = "http://" + url_delet_http[0:url_delet_http.find("/")] return url_root return 0 class imgParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) def handle_starttag(self,tagname,attribute): if tagname.lower() == "img": for i in attribute: if i[0].lower() == "src": img_url=i[1] f = open("collection_url.txt","a") f.write("%s\t"%img_url) f.close() if __name__ == "__main__": print('サイトURL入力') input_url = raw_input('>>> ') serch_url = input_url htmldata = urllib2.urlopen(serch_url) parser = imgParser() parser.feed(htmldata.read()) parser.close() htmldata.close() f = open("collection_url.txt","r") for row in f: row_url = row.split('\t') len_url = len(row_url) f.close() number_url = [] for i in range(0,(len_url-1)): number_url.append(row_url[i]) for j in range(0,(len_url-1)): url = number_url[j] print (url) if("../" in url): root_url = get_url_root(serch_url) if(root_url!=0): url = url.replace("..",root_url) print url download(url) elif("http://" in url): download(url) elif("https://" in url): download(url) else: download(input_url + url) os.remove("collection_url.txt") |
このget_image.pyが置いてあるディレクトリに移動して、
$ python get_image.py
と入力すると、”写真を取得したいサイトのURLを入力してください。”と出るので、そのままURLを入力します。今回は一応商用利用が可能なサイト、pixaboyを利用しました。
具体的には”ビーチ ヨーロッパ”、”ビーチ アジア”で検索した時のURLを指定しました。保存先は’web_image’というディレクトリに入ってます。今回はとりあえず、それぞれ20枚ほど取得しています。
Tutorial.2 画像リサイズ
これ必要ないかもしれません。が、一応やっておくことにします。これは、下記URLを参考にさせていただきました!ありがとうございました!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from PIL import Image import os import glob # JPEG (512, 512) RGB files = glob.glob('./web_image/*.jpg') for f in files: img = Image.open(f) img_resize = img.resize((64, 64)) ftitle, fext = os.path.splitext(f) print(ftitle,fext) img_resize.save(ftitle + '_resize' + fext) |
このコードを先ほどのget_image.pyと同じディレクトリに置いておきます。
そしてそのディレクトリで、
$ python resize.py
を実行すると、先ほど取得した”web_image”ディレクトリ内にある.jpgファイルが64 x 64サイズにリサイズされています。
そして次のフローのために、get_image.pyとresize.pyがあるディレクトリに、”train”というディレクトリを作成し、更にその中にそれぞれ”Europe”、”Asia”というサブディレクトリを作成し、該当する画像を格納しておきましょう!
本当はこの作業もしないようにプログラム組めって話なんですけどね、これが出来ないのが僕のスキル不足ですね、ほんと悲しくなります。
Tutorial.3 学習データ生成
先ほどリサイズした画像を基に、モデルの元となる学習データを生成します。tfrecordとかのファイル形式にする方法もあると思いますが、今回はcsvファイルで作成しております。これに関しては、下記サイトを参考にしております。ありがとうございました!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os import csv if __name__ == '__main__': #outdir = sys.argv[1] outdir = './train' if not os.path.isdir(outdir): sys.exit('%s is not directory' % outdir) names = { "Asia": 0, "Europe": 1, } #exts = ['.PNG','.JPG','.JPEG'] exts = ['.jpg','.JPG'] f = open('train.csv', 'w') writer = csv.writer(f, lineterminator='\n') for dirpath, dirnames, filenames in os.walk(outdir): for dirname in dirnames: if dirname in names: n = names[dirname] member_dir = os.path.join(dirpath, dirname) for dirpath2, dirnames2, filenames2 in os.walk(member_dir): if not dirpath2.endswith(dirname): continue for filename2 in filenames2: (fn,ext) = os.path.splitext(filename2) if ext.upper() in exts: #img_path = os.path.join(dirpath2, filename2) img_path = os.path.join(filename2) print '%s %s' % (img_path, n) writer.writerow([img_path, n]) f.close() |
これも先ほどと同じディレクトリに置いておきます。
実行コマンドは
$ python gen_testdata.py
です。
もとのコードに追加してcsvで落とすという処理を加えております。
“train”というディレクトリの中の”Europe”というサブディレクトリの画像は0、”Asia”は1、に識別されます。
そしてまた面倒なのですが、csvで落としたものをtxt形式に変換しておきます。そして出来たtrain.txtをコピーしてtest.txtを作っておきます。
※test.txtはモデル作成時のバリデーションのために使用するのですが、今回は兎にも角にもモデルさえ作れれば(精度は気にせず)、OKの考えのもとやっているので、test.txtは適当にしてます。
また、”data”というディレクトリを作成し、リサイズされた画像をどかっと格納しておきます。
本当にクソなフローにしてしまい、すいません。後で気が向いたら修正しておきます。
Tutorial.4 学習→モデル作成
どんな学習方法かはわからないです!とにかく、下記URLを参考にしております!ありがとうございました!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# -*- coding: utf-8 -*- import sys import cv2 import numpy as np import tensorflow as tf import tensorflow.python.platform import os NUM_CLASSES = 2 IMAGE_SIZE = 28 IMAGE_PIXELS = IMAGE_SIZE*IMAGE_SIZE*3 flags = tf.app.flags FLAGS = flags.FLAGS flags.DEFINE_string('train', 'train.txt', 'File name of train data') flags.DEFINE_string('test', 'test.txt', 'File name of train data') flags.DEFINE_string('image_dir', 'data', 'Directory of images') flags.DEFINE_string('train_dir', 'logdir', 'Directory to put the training data.') flags.DEFINE_integer('max_steps', 200, 'Number of steps to run trainer.') flags.DEFINE_integer('batch_size', 10, 'Batch size' 'Must divide evenly into the dataset sizes.') flags.DEFINE_float('learning_rate', 1e-5, 'Initial learning rate.') def inference(images_placeholder, keep_prob): """ 予測モデルを作成する関数 引数: images_placeholder: 画像のplaceholder keep_prob: dropout率のplace_holder 返り値: y_conv: 各クラスの確率(のようなもの) """ # 重みを標準偏差0.1の正規分布で初期化 def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) # バイアスを標準偏差0.1の正規分布で初期化 def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) # 畳み込み層の作成 def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') # プーリング層の作成 def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') # 入力を28x28x3に変形 x_image = tf.reshape(images_placeholder, [-1, 28, 28, 3]) # 畳み込み層1の作成 with tf.name_scope('conv1') as scope: W_conv1 = weight_variable([5, 5, 3, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) tf.summary.histogram("wc1", W_conv1) # プーリング層1の作成 with tf.name_scope('pool1') as scope: h_pool1 = max_pool_2x2(h_conv1) # 畳み込み層2の作成 with tf.name_scope('conv2') as scope: W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) tf.summary.histogram("wc2", W_conv2) # プーリング層2の作成 with tf.name_scope('pool2') as scope: h_pool2 = max_pool_2x2(h_conv2) # 全結合層1の作成 with tf.name_scope('fc1') as scope: W_fc1 = weight_variable([7*7*64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) # dropoutの設定 h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 全結合層2の作成 with tf.name_scope('fc2') as scope: W_fc2 = weight_variable([1024, NUM_CLASSES]) b_fc2 = bias_variable([NUM_CLASSES]) # ソフトマックス関数による正規化 with tf.name_scope('softmax') as scope: y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) # 各ラベルの確率のようなものを返す return y_conv def loss(logits, labels): """ lossを計算する関数 引数: logits: ロジットのtensor, float - [batch_size, NUM_CLASSES] labels: ラベルのtensor, int32 - [batch_size, NUM_CLASSES] 返り値: cross_entropy: 交差エントロピーのtensor, float """ # 交差エントロピーの計算 cross_entropy = -tf.reduce_sum(labels*tf.log(logits)) #cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits)) # TensorBoardで表示するよう指定 tf.summary.scalar("cross_entropy", cross_entropy) return cross_entropy def training(loss, learning_rate): """ 訓練のOpを定義する関数 引数: loss: 損失のtensor, loss()の結果 learning_rate: 学習係数 返り値: train_step: 訓練のOp """ train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss) return train_step def accuracy(logits, labels): """ 正解率(accuracy)を計算する関数 引数: logits: inference()の結果 labels: ラベルのtensor, int32 - [batch_size, NUM_CLASSES] 返り値: accuracy: 正解率(float) """ correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(labels, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) tf.summary.scalar("accuracy", accuracy) return accuracy if __name__ == '__main__': f = open(FLAGS.train, 'r') # データを入れる配列 train_image = [] train_label = [] for line in f: # 改行を除いてスペース区切りにする line = line.rstrip() l = line.split() # データを読み込んで28x28に縮小 #print (FLAGS.image_dir + '/' + l[0].split(',')[0]) img = cv2.imread(FLAGS.image_dir + '/' + l[0].split(',')[0]) img = cv2.resize(img, (28, 28)) # 一列にした後、0-1のfloat値にする train_image.append(img.flatten().astype(np.float32)/255.0) # ラベルを1-of-k方式で用意する tmp = np.zeros(NUM_CLASSES) #tmp[int(l[1])] = 1 tmp[int(l[0].split(',')[1])] = 1 train_label.append(tmp) # numpy形式に変換 train_image = np.asarray(train_image) train_label = np.asarray(train_label) f.close() f = open(FLAGS.test, 'r') test_image = [] test_label = [] for line in f: line = line.rstrip() l = line.split() img = cv2.imread(FLAGS.image_dir + '/' + l[0].split(',')[0]) img = cv2.resize(img, (28, 28)) test_image.append(img.flatten().astype(np.float32)/255.0) tmp = np.zeros(NUM_CLASSES) #tmp[int(l[1])] = 1 tmp[int(l[0].split(',')[1])] = 1 test_label.append(tmp) test_image = np.asarray(test_image) test_label = np.asarray(test_label) f.close() with tf.Graph().as_default(): # 画像を入れる仮のTensor images_placeholder = tf.placeholder("float", shape=(None, IMAGE_PIXELS)) # ラベルを入れる仮のTensor labels_placeholder = tf.placeholder("float", shape=(None, NUM_CLASSES)) # dropout率を入れる仮のTensor keep_prob = tf.placeholder("float") # inference()を呼び出してモデルを作る logits = inference(images_placeholder, keep_prob) # loss()を呼び出して損失を計算 loss_value = loss(logits, labels_placeholder) # training()を呼び出して訓練 train_op = training(loss_value, FLAGS.learning_rate) # 精度の計算 acc = accuracy(logits, labels_placeholder) # 保存の準備 saver = tf.train.Saver() # Sessionの作成 sess = tf.Session() # 変数の初期化 sess.run(tf.global_variables_initializer()) # TensorBoardで表示する値の設定 summary_op = tf.summary.merge_all() summary_writer = tf.summary.FileWriter(FLAGS.train_dir, sess.graph) # 訓練の実行 for step in range(FLAGS.max_steps): for i in range(int(len(train_image)/FLAGS.batch_size)): # batch_size分の画像に対して訓練の実行 batch = FLAGS.batch_size*i # feed_dictでplaceholderに入れるデータを指定する sess.run(train_op, feed_dict={ images_placeholder: train_image[batch:batch+FLAGS.batch_size], labels_placeholder: train_label[batch:batch+FLAGS.batch_size], keep_prob: 0.5}) # 1 step終わるたびに精度を計算する train_accuracy = sess.run(acc, feed_dict={ images_placeholder: train_image, labels_placeholder: train_label, keep_prob: 1.0}) print("step %d, training accuracy %g"%(step, train_accuracy)) # 1 step終わるたびにTensorBoardに表示する値を追加する summary_str = sess.run(summary_op, feed_dict={ images_placeholder: train_image, labels_placeholder: train_label, keep_prob: 1.0}) summary_writer.add_summary(summary_str, step) # 訓練が終了したらテストデータに対する精度を表示 print("test accuracy %g"%sess.run(acc, feed_dict={ images_placeholder: test_image, labels_placeholder: test_label, keep_prob: 1.0})) # 最終的なモデルを保存 save_path = saver.save(sess, os.getcwd() + "\\model.ckpt") save_path = saver.save(sess, "./model/model.ckpt") |
保持しているファル形式、パス構造に応じて多少コードを変更しております。
実行コマンドは、
$ python train.py
これでとにかく、モデルが出来ました!
ちなみに、精度は、
step 196, training accuracy 0.908257 step 197, training accuracy 0.917431 step 198, training accuracy 0.926606 step 199, training accuracy 0.926606 test accuracy 0.926606
当たり前ですが、testの精度とtrainingの精度は一緒です。なので、出したはいいものの、なんの参考にもなりません。
Tutorial.5 テスト
そして出来たモデルを基に実際にアジアビーチの画像をテストしてみましょう!これのコードも下記URLを参考にしております!ありがとうございました!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
#!/usr/bin/env python #! -*- coding: utf-8 -*- import sys import numpy as np import tensorflow as tf import cv2 NUM_CLASSES = 2 IMAGE_SIZE = 28 IMAGE_PIXELS = IMAGE_SIZE*IMAGE_SIZE*3 def inference(images_placeholder, keep_prob): """ モデルを作成する関数 引数: images_placeholder: inputs()で作成した画像のplaceholder keep_prob: dropout率のplace_holder 返り値: cross_entropy: モデルの計算結果 """ def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') x_image = tf.reshape(images_placeholder, [-1, 28, 28, 3]) with tf.name_scope('conv1') as scope: W_conv1 = weight_variable([5, 5, 3, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) with tf.name_scope('pool1') as scope: h_pool1 = max_pool_2x2(h_conv1) with tf.name_scope('conv2') as scope: W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) with tf.name_scope('pool2') as scope: h_pool2 = max_pool_2x2(h_conv2) with tf.name_scope('fc1') as scope: W_fc1 = weight_variable([7*7*64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) with tf.name_scope('fc2') as scope: W_fc2 = weight_variable([1024, NUM_CLASSES]) b_fc2 = bias_variable([NUM_CLASSES]) with tf.name_scope('softmax') as scope: y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) return y_conv if __name__ == '__main__': test_image = [] for i in range(1, len(sys.argv)): img = cv2.imread(sys.argv[i]) img = cv2.resize(img, (28, 28)) test_image.append(img.flatten().astype(np.float32)/255.0) test_image = np.asarray(test_image) images_placeholder = tf.placeholder("float", shape=(None, IMAGE_PIXELS)) labels_placeholder = tf.placeholder("float", shape=(None, NUM_CLASSES)) keep_prob = tf.placeholder("float") logits = inference(images_placeholder, keep_prob) sess = tf.InteractiveSession() saver = tf.train.Saver() sess.run(tf.initialize_all_variables()) saver.restore(sess, "./model/model.ckpt") for i in range(len(test_image)): pred = np.argmax(logits.eval(feed_dict={ images_placeholder: [test_image[i]], keep_prob: 1.0 })[0]) print pred |
これも多少、基のコードから変更したような気がします。そして先ほどのアジアビーチの画像をtest.jpgとし、test.pyと同じディレクトリに置いてコマンドを実行します。
$python test.py test.jpg 1
出ました!1という結果なので、ヨーロッパに分類されてますね!間違いですね笑!
まあ、画像も少ないですし、画像自体もほんとにアジアのものかとか、信憑性の高くない画像を使っているので、仕方がないかと思います。画像をもっと増やしたり、入れてる画像を精査したりすればもっと精度は上がると思います!
ちなみに下記ヨーロッパのビーチ画像でテストすると、
$python test.py test2.jpg 1
となり、一応こっちは分類出来ました!
まとめ
いかがでしたでしょうか。今回精度とかは全然気にしてなかったので、他の画像とかで、ちゃんとできるかはわからないです。本格的にやろうと思っている方は、他のサイトを検索してみたほうが良いと思います。
今回は単純に分類でしたが、画像同士の距離をマッピングして可視化できるtensorboardというのがあるので、時間があるときにそれも書けたらと思います。