あっぱれ日和、 - What a "Appare" day -

あっぱれ日和、 - What a "Appare" day -

通りすがりのしがない映画好きが綴る、モノづくりの記録や備忘録。そして、影響を受けた映画の記録。

スクレイピング メモ

はじめに、

この記事はWebスクレイピングに関する備忘録として記録します。
自分が実際にスクレイピングを実装した際に今後使えるなと思ったら、適宜更新していく予定ですのでよろしくお願いします。
言語はpythonです。
メモのような感じで書いていきます。

サイトの動的な値をとってくる

(20200809~記)
今回は株価のサイトの値をとってくる
今回は株探のサイトの日産(7201)で試してみます。

決算のページは以下のような感じ

f:id:appare99:20200809161143p:plain
2020年8月9日現在の日産の決算ページ(株探)

ここの2020年3月の営業利益の部分(以下の赤い矢印が指している赤い枠の部分)の値をとってくる
f:id:appare99:20200809164312p:plain
ここの値は「検証」で観てみるとtr要素(Table Row)の中の「セル」をtd要素(Table Data)に値が入っているのが分かります。
f:id:appare99:20200809164322p:plain

よってポイントは以下の通り

  • タブの値をとってくる
  • とってきた値の数字のみを抽出(ただしマイナスの文字は残す)

出たエラーも含めて手順を踏まえて書いていく

  • (1) まずhtmlの情報を収集するためにBeautifulSoupを使用する
  • (2) 対象のtdの値を抽出する。今回はwebページ(html)の中の46番目のtdの値なので[46]と指定する。
  • (3) 正規表現を使用して数字のみを抽出する。詳細は以下のメモに記載。
import re
import urllib.request, urllib.error
from bs4 import BeautifulSoup

html = urllib.request.urlopen("https://kabutan.jp/stock/finance?code=7201") # (1)まずhtmlの情報を収集するためにBeautifulSoupを使用する

soup = BeautifulSoup(html)
td = str(soup.find_all("td")) [46] #(2) 対象のtdの値を抽出する。今回はwebページ(html)の中の46番目のtdの値なので[46]と指定する。

value = re.sub(r"[^-\d]", "", td) #(3) 正規表現を使用して数字のみを抽出する。詳細は以下のメモに記載。

print(int(value))
# printの出力は-40469となる
上記コード化する際の注意点 and メモ
  • (2)のところでは-40,469という値が入ってくる。この時td = str(soup.find_all("td") [46])ではなく、td = soup.find_all("td") [46]とすると(3)のところで「expected string or bytes-like object」というエラーが出るので注意。
  • (2)の段階でtdに入っている値は-40,469なのでこれを-40469という値にしたい。このため数字とマイナス(-)の文字のみ抽出する必要がある((3)の処理)。これには正規表現を用いる。[^-\d]ではマイナス(-)と数字(\d)以外([^])の文字は""でなしにするという意味。
別解(こっちの方が汎用性があるのでいいかも)

例えば、上記画像の日産の決算の過去最高の売上高の表から過去最高の売上高の値、12189519を取り出したいと思った時、上記の方法で考えると

td = str(soup.find_all("td") [237])

とすれば取り出せる。
一方で以下の画像のセレス(3969)の決算の過去最高の売上高の表から過去最高の売上高の値、16510を取り出したいと思った時td = str(soup.find_all("td") [237])では1.34という値が抽出されてしまう。
tdのナンバリングが銘柄ごとの決算の数などによってかわってきてしまう。

f:id:appare99:20200810111550p:plain

これに対しての解決策としてテーブルをまず取り出してそのテーブルの中のtdのナンバリングで値を抽出するという方法で他の銘柄に対しても値をとってこれる。
今回の場合、過去最高の売上高の表はこのhtmlの13番目になるので上記のコードの「td = str(soup.find_all("td") [237])」の部分を以下に変えればよい。

table = soup.find_all("table")[12]
value = table.find_all("td")[0]

詳しく見ると、table = soup.find_all("table")[12]の段階で以下のような値がとってこれる。

<table>
<thead>
<tr>
<th class="fb_01" scope="col"> </th>
<th class="fb_02" scope="col">売上高</th>
<th class="fb_02" scope="col">営業益</th>
<th class="fb_02" scope="col">経常益</th>
<th class="fb_02" scope="col">最終益</th>
<th class="fb_03" scope="col">修正1株益</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">過去最高</th>
<td>16,510</td>
<td>1,221</td>
<td>960</td>
<td>650</td>
<td>62.6</td>
</tr>
<tr>
<th scope="row">決算期</th>
<td>2019.12  </td>
<td>2018.12  </td>
<td>2017.12  </td>
<td>2017.12  </td>
<td>2017.12  </td>
</tr>
</tbody>
</table>

tdの1番目(ナンバリングは0)のところに過去最高値が入っているので「table.find_all("td")[0]」でとってきたtableの値からとりだす。

機械学習初心者がDeep Learningを試してみる season CNN ~リベンジ編:第6話 改良、さらなる高みへ。Alexnetモデルの実装~

はじめに、

こんにちは、そしてこんばんわ。
前回の記事(以下リンク)ではData Augmentationを試してみました。この時は学習データを増やすことで精度が向上するかを試しました。で結果は60%ほどまでしか行きませんでした。
apao-m-appare99.hatenablog.com


最近、仕事が忙しいのとオンライン英会話に没頭していたので更新できていませんでした。
嗚呼、早くDeepLearningでワンちゃん猫ちゃん品種認識作りたい!
今回も前回も記事の続きとなります。今回はAlexnetを実装してみるということを実践してみたいと思います。

【目次】

Alexnetとは

今までのモデルは1998年に考案されたLeNetと呼ばれるCNNモデルです。
今回使用するのは2012年に開催された画像認識のコンテストであるILSVRCにおいてDeep Leerningのモデルとして初めて優勝したAlexnetです。

元の論文に記載されているモデルとしては以下のようなものになります。

f:id:appare99:20200725185152p:plain
AlexNet architecture

これオフィシャルな図なのですが画像が切れてしまってますよね。。。
以下の画像の方が分かりやすいかもしれません。以下は
Understanding AlexNet | Learn OpenCV
から引用した画像になります。

f:id:appare99:20200725185427p:plain
AlexNet architecture

実装環境

そういえば、ずっと実装環境について書いていなかった。
実装環境はずっと変わっていないです。すべての記事で同じ環境で行っています。
環境は次の通りになります。

  • OSはwindows10
  • メモリは8G
  • プログラムの言語はpythonでエディタはjupyter notebook

プログラム

コードを書くにあたり参考にしたサイトは次のサイトになります。
qiita.com
qiita.com

そして、今回のモデル構築部分のプログラムは以下になります。

from keras.initializers import TruncatedNormal, Constant

