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

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

コーディング試験用基礎問 from HackerRank

HackerRank Interview Preparation Kit

f:id:trafalbad:20210127054815j:plain


Type : Array

Arrays: Left Rotation

Explanation
When we perform left rotations, the array undergoes the following sequence of changes:

Sample Input

5 4
1 2 3 4 5

Sample Output

5 1 2 3 4


Solution

def rotLeft(a, d):
    return a[d:] + a[:d]

2D Array - DS

6×6のarrayのうちhourglassは下の位置要素で16こ存在する。

a b c
  d
e f g

maximum hourglass sumを求めよ
Solution

def hourglass_sums(arr):
    sums=[]
    for w in range(4):
        for h in range(4):
            hourglass = arr[h][w]+arr[h][w+1]+arr[h][w+2]+arr[h+1][w+1]+arr[h+2][w]+arr[h+2][w+1]+arr[h+2][w+2]
            sums.append(hourglass)
    return max(sums)


New Year Chaos



Sample Input

STDIN       Function
-----       --------
2           t = 2
5           n = 5
2 1 5 3 4   q = [2, 1, 5, 3, 4]
5           n = 5
2 5 1 3 4   q = [2, 5, 1, 3, 4]

Sample Output

3
Too chaotic

Solution

def minimumBribes(q):
    bribes = 0
    q = [i-1 for i in q]
    # reverse loop
    for i in range(len(q)-1,-1,-1):
        if q[i] - i > 2:
            print('Too chaotic')
            return
        # get specified value in loop
        for j in range(max(0, q[i] - 2),i):
            if q[j] > q[i]:
                bribes+=1
    print(bribes)

Type:Dictionaries and Hashmaps

Two Strings

Sample Input

2
hello
world
hi
world

sample output

YES
NO

Solution

def twoStrings(s1, s2):
    for s in s1:
        if s in s2:
            return 'YES'
    return 'NO'


Count Triplets

For example, sample input

len=5 ratio=5
1 5 5 25 125

Sample Output

4

The triplets satisfying are index (0, 1,3), (0,2,3), (1,3,4), (2,3,4)

Solution

from collections import Counter

def countTriplets(arr, r):
    r2 = Counter()
    r3 = Counter()
    count = 0
    for p in arr:
        if p in r3:
            count += r3[p]
        if p in r2:
            r3[p*r] += r2[p]
        r2[p*r] +=1
    return count

type:Sorting

Mark and Toys

Prices = [1, 2, 3,4 ]
k=7

The budget is 7 units of currency. He can buy items that cost [1, 2, 3]for 6, or [3, 4]for 7units. The maximum is 3 items.
Sample input

7 50
1 12 5 111 200 1000 10]

Sample outout

4

He can buy only 4 toys at most. These toys have the following prices: .[1, 12,5, 10]

Solution

def maximumToys(prices, k):
    total = 0
    count = 0
    prices = sorted(prices)
    for p in prices:
        if p+total <= k:
            total += p
            count += 1
        else:
            return count

Fraudulent Activity Notifications

Sample Input 1

lens=5 days lens=4
1 2 3 4 4


Sample Output

0

There are 4 days of data required so the first day a notice might go out is 5 day . Our trailing expenditures are [1,2,3,4] with a median of The client spends 4 which is less than 2✖️2.5(median of [1,2,3,4]) so no notification is sent.

Solution

import bisect as bs
def index(arr, x):
    return bs.bisect_left(arr, x)


def median(days, d):
    half = len(days)//2
    if d%2==0:
        med = (days[half]+days[half-1])/2
    else:
        med = days[half]
    return med

def activityNotifications(expenditure, d):
    notifications = 0
    days = sorted(expenditure[:d])
    for i in range(d, len(expenditure)-1): 
        med = median(days, d)
        if expenditure[i]>=med*2:
            notifications+=1
        del days[index(days, expenditure[i-d])]
        idx = bs.bisect_left(days, expenditure[i])
        days.insert(idx, expenditure[i])
    return notifications


Greedy Algorithms

Minimum Absolute Difference in an Array

Given an array of integers, find the minimum absolute difference between any two elements in the array.


Sample input

5
1 -3 71 68 17

Sample output

3

Explanation
The minimum absolute difference is |71-68|=3


Solution

def minimumAbsoluteDifference(arr):
    arr = sorted(arr)
    minabs = abs(arr[0] - arr[1])
    for i in range(0, len(arr)-1):
        if abs(arr[i] - arr[i+1])<minabs:
            minabs = abs(arr[i] - arr[i+1])
    return minabs

弟4回エッジAIコンペ(セグメンテーション) レポート・log【ハードウェア】

SIGNATEの第4回AIエッジコンペに参加したので、そのレポートもかねたログを書こうと思う。

機械学習だけじゃなくて、ハードウェアもガチのコンペでした。


目次
1.ネットワークについて
2.C++のアプリケーションコードの工夫について
3.ハードウェアプラットフォームについて


1.ネットワークについて

1.1 使ったmodelと戦略

ModelはカスタマイズしやすいUnetを使った。ライブラリはkerasとtensorflowで、量子化前の変換作業のために以下のversionを使用。

Keras==2.2.4
・tensorflow-gpu==1.13.1


Unetを選択したのはpretrainからfinetuneへのネットワークのカスタマイズとか、精度向上のためのカスタマイズがしやすかったから。

深さは512。処理速度が遅くならないようにモデル容量を少なめにしたので、メモリサイズは「14,067,237」。

このモデルでベンチマークを超える戦略をとった。

理由はこれでベンチマークを越えられれば、工夫・処理速度とかで、他の参加者とかなりの差別化になって、アドバンテージがとれると思ったから。

Yolov3とのモデルサイズの参考比較

Yolov3 62,002,753
Yolov3-tyny 8,861,918
今回のUnet 14,067,237


深さ512と1024の容量の比較

深さ  容量
512 14,067,237
1024 31,055,557

f:id:trafalbad:20201226171430p:plain
今回のUnetのネットワーク図



他には深さ1024(メモリサイズ:31,055,557)にpruningなどの軽量化テクを使う方法も考えた。

あと、今回のコンペは、セグメンテーションタスクや前処理とかで、ハードウェアのPS側の演算も多くなると考えたので、softmaxを最終層に使った。

このおかげでハードウェア側でsoftmax演算IPを使って、DPUの使用率を多くできた。






採用しなかったアプローチ


採用しなかったアプローチは1024以上の深さのmodelを作り、pruningやDistillation(蒸留)でmodelを軽量化するアプローチ。


このアプローチはpruningやDistilliationなどの技術がハードウェア特有の色が濃いため、習熟度・難易度の面で時間的・開発コストがかかりすぎる(独学だと時間的にきつい)。


軽量化しないと、深さ1024のmodelはメモリが30,000,000以上になって処理速度に如実に反映されるので、この戦略は使わなかった。







1.2 コンパイル時のエラー対策を考慮したネットワーク構成のポイント

量子化の直前・直後で精度劣化やエラーになるレイヤー構成が存在したので、それらを除外してネットワークを構築した。

改善した点


1.「Conv2D => BatchNormarization(BN) => relu 」の順番のレイヤー構成の厳守


NG構成は「relu=>BN」で、コンパイル時エラーになる

x = Conv2D(filters = n_filters, kernel_size = (kernel_size, kernel_size),
            kernel_initializer = 'he_normal', padding = 'same')(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)


2. Decoder側にDropoutを使う



Decoder側(ConcatenateレイヤーやAddレイヤーを使う場所で)BNを使うと量子化後の精度劣化につながる。



3. softmaxの前にConv2Dを使わずに、Conv2DTransposeを使った


今回はsoftmaxを使ったので、量子化するためには、softmax前にConv2Dレイヤー以外(Conv2Dtranspose, separateconv2D とか)を使う必要があった。








1.3 DPUと連携を考えてsoftmaxを使った

今回はハードウェアで、softmax演算IPを使えるように、unetでも最終層にsoftmaxを使った。

softmaxを使ったおかげで次のポイントが利点になった。

・DPUの使用率を増やせる

・マルチスレッドで、PL, DPU演算, softmax演算の3つを並行処理できる

・sigmoidやreluよりsoftmaxの方が精度が高い




SoftmaxをUnetで使うための条件


vitisのDPUだと、SoftmaxをUnetで使う中で、試行錯誤の過程から以下のことが分かった。



コンパイル時の制約として、softmaxの直前のレイヤーはConv2D以外(Conv2Dtranspose, SeparateConv2Dなど)
を使う必要がある

# Finetune時のUnet(model)最終層付近のコード
x=model.get_layer(index=-5).output
x = Conv2DTranspose(nClasses, kernel_size=1, use_bias=False)(x)
x = (Activation("softmax"))(x)


・Conv2DTransposeでは「use_bias=False」を指定しないと、DNNDKライブラリの「dpuGetOutputTensorScale()」出力が変化して、sfotmax出力でエラーになることがある






1.4. 精度向上のために工夫したテクニック

深さ512でIou=60%を超えるためには単純にネットワークを構築するだけでは難しく、精度向上のためネットワークに頼る以外の工夫をした。
使った主なテクニックは下の通り。


