2019年12月8日日曜日

にゃーん(異常検知しようとしてできたのか出来なかったのかよくわからない話)


0.初めに

 この投稿はMice Advent Calendar 2019の8日目の記事です.7日目はコヒロさんの2019年全日本大会で人権を取り戻した話でした.人権...僕も欲しい....



卒論のファイルの存在が虚無な中の人です.こんな時期にブログを書くなんて思っていなかったのですが,現役からの熱い圧力強い要望により書いています.今回は異常検知したかった話です.

1.異常検知とは

 異常検知(anomaly detection)は,"通常の動作として明確に定義された概念に準拠しないデータパターン"(wikiより)を検出することで,いろいろな分野で使われています.
 まあいつもの動き(狙った動き)と違うぞってことを見つけるって話です.

2.PID制御ぶるぶる問題

  マウスに限らず,限界感度法とかでPID制御のパラメータを決めている場合,パラメータを上げすぎると出力が発散(振動)することがあります.
見事に発散

別に適切なパラメータであれば問題はないのですが,どんな環境でも動くパラメータを見つけ出すのは至難の業.なので少し高めのパラメータをデフォルトに設定し,出力が発振しかかったらパラメータを下げるようにしてみました.

3.そうだ,検出しよう

さて,出力の発振を検出して...ってどうやって?ここで出力データをよーく見てみましょう.

出力とにらめっこ

何となくですが,比例制御で使う偏差の絶対値をとって,その値をテキトーな区間(半周期あたり)で積分すればいい気がしてきました.制御が荒ぶっているときは値が大きくなって,安定しているときは値が小さくなりそうです.あとはしきい値決めてぶった切れば勝ち確かな?

検出できた???

4.わたし,(誤検知)気になります!

3.で考えた検出方法にはある問題があります.アンダーシュート/オーバーシュートして安定している場合です.元データでは,加速度がノンゼロの区間の遅れが誤検知されています.勝ち確#とは.(※実はこの方法でもどうにか検出することもできたりできなかったり)

やっぱり駄目だったよ


 となると方針変更です.偏差を何か,入力データの変動のみを表すものに置き換えてやればできそうです.
 ある関数の変動を見るとすれば...ああ,微分なんてあったなぁ.まあ厳密に数値微分なんてする必要はないので,もういっそ引き算だけすればいいでしょう(やけくそ).

5.そして,よくわからないものへ

 さて,結果です.

さっきよりイケそう

差分だけならよさげなので,最初のデータと合わせて確認してみましょう.

イケたのでは
 
   とりあえず,誤検知が無くなりました.めでたしめでたし...なんてことにはならない.
実機にぶち込んで動いたら完全なる勝利です.さて,やってみましょう.

 
 あれ,思ったよりも誤検知多いのでは?とりあえずは安定して動いているので,良しとします.

結論.

 異常検知は難しい...が,マウス君はちょっとだけ安定するようになった.このやり方であってるかどうかは知らないです.もっといい方法があったら教えてください.

 Mice Advent Calendar 2019の9日目はべし先輩の未定です.""未定""です.(大事なことなので2回言いました)











おまけ : 偏差だけで異常検知はできるのか?

 3.で作った偏差だけを使うやり方では誤検知が多いという話をしました.が,実はそこで議論していないことがありました.しきい値です.
 具体的な値を言えば,3.の場合0.1をしきい値としていました.ここでしきい値を0.5に上げて比べてみましょう.

あれ,こっちもできてる

 どうやらうまく検出出来ているようです.ですが,4.の方法のほうがパラメータがテキトーでも成功しやすいと感じました.この辺の話はリソースとかの問題もあるので,一概にどちらのアルゴリズムがいいかを決めることはできませんが,まあ好きな方を選んでください.

おまけ2 : タイトルと内容について

タイトルと内容の決定のながれはこちらのツイート及びコヒロさんの2019年全日本大会で人権を取り戻した話の最後のほうを参照のこと.


©2019 shts All Right Reserved.

2019年9月17日火曜日

TinyFPGA BXをVSCode(+PowerShell)で開発する



 ブログのネタを探してさまよっていた中の人です。夏休みはマウスのコーディングやらバイトやらで忙しい毎日を送ってました。(今夏はアキバへ2桁回買い出しに行ってました。定期がなかったらヤバかった)
 今はこの記事を書きながら環境構築研究を始めています。圧倒的進捗のNASA。



 さて、今回のネタはちっちゃいFPGAをVSCode上で開発する話です。タイトルには(+PowerShell)って入ってますが、pipが入ってる(入る)ターミナルなら何でも大丈夫です。

0.TinyFPGA BXについて&開発の動機

 TinyFPGA BXはLattice Semiconductor Corporation製ICE40LP8Kを使ったFPGAボードで、その名の通り小さいのが特徴です。(AX2っていうもっと小さいのもある)

 サイズ感はArduino nanoやNucleo F303に近いです。
 このボードGUI環境でも開発ができるのですが、なんとPython2.xを使用しているのです!(インストールしようとしたら2.7.11を突っ込もうとしてきた)
 よって今回はPython3.x系で開発環境を整えようとした感じです。

1.今回の環境及び使うもの

  • VSCode 1.38.1
  • Python 3.7.4
  • pip 19.2.3
  • TinyFPGA BX
  • USB micro B ケーブル
