質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

Q&A

解決済

2回答

452閲覧

tensorFlowLiteに変換すると画像分類の精度が大幅に落ちる

masa-nakamura

総合スコア5

機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

0グッド

1クリップ

投稿2024/10/15 05:04

編集2024/10/15 07:06

実現したいこと

Colab上で精度よく(val_accuracyで0.9以上)分類されるのに、モデルをtensorFlowLiteに変換してスマホに組み込むと精度が大幅に低下した(ほとんど区別ができなくなった)。その理由や解決方法(あれば)を知りたい。また、もし簡単な解決方法がなければそのことを知りたい。

発生している問題・分からないこと

 Colab上で精度よく(val_accuracyで0.9以上)分類されるのに、モデルをtensorFlowLiteに変換してスマホに組み込むと精度が大幅に低下する(ほとんど区別ができなくなる)。その理由や解決方法を知りたい。なお、ImageDataGeneratorを用いていたときはそういうことはなかったが、非推奨になったためimage_dataset_from_directory を用いたところ、この問題が発生した。
「車のドアミラーが開いているか閉じているかを判定する」という画像分類において、Colab上では下記のように非常によく分類できるモデルを作ることができた。ところがスマホに組み込んでdrawableに置いたdata_dirの画像(つまり、モデル作成時に用いた画像)を分類させると正答率が25%(2択の問題なのに不正解のほうが多い)となった。調べると、ドアミラーが閉じている写真は全て「open」、開いている写真の半分は「open」残りの半分は「closed」に分類される状況であった。
イメージ説明

該当のソースコード

Python

1#モデル作成およびTFLiteモデルに変換するときのコード(Colab) 2import tensorflow as tf 3import keras 4from tensorflow.keras import layers 5from keras.models import Sequential 6from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D, BatchNormalization 7from keras.applications.vgg19 import VGG19 8from tensorflow.keras.preprocessing.image import ImageDataGenerator 9 10from google.colab import drive 11drive.mount('/content/drive') 12 13vgg19 = VGG19(include_top=False,weights='imagenet',input_shape=(224,224,3),pooling=None) 14for layer in vgg19.layers: 15 layer.trainable = False 16 17data_dir = '/content/drive/MyDrive/mirror1001/test' 18 19batch_size = 32 20img_height = 224 21img_width = 224 22 23train_ds = tf.keras.utils.image_dataset_from_directory( 24 data_dir, 25 validation_split=0.2, 26 subset="training", 27 seed=123, 28 image_size=(img_height, img_width), 29 batch_size=batch_size) 30 31val_ds = tf.keras.utils.image_dataset_from_directory( 32 data_dir, 33 validation_split=0.2, 34 subset="validation", 35 seed=123, 36 image_size=(img_height, img_width), 37 batch_size=batch_size) 38 39class_names = train_ds.class_names 40print(class_names) 41 42AUTOTUNE = tf.data.AUTOTUNE 43 44train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE) 45val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE) 46 47data_augmentation = keras.Sequential( 48 [ 49 layers.RandomRotation(0.02), 50 layers.RandomZoom(0.02), 51 ] 52) 53 54model = Sequential() 55model.add(data_augmentation) 56 57# Instantiate the sequential model and add the VGG19 model: 58model.add(vgg19) 59 60model.add(Dense(128, activation='relu',input_shape=(224,224))) 61 62# Add the custom layers atop the VGG19 model: 63model.add(Flatten(name='flattened')) 64model.add(Dropout(0.5, name='dropout')) 65model.add(Dense(2, activation='softmax', name='predictions')) 66 67 68model.compile( 69 optimizer='adam', 70 loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), 71 metrics=['accuracy']) 72 73model.fit( 74 train_ds, 75 validation_data=val_ds, 76 epochs=12 77) 78 79!mkdir -p /content/saved_model 80model.save('/content/saved_model/myModel.keras') 81 82 83converter = tf.lite.TFLiteConverter.from_keras_model(model) 84tflite_model = converter.convert() 85 86with open('Model1015b.tflite', 'wb') as f: 87 f.write(tflite_model) 88

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

ImageDataGeneratorを使わない場合に精度が低下することについての情報を見つけることができなかった。
データの入れ替え、データ拡張を追加するなど行ったが、精度が向上することはなかった。いくつか試した中では、2つのカテゴリーがほとんど区別できない場合や、上記で挙げたようにほとんど逆の判定をする場合などがあった。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ANK

