アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

衛星のSAR画像-セグメンテーションコンペの備忘録

衛星データのSAR画像を用いたセグメンテーションのコンペがあったので、その際の使ったコードとか手法の備忘録。

コンペ内容は事情により省略。手法だけまとめてきます。

大雑把に言うと、過去と現在の画像から特定の領域を0, 1でセグメンテーションするタスク。

目次
1. 使ったネットワーク「HRNet」
2.グレースケールのtifフォーマット画像の読み込み
3.tifフォーマット画像のrgb化
4.使える手法
5.予測したmask画像の0, 1化
6.クロップして予測してconcat
7.よかった手法、ダメだった手法



1.使ったネットワーク「HRNet」

使ったのは姿勢推定とかでSOTを出したネットワーク。すごい軽かったし、カスタマイズしやすかった。

f:id:trafalbad:20210905185837p:plain

reluをmishに変えたりしたのが効果的だった。

tensorflow版のmish活性化関数

import tensorflow_addons as tfa
x = tfa.activations.mish(x)

ちなみにクラスが1のセグメンテーションタスクだったので、アウトプットサイズも1でOK。


2.グレースケールのtifフォーマット画像の読み込み

グレースケールのtifフォーマットの画像は医療系データとか衛星データでよく見かける。

読み込みは普通のpngとかjpgとかとは違って工夫がいる。


pillowで読み込み

sar = Image.open('image.tif')

opencvでよみこみ

sar = cv2.imread('image.tif', -1)

tif用の画像の可視化関数。colablatelyで、使ってるものをjupyterで使えるようにした。

def cv2_imshow(a):
    """A replacement for cv2.imshow() for use in Jupyter notebooks.
    Args:
    a : np.ndarray. shape (N, M) or (N, M, 1) is an NxM grayscale image. shape
      (N, M, 3) is an NxM BGR color image. shape (N, M, 4) is an NxM BGRA color
      image.
    """
    a = a.clip(0, 255).astype('uint8')
    # cv2 stores colors as BGR; convert to RGB
    if a.ndim == 3:
        if a.shape[2] == 4:
            a = cv2.cvtColor(a, cv2.COLOR_BGRA2RGBA)
        else:
            a = cv2.cvtColor(a, cv2.COLOR_BGR2RGB)
    plt.imshow(a/255, 'gray'),plt.show()
    display.display(Image.fromarray(a))


f:id:trafalbad:20210905232204p:plain




3.tifフォーマット画像のrgb化

過去と現在の画像があるので
・差分をとる
・rgb化する方法

とかの方法がある。grayscaleで単に学習させるより、かなり効果的。

# load
im1 = load_tif('0VV.tif')
im2 = load_tif('0VH.tif')
im3 = load_tif('1VV.tif')

anno_test = cv2.imread('train.png', -1)

# RGB化
r = im1 * 255 
g = im2 * 255 
b = im3 * 255
rgb = np.dstack((r,g,b))
rgb = rgb(0, 255).astype('uint8')

f:id:trafalbad:20210905231932p:plain


4.使える手法

他の使える方法まとめ。

1.単純な引き算以外で差分をとる方法

diff = np.maximum(im1- im2], 0.5) - 0.5 
im_pred = np.heaviside(diff, 0)


2.とても細かい部分が多いので、細かい部分を除去する方法

def plot_correctness(im_truth, im_pred):
    r = im_truth
    g = im_pred 
    b = im_pred
    cv2_imshow(cv2.merge((r, g, b)))