今回はVHDLではなくverilogを使用します。(ビルドツールがVHDL未対応らしい)

2.インストール

最初は、TinyFPGA BX User Guideに従ってインストールを進めましょう。私が環境構築をしていて詰まったところは、

apio drivers --serial-enable

の前に

apio install drivers

をすることでしょうか。この辺りはそこまで難しくないはずです。
この時、一緒にverilogのExtensionをVSCodeに入れておくと.vが読みやすくなります。

Atomを入れろとなっていますが、今回は無視します。


3.実機実装

 FPGAの開発では、大体の場合シミュレーションを走らせてから実機実装しますが、面倒なので飛ばします。
とりあえず、サンプルコードを使いたいので、TinyFPGA BXのリポジトリをどうにかこうにかして落としましょう。落として来たら、apio_templateをフォルダコピーしておきます。(書き換えると面倒なので)

 コピーしたフォルダをVSCodeで開いたら、ターミナルを出して、

apio build

と打ちましょう。.vが間違っていなければビルドが通り、hardware.binが生成されているはずです。(ほかのファイルも同時に生成)



 ボードへの書き込みは、ケーブルでPCとつなぎ、リセットボタンを一回押した状態で、

tinyprog -p .\hardware.bin

と打てば書き込まれます。


書き込みがうまくいかないときは、User Guideの下の方に書いてあるブートローダのアップデートをやってみましょう。

参考:tinyFPGA-BX boardにTerminalのみでbuildとボードへの書き込みを行った




©2018 shts All Right Reserved.

2019年7月23日火曜日

matplotlibでマウスやらキーボードのイベントを取得する


 ブログ更新をさぼりにサボっていた中の人です.研究室とバイトとマウスに明け暮れたら,いつの間にか金と時間が溶けていました.
 私事ではございますが,大学院に進学することが決まりもう少しだけ学生でいられそうです.やったね(白目)

 それはさておき,今回はmatplotlibでマウスやらキーボードのイベントを取得する話です.

概略
 みんな大好きmatplotlibなのですが,実はマウスやキーボード,その他もろもろのイベントを拾うことができます.これによってグラフに表示されているデータをいじったりすることが可能です.

イベントの種類
 トリガとして扱えるイベントは以下の通り.
  1. button_press_event       : マウスのボタンが押されたら
  2. button_release_event    : マウスのボタンが離されたら
  3. draw_event                   : キャンバスが更新されたら
  4. key_press_event           : キーボードのボタンが押されたら
  5. key_press_event           : キーボードのボタンが離されたら
  6. motion_notify_event      : カーソルが動いたら
  7. pick_event                    : キャンバス内のオブジェクトが選択されたら
  8. resize_event                 : figureキャンバスがリサイズされたら
  9. scroll_event                  : マウスホイールが回ったら
  10. figure_enter_event        : カーソルがfigureの中に入ったら
  11. figure_leave_event        : カーソルがfigureの中から出たら
  12. axis_enter_event           : カーソルがaxisの中に入ったら
  13. axis_leave_event           : カーソルがaxisの中から出たら
参考:https://matplotlib.org/users/event_handling.html?highlight=event%20handling%20picking

使い方
 イベント発生時に動かしたい関数を事前に準備しておき,canvasに紐づける感じです.

#クリック時にカーソルの座標を取得する
def Click(event):
    print("x=%d, y=%d" % (event.x, event.y))

#紐づける
fig.canvas.mpl_connect("button_press_event", Click)

2020/8/17 追記:上記のmplの部分がmlpだったのを修正

eventはマウスカーソルの位置や選択したデータの値,マウスやキーボードの押したボタンなどを取得するために使うやつです.取得できるのは以下の通り

  1. x, y : canvas内のピクセル
  2. inaxis : カーネルがaxisを超えたかどうか(True/False)
  3. xdata, ydata : 選択されたデータ
  4. button : 押されていたマウスのボタン(None, 1, 2, 3, 'up', 'down')
  5. key : 押されていたキーボードのボタン(None, すべての文字,'shift', 'win', 'ctrl')


最後に
 便利機能が結構仕込まれているmatplotlibです.公式のサンプル等もあるので,そちらも参考にするといいと思います.


©2018 shts All Right Reserved.

2019年6月11日火曜日

Raspberry Pi 2とZero WでFCNを走らせてみた




れぽーよなんて大嫌いな中の人です.
今回は秋月で安売りしてたことを知ってわざわざ学校を抜け出して入手した
たまたま入手できたRaspberry Pi 2とZero Wを比較する話です.



今回この二つのボードを使っていくわけですが,その前にスペック比較を
Pi Zero
  • 1GHz, single-core CPU
  • 512MB RAM
Raspberry Pi 2
  • A 900MHz quad-core ARM Cortex-A7 CPU
  • 1GB RAM

シングルコアの性能はZeroのほうがいいかも.ただ4コアのPi 2のほうがトータルで速そうだけどどうなんだろう?

というわけで実際にプログラムを実行して比べてみましょう.実行するのは最近いじってきたFCNの最新版です.(レイヤーを4層に減らしたらなぜか精度が上がったのはなんででしょうか...)

Pi 2 : 0.020[s/frame]
Pi Zero W : 0.060[s/frame]
(それぞれ100回平均)

