Kerasで大規模な画像分類 - vgg16 転移学習 -

⏱️約3分
シェア:

1 イントロ

最近機械学習の勉強を始め、Kaggleにも参加するようになったので、自身の備忘録を兼ねて、ブログに記載する。

今回は画像分類のコンペに参加したので、その時にvgg16の転移学習について調べた内容をまとめる。

2 データについて

データセットは下記のとおり。 今までMNISTとかしかやったことなかったので、非常に大きなデータセットに感じたが、 現実にはそうでもないんでしょうね。

  • カテゴリ:約15000
  • 学習データの規模:数は約120万。300GB強
  • 学習データのサイズ:ばらばら。1600*1200とか。
  • テストデータ:約12万

3 実装

3.1 画像データの学習

まず、コード全文。

python
1from keras.preprocessing.image import ImageDataGenerator
2from keras import optimizers
3from keras.applications.vgg16 import VGG16
4from keras.layers import Dense, Dropout, Flatten, Input, BatchNormalization
5from keras.models import Model, Sequential
6from keras.callbacks import ModelCheckpoint
7import numpy as np
8
9train_data_dir = "/train/"
10validation_data_dir = "/validation/"
11
12train_datagen = ImageDataGenerator(rescale=1. / 255)
13validation_datagen = ImageDataGenerator(rescale=1. / 255)
14
15img_width, img_height = 200, 150
16nb_train_samples = 915649
17nb_validation_samples = 302091
18epochs = 50
19batch_size = 64
20nb_category = 14951
21
22train_generator = train_datagen.flow_from_directory(
23 train_data_dir,
24 target_size=(img_width, img_height),
25 batch_size=batch_size,
26 class_mode="categorical")
27
28validation_generator = validation_datagen.flow_from_directory(
29 validation_data_dir,
30 target_size=(img_width, img_height),
31 batch_size=batch_size,
32 class_mode="categorical")
33
34#input_tensorの定義
35input_tensor = Input(shape=(img_width, img_height, 3))
36
37vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
38
39top_model = Sequential()
40top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
41top_model.add(Dense(256, activation='relu', kernel_initializer='he_normal'))
42top_model.add(BatchNormalization())
43top_model.add(Dropout(0.5))
44top_model.add(Dense(nb_category, activation='softmax'))
45
46# vgg16とtop_modelを連結
47model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))
48
49# 15層目までの重みを固定
50for layer in model.layers[:15]:
51 layer.trainable = False
52
53optimizer = optimizers.rmsprop(lr=5e-7, decay=5e-5)
54model.compile(loss='categorical_crossentropy',
55 optimizer=optimizer,
56 metrics=['accuracy'])
57
58checkpoint_cb = ModelCheckpoint("snapshot/{epoch:03d}-{val_acc:.5f}.hdf5", save_best_only=True)
59
60model.fit_generator(
61 train_generator,
62 steps_per_epoch=nb_train_samples // batch_size,
63 epochs=epochs,
64 validation_data=validation_generator,
65 validation_steps=nb_validation_samples // batch_size,
66 callbacks=[checkpoint_cb])
67
68# モデルを保存
69model.save("model.h5")
70
71model.summary()

3.1.1 大規模データでの学習

よくチュートリアル等で見かけるのが、下記のように、学習データをnumpy.arrayとしてロードするパターン。

python
1(X_train, y_train), (X_test, y_test) = mnist.load_data()

しかしながら、今回のように大規模なデータの場合、全データをメモリ上に展開するのは不可能なので、flow_from_directory関数を使うのがよさそうである。この関数はリアルタイムにデータを拡張しながら、画像データを処理してくれる。

使い方として、まず、ImageDataGeneratorを作成する。

python
1train_datagen = ImageDataGenerator(rescale=1. / 255)
2validation_datagen = ImageDataGenerator(rescale=1. / 255)

このクラスは、前処理を行いつつ、画像データのバッチ生成を行ってくれる。今回はシンプルにrescaleだけを行う。1/255としているのは、画像のRGBの値域が0-255であるのを0-1に正規化するためである。

次に、データを読み込む。

python
1train_generator = train_datagen.flow_from_directory(
2 train_data_dir,
3 target_size=(img_width, img_height),
4 batch_size=batch_size,
5 class_mode="categorical")
6
7validation_generator = validation_datagen.flow_from_directory(
8 validation_data_dir,
9 target_size=(img_width, img_height),
10 batch_size=batch_size,
11 class_mode="categorical")

今回はカテゴリーの分類なので、class_modecategoricalを指定する。 この時に気を付けないといけないのが、フォルダの構成。下記のように、分類するクラスごとにサブフォルダを作成しておかないといけない。

javascript
1data/
2 train/
3 classA/
4 aaa.jpg
5 bbb.jpg
6 ...
7 classB/
8 ccc.jpg
9 ddd.jpg
10 ...
11
12 validation/
13 classA/
14 eee.jpg
15 fff.jpg
16 ...
17 classB/
18 ggg.jpg
19 hhh.jpg
20 ...

3.1.2 vgg16モデルの利用

Kerasで使えるVGG16モデルを使って学習を行う。

