pythonで果物分類をしてみた①データ前処理編



#果物　（りんご、みかん、バナナ）　の画像認識





#目次
**1**.画像収集
**2**.収集画像の処理
**3**.画像の水増し
**4**.画像を学習/検証データにする
**5**.モデル構築と保存
**6**.結果のグラフ化
**7**.Flaskコードで説明
**8**.Herokuアップロード説明
**9**.記事URL





#1.画像の収集(スクレイピング)
####データ前処理
　　↓
####fruits-360（ダウンロード（apple,banana,orange))
　　↓
####フォルダ化
　　↓
####移動
* %mv コマンド を使用（ファイルやディレクトリを移動したり名前を変えるときに使用）

* !unzip コマンドを使用（unzip ZIPファイル ファイル名」で、指定したファイルを取り出すことができます）
　
　↓

####画像サイズの変更

* cv2.resize(img,(50,50))

　　↓
####matplotlibcのモジュールpyplot使用

* label="acc"

* label="val_acc"
　　↓
####One-hot ベクトル

* [One-hotベクトル](https://mathwords.net/onehot) (記事）



**画像収集には、以下を利用しました。**

*  3種類の果物（りんご、みかん、バナナ）の画像分類を実施するため、icrawler画像ファイルをから取得

*  [icrawler入門](https://aiacademy.jp/media/?p=352)


#icrawler から画像取得
```
pip install icrawler
```
これを実行してまずはicrawlerをインストールします。
ターミナルで実行します。

```
search_name = "orange jpg photo fruit"
```




#####画像ファイル取得結果
* りんご約１０００〜２０００枚
* みかん約２００枚
* バナナ約５００枚ほど（後ほど水増し調整を行います。）

検索キーワード（apple, orange, banana）
＊単体ではヒットしないので近いスペルを変更し、色々試してみる。

icrawlerからダウンロードした不要なデータ（検索キーワードと関係ない画像ファイル）は目で見て削除する。



**Googleコラボラトリーで実行（エディタ）**

```python:icrawler
from icrawler.builtin import BingImageCrawler
import sys
import os
folder_name = "orange"
search_name = "orange jpg photo fruit"
if not os.path.isdir(folder_name):
    os.makedirs(folder_name)
crawler = BingImageCrawler(storage = {"root_dir" : folder_name})
crawler.crawl(keyword = search_name, max_num = 1000)
```
* %cd　（自分の今いるディレクトリを移動するときに使うコマンド）
* !ls　（ファイルまたはディレクトリの内容を表示する）
* pwd　（現在いるディレクトリの表示。）



```#python:scrapingコード
import glob 
import os
import shutil
path ="./"
files = os.listdir(path)
dir_list = [f for f in files if os.path.isdir(os.path.join(path, f))]
num = 0
for i in dir_list :
    print(i)
    filenames = glob.glob( i+"/*")
    for f in filenames:
           try :
              shutil.move(f ,'./')
           except:
              shutil.move(f ,'./tmp_'+str(num)+"jpg")
              num+=1
```

#2.モデルの構築(データセットの作成・画像データの変更）

**モデル構築**
　　↓
**VGG16**
　　↓
**転移学習**
　　↓
**CNN**

* 　取得した画像ファイルをkerasで作成した、HDF5形式で保存

*  データセット
　　畳み込みニューラルネットワークVGG16を使用
　　https://www.y-shinno.com/keras-vgg16/


#VGG16とは
VGG16は「ImageNet」と呼ばれる大規模画像データセットで学習されたCNNモデルです。16層から成り立っており、入力画像を1000のクラスに分類することができます．
1000クラス（犬,猫,...など）以外の、たとえば人間の画像を認識させるとガスマスクと認識したりします（人間だと認識できていない）

#転移学習とは
転移学習とは、ある領域の知識を別の領域の学習に適用させる技術です。
転移学習は、学習済みモデルの重みは固定し、追加した層のみを使用して学習します。
#「転移学習のメリット①：学習時間を短縮できる」
同じ分野で十分に学習された学習済みモデルを利用すると、少ないデータ量かつ短い時間で学習可能です。

#「転移学習のメリット②データが少なくても高い精度を出せる」
画像認識などの分野では、学習させるための良質なデータセットがインターネットから自由にダウンロードできる場合もあります。




#Dropout,Dense　について

・model.add(Dropout(0.5))
　ドロップアウト – 過学習予防。全結合の層とのつながりを「50%」無効化しています。

・model.add(Dense(3, activation=’softmax’))
  全結合層。出力3（0~3の3クラス判定のため）
  Denseのところで、畳み込みニューラルネットワーク（CNN）の最終的な全結合層の
出力次元数の決め方は、判定するクラス数を指定します。


#５０％にする解説

```
top_model.add(Dropout(0.5))
```

Dense層の出力に対してDropoutを適用しています。Dropout はアンサンブル学習と同じ効果を（擬似的に）ひとつのネットワークで実現しています
###アンサンブル学習とは？
アンサンブル学習には大きく分けて3つのタイプ「バギング」「ブースティング」「スタッキング」があります。
複数のモデル（学習器）を融合させて1つの学習モデルを生成する手法です。
アンサンブル学習を行うことで精度の低いモデル（弱学習器）でも高精度を実現することができます。

```
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers
from os import listdir
number = 100
path_apple = [filename for filename in listdir('./fruits-360/Training/all_Apple/') if not filename.startswith('.')]
path_banana = [filename for filename in listdir('./fruits-360/Training/all_Banana/') if not filename.startswith('.')]
path_orange = [filename for filename in listdir('./fruits-360/Training/all_Orange/') if not filename.startswith('.')]
img_apple = []
img_banana = []
img_orange = []
for i in range(len(path_apple)):
   img = cv2.imread('./fruits-360/Training/all_Apple/'+ path_apple[i])
   img = cv2.resize(img,(50,50))
   img_apple.append(img)
for i in range(len(path_banana)):
   img = cv2.imread('./fruits-360/Training/all_Banana/'+ path_banana[i])
   img = cv2.resize(img,(50,50))
   img_banana.append(img)
for i in range(len(path_orange)):
   img = cv2.imread('./fruits-360/Training/all_Orange/'+ path_orange[i])
   img = cv2.resize(img,(50,50))
   img_orange.append(img)
X = np.array(img_apple + img_banana + img_orange)
y =  np.array([0]*len(img_apple) + [1]*len(img_banana) + [2]*len(img_orange))
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
input_tensor = Input(shape=(50,50, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation="sigmoid"))
top_model.add(Dropout(0.5))
top_model.add(Dense(64, activation='sigmoid'))
top_model.add(Dropout(0.5))
top_model.add(Dense(32, activation='sigmoid'))
top_model.add(Dropout(0.5))
top_model.add(Dense(3, activation='softmax'))
model = Model(input=vgg16.input, output=top_model(vgg16.output))
for layer in model.layers[:15]:
   layer.trainable = False
model.compile(loss='categorical_crossentropy',
             optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
             metrics=['accuracy'])


#グラフ用
history = model.fit(X_train, y_train, batch_size=32, epochs=1, verbose=1, validation_data=(X_test, y_test))
score = model.evaluate(X_test, y_test, batch_size=32, verbose=0)
print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score))
#acc, val_accのプロット
plt.plot(history.history["acc"], label="acc", ls="-", marker="o")
plt.plot(history.history["val_acc"], label="val_acc", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()
#モデルを保存
model.save("model.h5")

# 学習
model.fit(X_train, y_train, batch_size=100, epochs=50, validation_data=(X_test, y_test))
model.save('model.h5')
```

#3.画像の水増し
###ImageDataGeneratorで水増しします。
・flow_from_directory(directory)　・　flow(x, y)　は使用していません。



####画像xに対して10回分、データ水増しを適用
####各画像に付き、ノイズを加えて10枚ずつデータ拡張を適用

```
def draw_images(generator, x, dir_name, index):
    # 出力ファイルの設定
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir, save_prefix=save_name, save_format='jpg')
    # 1つの入力画像から何枚拡張するかを指定
    # g.next()の回数分拡張される
    for i in range(10):
        bach = g.next()
```


```
import os
import glob
import numpy as np
from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img
def draw_images(generator, x, dir_name, index):
    # 出力ファイルの設定
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir, save_prefix=save_name, save_format='jpg')
    # 1つの入力画像から何枚拡張するかを指定
    # g.next()の回数分拡張される
    for i in range(10):
        bach = g.next()
if __name__ == '__main__':
    # 出力先ディレクトリの設定(適宜変更)
    output_dir = "/content/drive/MyDrive/ColabNotebooks/scraping_data/extended_orange"
    if not(os.path.exists(output_dir)):
        os.mkdir(output_dir)
    # 拡張する画像群の読み込み
    images = glob.glob(os.path.join('/content/drive/MyDrive/ColabNotebooks/scraping_data/orange', "*.jpg"))#ファイル名を指定
    # 拡張する際の設定
    generator = ImageDataGenerator(
                    rotation_range=90, # 90°まで回転
                    width_shift_range=0.1, # 水平方向にランダムでシフト
                    height_shift_range=0.1, # 垂直方向にランダムでシフト
                    channel_shift_range=50.0, # 色調をランダム変更
                    shear_range=0.39, # 斜め方向(pi/8まで)に引っ張る
                    horizontal_flip=True, # 垂直方向にランダムで反転
                    vertical_flip=True # 水平方向にランダムで反転
                    )
    # 読み込んだ画像を順に拡張
    for i in range(len(images)):
        img = load_img(images[i])
        # 画像を配列化して転置a
        x = img_to_array(img)
        x = np.expand_dims(x, axis=0)
        # 画像の拡張
        draw_images(generator, x, output_dir, i)
```

###枚数確認(水増し後）
Pythonでリスト（list型オブジェクト）のサイズ（要素数）を取得するには組み込み関数len()を使う。

```
print( "りんご枚数 :" , len(img_apple) )
print( "バナナ枚数 :" , len(img_banana) )
print( "オレンジ枚数 :" , len(img_orange) )

りんご枚数 : 4615
バナナ枚数 : 2698
オレンジ枚数 : 10282
```