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

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

ultra96v2の環境の構築。仮想環境のubuntuとMacの外付けHDDをマウントするまで -VitisPlatform作りpart1【fpga, avnet】

・1.ultra96-v2動作に必要なもの
はじめてのUltra96 必要なもの」によると、Ultra96-v2に必要なものは、買った付属品以外に、
「Vitisプラットフォーム(vitis IDE)」という開発環境の構築

環境構築用OS ubuntu

が必要らしい。



・2.まず自分がやること
Ultra96-v2でAIを動かすことための「GPUが必要、仮想環境はダメっぽい」など面倒な課題はあとでなんとかするとして、

・大容量の外付けHDD

ubuntu環境

を用意して、とにかく「vitisプラットフォーム」を外付けHDDの中に構築することにした




・3.この記事でやること
そのために、自分がこの記事でやることは下の通り(仮想環境と外付けHDDをマウントするまでの流れ)。

mac上でVirtualBoxの仮想環境を構築

仮想環境にubuntuをインストール

mac側の大容量の外付けHDDとubuntuをマウント

(次記事で外付けHDDの中に「vitisプラットフォーム」を構築する)

この作業の備忘録をまとめてく。




作成するネットワーク全体像f:id:trafalbad:20200308114134j:plain



*注:Macを以下「ホストOS」、ubuntuの仮想環境を「ゲストOS」と表記。




目次
1.VirtualBoxの仮想環境にubuntuをインストールする

2.ホストOSからsshでログインするため、ゲストOS内でネットワーク設定

3.ホストOS側の外付けHDDをゲストOS側とマウント





1.VirtualBoxの仮想環境にubuntuをインストールする

ほぼ「MacにVirtualBoxでUbuntuを立てる方法【画像での解説つき】」の通りにやった。




1.VirtualBoxのインストール & ubuntuをダウンロード


VirtualBoxMac用のubuntu for desktopは以下からdownload。

ubuntuのバージョンは「Ubuntu Linux 16.04.5 LTS, 16.04.6 LTS, 18.04.1 LTS, 18.04.2 LTS (64 ビット)」のどれか。
ここでは18.04.2 LTS (64 ビット)を選択。

VirtualBox
VirtualBox

ubuntuの18.04.2 LTS (64 ビット)





2.VirtualBoxのネットワーク(ホストオンリーネットワーク)の作成


サイトを参考に、virtualboxでネットワーク設定(ホストオンリーネットワークで「vboxnet0」を作成)。

VirtualBox起動=>「ファイル」=>「ホストネットワークマネージャー」=>「vboxnet0」の作成と有効化。


f:id:trafalbad:20200308114212p:plain



次にVirtualBoxからネットワークの設定。「ネットワーク」のアダプター1と2を以下の図のように設定。

f:id:trafalbad:20200504230849p:plain


f:id:trafalbad:20200504230907p:plain


3.VirtualBox起動してubuntuのインストール



ubuntuのインストール画面では、容量を節約するため「最小のインストール」を選択した。

・「通常のインストール」:オフィスソフトウェアやミュージックプレイヤーなどが合わせてインストールされる

・「最小のインストール」:ソフトウェアのみがインストール


後はサイト通りにインストールすればOK。



ubuntuをインストール中
f:id:trafalbad:20200308114251p:plain

# 例:ubuntuアカウント
Your name: Ls
PC name: Ls-VirtualBox
User name: Ls
Pasward: Ls11


2.ホストOSからsshでログインするため、ゲストOS内でネットワーク設定


1.ubuntu内のターミナルでネットワーク設定


# vimとopenssh-serverのインストール
$ sudo apt-get install vim openssh-server

# ubuntuでadressの確認
$ ip address show


# /etc/network/interfacesの編集
$ sudo vi /etc/network/interfaces

# 下のように書き込んで、「esc+:wq」
auto enp0s3
iface enp0s3 inet static
address 192.168.56.104
netmask 255.255.255.0


その後、GUIで再起動

参考サイト:Ubuntu18.04だとifconfigコマンドが標準で使えない?






2.ホストOS側ターミナルからゲストOSにsshでログイン


さっきのubuntuアカウントのYour nameと、/etc/network/interfacesの編集で使ったaddressでログイン。

# ssh [アカウント名]@[address]
$ ssh Ls@192.168.56.104

>>>
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-28-generic x86_64)

* Documentation:  https://help.ubuntu.com
* Management:     https://landscape.canonical.com
* Support:        https://ubuntu.com/advantage

* Canonical Livepatch is available for installation.
  - Reduce system reboots and improve kernel security. Activate at:
    https://ubuntu.com/livepatch

70 個のパッケージがアップデート可能です。
57 個のアップデートはセキュリティアップデートです。

Your Hardware Enablement Stack (HWE) is supported until April 2023.

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.


ログインできた。


