20210507のTensorFlowに関する記事は3件です。

Teachable Machine の機械学習モデル出力に関するメモ 〜画像プロジェクト編〜【2021年5月版】

この記事は、以前も何度か記事を書いてきた「Teachable Machine」に関する記事です。 もう少し補足すると、Teachable Machine には以下の「画像・音声・ポーズ」の 3つのプロジェクトがありますが、今回は画像プロジェクトで出力できる機械学習モデルの形式などに関するメモです。 画像プロジェクトの機械学習モデル 出力の方法と形式 画像プロジェクトの機械学習モデルを出力する際の画面は以下のとおりです。 上記の画像では、出力形式で TensorFlow.js が選択されていますが、その横に TensorFlow・TensorFlow Lite の 2つもあります。 ここで、出力できる方法・形式をまとめてみます。 クラウドにアップロード TensorFlow.js ダウンロード TensorFlow.js TensorFlow Keras SavedModel TensorFlow Lite 浮動小数点 量子化済み Edge TPU 以上のように、機械学習モデルをダウンロードする場合は複数の出力形式を選べます。 以下、TensorFlow・TensorFlow Lite を選択した際の画面のスクリーンショットものせておきます。 ダウンロードして得られるデータについて 以下の内容は、適当に 2クラス分の学習を行ってみて、機械学習モデルを各種形式でダウンロードした場合に得られたファイルの情報です。 TensorFlow.js metadata.json model.json weights.bin TensorFlow(Keras) keras_model.h5 labels.txt TensorFlow(SavedModel) 【フォルダ】 model.savedmodel 【フォルダ】 assets 【フォルダ】 variables variables.data-00000-of-00001 variables.index saved_model.pb labels.txt TensorFlow Lite(浮動小数点) model_unquant.tflite labels.txt TensorFlow Lite(量子化済み) model.tflite labels.txt TensorFlow Lite(Edge TPU) model_edgetpu.tflite labels.txt 出力したモデルの利用について 機械学習モデルの出力形式は複数の形式を選択できますが、それぞれをプログラムで利用する際に用いる言語等は、提示されるコードスニペットをベースにすると以下のようになります。 TensorFlow.js JavaScript(TensorFlow.js か ml5.js と組み合わせ) TensorFlow:Keras・SavedModel Python TensorFlow Lite:浮動小数点・量子化済み Java TensorFlow Lite:Edge TPU Python 機械学習モデルを用いる場合のコードスニペット・手順について、上記の中の一部はコード・手順だけでなく GitHub等へのリンクも表示されます。 TensorFlow.js を利用する場合のコードの関連リンク ml5.js を利用する場合のコードの関連リンク TensorFlow(Keras)を利用する場合のコードの関連リンク TensorFlow Lite の機械学習モデルを Android で利用する場合 TensorFlow Lite の機械学習モデルを Edge TPU で利用する場合 上記のサンプルを使う際の注意ですが、コードが古い API を使っていて今は利用できないものがあったり、情報が古いものが混じっているようでした。 それに関連する話の 1つは以下で書いています。  ●Mac で Coral USB Accelerator の利用環境準備 & Teachable Machine の Coral用モデルで画像分類(試行錯誤を含む) - Qiita   https://qiita.com/youtoy/items/f9829798b391826e4fec 【追記】 音声・ポーズのそれぞれのプロジェクトについて その後、他の 2つのプロジェクトについても情報をまとめてみました。  ●Teachable Machine の機械学習モデル出力に関するメモ 〜音声・ポーズプロジェクト編〜【2021年5月版】 - Qiita   https://qiita.com/youtoy/items/980e08e8469ae51ce556 あとは、Microsoft の Lobe も情報をまとめたいな。  ●Lobe | Machine Learning Made Easy   https://lobe.ai/   https://github.com/lobe
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FastGAN(LightweightGAN)を試してみる

