機械学習のExampleから覚えるPython(関数呼び出し)

今までPythonを感覚的に使っていたので、改めて文法を知ろうかなと。
その際にいま流行りの機械学習(深層学習)のExampleを例にすると
わかりやすいのかなと思ったので書いてみる。

※基本的には Python3.x系のつもりで記載してます

Example

https://www.tensorflow.org/tutorials/ に記載されている

import tensorflow as tf
mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)

※本記事記載の時点のコードです。

関数の呼び出し

mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()

tf.keras.datasets.mnistload_data関数を呼んでいます。
その返り値を x_train,y_train, x_test,y_testに代入しています。

load_data関数はこちらで定義されています。

@keras_export('keras.datasets.mnist.load_data')
def load_data(path='mnist.npz'):

「@」(デコレータ)は今後書こうと思います。
呼び出しの時に 引数pathは default : mnist.npz のままで良いということで省略してますね。

returnとしても以下で定義されているためそのまま格納ですね!

    return (x_train, y_train), (x_test, y_test)

返り値の受け取りについては後々...

機械学習のExampleから覚えるPython(import)

今までPythonを感覚的に使っていたので、改めて文法を知ろうかなと。
その際にいま流行りの機械学習(深層学習)のExampleを例にすると
わかりやすいのかなと思ったので書いてみる。

※基本的には Python3.x系のつもりで記載してます

Example

https://www.tensorflow.org/tutorials/ に記載されている

import tensorflow as tf
mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)

※本記事記載の時点のコードです。

import

本コードに適用するパッケージを記述します。
パッケージとは __init__.py があるディレクトリ構想のものを示します。
Python 3.3以降では __init__.py がなくても良いみたいです。

import module as identifier

import tensorflow as tf

tensorflow パッケージを読みこみ、tfと定義する。

もし、

import tensorflow

だけであれば、その後のコードは

mnist = tensorflow.keras.datasets.mnist

と書かないとエラーになります。

from relative_module import identifier

https://www.tensorflow.org/tutorials/keras/basic_classification より

# TensorFlow and tf.keras
import tensorflow as tf
from tensorflow import keras

from を使った記述があります。 これは、tensorflow パッケージにある keras モジュールを呼び出してます。

その後のコードでは、kerasから記述することができます。

fashion_mnist = keras.datasets.fashion_mnist

最初の import - as で書くと

fashion_mnist = tf.keras.datasets.fashion_mnist

import時の検索範囲

その他

「,」区切り

from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

とか書くことができます。 import xxxでも書けますが、Pythonのスタイルガイドでは非推奨らしいです。

「*」

from tensorflow import *

ですべてのモジュールをインポートすることができます。
ただ、個人的には自分で書いたライブラリ以外は必要分しかimport しないようにしてます。

追加(相対インポート)

コメントいただいたので、該当するリンクページ載せときます。

LazyAdamOptimizerを見てみた

見てみた

概要

Adam Optimizerの sparse更新をより効率化したもの。

既存のAdamアルゴリズムは2つの移動平均の各train variableに対して行います。 計算はすべてのStepで更新されます。 このクラスは sparse variableに対して勾配更新の緩やかな処理を行います。 すべてのインデックスのアキュムレータを更新するのではなく、 現在のバッチに含まれるスパース変数インデックスの移動平均アキュムレータのみを更新します。

オリジナルのAdamオプティマイザと比較すると、一部のアプリケーションではモデルトレーニングのスループットが大幅に向上しています。
ただし、元のAdamアルゴリズムとはわずかに異なるセマンティクスが提供されているため、異なる経験的結果が生じる可能性があります。

詳細

  • lazy_adam_optimizer.py
    では、Adam Optimizerから2つの関数のみoverrideしている。

  • _apple_sparse
    ※こちらを例に以後違いを説明します。

  • _resource_apply_sparse

_apple_sparse

Adam Optimizerでは以下のように関数を呼んでいる。