ここまでのネットワーク全体像f:id:trafalbad:20200308124910j:plain







3.ホストOS側の外付けHDDをゲストOS側とマウント


ホストOS側で大容量の外付けHDDを差し込んで、vitisをインストールする場所にした。

vitisのインストールには110GBくらい必要なので、大容量の外付けHDDを選択。



自分が選んだのは1TBある、BUFFALOの外付けHDD
f:id:trafalbad:20200308114408j:plain




最終的に「vitisプラットフォーム」構築は、

・ホストOS側とゲストOSをマウントする共有フォルダに外付けHDDを指定

・ゲストOS側(ubuntu環境)から外付けHDDの中に「vitisプラットフォーム」を作成する

という流れ。





1.ホストOS上でマウント用フォルダの作成


ホストOS上で、外付けHDDを差込み、中にマウント用ファルダ「ubuntu_mount」を作成。



外付けHDDの中身
f:id:trafalbad:20200308114441p:plain






2.VirtualBoxで外付けHDDの「ubuntu_mount」を「共有フォルダー」に設定




ubuntu_mount」のパスを確認。(外付けHDDのパスはディスクユーティリティから確認)

$ cd /Volumes/BuFFALO && ls

Xlinx        other        ubuntu_mount

$ cd ubuntu_mount && pwd
/Volumes/BuFFALO/ubuntu_mount


VirtualBox仮想マシンを選択=>「設定」=>「共有フォルダー」
=>右のフォルダマークをクリック=>下のようにパスとかを入れる


f:id:trafalbad:20200308114518p:plain

=>「"読み込み専用"のチェックを外す」=>「OK」クリック


f:id:trafalbad:20200308131749p:plain






3.「Guest addtions」をubuntuにインストール


ubuntuでマウントを完了するには「Guest addtions」のインストールが必要。


VirtualBoxからubuntuを起動して、上の「Decices」=>「Insert Guest Addition CD Image~」をクリック。


次に、ubuntu上の画面に表示された「Guest addtions」のソフトウェアを実行。

f:id:trafalbad:20200308114621j:plain


Returnをクリックしてインストール完了。
その後、GUIで再起動。


再起動した後に、ubuntuの「/media」に移動したところ、マウント用に作成されたフォルダ(sf_[マウントするフォルダ名])があって、ちゃんとマウントされてる。

$ cd /media && ls
sf_ubuntu_mount

***「Guest addtions」が入らない場合
ターミナルでこのコマンドを実行すれば、同じのが入る

sudo apt-get install virtualbox-guest-utils

参考:Unable to Install Guest Additions CD Image on Virtual Box



これで、ホストOS側の外付けHDDをゲストOS側とマウントするネットワークができた




完成図f:id:trafalbad:20200308124802j:plain




次記事でこの外付けHDDの「ubuntu_mount」の中に、ゲストOS側から「vitisプラットフォーム」を作ってく予定。





メイン参考サイト


VirtualboxのUbuntu ServerにOSXホストのフォルダをマウントする 

MacにVirtualBoxでUbuntuを立てる方法【画像での解説つき】

FPGAのultra96v2で「unboxingからLチカまで」の操作手順【avnet, hardware】

AIをカメラとかのハードウェア上で動かすために、ラズパイと同じFPGAField Programmable Gate Array)のUltra96-v2を購入した。

目的はハードでAIを使いたいから(≒ヒロアカのハイエンド脳無に惚れたから)。



FPGAでAIを動かすために必要な作業は下の図のようになり、今回は「SDカード作りの部分」をまとめた。


FPGAでAIを動かすために必要な作業f:id:trafalbad:20200229150230p:plain



SDカード作りは「unboxing(開封)してから、一通りUltra96-v2を動作させる(Lチカ)まで」で、その手順には


・ライセンス取得
・Micro SD cardイメージをSDカードに書き込む
wifiを通し、Lチカで動作確認


などが必要で、とりあえず作業で手順をまとめてく。

ultra96v2の構造



目次
1,ライセンス取得
2. Micro SD cardイメージをSDカードに書き込む
3.wifiを通し、Lチカで動作確認
4.linux上での一通りの操作


1,ライセンス取得

Ultra96-v2を買うと、XILINXのライセンス取得用の紙があるので用意。



XILINXのページにアクセスして、ユーザー名とパスワードを作成して、ログイン。


「Create New license」からvoucher code(ライセンス取得用紙の下のコード)を入力

f:id:trafalbad:20200229144631p:plain


そして、「OEM Zynq Ultra96 Vivado Design~Voucher pack」のライセンスを取得。

取得する際に、HostnameHost value idが必要。


・Hostname


linux上で

$ hostname

で確認



・Host value id


環境設定→「ネットワーク環境」で「内蔵Ethernet」を作成。

