あきらぼ

テック系ブログ

Tensorflowの画像分類器の学習設定で損失が減らずハマった話

こんにちは。

現在、VTuberアイドルグループであるHololiveの画像分類器を作成しているのですが、CNNのモデル設定でハマったのでそのことを書こうと思います。

ちなみにHololiveはこんなグループです。
所属タレント一覧 | hololive(ホロライブ)公式サイト




前提条件

まず、前提条件としては学習データとしてはホロライブメンバーのうち9人分の画像データをGoogle画像検索で集めました。
以下の方法です。
aki-lab.hatenadiary.com

そこで一人当たり400-500枚、計4175枚の画像になります。
これらのデータの内8割を学習データ、2割を検証データとします。

分類モデルとしては有名なvgg16を転移学習させる形でモデルを組みました。
最後に全結合層で512個のニューロンを追加しています。

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 rescaling (Rescaling)       (None, 256, 256, 3)       0         
                                                                 
 random_rotation (RandomRota  (None, 256, 256, 3)      0         
 tion)                                                           
                                                                 
 random_zoom (RandomZoom)    (None, 256, 256, 3)       0         
                                                                 
 random_flip (RandomFlip)    (None, 256, 256, 3)       0         
                                                                 
 random_translation (RandomT  (None, 256, 256, 3)      0         
 ranslation)                                                     
                                                                 
 vgg16 (Functional)          (None, None, None, 512)   14714688  
                                                                 
 flatten (Flatten)           (None, 32768)             0         
                                                                 
 dense (Dense)               (None, 512)               16777728  
                                                                 
 dropout (Dropout)           (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 9)                 4617      
                                                                 
=================================================================
Total params: 31,497,033
Trainable params: 23,861,769
Non-trainable params: 7,635,264
_________________________________________________________________

VGG16のパラメータの内、下流4層だけ学習可能にしています。
このうちrandom_rotation、random_zoom、random_flip、 random_translationはデータ強化層
dropoutは過学習対策の層となります。

今回は学習データが少ないので、データ強化層を4層入れています。
データ強化方法は、データ強化層を追加する方法以外に、そもそも元の学習データを事前に水増しする方法があります。(openCVやPillowを使って)

学習

ここから実際に学習をさせていきます。
パラメータ最適化方法としてはAdamを使用しました。

そして、バッチサイズや学習率を変化させて学習するようにします。
実際には全結合層の数や最適化手法も変更して色々試したのですが、なかなか教師データに対する損失(Loss)が減らずに悩んでいました。
結論から言うと、過学習対策を入れたままモデル検証していたからでした。

一例として過学習対策有りと無しで学習率の変化の影響がどう出るのか例を載せます。

過学習対策あり

以下は過学習対策ありのまま学習率を変化させた場合です。

確かに学習率を小さくして改善しているようですが、精度が低いままです。
これは過学習対策が教師データを各エピックで変更してしまっているために教師データに対しての精度も損失もあまり改善していないように見えてしまいます。

この状態だとモデルの表現力が分類に必要なだけあるかどうかも判断がつきづらいです。
エポック数を増やせば判断は付きますが、時間がかかってしまうのでモデル選定のトライ&エラーが大変になってしまします。

過学習対策なし

次に同じモデルで過学習対策を抜いた場合を見てましょう。

実際に過学習対策を抜いたモデルがこちら。

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 rescaling (Rescaling)       (None, 256, 256, 3)       0         
                                                                 
 vgg16 (Functional)          (None, None, None, 512)   14714688  
                                                                 
 flatten (Flatten)           (None, 32768)             0         
                                                                 
 dense (Dense)               (None, 512)               16777728  
                                                                 
 dense_1 (Dense)             (None, 9)                 4617      
                                                                 
=================================================================
Total params: 31,497,033
Trainable params: 23,861,769
Non-trainable params: 7,635,264
_________________________________________________________________

このモデルで同様に学習率違いを見てみましょう。

こちらの場合は学習率を小さくすることで学習が大きく改善され、損失が落ちて行っていることが一目で分かります。
もちろん、過学習対策を入れていないので、検証用データに対する損失と精度は悪いままですが、過学習対策を入れれば改善されます。

実際にこの学習率で過学習対策を入れてエポック数を増やせばかなり精度が上がります。

結論

今回の結果から過学習対策はモデル選定の段階では入れない方が良いということが分かりました。
過学習対策なしの状態で教師データに対して十分最適化され、精度が上がる状態で過学習対策を順次実装していく必要があります。

順番でいうとこんな感じ。

  1. 過学習対策なしでモデル選定・最適化パラメータ調整
  2. 教師データに十分フィッティングできる(低損失高精度)モデルであることを確認
  3. 過学習対策を追加

実際に過学習対策なしでフィッティングが進んだモデルでエポック数を増やしていくと以下のように精度が上がりました。

現状の分類器はこちら。

github.com

このような分類器作成の参考になる本がこちら。Keras、Tensorflowによるモデル作り方が分かりやすく解説されています。