オリジナル画像(HEIGHT, WIDTH)の比率をなるべく維持した画像サイズでのリサイズ、アスペクト比を維持してのresize



=> Shape=(400, 680)でresizeすることで元画像のサイズ比率をkeepした。また、opencvでresizeでアスペクト比を維持するようにresizeした。これで(224, 224)のように正方形でresizeするよりも小さい物体(signal, pedestrian)の予測精度が上がった。
多分位置情報がresizeでlostすることが減ったためと思う。




ヒストグラム平均化で暗い画像(画素平均80以下)を明るくする前処理


=> 暗い画像の細かい部分の精度向上に若干つながった。暗い画像は画素が偏る特徴があるため、
「画素平均が低い=暗い画像」
として画素平均80未満の画像にヒストグラム平均化を使って明るくした。

def clahe(bgr):
    #plt.imshow(bgr),plt.show()
    lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
    #plt.imshow(lab),plt.show()
    lab_planes = cv2.split(lab)
    clahe = cv2.createCLAHE(clipLimit=6.0,tileGridSize=(8,8))
    lab_planes[0] = clahe.apply(lab_planes[0])
    lab = cv2.merge(lab_planes)
    return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)


def NormalizeImageArr(path, H, W):
    NORM_FACTOR = 255
    img = cv2.imread(path, 1)
    img = cv2.resize(img, (H, W), interpolation=cv2.INTER_NEAREST)
    if img.mean()<80:
        img = clahe(img)
    img = img.astype(np.float32)
    img = img/NORM_FACTOR
    return img

augumatationでの学習


ノイズ系、contrast系、horizontal flipなど、位置を変化させずに済むaugumatationが一番効果があった。
車など上下反転することがない物体がある時はvertical flipは逆効果。

またaugumatationは一度にやらなくても少しずつ学習させた方が精度がだんだんと確実に上がっていくようだ。
以下の手順でaugumentationの学習をした。

Epoch データセット IOU Augmentation
100 CitySpacuies なし  なし
200 train:2143枚(コンペ用画像)、val:100枚(コンペ用画像) train=83.8%、Val = 74% なし
200 train:2143枚(flipした画像のみ)、val:100枚(flipした画像のみ) train=76%、Val = 68% Horizontal flip (validationにも適用)
100 train:4286枚、val:100枚 train=88.5%、Val = 75.8% Horizontal flip (valには適用なし)
100 train:4286枚、val:100枚 train=89.2%、Val = 77.5% contrast系(valには適用なし)


CitySpacesデータセットでpretrain



前回のコンペで前例があったので真似したら、精度がかなり上がった。




Residual構造やセグメンテーションに有利なサブレイヤーを追加するなどを試したが、深さ512だとモデルの表現力に限界があり、ほとんど効果がなかった。またMultiply演算を使うSENetなどもコンパイル時にエラーが出るなどの制約がある部分で精度向上ができなかったのがきつかった。

PDCAで学んだ点は何らかの精度向上のロジック・仮説がないまま闇雲に技術を駆使してもほとんどの工夫は無駄になるということ。




1.5.最後のネットワークのIouなどの結果

最終的にmodelサイズが「14,067,237」の状態でIou=61%(ほど)を達成した。








2.C++のアプリケーションコードの工夫について

処理速度やハードウェアの性能を引き出すためにC++で特に注力したポイントは2つ。


2.1 計算量の削減

今回はPS側の計算が多く、マルチスレッドを3つ使用したため、冗長なコードの削減・簡略化はかなり処理速度に効果がでた。
特に以下のような書き換えで、改善箇所1つにつき、30msほど速くなった。

・forループの効率化

・無駄な関数、その関数の無駄な呼び出しの削除

・決まった値の定数化





2.2. ハードウェアの性能を引き出すために、3つのマルチスレッド処理

今回は前処理やセグメンテーションでのforループなど、DPU演算以外のPS演算の使用率が多かっ たので、マルチスレッドを3つにすることで、30~50msほど早くなった。

下はDPUパラメータ(B1152, 「DSP48 USAGE=LOW」など)の時のマルチスレッド2つの時と3つの時の速度の違い

マルチスレッド個数  画像1枚の平均処理速度(ms)
2こ 1061
3こ 1007






3.3 PSとPL(DPU演算)のDPU演算とsoftmax演算の3つをマルチスレッドで並行処理してさらなる処理速度の向上


本来のマルチスレッドは「PS・PL」を並行処理することで処理速度を上げるのが目的だが、 今回はDNNDKライブラリを使用しているため、PLは

・DPU演算
・softmax演算



で使うメソッドが独立している。

DPU演算メソッド dpuRunTask()
softmax演算メソッド dpuRunSoftmax()

このため今回は

・PS演算
・DPU演算
・softmax演算


の3つをマルチスレッドの並行処理の対象とした。DPU演算とsoftmax演算の両方に非同期処理std::lock_guard lock(mtx_)を用いることで、

DPU演算とsoftmax演算を並行ことができ、マルチスレッドでさらなる処理速度向上が可能になった。





PS・DPU演算(PL)・spftmax演算(PL)の3つを並行処理したマルチスレッド用関数 (main_thread())の抜粋(重要箇所のみ)

#include <thread> 
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <dnndk/dnndk.h>
#include <mutex>  
std::mutex mtx_;
〜〜
〜〜

int main_thread(DPUKernel *kernelConv, int s_num, int e_num, int tid){
  assert(kernelConv);
  DPUTask *task = dpuCreateTask(kernelConv, DPU_MODE_NORMAL); 
  〜〜〜
  // Main Loop
  int cnt=0;
  for(cnt=s_num; cnt<=e_num; cnt+=BLOCK_SIZE){
      for(int i=0; i<BLOCK_SIZE;i++){
        if(cnt+i>e_num) break;
        Mat img;
        resize(input_image[i], img, for_resize, INTER_NEAREST);
        // pre-process with histgram avaraving
        Mat clahe_img = img;
        if((int)mean(img)[0] < 80) {
           clahe_img = clahe_preprocess(img);	
        }
    
        float *softmax = new float[outWidth*outHeight*outChannel]
        // Set image into Conv Task with mean value
        set_input_image(task, outWidth, clahe_img);
        {
          std::lock_guard<std::mutex> lock(mtx_);
          dpuRunTask(task);
        }
        {
          std::lock_guard<std::mutex> lock(mtx_);
          //cout << "outScale : " << outScale << endl;
          int8_t *outAddr = (int8_t *)dpuGetOutputTensorAddress(task, CONV_OUTPUT_NODE);
          dpuRunSoftmax(outAddr, softmax, outChannel,outSize/outChannel, outScale);
        }

        // Post process
        PostProc(softmax, outHeight, outWidth, outChannel, image_file_name[i].c_str());
        delete[] softmax;
      }
  }
  dpuDestroyTask(task);
   return 0;
}

f:id:trafalbad:20201221122329p:plain
3マルチスレッドでPS・DPU演算・softmax演算を並行処理








3.ハードウェアプラットフォームについて

3.1 開発環境

QiitaのVitis-AI開発環境のサイトを参考にした。Vitis-AI-Runtimeライブラリは使わなかったので、DNNDKライブラリベースで開発をした。



3.2 DPUのハードウェアプラットフォーム構築上の工夫について

Vitis-AI環境設定のチュートリアルと第2回のエッジAIコンペの資料(以下:参考資料)を主に参考に、チュートリアルのプラットフォームに改善を加え構築した。


3.2.1 softmax演算IPの活用


なるべくDPU演算を活用するためにsfotmax演算IPを活用。

UnetでConv2DTrasposeを使い、modelとの連携を考慮して、softmax演算を使った。





f:id:trafalbad:20201218012335j:plain
Softmax演算を含んだプラットフォーム(不要なIP削除ずみ)






3.2.2 プラットフォームの構築・改良



DNNDKベースでの開発のため、参考資料をヒントに、Visits-AIプラットフォームのチュートリアルをメインに改良した。
はじめはDPUを搭載したプラットフォームを参考資料の
・softmaxと連携したB1600のDPUの搭載

・DPUの周波数250MHz

の条件で動くことをはじめの目標にした。





そのためにまずチュートリアルのプラットフォームに

1.不要なIPの削除

2.不要なクロックの削除

をして、WNS=0.027でプラットフォームを構築。

今回はmodelの使用のためには「DepthwiseConv」をEnableにする必要性からパラメータと周波数に変更を加えた。






3.3 DPUパラメータと周波数

今回はDPUパラメータの「DepthwiseConv」をEnableにする必要があったため、パラメータがデカすぎて周波数が大きいとリブートしてしまうので、B1600で250MHzでのDPUパラメータでの搭載は出来なかった。

「DepthwiseConv」はB1600で「3292」のLUTを使用することから、DPUパラメータのリソースを参考資料よりかなり減らす必要があった。

特に今回のmodelではconvolution層を多用するため、
「Channel Augumetation」をEnableにしないと、「DSP48 Usage」をHighの時でもかなり処理速度が低下するため、

・「Channel Augmentaion」をEnable

・「DepthWiseConv」をEnable