その後下の「詳細」→ハードウェア→で「MACアドレス」の12桁の英数字がhost value id

f:id:trafalbad:20200229144706j:plain

参考:MACアドレスの確認・IPアドレスの設定

またはこのコマンドを打てば、host IDがわかる。

$ /usr/sbin/netstat -I en0

ライセンスを取得するとライセンス確認用メールが届く。


再度ログイン後に、さっきのページで「Manege Licenseでlicense」から取得したライセンスを確認できてる。

f:id:trafalbad:20200229144740p:plain







2. Micro SD cardイメージをSDカードに書き込む


まず、MicroSDカードにイメージを書き込むために「etcher」をdownload。


次にMicro SD cardイメージのdownload

$ wget http://www.fpga.co.jp/AI-EDGE/ultra96v2_oob.img.zip 

etcherを起動後にinstall &解凍した「ultra96v2_oob.img」を開く。
MicroSD カードへのイメージの書き込みstart

f:id:trafalbad:20200229144838p:plain



書き込み完了

f:id:trafalbad:20200229144859j:plain







3. wifiを通し、Lチカで動作確認

Ultra96-v2の構成は以下のようになってる。


f:id:trafalbad:20200229144918p:plain




まず、microSDカードを取り出しultra96-v2に差し込む。
そしてSW4のスイッチを押す。

f:id:trafalbad:20200229144950j:plain



wifi名「ultra96-v2_ ~」が出てることを確認。


f:id:trafalbad:20200229145009j:plain

アクセスして、ブラウザから「http://192.168.2.1」にアクセス。



ultra96-v2の接続画面に入れた。

f:id:trafalbad:20200229145032j:plain


ここからのLチカの動作はこちらのyoutube動画を参照。

youtu.be






4.linux上での一通りの操作

とりあえず、例としてscpで必要なファイル転送が可能にしてみる。
パスワードは「root」を入力

$ ssh -l root 192.168.2.1
>>>
root@192.168.2.1's password: 
mount: /mnt: /dev/mmcblk0p1 already mounted on /run/media/mmcblk0p1.
mount: /mnt: /dev/mmcblk0 already mounted or mount point busy.
attempting to run /mnt/init.sh
/mnt
root@ultra96v2-oob-2019_2:~# 

これでUltra96-v2を購入してからLチカまでできるようになる。



次の記事ではコードを書いたり、FPGAを操作するIDEの「vitisプラットフォーム」を構築でき次第、記事にまとめようと思う。

f:id:trafalbad:20200229145519j:plain

Efficient-Unetで天気画像の予測(パターン認識)リベンジ【機械学習】

今回はこの前の記事で書いた「signateの天気コンペ」の優勝者(天気の専門家ガチ勢)が実装したモデルを、既存のデータでも予測できる構成で個人的に作ってみた。


convLSTMを使わずにUnetにしたのは「予測」タスクではなく、「パターン認識」タスクにしたかったから。


特定の気象条件なら、次の24時間はこういう画像になる」というパターン認識の形で次の24枚の天気画像を生成(予測)する。


目次
1.ネットワーク構成
2.ネットワークに使った技術
3.予測した画像
4.他の上位入賞者のテクの備忘録




1. ネットワーク構成


優勝者の使ったモデル



・自分で天気の特徴量を350こ作った

・ハイスペックマシンで計算可能な環境を持っていた

efficient-Netという最高精度の画像分類ネットワークを使ったencoderを2つ使ったUnet

hypercolumnの使用

Feature Pyramid Attention(FPA)の使用


という点が特徴で下のネットワーク構成
f:id:trafalbad:20200223012634j:plain




自分が作ったモデル




自分が目をつぶったポイント

・特別な特徴量を作成しない(面倒くさい、ガチ勢ちゃうから)

gooogle colabで計算できる計算量(金かけたくない)



上のポイントでケチったため、自分なりにかなり近い形の、下図のネットワークを作成した(encoderのinputは予測する一時間前の画像(直前の画像))

f:id:trafalbad:20200223012544j:plain

encoder input予測直前の画像(前日の23時の画像)
met input前日の3時間ごとの風の特徴量8こ
sat input前日の3時間ごとの天気画像8枚







2. ネットワークに使った技術

1.最後の出力層のoutput channelを24にして、セグメントタスクと同じ形式にした



out_channels = 24
o = Conv2D(out_channels, (1, 1), padding='same', kernel_initializer=conv_kernel_initializer)(o)




2. sigmoidを入れて、生成画像のブレをなくす


met-inputとsat-inputをdecoderに注入してるので、sigmoidで単位を合わせてあげなければ、画像がチャけた白黒テレビのようにブレブレになる。


f:id:trafalbad:20200223013111j:plain


sigmoidとhypercolumnの組み合わせで、ぶれを解消





3. PSP moduleの使用