引き続き分類器の作成を続けていこうと思います。

Python:grobでフォルダ内の大量の画像すべてを一括で読み込む

こんにちは。


今回は先日落としてきた大量の画像データの読み込みに関してメモがてら書こうと思います。


先日は画像分類器のために画像を大量にダウンロードしてきました。


aki-lab.hatenadiary.com


すると格納フォルダはこんなことになっています。



一応画像はナンバリングされているのですが、関係ない画像等を抜いたりもしているので、連番ではありません。
簡単に一括で処理できる方法がないかと調べてみたらgrabというパッケージがありました。


簡単な例を以下に書きます。



すると以下のような結果を返します。

./Train_Data/pekora\pekora002.jpg
./Train_Data/pekora\pekora003.jpg
./Train_Data/pekora\pekora004.jpg
(省略)
./Train_Data/pekora\pekora499.jpg
./Train_Data/pekora\pekora500.jpg
./Train_Data/pekora\pekora501.jpg


このように参照ディレクトリの内部ファイルのパスを全て返してくれます。





実際に画像分類器で使用したときは以下のようにして使用していました。



こうすることでImagesとnamesが教師データとして使用できます。


分類器全体はこちら
github.com


Image Downloaderを使用したGoogle画像検索サムネイル一括ダウンロード

こんにちは。


最近VTuberアイドルであるHololiveが身近で流行っているのですが、メンバーが多く覚えきれないので画像分類器を作成しようと考えています。


そこで、教師データとなる大量の画像データが必要になるので、GoogleChromeとそのプラグインであるImgaDownloaderを使用したGoogle画像検索のサムネイルを一括でダウンロードする方法を書こうと思います。


使用するのはこちらの拡張機能「ImageDownloader」です。


今回は宝鐘マリンの画像を集めようと思います。




まず、下準備としてGoogleChromeのダウンロード保存先を確認されないように設定変更します。
これを設定しておかないと数百回保存ボタンを押す羽目になります

次に画像検索で対象のページを開きます。

そしたら一度すべての検索結果を表示させるために一番下までスクロールしてすべての画像をブラウザに読み込ませます。
一番下まで行くと「未読はありません」との表示が出ます。

この状態で拡張機能であるImgaeDownloaderを起動します。
すると表示されているすべての画像がリストアップされています。今回は559件です。

しかし、この中には検索結果以外の画像も含まれています。
例えばGoogleのロゴだったり、自分のアイコンだったり。

そこで検索結果だけフィルタリングするために一番上のフィルターURLに以下のアドレスを入力します。

encrypted-tbn0.gstatic.com

すると検索結果の画像のサムネイルのみ表示されます。今回は515件ですね。

あとはSelectAllのチェックを入れて、ダウンロードフォルダ名と画像のファイル名を指定してあげます。

あとはダウンロードするだけです。

ブラウザで指定しているダウンロードフォルダに新しいフォルダと連番で名前付けされた画像ファイルが問題なく保存されています。

ちょっと調べた限りでは画像分類器には高解像度の画像をあまり使用していないことが多い様なので解像度としては十分かなと思います。
あとはデータ数が少ないので、どうデータ強化していくかがキーだとは思うのですが、ぼちぼちやっていこうと思います。

Python Tensorflow でcudart64_110.dll not found

PythonのTensorflowでプリグラムを実行しようとしたところ以下のようなエラーが出ました。

2022-04-24 20:52:29.885267: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found
2022-04-24 20:52:29.885774: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.

単純にCUDAとcuDNNがインストールされていないためのエラーになります。

まずCUDAを以下からインストールします。
developer.nvidia.com

次に、cuDNNを以下のサイトからダウンロードします。
developer.nvidia.com

インストールのやり方は、単純にCUDAのインストール先に解凍したcuDNNの中のbin, includeファイル等をすべてコピーするだけです。
私の場合は以下のパスです。
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6

最後にパス通して完了です。

するとエラーが以下のように無くなりました。

2022-04-24 22:22:43.985293: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2022-04-24 22:22:44.743542: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1525] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 9633 MB memory:  -> device: 0, name: NVIDIA GeForce GTX 1080 Ti, pci bus id: 0000:01:00.0, compute capability: 6.1

しかし、cuDNNとTensorflow, CUDA等には動かないバージョンの組み合わせがあるので気を付けてください。
私も他のところでバージョンによるエラーが生じました。

以下の記事参照。
aki-lab.hatenadiary.com

Tensorflowで「Loaded cuDNN version 8400」からのクラッシュ

Tensorflowでモデルを回そうとしたところEpoch一回目で、以下のメッセージがでて、その後クラッシュしてしまいました。

Epoch 1/10
2022-04-24 22:44:52.714618: I tensorflow/stream_executor/cuda/cuda_dnn.cc:368] Loaded cuDNN version 8400

cuDNNのバージョンミスマッチでしょうか。
cuDNNが v8.4
Tensorflow v2.8.0
Cudaのバージョンがv11.6になります。