LazyAdam Optimizerでは*\_shared関数は呼んでいない

  def _apply_sparse(self, grad, var):
    beta1_power, beta2_power = self._get_beta_accumulators()
    beta1_power = math_ops.cast(beta1_power, var.dtype.base_dtype)
    beta2_power = math_ops.cast(beta2_power, var.dtype.base_dtype)
    lr_t = math_ops.cast(self._lr_t, var.dtype.base_dtype)
    beta1_t = math_ops.cast(self._beta1_t, var.dtype.base_dtype)
    beta2_t = math_ops.cast(self._beta2_t, var.dtype.base_dtype)
    epsilon_t = math_ops.cast(self._epsilon_t, var.dtype.base_dtype)
    lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power))

    # \\(m := beta1 * m + (1 - beta1) * g_t\\)
    m = self.get_slot(var, "m")
    m_t = state_ops.scatter_update(m, grad.indices,
                                   beta1_t * array_ops.gather(m, grad.indices) +
                                   (1 - beta1_t) * grad.values,
                                   use_locking=self._use_locking)

    # \\(v := beta2 * v + (1 - beta2) * (g_t * g_t)\\)
    v = self.get_slot(var, "v")
    v_t = state_ops.scatter_update(v, grad.indices,
                                   beta2_t * array_ops.gather(v, grad.indices) +
                                   (1 - beta2_t) * math_ops.square(grad.values),
                                   use_locking=self._use_locking)

    # \\(variable -= learning_rate * m_t / (epsilon_t + sqrt(v_t))\\)
    m_t_slice = array_ops.gather(m_t, grad.indices)
    v_t_slice = array_ops.gather(v_t, grad.indices)
    denominator_slice = math_ops.sqrt(v_t_slice) + epsilon_t
    var_update = state_ops.scatter_sub(var, grad.indices,
                                       lr * m_t_slice / denominator_slice,
                                       use_locking=self._use_locking)
    return control_flow_ops.group(var_update, m_t, v_t)

 m_t := beta_1 * m_ {t-1} *+ (1 - beta_1) * g

の箇所だけを比較すると

  • Adam Optimizer
    # m_t = beta1 * m + (1 - beta1) * g_t
    m = self.get_slot(var, "m")
    m_scaled_g_values = grad * (1 - beta1_t)
    m_t = state_ops.assign(m, m * beta1_t,
                           use_locking=self._use_locking)
    with ops.control_dependencies([m_t]):
      m_t = scatter_add(m, indices, m_scaled_g_values)
  • LazyAdam Optimizer
    # \\(m := beta1 * m + (1 - beta1) * g_t\\)
    m = self.get_slot(var, "m")
    m_t = state_ops.scatter_update(m, grad.indices,
                                   beta1_t * array_ops.gather(m, grad.indices) +
                                   (1 - beta1_t) * grad.values,
                                   use_locking=self._use_locking)

となっている。

LazyAdamで使っている scatter_updateindicesの部分(sparse)のみ更新する関数

そのあと v_tも同じように計算し

    m_t_slice = array_ops.gather(m_t, grad.indices)
    v_t_slice = array_ops.gather(v_t, grad.indices)

切り取って

    denominator_slice = math_ops.sqrt(v_t_slice) + epsilon_t
    var_update = state_ops.scatter_sub(var, grad.indices,
                                       lr * m_t_slice / denominator_slice,
                                       use_locking=self._use_locking)

更新値を算出。

まとめ

Moving-Averageの計算において、部分な値にて計算しているだけみたい。
(一部 addから updateの変更も合っているけど)

参考

初Kaggle体験記

Kaggleのコンペを初めてやったので、その体験記。

参加したコンペは

結果は残念ながら、791/1304位(※現時点では未確定)ぐらい。

でも、初めてやったなりに面白かったので書きます。
機械学習初心者です。

概要

f:id:kocha2012:20181205164439p:plain

↑こんな感じでスコア遷移してました。

最高が0.8892だったので、公開Kernelより低いのでぉぃぉぃって感じですが
自分でやった際にはこんな感じでした。

やってきたトピックをあげるとすれば、以下のような感じです。
それぞれ書いていこうと思います。

  • 1.CPU メモリの壁
  • 2.GPU メモリの壁
  • 3.実行時間の壁

実行環境

自分の手持ちパソコンのスペックが低すぎて、時間制限あるなかでの無料枠トライでした。
(次回はパソコン買って出直すぞ)

1.CPU メモリの壁

初めてなので、Kernel見ながらやり方を学んでた。
CNNで判別しようと考えていたので、データは simplifiedのほうを選択して、
csv→suffle→imageという流れでtrainデータを作ろうとしていたらメモリオーバー。。。

判別が340種類で、1種類あたり10万を超えるデータ数でsuffleする前からアウトオブメモリ
Kernelや自分も試してみてだいたい、3万くらいが上限だった。
Kernelでは suffleして tar.gzにしているやり方があったけど今にして思えば真似しとけばよかったかなと。
その当時はGoogle Colabのほうがメインで、Kaggle Kernelで人のデータを使えることを知らなかった。