def conv2d(filters, kernel_size, strides=(1, 1), bias_init=1, **kwargs):
    trunc = TruncatedNormal(mean=0.0, stddev=0.01) # 正規分布で重みを初期化
    cnst = Constant(value=bias_init) # 全ての重みを定数(ここではbias_init=1で初期化)
    # 活性化関数relu
    # ゼロパディング
    return Conv2D(
        filters,
        kernel_size,
        strides=strides,
        padding='same',
        activation='relu',
        kernel_initializer=trunc,
        bias_initializer=cnst,
        **kwargs
    )
def dense(units, activation='tanh'):
    trunc = TruncatedNormal(mean=0.0, stddev=0.01)
    cnst = Constant(value=1)
    return Dense(
        units,
        activation=activation,
        kernel_initializer=trunc,
        bias_initializer=cnst,
    )
from keras.models import Sequential
from keras.layers.convolutional import MaxPooling2D
from keras.layers import Activation, Conv2D, Flatten, Dense,Dropout
from keras.optimizers import SGD, Adadelta, Adagrad, Adam, Adamax, RMSprop, Nadam
import time
from keras.layers.normalization import BatchNormalization


model = Sequential()

# 第1畳み込み層
model.add(conv2d(96, 11, strides=(4,4), bias_init=0, input_shape=(image_size, image_size, 3)))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())

# 第2畳み込み層
model.add(conv2d(256, 5, bias_init=1))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())

# 第3~5畳み込み層
model.add(conv2d(384, 3, bias_init=0))

model.add(conv2d(384, 3))

model.add(conv2d(256, 3))

model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())

# 密結合層
model.add(Flatten())
model.add(dense(4096))
model.add(Dropout(0.5))
model.add(dense(4096))
model.add(Dropout(0.5))

# 読み出し層
model.add(dense(dense_size, activation='softmax'))

# コンパイル
model.compile(optimizer=SGD(lr=0.01), loss='categorical_crossentropy', metrics=['accuracy'])

model.summary()

実際に学習させてみる①

ここで前回の記事では以下のData Augmentationで精度が60%程に達したということでした。なので今回は以下のデータを元データに加えたデータで学習させていきましょう。

  • 上下反転
  • 左右反転
  • Cutout(画面の一部を塗りつぶす)
  • 画像回転(60°~120°)×3

...と思ったのですがこれだと以下のようなエラーが出てきました。Xのところは数字が入ってました。

MemoryError: Unable to allocate XXXX GiB for an array with shape (XXXXXXX, 227, 227, 3) and data type float32

●ここでポイント
今回の実装環境では以下のようなエラーが出てきます。

MemoryError: Unable to allocate 29.8 GiB for an array with shape (51688, 227, 227, 3) and data type float32

これはおそらくtensorflowで使うメモリを使いすぎてしまっていることによって発生しているものと思われます。(間違っていたらご指摘ください)
その証拠に例えば複数のそれぞれ違うプログラムを立ち上げて学習させていたとしましょう。この時、今エラーが起きているプログラム以外に回しているプログラムを止めて(shutdownして)、改めて学習をさせるとエラーが解消されることがあります。

しかし

  • 上下反転
  • 左右反転
  • Cutout(画面の一部を塗りつぶす)
  • 画像回転(60°~120°)×3

と元画像を含んだデータでは他のプログラムをshutdownしてもエラーが出たままになりました。

少しずつ減らしましたが、結局、元画像 +上下反転で学習させてみることになりました。これ以上増やすと上記エラーが出てきてしまい学習できないです。せっかくのData Augmntationの検証がパアになってしまいました。HAHAHA....

まあいいでしょう。この元画像 +上下反転で学習させてみます。
・・・
あまり時間は定かではないですが、2日~3日プログラムを回し続けた状態になりました。
そして3日目になって以下のメッセージが出て学習のプログラムが止まりました。

f:id:appare99:20200613190818p:plain

見てください。赤枠で囲まれているところ。エポック数100で回して91回目のエポック数の時に止まっている。後、9回だったのに。。。。
f:id:appare99:20200613192026p:plain

OMG!!!!!!!
頭の中の小梅太夫が叫んでます。「チクショー!!!」って。そして頭の中のザブングルが叫んでます「悔しいです!!!!」って。
もう混乱です。。。
というかこんな時間かけて結果1つ出せないとめんどくさくなってきます。。。

最近、私の大切なノートPCに負荷をかけすぎていると思うので当分の間はできるだけ学習とかは自粛したいなぁなんて思ってます。。。

そもそもDeep LearningってGPUとかが出てきたから現実性の出てきた技術なのにCPUしか積んでないノートPCで挑もうなんて最初から無謀だったんすかね。。。

愚痴多めで申し訳ないです。
学習方法①(実際に回してみる①)だと効率が悪いというかPCに負荷をかけすぎている気がしてならないので次の学習方法②(実際に回してみる②)を試してみたいと思います。

実際に学習させてみる②

「実際に学習させてみる①」とは別の方法として学習方法2ではモデルを保存しながら学習を繰り返していくという方法をとりたいと思います。
具体的には、

元画像を学習→学習させたモデルを保存(この時のモデルをモデル phase 1とする)→モデル phase 1を用いて今度は左右反転させたモデルを学習させる→学習させたモデルを保存(この時のモデルをモデル phase 2とする)→モデル phase 1を用いて今度は上下反転させたモデルを学習させる→...

というように学習させて学習させたモデルを保存、そして保存したモデルを今度は別の学習データ(上下反転、左右反転、Cutout...etc)を使って学習するということを繰り返していくことにしました。
この方法がいい方法なのかはわかりませんが、、、ただ、特徴量を最適化するための学習を繰り返して更新していくので特に問題ないじゃないかと思ってます。

まずは1回目の学習における検証の損失と精度になります。(phase 1)
1回目は普通の画像データを学習させます。
...。
検証損失: ????
検証精度: ????
...。
すいません。1回目の結果を出力させるのを忘れてしまっていました。

まずは2回目の学習における検証の損失と精度になります。(phase 2)
2回目は左右反転させた画像データを学習させたものになります。
検証損失: 1.72
検証精度: 0.559

続いて3回目の学習における検証の損失と精度になります。(phase 3)
3回目は上下反転させた画像データを学習させたものになります。
検証損失: 1.86
検証精度: 0.552
いや、悪化しとる...

続けます。4回目の学習における検証の損失と精度になります。(phase 4)
4回目はCutoutさせた画像(画像の一部を塗りつぶした画像)データを学習させたものになります。
検証損失: 1.17
検証精度: 0.689
お、精度が上がった!

そして5回目。5回目の学習における検証の損失と精度になります。(phase 5)
5回目はランダムな角度に回転させた画像データを学習させたものになります。
検証損失: 2.34
検証精度: 0.468
あらら、下がった。

なんか学習させていくのにつかれたのでphase 4の段階のモデルでいったん完成といたします。約70%の精度になります。時には引くことも大事ですよね。
精度が上がったり下がったりしてましたが、結局の原因はわからずですね。これが機械学習ブラックボックスと言われる所以ですね。