Pi 2の速さが圧倒的すぎる.(目標の30fps超えちゃってるヨ...)
個人的にはPi 2がもう少し遅いと思ったんですが,予想を超えてきましたね.Pi Zero Wはレイヤー数を落とした関係で15%ぐらい速くなったのはうれしいです.

まあ今更Pi 2かよって話ですが,予想以上に高速化できたのでよかったです.(Pi 3Bとかだともっと速くなりそう)


参考
https://www.raspberrypi.org/products/raspberry-pi-zero-w/
https://www.raspberrypi.org/products/raspberry-pi-2-model-b/

STM32のSPI+DMAで詰まった話(HAL使用・STM32F413)


 最近いろいろあってマウスお預け状態の中の人です。MPU6500+AS5047をSPI一系統で読もうとするとSPIのモードやらクロック周波数やらを変える必要がありますが、最も問題になるのが送受信のデータフォーマットです(MPU6500は8bit、AS5047は16bit)。今回はその違いにより結構影響を受けた話&その解決策です。


1. DMA転送を使ってSPI通信をする

 STM32ではDMA転送を使ってSPI通信をすることができます。で、こいつを使う際にはSPIの設定のほかにDMAの設定を行わなくてはなりません。
 ...といってもDMAの設定はNormal/Circularの設定と、転送データ幅のみです。今回はSPIの要求をいちいち出す方式にしたので、Normalにしました。 問題となったのは転送データ幅の部分です。

2. 転送データ幅とSPIのデータサイズ

今回使用したAS5047とMPU6500ではSPIのモードや一度にやり取りするデータサイズが異なっていました。

AS5047 DataWidth:16bit CPOL:0 CHPA:0 -> Mode:1
MPU6500 DataWidth:8bit CPOL:0 CHPA:1 -> Mode:0

マイコンのピン数の問題で、SPIは1系統に絞ったので通信するたびにモードを変更する必要があります。こちらはHAL_SPI_Initでどうにかなったのですが、問題はDMA転送をどうするかです。
で、以下の二つを試しました。

1): DMAのデータ幅を16bitに設定して、AS5047と通信する
2): DMAのデータ幅を8bitに設定して、AS5047と通信するときは2Byte分送りつける

3. 試した結果

1) データ幅16bit




  こんな感じで設定して...


こんな結果が出てきました。上側のエンコーダの値はあっていそうですが、当然ジャイロからは値がかえって来ません(HAL_DMA_Initか何かで設定をかえれば読み取れるとは思いますが)。

2) データ幅8bit



こちらもこんな感じで設定して...


結果はこんな感じ。設定を変えずとも送るデータ数を倍にすれば読み取れそうです。ジャイロも読み取れています。

4. で、どうするよ

設定を変えるのが面倒だったので、今回はデータ幅8bitでやっていこうと思います。


©2019 shts All Right Reserved.

2019年5月22日水曜日

Raspberry PiでDRV8830を動かした話

 研究室課題の論文輪講やマウスの通信線の断線と圧着に四苦八苦している中の人です。
 数か月前に投稿したこちらの記事ですが、DRV8830だけ自分でコードを書いた?(ライブラリ使っただけでは?)ので少し解説していきたいと思います。(数か月放置してたのは内緒)


ICについて

DRV8830は1chのモータドライバで、最大の特徴はI2Cのみで出力電圧の制御を行う点です。別段精度の良いPWMが出ればいいのですが(Raspberry Piでも結構精度の高いPWMを出すことのできるライブラリとかもあるようです)、あまりあてにならない部分があったためI2Cが使えるこのモータドライバを使いました。
 

回路について


 DRV8830周りの回路はこんな感じです。データシート上ではVCCとGNDPWR間には0.1uF(最小)を挟めと書いてありますが、どうやら容量不足になりやすいらしいので追加で10uFを載せてあります。ISENSEには0Ω抵抗を入れましたが、ここの抵抗を変えることで最大電流を変えることができます。FAULTnは障害通知用の端子で、障害が発生するとLowになるのでLEDはこの向きでつけてあります。
 A0/A1はアドレス設定用の端子で、Low/NC/Highの3パターン^2=9通りで設定し、それに応じて書き込み用のアドレスと読み取り用のアドレスが変わります(データシートのp.12)。ここで注意したいのが、データシートでのアドレスがプログラム上のアドレスと一致しない(正確には1つシフトされている)点です。これについては後述。
 SDA/SCLは一応10kΩのプルアップ抵抗を挟んであります(多分いらない気もする)。

PCBについて


 今回は秋月とかで販売しているブレークアウトボードを使わずに自前でプリント基板を設計したのですが、このICの裏面にはサーマルパッドがあるため、普通のはんだでやるには少し工夫が必要でした。ざっくりいえばサーマルパッドに穴をあけ、裏側から熱を与えてはんだ付けを行う方法です。注意してほしいのが、穴からはんだを流し込むのではなくICをつける前にはんだで穴をふさいでおき、その後はんだごてを裏から当てるということです(参考:QFNパッケージをリフローオーブンを使わずにハンダ付け)。配線は...酷いです。

この配線は何があっても参考にしないこと。
(こんなにひどい状態でも動くあたりがすごい)

プログラミングについて

 今回はPythonから操作することとしました。I2Cを使う場合、幸運なことにPython-smbusが提供されているのでこいつを使うことにします。

#-*-coding:utf-8-*-
import smbus
import time

