四畳半テクノポリス

地方国立大学のクズ大学生の吐き溜めです。やる気をください

「mbedとFPGAで作った AIチップもどき」 mbed祭り 2019@春の名古屋 発表スライド

mbed祭りで発表に使用したスライドを公開します。

 

発表で登場したニューラルネットワークリポジトリです。

github.com

LPC1768で動作するニューラルネットワークのデモコードです。

os.mbed.com

やったー!PytorchでVGG11できたよー!

目的

大学院への進学に備えディープラーニングのお勉強をしていたのですが、Pytrochのチュートリアルに飽きてきたのでVGG11を実装してみました。VGG11というのは畳み込み8層と全結合3層からなるニューラルネットワークです。兄弟にVGG16とかも居ます。最初はVGG16を作ったが学習が遅すぎて飽きちゃったので無かったことにする。

やったこと

 今回はそのVGG11を使ってMNISTの分類をやってみました。MNISTというのはみんなご存知でしょうが手書き数字の分類を行うためのデータセットです。28x28の手書き文字が何万枚も含まれています。

 それに対しVGGは画像分類のためのネットワークです。こちらはImageNetのような写真の分類を目的としたニューラルネットワークです。このVGGはPytorchにはデフォルトで含まれていて転移学習で使うことが出来るのですが、今回は僕のGTX1070の火力を試してみたかったので自力でガバガバ実装しました。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.upsample = nn.Upsample(scale_factor=8, mode='nearest')
        self.conv1 = nn.Conv2d(1, 64, 3, padding=1)
        self.pool1 = nn.MaxPool2d(2, 2)

        self.conv2 = nn.Conv2d(64, 128, 3, padding=1)
        self.pool2 = nn.MaxPool2d(2, 2)

        self.conv3_1 = nn.Conv2d(128, 256, 3, padding=1)
        self.conv3_2 = nn.Conv2d(256, 256, 3, padding=1)
        self.pool3 = nn.MaxPool2d(2, 2)

        self.conv4_1 = nn.Conv2d(256, 512, 3, padding=1)
        self.conv4_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.pool4 = nn.MaxPool2d(2, 2)

        self.conv5_1 = nn.Conv2d(512, 512, 3, padding=1)
        self.conv5_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.pool5 = nn.MaxPool2d(2, 2)

        self.fc6 = nn.Linear(25088, 4096)
        self.drop6 = nn.Dropout2d()
        self.fc7 = nn.Linear(4096, 4096)
        self.drop7 = nn.Dropout2d()
        self.fc8 = nn.Linear(4096, 10)

    def forward(self, x):
        x = self.upsample(x)

        x = F.relu(self.conv1(x))
        x = self.pool1(x)

        x = F.relu(self.conv2(x))
        x = self.pool2(x)

        x = F.relu(self.conv3_1(x))
        x = F.relu(self.conv3_2(x))
        x = self.pool3(x)

        x = F.relu(self.conv4_1(x))
        x = F.relu(self.conv4_2(x))
        x = self.pool4(x)

        x = F.relu(self.conv5_1(x))
        x = F.relu(self.conv5_2(x))
        x = self.pool5(x)

        x = x.view(-1, 25088)

        x = F.relu(self.fc6(x))
        x = self.drop6(x)
        x = F.relu(self.fc7(x))
        x = self.drop7(x)
        x = self.fc8(x)
        return x

 本当はレイヤーごとに関数化してまとめたりするみたいなんですが初心者だしよくわかんないので全部まとめて一つの関数に書きました。しょうがないね。

 一応元のVGGと違うところがいくつかあるので書いておくが、まず入力の段で元のVGG11は224x224のサイズの画像を入力するように作ってあったのでMNISTを8倍にバイリニアで引き伸ばしてから入力している。また、元のVGG11は1000クラスのやっていたが今回はMNISTなので出力の段が10個なっている。

 ここではプログラム全体を載せ無いが、このコードをGPUで実行する場合バッチサイズはある程度大きくしたほうが良いと思われる(CPUでやると遅すぎて辛いくなる)。というのもGPUというのはCPUよりも並列性は高いがメモリ転送のオーバーヘッドが大きいという性質がある。よって、ある程度バッチサイズを大きくしないとメモリ転送のボトルネックが並列性の恩恵を上回ってしまうのである。ある程度がどんくらいかはNAISTの人が書いた論文に詳しく載ってたからそっち参照してほしい。