を必須条件にした。
結果的に、周波数225MHz以上で「DSP48 USAGE」をHIGHにした状態だとリブートしてしまったので、最終的に周波数200MHz、かつ以下のパラメータでDPUを搭載した。

周波数 200MHz
DPU B1600(ReLU+ReLU6)
Channel Augmentation Enable
DepthWiseConv Enable
PoolAverage Disnable
DSP48 USAGE HIGH
RAM USAGE LOW
Softmax Enable

リソースの関係上これ以上の周波数向上はできなかった。



3.4 implとsynthストラテジで処理速度の向上

今回のDPUパラメータに周波数200MHzでは、DPUの性能を引き出すには周波数が足りないので、ストラテジの組み合わせで処理速度が向上できないか、fixstarsのサイトを参考にいくつか試した。


高集積度FPGA設計ガイド」によるとリソースが多いほど集積度を低くしないと、リソースの使用度が難しくなるらしい。

f:id:trafalbad:20201214164645p:plain

今回はDPUのリソースが多かったため、集積度が低くなるように、分散させる系の以下のストラテジの組み合わせを選択したことで35msほど早くなった。

impl Congestion_SpreadLogic_high
Synth Flow_AreaOptimized_high
WNS 0.131 ns

SSIに分散するストラテジ「impl : Congestion_SSI_SpreadLogic_low」も試したが、SSIは消費電力は低いものの、集積度が高いので、上の組み合わせの方が処理性能は高かった。



このストラテジーの組み合わせ、B1600、周波数200MHZなどで制約を満たしたDPUを搭載した。
f:id:trafalbad:20201219013200p:plain

PSと比較してDPUとsoftmaxの使用率は以下の通りになった。

PS & PL Tototal 93%
DPU 43%
Softmax 6%






f:id:trafalbad:20201219013211p:plain

今回の消費電力レポート




f:id:trafalbad:20201227080507j:plain

参考資料



vitis-AI platform site(qiita)

DPU-TRD

Xilinx GitHub Vitis-AI-TUTORIAL

Vivado の合成/インプリメンテーションストラテジを変えてみる(WNS・走行時間編)

第2回AIエッジコンペ資料

Zynq DPU v3.2ガイド

高集積度 FPGA 設計手法 ガイド

Vitis-AIを使ってultra96v2上で学習済みモデルを動かすまで【hardware, FPGA, AI】

今回はultra96v2上で学習済みモデルを動かしてみる。

この作業がすんなりできれば、論理回路の高位合成とか組み込み部分を除けば、学習済みモデルを作れればドローン制御、自動運転の制御とかいろんなことの礎になる。

本家サイト「Ultra96V2向けVitis AI(2019.2)の組み立て方」通りに進めたけど情報が古かったのでかなり苦労した。


f:id:trafalbad:20200429092854p:plain


目次
1.動作環境「vitis AI」の構築
2.Deep Learning Processor Unit (DPU) IP の作成
3.学習済みデータと画像の用意
4.pbファイルの量子化(quantization)
5.量子化したファイルからFPGA用アプリケーションの作成
6.Ultra96v2ボードでの動作確認




1.動作環境「vitis AI」の構築

dockerのインストール



dockerが必要なのでdockerをインストール。

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
>>>
OK

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# Uninstall Old Versions of Docker & Install Docker
$ sudo apt-get remove docker docker-engine docker.io && sudo apt install docker.io
# Start and Automate Docker
$ sudo systemctl start docker && sudo systemctl enable docker
# コンテナ確認
$ sudo docker ps
>>>
CONTAINER ID        IMAGE               COMMAND   CREATED             STATUS              PORTS     NAMES


sudo なしで実行できるようにする。

# dockerグループ作成 & 現行ユーザをdockerグループに所属させる
$ sudo groupadd docker && sudo gpasswd -a $USER docker
# dockerデーモンを再起動する (CentOS7の場合)
$ sudo systemctl restart docker
# exitして再ログインすると反映される。
$ sudo reboot
# コンテナ確認
$ docker ps
>>>
CONTAINER ID        IMAGE               COMMAND   CREATED             STATUS              PORTS     NAMES

参考:Ubuntu 18.04にDockerをインストールする(+docker-composeも)


vitis-Aiのインストール


「tesnsorflow_q_val」コマンドがvirtualbocxだとCPUの関係で使えないので、EC2インスタンスで作業した。

次にvitis AIのインストール。
XilinxのVitis-AIがupgradeされてた。ブランチのv1.1をgit clone。

$ git clone -b v1.1 https://github.com/Xilinx/Vitis-AI
# Dockerの設定
$ cd Vitis-AI/docker
$ ./docker_build_cpu.sh
$ cd Vitis-AI
$ ./docker_run.sh xilinx/vitis-ai-cpu:latest


今回はCPU環境で試してみて、動いた!
f:id:trafalbad:20200429092932p:plain

# 抜ける
$ exit
# GPUの時はこちら
$ sudo ./docker_build_gpu.sh
$ sudo ./docker_run.sh xilinx/vitis-ai-gpu:latest


BitStreamのためにメモリの増設



VirtualBoxで作業してるので、DPUは死ぬほどメモリを食うので


プロセッサーでCPUを6枚

・メインメモリを11800くらいに変更

して計算リソースを増やした。

f:id:trafalbad:20200808225619p:plain

f:id:trafalbad:20200808225623p:plain


計算リソースが足りないとBitStream時に「強制終了エラー」が出る。




2.Deep Learning Processor Unit (DPU) IP の作成

DPU IPは「DPU for Convolutional Neural Network v1.1」によるとFPGAでCNNとかを動かすリソース(レジスタ設定、データ コントローラー、たたみ込み演算の各モジュールとか)が組み込まれてる。

DPU IPはvivadoで作成しなきゃならないけど、ここは本家サイトからresnet50用のDPUをdownloadした。


DPU(AI)IP作成パート
f:id:trafalbad:20200429092954p:plain

download後にultra96v2ボードにetcherでコピー。


コピー後のボード内ファイル一覧

$ ls ultra96_oob
BOOT.BIN        image.ub        system.dtb
README.txt        init.sh            ultra96v2_oob.hwh
dpu.xclbin        platform_desc.tx

これはSDCARDのboot領域(/media/user/${SDCARD}/boot)にコピーされる



3.学習済みネットワークと画像の用意

学習済みネットワークと画像の用意のパート
f:id:trafalbad:20200429093029p:plain



1.学習済みネットワークを用意



まず、作業環境のDocker起動(これ以降はほとんどdocker上で動かす)

$ cd Vitis-AI
$ sudo ./docker_run.sh xilinx/vitis-ai-cpu:latest
# 作業ディレクトリ作成
$ mkdir workspace
$ cd workspace


“vitis-ai-tensorflow”(または”vitis-ai-caffe”)をcondaで実行。

$ conda activate vitis-ai-caffe
# tensorflowの場合
$ conda activate vitis-ai-tensorflow


今回はtensorflowのunetを自分で学習して、kerasの重みファイル(hdf5, ckptファイル)からultra96v2で動くアプリケーションを作る。



2.評価用画像の用意





評価用として学習用に使った画像をprepare_dataset.pyで100枚用意した。

量子化に使う「graph_input_fn.py」を少し書き換えた。

graph_input_fn.py

calib_batch_size = 10

def calib_input(iter):
  images = []
  line = open(calib_image_list).readlines()
  #print(line)
  for index in range(0, calib_batch_size):
      curline = line[iter*calib_batch_size + index]
      #print("iter= ", iter, "index= ", index, "sum= ", int(iter*calib_batch_size + index), "curline= ", curline)
      calib_image_name = curline.strip()

      image_path = os.path.join(calib_image_dir, calib_image_name)
      image2 = NormalizeImageArr(image_path)

      #image2 = image2.reshape((image2.shape[0], image2.shape[1], 3))
      images.append(image2)

  return {"input_1": images}


def main():
  calib_input()

if __name__ == "__main__":
    main()


4.pbファイルの量子化(quantization)


kerasの訓練済の重みファイルを変換して、freezeしたpbファイル(frozen_graph.pb)を量子化する。

quantize.shで量子化

#!/bin/sh
FREEZE_DIR=freeze_tfpb
FROZEN_GRAPH_FILENAME=frozen_graph.pb
QUANT_DIR=quantized_model
INPUT_NODE="input_1"
Q_OUTPUT_NODE="conv2d_23/Sigmoid" # output node of quantized CNN

vai_q_tensorflow quantize \
	 --input_frozen_graph  ${FREEZE_DIR}/${FROZEN_GRAPH_FILENAME} \
	 --input_nodes         ${INPUT_NODE} \
	 --input_shapes        ?,224,224,3 \
	 --output_nodes        ${Q_OUTPUT_NODE} \
	 --output_dir          ${QUANT_DIR}/ \
	 --method              1 \
	 --input_fn            graph_input_fn.calib_input \
	 --calib_iter          10 \
	 --gpu 0




4.量子化データから、FPGA用アプリケーションの作成

アプリケーション作成のパートf:id:trafalbad:20200429093057p:plain


1.dcfファイルの作成