DRV8830_addr = 0x60
i2c = smbus.SMBus(1)
min_pow = 0x06
max_pow = 0x26

try:
    #test DC motor
    for i in range(min_pow, max_pow + 1, 1):
        i2c.write_byte_data(DRV8830_addr, 0, (i<<2)+1)
 time.sleep(0.1)

こんな感じで書けば動くはず。データシートではA0/A1をLowにした場合、書き込み時のDRV8830_addrは0xC0になるはずです。ですがI2Cの仕様上、7bitのICを区別するためのアドレスにR/W用のビットをくっつけているため、実際には0xC0 >> 1 = 0x60となります。最後のR/W用のビットはライブラリ側でつけられるようです。出力電圧と回転方向に関してはデータシートのp.13を見つつ、D7-D2に電圧、D1とD0に回転方向が来るように設定しました。

最後に

そこまで難しいわけではありませんが、データシートと違うじゃんみたいなところもあったのでそこだけ気を付ければ大丈夫な気がします。で、結構重要な話がありまして、実はこのIC...



単品なくなってたのね...(´・ω・`)
モジュールの販売は続けているようなのでほしい方はぜひそちらをお求めください。(この記事の大半意味なくなったのは?)


©2018 shts All Right Reserved.

2019年5月12日日曜日

CubeIDE+STM32F413CHでprintfを使う話



 PYNQかZYBO Z7-20が欲しい中の人です(誰かくれないかなぁ)。前回からの続きで、printfでfloatを表示させる話です。


 マイコンのデバッグでは、UARTを出力先に指定したprintfを実行させることがあります(なんか変な感じするけど)。STM32の場合syscalls.cの__io_putchar()を書き換えてUARTに吐き出したり(weak定義に関してはこちらから)、_write()関数の中身を書き換えることで実装するのが楽なようです(こことかここを参照)。

1. とりあえずprintfをUARTで使えるようにする

*UARTが使えている前提で話を進めていきます。HAL_UART_Transmitとかが使えていれば大丈夫。

 CubeIDEでも同様にしてprintfの出力先をUARTに変更できます。/(プロジェクト名)/Srcの中にsyscalls.cがあるはずなので、それを開き_write()のところまで行きます。で、その中身をこんな感じで書き換えます。
__attribute__((weak)) int _write(int file, char *ptr, int len) {
 int DataIdx;

 for (DataIdx = 0; DataIdx < len; DataIdx++) {
//  __io_putchar(*ptr++);
  HAL_UART_Transmit(&huart1, ptr++, 1, 1);
 }
 return len;
}

 このままコンパイルすると"huart1ってなんだよ"って怒られるので、ヘッダと変数の宣言部分に

/* Includes */
#include 
#include 
#include 
#include 
#include 
#include 
...
と書き換えておきます。これでprintf()を実行するとTerminalとかになんか出てくるはずです(%fに関しては後述)。

2. %fを出力する

さて、ここまででprintfは%fフォーマットを除いて動くようになったはずです。で、%fはこのままでは動きません(TrueStudioやSW4STM32同様)。原因はリンカの設定なのでここを修正していきます。

*ここからの話はSTM32Cube FW_F4 V1.24.1での話です(2019/05/10時点で最新)。バージョンの違いに注意してください。

 とりあえず、リンカオプションの設定から。



Project->Propertiesを選択し、C++/C Buildを選択します。


 次にSettings->MCU GCC Linkerを選択し、Other flagsに"-u _printf_float"を追加します。
 ここまで終われば動きそうな気がしますが、うまくいかない(なぜいきなりTrueStudioやSW4STM32をNRNDにしたし)。この問題に気が付いたのがGWの中ごろで、そこから1週間ほどとかしました。(´・ω・`)
 この原因についてはまだわからないこともありますが、ここを参考にすることで%fを有効化できました。手順としては、STM32F413CHUX_FLASH.ldの_estackの値を0x2004FFFFから0x20050000に書き換えるだけです。この値に関しては、データシートの21ページに書いてある

"CPU can access SRAM1 memory via S-bus, when SRAM1 is mapped at the address range: 0x2000 0000 to 0x2003 FFFF. CPU can access SRAM2 memory via S-bus, when SRAM2 is mapped at the address range: 0x2004 0000 to 0x2004 FFFF."

を見ながら決めました。_estackにはRAM領域の終端アドレスを指定するのですが、どうやら奇数だと動かず、偶数でないと動かないらしいです(この辺検証とかもできていないので怪しいですが、0x20050000や0x2004e200、0x20040000の場合は動き、0x2003FFFF、0x2004FFFFでは動いていないことは説明できそう)。

2019/05/13追記
@osaboh さんからの指摘で、Application Binary Interface(ABI)の呼び出し規約の中に

5.2.1.1 Universal stack constraints At all times the following basic constraints must hold:
(中略)
 SP mod 4 = 0. The stack must at all times be aligned to a word boundary.
(中略)
5.2.1.2 Stack constraints at a public interface The stack must also conform to the following constraint at a public interface:
 SP mod 8 = 0. The stack must be double-word aligned.

とあるので、アドレスは4 or 8 Byteで割り切れる必要があるそうです。

参考:Procedure Call Standard for the ARM Architecture

謝辞