最終的には、1種類あたり1-2万のデータ(計340-640万)で学習させました。

2.GPU メモリの壁

モデルはMobileNet v2を使用。
当初、画像サイズ64x64で 1種類当たり500のデータをtrainデータへ
batchsizeは32
→ ココらへんが最初の頃の50%前後の遷移。。。

認識率が上がらないので、どうしたものかと思っていた。
施策として

  • データを変える
  • データを増やす
  • 画像サイズをを大きくする
  • batchsizeを増やす
  • モデルを変える

データを変える

データには「Recognized」というフィールドがあり、Trueでフィルタを変えたほうが良いというのがKernelに上がっていたので試した。
結果、あんまり変わらないかった。

データを増やす

trainデータ多くすると、メモリが溢れてしまった。。。

画像サイズを大きくする

trainデータ多くすると、メモリが溢れてしまった。。。

fit_generator との出会い

WebではClassを作るような記事が多かったが、私は関数のみで書いてた。

def data_generator():
  while True:
    #なんかしらのデータ生成
    yield x,y

model.fit_generator(generator=data_generator(), ...

これでbatchsize分だけ容量となり、画像サイズ:128x128, batchsize=256までいけるようになった。 (Kaggle Kernel)
→ 最高70%近くまで遷移

3.実行時間の壁

Kaggle Kernelは 6h
Google Colabは 12h?
と時間制限があるので、どうしても長時間学習することが難しかった。
(つらい)

Google Colab(TPU)

11/8 に参加した TFUG 福岡 #2 (2018/11/08 19:30〜) で、TPUについてあったので試してみた。
コメント部分は、TPUにするために書き直した部分。
Kerasからの変換は制限があるもよう。
fit時のcallbacksも指定なしにしないといけなかった。

# TPU
tpu_model = tf.contrib.tpu.keras_to_tpu_model(
    model,
    strategy=tf.contrib.tpu.TPUDistributionStrategy(
        tf.contrib.cluster_resolver.TPUClusterResolver(tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
    )
)

def top_3_accuracy(x,y): return keras.metrics.top_k_categorical_accuracy(x,y, 3)

tpu_model.compile(
#    optimizer=tf.keras.optimizers.Adam(lr=0.002),
    optimizer=tf.train.AdamOptimizer(learning_rate=0.002, ),
    loss=tf.keras.losses.categorical_crossentropy,
#    metrics=['accuracy', top_3_accuracy]
    metrics=['accuracy']
)

TPUにしたことで、画像サイズも256x256にできbatchsizeも1024まで増やせた。
(今思うと画像サイズそこまで必要だったかは不明)

そのおかけで、認識率は 80%以上を超えて、90%近くまでいった。
それ以降、3epochぐらい実行するとcolabが再接続するという謎の現象にあってしまいスコアあがらず。。。

過学習にもなってきて、どうしたものかと思って試したのが、
ImageDataGenerator

教師データを水増ししてなんとかならないかと。。。
data_generatorに埋め込んだ。

datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    horizontal_flip=True)

def data_generator(batchsize):
  #なんかのコード
  for img in datagen.flow(x, batchsize=batchsize)
    

結果は後半の劣化の結果となってしまった。
Google Colabで再接続が頻繁に起こらなければ。。。と思いつつ無念な結果に。

やってた状況は 昼間はKaggleやれず、作業は夜のみで夜投入して朝確認の繰り返しだったので 途中エラーや再接続とかなると1日がパァーになった。

まとめ

  • Kaggle楽しい
  • KernelやDiscussion読んでて色々ためになった
  • 自分で環境揃えないと厳しい><
  • fit_generator 良い
  • ImageDataGenerator 楽
  • 高スコアのモデルや手法を知りたいので暫く待とう
  • Kaggleも昼間にやれる職に変えたい

次回、別コンペでリベンジです!

UVMをRISC-Vプロセッサの検証に

UVM-based Verification of a RISC-V Processor Core Using a Golden Predictor Model and a Configuration Layer

RISC-Vプロセッサの検証を UVMをベースした検証環境について記載されてます。

(ここで出てきている Codasip Studioってなんだろう?)

コンフィグレーション(実装命令セット)から環境作ってくれるみたい。
エミュレータまでのフローもあり、25倍から100倍の高速化