Towards Faster and Stabilized GAN Training for High-fidelity Few-shot Image Synthesisを試してみたのでまとめてみます。tensorflowで実装してみたのでその際に詰まった点なども書いておこうと思います。  名前についてですが、このGANは「LightweightGAN」と呼ばれることが多いと思います。おそらく、公式実装よりも早く公開されたhttps://github.com/lucidrains/lightweight-ganからそう呼ばれているのでしょう。AI-SCHOLARの記事でも「GPU1枚、1日未満で学習!超高速学習GAN、「Lightweight GAN」」のように紹介されています。ですが、後に著者による実装(FastGAN-pytorch)が公開されたのでここでは「FastGAN」と呼ぶことにします。 FastGANについて  モデルの構造について簡単に説明します。ポイントは以下の2つ。内容はほとんど論文の要約+自分の解釈なので、間違っていることがあればご指摘頂きたいと思います。 SKIP-LAYER CHANNEL-WISE EXCITATION SELF-SUPERVISED DISCRIMINATOR tf.keras.utils.plot_modelで出力した図です。 サイズが大きいので折りたたんであります。 Generatorモデル図 Discriminatorモデル図 SKIP-LAYER CHANNEL-WISE EXCITATION これはSqueeze-and-Excitation block(SEblock)と似ています。 A Squeeze-and-Excitation block. Squeeze-and-Excitation Networksより  SEblockではチャンネルごとに重みづけを行います。これによってAttentionのような効果が期待できるようです。SEblockでは同じfeature mapからweightを求めていますが、SKIP-LAYER CHANNEL-WISE EXCITATIONでは「SKIP-LAYER」とあるように別のfeature mapからweightを求めます。これによってSEblockの効果に加えて、異なる層のskip connectionによって勾配の伝播が効率的に行えるようになることが期待されます。  またstyleGANのようなstyle mixingのような画像生成も可能です。styleGANについては詳しくは書きませんが、解像度ごとにstyleベクトルを作用させて画像生成を行います。その際ある解像度から別のstyleベクトルを使うことで複数の特徴を持った画像を出力させることができるというものです。FastGANではSKIP-LAYER CHANNEL-WISE EXCITATIONにおいて、別の潜在変数から生成したweightを掛け合わせることで同様の画像生成を行えます。これについては後述します。 SELF-SUPERVISED DISCRIMINATOR こちらはDiscriminator側の工夫で、やっていることはかなりシンプルです。DiscriminatorをAutoEncoderのような構造にし、Discriminatorの中間層の出力から元の画像を復元するDecoderを追加しその復元誤差をDのロスに加えて学習させます。Decoderの構造は以下のようにシンプルなネットワークになっています。  Decoderの出力は128x128で、同じサイズにリサイズした画像と誤差を取ります。Decoderは2つ用いていて1つは8x8のfeature mapから画像全体を復元し、もう1つは16x16のfeature mapを8x8にクロップして復元します。クロップはランダムに行いますが、完全にランダムクロップするのではなくHW方向にそれぞれ2分割した4つの中からランダムにピックアップしてDecoderに通します。それを同じ部分を切り取り・128x128にリサイズした画像と誤差を取ります。  AutoEncoder構造によってDiscriminatorがより包括的(comprehensive)な特徴を抽出するようになります。8x8から画像全体を復元することで全体的な特徴を、クロップして部分的に復元することで局所的な特徴を学習することが期待されます。 実装時の注意点 実装時にハマった点や、著者実装と論文との差異・(私が読んだ限り)論文に明記されていない点などを書いていきます。 Spectral Normalization  論文中には明記されていなかったと思いますが、すべての畳み込み層にSpectral Normalizationを用いています。Spectral NormalizationはGANの正規化の手法でGANの安定化にかなり有効だと言われています。私は数学的な理論や背景を詳しく理解しているわけではないので解説はできませんが、解説記事などもあるので詳しく知りたい方はそちらや元論文を参照してください。Tensorflow+kerasで実装する際に一番苦労した点です。当初はtensorflow_addonsのSpectral Normalizationを用いていましたが、学習が全く進まずこの調査にかなりの時間を要しました。 tensorflow_addonsのSpectral Normalizationの問題点  以下、tensorflow_addons 0.13.0-dev時点での内容なのでご注意下さい(開発中のバージョンですが、ソースコードを読む限り2021/4/15時点では修正されていないようです)githubのissuesで指摘されているので近いうちに修正されるかもしれません。 問題点は以下の2つです。 1. power iterationの実装 2. wのassign 以下はtensorflow_addonsの実装の一部です。 @tf.function def normalize_weights(self): """Generate spectral normalized weights. This method will update the value of `self.w` with the spectral normalized value, so that the layer is ready for `call()`. """ w = tf.reshape(self.w, [-1, self.w_shape[-1]]) u = self.u with tf.name_scope("spectral_normalize"): for _ in range(self.power_iterations): v = tf.math.l2_normalize(tf.matmul(u, w, transpose_b=True)) u = tf.math.l2_normalize(tf.matmul(v, w)) sigma = tf.matmul(tf.matmul(v, w), u, transpose_b=True) self.w.assign(self.w / sigma) self.u.assign(u) まず1つ目の問題点ですが、 v = tf.math.l2_normalize(tf.matmul(u, w, transpose_b=True)) u = tf.math.l2_normalize(tf.matmul(v, w)) の部分です。wは正規化の対象となる畳み込み層や全結合層のweightです。この計算自体に問題はありませんが、計算にwを用いているため勾配を逆伝播させる際にpower iterationを通して伝播してしまいます。この計算はSpectral Normを求める(近似する)ためのものでこれを通してbackpropagationされてしまうと学習がおかしくなります。なのでtf.stop_gradientを使って勾配の逆伝播を止める必要があります。 2つ目の問題として、正規化したwを直接assignしている点です。私が間違って理解していたら指摘して欲しいと思いますが、Spectral Normalizationを用いて学習させる際は順伝播には正規化されたweightを使います。正規化はpower iterationによって定数(Spectral Norm)を求めて定数倍することです。backpropagationの際は、順伝播時に用いた正規化されたweightを通して正規化前の元々のweightに対して勾配を求めてweightの更新を行うものと理解しています。なので、正規化されたweightを直接assignしてしまうと学習に問題が起こると思われます。 これらを踏まえて修正したコードが以下になります。計算上の問題はクリアされていると思いますが、かなり使い勝手が悪いので要改善です。 Spectral Normalization Convolution, transposed Convolution class SNConv2d(tf.keras.layers.Conv2D): def __init__(self, filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), groups=1, activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None, power_iterations=1, **kwargs): super(SNConv2d, self).__init__( filters=filters, kernel_size=kernel_size, strides=strides, padding=padding, data_format=data_format, dilation_rate=dilation_rate, groups=groups, activation=activation, use_bias=use_bias, kernel_initializer=kernel_initializer, bias_initializer=bias_initializer, kernel_regularizer=kernel_regularizer, bias_regularizer=bias_regularizer, activity_regularizer=activity_regularizer, kernel_constraint=kernel_constraint, bias_constraint=bias_constraint, **kwargs) self.power_iterations = power_iterations self._kernel = None self.sigma = tf.constant(1.0, tf.float32) @property def kernel(self): return self._kernel / self.sigma @kernel.setter def kernel(self, val): self._kernel = val def build(self, input_shape): super().build(input_shape) self.w_shape = self._kernel.shape.as_list() self.u = self.add_weight( shape=(1, self.w_shape[-1]), initializer=tf.initializers.TruncatedNormal(stddev=0.02), trainable=False, name="sn_u", dtype=self._kernel.dtype, ) def call(self, inputs, training=None): if training is None: training = tf.keras.backend.learning_phase() if training: self.sigma = self.normalize_weights() output = super().call(inputs) return output # @tf.function def normalize_weights(self): w = tf.reshape(self._kernel, [-1, self.w_shape[-1]]) u = self.u with tf.name_scope("spectral_normalize"): for _ in range(self.power_iterations): v = tf.stop_gradient(tf.math.l2_normalize(tf.matmul(u, w, transpose_b=True))) u = tf.stop_gradient(tf.math.l2_normalize(tf.matmul(v, w))) sigma = tf.matmul(tf.matmul(v, w), u, transpose_b=True) self.u.assign(u) return tf.squeeze(sigma) def get_config(self): config = {"power_iterations": self.power_iterations} base_config = super().get_config() return {**base_config, **config} class SNConv2d_transpose(tf.keras.layers.Conv2DTranspose): def __init__(self, filters, kernel_size, strides=(1, 1), padding='valid', output_padding=None, data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None, power_iterations=1, **kwargs): super(SNConv2d_transpose, self).__init__( filters=filters, kernel_size=kernel_size, strides=strides, padding=padding, data_format=data_format, dilation_rate=dilation_rate, activation=activation, use_bias=use_bias, kernel_initializer=kernel_initializer, bias_initializer=bias_initializer, kernel_regularizer=kernel_regularizer, bias_regularizer=bias_regularizer, activity_regularizer=activity_regularizer, kernel_constraint=kernel_constraint, bias_constraint=bias_constraint, **kwargs) self.power_iterations = power_iterations self._kernel = None self.sigma = tf.constant(1.0, tf.float32) @property def kernel(self): return self._kernel / self.sigma @kernel.setter def kernel(self, val): self._kernel = val def build(self, input_shape): super().build(input_shape) self.w_shape = self._kernel.shape.as_list() self.u = self.add_weight( shape=(1, self.w_shape[-2]), initializer=tf.initializers.TruncatedNormal(stddev=0.02), trainable=False, name="sn_u", dtype=self._kernel.dtype, ) def call(self, inputs, training=None): if training is None: training = tf.keras.backend.learning_phase() if training: self.sigma = self.normalize_weights() output = super().call(inputs) return output # @tf.function def normalize_weights(self): w = tf.reshape(tf.transpose(self._kernel, [0, 1, 3, 2]), [-1, self.w_shape[-2]]) u = self.u with tf.name_scope("spectral_normalize"): for _ in range(self.power_iterations): v = tf.stop_gradient(tf.math.l2_normalize(tf.matmul(u, w, transpose_b=True))) u = tf.stop_gradient(tf.math.l2_normalize(tf.matmul(v, w))) sigma = tf.matmul(tf.matmul(v, w), u, transpose_b=True) self.u.assign(u) return tf.squeeze(sigma) def get_config(self): config = {"power_iterations": self.power_iterations} base_config = super().get_config() return {**base_config, **config} SKIP-LAYER CHANNEL-WISE EXCITATION in Discriminator 詳細はDiscriminatorのモデル図を見てもらうと分かりますが、DiscriminatorにもSKIP-LAYER CHANNEL-WISE EXCITATIONが用いられていました。 Small Discriminator Generatorのモデル図の中に128x128x3の出力があります。これは128x128の画像を出力しこちらについてもAdversarial lossを取り学習させています。これも論文中で特に言及されていなかったと思いますが、これはかなり効果的だと思います。一度しか試していませんが、この構造を除いた際に学習がほぼ進みませんでした。なので中間層から低解像の出力をしてそちらも使って学習させることで安定化につながるor学習スピードが上がる効果があると考えられます。 reconstruction loss  Discriminatorのロスとして再構成誤差を使うことは上述しましたが、その誤差を取る際にMAEやMSEではなく、LPIPSを使用しています。LPIPSは2つの画像間の差異を測る指標でalexnetやVGGなどの学習済みモデルの中間層の出力を全結合層に入力して画像間の差のスコアを出力します。LPIPSについてはLPIPSのproject pageに詳しく書かれています。今回はhttps://github.com/richzhang/PerceptualSimilarityで公開されている学習済みモデルを利用しました。 +α  ここからは自分が試すときに追加で試してみた手法についてです。 Small Discriminator  低解像のDiscriminatorを学習させる際にGeneratorの出力を1チャンネルにしてグレースケール画像を出力させるようにしました。この目的としては低解像部分ではテクスチャを、高解像部分では色についての特徴を学習するように役割分担させるためです。以下はanimefaceで学習させた10,000step時点での出力の比較です。 color grayscale BATCHNORM STATISTICS AND SAMPLING  これはbigGANで使われていた手法で、推論時のbatch normalizationの扱いについてです。この問題点についてbigGANの論文中で次のように指摘されています。(以下は私の要約です。詳細はbigGANの論文を参照してください。) Batch normalizationは通常、推論時には学習時に保存していた平均・分散の移動平均を用いて正規化を行います。一方、GANにおいては推論時にもbatch内の平均・分散を用いたほうがよいといわれています。しかし、推論時にbatch内の統計量を用いることは生成画像がbatchsizeの影響を受けてしまうことや、生成画像の再現性が失われてしまうという問題があります。 これについてbigGANでは学習後のモデルでbatch内の平均・分散を保存しておき、推論時はその平均を用いて正規化を行っています。 学習結果 最後に学習結果です。絵画のデータセット(著者が公開していたもの)1000枚・oxford flowers Dataset 約8000枚・アニメイラスト 約5000枚で学習させました。それぞれ約50000step学習させました。 おわりに 今回実装するにあたってspectral normalizationでかなり苦労しました。最初は何も考えずにライブラリを使っていましたが、それがどういった実装になっているかまで見ていくことは必要だと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Pi 4上のDockerコンテナでtensorflowを走らせるのに少しハマったので書く