@osaboh さん@sora_siro1 さん、@Sakurai_Tatuyosさんには%fが表示できない件に関して、動作検証や改善手順の提案などで有益な情報をいただきました。本当にありがとうございます。


©2018 shts All Right Reserved.

2019年5月9日木曜日

朝起きたらTrueStudioがNRNDだったので、CubeIDEに乗り換えた話


 春の連休は何してましたか?寝てましたか?もう一度大型連休くれませんか?

 どうも中の人です。今回は、"朝起きたらTrueStudioがNRNDだったので、CubeIDEに乗り換えた話"です。CubeIDEに関しては、こちらを参照してください。基本的にTrueStudioにCubeMXをくっつけた感じですが(SW4STM32はいじったことないからわからない)、詰まったところもあったのでとりあえずカキカキしておきます。

1. ダウンロード&インストール


 まずはここからインストーラを落として、とりあえずインストールします。TrueStudioは残しておいてもとりあえず大丈夫でした。

2. プロジェクト作成


 インストールが終わったらプロジェクトを作成しますが、プロジェクトの作成段階でCubeMX用のファイル(.ioc)も同時に生成されるあたりがTrueStudioとは異なる点になります。



3.さあ、動かしてみよう


 プロジェクトの作成が終わると見慣れたCubeMXの画面が出てくるので、テキトーに設定しておきます。初期設定だとCtrl+sで保存するとGenerate Codeするかどうかを聞かれるので、Yesに入れておきましょう(手動でもGenerate Code可能)。あとはEclipseのエディタ画面が出てくる(またはmain.cあたりをクリック)ので、コードをカキカキすればOKです。

 TrueStudioにはRunボタンとDebugボタンがありましたが、CubeIDEではDebugボタンのみになっています(なんでだろ)。SWDで書き込むときはDebugボタンをポチりましょう。(UARTは使ってないんでワカラン)

 とりあえずこれでLチカはできるはずです。ただしこのままだとprintf周りができていないので、その話を今度しようと思います。
©2019 shts All Right Reserved.

2019年5月3日金曜日

ZYBO Z7 (Z7-20)でHDMI Paththroughを作る(IP使用)


 精神統一にははんだ付けとよく言われますが、

ただし0603(metric)、テメーはダメだ

 どうも中の人です。マウスの進捗はぼちぼちです。朝起きたらTrueStudioがNRNDになってCubeIDEが登場していましたね。そのうち部内向けに記事でも書こうかなぁ(現状printfでfloatが吐き出せないのでツラい)。
 今回は研究室に入って触り始めたFPGAの話です。単にFPGAといってもいろいろあるのですが、Digilentから発売されているZYBO Z7を使用して、HDMIの入力をただ出力するだけの回路を作成しようと思います(ZYBO Z7についてはこちらを参照)。まあ備忘録的なものなので、動かなくても責任は負えません。はい。



 このボードには入力用と出力用のHDMIが別々についており入出力用の回路を組むことで、FPGA(が入ったSoC)上でのデータ処理ができます。しゅごい。とりあえず今回組んだ回路はこんな感じ。

 回路構成自体はここのP.22 Figure 3.2を参照し、出力サイズは720pのみに限定しました。ここからは各IPの設定になります。


 Clocking Wizardの設定。Reference Clockとして200MHzを供給しています。(入力の125MHzは外部クロックを引き込んでいるためで、PSから持ってくるなら100MHzになるはず)


 DVI to RGBの設定。720pを使用しますがTMDSのClock Rangeは< 120MHzに設定しないと動きませんでした(なんでだろ)。


 Video in to AXI4-Streamの設定。Click ModeをIndependentにするのを忘れずに。FIFO Depthは適当に4096を選択。


 AXI4-Stream to Video Outの設定。Video in to AXI4-Streamの設定に合わせる形で設定。Timing ModeはSlave、Hysteresis Levelは推奨されている値のうちから12を選択。


 RGB to DVIの設定。DVI to RGBと同じく、TMDS Clock Range は< 120MHzにすること(入力と出力が同期している関係)。


 Constant基HDMIのHPDの設定。常にHIGHになるように設定。

2019/05/06追記:Video Timing Controlerの設定を載せ忘れてました...。



 ここまでで大体の設定が終了し、ここから配線作業になります。今回は時間を溶かしまくったPixel ClockとReference Clock、AXI4-Stream to Video OutのVTG_CEを置いておきます。




 制約ファイルに関しては、ここの必要なところだけ持ってきた感じです。あとはテキトーにBitstream生成すれば動く...はずです(動かなかったらすんません)。

2019年4月8日月曜日

SonyのNeural Network Libraries(NNabla)でGPUを使ってみる


 マウスの進捗は何も出ていないのに、パーツ代で貯金が吹き飛んで行っている中の人です。今回はNNablaでGPUを使う話です。

 前回まででCPU上での学習及び推論ができるようになりました。ですが学習などは結構時間がかかることが多いですよね(18コア36スレとかでやればまた違うのかもしれませんが)。そこで、どこのご家庭にも転がっているであろうNvidiaのGPUをNNablaで使い、高速化してみようというのが今回の主題。

 NNablaでGPUを使う場合、CUDA及びcuDNNのインストールが必要になります。ここで注意したいのがCUDA及びcuDNNのバージョンで、ここを確認しておきましょう。

 次にNNablaからCUDA/cuDNNを利用するためのライブラリのインストールですが、