次に、etcherでイメージをSDカードにコピーしたときできた、”ultra96v2_oob.hwh”のhwhファイル(ハードウェア情報ファイル)を使って、dcfファイルを作る。

$ dlet -f ultra96v2_oob.hwh
>>>
[DLet]Generate DPU DCF file dpu-11-18-2019-18-45.dcf successfully.
# rename
$ mv dpu-11-18-2019-18-45.dcf resnet50.dcf

dletコマンドは“vitis-ai-caffe”(“vitis-ai-tensorflow”)内でのみ使える。
dcfファイルは後で使う。




2.compileを実行



compile.sh

#! /bin/sh
CNN=unet
COMPILE_DIR=output_compile
QUANT_DIR=quantized_model
TARGET=custom
ARCH=${TARGET}.json
vai_c_tensorflow \
 	 --frozen_pb ${QUANT_DIR}/deploy_model.pb \
 	 --arch ${ARCH} \
 	 --output_dir ${COMPILE_DIR}/${CNN}2 \
	 --options    "{'mode':'normal'}" \
	 --net_name ${CNN}2

dcfを入れたcustom.jsonの中身。

$cat custom.json
>>>
{"target": "dpuv2", "dcf": "dpu-11-18-2019-18-45.dcf", "cpu_arch": "arm64"}


compileを実行。

$ ./compile.sh
>>>>

**************************************************
* VITIS_AI Compilation - Xilinx Inc.
**************************************************
[VAI_C][Warning] layer [conv2d_23_Sigmoid] (type: Sigmoid) is not supported in DPU, deploy it in CPU instead.

Kernel topology "unet2_kernel_graph.jpg" for network "unet2"
kernel list info for network "unet2"
                               Kernel ID : Name
                                       0 : unet2_0
                                       1 : unet2_1

                             Kernel Name : unet2_0
--------------------------------------------------------------------------------
                             Kernel Type : DPUKernel
                               Code Size : 1.16MB
                              Param Size : 29.63MB
                           Workload MACs : 83685.54MOPS
                         IO Memory Space : 17.04MB
                              Mean Value : 0, 0, 0, 
                      Total Tensor Count : 40
                Boundary Input Tensor(s)   (H*W*C)
                            input_1:0(0) : 224*224*3

               Boundary Output Tensor(s)   (H*W*C)
              conv2d_23_convolution:0(0) : 224*224*11

                        Total Node Count : 35
                           Input Node(s)   (H*W*C)
                 conv2d_1_convolution(0) : 224*224*3

                          Output Node(s)   (H*W*C)
                conv2d_23_convolution(0) : 224*224*11

                             Kernel Name : unet2_1
--------------------------------------------------------------------------------
                             Kernel Type : CPUKernel
                Boundary Input Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

               Boundary Output Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

                           Input Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

                          Output Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

終わったあとのファルダ構造。

$ tree 
>>>
compile
├── compile.sh
├── custom.json
├── dpu-11-18-2019-18-45.dcf
├── output_compile
│   └── unet2
│       └── unet2_kernel_graph.gv
|        └── dpu_unet2_0.elf
├── quantized_model
│   ├── deploy_model.pb
│   └── quantize_eval_model.pb
└── ultra96v2_oob.hwh


3.アプリケーションの作成(sigmoidを使う


まずdocker。Xilinx提供のVitis AI runtimeを実行。

$ ./docker_run.sh xilinx/vitis-ai:runtime-1.0.0-cpu

今回unetにsigmoidを使った。

sigmoidはDPUで対応してないため、DPUKernelとCPUKernelが分割されるので、dpu_unet2_0.elf(unet2_0)が作成される。


なのでアプリケーション作成時に使う
「src/fpc_main.cc」のコードで

#define KERNEL_CONV   "unet2"

から

#define KERNEL_CONV       "unet2_0"

に置き換えないとultra96v2上で動かない。








5.量子化したファイルからFPGA用アプリケーションの作成


1.開発環境構築・ライブラリをSDカードへコピー



Vitis AIをUltra96上で動かすには、xilinxのライブラリーが必要なので、runtime パッケージをSDカード(rootのsdcardフォルダ)にコピー

# パッケージのinstall(開発環境構築)
$ sudo cp -r /opt/vitis_ai/xilinx_vai_board_package Vitis-AI/workspace/
$ cd Vitis-AI/workspace/xilinx_vai_board_package/
$ sudo ./install.sh

# パッケージをSDカードにコピー
$ sudo cp -r /opt/vitis_ai/xilinx_vai_board_package /media/user/${SDCARD}/root/home/root/


2.FPGA用アプリケーションの作成




さっき作った「dpu_unet2_0.elf」をのmodelファルダの中に移動。
「$make」コマンドでアプリケーション作成実行。

コードはXilinxsegmentationチュートリアルのコードを少し改造して使った。

# アプリケーションの作成実行
$ sudo make 


アプリケーション完成後のディレクトリ構造

$ tree 
>>>
├── common
│   ├── dputils.cpp
│   ├── dputils.h
│   └── dputils.py
└── unet2
    ├── Makefile
    ├── build
    │   ├── dputils.o
    │   └── fps_main.o
    ├── model
    │   ├── dpu_unet2_0.elf
    │   └── libdpumodelunet2.so
    ├── src
    │   └── fps_main.cc
    └── unet2

src/fps_main.ccの一部を変更。

// constants for segmentation network
#define KERNEL_CONV       "unet2_0"
#define CONV_INPUT_NODE   "conv2d_1_convolution"
#define CONV_OUTPUT_NODE  "conv2d_23_convolution"


アプリケーションとして「unet2」と「libdpumodelunet2.so」ができるので、それをSDカード(rootのsdcardフォルダ)にコピー。

$ cp resnet50 /media/user/${SDCARD}/root/home/root/



6.Ultra96v2ボードでの動作確認

いつもみたいにultra96v2の実機を起動後に、gtktermで接続してログイン

$ gtkterm -p /dev/ttyUSB1 -s 115200
# ライブラリのinstall
$ cd xilinx_vai_board_package
$ ./install.sh
>>>
Begin to install Xilinx DNNDK ...
Requirement already satisfied: Edge-Vitis-AI==1.0 from file:///sdcard/pkgs/python/Edge_Vitis_AI-1.0-py2.py3-none-any.whl in /usr/lib/python3.5/site-packages (1.0)
Complete installation successfully.

判別用画像をroot/home/rootの「worksapace/dataset1/」の中に入れておく。

$ cd unet2/
$ sudo chmod 755 *
$ ./unet2

f:id:trafalbad:20200902200235p:plain


中身は適当なので出力結果は気にしないけど、とりあえず動いた。





ここでこの部分の作業が終わって、AIを動かす過程が一通り終了。
f:id:trafalbad:20200429093241p:plain




ここまででようやくultra96v2上で学習済みモデルを動かせた。

f:id:trafalbad:20200429094131j:plain


参考サイト



Ultra96V2向けVitis AI(2019.2)の組み立て方

Ubuntu 18.04にDockerをインストールする(+docker-composeも)

Vitis AI User Guide

ザイリンクス社「Vitis AI開発環境」を評価キット ZCU102 で動かしてみた

GitHub - Xilinx/Vitis-AI at v1.1

Vitis-AI 1.1 Flow for Avnet VITIS Platforms - Part 1

UbuntuでSDカードのEXT4とFAT32のパーテションの作り方

ubuntuでSDカードのパーティション作成のメモ

目次
1.ubuntumacにインストールする
2. ubuntuを起動、SDカードの確認
3.パーティションの作成
4.GUIでパーテイションの内訳を確認してみる
追記.Ffrom CUI


1.ubuntumacにインストールする

1.USBカードを差し込む。

$ diskutil list
$ diskutil unMountDisk /dev/disk2 (USB= /dev/disk2)
$ sudo dd if=ubuntu-18.4.2.iso of=/dev/disk2 bs=1m

終わったら、「無視」をクリック。


2.macをoption押しながら起動してUSBの選択肢を選択する。


3.あとは[ubuntu〜 install]をクリックしてwifi設定も含めてubuntuをinstall
ほとんどvirtualBoxと同じ方法でinstall。




2. ubuntuを起動、SDカードの確認

macの「diskutil list」と同じコマンドを打って、SDカードの内訳確認

$ dmesg | tail
>>>>
...
[ 6854.215650] sd 7:0:0:0: [sdc] Mode Sense: 0b 00 00 08
[ 6854.215653] sd 7:0:0:0: [sdc] Assuming drive cache: write through
[ 6854.215659]  sdc: sdc1


この例では/dev/sdcとして認識されてる。





3.パーティションの作成

fdiskコマンドの確認
・p:確認
・d:パーティション削除
・n:パーティション新規作成
・a:起動フラグの有効化

$ sudo fdisk /dev/sdc

fdisk (util-linux 2.31.1) へようこそ。
ここで設定した内容は、書き込みコマンドを実行するまでメモリのみに保持されます。
書き込みコマンドを使用する際は、注意して実行してください。


・pコマンドで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から   セクタ サイズ Id タイプ
/dev/sdc1           2048  2936831  2934784   1.4G  c W95 FAT32 (LBA)
/dev/sdc2        2936832 14680063 11743232   5.6G 83 Linux


・dコマンドでパーティション削除

コマンド (m でヘルプ): d
パーティション番号 (1,2, 既定値 2): 2

パーティション 2 を削除しました。

コマンド (m でヘルプ): d
パーティション 1 を選択
パーティション 1 を削除しました。


・pで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70


・nで新規作成(n => p => 1 => 2048)

コマンド (m でヘルプ): n
パーティションタイプ
   p   基本パーティション (0 プライマリ, 0 拡張, 4 空き)
   e   拡張領域 (論理パーティションが入ります)
選択 (既定値 p): p
パーティション番号 (1-4, 既定値 1): 1
最初のセクタ (2048-31293439, 既定値 2048): 2048
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} (2048-31293439, 既定値 31293439): 2936831