2024/10/15 06:33

スクリプト全体(importの部分や、vgg19の部分など)を書いていただけないでしょうか。
masa-nakamura

2024/10/15 07:06

コメントありがとうございます。追記いたします。
bsdfan

2024/10/15 08:09

TensorFlow Lite に変換したものを、Colab上で、学習に使ったデータを使ってテストすると、どういう結果になるのでしょうか?
masa-nakamura

2024/10/15 09:45

初歩的なことですみません、TensorFlow LiteをColab上でテストする方法がわからず、まだやっていません。もし、解説したサイト等ありましたら、教えていただけると幸いです。
bsdfan

2024/10/15 10:25

https://www.tensorflow.org/lite/guide/inference?hl=ja#python_%E3%81%A7%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%93%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B とかでしょうか。 tf.lite.Interpreter で読み込んで実行するんだと思います。 こちらのnotebookでは、モデルを作成して、TF Lite に変換して、テストするとこまでやっています。 https://colab.research.google.com/github/frogermcs/TFLite-Tester/blob/master/notebooks/Testing_TFLite_model.ipynb
masa-nakamura

2024/10/15 10:49

ありがとうございます。教えていただいたサイト、勉強してやってみます。
masa-nakamura

2024/10/17 13:20

 遅くなりましたが、教えていただいたサイトを参考に、次のようなコード(つぎはぎですが、あっているでしょうか・・・)を付け加えたところ、TFLiteモデルのテストができました。 tflite_interpreter = tf.lite.Interpreter(model_path = "/content/Model.tflite") input_details = tflite_interpreter.get_input_details() output_details = tflite_interpreter.get_output_details() tflite_interpreter.resize_tensor_input(input_details[0]['index'], (32, 224, 224, 3)) tflite_interpreter.resize_tensor_input(output_details[0]['index'], (32, 5)) tflite_interpreter.allocate_tensors() input_details = tflite_interpreter.get_input_details() output_details = tflite_interpreter.get_output_details() datagen_kwargs = dict(rescale=1./255, validation_split=.20) valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator(**datagen_kwargs) train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(**datagen_kwargs) train_generator = train_datagen.flow_from_directory( "/content/drive/MyDrive/mirror1001/test", subset="training", shuffle=True, target_size= (224, 224)) valid_generator = valid_datagen.flow_from_directory( "/content/drive/MyDrive/mirror1001/test", subset="validation", shuffle=True, target_size= (224, 224) ) val_image_batch, val_label_batch = next(iter(valid_generator)) tflite_interpreter.set_tensor(input_details[0]['index'], val_image_batch) tflite_interpreter.invoke() tflite_model_predictions = tflite_interpreter.get_tensor(output_details[0]['index']) print("Prediction results shape:", tflite_model_predictions.shape) true_label_ids = np.argmax(val_label_batch, axis=-1) print(true_label_ids) dataset_labels = sorted(train_generator.class_indices.items(), key=lambda pair:pair[1]) dataset_labels = np.array([key.title() for key, value in dataset_labels]) print(dataset_labels) tflite_pred_dataframe = pd.DataFrame(tflite_model_predictions) tflite_pred_dataframe.columns = dataset_labels print("TFLite prediction results for the first elements") tflite_pred_dataframe.head(50)  ところが結果は、openとclosedを全く区別できていませんでした。開いている写真についてモデルが判定した「openの確率」は9-39%に散らばっており、閉じている写真については2-59%に散らばっているという状態でした。  ということは、スマホ用アプリに組み込む前、TFLiteに変換した時点ですでに問題があるということですね。
bsdfan

2024/10/18 00:06 編集

今回の質問者さんのプログラムで、おかしくなったときの変化点はいくつかあって、 1. TensorFlow / TensorFlow Lite 2. image_dataset_from_directory / ImageDataGenerator 3. Colab / スマホ 原因がどこかを調査する必要があるのですが、上のコードでは3は同じですが、1と2の両方が変わってるので原因の切り分けができません。 Colab上で、TF Liteのモデルで、image_dataset_from_directory を使って試すか、 もしくは、Colab上で、変換前のモデルとImageDataGenerator の組み合わせでテストしてみるのもいいと思います。 私の印象としては、2. の image_dataset_from_directory のとき 1/255 の rescale をできていないのが原因ではないかと思っています。(TF Liteは原因ではないんじゃないかと思っています)
masa-nakamura