pip install nnabla-ext-cudaxx

で終了(cudaxxはCUDA/cuDNNのバージョンで変わる。ここを参照)。

 最後にコードの変更については(コードは前々回を参照)、ライブラリの追加とデフォルトの環境を設定するコードのみになります。ライブラリは

環境設定は

となります。一応これだけで動くはずです...。

参考:20171212 Sony Neural Network Libraries and Console for GTC Japan 2017

p.s.
突然GPUが読めなくなる(カーネルモジュールのロードがうまくいかなくなる)ことがあったんですが、どうやらセキュアブートが有効になっている環境ではロードさせないようになっているようです(回避すればいいのですが)。

©2018 shts All Right Reserved.

2019年3月27日水曜日

SonyのNeural Network Libraries(NNabla)でFCNもどきを動かす



 いろいろなことを同時に走らせて、全部中途半端になっている中の人です。マウスの設計とFPGA入門、バイトの教材開発を同時に進めるのはやめておきましょう。前回に引き続き今回もNeural Network Libraries(NNabla)でFCNを動かす話です。
 今回は実装編ですが学習編と結構似たコードになるので、学習用のコードから流用できます。やったぜ。  とりあえず必要なライブラリ群を読み込みます。
#-*-coding:utf-8-*-

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF

import os
import sys
import cv2
import numpy as np

 学習ではsolversやinitializer、データセットを載せるためのdata_iteratorをimportしていましたが、今回は不要なので削除。データセット(npz)の読み込みはこの前と同じものを使用します。学習時になかったものとしては各ピクセルに割り当てられたラベルを画像に変換する関数を実装しました。(もっと頭良く組めるはずなんですが、僕には難しいです。)

def label2img(label):
    """
    * 1 | road        | blue 
    * 2 | out of road | green
    * 3 | line        | red
    * 4 | backgrownd  | black
    * 5 | object      | yellow
    * 6 | other rane  | white
    *
    * out   : 3-dimensional numpy array([ch][y][x])
    * buff  : 3-dimensional numpy array([1][y][x])
    * label : 4-dimensional numpy array([1][6][y][x])
    * color : [[B,G,R]]
    """
    color = np.array([[255,0,0],[0,255,0],[0,0,255],[0,0,0],[0,255,255],[255,255,255]])
    buff = np.argmax(label, axis = 1)
    out = np.zeros((3,buff.shape[1],buff.shape[2]))
    
    for i in range(len(color)):
        out[0][buff[0] == i] = color[i][0]
        out[1][buff[0] == i] = color[i][1]
        out[2][buff[0] == i] = color[i][2]

    return out.astype(np.uint8)

 ネットワークは学習時と同じものを使用してください。ここまでで必要な関数がそろいました。では実際に推論を走らせるコードに移ります。


try:
    #Loading Dataset
    train, teach = load_data(NPZ,"img","img_test")
    
    #Set Params to network
    nn.clear_parameters()
    x = nn.Variable(train[0:1].shape)
    y = network(x, test=True)
    t = nn.Variable(teach[0:1].shape)
    print("x:",x.shape, "y:", y.shape, "t:", t.shape)

    #Search if Params file or not
    if os.path.exists(param_file) == True:
        nn.load_parameters(param_file)
    else:
        print("Parameter file was not found!!!")
        sys.exit()

    #Test
    for i in range(train.shape[0]):
        x.d, t.d = train[i:i+1], teach[i:i+1]
        y.forward()

        input_img = x.d[0].transpose(1,2,0).astype(np.uint8)

        predict_img = y.d
        predict_img = label2img(predict_img).transpose(1,2,0)
        
        ground_truth_img = t.d
        ground_truth_img = label2img(ground_truth_img).transpose(1,2,0)
        
        show_img = np.concatenate((input_img, 
                                   predict_img, 
                                   ground_truth_img,
                                   ),axis=1)
        cv2.imshow("imshow", show_img[::-1,:,:])
        
        cv2.waitKey(1)

except:
    import traceback
    traceback.print_exc()

finally:
    input(">>")
    cv2.destroyAllWindows()

 load_data関数で推論させるデータを取り込みます。今回は入力画像、推論結果に加えて教師画像も載せたいので、ついでに取り込みます。
 その後パラメータの読み込みまでは学習時とほぼ同じです。学習済みパラメータの読み込み部のみ、パラメータがなかったらプログラムを終了できるように変えてあります。
 for分の中では実際に推論をしています。t.dに教師画像を入れてありますが、推論のみを行うのであれば不要になります。
 また推論自体はy.forward()で行われ、y.dに推論結果が入っています。あとの部分はcv2での画像表示に関する部分です。そこまで難しくはないはずですが、cv2で画像を表示させる場合、データ構造がNNabla側の(1, color, row, col)から(row, col, color)に代わるのでそれをimg2label関数とnp.transposeで変換しています。

 ここまでで推論部分が完成しました。学習部分さえできればその応用で推論ができるのは結構楽ですね(Chainerも似てるけど)。あとはおまけでGPUあたりのお話ができればいいかなぁ。
©2018 shts All Right Reserved.

2019年3月9日土曜日