まとめ

とりあえず、猫ちゃんとワンちゃんの認識器が完成いたしました(完成したということにしましょう(笑))。精度は約70%ですが、まあ許容範囲とします。(甘えです申し訳ないです。)
次回以降はこの認識モデルを用いてLINEのチャットボットとして実装することに挑戦したいと思っております。

ちょっと考察

以下のグラフ画像は画像認識コンテストであるILSVRCにおいて優勝したチームのモデルの誤認識率を表しています。

引用ページは以下になります。
devopedia.org

f:id:appare99:20200613203529p:plain
Image Net画像認識コンテスト(ILSVRC)の年度ごとの優勝チームの誤認識率

こちらのグラフでAlexnetは16%の誤認識率になっています。つまり良くても84%ほどの精度にまでしかならないということでしょう。
それ以降のモデルだとさらに高い精度が期待できるかもしれません。いつかやってみる機会があればやってみたいです。

機械学習初心者がDeep Learningを試してみる season CNN ~リベンジ編:第5話 改良、さらなる高みへ。Data Augmentation~

はじめに、

こんにちは!!前回の記事(以下)では、モデルの改良を行いました。
apao-m-appare99.hatenablog.com
成果の一つとしてData Augmentationを実施し、学習させたところ54.5%程の精度に上昇ました(その前は23%ほどでした。)。
今回はData Augumentationに焦点を当ててどのように精度が向上するかを試したいと思います。
今回の記事はただ画像を増やしてグラフがどうなるかを見るだけになります。(本記事は収穫度でいうと100点満点中10点ぐらいです。あまり読む価値ないかも。。。)
そして、今回も執筆するのはあくまでも機械学習ど素人のエンジニアもどきである私です。温かく見守ってください。
また間違っていたらご指摘してくださると助かります。

【目次】

画像データをさらに50%増やす。

前回の記事(上記リンク参照)で各カテゴリ毎に約200枚のあるデータセットを4倍に増やしました(各カテゴリ毎に約800枚)。その結果23%から54%に精度が増えました。倍以上に増えたんです。
つまり100%データを増価させて精度が倍以上になるなら、さらに50%増やせば、さらに1.5倍以上に精度が上がるのではと考えたわけです。(頭の悪い考え方)
さて、今回は回転とノイズを入れてみることにします。

回転を加える

回転を加えた画像を3種類用意します。60°から120°の値をランダムに3つ値を求め、それぞれ各角度分回転させた画像を生成します。
回転させる関数のコードは次のようになります。

def rotate_image(image):
    return transform.rotate(image, angle=random.randint(60, 120), resize=False, center=None)

そして各画像を回転させて対象フォルダに保存させます。以下がコードになります。

from keras.utils import np_utils
from sklearn.model_selection import train_test_split

from skimage import transform
import random

from PIL import Image
import numpy as np
import glob
import os

DATAPATH = "./data/src_augmentation/" 
folder = os.listdir(DATAPATH) 

dense_size  = len(folder)

for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        for j in range(3):
            image = Image.open(file)
            image = image.convert("RGB")
            image = np.array(image, dtype=np.float32)
        
            image = rotate_image(image)
            image = Image.fromarray(np.uint8(image))
            #print(DATAPATH, name, i, file)
            image_path_separate = file.split('_') # ./data/src_augmentation/abyssinian\Abyssinian_1.jpgの1.jpgを抽出
            image_path_separate2 = image_path_separate[-1].split('.')
            image.save("./data/output2/" + name + "/" + name + "_" + image_path_separate2[0] + "_Rotate_" + str(j) + ".jpg")

出力例が以下、1枚目が元画像、2~4枚目が60°から120°の間のランダム値で回転させた画像。3枚目と4枚目がほとんど同じ画像ですが、一応別々の画像になります。

f:id:appare99:20200530195048j:plainf:id:appare99:20200603002728j:plainf:id:appare99:20200603002741j:plainf:id:appare99:20200603002749j:plain

ノイズを加える

ノイズはガウス雑音(ガウスノイズ)を加えることにします。
ガウス雑音は正規分布と等しい確率密度関数を持つ統計的雑音で、つまり、ノイズがとる値がガウス分布であるということ。(ウィキペディアより)
コードは以下のようになります。

def addGaussianNoise(src):
    row,col,ch= src.shape
    mean = 0
    var = 0.1
    sigma = 15
    gauss = np.random.normal(mean,sigma,(row,col,ch))
    gauss = gauss.reshape(row,col,ch)
    noisy = src + gauss

    return noisy

出力の際のコードは上記の回転の時とほぼ同じです。一応載せときます。

from keras.utils import np_utils
from sklearn.model_selection import train_test_split

from skimage import transform
import random

from PIL import Image
import numpy as np
import glob
import os
from skimage import util
import matplotlib.pyplot as plt
%matplotlib inline

DATAPATH = "./data/src_augmentation/" 
folder = os.listdir(DATAPATH) 

dense_size  = len(folder)


for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        image = Image.open(file)
        image = image.convert("RGB")
        image = np.array(image, dtype=np.float32)
        
        image = addGaussianNoise(image)
        image = Image.fromarray(np.uint8(image))
        #print(DATAPATH, name, i, file)
        image_path_separate = file.split('_') # ./data/src_augmentation/abyssinian\Abyssinian_1.jpgの1.jpgを抽出
        image_path_separate2 = image_path_separate[-1].split('.')[f:id:appare99:20200606150010p:plain]
        image.save("./data/output/" + name + "/" + name + "_" + image_path_separate2[0] + "_Noise.jpg")

出力例が以下。左が元画像、右がノイズを加えた画像です。

f:id:appare99:20200530195048j:plainf:id:appare99:20200603003630j:plain

増加させたデータセットで学習させてみる(各カテゴリ毎約1200枚の画像)

では学習させていきます。学習のモデルは過去の記事(以下リンク参照)に用いたコードと同じものを使用しています。
apao-m-appare99.hatenablog.com
結果は以下のようになりました。
右が精度のプロット、左が損失の推移プロットになります。

f:id:appare99:20200606145453p:plainf:id:appare99:20200606145501p:plain
なんか、グラフが粗いですね。ノイズ画像が画像として認識を妨げている可能性がありますね。
一旦、ノイズ画像を除いた場合でも学習をさせました。その際の学習結果の推移プロットを載せてみます。
f:id:appare99:20200606145953p:plainf:id:appare99:20200606150010p:plain
なんか粗くなくなりましたね。ノイズのあった時のブレブレのグラフと比べて安定しています。ちなみにノイズ画像を入れて学習させた時の精度は 0.623でノイズ画像を入れずに学習させた時の精度は0.601であまり変わらなかったです。

一旦、まとめ

今回画像を50%増やしましたが54%から60%と、わずか6%しか上昇しませんでした。