2024/10/19 01:16

trLiteのモデルをColab上でテストしたときのコードの中で、subset="training" というのがありましたが、これをはずしてみたところ、なぜか、「開いた画像についてはopenと判定される確率が60%を上回る判定となることが多く、閉じた画像については下回ることが多い」となりました。すなわち、閾値を50%でなく60%に設定するとかなりよく判定できるということです。これは、最初にスマホで行ったときの結果とも一致します。ということは、テストの方法としてはsubsetをはずすのが正しかったのでしょうか。また、閾値がずれてしまった原因はわかりませんが、スマホ上で60%を閾値とするアプリを動かせば、一応問題への対応はできるように思われました。
masa-nakamura

2024/10/19 01:22

なお、rescaleについては、54行目の model = Sequential() の後に model.add(tf.keras.layers.Rescaling(1./255)) を加えるということをやってみましたが(回答へのコメントに書きましたが)、2種類の画像を、閾値等にかかわらず全く区別できなくなるという結果でした。
masa-nakamura

2024/10/19 14:05

 すみません、前の前のコメントの補足です。  スマホの判定では「不正解のほうが多い」となっていましたがその原因は次のように考えられます。すなわち、スマホの上では「open/closed」というラベルを用いており、ImageDataGeneratorでもその順序でラベルを指定していたのに、image_dataset_from_directoryではラベルを指定しておらず、読むこむデータのフォルダ名がラベルとなっておりその順序が「closed/open」となっていました(多分アルファベット順?)。ですから前者ではモデル作成時とスマホ上でラベルの順序が同じだったのに、後者では逆になっていたようです。  これを修正したうえで閾値を変えれば、正常に動くスマホのアプリを作れるのではと考えました。まずこれをやってみて、また報告させていただきます。
masa-nakamura

2024/11/20 04:55

先日はありがとうございました。その後、「閾値を変える」という方法で実用上は何とかなったので、なぜそうなるか等の問題は残りますが、自己解決として報告させていただきました。
guest

回答2

0

自己解決

疑問点は残るのですが実用上はとりあえずの解決したので、自己解決ということで投稿させていただきます。

質問した際にうまくいかなかったのには複数の問題がかかわっていたのですが、その中で最も重要だったのは、「モデルの各段階でprobablilityの値が変わってしまう」という点でした。すなわち、ColabでTFLiteモデルを作ってスマホ等で利用する場合には次のようなプロセスを経ると思います。
①model.fitでモデルをトレーニングする。
②Kerasのモデルができる。
③TFLiteのモデルに変換し、ダウンロードする。
④TFLiteモデルをメタデータが付いた形に変換する。
⑤モデルをアプリに組み込んでスマホで利用する。
このうち②③⑤の段階で、画像に対するprobabilityの値を計算することが可能です。そのうち②のKerasモデルによって計算したprobabilityと③のTFLiteモデルによって計算したprobabilityの値は一致します。しかしこれらと、⑤で計算したprobabilityの値は異なったものになってしまうということがわかりました。これをグラフで示すと下のようになります。

また、このモデルを作成した際に①の段階で求められたval_accuracyは0.9404とかなり高い値を示していました。一方、グラフからは②及び③の段階、⑤の段階のいずれも「probabilityが0.5未満は開いている、0.5以上は閉じている」と分類した場合には精度が非常に低いことがわかります。

この問題は、実用上は分類の閾値を変えることによって解決できます。例えば下のグラフのスマホによる推定においては、閾値を86.5とすれば分類の精度は約93%とかなり高くなりました。
この閾値の値はモデルを作成するたびに異なるので、モデル作成のたびごとに実際に多数のサンプルについてprobabilityを計算しなければならないようです。

また、質問に際して、ImageDataGeneratorからimage_dataset_from_directoryに変えた時点でこの問題が発生したと書きましたが、どちらを使うかは実際には関係なく、私がImageDataGeneratorを使っていた際にはたまたま閾値が50に近いところにあったので問題に気づかなかったためと思われます。

なお、なぜこうした問題が起きるのかはよくわかりません。この問題が起きた状況としては、今回の課題が「車のドアミラーが開いているか閉じているかの分類」というものであり、画像のごく一部の特徴から判定するような種類の問題の際に起こりやすいのではないかと思われますが、検証はできていません。