PSP moduleはセグメントで有名なPSPNetで使われているモジュール。


PSPNetはEncoderにResNet101を使い、EncoderとDecoderの間にPyramid Pooling Module(PSP module)を追加している。

f:id:trafalbad:20200223012742p:plain


PSP moduleはhypercolmunと、注入したデータ(met-input, sat-Input)に適用したら、全てのケースで精度があがった。





4. 天気の特徴量(met-input, sat-input)をdecoderに挿入


met-input(風の特徴量), sat-input(天気画像)を全DecoderのConvtranspose層に挿入

# LambdaでreshapeしないとNonetypeエラーが出る
def psp_layer(inputs, skip_h, skip_w):
    inputs = PyramidPoolingModule()(inputs)
    output = Lambda(lambda x:tf.image.resize_images(x, size=(skip_h, skip_w), method=tf.image.ResizeMethod.BILINEAR))(inputs)
    return output



# encoderで挿入するより、直接Decoderに挿入の方がいい(やりやすい、早い、便利)。
def Conv2DTranspose_block(filters, kernel_size=(3, 3), transpose_kernel_size=(2, 2), upsample_rate=(2, 2),
                          initializer='glorot_uniform', skip=None, met_input=None, sat_input=None):
    def layer(input_tensor):
        x = Conv2DTranspose(filters, transpose_kernel_size, strides=upsample_rate, padding='same')(input_tensor)
        
        if skip is not None and met_input is not None:
            h, w = skip.get_shape().as_list()[1:3]
            met_output = psp_layer(met_input, h, w)
            sat_output = psp_layer(sat_input, h, w)
            x = Concatenate()([x, skip, met_output, sat_output])
        elif skip is not None and met_input is None:
            x = Concatenate()([x, skip])

        x = channel_spatial_squeeze_excite(x)
        return x

    return layer






5. FPA(Feature Pyramid Attention)


FPAはCNNで抽出した汎用特徴量の細かい部分まで補ってくれる。

セグメンテーションタスクだと細かい部分のピクセルまで再現できるので、セグメンテーションタスクでも使ってる例は多い。

f:id:trafalbad:20200223093005j:plain




FPAを使うときは以下の点に注意

・FPAを使うときは 画像サイズは shape=(256, 256)かそれ以上のshapeの必要がある

計算量が多くなる

今回は計算量の関係で使わなかったが、試しに使ってみると精度はかなり良くなった。




FPAのセグメンテーション使用例
f:id:trafalbad:20200223012840p:plain






6. SCSE(Concurrent Spatial and Channel Squeeze & Excitation)



SCSEはすでに出回ってる既存モデル(resnet50とかその他多数)に計算量をそこまで増やさず & 簡単に組み込むことができる。

f:id:trafalbad:20200223012859p:plain

特にセグメンテーション系などのタスクで性能を向上させることができる。


実際、DecoderのConvolution層の代わりに、SCSEを使ったら、計算量が減り、精度が上がった。






7. hypercolumn


hypercolumn」はConvolution層の出力を組み合わせて精度を上げる手法。

f:id:trafalbad:20200223012923p:plain


ベターな組み合わせは「Convolutional hypercolumns in Python」に詳しく載ってる。


今回のベストプラクティスは「decoder1とdecoder5」だった。

なるべく対照的な位置のlayerを組み合わせると良いらしい。

def interpolation(inputs, interpolation_shape):
    patch_size = np.multiply(inputs.get_shape().as_list()[1:3], interpolation_shape)
    inputs = PyramidPoolingModule()(inputs)
    output = Lambda(lambda x: tf.image.resize_images(x, size=patch_size, method=tf.image.ResizeMethod.BILINEAR))(inputs)
    return output

# decoder のhypercolumnの部分
if hypercolumn:
        o = Lambda(lambda x: tf.concat([x, d5], axis=3))(o)



8.精度指標


精度指標はなるべく多く使い(MSE, PSNR, SSIM)、多角的にモデルの性能を判断した。

from sklearn.metrics import mean_absolute_error
from skimage.measure import compare_ssim, compare_psnr
def measurement(func, **kwargs):
    val = func(kwargs["img1"], kwargs["img2"])
    return val

maes= 0
psnrs = 0
ssims = 0
for idx, (pr, im) in enumerate(zip(y_pred, test)):
  maes += mean_absolute_error(pr, im)
  psnrs += measurement(compare_psnr, img1=im, img2=pr)
  ssims += measurement(compare_ssim, img1=im, img2=pr)

print('total mae', maes/24)
print('total psnr', psnrs/24)
print('total ssim', ssims/24)


参考:MSE/PSNR vs SSIM の比較画像紹介






予測した画像

前回のと比べるとかなり良くなった

【GroundTruth】
f:id:trafalbad:20200223013020g:plain




【自分が予測した画像】
f:id:trafalbad:20200223013037g:plain