新しいパーティション 1 をタイプ Linux、サイズ 1.4 GiB で作成しました。
パーティション #1 には vfat 署名が書き込まれています。

署名を削除しますか? [Y]es/[N]o: Yes

署名は write (書き込み)コマンドを実行すると消えてしまいます。


・aで起動フラグを有効にする

コマンド (m でヘルプ): a
パーティション 1 を選択
パーティション 1 の起動フラグを有効にしました。


・pで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から  セクタ サイズ Id タイプ
/dev/sdc1  *        2048  2936831 2934784   1.4G 83 Linux

パーティション 1 にあるファイルシステム/RAIDの署名が完全に消去されます。


・nで2個めのパーティション作成(n => p => 2 => 2936832 => 31293439)

コマンド (m でヘルプ): n
パーティションタイプ
   p   基本パーティション (1 プライマリ, 0 拡張, 3 空き)
   e   拡張領域 (論理パーティションが入ります)
選択 (既定値 p): p
パーティション番号 (2-4, 既定値 2): 2
最初のセクタ (2936832-31293439, 既定値 2936832): 2936832
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} (2936832-31293439, 既定値 31293439): 31293439

新しいパーティション 2 をタイプ Linux、サイズ 13.5 GiB で作成しました。
パーティション #2 には ext4 署名が書き込まれています。

署名を削除しますか? [Y]es/[N]o: Yes

署名は write (書き込み)コマンドを実行すると消えてしまいます。


・pで最終確認 & 終了

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から   セクタ サイズ Id タイプ
/dev/sdc1  *        2048  2936831  2934784   1.4G 83 Linux
/dev/sdc2        2936832 31293439 28356608  13.5G 83 Linux

パーティション 1 にあるファイルシステム/RAIDの署名が完全に消去されます。
パーティション 2 にあるファイルシステム/RAIDの署名が完全に消去されます。

コマンド (m でヘルプ): ^C
終了してよろしいですか? y




4.GUIパーティションの内訳を確認してみる

deskopから、「desk」を選択。

SDカードを差し込むと「boot」と「root」の両方があるのが見える。
f:id:trafalbad:20200703212618j:plain

FAT32パーティション(boot)f:id:trafalbad:20200703212646j:plain


EXT4(linux用)パーティション(root)f:id:trafalbad:20200703212652j:plain



FAT32パーティション(boot)の中身f:id:trafalbad:20200703212700j:plain



EXT4パーティション(root)の中身f:id:trafalbad:20200703212708j:plain


sdカードをFAT32EXT4に分割できた。

ちなみにmacでは無理で、ubuntuをOSで入れないとできない



追記.From CUI

1.HDDを仮想環境に追加



設定=>ストレージ=>「コントロール:SOTA」の右のプラスボタンをクリックで作成。
・VDI(Virtual Disk Image)

・可変サイズ

・とりあえず50GB
で作成。

f:id:trafalbad:20200929020441p:plain

50GBのHDDが追加されたか確認。

$ dmesg | grep sdb
>>>
[    3.462702] sd 3:0:0:0: [sdb] 104857600 512-byte logical blocks: (53.7 GB/50.0 GiB)



2イメージの書き込み



fdiskじゃなくてpartedコマンドで作る

このサイトからinstallしたイメージをEtcherでSD CARDにコピーすれば、rootとbootは勝手に作られてる。
だからfdiskコマンドでパーティションを作ってやる必要ないのでそのままコピー。

コピーするsd_cardの中身は下のファイル構成

$ tree sd_card
>>>
sd_card
├── boot
│   ├── BOOT.BIN
│   ├── dpu.xclbin
│   ├── image.ub
  ├── platform_desc.txt
     ├── dpu.xclbin
     ├── README.txt
     ├── ultra96v2_oob.hwh
│   └── system.dtb
└── root
    └── rootfs.tar.gz


sdカードに書きこみ用にsd.imgを作成

# sd.imgファイルを作成
truncate -s 8GiB sd.img
sudo losetup -f
>>>
/dev/loop17

sudo losetup /dev/loop17 sd.img
# sudo parted /dev/loop17 -s mklabel msdos -s mkpart primary fat32 0% 2GiB -s mkpart primary ext4 2GiB 100%
sudo parted /dev/loop17
###
mklabel msdos 
mkpart primary fat32 0% 2GiB
mkpart primary ext4 2GiB 100%
q
###

# 割り当ての確認
$ ls /dev/loop17*
>>>
/dev/loop17  /dev/loop17p1  /dev/loop17p2

sudo mkfs.vfat /dev/loop17p1
sudo mkfs.ext4 /dev/loop17p2

# マウント
mkdir -p ./mnt/boot ./mnt/root
sudo mount /dev/loop17p1 ./mnt/boot/
sudo mount /dev/loop17p2 ./mnt/root/