上記以外の問題として、次のものがありました。
1)ImageDataGeneratorでは分類ラベルの順序を、「①open、②closed」と指定していたが、image_dataset_from_directoryでは指定していなかったため、多分アルファベット順で「①closed、②open」となってしまっていたため、不正解のほうが多いという結果が出てしまっていた。
2)Vgg19を使っていたが、その出力の形がその次の層であるDenseの入力の形と合っていなかったので修正した。
3) model = Sequential() の後に model.add(tf.keras.layers.Rescaling(1./255)) を加えるということを試したが、すぺてのprobabilityの値が狭い範囲(今回の例では7.8~8.1%)に含まれてしまい、2種類の画像を区別することは全くできなかった。

イメージ説明

投稿2024/11/20 04:52

masa-nakamura

総合スコア5

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

vgg19においてpooling=Noneとしているので、その出力は3次元のテンソル(高さ×幅×チャネル)になると予想されます。なので、Denseの前にFlattenかGlobalAveragePooling2Dなどが必要になります。また、input_shapeの指定は必要ありません。
では、なぜ正常に学習が出来たのか、それが疑問だと思います。まず、このスクリプトではDenseにおいてinput_shape=(224,224)としていますが、実際にはそれは無視され、自動的に構築されています。また、フラット化が暗黙的に行われています。
恐らくこれが原因ではないでしょうか。
(申し訳無いのですが検証はしていません。まるで的を得ていなかったらごめんなさい。)

投稿2024/10/15 08:20

ANK

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ANK

2024/10/15 08:21

間違えて回答の方に書いてしまいました...
masa-nakamura

2024/10/15 09:43

ありがとうございます。見様見真似でやっていて初歩的なエラーがあるのだと思います。60行目のDenseの入力がVgg19の出力と合っていないとのご指摘と考え、その部分を削除して、63行目のFlattenにつながるようにしてみたのですが(それでご指摘の点は修正できたのでしょうか)、似たような結果となりました。
ANK

2024/10/15 10:44 編集

これは明示するようにしただけで関係なかったのでしょうかね。 下記のようにすると解決するかもしれません。 (最初から学習をやり直さなくても、保存したmyModel.kerasをロードして試して頂いても大丈夫です。一応。) converter.experimental_new_converter = True converter.experimental_new_quantizer = True また、スマホのメモリ不足によってデータの欠損が生じることが原因かもしれません。その場合は、見たところ量子化を行っていない様なので、それを実施するといいかもしれません。 下記の方法はサイズを大幅に削減しますが、精度が少し落ちます。 converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] # キャリブレーション用の関数を定義 def representative_dataset_generator(): for input in dataset: # datasetは推論時に使うデータのセット yield [input] converter.representative_dataset = representative_dataset_generator tflite_model = converter.convert() 手探り状態で申し訳ないです。
masa-nakamura

2024/10/15 10:53

ありがとうございます。やってみます。
masa-nakamura

2024/10/15 14:45

ひとつめのアドバイスについて、 converter.experimental_new_converter = True converter.experimental_new_quantizer = True  の追加をしてみましたが、似たような結果でした。 2つめのアドバイスについて、 for input in dataset: # datasetは推論時に使うデータのセット とありますが、このdatasetをどうやって作ればいいのでしょうか。(ほんとに初歩的ですみません) これに関連してですが、このような画像判定は、GoogleTeachableMachineを使うと、少ないtrain用画像でかなりの精度で判定できるのですが(おそらく、20枚ずつの画像で80%以上など)、これをTFLiteに変換すると、今回の質問の件と同様、全く判別できなくなりました。これは、浮動小数点でも量子化でも同じことでした。犬と猫を区別するなど画像の中の大きな形を判定するのはこの方法でできるのですが、今回の課題は「車を撮影してそのドアミラーを判別する」ということなので、画面の限られた一部の形状の違いからの判定になります。TFLiteではこうした判定は困難という可能性はありますでしょうか?(ただしImageDataGeneratorを使うとなぜかかなりの精度で判定できたのですが・・・)
ANK

2024/10/15 21:56