画像データをさらに増やしてみる。

ここから精度は増えるのかという好奇心があったのでやってみました。
ここまでで

  • 上下反転
  • 左右反転
  • Cutout(画像の一部を塗りつぶす)
  • 画像回転(60°~120°)×3

の画像を作成し、1カテゴリあたりの画像を約200枚から約1400枚へと増加させました。
ここでさらに-60°~-120°の画像を3枚ずつ作成します。これにより1カテゴリあたり約2000枚の画像を学習させることになります。
ちなみに60°~-120°の画像を3枚ずつ作成した結果の例は以下のようになります。

f:id:appare99:20200606151541j:plainf:id:appare99:20200606151556j:plainf:id:appare99:20200606151603j:plain
1カテゴリあたり約2000枚の画像を学習した結果は以下のようになりました。
毎度のことながら右が精度のプロット、左が損失の推移プロットになります。
f:id:appare99:20200606151940p:plainf:id:appare99:20200606151959p:plain
また評価精度は0.382、評価損失は2.39となりました。
...増やせばいいってもんでもないですね。というかこんなすぐに上限に達してしまうとは。
限界ですね。今回はこの辺で終わりますか。

まとめ

今回はData Augmentationということで画像を増やすことでどんな結果になるかを見てみました。
前回の記事では学習データを増やすことで精度を30%程上げられたので、希望をもって挑みましたが60%止まりの精度で終わってしまいました。ここがこのデータにおけるData Augmentationの限界なのかもしれません。
次回以降は60%の精度を出した。

  • 上下反転
  • 左右反転
  • Cutout(画像の一部を塗りつぶす)
  • 画像回転(60°~120°)×3

のデータセットを用いていくことにします。
次回はCNNの別のモデルを構築する予定です。予定しているのはAlexnetです。

機械学習初心者がDeep Learningを試してみる season CNN ~リベンジ編:第4話 改良~

はじめに、

こんにちは!
はい、前回の記事(以下)では、モデルの改良について考察を行いました。
apao-m-appare99.hatenablog.com
そして、本記事では実際に考察をもとに改良を試みていきます。
前回の記事でまとめましたが、今回改良のために実装するのは以下になります。

  • 入力データのピクセル数を増やす
  • モデルの層を増やす
  • 学習データを増やす

そして、今回も執筆するのはあくまでも機械学習ど素人のエンジニアもどきである私です。温かく見守ってください。
また間違っていたらご指摘のほどよろしくお願いいたします。

【目次】

改良策①:入力データのピクセル数を増やす

入力画像のピクセルは50×50でした。今回は100×100で実装してみます。それ以外のところは変更しません。コードは次の記事のimage_size = 50をimage_size = 100に買えるだけです。
apao-m-appare99.hatenablog.com
試しにどんな感じの画像か見てみましょう。
f:id:appare99:20200524000617p:plain
...画像が悪かったんだと思います。犬だとわかってないとわかんないですね。ただ50×50より鮮明になっていることは事実です。。。
ということで気になる学習結果を見てみましょう。

気になる結果

結果を以下に表示します。ちなみに前回の評価精度は約0.23です。

まずは評価精度は約0.208でした。
そして評価損失は約9.78でした。
...。
......。
いや、下がっとるやないか!
じゃあ一応精度と損失の推移プロットを以下に示します。(もう投げやり)
右が精度のプロット、左が損失の推移プロットになります。

f:id:appare99:20200524001518p:plainf:id:appare99:20200524001556p:plain

...もう次に行きますか。

改良策②:モデルの層を増やす

気を取り直して、次はモデルの層を増やします。
畳み込み層を深くすればするほど学習精度が上昇するので(と以下のページに書いてあったので)、
Deep Learning
畳み込み層を増やします。

前回までのモデル構成は以下のようになってました。(少し見にくいですがご容赦ください)


_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 50, 50, 32) 896
_________________________________________________________________
activation_1 (Activation) (None, 50, 50, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 48, 48, 32) 9248
_________________________________________________________________
activation_2 (Activation) (None, 48, 48, 32) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 24, 24, 32) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 24, 24, 32) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 24, 24, 64) 18496
_________________________________________________________________
activation_3 (Activation) (None, 24, 24, 64) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 22, 22, 64) 36928
_________________________________________________________________
activation_4 (Activation) (None, 22, 22, 64) 0
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 11, 11, 64) 0
_________________________________________________________________
dropout_2 (Dropout) (None, 11, 11, 64) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 7744) 0
_________________________________________________________________
dense_1 (Dense) (None, 512) 3965440
_________________________________________________________________
activation_5 (Activation) (None, 512) 0
_________________________________________________________________
dropout_3 (Dropout) (None, 512) 0
_________________________________________________________________
dense_2 (Dense) (None, 37) 18981
_________________________________________________________________
activation_6 (Activation) (None, 37) 0
=================================================================

今回は以下のサイトのモデルを使用してみることにします。
qiita.com
こちらのサイトではCIFAR-10のデータセットの検証制度を90%超えるようなモデルを作成していました。実際にこのモデルをThe Oxford-IIIT Pet Datasetに用いた際にどれだけの精度になるかが気になったということもあります。

モデルのコードは以下のようになりました。