SonyのNeural Network Libraries(NNabla)でFCNもどきを学習させる







 研究室配属も無事終了した中の人です。研究室の本格始動は4月からなので、3月中はマウスの設計やFPGA、NNablaと戯れたいと思います。さて、前の投稿ではNeural Network Libraries(NNabla)でnumpy-arrayの読み込みを行いました。そこで今回は読み込んだデータを使用して、FCNもどきの学習を行いたいと思います。

今回のコードもこちらのサイトを参考に実装していきます。

NNabla(Neural Network Libraries)のシンプルチュートリアルをPython3.5でやってみる。

NNablaで学習を行う場合、
  • データセットの読み込み
  • モデルの構築
  • 損失関数の定義
  • 学習部の実装
が必要になります(この辺はほかのライブラリとあまり変わらない)。まずはデータセットの読み込み、と見せかけて必要なライブラリ群のimportとデータセットを入れたファイル名、パラメータファイルから

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import nnabla.solvers as S
import nnabla.initializer as I
from nnabla.utils.data_iterator import data_iterator_simple

import os
import numpy as np

NPZ = "data/img2train_data_test.npz"
param_file = "nnabla_parameters.h5"

 データセットの読み込みはこんな感じで実装
#Read .npz File
def load_data(npz, input="img", teacher="img_test"):
    print("loading dataset for training")
    #loading data from NPZ file
    with np.load(npz) as data:
        tmp_train = data[input]
        tmp_train_label = data[teacher]
    
    print(tmp_train.shape)
    print(tmp_train_label.shape)

    return tmp_train, tmp_train_label

#Loading Data
def data_iterator_my_dateset(train, teach, batch_size=8, shuffle=False, rng=None):
    def load_func(index):
         """Loading an image and its label"""
         img = train[index]
         label = teach[index]
         return img, label
    return data_iterator_simple(load_func, train.shape[0], batch_size, shuffle, rng, with_file_cache=False)

 .npzでデータセットを保持している関係で、それを読み込む関数を実装しました。下のデータセットの取り込みは前回と同じ。
 モデルはこんな感じの奴。
#Define network
def network(x):
    with nn.parameter_scope("cnn"):
        with nn.parameter_scope("conv0"):
            h = F.relu(PF.convolution(x, 6, (2,2)))
        with nn.parameter_scope("deconv0"):
            h = F.relu(PF.deconvolution(h, 3, (2,2)))
        with nn.parameter_scope("conv1"):
            h = F.relu(PF.convolution(x, 6, (4,8), stride=(2,4)))
        with nn.parameter_scope("conv2"):
            h = F.relu(PF.convolution(h, 16, (9,9), stride=(3,3)))
        with nn.parameter_scope("deconv2"):
            h = F.relu(PF.deconvolution(h, 16, (9,9), stride=(3,3)))
        with nn.parameter_scope("deconv1"):
            h = PF.deconvolution(h, 6, (4,8), stride=(2,4))
    return h

 Raspberry Piでそこそこ速く動かそうと思うと、こうなりました。ここはもっと手を入れる予定。損失関数はSigmoid Cross Entropyを使用。

#Define Loss Function
def loss_func(y, t):
    loss_f = F.sigmoid_cross_entropy(y, t)
    return loss_f
実の話をすると損失関数に最初はSoftmax Cross Entropyを使っていたのですが、要素(今回はピクセル)ごとのクラス分類がうまくいきませんでした。(ここで1週間溶ける)
 そこで公式のDCGAN実装を見たところ Sigmoid Cross Entropyを使っていたので、使ってみたところうまく学習できました。やったね!
(1つのピクセルが各ラベルに属するかどうかを見ているのでこれでもよいってだけで、Softmax Cross Entropyでも原理的にはできるはず)(参考:損失関数の紹介 - renom.jp)
 学習部はこんな感じ。
#Define Training Function
def training(xt,tt,data,loss,steps):
     solver = S.Adam()
     solver.set_parameters(nn.get_parameters()) # Set parameter variables to be updatd.
         
     for i in range(steps):
         xt.d, tt.d = data.next()
         solver.zero_grad() # Initialize gradients of all parameters to zero.
         loss.forward()
         loss.backward()
         solver.update()
         if i % 100 == 0: # Print for each 10 iterations
              print(str(i) +":" + str(np.mean(loss.d)))


 教師データ読み込み→とりあえず勾配を初期化→推論する→誤差逆伝播→パラメータを更新、を繰り返す感じ。後はこれらを実行していく形になります。

try:
    #Loading Dataset
    train, teach = load_data(NPZ,"img","img_test")
    data = data_iterator_my_dateset(train, teach, batch_size=30, shuffle=True)

    #Set Params to network
    nn.clear_parameters()
    img, label = data.next()

    x = nn.Variable(img.shape)
    y = network(x)
    t = nn.Variable(label.shape)
    loss = loss_func(y, t)

    #Search if Params file or not
    if os.path.exists(param_file) == True:
        yn = input("parameter is exist! use this? y/n : ")
        if yn == "Y" or yn == "y":
            nn.load_parameters(param_file)

    #Training
    training(x,t,data,loss,500)
    
except:
    import traceback
    traceback.print_exc()

finally:
    #Save Params
    nn.save_parameters(param_file)
    input(">>")

パラメータのクリアをモデル構築前にするのを忘れなければうまく動くはず...です。次回は実装になるはず。この記事長すぎるのでは?
©2018 shts All Right Reserved.

2019年3月1日金曜日

SonyのNeural Network Libraries(NNabla)でnumpy_arrayの読み込み