total mae 0.04931812573362152
total psnr 22.823581818309222
total ssim 0.7469266660346182



4. 他の上位入賞者のテクの備忘録


・好奇心でやってる人がほぼ全員、義務感でやってる奴はほぼ下位

画像サイズを変更して、同じネットワークでアンサンブル学習する。

efficient-Netのb4とかb5とかでうまくネットワーク変えながら、アンサンブルしてた。

metaデータからうまく天気と関連した特徴量を作成(マイナスの値を0に置換して、風の向きをうまくプラスのベクトルのみで表現してた)

論文から実装が多く、特にネットワークに変更は加えてない(PredRNN++とか)

・セグメントタスクみたいにチャンネルを24にして予測する手法はみんなしてた。

データ量は少なくして(1 batchとか)、爆速でPDCA回す。

精度指標は多めにして多角的視点から精度評価してた




参考サイト


hypercolumn参考GitHub

FPAを使用したUnetの参考GitHub

エイリアスを設定してlinuxのショートカットコマンドを作成する(linuxカスタマイズ)

エイリアスを設定して、長ったらしいlinuxコマンドのショートカットコマンドを、自分なりにカスタマイズして作成したのでその備忘録。

これで面倒なコマンドはいちいちググらずに済む。


f:id:trafalbad:20200210103255j:plain


目次
1. .bashrcと.zshenv にエイリアスを記述する
2. .bash_profileで.bashrc(と.zshenv)を読み込む設定をする
3. 実験
4. まとめ



1. .bashrcと.zshenvにエイリアスを記述する

自分のPCの場合、anaconaをインストールしてたためか、エラー

zsh: command not found


が出るので「.zshenv」ファイルにも同じことを書く必要があった。

まず試しにファイル数をカウントするコマンドで

alias count='ls -1 | wc -l'

を書いてみる。

# .bashrc(と.zshenv)ファイルをvimで開く
$ vi ~/.bashrc  (vi ~/.zshenv)


# 「i」で入力モードになるので入力モードで
$ alias count='ls -1 | wc -l'

と入力後「esc+:wq」で入力モードを終了。





2. .bash_profileで.bashrc(と.zshenv)を読み込む設定をする

エイリアスは.bash_profileに.bashrc(と.zshenv)を読んでもらう必要がある。


.bash_profileはターミナルが起動すると読み込まれる仕組みなので、ターミナルを起動するたびに読み込んでもらう。つまりショートカットコマンドを永久に保存できる。

$ vi ~/.bash_profile
# 以下を入力
source ~/.bashrc
source ~/.zshenv


と記述。この変更自体も反映させなければいけないので、ターミナルから以下を実行

$ source ~/.bash_profile


自分のPCはanacondaの変なコマンドが書いてあるので、消去してまっさらな状態で上のコマンドだけ書いて実行。







3.実験

実験1



# desktopに移動コマンドのショートカット作成
$ vi ~/.bashrc (vi ~/.zshenv)
alias desk='cd && cd desktop'

# 反映
$ source ~/.bash_profile

# 実行
$ desk
$ pwd 
>>>> /Users/~/desktop

移動できた





実験2



# ファイル数カウントコマンドのショートカット作成
$ vi ~/.bashrc (vi ~/.zshenv)
$ alias count='ls -1 | wc -l’

# 反映
$ source ~/.bash_profile

# 実行
$ count
>>>> 6


できた。





4.まとめ

エイリアス設定の利点はたくさんあるけど、下の2つがでかい。

・いちいち調べてたlinuxコマンドを調べる手間が減った
・長いlinuxコマンドを入力せずに簡単に実行できた。

これが生産性向上かと思う瞬間だった気がした。


# vi ~/.bashrcと~/.zshenvの中身
alias count='ls -1 | wc -l'
alias desk='cd && cd desktop'
alias down='cd && cd downloads'
# vi ~/.bash_profileの中身
source ~/.bashrc
source ~/.zshenv




参考サイト



ターミナルをいじってるとzshが「command not found」と叫ぶので、叫ばないようにした

conda activate の CommandNotFoundError への対処方法

【初心者向け】エイリアスの設定方法

学習済みのYOLOv3でオリジナルデータに転移学習(finetune)【物体検出】

オリジナルのデータセットにYOLOv3を使って物体検出した。
一から学習せずに、COCOデータセットの学習済みYOLOv3で転移学習してみたのでその備忘録


目次
1.オリジナルデータセットのclasses.txtと学習済みモデルの作成
2.訓練
3.学習結果


1.オリジナルデータセットのclasses.txtと学習済みモデルの作成

オリジナルデータセットはとあるコンペの公開画像を使用。

データセットformat(classes.txt)


[image_path, x1, y1, x2, y2, clas_id] で入力