def remove_blob(im_in, threshold_blob_area=25): 
    '''remove small blob from your image '''
    im_out = im_in.copy()
    contours, hierarchy = cv2.findContours(im_in.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    for i in range (1, len(contours)): 
        index_level = int(hierarchy[0][i][1]) 
        if index_level <= i:
            cnt = contours[i]
            area = cv2.contourArea(cnt)
            if area <= threshold_blob_area: 
                cv2.drawContours(im_out, [cnt], -1, 0, -1, 1)
    return im_out

im_out = remove_blob(im_pred * 255, threshold_blob_area=25)
plot_correctness(anno_test * 255, im_out.astype(np.uint8))


f:id:trafalbad:20210905232050p:plain



5.予測したmask画像の0, 1化

予測したgrayscaleの画像を0と1のマスクに変換する。
sigmoidを使うことがほとんどで、閾値で判別して、0か1を突っ込む。

閾値の設定が割と定まってないのが難儀。

def create_binary_mask(pred, threshold=0.1):
    mask = np.zeros_like(pred)
    mask[pred < threshold] = 0
    mask[pred > threshold] = 1
    return mask

# IOU計算
def calc_IoU(y, y_pred):
    by = np.array(y, dtype=bool)
    by_pred = np.array(y_pred, dtype=bool) 
    overlap = by * by_pred
    union = by + by_pred
    return overlap.sum() / float(union.sum())


6.クロップして予測してconcat

今回のコンペ は

・画像枚数が少ない(40枚以下)
・セグメンテーション領域が細かい
・画像サイズがでかい

ので、1枚の画像を複数にクロップして学習する。予測する画像もクロップしてから予測。そのあとくっつける(concat)。


こうすることで、細かい部分もかなり精密にセグメンテーションできるので、丸ごと画像を入れるより、かなり正確にセグメンテーションできる。(引用:qiita

f:id:trafalbad:20210905191827p:plain





7.よかった手法、ダメだった手法

効果があった手法

・差分とってrgb化
・augmentation :horizontal flip, vertical flip,
horizontal and vertical flip
SGD
・bce dice loss
・cropして予測した後concat(896にリサイズ後にサイズ448×448にクロップして4枚にした)
・unet, efficientunet
ヒストグラム平均化
・sigmoid
・0〜1で正規化

ダメだった手法

・adam
・focal loss, jacard loss
・小さい塊を除去する
・softmax
・標準化

終了2週間前に参加して、IOUが45%でブロンズに入れたので、よくできた方だと思う。手法は過去コンペとか読み漁ったのが良かった。

あとは試行回数とアイデア勝負。引き出しの大きさが重要だな感じた。

あとコンペ のdiscussionとかを読み込むことで、コンペ の概要を手っ取り早くしれるのであと出しの参加でも十分勝負できた。

Jetson Nano セットアップ備忘録 (Pytorch , onnxとか動かすまで)

Jetson nanoを使ったのでセットアップの備忘録。

nv-jetson-nano-sd-card-image-r32.3.1(JetPack 4.3)」のページのイメージをMicro SDカードに焼いた。(from qiitaの記事)


最新バージョンはJetpack==4.5.1だけど4.3に合わせた。

パーツ一式はこのサイト「Mac でJetson Nanoをセットアップ」を参考にした。

目次
1.SDカードに書き込み
2.はじめにjetson nano起動 & apt upgrade
3.仮想環境作成
4.Pytorch のinstall
5.whlでTensorflowのインストール
6.jetson上でonnxでyolov4のリアルタイム推論してみる

1.SDカードに書き込み

Getting Started with Jetson Nano Developer Kit」通りにパーティションをいじる。

SDカードが刺さってない状態

# 何も表示されない。
$ diskutil list external | fgrep '/dev/disk' 

SDカード入れると

$ diskutil list external | fgrep '/dev/disk'
>>>>>
/dev/disk4 (external, physical):
/dev/disk5 (synthesized):


EtcherからSDカードは/dev/disk4らしいので、
f:id:trafalbad:20210804090050p:plain

$ sudo diskutil partitionDisk /dev/disk4 1 GPT "Free Space" "%noformat%" 100% 
>>>>
Unmounting disk
Creating the partition map
Waiting for partitions to activate
Finished partitioning on disk4

Etcherで書き込み。

書き込み完了。
f:id:trafalbad:20210804091011p:plain



2.はじめにjetson nano起動 & apt upgrade

sudo apt update
sudo apt upgrade
sudo apt dist-upgrade

3.仮想環境作成

sudo apt install python3-venv

仮想環境の例

python3 -m venv place
cd place
source bin/activate

# 抜けるとき
deactivate
python3 -m venv place 
cd place
source bin/activate

# dependencies
# sudo pip3 install wheel
sudo apt install gfortran libopenblas-base libopenmpi-dev libopenblas-dev libjpeg-dev zlib1g-dev libv4l-dev python3-pip
# pip update
sudo pip3 install -U pip

# sudo pip3 install jupyter notebook
sudo pip3 install cython numpy # scipy
sudo pip3 install pandas tqdm Pillow pybind11 scikit-learn
sudo pip3 install opencv-python

4.Pytorch のinstall

nvidiaのサイトから

pytorch version 1.4をdownload

wget https://nvidia.box.com/shared/static/ncgzus5o23uck9i5oth2n8n06k340l6k.whl -O torch-1.4.0-cp36-cp36m-linux_aarch64.whl

sudo pip3 install torch-1.4.0-cp36-cp36m-linux_aarch64.whl

# torchvision インストール
pip3 uninstall numpy
pip3 install numpy==1.19.4

git clone https://github.com/pytorch/vision torchvision
cd torchvision && git checkout v0.5.0 && export BUILD_VERSION=0.5.0
sudo python3 setup.py install


Version確認

$ python3
$ import torch
$ torch.__version__
>>>>>
'1.4.0'


Pytorch とtorchVisionのversionの対応表






Pytorchのバージョン対応するtorchVisionのバージョン
1.0v0.2.2
1.1v0.3.0
1.2v0.4.0
1.3v0.4.2
1.4v0.5.0


5.whlでTensorflowのインストール

whlでnvidiaのサイトからインストールする。

sudo apt-get install libjpeg8-dev hdf5-tools libhdf5-serial-dev libhdf5-dev zlib1g-dev zip

# Python package dependencies
sudo pip3 install bumpy grpcio absl-py py-cpuinfo psutil portpicker mock six requests gast h5py astor termcolor wrapt protobuf google-pasta keras_preprocessing keras_applications

# tensorflow install
sudo pip3 install https://developer.download.nvidia.com/compute/redist/jp/v42/tensorflow-gpu/tensorflow_gpu-1.13.1+nv19.5-cp36-cp36m-linux_aarch64.whl


tensorflowのversion確認

$ python3 
$ import tensorflow as tf
$ tf.__version__
>>>
1.13.1


6.jetson上でonnxでyolov4のリアルタイム推論してみる

onnxに変換したyolov4-tinyでリアルタイム推論してみる。

# onnx dependencies
sudo apt install libprotobuf-dev protobuf-compiler pybind11-dev libprotoc-dev

# onnx install
sudo pip3 install onnxruntime
sudo pip3 install onnxconverter-common==1.6.0
sudo pip3 install onnx==1.6.0

# 推論
$ python3 camera_estimate.py yolov4_tiny.onnx

jetson上での推論をモニターで写した動画。

f:id:trafalbad:20210805220141g:plain

画像サイズ小さくしたから、距離推定の値も小さくなってもうた。
tensorRTとかc++は機会があったら。




f:id:trafalbad:20210805220415j:plain
大相撲インフェルノ(趣味のカラクリ武器作り)

参考サイト

This guide provides instructions for installing TensorFlow for Jetson Platform.

【Jetson_nano】インストールからTensorflow,Chainer,そしてKeras環境構築出来たよ♬

【Jetson Nano】Jetson NanoにPyTorchをインストールしようとしてハマった話。

MobilenetベースのBlazefaceをResnetベースに蒸留して性能を移植してみた【機械学習】

Blazefacegoogleの訓練済みのデバイス用の顏/手の認識モデルで速くて高性能なモデル。
役割は顔/手の位置とキーポイントを高速に検出する機械学習モデル。


BlazeFaceはMobileNetをバックボーンをベースにして作られてる。

今回はやることは

1.tfliteのblazefaceをpytorch用の重みに変換する。

2.それで、本来のMobileNetバックボーンに加えて、Resnetバックボーンのblazefaceを自作する。

3.自作ResNetバックボーンにMobileNetバックボーン(本家)の性能を「蒸留(Distillation)」を使って移植し、再現。

なので今回はその備忘録。

目次
1.Distillationについて
2.今回使ったloss
3.Resnetベースのバックボーンモデル
4.蒸留の精度
5.精度曲線とloss曲線




1.Distillationについて

蒸留はかなりやり方が多様で、クリエイティビティな側面が大きい。
個人的に下の神記事のWavenetとやり方とkaggleのサイトがかなり参考になった。

Distillation Implementation with Freesound2
Deep Learningにおける知識の蒸留


BlazeFaceは出力が2つある。

・出力2はbboxとランドマークの位置情報
・出力1はクラスの確率

なので、出力1を蒸留用のlossに使うことにした。

あとは普通に出力1, 2の教師あり学習データは、pseudo-labelingで代用。

蒸留の仕組み
f:id:trafalbad:20210701113724p:plain

BlazeFaceの出力部分

class BlazeFace(nn.Module):

〜〜〜〜〜
  c = torch.cat((c1, c2), dim=1)  # (b, 896, 1)

        r1 = self.regressor_8(x)        # (b, 32, 16, 16)
        r1 = r1.permute(0, 2, 3, 1)     # (b, 16, 16, 32)
        r1 = r1.reshape(b, -1, 16)      # (b, 512, 16)

        r2 = self.regressor_16(h)       # (b, 96, 8, 8)
        r2 = r2.permute(0, 2, 3, 1)     # (b, 8, 8, 96)
        r2 = r2.reshape(b, -1, 16)      # (b, 384, 16)

        r = torch.cat((r1, r2), dim=1)  # (b, 896, 16)
        return [r, c]

〜〜〜〜〜

以下の部分が蒸留で大事なのではと思った。

・KL Divergence_lossに入れるのはネットワークの出力に近いやつほどいい。(確率分布)

・Softmax with Temperature(SwT)はKL Divergence_lossと併用が一般的。

・なるべく蒸留はlossでは確率分布を学びやすいように目的に合わせて、前処理する。

教師あり学習はラベル問題とかセグメンテーションタスクみたいにリアルなデータを使うのが普通だが、教師データがなくてもpseudo-labelingでもいけた。

・lossはMSE以外にも出力が似るように収束するlossならOK。





2.今回使ったloss

蒸留用lossと教師ありデータ用lossでうまくいったのは下の2つ。蒸留用lossは一般的なnn.KLDivLoss()を使って、確率分布を前処理する形にした。
教師ありも本家のlossとは違い、出力が似やすいように前処理した。



1つ目:best性能loss。前処理重視でMAEnn.L1Loss()とかMSEnn.MSELoss(), F.binary_cross_entropyを使った

import torch
import torch.nn as nn
import torch.nn.functional as F

def kl_divergence_loss(logits, target):
    T = 0.01
    alpha = 0.6
    thresh = 100
    criterion = nn.L1Loss()
    # c : preprocess for distillation
    log2div = logits[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    tar2div = target[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    closs = nn.KLDivLoss(reduction="batchmean")(F.log_softmax((log2div / T), dim = 1), F.softmax((tar2div / T), dim = 1))*(alpha * T * T) + criterion(log2div, tar2div) * (1-alpha)
    
    # r
    anchor = load_anchors("src/anchors.npy")
    rlogits = decode_boxes(logits[0], anchor)
    rtarget = decode_boxes(target[0], anchor)
    rloss = criterion(rlogits, rtarget) 
     
    return closs + rloss
def kl_divergence_loss(logits, target):
    T = 0.01
    alpha = 0.6
    thresh = 100
    criterion = nn.MSELoss()
    # c : preprocess for distillation
    log2div = logits[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    tar2div = target[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1).detach()
    closs = nn.KLDivLoss(reduction="batchmean")(F.log_softmax((log2div / T), dim = 1), F.softmax((tar2div / T), dim = 1))*(alpha * T * T) + F.binary_cross_entropy(log2div, tar2div) * (1-alpha)
    
    # r
    anchor = load_anchors("src/anchors.npy")
    rlogits = decode_boxes(logits[0], anchor)
    rtarget = decode_boxes(target[0], anchor)
    rloss = criterion(rlogits, rtarget) 
     
    return closs + rloss


Tを0.01〜10にしてもあんまり影響なく、いかに蒸留と教師あり学習のlossを上手く学びやすい形にするかが重要。


2つ目:あんまり性能出なかったloss。前処理しないと多分、blazefaceみたいな特殊な出力はうまく蒸留できない。

def kl_divergence_loss(logits, target):
    T = 0.01
    alpha = 0.6
    thresh = 100
    criterion = nn.L1Loss()
    # c : preprocess for distillation
    log2div = logits[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    tar2div = target[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    closs = nn.KLDivLoss(reduction="batchmean")(F.log_softmax((log2div / T), dim = 1), F.softmax((tar2div / T), dim = 1))*(alpha * T * T) + criterion(logits[1], target[1]) * (1-alpha)
    
    # r
    rloss = criterion(logits[0], target[0]) 
    return closs + rloss
def kl_divergence_loss(logits, target):
    T = 0.01
    alpha = 0.6
    thresh = 100
    criterion = nn.MSELoss()
    # c : preprocess for distillation
    log2div = logits[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    tar2div = target[1].clamp(-thresh, thresh).sigmoid().squeeze(dim=-1)
    closs = nn.KLDivLoss(reduction="batchmean")(F.log_softmax((log2div / T), dim = 1), F.softmax((tar2div / T), dim = 1))*(alpha * T * T) + criterion(logits[1].squeeze(dim=-1), target[1].squeeze(dim=-1)) * (1-alpha)
    
    # r
    anchor = load_anchors("src/anchors.npy")
    rlogits = decode_boxes(logits[0], anchor)
    rtarget = decode_boxes(target[0], anchor)
    rloss = criterion(rlogits, rtarget) 


3.Resnetベースのバックボーンモデル


Resnetベースはresnet18の構造はMobilenetのモデルにresidual構造を加えたネットワークを自作。

residual構造にした部分

class BlazeFace(nn.Module):
〜〜〜
if self.resnet_backborn:
            print('resnet base')
            inputs = x
            o1 = self.conv(inputs)
            o2 = self.relu(o1)
            o3 = self.b1(o2)
            o4 = self.b2(o3) + self.resconv1(o2)
            o5 = self.b3(o4)
            o6 = self.b4(o5)
            o7 = self.b5(o6) + self.resconv2(o5)
            o8 = self.b6(o7)
            o9 = self.b7(o8)
            o10 = self.b8(o9)
            o11 = self.b9(o10)
            o12 = self.b10(o11) + self.resconv3(o8)
            x = self.backbone1(o12) # (b, 88, 16, 16)
            x1_ = self.b11(x)
            x2_ = self.b12(x1_)
            x3_ = self.b13(x2_) + x1_
            x4_ = self.b14(x3_)
            h = self.backbone2(x4_) + x1_  # (b, 96, 8, 8)
〜〜〜


4.蒸留の精度

実験1.

MobileNetベース(googleのやつ)



f:id:trafalbad:20210701112406p:plain


f:id:trafalbad:20210701112421p:plain


Resnetベース(蒸留したやつ)

f:id:trafalbad:20210701112451p:plain


f:id:trafalbad:20210701112506p:plain

ほとんど本家のblazefaceと同じ精度。蒸留ってすご。





5.精度曲線とloss曲線

精度曲線(MAE)

R出力(出力1)
f:id:trafalbad:20210701074609p:plain


C出力(出力2)
f:id:trafalbad:20210701074639p:plain

loss曲線

train loss
f:id:trafalbad:20210701074654p:plain

validation loss
f:id:trafalbad:20210701074709p:plain


初めは全然違うが段々、値が似てくる。評価用のテストスクリプトでテストできるようにして、なるべく高速で多くPDCAが回しやすいようにした。

tensorboardのグラフでデータがかなり客観的でにとらえられた。








今回初めてだったけど、かなり上手くいった。

教師データがないのが痛かったけどpseudo-labelingで上手いように学習できたのは前処理でさんざん悩んだおかげ。

蒸留は、ハードウェアでは、The・トレンド感な技術なだけあって、今回みたいな変な出力を蒸留するのは相当難かった。


精度はかなり忠実に移植できたので、相当いい結果になったつもり。



参考サイト

tensorflow-BlazeFaceのGitHub

Deep Learningにおける知識の蒸留

モデルの蒸留を実装し freesound2019 コンペで検証してみた。

knowledge-distillation-pytorch

顔から性別・年齢推定アルゴリズム(age gender estimation model)の工夫まとめ【機械学習】

人気の「age gender estimation」とかいう、人間の顔から性別と年齢を予測するモデルを作った時の、テクニックを備忘録として忘れないようにまとめとく。

目次
1.Age-gender-estimation model本体
2.後処理での工夫
3.予測結果


1. Age-gender-estimation model本体


Inceptionv3を使った。ラストの部分以外、特に変わった工夫はしてない。

from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.layers import *
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import *

EPOCHS = 5
BATCH_SIZE = 8
HEIGHT = WIDTH = 299

def load_model(gender_cls=2, generation_cls=6, identity_cls=21):
    adam = Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=1e-07, decay=0.0)
    input_shape = (HEIGHT, WIDTH, 3)
    base_model = InceptionV3(input_shape=input_shape, weights='imagenet', include_top=True)
    bottle = base_model.get_layer(index=-3).output
    bottle = Dropout(rate=0.3)(bottle)
    bottle = GlobalAveragePooling2D()(bottle)
    # gender
    gender_output = Dense(units=gender_cls, activation='softmax', name='gender_output')(bottle)
    
    # generation
    generation_output = Dense(units=generation_cls, activation='softmax', name='generation_output')(bottle)
    
    # identity age
    identity_outout = Dense(units=identity_cls, activation='softmax', name='identity_outout')(bottle)

    model = Model(inputs=base_model.input, outputs=[generation_output, identity_outout, gender_output])
    model.compile(optimizer=adam,
                  loss={'generation_output': 'categorical_crossentropy',
                      'identity_outout': 'categorical_crossentropy',
                      'gender_output': 'binary_crossentropy'},
                  #loss_weights={'realage_output': 1, 'gender_output': 10},
                  metrics={'generation_output':'accuracy',
                           'identity_outout':'accuracy',
                           'gender_output': 'accuracy'})

    model.summary()
    return model

if __name__=='__main__':
    model = load_model()

2.後処理での工夫

人間はそれぞれ個性があるので顔自体は2〜3歳でもほとんど変化しないという性質を利用。

下は人間の顔のしわの数が年齢毎に増加するのをグラフにしたもの。

f:id:trafalbad:20210524111813p:plain

歳をとるごとにしわが増えているのがわかる。



そこでまずは全年齢を予測するのは難しいので、以下の手順で問題を細分化して簡単にした。


1.generation(世代)=6カテゴリ、identity-age(大まかな年齢)=21カテゴリを予測対象
2.generationとidentity-ageを予測


f:id:trafalbad:20210524111852p:plain



3.予測したgenerationから、identity-ageの範囲を絞り、そこから年齢を求める

f:id:trafalbad:20210524111940p:plain

というふうに難しい問題を簡単に分割して予測誤差を減らした。


こうすれば100歳分の予測やカテゴリ分類よりは簡単かつ正確にできる。



後処理コード

# 世代で0~7, 7~15, 15~25, 25~45....の6カテゴリに分類

def return_generation(age):
    if age <7:
        return 0
    elif age >=7 and age<15:
        return 1
    elif age >=15 and age < 25:
        return 2
    elif age >=25 and age < 45:
        return 3
    elif age >=45 and age <70:
        return 4
    elif age >=70:
        return 5

6クラスで世代を予測。 正解率は 83%

identity ageを21クラスで予測。 正解率は 46%



まず世代を予測して、indeentity-ageの範囲を絞り、次にidentity-ageとnp.argmaxで大まかな実年齢を求める。

難しい問題を簡単な問題に分割してやることで劇的に正解率が向上した。

class PostProcess(object):
    def __init__(self, pred_generation, pred_identity):
        self.pred_generation = pred_generation
        self.pred_identity = pred_identity
    
    def generation2identity(self, generation):
        if generation==0:
            return 0, 3
        elif generation==1:
            return 3, 5
        elif generation==2:
            return 6, 9
〜〜〜〜略〜〜〜〜
        
        
    def post_age_process(self):
  
        # generation(予測した世代)からidentity-ageの範囲のindexを取り出す
        lowidx, largeidx = self.generation2identity(np.argmax(self.pred_generation))
        print("lowidx, largeidx from generation", lowidx, largeidx, np.argmax(self.pred_generation))

   # identity-ageの範囲を絞る
        slice_pred_identity = self.pred_identity[0][lowidx:largeidx]
        print('pred_identity', self.pred_identity)
        print('list', slice_pred_identity)
        print("pred identity idx", np.argmax(slice_pred_identity)+lowidx)

   # identity-ageを求めて、実年齢に変換
        a = np.argmax(slice_pred_identity)+lowidx
        if a==0:
            return 2
        elif a==1:
            return 4
        elif a==2:
            return 6
        elif a==3:
            return 9
〜〜〜略〜〜〜

3.予測結果

顔1

f:id:trafalbad:20210524164046j:plain

予測年齢:21歳 Famale
f:id:trafalbad:20210524164130p:plain

顔2

f:id:trafalbad:20210524164118j:plain

予測年齢:2歳 Female

f:id:trafalbad:20210524164205p:plain





かなりうまく予測できてる感じする。


全体コード

import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'

import sys
import time
import cv2
import numpy as np

from models import model, load, identity_age
from models.load import to_mean_pixel, MEAN_AVG

x = 100
y = 250
savepath='/Users/~/desktop/'

age_estimate_model = model.load_model()
age_estimate_model.load_weights('weights/best.hdf5')

def draw_label(image, point, label, font=cv2.FONT_HERSHEY_PLAIN,
               font_scale=1.5, thickness=2):
    text_color = (255, 255, 255)
    cv2.putText(image, label, point, font, font_scale, text_color, thickness, lineType=cv2.LINE_AA)


img_path='image1.jpg'
img = cv2.imread(img_path)
img = cv2.resize(img, (299, 299))
cimg = img.copy()
img = to_mean_pixel(img, MEAN_AVG)
img = img.astype(np.float32)/255
img = np.reshape(img, (1, 299, 299, 3))

generation, iden, gender = age_estimate_model.predict(img)
pred_identity = PostProcess(generation, iden).post_age_process()
    
pred_gender = "Male" if np.argmax(gender) < 0.5 else "Female"
print('gender is {0} and predict age is {1}'.format(pred_gender, pred_identity))
label = "{0} age  {1}".format(int(pred_identity), "Male" if np.argmax(gender) < 0.5 else "Female")

draw_label(cimg, (x, y), label)
cv2.imwrite('prediction.png', cimg)


こういう誤差をなくすように工夫することで、重いベンチマークのモデルを使わなくても簡単なモデルでかなりいい具合の予測ができた。

人間の脳と同じで問題がむずければ完璧主義はやめて、園児が解けるレベルに簡単にしても十分性能の良いモデルができる。

Google openImage Datasetでyolov4のデータセットをdownload & annotationファイルの作成

今回はgoogle open Image datasetのyolov4データをdownloadする方法。

google open Image datasetは物体検出からセグメンテーションまで良質なデータが揃ってtて、v1〜v6まである。

直でdownloadすると割と面倒。(調べるのがめんどい)

なので今回は物体検出の特定のclassのデータをdownloadする方法のメモ。

データのDownload

OIDv4_ToolKitを使う。

  • Open Images Dataset V4 の任意のクラスだけの画像とアノテーションデータをダウンロードすることができる
  • アノテーションデータは、物体検出 のみ。セグメンテーションは対応していない。
  • bbox は [name_of_the_class, left, top, right, bottom] の .txt フォーマットで得られるため、場合によっては変換が必要
$ git clone https://github.com/EscVM/OIDv4_ToolKit.git
$ cd OIDv4_ToolKit
$ pip3 install -r requirements.txt

今回はclass==Knife をdownload。
classはGoogle open Image Datsetの検索欄から見れる。


--type_csv[train/validation/test/all]と選択可能。
全部欲しいのでallを指定。

引数はgithubに書いてある通りに指定できる


IsOccluded: Indicates that the object is occluded by another object in the image.

IsTruncated: Indicates that the object extends beyond the boundary of the image.

IsGroupOf: Indicates that the box spans a group of objects (e.g., a bed of flowers or a crowd of people). We asked annotators to use this tag for cases with more than 5 instances which are heavily occluding each other and are physically touching.

IsDepiction: Indicates that the object is a depiction (e.g., a cartoon or drawing of the object, not a real physical instance).

IsInside: Indicates a picture taken from the inside of the object (e.g., a car interior or inside of a building).

n_threads: Select how many threads you want to use. The ToolKit will take care for you to download multiple images in parallel, considerably speeding up the downloading process.

limit: Limit the number of images being downloaded. Useful if you want to restrict the size of your dataset.

y: Answer yes when have to download missing csv files.

$ python3 main.py downloader --classes Knife --type_csv all

全部 [Y]で進み、download。





フォルダ構造

$ tree OID
>>>>

OID
|-- Dataset
|   |-- test
|   |   |
|   |   |
|   |   |-- Knife
            |--〜.jpg (Knife画像)
            -- Label
                 |-- ~.txt (box用label text)
|   |-- train
|   |   |
|   |   |
|   |   |-- Knife
            |--〜.jpg (Knife画像)
            -- Label
                 |-- ~.txt (box用label text)
|   |-- validation
|   |   |
|   |   |
|   |   |-- Knife
            |--〜.jpg (Knife画像)
            -- Label
                 |-- ~.txt (box用label text)
`-- csv_folder
    |-- class-descriptions-boxable.csv
    |-- test-annotations-bbox.csv
    |-- train-annotations-bbox.csv
    `-- validation-annotations-bbox.csv


データの情報・中身

# OIDv4_ToolKit/OID/Dataset/train/Knife/Labelのtxtファイルの中身
# validationとtestも同じ

$ cat 870eb1cdddbcce5a.txt
Knife 24.320256 23.04 767.360256 849.92

# OIDv4_ToolKit/OID/Dataset/train/Knife/Labelのデータ数
$ ls -l | wc -l
611

# OIDv4_ToolKit/OID/Dataset/train/Knifeの画像枚数
$ ls |wc -l 
611

# OIDv4_ToolKit/OID/Dataset/test/Knifeの画像とラベル数
161
# OIDv4_ToolKit/OID/Dataset/validation/Knifeの画像とラベル数
56
# csv_folderのフィイルの中身
$ cat class-descriptions-boxable.csv

~~
/m/0pcr,Alpaca
/m/0pg52,Taxi
/m/0ph39,Canoe
/m/0qjjc,Remote control
/m/0qmmr,Wheelchair
/m/0wdt60w,Rugby ball
/m/0xfy,Armadillo
/m/0xzly,Maracas
/m/0zvk5,Helmet

$ cat test-annotations-bbox.csv
>>>
fffc6543b32da1dd,freeform,/m/0jbk,1,0.013794,0.999996,0.388438,0.727906,0,0,1,0,0
fffd0258c243bbea,freeform,/m/01g317,1,0.000120,0.999896,0.000000,1.000000,1,0,1,0,0

$ cat validation-annotations-bbox.csv

>>>
ffff21932da3ed01,freeform,/m/0c9ph5,1,0.540223,0.624863,0.493633,0.577892,1,0,1,0,0
ffff21932da3ed01,freeform,/m/0cgh4,1,0.002521,1.000000,0.000000,0.998685,0,0,0,0,1

Knifeの画像データ

f:id:trafalbad:20210510112838j:plain


データをyolov4で読み込ませる

dataフォルダにKnifeフォルダを入れる。

そんでyolov4用のtextファイルの作成

classes = ['Knife']
classes_dicts = {key:idx for idx, key in enumerate(classes)}

def main(label_path, jpg_path_name, save_filetxt_name):
    with open(save_filetxt_name, 'w') as f:
        for path in os.listdir(label_path):
            filename = path.replace('txt', 'jpg')
            f.write(os.path.join(jpg_path_name, filename))
            
            loadf = open(os.path.join(label_path, path), 'r', encoding='utf-8')
            for line in loadf.readlines():
                cls, x_min, y_min, x_max, y_max = line.split(" ")
                ## rewrite
                y_max = y_max.rstrip('\n')
                x_min, y_min, x_max, y_max = int(float(x_min)), int(float(y_min)), int(float(x_max)), int(float(y_max))
                cls = classes_dicts[cls]
                box_info = " %d,%d,%d,%d,%d" % (
                x_min, y_min, x_max, y_max, int(cls))
                f.write(box_info)
            f.write('\n')
            
if __name__=='__main__':
    data_type='test'
    assert data_type in ['train', 'validation', 'test'], 'corecct word from [train, validation, test]'
    jpg_path_name = 'data/Knife/Dataset/{}/Knife'.format(data_type)
    save_filetxt_name = 'data/pytorch_yolov4_{}.txt'.format(data_type)
    label_path = 'data/Knife/Dataset/{}/Knife/Label'.format(data_type)
    main(label_path, jpg_path_name, save_filetxt_name)


出来上がった、「pytorch_yolov4_validation.txt」を開いてみる。

# load用関数
data_type='validation'
save_filetxt_name = 'data/pytorch_yolov4_{}.txt'.format(data_type)
lable_path = save_filetxt_name

def open_txtfile(label_path):
    truth = {}
    f = open(lable_path, 'r', encoding='utf-8')
    for line in f.readlines():
        data = line.split(" ")
        truth[data[0]] = []
        for i in data[1:]:
            truth[data[0]].append([int(float(j)) for j in i.split(',')])
            print(truth)

open_txtfile(label_path)

>>>

data/Knife/Dataset/validation/Knife/2497ac78d31d89d5.jpg 15,166,942,489,0
data/Knife/Dataset/validation/Knife/09a9a9d1fe0a592a.jpg 55,313,333,1024,0
data/Knife/Dataset/validation/Knife/f2a2a1a0095f5d79.jpg 108,481,1024,636,0
data/Knife/Dataset/validation/Knife/4b6d3c391753e5ce.jpg 225,59,372,219,0 539,242,1024,292,0 611,478,1024,720,0 776,179,1024,244,0
data/Knife/Dataset/validation/Knife/4b1fc77d58646a7e.jpg 65,66,983,744,0
〜〜〜

一応githubのREADME.mdのやつと同じにできてる。これでyolov4用のannotation txt fileができた。





yolov4ファイルの変更ポイント

読み込ませるには以下の点を変更した。

  • dataset.pyのYolo_datasetクラスのimageをloadするときのos.path.joinを消した。
# dataset.py
class Yolo_dataset(Dataset):
〜〜
  def __getitem__(self, index):
        if not self.train:
            return self._get_val_item(index)
        img_path = self.imgs[index]
        bboxes = np.array(self.truth.get(img_path), dtype=np.float)
        img_path = img_path
        use_mixup = self.cfg.mixup
        if random.randint(0, 1):
            use_mixup = 0

  for i in range(use_mixup + 1):
            if i != 0:
                img_path = random.choice(list(self.truth.keys()))
                bboxes = np.array(self.truth.get(img_path), dtype=np.float)
                img_path = img_path
            img = cv2.imread(img_path)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  • メモリ調整のためtrain.pyの引数にnum_workerを追加
def train(model, device, config, epochs=5, batch_size=1, save_cp=True, num_worker = 0, log_step=20, img_scale=0.5):
    train_dataset = Yolo_dataset(config.train_label, config, train=True)
    val_dataset = Yolo_dataset(config.val_label, config, train=False)
   〜〜〜〜
 writer.close()
#  train.py => 実行
# num_workerでメモリ加減を調整

try:
    train(model=model,
          device=device,
          config=cfg,
          epochs=cfg.TRAIN_EPOCHS,
          num_worker=0)
except KeyboardInterrupt:
    torch.save(model.state_dict(), 'INTERRUPTED.pth')
    logging.info('Saved interrupt')
    try:
        sys.exit(0)
    except SystemExit:
        os._exit(0)

>>>>>

2021-05-11 07:57:09,935 <ipython-input-3-6cfc1c1d5a28>[line:36] INFO: Starting training:
        Epochs:          300
        Batch size:      64
        Subdivisions:    16
        Learning rate:   0.001
        Training size:   610
        Validation size: 56
        Checkpoints:     True
        Device:          cpu
        Images size:     608
        Optimizer:       adam
        Dataset classes: 1
        Train label path:data/pytorch_yolov4_train.txt
        Pretrained:
    
Epoch 1/300:   0%|       | 0/610 [00:07<?, ?img/s]


無事動いた。

参考サイト

はじめての Google Open Images Dataset V6

OIDv4_ToolKit

M1 MacBook Air のsetupの記録

M1のMac、2021/04の時点で、brewはいかれてるは、tensorflowはinstallできないはで普通に使えない。
試行錯誤した時のメモ。


時系列順に実行した記録。


f:id:trafalbad:20210406003747j:plain

python3とpip3のinstall

python3.9にtensorflow非対応なので、python3.8に下げる。

まずIntelと混ざるのを防ぐため、brewのpython3を消す

$ brew uninstall --ignore-dependencies python3
$ python3 --version
>>>
Python 3.8.2

$ python3 -m pip install --upgrade pip --user
$ pip3 --version   
>>>>   
pip 21.0.1 from /Users/ha~/Library/Python/3.8/lib/python/site-packages/pip (python 3.8)


$ which python3
>>>>>
/usr/bin/python3


******pay attention
M1のMac(2021/04時点)では「/opt」以下にanacondaとかbrewがinstallされてます。M1 Macでのhomebrewは 公式のドキュメント で /opt/homebrew にインストールすることが推奨されています(Intel版との衝突を避けて共存のため)。



condaのinstall

まず普通にanacondaのdownload。

$ conda
>>>>
zsh: command not found: conda

エラー吐くクソ野郎なので、正常にinstallされてるか確認

$ /opt/anaconda3/bin/conda init zsh

エラーが出なければOK

$ /opt/anaconda3/bin/conda --version
>>>
conda 4.9.2


やっぱりこの類のエラーかよ。



めんどいのでcondaのショートカットを作成

オリジナルのコマンド「conde」でcondaを使えるようにした。

# コマンドを追加
$ sudo vi ~/.bashrc
$ sudo vi ~/.zshenv

>>>>>
alias conde='/opt/anaconda3/bin/conda'
# パスを通す
$ sudo vi ~/.bash_profile
>>>
source ~/.bashrc
source ~/.zshenv


$ source ~/.bash_profile
# 確認
$ conde --version
>>>>
conda 4.9.2


できた。


アーキテクチャの確認・切替

# 買った時のモード
$ uname -m
>>>>arm64
$ arch 
>>>>arm64


archコマンドでアーキテクチャの切替

$ arch -x86_64 bash

# Rosetta2(Intel)で動いている多分
$ arch ($uname -m)
i386
元に戻す。
$ arch -arm64 bash


tensorflowのinstall

pip3でinstallしたtensorflowを実行するとzsh illegal hardwareとエラーが出る。
もうcondaでinstallするしか、方法がわかりませんでした。

# pip3 でinstallしたtensorflowをuninstall
$ pip3 uninstall tensorflow

# condaでtensorflowをinstall
$ (/opt/anaconda3/bin/conda) conde install tensorflow

>>>>

Specifications:

  - tensorflow -> python[version='2.7.*|3.7.*|3.6.*|3.5.*']

Your python: python=3.8


python 3.6か3.5にしろと言われた。

$ (/opt/anaconda3/bin/conda) conde install python=3.6

$ (/opt/anaconda3/bin/conda) conde install tensorflow


実行できるか確かめる

# tf.py
import tensorflow
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.models import Model
from tensorflow.keras.layers import *
from tensorflow.keras.optimizers import RMSprop, Adam, SGD
$ python3 tf.py


エラーが出ないのでtensorflowをやっと実行できた。

****Pay attention
最初はM1 macbookairはpython3.9だったので、python3.9をサポートしてないtensorflowはpip3でinstallできませんでした。
最終手段でもうcondaでinstallするしかなかったです。



関連ライブラリもcondaでinstall (almost never pip3)

python3.6に下げたので、ライブラリの入れ直し。
pip3との混在を避けるためpip3でinstallするしかないライブラリ以外は、condaでinstallする

# opencvの例
$ (/opt/anaconda3/bin/conda) conde install -c conda-forge opencv


参考

M1 Mac+tensorflow-macosでディープラーニングする

M1 Mac買ったので行ったセットアップを書いていく

GCSの画像を使ってGCRのDockerコンテナでAI Platformのトレーニングジョブを動かす【MLops building2】

今度はGCSにuploadした画像を読み込んで自前スクリプトをDocker化して、GCRにpush。
それからAIplatformでトレーニングジョブを実行してみる。

おおおまかにここのチュートリアルを参考にしてDockerをGCRにpushしてから、トレーニングジョブを実行してみた。

まずは「AI Platform Training & Prediction, Compute Engine and Container Registry API 」を有効にしてから、
GCPVMインスタンス上でジョブを実行。


目次
1.GCSに画像をupload
2.環境変数の設定
3.トレーニング用pythonスクリプト
4.DockerコンテナをGCRにpushして作成
5.AI Platformでジョブを実行


1.GCSに画像をupload

AIPlatformでノートブックインスタンスを作成。regionはus-central1

ログインして、画像upload用のバケット「mlops-test-bakura」作成

$ gsutil mb gs://mlops-test-bakura/
>>>
Creating gs://mlops-test-bakura/...


画像をGUIでフォルダ「right」をupload

# GCSに「right」フォルダ内に画像があることを確認
$  gsutil ls gs://mlops-test-bakura/right/*.jpg
>>>
〜
gs://mlops-test-bakura/right/ml_670008765.jpg
gs://mlops-test-bakura/right/nm_78009843.jpg
gs://mlops-test-bakura/right/kj_78009847.jpg

2.環境変数の設定

# output用バケットの作成と環境変数の設定
export BUCKET_ID=output-aiplatform
gsutil mb gs://$BUCKET_ID/
export PROJECT_ID=$(gcloud config list project --format "value(core.project)")



3.トレーニング用pythonスクリプト

# コードをgit clone
$ git clone https://github.com/GoogleCloudPlatform/cloudml-samples
$ cd cloudml-samples/tensorflow/con*/un*/ 

# 訓練用スクリプト 
$ tree
>>>
├── Dockerfile
├── data_utils.py
├── model.py
└── task.py

ここで訓練用スクリプトを自分用に書き換える

model.py
自前ネットワーク

from tensorflow.keras import Sequential
from tensorflow.keras.layers import *
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model

def sonar_model():
    base_model = VGG16(weights='imagenet', include_top=True, input_tensor=Input(shape=(224,224,3)))
    x = base_model.get_layer(index=-5).output
    x = Dropout(rate=0.3)(x)
    x = GlobalAveragePooling2D()(x)
    o = Dense(3, activation='softmax')(x)
    model = Model(inputs=base_model.input, outputs=o)
    model.compile(loss='categorical_crossentropy', optimizer='sgd',
                  metrics=['accuracy'])
    return model



data_utils.py
さっきのGCSから画像を読み込む。

import datetime
from google.cloud import storage
import tempfile
import os, cv2
import numpy as np
from sklearn.model_selection import train_test_split
import tensorflow
from tensorflow.keras.utils import to_categorical


client = storage.Client()
BUCKET_NAME = "mlops-test-bakura"
FOLDER_NAME = "right"
NUM_CLS = 2+1


def load_label(y, num_classes=3):
    return to_categorical(y, num_classes=num_classes)
    
    
def download(bucket_name, folder_name):
    images = []
    labels = []
    c = 0
    for blob in client.list_blobs(bucket_name, prefix=folder_name):
        _, _ext = os.path.splitext(blob.name)
        _, temp_local_filename = tempfile.mkstemp(suffix=_ext)
        blob.download_to_filename(temp_local_filename)
        img = cv2.imread(temp_local_filename)
        images.append(cv2.resize(img, (224, 224)))
        if len(images)==200:
            c += 1
        elif len(images)==400:
            c += 1
        labels.append(c)
        #print(f"Blob {blob_name} downloaded to {temp_local_filename}.")
    return np.array(images)/255, np.array(labels)
    
    
def load_data(args):
    imgs, labels = download(BUCKET_NAME, FOLDER_NAME)
    labels = load_label(labels, num_classes=NUM_CLS)
    print(imgs.shape, labels.shape)
    train_f, test_f, train_l, test_l = train_test_split(
            imgs, labels, test_size=args.test_split, random_state=args.seed)
    return train_f, test_f, train_l, test_l
    
    
def save_model(model_dir, model_name):
    """Saves the model to Google Cloud Storage"""
    bucket = storage.Client().bucket(model_dir)
    blob = bucket.blob('{}/{}'.format(
        datetime.datetime.now().strftime('sonar_%Y%m%d_%H%M%S'),
        model_name))
    blob.upload_from_filename(model_name)


task.py

import argparse
import data_utils
import model


def train_model(args):
    train_features, test_features, train_labels, test_labels = \
        data_utils.load_data(args)

    sonar_model = model.sonar_model()

    sonar_model.fit(train_features, train_labels, epochs=args.epochs,
                    batch_size=args.batch_size)

    score = sonar_model.evaluate(test_features, test_labels,
                                 batch_size=args.batch_size)
    print(score)

    # Export the trained model
    sonar_model.save(args.model_name)

    if args.model_dir:
        # Save the model to GCS
        data_utils.save_model(args.model_dir, args.model_name)


def get_args():
    parser = argparse.ArgumentParser(description='Keras Sonar Example')
    parser.add_argument('--model-dir',
                        type=str,
                        help='Where to save the model')
    parser.add_argument('--model-name',
                        type=str,
                        default='sonar_model.h5',
                        help='What to name the saved model file')
    parser.add_argument('--batch-size',
                        type=int,
                        default=4,
                        help='input batch size for training (default: 4)')
    parser.add_argument('--test-split',
                        type=float,
                        default=0.2,
                        help='split size for training / testing dataset')
    parser.add_argument('--epochs',
                        type=int,
                        default=1,
                        help='number of epochs to train (default: 10)')
    parser.add_argument('--seed',
                        type=int,
                        default=42,
                        help='random seed (default: 42)')
    args = parser.parse_args()
    return args

def main():
    args = get_args()
    train_model(args)

if __name__ == '__main__':
    main()


Dockerfile

FROM tensorflow/tensorflow:nightly
WORKDIR /root
ENV DEBIAN_FRONTEND=noninteractive

# Installs pandas, google-cloud-storage, and scikit-learn
# scikit-learn is used when loading the data

RUN pip install pandas google-cloud-storage scikit-learn
RUN apt-get install -y python-opencv python3-opencv

# Install curl
RUN apt-get update; apt-get install curl -y
# The data for this sample has been publicly hosted on a GCS bucket.
# Download the data from the public Google Cloud Storage bucket for this sample
RUN curl https://storage.googleapis.com/cloud-samples-data/ml-engine/sonar/sonar.all-data --output ./sonar.all-data
# Copies the trainer code to the docker image.
COPY model.py ./model.py
COPY data_utils.py ./data_utils.py
COPY task.py ./task.py
# Set up the entry point to invoke the trainer.
ENTRYPOINT ["python", "task.py"]

3.DockerコンテナをGCRにpushして作成

次にGCRにDockerのカスタムコンテナを作成

# gcloudでdockerを認証
sudo docker run busybox date
gcloud auth configure-docker
# Docker iamgeのbuild
REGION=us-central1
export IMAGE_REPO_NAME=sonar_tf_nightly_container
export IMAGE_TAG=sonar_tf

# IMAGE_URI: the complete URI location for Cloud Container Registry
export IMAGE_URI=gcr.io/$PROJECT_ID/$IMAGE_REPO_NAME:$IMAGE_TAG
export JOB_NAME=custom_container_tf_nightly_job_$(date +%Y%m%d_%H%M%S)



# docker ビルド (docker build -t gcr.io/[project id]/[app]:latest .)
sudo docker build -f Dockerfile -t $IMAGE_URI ./

# 正常に動作してるか確認
sudo docker run $IMAGE_URI --epochs 1
>>>>
〜〜〜〜
553467904/553467096 [==============================] - 3s 0us/step
2021-03-13 03:46:29.032178: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimi
zation Passes are enabled (registered 2)
2021-03-13 03:46:29.032776: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2299995000 Hz
30/30 [==============================] - 103s 3s/step - loss: 0.2690 - accuracy: 0.8701 
8/8 [==============================] - 7s 847ms/step - loss: 4.4660e-04 - accuracy: 1.0000
[0.0004466005484573543, 1.0]

# imageをGCRにpush
# docker push gcr.io/[project id]/[app]:latest
sudo docker push $IMAGE_URI

GCRにdocker imageがpushされてる
f:id:trafalbad:20210303165654p:plain




4.AI Platformでジョブを実行

# ジョブを実行
$ gcloud components install beta
$ gcloud beta ai-platform jobs submit training $JOB_NAME --region $REGION --master-image-uri $IMAGE_URI --scale-tier BASIC -- --model-dir=$BUCKET_ID --epochs=1


# ジョブテータスとストリームログをモニタリング
gcloud ai-platform jobs describe $JOB_NAME
gcloud ai-platform jobs stream-logs $JOB_NAME

>>>>
〜〜〜〜〜
INFO    2021-03-03 07:28:04 +0000       master-replica-0                Test set: Average loss: 0.0516, Accuracy: 9
839/10000 (98%)
INFO    2021-03-03 07:28:04 +0000       master-replica-0
INFO    2021-03-03 07:30:30 +0000       service         Job completed successfully.
# GCSにモデルが保存されてるか確認
$ gsutil ls gs://$BUCKET_ID/sonar_*
>>>
gs://output-aiplatform/sonar_20210313_055918/sonar_model.h5

ちゃんとジョブが成功してGCSにh5の重みが保存されてた。




参考サイト

スタートガイド: カスタム コンテナを使用したトレーニング
AI Platform(GCP)でGPU 100個同時に使いテンションあがった
github:cloudML-sample