結果

f:id:toriten1024:20190222215108p:plain

学習経過

 上に示す図がVGG11を学習させた過程loss(損失関数の結果)の変化である。ある一定の学習回数を重ねたあと急激に学習が進む。じれったいので、ここまで待つのがすげーイライラする。全体としての学習には3時間ほどの時間を要した。PCのがうるさくてあんま寝れなかった。

 テストデータに対しては99%の推論制度を叩き出した。今まで勉強していた三層のニューラルネットワークに比べると破格に高い推論制度を叩き出している。最後の方では少しlossが増えて居るように見えるからこのまま行くと過学習ボロボロになるのかもしんない。

感想

 VGG11すごいと思う他に低速な計算機でここまで深いネットワークを学習させた先人たちの凄さを感じることができた。多分フルスクラッチでこのようなネットワークを考え実装することは私にはできない。巨人の肩美味しいです(^q^)

 今までごく浅いニューラルネットワークをいじってきたことから、このような11層もあるニューラルネットワークの学習を行えたことは今後に生きる良い経験であった。というかコレが僕の初ディープラーニングかもしれない。

 実装は行ったが未だVGGの論文を読んでないのでちゃんと読む必要がある。と言うか実装してるだけで理論的な理解が全くできてないのは大きな問題である。

 学生実験の糞みたいな実験の更にクソみたいな実験レポートみたいな記事だが別にいいのだ、俺は初心者なのだ。

Risc-Vの即値表現

Day3:Risc-Vの即値表現

 risc-vの命令を逆アセンブラを作成しているときに即値のビット列が並び変わっていてややこしく感じたのでそれについて便所の落書き程度に書く。

 Risc-Vには即値命令のデータを用いる命令がいくつか存在するが、その中でも   S-type,B-type,J-typeの三種類は即値が2つに分割して格納されていたり、ビット列が並び変わってたりする。そのため命令を解釈するためにはビット列を並べ替え数値として扱える状態に変形しなければならない

f:id:toriten1024:20190219215629p:plain

Risc-Vの命令の表現(出典:The RISC-V Instruction Set Manual
Volume I: User-Level ISA Document Version 2.2)

S-type

この表現を持つ命令SB, SH, SW

S-typeの命令は2つに分割された即値を持っている。この表現を持つ命令はメモリへデータを書き込むストア系の命令であり、即値は書き込み先のアドレスを表す。次節以降の2つB-type、J-typeと比較すると最下位ビット(0の桁)を持っているという違いがある。まぁ次の2つはジャンプのアドレスで、この命令はデータのアドレスなんだから当たり前だよね。よってメモリ番地上で任意の1バイトにアクセスすることが出来る。

f:id:toriten1024:20190219220751p:plain

S-typeの即値表現

B-type

この表現を持つ命令 BEQ, BNE, BLT, BGE, BLTU, BGEU

B-typeの命令は2つに分割され、更にその中で並べ替えられた即値を持っている。これは分岐命令に使われる表現であり、即値はアドレスを表す。即値が格納されている場所はS-typeと同じであるが最下位ビットが存在せず表現はbyte飛ばしになる。

f:id:toriten1024:20190219220417p:plain

B-typeの即値表現

 

J-type

この表現を持つ命令 JAL

J-typeは並べ替えられた20bit分の即値を持つ命令であり、ジャンプ命令に用いられる。即値は最下位ビットが1から始まることからアドレスは2個飛ばしとなる。

f:id:toriten1024:20190219220601p:plain

J-typeの即値

感想とか

MIPSはI形式やJ形式でも即値がきれいにまとまっていたので、こうやってビットが並び変わっているのはPIC16FのA/Dコンバータの出力みたいで、人間には読みづらいなと思いました。(粉みかん)tしかし、並び変わっているビット列が他のタイプの命令と同じビットの区分で並べられていたり計算機にしてみたらよくまとまっており、かなり考えて作られていることが伺える。

Risc-Vの逆アセンブラ

Day2:Risc-Vの逆アセンブラ

ビッグエンディアン トルエンディアン

命令コード:

 

使い方

  1. フォームに32bit(16進数で8桁)の命令を入力します。
  2. Disassembleボタンを押します。
  3. 枠内にニモニックが表示されます。

なぜ逆アセンブラを作ったのか