anno_path = 'dataset_path'
fname = [path for path in os.listdir(anno_path)]
sorted_file =[path for path in natsorted(fname)]

df=[]
for idx, f in enumerate(sorted_file):
    fname, save,  b, cls_id = convert_annotation(anno_path, f)
    img = cv2.imread(fname)
    df.append([save, b[0], b[1], b[2], b[3], cls_id])

with open('classes.txt', 'w+') as f:
    for d in df:
        f.write(','.join(map(str, d)) + '\n')


classes.txt

Car
Pedestrian
Truck
Signal
Signs
Bicycle




学習済みモデル作成手順



kerasのYOLOv3を参考にdarknet53のweightの代わりに、COCO datasetの学習済みのYOLOv3のweightを使用

# Keras versionのYOLOv3をgit clone

$ git clone https://github.com/tanakataiki/keras-yolo3

# 学習済みmodelをdownloads
$ wget https://pjreddie.com/media/files/yolov3.weights

# 変換
$ python convert.py yolov3.cfg yolov3.weights yolo.h5

サイトに書いてあるとおり。






train.pyで使用

DARKNET_PATH = 'yolov3.h5'

def create_tiny_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
            weights_path=DARKNET_PATH):
    '''create the training model, for Tiny YOLOv3'''
    K.clear_session() # get a new session
    image_input = Input(shape=(None, None, 3))
    h, w = input_shape
    num_anchors = len(anchors)

    y_true = [Input(shape=(h//{0:32, 1:16}[l], w//{0:32, 1:16}[l], \
        num_anchors//2, num_classes+5)) for l in range(2)]

    model_body = tiny_yolo_body(image_input, num_anchors//2, num_classes)
    print('Create Tiny YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))

    if load_pretrained:
        model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
        print('Load weights {}.'.format(weights_path))
        if freeze_body in [1, 2]:
            # Freeze the darknet body or freeze all but 2 output layers.
            num = (20, len(model_body.layers)-2)[freeze_body-1]
            for i in range(num): model_body.layers[i].trainable = False
            print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))

    model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
        arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.7})(
        [*model_body.output, *y_true])
    model = Model([model_body.input, *y_true], model_loss)

    return model




2.訓練

optimizerとcallbackメソッド

Adam(lr=1e-3)

ReduceLROnPlateau

ModelCheckpoint

model.compile(optimizer=Adam(lr=1e-3), loss={'yolo_loss': lambda y_true, y_pred: y_pred})

reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
checkpoint = ModelCheckpoint('drive/My Drive/ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
        monitor='val_loss', save_weights_only=True, save_best_only=True, period=1)


学習曲線は怖いくらい順調に下がり、ログをとってないけどほぼ下の図のように降下。汎化性能も抜群


学習曲線図

f:id:trafalbad:20200118163451j:plain


YOLOv3はネットワークの構造上、大きい物体を学習しておけば、推論時には小さい物体も検知できるので、学習時には大きい物体のみくっきり写ってる「良質かつ大量」のデータセットで学習


yolov3は構造上小さい物体も検知できる

f:id:trafalbad:20200118163430p:plain




3.学習結果

予測した画像は下の感じ。かなりいい感じに検出できてる


明るい画像



f:id:trafalbad:20200118163531j:plain



f:id:trafalbad:20200118163528j:plain


夜の暗い画像


f:id:trafalbad:20200118163550j:plain


f:id:trafalbad:20200118163554j:plain


学習時に気をつけたことは

大きい物体がくっきり写ってる良質かつ大量のデータセットで学習(小さい物体は学習させてない)


optimizerはSGDではなくAdamに変更

callbackメソッドでval_lossの調整が意外に効いた

公開されてる学習済みモデルで転移学習はコスパ的にかなり秀逸


今回はdarknet53の重みで一から学習せずにYOLOv3で転移学習してみた。faster-RCNNより小さい物体の検出対策がやりやすいので好きになった。



一から学習するより公開済みの学習ずみモデルで転移学習すれば、ほとんどのモデルで予想以上の成果でそう。


参考サイト


A Closer Look at YOLOv3

Kaggle 2018 Google AI Open Images - Object Detection Track

HOG+Adaboostで物体検出(object detection)【機械学習】

HOGとの組み合わせはSVMが有名だけど、今回はSVMじゃなく「HOG+AdaBoost」を使った。

どっちも精度は同じくらいだけど、AdaBoostの方が2回目以降精度が増すし、汎用性が高いのが特徴。


目次
・訓練データ・テストデータの収集
・前処理
HOG+AdaBoostで物体検出


訓練データ・テストデータの収集

あらかじめバウンディングボックスの座標がアノテーションされた画像から、車とそれ以外のものをクロップして

・Positiveサンプル(車):1500枚 
・negativeサンプル(車以外): 1500枚

を集めた。

車がちょうどよく入る大きさのにresize(300px)。Sliding windowサイズはリサイズしたサイズより大きめ(400~600px)にとった。

サンプルが多く、綺麗に物体が写ってるほど精度も良かった。

# trainと精度
print("HOG classifier AdaBoosting...")
ada = AdaBoostClassifier(n_estimators=100, random_state=0)
ada.fit(trainData, trainLabels)

print("HOG classifier Evaluating on test data ...")
predictions = ada.predict(testData)
print(classification_report(testLabels, predictions))

>>>>>>
"""
 Evaluating classifier on test data ...
              precision    recall  f1-score   support

           0       0.91      0.92      0.91       375
           1       0.90      0.88      0.89       301

    accuracy                           0.90       676
   macro avg       0.90      0.90      0.90       676
weighted avg       0.90      0.90      0.90       676
"""



前処理

基本的に前処理は以下の流れ

1. trainの時にHOG特徴量が検出しやすいように画像を明るくする

2. testや本番で使う画像には前処理かけない(明るくしない)



明るくする方法はいくるかあるけどgammaを使ったコントラストは上手くいかない、しかも精度を下げる結果に。

def ganmma(img, gamma = 3.0):
    lookUpTable = np.zeros((256, 1), dtype = 'uint8')
    for i in range(256):
        lookUpTable[i][0] = 255 * pow(float(i) / 255, 1.0 / gamma)

    img_gamma = cv2.LUT(img, lookUpTable)
    return img_gamma

なので明るくするために、単純にRGBごとに明るくして、不純物を極力含めないようにした。

def equalize(img):
    for j in range(3):
        img[:, :, j] = cv2.equalizeHist(img[:, :, j])
    return img






HOG+AdaBoostで物体検出

HOG特徴量を使った物体検出の仕組みは、sliding windowを順番に画像内を走らせて、検出できた領域をバウンディングボックスで囲む。



f:id:trafalbad:20200112185716p:plain

def sliding_window(image, stepSize, windowSize):
    # slide a window across the image
    for y in range(0, image.shape[0], stepSize):
        for x in range(0, image.shape[1], stepSize):
            # yield the current window
            yield (x, y, image[y: y + windowSize[1], x:x + windowSize[0]])



検出結果



Sliding windowサイズを大きめ(400〜600px)にして、検出後にHOG特徴量用の300pxにリサイズした方が検出結果がよかった。

ベストパフォーマンスはWindow size = (500, 500) にResize size = (300, 300)




Window size = (500, 500) Resize size = (300, 300)のとき(ベストパフォーマンス)
f:id:trafalbad:20200112185839j:plain





Window size = (400, 400) Resize size = (300, 300)のとき
f:id:trafalbad:20200112185902j:plain




Window size = (600, 600) Resize size = (300, 300)のとき
f:id:trafalbad:20200112185918j:plain







物体検出以外の使い方



HOGでの物体検出は検出領域をバウンディングボックスで囲む以外にも、「エッジ検出」したり、「輪郭抽出」したりといろんな使い方ができる。

HOGでのsliding windowでの用途は多様なので、yolov3とかfaster-RCNNの物体検出ニューラルネットワークの「補助的な役割」として使うくらいがお利口な使い方かと思った。


バウンディングボックスで囲む部分のエッジ検出

def rgb_to_gray(src):
    b, g, r = src[:,:,0], src[:,:,1], src[:,:,2]
    return np.array(0.2989 * r + 0.5870 * g + 0.1140 * b, dtype='uint8')

def edge_detection(img):
    img_hist_eq = img.copy()
    for i in range(3):
        img_hist_eq[:, :, i] = cv2.equalizeHist(img_hist_eq[:, :, i])
    # img_gray = cv2.cvtColor(img_hist_eq, cv2.COLOR_BGR2GRAY) 
    img_gray = rgb_to_gray(img_hist_eq)
    canny_img = cv2.Canny(img_gray, 50, 110)
    backtorgb = cv2.cvtColor(canny_img,cv2.COLOR_GRAY2RGB)
    return backtorgb


f:id:trafalbad:20200112185949j:plain






バウンディングボックスで囲む部分の輪郭抽出

def extract_contours(img):
    imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(imgray,127,255,0)
    image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

    contour_img = cv2.drawContours(img, contours, -1, (0,255,0), 3)
    return contour_img

f:id:trafalbad:20200112190007j:plain






発展系 joint-HOG


単なるHOG特徴量だけを使うのではなく、ピクセルごとのHOG特徴量(の勾配)の組み合わせを使った「joint-HOG」もある。



joint-HOGの検出例
f:id:trafalbad:20200112190051p:plain


下のように、仕組みを真似して自分なりに組んでみたけど特に精度が良くなるわけではなかった。

from skimage.feature import hog
from skimage import data, exposure

# 輝度勾配を計算
def hog_gradient_img(image):
    fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
                        cells_per_block=(1, 1), visualize=True)

    hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))
    return hog_image_rescaled