python
1#input_tensorの定義
2input_tensor = Input(shape=(img_width, img_height, 3))
3
4vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
5
6top_model = Sequential()
7top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
8top_model.add(Dense(256, activation='relu', kernel_initializer='he_normal'))
9top_model.add(BatchNormalization())
10top_model.add(Dropout(0.5))
11top_model.add(Dense(nb_category, activation='softmax'))
12
13# vgg16とtop_modelを連結
14model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))
15
16# 15層目までの重みを固定
17for layer in model.layers[:15]:
18 layer.trainable = False
19
20optimizer = optimizers.rmsprop(lr=5e-7, decay=5e-5)
21model.compile(loss='categorical_crossentropy',
22 optimizer=optimizer,
23 metrics=['accuracy'])
24
25checkpoint_cb = ModelCheckpoint("snapshot/{epoch:03d}-{val_acc:.5f}.hdf5", save_best_only=True)
26
27model.fit_generator(
28 train_generator,
29 steps_per_epoch=nb_train_samples // batch_size,
30 epochs=epochs,
31 validation_data=validation_generator,
32 validation_steps=nb_validation_samples // batch_size,
33 callbacks=[checkpoint_cb])

まず、デフォルトで用意されているvgg16モデルを使う。 この時に、入力のデータセットのサイズを指定する。

python
1input_tensor = Input(shape=(img_width, img_height, 3))
2
3vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

次に、自分で作ったモデルをくっつける。 その前提で、上記の引数のinclude_topはFalseにしている。

python
1top_model = Sequential()
2top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
3top_model.add(Dense(256, activation='relu', kernel_initializer='he_normal'))
4top_model.add(BatchNormalization())
5top_model.add(Dropout(0.5))
6top_model.add(Dense(nb_category, activation='softmax'))
7
8# vgg16とtop_modelを連結
9model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

正直に言って、ここで指定しているrelu関数とかは、ちゃんと理解して使っているわけではない。 なので、ここのモデルの中身はあまり参考にならないと思う。

次に重みを固定する。これを指定しない場合は、一から重みを学習しなおすのだろうけど、それだと(今回の場合は)意味ないので、今回は15層まで固定する。

python
1for layer in model.layers[:15]:
2 layer.trainable = False

モデルのコンパイル。この時に、optimizerを指定する。 これはパラメータを更新するときに、どういうアルゴリズムを使うかという指定。

python
1optimizer = optimizers.rmsprop(lr=5e-7, decay=5e-5)
2model.compile(loss='categorical_crossentropy',
3 optimizer=optimizer,
4 metrics=['accuracy'])

最後に学習。callbacksにmodelcheckpointを設定しておくと、途中の結果を保存できる。なくてもよい。

python
1checkpoint_cb = ModelCheckpoint("snapshot/{epoch:03d}-{val_acc:.5f}.hdf5", save_best_only=True)
2
3model.fit_generator(
4 train_generator,
5 steps_per_epoch=nb_train_samples // batch_size,
6 epochs=epochs,
7 validation_data=validation_generator,
8 validation_steps=nb_validation_samples // batch_size,
9 callbacks=[checkpoint_cb])

3.2 予測

python
1from keras.preprocessing.image import ImageDataGenerator
2from keras import optimizers
3from keras.applications.vgg16 import VGG16
4from keras.layers import Dense, Dropout, Flatten, Input, BatchNormalization
5from keras.models import Model, Sequential, load_model
6import pandas as pd
7import numpy as np
8import os
9from pandas import DataFrame
10
11test_data_dir = "/test/"
12
13test_datagen = ImageDataGenerator(rescale=1. / 255)
14
15img_width, img_height = 200, 150
16nb_test_samples = 115474
17batch_size = 1
18nb_category = 14951
19
20test_generator = test_datagen.flow_from_directory(
21 test_data_dir,
22 target_size=(img_width, img_height),
23 batch_size=batch_size,
24 class_mode=None,
25 shuffle=False)
26
27model = load_model("model.h5")
28
29pred = model.predict_generator(
30 test_generator,
31 steps=nb_test_samples,
32 verbose=1)

さっきとそんなに変わりません。同じようにflow_from_directoryでデータを読み込むが、注意点はテストデータ(=正解クラス不明)の場合でもサブフォルダが必要だということ。例えば下記のような構成で、データをサブフォルダにまとめておかないと、うまく動きません。地味にはまった。。

javascript
1data/
2 test/
3 sub/
4 aaa.jpg
5 bbb.jpg
6 ...

ただ、クラスは不明なので、class_modeNoneにしておくこと。

4 まとめ

  • vgg16使う場合の一通りの流れを理解。
  • 巨大なデータはflow_from_directoryで読み込む。
  • その場合はフォルダ構成に注意。

5 参考

シェア:

関連記事

FlutterのCI/CD - Android編
Guides

FlutterのCI/CD - Android編

FlutterによるAndroidアプリのCI/CD pipeline構築方法を説明します。GitHub Actionsを使用し、fastlaneは使いません。

mark241
FlutterのLocalization
Guides

FlutterのLocalization

FlutterでARBファイルを使ったLocalization対応方法を解説します。Flutter 2.0.1で動作確認しています。

mark241