最初はRisc-Vの命令表を作ろうと記事を書いていたのですが、東大の方がすでに良いものを公開してらっしゃったのと、書いている途中で消失し、やる気をなくしたのでやめました。そして前回の記事で作成した環境でバイナリの解析を行っていたところ、Risc-Vには疑似命令というものが存在し(別にPICにだってある)、命令とアセンブラが直交しないことがわかりました。コレはちょっと面倒くさいです。そこで逆アセンブラの開発を行い、結果としてRV32Iの命令を逆アセンブル出来るものが完成しました。逆アセンブル部分は完成したのですが、ELFヘッダとかがgccのバイナリにはついてるので、なんだか面倒臭くなっちゃってファイルを読み込んで逆アセンブルする機能は作らずにこんな中途半端な1命令逆アセンブラが完成しました。

アセンブラの開発を行ったもう一つ狙いとしてはエミュレータの命令デコーダと逆アセンブラ構造が大体一緒なので、逆アセンブラを改造したらエミュレータが出来るんじゃね?っという狙いがあります。今回のプロジェクトの最大の目標はFPGAへの実装ですから、エミュレータの開発はほぼ必須なので逆アセンブラ作ったのは悪くなかったと思います。でもさ、Twitter見てるとエミュ書かずに直接FC互換機と書いちゃう化物がいて怖いよね。

失敗したこと

アセンブラを作った当初は「逆アセンブラアセンブラは真逆の動きをするだけだから逆アセンブラ用のswitch文ツリーをそのままアセンブラに流用できんじゃね?」とかアホなことを考えていたのですが、アセンブラでは疑似命令が登場するのでちょっとむずかしいですね。はい

「お前進捗おくれてんじゃねぇか」

ごめんなさい自動車免許の合宿に行ってました。僕は高専技科大学の純粋培養ちゃんなので世の中色んな人が居るんだなと思いました。

Rsic-Vの開発環境の構築

Day1:Risc-Vの開発環境の構築を行ったメモです。

 エミュレータの開発を行おうにも正しいバイナリが存在しなければ動作させることができません、そこでRisc-V財団が提供しているRisc-Vのコンパイラをビルドしてみました。Risc-Vのコンパイラのビルド方法は公式ページを見ればかんたんにできると思います。ただRisc-Vのコンパイラはビルド時にアーキテクチャが指定できるのですがそのへんがややこしかったので、そのへんについて書きます。

Risc-Vにはご存知の通りたくさんのサブセットが存在します

  • I: 整数演算命令
  • M:整数乗算命令
  • A:アトミック(マルチコアのためのメモリ管理とかそのへん)
  • F:浮動小数
  • D:倍精度

この他にも色々あるけど、二進化十進表現みたいな銀行員しか使わないそうなサブセットもサポートしています。

 公式が提供しているコンパイラはビルド前のconfigureで必要なサブセットを指定できます。

./configure --prefix=/opt/riscv --with-arch=rv32gc --with-abi=ilp32d

 例えば上記のように指定した場合、まず--prefixコンパイルしてできたバイナリを保存する場所でです。--with-arcはアーキテクチャを指定するオプションでrv32iにサブセットを書き足すことによって命令セットを指定するできます。例で登場するRV32GCというIMAFDを全部含んだ基本命令セットになります--with-abiはどうも小数点の扱いを指定できるようです。lip32を指定するとソフトfloat、lip32dを指定すると32bitハードfloatを利用が可能になります。

私は整数と乗算だけで良かったので、次のように指定してコンパイルを行いました。

./configure --prefix=/opt/riscv --with-arch=rv3im --with-abi=ilp32

 iが整数型の命令でmが乗算命令のサブセットです。浮動小数点はソフトウェアエミュレートで使うよう指定しビルドを行いました。今回の用途ではlinuxapiを使わない生バイナリを生成すればよかったので、

sudo make -j8

 と指定しました。sudoがあるのはmake時にファイルの配置まで行うからです。正直makeとmake install は別々にしてほしい。

するとコンパイル終盤あたりでエラーが発生してしまいました。エラーを見ると「rv3imなんて知らねぇよ」みたいなことが書いてありました。/opt/riscvを確認するとriscv32-unknown-elf-gccまでは生成できていました。そこでコンパイラに-Sオプションを指定してソースコードを食わすとそれっぽいアセンブラができていいました。