# 勾配画像からjoint HOGを作成
def joint_hog(hog_img, threshold = 0.1):
    joint_fd = []
    fd = hog_img.flatten()
    for y in fd:
        for x in fd:
            if y-x > threshold:
                bit = 0
            else:
                bit = 1
            joint_fd.append(bit)
    return np.array(joint_fd)

組み方がよくないのは確かなので、正規のやり方でやればいい結果が出ることは間違いない。


HOGは決まったサイズの画像にしか適用できない上に、sliding windowを使うと計算コストもバカ高いので、物体検出はやっぱニューラルネットワーク一択だなと実感した。

ただニューラルネットワークのための画像前処理としては十分使えるのは間違いない。
joint-HOGは時間がなくてガチなのは組めなかったけど、qiitaとかで注目されてきたらやってみたい




参考サイト



【joint-HOG
第20回LSIデザインコンテスト・イン沖縄 設計仕様書 - 3-1
python-hog(github)

Joint HOG 特徴を用いた 2 段階 AdaBoost による車両検出

局所特徴量と統計学習法による物体検出



HOG物体検出参考サイト】
Opencvチュートリアル

python-bow-hog-object-detection

GitでPR(pull request)を投げるまでと、チームでgitでのコード管理ログ

gitコマンドでbranchを作成してPR(Pull Request)を投げるなど、チームでコード管理したのでその備忘録。