2つ目において、自分の説明が足りていませんでした。このようにするといいと思います。ただ返答によると量子化は関係が無いようなので意味無いかもしれません。 def representative_dataset_generator(): for inputs, _ in val_ds.take(100): yield [inputs[0].numpy()] 先程気がついたのですが、ImageDataLoaderを使っていた時は画像の正規化(0~1にする)をしているのでしょうか?差があるとすればそこかなと思いまして。 これ(IDL 略)を使っていた時は学習が上手くいっていたようなので、スマホの画像のサイズが違うということはないと思っています。
ANK

2024/10/16 05:06 編集

小さい可能性ですが、学習は正規化前(0~255)で行っていて、スマホでは正規化後(0~1)で判別しているのではないでしょうか。 もしそうだとすると、特徴が小さくなるので(?)小さな物体は見分けにくくなるのかも知れません。
masa-nakamura

2024/10/16 14:02

コメントありがとうございます。  量子化については、また日を改めてやってみます。  正規化の件ですが、ImageDataGeneratorを使用していたときは、train_datagen = ImageDataGenerator( rescale=1.0/255,云々と記述しておりまして、正規化しておりました。そこで、今回、54行目の model = Sequential() の後に model.add(tf.keras.layers.Rescaling(1./255)) を加えて正規化をしてみました(このやり方で良いのでしょうか)。  すると、スマホで判定を行ったところ全ての写真が「closed」に分類されてしまいました。また、それぞれの写真について「openの確率」を出力させてみたところ、全て7.8~8.1%の間に入っていて、一見したところミラーが開いた写真と閉じた写真の間に差がないような状態でした。この数値からは、「特徴が小さくなる」というのがあてはまる状態のように思えましたが、どうなのでしょうか。
ANK

2024/10/16 21:52

仮説です。 ImageDataLoaderを使っていた時、その中で正規化をしていました。そうして学習をした後、モデルをTFLiteに変換してスマホに送って試して見たのだと思います。しかし、その時は学習をする訳ではないので、ImageDataLoaderを使わなかった(もしくは正規化をしなかった)のでは無いでしょうか?もしそうだとすると、スマホの中に入っている画像が既に正規化されているものであるという可能性が出てきます。 既に正規化されている場合、IDLを使っていた時はスマホで使わなくなったので問題ありませんが、モデル内でスケールした時は0~1が更に1/255されてしまいます。また、スケールがなかった時も、モデルは0~255で学習されているので、0~1の画像では上手くいかなかったのかも知れません。 スマホにある画像が正規化済みのものなのかどうかを確認してみてください。 また、スマホでの画像の読み込み(モデル用)後のデータの範囲を確認してみてください。 今までで1番自信があります。(というかこれが違かったらお手上げかもしれない...)
masa-nakamura

2024/10/17 14:39

 ImageDataLoaderとありますが、ImageDataGeneratorのことでよいでしょうか?  それから、いただいたコメントを十分理解しているか怪しいのですが、自分の理解した範囲で次のことを試しました(このような意図で言われたのでしょうか)。  スマホにある画像(Bitmapに変換したもの)について、androidStudioでコードを書いてスマホの機能を使って、写真の適当な場所について画素値を出力させると、R,G,Bのそれぞれについて40-120くらいの値が多くみられました。ということは、スマホの画像は正規化されていないということでしょうか。
ANK

2024/10/18 10:09

その通りです。名前を間違えました。 40~120ぐらいなら普通の(正規化前の)画像だと考えて大丈夫だと思います。 これでもないですか... 最終手段かもしれませんが、tensorflowのモデルとtensorflow liteのモデルの重みを比べてみるのはどうでしょうか。同じ場所において値が違うなら変換の問題で、同じならその他が原因になると思います。 同じ値の時考えられるのはこれまでの正規化の問題やメモリ不足、画像の前処理の問題などになると思います。
masa-nakamura

2024/10/19 01:19

問題へのコメントに書きましたが、閾値を変更すればとりあえずの解決にはなりそうな気がしてきたので、まずそれをやってみようと思います。
ANK

2024/10/19 01:27

分かりました。頑張ってください!!
masa-nakamura

2024/11/20 04:55

先日はありがとうございました。その後、「閾値を変える」という方法で実用上は何とかなったので、なぜそうなるか等の問題は残りますが、自己解決として報告させていただきました。
ANK

2024/11/20 05:01

上手くいって良かったです。 私もコメントを通じて色々と疑問に思いましたので自分の環境で検証してみようと思います。良い機会をありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問