研究室がいまだ決まってない(後数時間なんですけどね)中の人です。前に投稿したSTM32でduty比をDMAで転送する話でも少し触れた、SonyのNeural Network Libraries(NNabla)を使って、Fully Convolutional Networksをやってみようと思います。



 NNablaは元々Sony内部で使われていたライブラリを公開したものらしいです。(詳しくはここを見てくれ)実の話をすると、本来はNeural Network Console(NNC)を使いたかったのですが(GUIでネットワークを記述するとか面白そう)、npzファイルで保存されている手持ちのChainer向けデータセットを転用したかったので今回はNNablaを使ってみることにしました。
 NNablaは組込み向けを意識しているのか、量子化に関するレイヤーが標準で実装されているので、今までChainerでやってきたやつを転用しようと考えた次第です。

 とりあえずチュートリアルと先人たちの解説を読みあさりつつ、サンプルをやってみました。今回はPython3.6のIDLEを使用しているので、公式チュートリアルコピペだと動かないことがありましたが、IDLEで実行している人がいました。ラッキー


NNabla(Neural Network Libraries)のシンプルチュートリアルをPython3.5でやってみる。


 このコードを参考にしてtiny_digitsによる学習はできたのですが、今度は独自のデータセットに対応させる必要があります。
いろいろ考えても進まないので、このサンプルコードで使用されているtiny_digitsのデータ構造を見ていきます。...といっても実態はnumpy-ndarrayなので、shapeで配列の各次元の要素数を確認しました。(参考:The Digit Dataset)

入力(image):(1797,8,8)
出力(target):(1797,)

ここで、オレオレデータセットのフォーマットを確認すると、

入力:((データ数),3,32,64)
出力:((データ数),6,32,64)

おっ、これだったらオレオレデータセットも読み込めそう...なのか?次元数違うけど大丈夫か...?というわけで、NNablaの公式リファレンスに飛び込んでみる。すると、

nnabla.utils.data_iterator.data_iterator_simple

の欄にload_funcの例が載っており、それが(ch, h, w)の画像の読み出しになっていました。これはオレオレデータセットの構造とほぼ同じなのでいけそうです。
 とりあえずそれを参考にしつつデータ読み込みの関数を書いてみました。

def data_iterator_my_dateset(train, teach, batch_size=8, shuffle=False, rng=None):
    def load_func(index):
         """Loading an image and its label"""
         img = train[index]
         label = teach[index]
         return img, label
    return data_iterator_simple(load_func, train.shape[0], batch_size, shuffle, rng, with_file_cache=False)

とりあえずデータ読み込みはこれでできました。やったぜ。この辺の話をやっている人をインターネッツでは見かけなかったので、結構苦労しました。(これだけで3h吹き飛んでる)
 多分今度の話は学習部分(というよりも使い方がよくわからなかったsoftmax_cross_entropy)になりそうです。


©2018 shts All Right Reserved.

2019年2月21日木曜日

PWMのduty比設定をDMAで行う(STM32F4)



 絶賛ひきこもり生活中の中の人です。ひきこもっている間何してたかといえばGT6、Twitter巡回、Youtube再生業といった時間の浪費とてもアクティブなことです。はい。まあ、それ以外だとSTM32をいじったり、nnablaってライブラリを触ってました。今回はSTM32の話です。(nnablaはそのうちブログ書くかも)


 STM32で複数のPWMを出そうとする時(唐突)、大体は
 
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK) {
    Error_Handler();
}

if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2) != HAL_OK) {
    Error_Handler();
}

if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3) != HAL_OK) {
    Error_Handler();
}

while (1) {
     for (i = 0; i < 100; i++) {
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 100 - i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);
         __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 100 - i);
    }
}

みたいにTIMの初期設定→PWMのduty比をテキトーに変えていく感じになりますよね。
まあこれでもいいんですが、CubeMXで設定欄を見ていたら

?

何なんだと、こいつもDMA設定あるのかよ...。
というわけで本題。(今までの前振りの時間を返せ!)
DMAを使ってPWMを吐き出してみましょう。まずはCubeMX側の設定で、TIM本体の設定は同じ。今回はTIM1を使用します。



TIM1のDMA設定。この辺よくわかってないんで、ワタシエスティーエムチョットデキル方がいたら教えてくだせぇ。
次はプログラム側での話。どうやらdutyを設定する関数がなくて、PWMスタートとduty比設定が一緒って感じです。(求ム、有識者。コノ辺、ワカラン。)

while (1) {
    for (i = 0; i < 100; i += 2) {
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *) &i, 1);
        htim1.State = HAL_TIM_STATE_READY;
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t *) &j, 1);
        htim1.State = HAL_TIM_STATE_READY;
        HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_3, (uint32_t *) &j, 1);
        htim1.State = HAL_TIM_STATE_READY;
    }
}

TIM1のStateをいじっているのは、duty比設定用の関数がHAL_TIM_STATE_READYの時のみduty比のセットを行えるため...らしい。イーノック、こんな解説で大丈夫か?
これで動くはず...です...。少なくとも僕のNucleoは動いたんだ...。





おまけ:
CeleronG3900 + 4GB RAM + GTX1060(6GB)って変態構成の自作PCをどうにかしたい(後赤く光るファンを追加したい)


©2018 shts All Right Reserved.