git cloneしてPRを投げるまでの branchの流れ
f:id:trafalbad:20200107205057j:plain





目次
1. PRを投げるまで
2.PR作成作業
3.キレーなコードの書き方

PRを投げるまで

# コンソールのコマンドいじるファイル操作
$ source ~/.bashrc

macOSならこのファイルにいろいろ書き込むことでコンソールのコマンドを自分好みにカスタマイズできる。

# まずgitでコードをクローンする
git clone [URL] && cd [dirctory]

# 新しいブランチ作成(必ず[origin/master]を入れる)
$ git checkout -b refactorer origin/master
(git checkout -b [new branch] [current branch])

>>>
Branch 'refactorer' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'refactorer'

変更が反映された。




# 今いるbranchを確認
$ git branch
>>>
   master
* refactorer


# ステージング(git commitするときのファイルのリスト)の変更 →index.htmlを変更。commmit すると変更される

$ git add index.html



# git addでの変更内容の確認
$ git status

>>>
On branch refactoring_hagi
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified:  index.html

no changes added to commit (use "git add" and/or "git commit -a")


# コミット
$ git commit -m “コメント”

>>>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:

git config --global --edit

After doing this, you may fix the identity used for this commit with:

git commit --amend ―reset-author

1 files changed, 183 insertions(+), 184 deletions(-)
rewrite index.html(88%)


# originにrefactorerをpushする
$ git push origin refactorer




新しいbranchができたので、PRを作成
f:id:trafalbad:20200107205151p:plain




※※※※※branch 削除用コマンド

git branch -D [ブランチ名]




PR作成作業


GUIでbranchを変更する場所



f:id:trafalbad:20200107205225p:plain

「default」がついてる branchがgit cloneしてきたときの branchになる。



Pull Request(PR)出すページ



f:id:trafalbad:20200107205249j:plain


・「Able to merge」が出てれば、即mergeできる

・PRの対象者は「Reviewers」から選択・変更できる





Reviewersを変更


f:id:trafalbad:20200107205313p:plain


ちなみに

・ファイルごとにPR投げる

ファイルごとに branchを作る必要あり


PRは、ファイルの変更ごとに投げたり、全体の変更を一括で変更するため投げたりできる。







gitでコード管理するときのserverとlocalとのおとまかな関係図
f:id:trafalbad:20200107205341j:plain


.gitはローカルにある隠しファイル的な存在で、表には出てこない。


・fetchコマンドはローカルにある".git"を変更

・mergeコマンドはPR後に行うやつで、serveも".git"の変更を反映する




branchを作ることでチームでのコード管理が簡単にできた。







キレーなコードの書き方

・関数は動詞+名
→何がされて何が返ってくるかを名前を反映させる


・変数名の付け方は「be 動詞 = 主語」
→ 例 is_completed_lv = 処理(主語)


・同じ動作が多い場合は減らす


・Ifの多さ
ネストが3は深いので4以降NG。関数化すればネストはなくなる


・1箇所変更するだけで、何箇所も同時に変更できる書き方
→ もし10箇所エラー、 1箇所直すだけで済む



・キレー・読みやすい
→元々英語で読むため、コードを読んでどんな処理が行われているかわかること



参考サイト


よく使うgitコマンド

Gitでローカルブランチを削除する

Pythonの命名規則まとめ