これは多分上位互換のアセンブラーにアセンブラファイルを食わせればうまく動くのではないかと考え、

./configure --prefix=/home/[ユーザー名]/riscv --with-arch=rv3gc

 でコンパイルを行い、そのなかの中身のbinopt/riscv/binにコピーしました。

そして次のMakefileのように指定してコンパイルするとちゃんとバイナリが出力されたんだよなぁ

CXX = /opt/riscv/bin/riscv32-unknown-elf-gcc 
AS = /opt/riscv/bin/riscv32-unknown-elf-as 

main: main.c
	$(CXX) -S main.c
	$(AS) main.s

まだ命令セットを把握しているわけじゃないからアセンブラ読んでもよくわからないんだけど足し算とか定数が見えるから、ちゃんと動いてんのかなコレ?

自作CPU上でセルフホスティングできる自作コンパイラを動かしたい

自作CPU上でセルフホスティングできる自作コンパイラを動かしたい

 知っている方は知っていると思うが、私は去年大学院入試で失敗し今年の前半は大学院浪人生活を送っていた、前半は浪人生として真面目に勉強していたのだが8月の院試に合格すると本格的にニートになってしまった。その後10月に語学留学にカナダを予定していたので、その対策を行ったりしていたのだが。二ヶ月後には語学留学も終わり本格的にやらなければ行けないことがなくなってしまった。

1月現在ニート生活もあと三ヶ月になった。現在英語の勉強をしたり大学院への準備としてニューラルネットワークの勉強をしたりしているわけであるが、かの有名な東京大学のCPU実験に触発されて自分でも似たようなことが前からやりたかったので、やることにした。もちろん、大学院ではハードウェアアクセラレーションの研究をするのでCPUの設計はもちろんコンパイラの作成も十分に役に立つのではないかと考えている。だたまぁ東京大学のCPU実験はグループでやる実験だし、私は東大生ほど賢くないのでどこまでできるかよくわからない。

やることの目標は次のようにした。

RISC-Vを選んだ理由は命令が単純でなんとなく流行っているからであり、就職のときネタにできるかなっていう軽いノリだけである。

C言語を選んだのはC言語を実装してしまえばC言語完全に理解したってネタが実際にできると思ったからである。ソンダケ

セルフホスティングを目的にしたのは自分の作ってCPU上でプログラムをコンパイルできれば、PCから独立した端末として成長できるということになり面白いと思ったからである。

やること

今回の計画でやることは次の通りである。

1.エミュレータの作成

コンパイラを作成する上で実行環境がなとテストを行えないので、まずはRISC-Vのアークてくちゃの理解も兼ねて実行環境の構築を行う、gcc等で生成したバイナリを使って動作確認を行う。

2.コンパイラの作成

エミュレータ上で動作するバイナリを出力できるコンパイラの開発を行う、東大のCPU実験のようにレイトレーシングを行うわけではないので、とりあえずちゃんと動作するバイナリを吐けるものを目指す。

3.OSの作成

セルフホスティングする際にファイルの読み込みやバイナリの出力、プログラムの実行等を行わなければ行けないためOSが必要になる。そのための機能を持ったOSが必要を用意する。できることならμCLinuxやMINIXが使えると面白そうなんだけどなぁ。

4.CPUの作成

RISC-VをFPGA上に実装する。正直あんまり作り込むつもりはないがパイプライン化くらいできるといいな程度に考えている。アウトオブオーダー等にはチャレンジしない。

5.セルフホスティング

エミュレータ上で作成した環境を実機上に移植しコンパイラを動作させ、プログラミングをおこなう

 

 

 

Gstreamer+OpenCVの環境構築

Gstreamer+OpenCVの環境構築

 今回ロボコンの大会に出場することになり、友人のPC上に私と同じ環境を構築する必要が発生したため、大会で使用するプログラムのための環境構築について親愛なるMRI氏のために解説する。
 ロボットを遠隔操縦する場合ロボットに搭載されたカメラの映像をネットワーク経由でストリーミングできると便利である。MJPG-streamer等の導入が容易なシステムも存在するが、GstreamerにはOpenCVと容易に連結し画像処理を行うことができ、H264が利用できるためRasberryPiのハードウェアエンコーダを利用したH264による高速高画質なストリーミングが行えるなど多くの利点が存在する。