Tensorflowのバージョンはpythonで以下のコードで確認することができます。

import tensorflow as tf
print(tf.__version__)

コマンドプロンプト上で以下のコマンドでCudaのバージョンを確認できます。

nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2022 NVIDIA Corporation
Built on Tue_Mar__8_18:36:24_Pacific_Standard_Time_2022
Cuda compilation tools, release 11.6, V11.6.124
Build cuda_11.6.r11.6/compiler.31057947_0

cuDNNのバージョンはインストールされているディレクトリの
include/cudnn_version.h
のファイルに記載されています。

/**
 * \file: The master cuDNN version file.
 */

#ifndef CUDNN_VERSION_H_
#define CUDNN_VERSION_H_

#define CUDNN_MAJOR 8
#define CUDNN_MINOR 4
#define CUDNN_PATCHLEVEL 0

#define CUDNN_VERSION (CUDNN_MAJOR * 1000 + CUDNN_MINOR * 100 + CUDNN_PATCHLEVEL)

#endif /* CUDNN_VERSION_H */

とりあえず急ぎではないので以下の方法でGPU無効化してTensorflow自体は動くことは確認済みです。

import os
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'

回答はありませんが、同様のエラーの人もいるようですね。
https://forums.developer.nvidia.com/t/use-gpu-for-tensorflow-crashes/208849

追記

やはりcuDNNのバージョンによるものでした。
私の上記環境であれば、cuDNNのv8.2.4 for Cuda 11.4でTensorflowをGPUで動かすことできました。

ちなみにTensorflowの検証済み環境を使用するのが一番無難だとは思います。
www.tensorflow.org

RaspberryPi3でI2Cセンサを接続する。

今回は先日購入したCO2センサであるCCS811と気圧センサであるBME280のセンサをI2CでRaspberyy Piと接続してみました。

センサーは先日STMのNucleoで動作確認したものです。

aki-lab.hatenadiary.com




センサ接続のための準備

まず、接続できるようにユニバーサル基盤にセンサを取り付ける下準備をします。

家に余っているピンソケットでかろうじてラズパイのピンをさせるようにします。

f:id:AKIRA_san:20220417141857j:plain

配線は結構汚いですがどうせ室内で固定だし「ヨシッ!」
f:id:AKIRA_san:20220417142012j:plain

余った基板部分はリューターで切ってみました。
結構硬いユニバーサル基盤ですが、意外と綺麗に切断できました。

f:id:AKIRA_san:20220417142159j:plainf:id:AKIRA_san:20220417142207j:plain

固定穴はドリルで拡張して開けました。

テキトーに足をつけて、完成!(と思ったらプルアップ抵抗つけ忘れてて後付け)

f:id:AKIRA_san:20220417142409j:plainf:id:AKIRA_san:20220417142417j:plain

これで下準備は完了。

接続確認

次に接続確認をしていきます。

まずRaspberyy PiでI2C通信を有効化します。

sudo raspi-config

メニューからI2Cを有効化します。

f:id:AKIRA_san:20220417143608p:plainf:id:AKIRA_san:20220417143633p:plain

そしたら以下のコマンドで同じI2Cライン上のデバイスを検出します。

i2cdetect -y 1

以下のように表示されます。

f:id:AKIRA_san:20220417143836p:plain

見方は列で下位4bitの値、行で上位3bitの値を示しています。
I2Cのアドレスは7bitなので行の数字が70までしかないということです。

今回はCCS811のアドレスが0x5b、BME280のアドレスが0x77なので正しく接続できていることが分かります。
恐らく、このコマンドで全アドレスにリクエストを送ってレスポンスあるアドレスが表示されているのですかね。(めんどくさいので詳しくは調べませんが(笑))

とりあえずI2Cで接続できたことを確認できたので今回はここまでです。

次回は実際のセンサ値を読み込んで色々したいと思います。

kswapd0のプロセスでサーバーが重くなった件

こんにちは。

先日ARKのサーバーとして使っているKagoyaVPSのUbuntuを再起動したところ、なにやらサーバーが重くなってしまいました。

サーバーのプロセス状態をTOPで確認してみます。

f:id:AKIRA_san:20220417091739p:plain

kswapd0というプロセスが、ほぼほぼCPUを食いつくしています。
よく見ると8GB確保しているSwap領域がなくなっています。

つまり、原因としてはSwap領域不足によるメモリ不足。
流れとしてはこんな感じです。
f:id:AKIRA_san:20220417092225p:plain

ここで気づいたのですが、以前ゲーム用に確保したメモリ容量ですが、再起動すると割り当て解放されてしまうようです。
起動時に仮想メモリマウントする設定をし忘れていました。(サーバーってあんま再起動しないから気づかないですね(笑))

起動時に仮想メモリをマウントするには以下のファイルを編集します。

/etc/fstab

私の追加のSwapファイルは/var/swpfileなので、このファイルに以下の文を追加します。

/var/swpfile swap swap default 0 0

これで再起動後にゲームサーバーが自動で起動されても問題ないと思います。