$ sudo cp sd_card/boot/* ./mnt/boot/
$ sudo tar -C ./mnt/root/ -xzvf sd_card/root/rootfs.tar.gz

$ # 追加で必要なデータは、 ./mnt/root/home/root 以下に置く
# sudo cp -r xilinx_vai_board_package ./mnt/root/home/root/
# sudo mkdir ./mnt/root/home/root/place
# sudo cp -r seg_test_images ./mnt/root/home/root/place/
# sudo mkdir ./mnt/root/home/root/place/output

sudo sync
sudo umount ./mnt/boot
sudo umount ./mnt/root
sudo losetup -d /dev/loop17

# sd.imgに格納されてるか容量をcheck
$ du -hs sd.img
>>>
2.0G	sd.img

あとはEtcherでsdカードに焼けばOK。




3.gtktermでultra96v2にログイン


VirtualBoxにUSBを認識させた。teratermubuntu版「gtkterm」を使った

Ultra96v2ボードは1をoffに2をOnにしてSDカードのモード(JTAGではなく)にしておく。

まずVirtualBoxにUSB(JTAG)を認識させる。
VirtualBox - 仮想マシンにUSBメモリを認識・マウント

# gtktermのインストール
$ sudo apt-get install gtkterm

# USBが認識されてるか確認
$ ls -l /dev/ttyUSB*
>>>
crw-rw---- 1 root dialout 188, 0  828 17:11 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1  828 17:11 /dev/ttyUSB1

# ultra96v2ボードにアクセス(terminalで実行)
$ gtkterm -p /dev/ttyUSB1 -s 115200



参考サイト



How to format SD card for SD boot

fdiskでフォーマットする

VirtualBox の Ubuntu にHDDを追加する方法

Ultra96-v2でCNN推論エンジン(DPU)を動かすまで

PytorchのTransformerでテキストからの音声生成(TextToSpeech)をやってみた【機械学習】

今回はTransformerを改造して、文章から音声を生成してみた。

俗に言う、エンコーダ・デコーダ形式のモデル。
プロセスが長くなりそうなので、要点だけ備忘録もかねてまとめようと思う。


目次
1.Transformer-TextToSpeechとは?
2.テキスト前処理
3.TransformerとPostNetの学習
4.テキストから音声を作成してみる



1.transformer-TextToSpeechとは?

今回作ったtransformerでの音声生成は、Google が発表したTactron2を改造した。

tactron2のEncoderとDecoderをTransformerに置き換えて、waveGlowをpostnetに置き換えたモデル。

Tactron2はそもそもGoogleのこれまでの音声生成プロジェクトで作られた、WaveNetと初代Tacotronのネットワークを組み合わせたもので、詳しくはサイトを見て欲しい。

tacotron2での音声生成処理の流れ
f:id:trafalbad:20200630215005p:plain




今回作ったTransformerの音声生成処理の流れ
f:id:trafalbad:20200630215350j:plain


つまり、

・tacotron2 => Transformer
・waveGlow => PostNet
に置き換えた。

単純なAttentionモデルのseq2seqを使った場合より、4倍くらい速く、精度もbetter。

特にtransformerのattentionの部分が正確な音声の作成にかなり有効っぽい。




2.テキスト前処理

今回データセットは「The LJ Speech Dataset」を使った。

英語での音声を想定したデータセット


前処理では、日本語でもローマ字(英語のスペル)に変換する。
今回は英語対応なので、すべての文字を英語のスペルに変換。


試しにデータセットの一部分の、LJ050-0278のデータをのぞいてみる。

textデータ(csv)

LJ050-0278|the recommendations we have here suggested would greatly advance the security of the office without any impairment of our fundamental liberties.|the recommendations we have here suggested would greatly advance the security of the office without any impairment of our fundamental liberties.


音声データ

LJ050-0278.mag.npy
LJ050-0278.pt.npy
LJ050-0278.wav


まず訓練前にこのテキストデータを前処理した。







3.TransformerとPostNetの学習

まず、Transformerを学習させてから、最後にPostNetを学習して、音声合成する。

Transformerはスペクトログラム(人間の音高知覚に調整した特徴量:メル周波数)を出力。

PostNetは音声の波形データを出力する。

Pytorchなのでmodelをprint()してみた

Transformer

model = Transformer_model()
print(model)

>>>>>
Model(
(encoder): Encoder(
(pos_emb): Embedding(1024, 256)
(pos_dropout): Dropout(p=0.1, inplace=False)
(encoder_prenet): EncoderPrenet(
  (embed): Embedding(149, 512, padding_idx=0)
  (conv1): Conv(
    (conv): Conv1d(512, 256, kernel_size=(5,), stride=(1,), padding=(2,))

 ~略~

  (1): Attention(
    (key): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)
    )
    (value): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)
    )
    (query): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)

 ~略~

(decoder): MelDecoder(
(pos_emb): Embedding(1024, 256)
(pos_dropout): Dropout(p=0.1, inplace=False)
(decoder_prenet): Prenet(
  (layer): Sequential(
    (fc1): Linear(
      (linear_layer): Linear(in_features=80, out_features=512, bias=True)
    )

 ~略~

  (pre_batchnorm): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (dropout1): Dropout(p=0.1, inplace=False)
  (dropout_list): ModuleList(
    (0): Dropout(p=0.1, inplace=False)
    (1): Dropout(p=0.1, inplace=False)
    (2): Dropout(p=0.1, inplace=False)
  )
)
)
)

PostNet

model = PostNet_model() 
print(model)
>>>>>>

ModelPostNet(
(pre_projection): Conv(
(conv): Conv1d(80, 256, kernel_size=(1,), stride=(1,))
)
(cbhg): CBHG(
(convbank_list): ModuleList(
  (0): Conv1d(256, 256, kernel_size=(1,), stride=(1,))
  (1): Conv1d(256, 256, kernel_size=(2,), stride=(1,), padding=(1,))
  (2): Conv1d(256, 256, kernel_size=(3,), stride=(1,), padding=(1,))
  (3): Conv1d(256, 256, kernel_size=(4,), stride=(1,), padding=(2,))
  (4): Conv1d(256, 256, kernel_size=(5,), stride=(1,), padding=(2,))
  (5): Conv1d(256, 256, kernel_size=(6,), stride=(1,), padding=(3,))
  (6): Conv1d(256, 256, kernel_size=(7,), stride=(1,), padding=(3,))
  (7): Conv1d(256, 256, kernel_size=(8,), stride=(1,), padding=(4,))

  ~略~

  (linears): ModuleList(
    (0): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (1): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (2): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (3): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
  )
)
(gru): GRU(256, 128, num_layers=2, batch_first=True, bidirectional=True)
)
(post_projection): Conv(
(conv): Conv1d(256, 1025, kernel_size=(1,), stride=(1,))
)
)

コードだと分かりにくいのでモデルの全体像。
f:id:trafalbad:20200630215037p:plain



学習はfine-tuneで行った。
学習率のlearning rateは0.001がベストプラクティス。
あと学習が進むにつれてlearning rateも下がる調整もかなり重要っぽい

def adjust_learning_rate(optimizer, step_num, warmup_step=4000):
    lr = hp.lr * warmup_step**0.5 * min(step_num * warmup_step**-1.5, step_num**-0.5)
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr


loss曲線はtctron2からの引用だけど、同等もさくはそれ以上に、かなりいい具合に減少。
f:id:trafalbad:20200630215049p:plain




でかいデータセットでやるとメモリエラーも起こるし、時間もとんでもなくかかるからfine-tuneがおすすめ。

最近はもうfine-tuneの方がかなり効率いいので、end-to-endの学習は余程のことがないとしないんじゃないかな?





4.テキストから音声を作成してみる

映画ホームアローンのハリーのセリフを作成してみる。

f:id:trafalbad:20200630215151j:plain



text1 = "I never made it to sixth grade"
text2 = "it dose not look like you are gonna"

周波数(fs)は低くするとドスのきいた声になり、高いと早口言葉みたいになる。

max_lenはtextの長さに比例するのでそれぞれちょうどいい具合に調整した。

def calculate_melsp(x, n_fft=1024, hop_length=128, n_mels=128):
    stft = np.abs(librosa.stft(x, n_fft=n_fft, hop_length=hop_length))**2
    log_stft = librosa.power_to_db(stft)
    melsp = librosa.feature.melspectrogram(S=log_stft, n_mels=n_mels)
    return melsp

# display wave in plots
def show_wave(x):
    plt.plot(x)
    plt.show()
    
    
# display wave in heatmap
def show_melsp(melsp, fs):
    librosa.display.specshow(melsp, sr=fs)
    plt.colorbar()
    plt.show()

text1 = "I never made it to sixth grade, kid."

max_len = 500
fs = 25000

text1 = "I never made it to sixth grade, kid."
wav = create_audio_wave(text1, max_len)

print(wav.shape)  # (137225,)
show_wave(wav)

melsp = calculate_melsp(wav, n_fft=fs, hop_length=max_len, n_mels=max_len)
print(melsp.shape) # (500, 275)

show_melsp(melsp, fs)

# 実際にjupyter上で音声が聞ける
ipd.Audio(wav, rate=fs)

この投稿をInstagramで見る

開発用 "I never made it to sixth grade"

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -





text2 = "it dose not look like you are gonna"

text2 = "it dose not look like you are gonna"
wav = create_audio_wave(text2, max_len)

ipd.Audio(wav, rate=fs)

この投稿をInstagramで見る

開発用2 "it dose not look like you are gonna"

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -




AttentionはNLPだけじゃなく、いろんな精度向上に役立つっぽい。

ガチの音声生成をしたのははじめてだった。分類系より、生成系は面白い。



参考サイト



GitHub - soobinseo/Transformer-TTS: A Pytorch Implementation of "Neural Speech Synthesis with Transformer Network"

強化学習で「手押し井戸ポンプ」で水をくむ動作をArduino Unoに学習させる【AI・Hardware】

「手押し井戸ポンプ」は、昔から田舎で使われてる「井戸から手動で水をくむポンプ」のこと(Amazonでも売ってる)。

「手押し井戸ポンプ」の水をくむまでの大まかな動作は

押す(down)=>水をくむ(push pump)=>あげる(up)

の行動パターンを、シーソーのごとく繰り返すことで、井戸から水をくめる。

手押し井戸ポンプの動作原理f:id:trafalbad:20200620111544g:plain



今回はArduinoを使って、実際に近い状況を再現(シミュレーション)して、強化学習で水をくむ動作を学習させた。

深層学習の強化学習アルゴリズムはCartPoleでお馴染みの「DQN」を使う。特にkeras-rlとか強化学習ライブラリを使わずに、普通にDQNの学習コード書いた。

その要点をまとめて行こうと思う。

目次
1.Arduinoで「手押し井戸ポンプ」の状況の再現
2.Arduinoの構成
3.DQNで学習
4.学習結果
5.最後に




1.Arduinoで「手押し井戸ポンプ」の状況の再現


まず、Arduinoで実際の「手押し井戸ポンプ」の状況を実際に再現してシミュレーションした。

はじめにwebカメラでデフォルトの画像を読み込ませる。

それをニューラルネットワーク(NN)に読み込ませて、DQNの学習システムに沿って水をくむ行動パターンを学習させていく。

1.webカメラで画像を読み込み、NNに入れる

2.PC側でNNから行動を出力して、Arduinoに送る

3.Arduinoで行動を読み取り動作させる。ポンプで水をくめたら、PCに「1」の信号送る

4.Arduinoから「1」を受け取りPC側で報酬(Reward)として受け取る


f:id:trafalbad:20200620111631j:plain

#include <Servo.h>

Servo mServo;

int state[2];
int pin_number = 10;

void setup() {
  mServo.attach(9);
  mServo.write(10);
  delay(500);
  Serial.begin(9600);
  pinMode(pin_number, OUTPUT);
  state[0] = 0;
  state[1] = 1;
  digitalWrite(pin_number, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'p') {
      if (state[0] == 0) {
        Serial.print("0");
        mServo.write(90);
        delay(500);
        state[0] = 1;
      }
      else {
        Serial.print("0");
        mServo.write(10);
        delay(500);
        state[0] = 0;
        state[1] = 1;
        digitalWrite(10, LOW);
      }
    }
    else if (c == 'i') {
      if (state[0] == 1 && state[1] == 1) {
        Serial.print("1");
        delay(500);
        digitalWrite(10, HIGH);
        state[1] = 0;
      }
      else {
        Serial.print("0");
      }
    }
    else if (c == 'c') {
      state[0] = 0;
      state[1] = 1;
      digitalWrite(10, LOW);
      mServo.write(10);
      delay(500);
    }
  }
}



Arduinoとリアルの「手押し井戸ポンプ」の行動の対応関係


「手押し井戸ポンプ」から水をくむのに必要な行動パターンは3つあって、順番に行動する(行動パターンを覚える)必要がある。



水をくむまで行動パターン

Action1(a1) : 押す(down)
Action2(a2) : 水をくむ(Push pump)
Action(a3) : あげる(up)



リアルの「手押し井戸ポンプ」の行動パターンf:id:trafalbad:20200620112256j:plain



Arduinoのシミュレーションの行動パターンf:id:trafalbad:20200620111714j:plain







2.Arduinoの構成

Arduino(Uno)で用意したのは付属パーツは

サーボモータ

・USBカメラ(Mac用)

・抵抗

・LED



Arduino回路図f:id:trafalbad:20200620111744j:plain




実際にUSBカメラで読み込むArduinoの映像

この投稿をInstagramで見る

#arduino for development

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -








3.DQNで学習

アルゴリズムはDeep-QNetwork(DQN)。
DQNはモデルフリーなので報酬とか戦略の設定が大事だと思う。


このDQNのメカニズムを使ってNNに「手押し井戸ポンプ」から水を汲む行動パターンを覚えさせる。



DQNでの学習サイクル(Episode cycle)
f:id:trafalbad:20200620111911j:plain








Model



DQNではlossにHuber lossを使うのが一般的らしい。「Squeeze-and-Excitation Networks」を使って少し凝ったCNNを作った。

ef QFunction(inputs_):
    o = Conv2D(32, (3, 3), padding='same', kernel_initializer='random_uniform')(inputs_)
    o = channel_spatial_squeeze_excite(o)
    o = MaxPooling2D((2, 2))(o)
    o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
    o = channel_spatial_squeeze_excite(o)
    o = MaxPooling2D((2, 2))(o)
    o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
    o = channel_spatial_squeeze_excite(o)
    o = Flatten()(o)
    o = Dense(2, activation='linear')(o)
    model = Model(inputs=inputs_, outputs=o)
    model.compile(optimizer=Adam(lr=0.001), loss=huber_loss_mean, metrics=['acc'])
    return model

USBカメラから画像を読み込む部分。

cap = cv2.VideoCapture(0)

def capture(ndim=3):
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    xp = int(frame.shape[1]/2)
    yp = int(frame.shape[0]/2)
    d = 400
    resize = 128
    cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
    cv2.imshow('gray', gray)
    gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
    env = np.asarray(gray, dtype=np.float32)
    if ndim == 3:
        return env[np.newaxis, :, :] 
    else:
        return env[np.newaxis, np.newaxis, :, :] 

camera_image = capture(ndim=3)
cap.release()



Environment


1回のepisodeでStepを30回回して、水汲みに8回成功したらsuccessに+1する。それを全episode中に5回(success=5)繰り返せたら学習終了。

NUM_EPISODES = 50 # エピソード数
GAMMA = 1.0 

# 探索パラメータ
E_START = 1.0 # εの初期値
E_STOP = 0.01 # εの最終値
E_DECAY_RATE = 0.001 # εの減衰率

SUCCESS_REWARD = 8

# メモリパラメータ
MEMORY_SIZE = 10000 # 経験メモリのサイズ
MAX_STEPS = 30 # 最大ステップ数
BATCH_SIZE = MAX_STEPS

H = 128
W = 128
C = 1
inputs = Input([H, W, C])

class Environment:
    def __init__(self, inputs):
        # main-networkの作成
        self.main_qn = QFunction(inputs)

        # target-networkの作成
        self.target_qn = QFunction(inputs)
        # 経験メモリの作成
        self.memory = Memory(MEMORY_SIZE)

 # 行動価値関数
    def action_value_function(self, action_b, step_reward_b):
        target = 0
        if action_b == 1:
            target = GAMMA + step_reward_b/MAX_STEPS
        else:
            target = GAMMA + step_reward_b/MAX_STEPS
        return target

    def default(self):
       # Memoryの初期化
        self.memory = Memory(MEMORY_SIZE)

    def run(self):
        # エピソード数分のエピソードを繰り返す
        total_step = 0 # 総ステップ数
        success_count = 0 # 成功数
        for episode in range(1, NUM_EPISODES+1):
            step = 0 # ステップ数
            R = 0
            batch_size = 0
            ser.write(b"c") # Arduino側を初期状態に戻す

            # target-networkの更新
            self.target_qn.set_weights(self.main_qn.get_weights())
            
            # 1エピソードのループ
            for _ in range(1, MAX_STEPS+1):
                step += 1
                total_step += 1
                camera_state = capture(ndim=3)
                camera_state = camera_state.reshape(1, H, W, C)
                # εを減らす
                epsilon = E_STOP + (E_START - E_STOP)*np.exp(-E_DECAY_RATE*total_step)
          
                # ランダムな行動を選択
                if epsilon > np.random.rand():
                    action = int(np.random.randint(0, 2, 1))
                # 行動価値関数で行動を選択
                else:
                    action = np.argmax(self.main_qn.predict(camera_state))
                    pred = self.main_qn.predict(camera_state)
   
                # 行動に応じて状態と報酬を得る
                reward = action_step(action)
                R += reward
                self.memory.add((camera_state, action, reward, R)) 
                print('step', step, 'action', action, "R", R, 'reward', reward)
            if R >=SUCCESS_REWARD:
                success_count += 1
                
            # ニューラルネットワークの入力と出力の準備
            inputs = np.zeros((BATCH_SIZE, H, W, C)) # 入力(状態)
            targets = np.zeros((BATCH_SIZE, 2)) # 出力(行動ごとの価値)
            # バッチサイズ分の経験を取得
            minibatch = self.memory.sample(BATCH_SIZE)
            
            # ニューラルネットワークの入力と出力の生成
            for i, (state_b, action_b, reward_b, step_reward_b) in enumerate(minibatch):
                
                # 入力に状態を指定
                inputs[i] = state_b
                
                # 採った行動の価値を計算
                target = self.action_value_function(action_b, step_reward_b)
                # 出力に行動ごとの価値を指定
                targets[i] = self.main_qn.predict(state_b)
                targets[i][action_b] = target # 行動の価値

            # 行動価値関数の更新
            print('training....')
            self.main_qn.fit(inputs, targets, epochs=30, verbose=0)
            
            # エピソード完了時のログ表示
            print('エピソード: {}, ステップ数: {}, epsilon: {:.4f}'.format(episode, step, epsilon))
            self.default()
            # 5回成功で学習終了
            if success_count >= 5:
                break

WEbカメラから画像をNNに入れる => 行動価値関数を出力 => 行動に変換 => 報酬get


の簡単な流れを図にしてみた。
f:id:trafalbad:20200620111700j:plain




4.学習結果


epochの1~50までのパターン行動の成功回数の推移
f:id:trafalbad:20200620112004p:plain

順調にパターンを学習して、水をくむ回数が増えていってるのがわかる。


行動パターンを覚えさせるために、行動価値関数を次のように設計した。

# 行動価値関数
 def action_value_function(action_b, step_reward_b):
        target = 0
        if action_b == 1:
            target = GAMMA + step_reward_b/MAX_STEPS
        else:
            target = GAMMA + step_reward_b/MAX_STEPS
        return target

# 行動を返す関数
def action_step(actions):
    r = 0
    if actions==0:
        ser.write(b"p")
    else:
        ser.write(b"i")
    time.sleep(1.0)
    r = ser.read() 
    return int(r)

この設定だとepisodeが進むにつれて、それぞれの行動(0と1)の行動価値が最後は均衡してくる。

NNからoutputされる行動価値の推移f:id:trafalbad:20200620112018j:plain

もっと上手い設定方法はあるけど、とりあえずうまく学習できてたので自分としては上出来。



5.最後に

50回目までにStep1回で水汲み16回以上を達成できるようになって、無事クリア。DQN

・報酬の設定

・行動価値をどう決めるか 

・戦略

・経験の学習



とかが一番学習結果に影響した。逆にネットワークは別にそんな複雑なものである必要はなかった。

現に今回は白黒画像でやってもこれだけの成果が出たし。強化学習は戦略とか「どう学ばせるか」が肝だと思う。


ハードウェアで強化学習ができるのはいろいろと厨二病的な愉悦。

f:id:trafalbad:20200620112502j:plain

webカメラ+Arduino Uno+CNN画像分類(AI)でカギの開錠・施錠システムを作ってみた【hardware,機械学習】

今回はArduino機械学習(AI)を埋め込んで、webカメラから読み込んだストリーミング画像の画像分類結果からカギを解錠・施錠できるシステムを作ってみた。

機械学習(AI)をハードウェアで実際に取り込んで作ったのはVitis AI以来、今回がはじめて。

Arduino系の記事はたくさんあるけど、機械学習を使った例はあんまなかったので、今回はその作成過程をまとめてく。


目次
1.CNN+Arduinoの鍵の開錠・施錠システムの構造
2.データセットの作成・CNNで学習
3.CNN+Arduinoで金庫の鍵の開錠・施錠


1.CNN+Arduinoの鍵の開錠・施錠システムの構造

用意したもの

Arduino Uno
・ジャックDIP化キット
サーボモータ
・ACアダプター(5V)
・USBカメラ
・スイッチ


Arduinoの回路図の概略f:id:trafalbad:20200601112800j:plain



作業手順

1.webカメラで学習用画像集めてデータセットを作成する

2.CNNで学習

3.key.txtに開錠番号を記載

4.Webカメラで取り込んだストリーミング画像を推論
=> 推論結果とkey.txtと同じ番号ならArduinoでカギの解錠

今回工夫したのは、
Webカメラからデータセットを作れるようにした

Webカメラのストリーミング画像を推論してkey.txtの番号と一致したら解錠する仕組み

の2点。


実物f:id:trafalbad:20200601113319j:plain



2.データセットの作成・CNNで学習

学習から推論までは下のフローで進めてく。

Webカメラからデータセットを作成=>訓練=>Webカメラのストリーミング画像を推論

解錠システムのデータセット作成から推論までの流れは図にするとこんな感じ
f:id:trafalbad:20200601112958j:plain





Webカメラから学習データ収集


今回はWebカメラから直接データセットを作れるようにした。

訓練画像にしたい部分をwebカメラバウンディングボックス内に写す。
そのとき、PCキーボードの番号(0~5)を押せばその番号のimgファルダに画像が保存されるので、大量の画像を短時間で作れる。

今回は訓練画像は6種類。何も写ってない画像を0として含めた。

訓練画像1~5f:id:trafalbad:20200601113108j:plain

Webカメラで写しながら、キーボードの数字を押せばimgファルダの該当番号のファルダに保存されてく仕組み。

import cv2

n0 = 0
n1 = 0
n2 = 0
n3 = 0
n4 = 0
n5 = 0
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    xp = int(frame.shape[1]/2)
    yp = int(frame.shape[0]/2)
    d = 400 # bbox size
    resize = 256
    cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
    cv2.imshow('gray', gray)
    gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
    c =cv2.waitKey(10) 
    if c == 48:#0
        cv2.imwrite('img/0/{0}.png'.format(n0), gray)
        n0 = n0 + 1
    elif c == 49:#1
        cv2.imwrite('img/1/{0}.png'.format(n1), gray)
        n1 = n1 + 1
    elif c == 50:#2
        cv2.imwrite('img/2/{0}.png'.format(n2), gray)
        n2 = n2 + 1
    elif c == 51:#3
        cv2.imwrite('img/3/{0}.png'.format(n3), gray)
        n3 = n3 + 1
    elif c == 52:#4
        cv2.imwrite('img/4/{0}.png'.format(n4), gray)
        n4 = n4 + 1
    elif c == 53:#5
        cv2.imwrite('img/5/{0}.png'.format(n5), gray)
        n5 = n5 + 1
    elif c == 27:#Esc
        break
cap.release()
$ tree img
img
├── 0
│   └── 0.png
├── 1
│   └── 1.png
├── 2
│   └── 2.png
├── 3
│   └── 3.png
├── 4
│   └── 4.png
└── 5
  └── 5.png




CNNで学習



MNIST並みに単純な画像なので、普通のCNNで訓練した。
Google colab上でGPUで訓練してから、学習済みモデルをローカルに落として使うことで、簡単に再訓練ができる構造。

def create_model(inputs_):
  o = Conv2D(32, (3, 3), padding='same', kernel_initializer='random_uniform')(inputs_)
  o = channel_spatial_squeeze_excite(o)
  o = MaxPooling2D((2, 2))(o)
  o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
  o = channel_spatial_squeeze_excite(o)
  o = MaxPooling2D((2, 2))(o)
  o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
  o = channel_spatial_squeeze_excite(o)
  #x = Dense(64, activation='relu')(x)
  o = Flatten()(o)
  o = Dense(6, activation='softmax')(o)
  model = Model(inputs=inputs_, outputs=o)
  model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
  return model


def make_train_dataset(img_dir):
    x = []
    y =[]
    label = 0
    for c in os.listdir(img_dir):
        print('class: {}, class id: {}'.format(c, label))
        d = os.path.join(img_dir, c) 
        try:       
            imgs = os.listdir(d)
        except:
            continue
        for i in [f for f in imgs if ('png' in f)]:
            x.append(cv2.imread(os.path.join(d, i), 0))
            y.append(label)            
        label += 1
    return np.array(x)/255, to_categorical(y)


X, Y = make_train_dataset('img')
H = X.shape[1]
W = X.shape[2]
X = np.reshape(X, (len(X), H, W, 1))
model = create_model(inputs)
model.summary()

try:
    model.fit(X, Y, batch_size=batch_size, epochs=num_ep, callbacks=callback,
                          validation_data=(valid_X, valid_y), shuffle=True)
finally:
    model.save('CNN_model.h5')




3.CNN+Arduinoで金庫の鍵の開錠・施錠

Webカメラで深層学習の画像分類できるかテスト



バウンディングボックスに写った画像を推論。

推論結果がkey.txtの番号と一致していたら開錠の指令をシリアル通信でArduinoに送る仕組み。
f:id:trafalbad:20200601112840j:plain




Webカメラで写したPC画面はこんな感んじ。
f:id:trafalbad:20200601113042p:plain

Security.py

import numpy as np
import os
import cv2
import keras
from keras.models import Model
from keras.layers import *
from train_modules.scse import channel_spatial_squeeze_excite
from CNN_model import create_model
import serial
import time

H = 256
W = 256
inputs = Input((H, W, 1))
model = create_model(inputs)
model.load_weights('CNN_model.h5')
key_file = 'key.txt'
cap = cv2.VideoCapture(0)

with serial.Serial('/dev/cu.usbmodem14301', timeout=0.1) as ser:

    while True:
        ret, frame = cap.read()            
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        xp = int(frame.shape[1]/2)
        yp = int(frame.shape[0]/2)
        d = 400 # bbox size
        resize = 256
        cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
        cv2.imshow('gray', gray)
        if cv2.waitKey(10) == 27:
            break
        a = ser.read()
        print(a)
        if a == b'1':
            print('pass')
            gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
            img = np.asarray(gray,dtype=np.float32)  
            img = np.reshape(img, (1, 256, 256, 1))
            img = img/255
            y_pred = model.predict(img)
            c = int(np.argmax(y_pred, axis=1))
            print(c)
            with open('key.txt', 'r') as f:
                b = int(f.read())
            if b==0:
                if c!=0:
                    time.sleep(5.0) # wait 5 second
                    ser.write(b"o")
                    with open(key_file, 'w') as f:
                        f.write(str(c))
                    print('close')
            else:
                if b==c:
                    time.sleep(5.0) # wait 5 second
                    ser.write(b"c")
                    with open(key_file, 'w') as f:
                        f.write('0')
                    print('open')
cap.release()




Aruduinoで画像分類して、サーボモータで鍵を開錠・施錠


シリアル通信で開錠の指令がきたらサーボモータを180度回転させて解錠。

番号が間違ってたら施錠したまま。

Security.ino

#include <Servo.h>

Servo myservo; // Servoオブジェクト宣言

void setup() {
  Serial.begin(9600);
  myservo.attach(9); //9番ピンでサーボモータを動かす
  pinMode(2, INPUT_PULLUP); // Inputモードでプルアップ抵抗を有効
  pinMode(LED_BUILTIN, OUTPUT);
  myservo.write(120); // angle
  digitalWrite(LED_BUILTIN, HIGH);
}
 
void loop(){
  static int flag=0;
  if(digitalRead(2)==LOW){
    flag=1;
  }
  else{
    if(flag==1){
      flag=0;
      Serial.write('1');
      delay(500);
    }
  }
  if(Serial.available()>0){
    char a = Serial.read();
    if(a=='o'){
      myservo.write(120); // angle
      digitalWrite(LED_BUILTIN, HIGH);
    }
    else if(a=='c'){
      myservo.write(20);  // angle
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
}

PythonコードとArduinoコードが用意できたら、ラストはPC側でPythonコードを実行。

Arduino回路のスイッチを押せば推論が行われるので、推論結果がkey.txtと合ってれば解錠。

# PC側で実行
$ python3 Security.py
>>>
Using TensorFlow backend.
StreamExecutor device (0): Host, Default Version
b''
b''
b’1' # 推論実行
pass
5
close # 不一致なので施錠

b''
b''
b’1' # 推論実行
pass
3
open # key.txtの番号と推論結果が一致したので解錠
b''


下の画像は解錠・施錠を交互に行った動画。

この投稿をInstagramで見る

保存用動画

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -

金庫がないのでわかりにくいけど、USBカメラに画像を写せば「ピピっ」と漫画みたいに反応した後、キーの解錠・施錠ができる。

セキュリティのために金庫の鍵の解錠施錠とかいろんな用途に使えそう。

f:id:trafalbad:20200601113617j:plain



Vitis AIでチュートリアルをやった以来、はじめてハードウェアで機械学習(AI)を組み混んでモノを作ってみた。
Arduinoは速度は置いといて、仕組みさえわかれば、かなり簡単に機械学習システムを組み込めた。


参考サイト


Arduinoを用いてサーボモータを制御する