環境構築

①Gstreamerのインストール

 OpenCVをビルドする前にGstreamerをインストールしておく必要がある。これは公式に従いUbuntuであれば次のように実行する。

 sudo apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools 
OpenCVのダウンロード

 適当なディレクトリにOpenCVをダウンロードする。デバッグを行う際にエラーメッセージ内でOpenCVのライブラリを参照することがあるのでhomeにOpenCVというディレクトリを作っておくと良いかもしれない。今回はOpenCV3.4を利用することにする。

mkdir OpenCV
cd OpenCV
wget  https://jaist.dl.sourceforge.net/project/opencvlibrary/opencv-android/3.4.0/opencv-3.4.2-android-sdk.zip
unzip opencv-3.4.2.zip
OpenCVのcmake

 ダウンロードしたOpenCVのzipファイルを解凍し、解凍したディレクトリ内にbuildという名前のディレクトリを作成し中に移動する。
ここからが難関である。Gstreamerと競合するライブラリがあるとGstreamerが上手く動かなくなるので、競合するライブラリをすべて無効化しておく必要がある。

cd opencv-3.4.2
mkdir build
cmake -D CMAKE_BUILD_TYPE=RELEASE  -D  INSTALL_C_EXAMPLES=ON   -D WITH_1394=OFF -D WITH_GSTREAMER=ON -D WITH_FFMPEG=OFF -D WITH_QT=ON ..

重要なのは次の3つの項目である。

  1. WITH_1394=OFF
  2. WITH_FFMPEG=OFF
  3. WITH_GSTREAMER=ON

1つ目のWITH_1394はIEEE1394のことらしくカメラのインタフェースに関する項目であるがGstreamerと競合するようなので無効化する。2つ目のWITH_FFMPEGであるがこれはメディアフレームワークでありGstreamerと思いっきりキャラがかぶっているので無効化する。3つめのWITH_GSTREAMERであるが、これを有効化することでGSTREAMERが利用可能になる。

この設定でcmakeを行うと次のようなログが出力されるので確認してほしい。Video I/Oの項目でGstreamer以外にYESが無ければ成功である。もし上記の設定でcmakeを行い、YESの項目が存在すればここにcmakeのオプション一覧があるので、参照してYESになっている項目を無効化する。

--   Video I/O:
--     GStreamer:                   
--       base:                      YES (ver 1.8.3)
--       video:                     YES (ver 1.8.3)
--       app:                       YES (ver 1.8.3)
--       riff:                      YES (ver 1.8.3)
--       pbutils:                   YES (ver 1.8.3)
--     libv4l/libv4l2:              NO
--     v4l/v4l2:                    linux/videodev2.h
--     gPhoto2:                     NO
OpenCVのビルド

 OpenCVのビルドとインストールを行う。makeのスレッド数はコア数の倍程度が好ましいとされているが、RasberryPi3の場合は -j 4 程度にしておいたほうが無難である。

make -j 8 #8スレッド
sudo make install


以上で環境構築は終わりである。

使用例

 OpenCVにはこのGstreamerを利用するための拡張機能が存在し、VideoCaptureやVideoWriterを使ってGstreamerのパイプラインとOpenCVを連結することが可能である。

使用例としてgstreamerのテスト信号をキャプチャしOpenCVで表示するプログラムを掲載する。

gstreamer_test.cpp

#include <opencv2/opencv.hpp>

int main(int argc, char *argv[])
{
	cv::VideoCapture gstreamer;
	//gst-launchと同じコマンドを実行することができる。
	gstreamer.open("videotestsrc  ! appsink");                                 //カラーバー
	//	gstreamer.open("videotestsrc pattern=ball ! appsink");      //ボール
	//	gstreamer.open("videotestsrc pattern=snow ! appsink");   //じゃみじゃみ

	if (!gstreamer.isOpened()) 
	{
		printf("=ERR= fail to open\n");
		return -1;
	}

	while (1)
	{
		cv::Mat GstCap;
		gstreamer >> GstCap;
		cv::imshow("Gstreamer test", GstCap);

		int key = cv::waitKey(1);

		//’q’で終了
		if (key == 'q')
		{
			break;
		}
	}
	return 0;
}

コンパイル

g++ `pkg-config --cflags opencv`  gstreamer_test.cpp -std=c++11 -g `pkg-config --libs opencv`