model = Sequential()
model.add(Conv2D(64, (1, 1), input_shape=X_train.shape[1:]))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(64, (5, 5)))
model.add(BatchNormalization())
model.add(Activation('relu'))          
model.add(Dropout(0.25))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(128, (1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(128, (3, 3)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(128, (5, 5)))
model.add(BatchNormalization())
model.add(Activation('relu'))          
model.add(Dropout(0.25))
          
model.add(Conv2D(256, (1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(256, (3, 3)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(256, (5, 5)))
model.add(BatchNormalization())
model.add(Activation('relu'))          
model.add(Dropout(0.25))
                    
model.add(Flatten())
model.add(Dense(dense_size)) # 37種類分のカテゴリへ出力
model.add(Activation('softmax'))

またモデル構成は以下のようになります。見ての通り層を増やしました。しかもはCIFAR-10のデータセットの検証制度を90%超えるようなモデルということで期待が高まります!


_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_2 (Conv2D) (None, 64, 64, 64) 256
_________________________________________________________________
batch_normalization_1 (Batch (None, 64, 64, 64) 256
_________________________________________________________________
activation_1 (Activation) (None, 64, 64, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 62, 62, 64) 36928
_________________________________________________________________
batch_normalization_2 (Batch (None, 62, 62, 64) 256
_________________________________________________________________
activation_2 (Activation) (None, 62, 62, 64) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 58, 58, 64) 102464
_________________________________________________________________
batch_normalization_3 (Batch (None, 58, 58, 64) 256
_________________________________________________________________
activation_3 (Activation) (None, 58, 58, 64) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 58, 58, 64) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 29, 29, 64) 0
_________________________________________________________________
conv2d_5 (Conv2D) (None, 29, 29, 128) 8320
_________________________________________________________________
batch_normalization_4 (Batch (None, 29, 29, 128) 512
_________________________________________________________________
activation_4 (Activation) (None, 29, 29, 128) 0
_________________________________________________________________
conv2d_6 (Conv2D) (None, 27, 27, 128) 147584
_________________________________________________________________
batch_normalization_5 (Batch (None, 27, 27, 128) 512
_________________________________________________________________
activation_5 (Activation) (None, 27, 27, 128) 0
_________________________________________________________________
conv2d_7 (Conv2D) (None, 23, 23, 128) 409728
_________________________________________________________________
batch_normalization_6 (Batch (None, 23, 23, 128) 512
_________________________________________________________________
activation_6 (Activation) (None, 23, 23, 128) 0
_________________________________________________________________
dropout_2 (Dropout) (None, 23, 23, 128) 0
_________________________________________________________________
conv2d_8 (Conv2D) (None, 23, 23, 256) 33024
_________________________________________________________________
batch_normalization_7 (Batch (None, 23, 23, 256) 1024
_________________________________________________________________
activation_7 (Activation) (None, 23, 23, 256) 0
_________________________________________________________________
conv2d_9 (Conv2D) (None, 21, 21, 256) 590080
_________________________________________________________________
batch_normalization_8 (Batch (None, 21, 21, 256) 1024
_________________________________________________________________
activation_8 (Activation) (None, 21, 21, 256) 0
_________________________________________________________________
conv2d_10 (Conv2D) (None, 17, 17, 256) 1638656
_________________________________________________________________
batch_normalization_9 (Batch (None, 17, 17, 256) 1024
_________________________________________________________________
activation_9 (Activation) (None, 17, 17, 256) 0
_________________________________________________________________
dropout_3 (Dropout) (None, 17, 17, 256) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 73984) 0
_________________________________________________________________
dense_1 (Dense) (None, 37) 2737445
_________________________________________________________________
activation_10 (Activation) (None, 37) 0
=================================================================

気になる結果

気になる結果は以下のようになりました。
検証損失: 8.299899394844479
検証精度: 0.14614343643188477

。。。はい、検証制度0.146ですね。。。低いです。
一応、精度と損失の推移プロットを以下に示します。(もう投げやり(2回目))
右が精度のプロット、左が損失のプロットです。

f:id:appare99:20200530103244p:plainf:id:appare99:20200530103318p:plain

CIFAR-10のデータセットの検証制度を90%超えるようなモデルを用いてもThe Oxford-IIIT Pet Datasetでは14.6%の精度にしかならないんですね。。。
というか、下がるとは思ってませんでした。少なくとも上がると思ってました。
これが、モデルの構築は職人技になってしまうと言われる要因なんでしょうかね。

余談(自分なりに作成したモデルについて)
上記のモデルは参考にしたサイトがありましたが、なにも参考にせず、自分なりに最初に作ったモデルのコードが以下となります。

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',input_shape=X_train.shape[1:]))
model.add(Activation('relu')) # 活性化関数ReLU

model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))

model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
          
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
          
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))

model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
          
model.add(Flatten())
model.add(Dense(512)) # 512は出力の次元数
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(dense_size)) # 37種類分のカテゴリへ出力
model.add(Activation('softmax'))

で結果は
検証損失: 3.6147831518691995
検証精度: 0.02435723878443241
2%ってほぼ当てずっぽうですよね。
一応推移も載せときます。
これも上記と同様に右が精度のプロット、左が損失のプロットです。

f:id:appare99:20200530192159p:plainf:id:appare99:20200530192221p:plain
いやぁ、ひどいっすね。。。
層を増やせばある程度精度は上がると思っていましたがそんなことはないのですね。。。
すいません、なぜここまで低い精度になったかはっきりといった原因がわからないのですが、この結果からまだモデルを自作するにはまだハードルが高そうです。。。

改良策③:学習データを増やす

一般的に訓練データ数は多ければ多いほど精度が上がると言われているのでデータを増やします。
正確にはデータを収集することで増やすのではなく、疑似的にデータを増やします。これはデータ拡張、または、Data Augmentationと呼ばれます。
具体例としては

  • 上下左右をずらす
  • 上下左右反転
  • 拡大縮小
  • 回転
  • 一部を切り取る
  • コントラストを変える

などがあります。
さて、前回の記事(上記「はじめに、」段落のリンク参照)でも記載しましたが、今回のData Augmentationは以下のサイトを参考にさせていただきます。
www.kumilog.net

今回は

  • 左右反転
  • 上下反転
  • Cutout という画像の一部マスクする手法を用いる

を用いることにいたします。
それでは一つ一つ見ていきましょう。

左右反転
左右反転は以下のようなプログラムになります。

DATAPATH = "./data/src_augmentation/" 
folder = os.listdir(DATAPATH) 

dense_size  = len(folder)

for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        image = Image.open(file)
        image = image.convert("RGB") 
        image = np.array(image, dtype=np.float32)
        image = image[:, ::-1, :] # ★画像を左右反転
        image = Image.fromarray(np.uint8(image))
        image_path_separate = file.split('_') # ./data/src_augmentation/abyssinian\Abyssinian_1.jpgの1.jpgを抽出
        image_path_separate2 = image_path_separate[-1].split('.') # 1.jpgの1を抽出
        image.save("./data/output/" + name +  "/" + name + "_" + image_path_separate2[0] + "_flip_horizontal.jpg")

実際の左右反転した画像は以下のようになりました。左が元画像で右が左右反転した画像になります。

f:id:appare99:20200530195048j:plainf:id:appare99:20200530195123j:plain

上下反転
上下反転は以下のようなプログラムになります。

DATAPATH = "./data/src_augmentation/" 
folder = os.listdir(DATAPATH) 

dense_size  = len(folder)

for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        image = Image.open(file)
        image = image.convert("RGB")
        image = np.array(image, dtype=np.float32)
        image = image[::-1, :, :] # ★画像上下反転
        image = Image.fromarray(np.uint8(image))
        image_path_separate = file.split('_') # ./data/src_augmentation/abyssinian\Abyssinian_1.jpgの1.jpgを抽出
        image_path_separate2 = image_path_separate[-1].split('.')
        image.save("./data/output/" + name + "/" + name + "_" + image_path_separate2[0] + "_flip_upside_down.jpg")

実際の上下反転した画像は以下のようになりました。左が元画像で右が上下反転した画像になります。

f:id:appare99:20200530195048j:plainf:id:appare99:20200530200022j:plain

Cutout
そして最後にCutout。Cutoutは対象画像の一部を単色で塗りつぶす方法です。今回は画像の縦と横の1/3を塗りつぶすようにしています。
コードは以下のようになります。
まずはCutoutの関数部分

def cutout(image_origin, mask_size_h, mask_size_w):
    image = np.copy(image_origin)
    mask_value = image.mean()

    h, w, _ = image.shape
    top = np.random.randint(0 - mask_size_h // 2, h - mask_size_h)
    left = np.random.randint(0 - mask_size_w // 2, w - mask_size_w)
    bottom = top + mask_size_h
    right = left + mask_size_w

    if top < 0:
        top = 0
    if left < 0:
        left = 0

    image[top:bottom, left:right, :].fill(mask_value)
    return image

そして、Cutoutの実装部分(といっても左右反転や上下反転のときと同様な感じで関数代入させただけですが。。。)

DATAPATH = "./data/src_augmentation/" 
folder = os.listdir(DATAPATH) 

dense_size  = len(folder)

for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        image = Image.open(file)
        image = image.convert("RGB")
        image = np.array(image, dtype=np.float32)
        h, w, _ = image.shape
        mask_size_h = h // 3
        mask_size_w = w // 3
        
        image = cutout(image, mask_size_h, mask_size_w)
        image = Image.fromarray(np.uint8(image))
        image_path_separate = file.split('_')
        image_path_separate2 = image_path_separate[-1].split('.')
        image.save("./data/output/" + name + "/" + name + "_" + image_path_separate2[0] + "_cutout.jpg")

実際のCutoutした画像は以下のようになりました。左が元画像で右がCutoutした画像になります。

f:id:appare99:20200530195048j:plainf:id:appare99:20200530200843j:plain

以上より各カテゴリー画像毎の枚数は約200枚から約800枚に増えました。

気になる結果
結果は以下のようになりました。
検証損失: 2.4042056906279794
検証精度: 0.5453622341156006
おっ!精度が増えている!!
そして推移プロットは次のようになります。
これも上記と同様に右が精度のプロット、左が損失のプロットです。
f:id:appare99:20200530202026p:plainf:id:appare99:20200530202034p:plain

まとめ

長くなりそうなので、今回はこの辺でやめます。
リベンジと言いながら、結局50%ほどの精度にしかなりませんでした。しかし、Data Augmentationの実装、そして、その有効性を肌で感じることができたことは大きな収穫と言えます。

そして、結局、目標としていた精度85%には到達できませんでしたが、希望として学習データを増やせばより精度が上がることが見えました。
今後はデータを増やしてみることで精度を上げてみようと思います。

また、モデルの構築も適切なものを用意すればさらに精度が向上すると思われます。
今後はCNNモデルについて考察及び実装し、精度が向上するかを試してみようと思います。(というかいろんなモデルを勉強できるいい機会になりそうです。)
モデルの自作ですが、なんかとてもハードルが高いような気がしました。モデル自体を自分で作るとなるとかなりの知識が必要になるかもしれません。。。
CNNのモデルは様々なモデルが考案されているのでそこら辺を調べてみようと思います。

機械学習初心者がDeep Learningを試してみる season CNN ~リベンジ編:第3話 モデルを改良する準備~

はじめに、

こんにちは。前回の記事(以下)ではズタボロの結果になりました。
apao-m-appare99.hatenablog.com
今回はリベンジに向けて改善策を練ります。
そして、今回は3つの改善策を試してみることにします。前半である本記事は改善策の考察がメインになります。
実際に実装して改善策がどれだけ有効かというリベンジは後半で行うことにします。
後半では改善策それぞれがどれだけ効果があるのかを見るとともに、3つすべてを組み合わせたらどれだけの精度が出るのかを見てみることにするつもりです。
そして、今回も執筆するのはあくまでも機械学習ど素人のエンジニアもどきである私です。温かく見守ってください。
また間違っていたらご指摘のほどよろしくお願いいたします。

【目次】

考察その① ~実際にテストに用いた画像を見てみる~

前回までに作成したモデルの認識精度は23%でした。37の猫と犬の品種をカテゴライズする認識器なのでランダムにしたら、2.7%ほどになるので識別しようとはしてるっぽい...。

ではまず、前回までに使った画像を見てみます。画像を見てみるとは前回まで学習する際の画像は50×50ピクセルにしてありました。
学習用のデータの1番目の画像を見てみることにします。
コードは以下のようにします。(コードは前回の記事(本記事の上部にリンクあり)で行ったコードの続きになります。このため以下コードのX_train[0]にはあらかじめデータを前処理した画像データが入っています。)

example = X_train[0]

plt.figure()
plt.imshow(example)
plt.grid(False)
plt.show()

このコードを実行すると以下のような画像が出力されます。
f:id:appare99:20200519232409p:plain

この画像が実際に学習で使われる50×50の画像になります。
見た感じ、人間には犬か猫かの判別はできる画像のようです。しかし品種となると難しいかもしれません。
例えば、以下の画像は今回のデータセットの中でbasset houndとカテゴライズされている画像になります。(basset hound(バセット・ハウンド)は16世紀に初めて発見されたフランス原産の中型犬です(ウィキペディアより))
f:id:appare99:20200519232712j:plain


一方で、以下はbeagleとカテゴライズされている画像になります。
f:id:appare99:20200519232931j:plain

どうでしょう。この2種類の品種似てますよね。ピクセル数が減る分、情報量が減るのでこのような品種の識別が難しくなるかもしれません。

もう一つ今回の認識器の構築に用いた画像を見てみましょう。そしてこの画像を識別できるか試してみましょう。
f:id:appare99:20200523115014p:plain
って、ちょっと待って。この画像、そもそも何なのかわからないですね。。。
品種がなんなのか以前に、いや、猫なのか犬なのか以前に、そもそも何なのかが人間の僕にすらわかりません。。。
さて、識別を試してみます。
この画像を用いた認識結果は30番と出ました。
30番はスコティッシュテリアですね。ちょっとスコティッシュテリアの画像セットをのぞいてみます。
f:id:appare99:20200523102851p:plain

お、当たってますね。認識は正解を出しました。(本当は不正解を出すと思ったのですが、それなりにできますね。。。)

お次はネットの画像を持ってきて識別させてみます。
以下のサイトから
25 Smallest Dogs In The World

以下の画像を使ってみます。
f:id:appare99:20200523103744p:plain

品種はヨークシャテリアです。ちなみに任意の画像を読み込ませて学習させるコードは以下のようになります。

import tensorflow as tf

model_00 = tf.keras.models.load_model('./models/cnn_leaning_ver01.model')


image = Image.open("./data/test/yorkshire_terrier.png")
image = image.convert("RGB")
image = image.resize((image_size, image_size))
data = np.asarray(image)

data = np.array(data)
data = data.astype('float32') # 学習用のモデルに渡すためにfloat32に変換する必要がある
data = data / 255.0 # 正規化処理:値を0~1の間に分布するようにさせる

prediction = model_00.predict(data[np.newaxis, :, :, :])
print(np.argmax(prediction[0]))

この予測結果は21番になりました。21番はミニチュアピンシャーですね。外れです。ミニチュアピンシャーは以下のような品種になります。

f:id:appare99:20200523104333p:plain

全然似てない。。。草むらにいる画像が多めだったからこの認識になったのか。。。

以上を踏まえて、改善策その①

以上を踏まえて、学習に使う画像のピクセル数を落としてしまうと画像自体の情報量が減ってしまうので、
ピクセル数を増やしてみてみます。
(今回は50×50ピクセルから100×100ピクセルに増やすことにする)

考察その② ~モデルについて考える~

今回使用したモデルは

input -> Conv + ReLU -> Conv + ReLU + MP -> Conv + ReLU -> Conv + ReLU +MP -> ReLU + softmax -> output
※ConvはConvolution layer(畳み込み層)、ReLUはReLU関数(活性化関数)、MPはMax Pooling(プーリング層)を表している

のようなモデルになります。

そもそもDeep Learningは層の多さが特徴の一つとなります。
層が多ければ多いほど関数が組み合わさり、より複雑な関数が表現できるのでこの層を増やす必要があります。

以上を踏まえて、改善策その②

以上を踏まえて、学習モデルの層を増やしてみる。
(今回は層を2倍用意してみる)

考察その③ ~学習量について考える~

今回使用したデータセットは1カテゴリあたり200枚の画像があります。
そして一般的に訓練データ数は多ければ多いほど精度が上がると言われているので、今回用意したデータを増やすことで精度向上を試みることにします。
具体的にはData Augmentationの手法を行います。Data Augmentationとまた長い横文字が出てきましたが、大したことは行いません。データを増やすため、画像を左右反転させたり、上下反転させたりして訓練データをかさましします。
以下のサイトを参考に参考にいたします。
www.kumilog.net

以上を踏まえて、改善策その③

以上を踏まえて、訓練用データ画像を増やそうと思います。
具体的には以下を行い画像を増やします。

  • 左右反転
  • 上下反転
  • Cutout という画像の一部マスクする手法を用いる

これにより訓練画像データは4倍に増えます。

まとめ、というより感想

はい、ということで今回は学習精度の向上に関する考察をつらつら書いてみました。
一応、普段機械学習に触れている方からすると内容がほぼないと思いますが、あくまで機械学習初心者の備忘録ですので、大目に見てください。(しかも、ほぼ画像データの話でしたね。。。)

認識率の目標は85%以上にしていますが、現在23%。。。果たして達成できるかしら。。。

機械学習初心者がDeep Learningを試してみる ~CNN(Convolutional Neural Network, またの名を畳み込みニューラルネットワーク) 後編、Let`s学習~

はじめに、

こんにちは!
今回はCNNをKerasを用いて実装してみます。また、本記事は後編となります。画像の取得、画像の前加工、またCNNとは何ぞやなどは前編に記載してあります。前編は以下のリンクから行けます。興味のある方は読んでみてください
apao-m-appare99.hatenablog.com

結論から言うと全然いい認識結果は得られませんでした。。。

本記事はあくまでも機械学習ど素人のエンジニアもどきである私の記録になります。

【目次】

実装環境

今回実装する環境に関してですが、以下のようになります。

モデルを作成する

さて、それでは早速モデルを作成していきましょう。
今回参考にさせていただいた記事は以下の記事になります。(というかほぼコピペです。。。)
qiita.com

とりあえずこのモデルでどこまでの学習ができるかを試したいと思います。
モデルの作成のコードは以下のようになります。コードについてはコメントアウトの部分も参考にしてみてください。

from keras.models import Sequential
from keras.layers.convolutional import MaxPooling2D
from keras.layers import Activation, Conv2D, Flatten, Dense,Dropout
from keras.optimizers import SGD, Adadelta, Adagrad, Adam, Adamax, RMSprop, Nadam
import time

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',input_shape=X_train.shape[1:]))
'''
Conv2D: 2次元畳み込み層
Conv2D(32, (3, 3)で3×3の32種類のフィルタを各ピクセルにかける

padding='same' : 出力画像のサイズが変わらないようにパディングを実施する(ゼロパディング)

'''

model.add(Activation('relu')) # 活性化関数ReLU
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(dense_size))
model.add(Activation('softmax'))

model.summary()

次にmodelのコンパイルの部分ですね。コードは以下のようになります。ここの部分で処理が終わるまで20分ほどかかりました。

optimizers ="Adadelta"
results = {}
epochs = 50
model.compile(loss='categorical_crossentropy', optimizer=optimizers, metrics=['accuracy'])
results= model.fit(X_train, y_train, 
                   validation_split=0.2,
                   epochs=epochs)

●補足:batch sizeの指定がしていませんが、していない場合デフォルトでbatch sizeは32になります。

TestとValidationの学習における推移を見てみる

コードは以下のようにして推移のプロットを見てみる

import matplotlib.pyplot as plt
%matplotlib inline

acc = results.history['accuracy']
val_acc = results.history['val_accuracy']
loss = results.history['loss']
val_loss = results.history['val_loss']

epochs = range(len(acc))

# 1) Accracy Plt
plt.plot(epochs, acc,label = 'training acc')
plt.plot(epochs, val_acc, label= 'validation acc')
plt.title('Training and Validation acc')
plt.legend()

plt.figure()

# 2) Loss Plt
plt.plot(epochs, loss,label = 'training loss')
plt.plot(epochs, val_loss, label= 'validation loss')
plt.title('Training and Validation loss')
plt.legend()

plt.show()

結果は以下のようになりました。
まずは精度
f:id:appare99:20200517131213p:plain
学習(青の曲線)はうまくできているっぽいですが、検証のグラフ(オレンジの曲線)は20%あたりで止まってしまっています。。。
そして損失も見てみましょう。
f:id:appare99:20200517131439p:plain
こちらも学習の方(青の曲線)はうまく損失が減っていますが、検証のグラフ(オレンジの曲線)は増加していっています。

評価

ボロボロの結果なので期待なんて皆無ですが、一応評価を行います。

score = model.evaluate(X_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

'''
■以下の結果が出力されます。
Test loss: 7.079138089258713
Test accuracy: 0.23274695873260498
'''

...。
......。
.........。
...はい、精度は23%ですね。37のカテゴリに分ける問題と考えるとランダムよりはだいぶましな結果といえますが、これは認識器としては使い物にはならないですね。。。

まとめ

はい、ということで、ほぼコピペのモデルでCNNを実装してみましたが、結果は惨敗となりました。
まあ、はじめからうまくいくなんて思っていません。
改善を行っていくつもりです。85%は超えたいなーー。すいません、今日は惨敗でしたが、次回はリベンジということでやっていきます。

機械学習初心者がDeep Learningを試してみる ~CNN(Convolutional Neural Network, またの名を畳み込みニューラルネットワーク) 前編、学習させる前に下準備~

はじめに、

こんにちは。今回はDeep Leaningの中でもとても有名なCNN(Convolutional Neural Network)を実装しながらどんなものかを見てみようと思います。

そして、今回も機械学習初心者の人間がDeep Learningの実装にチャレンジする記録を残していこうと思いのもと書いている記事になります。間違えやミスがあればご指摘いただければ幸いです。


【目次】

前回の記事ではmnistを用いて画像認識を行いました。(前回の記事は以下になります。)
apao-m-appare99.hatenablog.com

これはDeep Neural Networkというものです。
モデルとしては以下のように
f:id:appare99:20200516160153p:plain

Input -> Affine+ReLU -> Affine+ReLU -> ・・・ -> Affine+ReLU -> Affine+softmax -> Output
のようなモデル構成となります。

さて、それではこの学習方法と比較してCNNについてみていきましょう。

CNNとは

CNNはニューラルネットワークに「畳み込み」という処理を取り入れたものであり、画像をそのまま2次元で入力として用いることができることが特徴です。

「畳み込み」の処理については以下の記事がとても分かりやすく説明しています。(というよりCNN自体についてもわかりやすく説明しています。)
www.atmarkit.co.jp
一応、畳み込みについて簡単に説明すると、カーネルというフィルタを用いることで画像から特徴を抽出する処理の事になります。つまり、画像の特徴を抽出し、ニューラルネットワークで重みを学習していったように、この特徴の値を学習していくことになります。

CNNは様々なモデルが考案されています。
そして、今回参考にするのが、1998年にヤン・ルカン氏によって考案されたLeNetと呼ばれる有名なCNNモデルになります。
以下がその概要図です。(https://nocotan.github.io/chainer/2017/08/04/chainercnn-copy.html より引用)
f:id:appare99:20200516164405p:plain


このモデルでは畳み込み層とプーリング層の2種類を交互に複数組み合わせるようなモデル構成となっています。

畳み込み層では上記で説明した畳み込み処理を行いその結果を出力します。
次のプーリング層ですが、
プーリングはダウンサンプリングが行われています。イメージとしてはピクセルの画像を減らして小さい画像を生成することです。そしてこの画像を生成する際に画像のピクセルの値をどのようにするかを決めていきます。決める方法として例えばmaxプーリングやavgプーリングがあります。

プーリングについては以下のサイトが分かりやすく説明しています。
products.sint.co.jp

以上からmnistを使用したときのDNNは
Input -> Affine+ReLU -> Affine+ReLU -> ・・・ -> Affine+ReLU -> Affine+softmax -> Output
と上記で記載しましたが、
CNNは次のようなモデル構成となります。ここでConvは畳み込み層(Convolution)、Poolはプーリング層(Pooling)を表します。
Input -> Conv+ReLU+Pool -> Conv+ReLU+Pool -> ・・・ -> Conv+ReLU+Pool -> Affine+softmax -> Outpu

今回はこのようなモデル構成のCNNでモデルを作成することになります。
●補足:以下の記事もCNNについてとても分かりやすく書かれています。
qiita.com

今回使用するデータ

今回使用する画像データはThe Oxford-IIIT Pet Datasetになります。以下のリンクから画像データは入手できます。
www.robots.ox.ac.uk


今回この画像データを使用しようと思った理由は、前回と同じmnistを用いて学習させてもよかったのですが、mnistは割かし整えられている画像ではあるので、今回は少し複雑な画像を使用してみたいと思ったからです。
また有名な画像データとしてCIFAR-10でもよかったのですが、CNNのサンプルコードが結構ネット上にあるので敢えてネット上にあまりない、The Oxford-IIIT Pet Datasetを使用することで勉強になるかと思ったからでもあります。

こちらのデータセットは37種の品種で分けられたカテゴリーと各カテゴリーに対して約200枚の画像で構成されています。
以下The Oxford-IIIT Pet Datasetのページに載っているデータの詳細になります。(https://www.robots.ox.ac.uk/~vgg/data/pets/ より参照)
f:id:appare99:20200516190415p:plain

データの前処理

お次は学習させるための画像の前処理を行っていきます。
まずはThe Oxford-IIIT Pet Datasetでとってきた画像を見ていきましょう。ダウンロードしてきたgz.tarを解凍してみましょう。
画像たちを見てみましょう。かわいい猫ちゃんやワンちゃんの画像が入っています。これが今回使う画像です。
あ、あれ?なんか枚数足りなくない?No.38, 39が無かったり、No41, No42が無かったり。。。
ま、まあ画像自体は悪くないし一つの品種に対し200枚ほどあるので学習させればうまくいくと願ってます。
f:id:appare99:20200516185739p:plain

さて、これらの画像をそれぞれのファイルに振り分けていきましょう。ラベル付けではなくフォルダに分けて区別していきます。(ザ・ごり押し(笑))
以下のような感じになりました。
f:id:appare99:20200516190552p:plain

学習前の前処理

さてそれでは早速画像を読み込んで本記事の本題でもある学習前の処理を実装しましょう。
参考にしたサイトは以下の2つになります。
qiita.com
newtechnologylifestyle.net

で、データの前処理までの全体像のコードが以下のようになりました。コメントアウトで各処理に関して書いてあるので参考にしてみてください。

from keras.utils import np_utils
from sklearn.model_selection import train_test_split
from PIL import Image
import numpy as np
import glob
import os

DATAPATH = "./data/src/" 

folder = os.listdir(DATAPATH) # ファイル、ディレクトリの一覧を取得

image_size = 50
dense_size  = len(folder)

X = []
Y = []

'''
Xには50×50のサイズでRBG形式のため[Red,Green,Blue]で格納されてます。
Yにはフォルダのナンバリング、
jpg形式のファイルを取り出し、RGB形式、50×50のサイズに変更し、
そのデータをX、Yの配列に格納しています。
'''

for index, name in enumerate(folder):    
    files = glob.glob(DATAPATH + name + "/*.jpg") #ディレクトリごとに画像をまとめて格納
    for i, file in enumerate(files): # 1つのディレクトリの画像に対し、一つ一つ画像を見ていく
        image = Image.open(file)
        image = image.convert("RGB")
        image = image.resize((image_size, image_size))
        data = np.asarray(image)
        X.append(data)
        Y.append(index)

X = np.array(X)
Y = np.array(Y)

X = X.astype('float32') # 学習用のモデルに渡すためにfloat32に変換する必要がある
X = X / 255.0 # 正規化処理:値を0~1の間に分布するようにさせる

Y = np_utils.to_categorical(Y, dense_size) # np_utils.to_categoricalを使用する事により、ラベルをベクトルに変換出来る
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.10)
# 訓練用の画像とラベル、テスト用の画像とラベルに分ける。今回の場合は画像データの10%をテスト用に使う

まとめ

はい、ということで今日はここまでにします。ざっくりとCNNを説明して学習用データの前処理まで終えることができました。
次回は実際に学習をさせてみて評価まで行いたいと思います。