タイトルの通り 前提 docker-composeを使ってpythonコンテナを走らせようと思いました. 何の変哲もないpythonコンテナで,x86_64のDocker環境だと普通に動いていました. docker-compose.yml version: '3' services: hogehoge-backend: restart: always build: context: ./hogehoge-backend dockerfile: ./Dockerfile container_name: hogehoge-backend working_dir: '/root/' tty: true volumes: - ./opt:/root/opt Dockerfile FROM python:3.6 USER root RUN apt-get update RUN apt-get -y install locales && \ localedef -f UTF-8 -i ja_JP ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:ja ENV LC_ALL ja_JP.UTF-8 ENV TZ JST-9 ENV TERM xterm RUN apt-get install -y vim less RUN pip install --upgrade pip RUN pip install --upgrade setuptoolslibxkbcommon0 xdg-utils libgtk-3-0 RUN apt-get update RUN apt-get -y install python3-pip 加えて,コンテナ内でアプリケーションを利用するためにpip用のrequirements.txtを用意していました. 下記の通り. requirements.txt pandas tensorflow requests keras urllib3 sklearn 初回起動時に pip3 install -r requirements.txt でインストールするのを想定してました(ビルド時に自動化はしてない) 前述の通り,x86_64環境でアプリケーション作ってから,「RPi4で動かせば電気代安いんじゃね・・・?」と素朴に欲を出して試してみたところ次節に述べる通りハマりました ハマったところ tensorflowがpipで入らない 状況 pipでrequirements.txtを用いてtensorflowをインストールしようと試みると下記のようなエラーが出る $ pip3 install -r requirements.txt ERROR: Could not find a version that satisfies the requirement tensorflow ERROR: No matching distribution found for tensorflow $ uname -a Linux 24aa5e15abb5 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux 解決策 pipをupgradeしてもダメだったので,結局wheelをダウンロードしてきた. $ wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v2.0.0/tensorflow-2.0.0-cp37-none-linux_armv7l.whl $ python3 -m pip uninstall tensorflow $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl バージョン選びたい方はこちらから選択して所望のバージョンのwhlを落としてくると良いと思います ※なお,tensorflow-on-armは cp35 か cp37 しか用意されていない様子だったので(どうやらcpの後の数字はpythonのバージョンらしい),あらかじめコンテナ上ではpython3.7を利用するようにDockerfileを書き換えてビルドしておいた Dockerfile - FROM python:3.6 + FROM python:3.7 h5pyが入らない 状況 前述の通りtensorflowをインストールしたところ,h5pyのインストール時にエラーが出る $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl Building wheels for collected packages: numpy, termcolor, wrapt, h5py Building wheel for numpy (PEP 517) ... done Created wheel for numpy: filename=numpy-1.20.2-cp37-cp37m-linux_armv7l.whl size=15411079 sha256=1934c0d7b18fcc872ff7b95a361b9580d7586aabab8bada8991e6df443ed047f Stored in directory: /root/.cache/pip/wheels/dc/89/4e/d661a082dcb028182ea7b4561c34dbcf717169c443ea0087a2 Building wheel for termcolor (setup.py) ... done Created wheel for termcolor: filename=termcolor-1.1.0-py3-none-any.whl size=4830 sha256=0844dee89ff65f17c6d1d00bd40ef6ac414156937121fa8e51773d7658511f64 Stored in directory: /root/.cache/pip/wheels/3f/e3/ec/8a8336ff196023622fbcb36de0c5a5c218cbb24111d1d4c7f2 Building wheel for wrapt (setup.py) ... done Created wheel for wrapt: filename=wrapt-1.12.1-cp37-cp37m-linux_armv7l.whl size=72200 sha256=e7331863b9a094e6ab7520c5157b321fc81739732dfc835b241a0f916a07c9ce Stored in directory: /root/.cache/pip/wheels/62/76/4c/aa25851149f3f6d9785f6c869387ad82b3fd37582fa8147ac6 Building wheel for h5py (PEP 517) ... error Loading library to get build settings and version: libhdf5.so (中略) running build_ext Loading library to get build settings and version: libhdf5.so error: Unable to load dependency HDF5, make sure HDF5 is installed properly error: libhdf5.so: cannot open shared object file: No such file or directory ---------------------------------------- ERROR: Failed building wheel for h5py 解決策 libhdf5-devをインストールする(※) $ apt-get install libhdf5-dev この後再度tensorflowのインストールを実行すると進む. $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl ※はじめからDockerfileに記述しておいたほうが良いかも
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む