日曜技術者のメモ

趣味でやった事のメモ書きです。

多層パーセプトロンをPythonで実装してみた-1-

A Neural Network Playgroundのデモが素晴らしかったので
Pythonを使ってそれっぽく動く物を作ってみた。

開発環境

  • Visual studio 2015 community
    • PTVS
  • Python3.5.2
    • numpy 1.11.1
    • scipy 0.18.0
    • scikit-learn 0.18
    • matplotlib 1.5.3

多層パーセプトロン

ニューラルネットワークはまったくの初心者だったので色々調べると
多層パーセプトロンを使えばできそうなのでそれについて調べた。

順方向の計算は簡単

f:id:ginnyu-tei:20161008103403g:plain:w400

逆誤差伝播法はかなり難しい・・・
出力層はなんとか自分で計算できたが隠れ層は無理。
↓も正解か自信がない(手書き文字認識は問題なく出来たから大丈夫だとは思う)

+出力層
f:id:ginnyu-tei:20161008111856g:plain:w400

+隠れ層
f:id:ginnyu-tei:20161008110211g:plain:w400

学習は1データ(x,yの値と期待値y)毎に更新するオンライン学習
重み更新は学習率固定

ニューロンレイヤークラス

まず基本となるレイヤークラスを作成して
それを継承して出力層クラスと隠れ層クラスを作成した。
playgroundのデモは値の範囲が-1~+1までなので活性化関数はtanh(x)を使った。

※11/1複数の出力層に対応する様修正

# -*- coding: utf-8 -*-

import numpy as np

class neuronLayer(object):
  """レイヤー基本クラス"""
  def __init__(self,input_num,neuron_num,output_num,e,bias=None):
    self._input_num = input_num
    self._neuron_num = neuron_num
    self._ouput_num = output_num
    self._e = e

    if bias is None:
      self._isBias = False
    else:
      self._isBias = True
      #biasを追加する拡張
      self._input_num += 1
      self._bias = np.ones([self._neuron_num,1]).reshape(self._neuron_num,1)

    #初期化
    self._x = np.zeros(self._input_num*self._neuron_num).reshape(self._neuron_num,self._input_num)
    self._s = np.zeros(self._neuron_num)
    self._y = np.zeros(self._ouput_num*self._neuron_num).reshape(self._neuron_num,self._ouput_num)
    self._delWdelE = np.zeros(self._input_num*self._neuron_num).reshape(self._neuron_num,self._input_num)
    self._delSdelE = np.zeros(self._ouput_num*self._neuron_num).reshape(self._neuron_num,self._ouput_num)

    #重みランダム初期化
    #self._weight = np.random.rand(self._neuron_num,self._input_num)*2-1.0
    #http://deeplearning.net/tutorial/mlp.html
    self._weight = np.asarray(
                np.random.uniform(
                    low=-np.sqrt(6. / (self._input_num + self._ouput_num)),
                    high=np.sqrt(6. / (self._input_num + self._ouput_num)),
                    size=(self._neuron_num, self._input_num)
                )
            )

  def activationFunction(self,x):
    return self.tanh(x)

  def dActivationFunction(self,x):
    return self.dtanh(x)

  def sigmoid(self,x):
    return(1.0/(1.0+np.exp(-x)))

  def dsigmoid(self,x):
    return self.sigmoid(x)*(1-self.sigmoid(x))

  def tanh(self,x):
    return np.tanh(x)

  def dtanh(self,x):
    return 1.0-self.tanh(x)*self.tanh(x)
  
  def ReLu(self,x):
    return x * (x > 0)

  def dReLu(self,x):
    return 1.0 * (x > 0)

  def foward(self,x):
    #biasが有効のときはbiasを追加
    if(self._isBias == True):
      self._x = np.hstack((x,self._bias))
    else:
      self._x = x

    # s = Σ(x*w)
    if(self._neuron_num == 1):
      self._s = (self._x * self._weight).sum()
    else:
      self._s = (self._x * self._weight).sum(axis=1)+np.zeros(self._ouput_num).reshape(self._ouput_num,1)
    self._y = self.activationFunction(self._s)

  def updateWeight(self):
    #print(self._weight)
    self._weight -= self._e * self._delWdelE

  def getY(self):
    return self._y
  
  def getWeight(self):
    if(self._isBias == True):
      #前段はbiasの重みが不要なので削除して返す
      return np.delete(self._weight,self._input_num-1,1)
    else:
      return self._weight

  def getDelSdelE(self):
    return self._delSdelE


class inputLayer(object):
  def __init__(self,input_num,neuron_num,output_num):
    self._input_num = input_num
    self._neuron_num = neuron_num
    self._ouput_num = output_num

    self._x = np.zeros(self._ouput_num).reshape(self._ouput_num,1)
    self._y = np.zeros(self._input_num*self._ouput_num).reshape(self._input_num,self._ouput_num)

  def foward(self,x):
      self._y = x + self._x

  def getY(self):
    return self._y


class outputLayer(neuronLayer):
  def backprop(self,y):
    if(self._neuron_num==1):
      self._delSdelE = (self._y -y) * self.dActivationFunction(self._s)
    else:
      self._delSdelE = (self._y -y) * self.dActivationFunction(self._s)
      self._delSdelE = self._delSdelE.T

    self._delWdelE = self._delSdelE * self._x



class hiddenLayer(neuronLayer):
  def backdrop(self,delSdelE,wn):
    if(self._ouput_num==1):
      self._delSdelE = (delSdelE * wn).sum() * self.dActivationFunction(self._s)
      self._delSdelE = self._delSdelE.reshape(self._neuron_num,1)
    else:
      self._delSdelE = (delSdelE * wn).sum(axis=0).reshape(1,self._neuron_num)
      self._delSdelE *= self.dActivationFunction(self._s[0,:])
      self._delSdelE = self._delSdelE.T

    self._delWdelE = self._delSdelE * self._x

実際に分類したりMNISTで手書き文字認識したりは次の記事に書く。

LPC1114FN28でマルチコプター作ってみた-フライトコントローラー-

マルチコプターのフライトコントローラを作ったときのメモ

プロポ入力

S-BUSが使いたかったのでFutaba 10Jを採用

S-BUSはFutabaの規格で仕様は公開されてないが
解析している人がいたのでありがたく使わせてもらった

Futaba S-BUS controlled by mbed | mbed

反転したボーレート100kのUARTなので
74AC04で反転してLPC1114FN28のシリアルポートで受信した

S-BUS2だとテレメトリーの情報をプロポに戻せるので対応したかったが
残念ながら実装するスペースがなかった
情報は↓に載ってた sbustelemetrysensors.blogspot.jp

6軸センサー

Amazonでも売ってるMPU-6050を採用
I/FもI2Cなのでマイコンとの接続も楽チン
センサー周りはプログラムも含め別途記事にする予定。

回路図

基板を作成するために回路図を作成した。

f:id:ginnyu-tei:20161004210458j:plain

まず、ブレッドボードで様子見して基板を作成したが、
ブレッドボードで飛ばした時に比べ基板を使うとと明らかに挙動が悪くなった。

原因はブレッドボードに比べ、基板はしっかりフレームにねじ止めしたので
モーターの振動が6軸センサーに悪影響を及ぼしたと思う。

センサー値にLPFをかけたりしたが改善しなかった。
なので、最終的にはブレッドボードのままにした。

なお、先日のマルチコプター本体は超音波センサーとFXMA108は
スペースが足りず実装しなかった。

プログラムについてはまた後日記事にする予定。

LPC1114でマルチコプター作ってみた-フレーム-

マルチコプターのフレームを作ったときのメモ

フレーム材料

材料は悩んだが入手性・強度・軽量・加工性を考えてアルミの角材+アルミ板を採用
カーボンは理想的だけと加工は大変だし何より高いので不採用
木材は加工が容易だが強度の見積もり方法が全く分からなかったのでこれも不採用
木材で工作している人は強度をどうやって計算しているのだろう?

強度計算

強度計算は構造計算に使う計算式を使った。

公式集−構造計算 片持ち梁 (曲げモーメント、せん断、反力、たわみ・・)

中心部を支点、プロペラ位置を荷重点と考えて計算

↓がホームセンターで売ってるアルミ材を元に計算した結果

f:id:ginnyu-tei:20161002201244g:plain

上図から15×15×1.5を採用

なお、重さだかどうするか悩んだ結果以下の見積もりにした

・本体重量は1.5kg(モーター1個当たり375g)
・4Gまで耐える
・安全率は5

なので合計の7.5kgとした。

この見積もりで問題ないかは全く不明・・・
結果論だがマルチコプターが墜落しても壁にぶち当たってもフレームには全く問題が起きなかった。

設計図

検討結果から書いた設計図

f:id:ginnyu-tei:20161002193552g:plain:w800

LPC1114FN28でマルチコプター作ってみた

1年位マルチコプターを作ってとりあえず飛んだ浮いたので色々メモしておく。

↓はプログラム修正中でプロペラを外した写真

f:id:ginnyu-tei:20150721182415j:plain:w600

買った物

これで5万位

  • フレーム
    • アルミ角材
    • アルミ板
    • ねじ
  • プロペラ
    • 1045プロペラ
  • 電装
    • ブラシレスDCモーター(A2212)
    • ESC(Simonk 30A)
  • バッテリー(11.1V 2700mAh)
  • バッテリー充電器
  • フライトコントローラー
    • LPC1114
    • MPU-6050
    • 74AC04
    • RN-42 BTモジュール
    • 2SC1815
    • 抵抗
    • LED
  • プロポ(Futaba 10J)

詳細は次回予定の記事書く予定

LPC1114+CMSIS Libraryでprintfを使ってみた

LPC11xx_cmsis2_Libに実装されているUARTSend関数を使ってシリアル出力していたが
文字列以外の表示が面倒なのでprintfを使えるようにした。

今の所2種類の方法で動いたので両方メモしておく。

試した環境

  • LPCXpresso_7.7.2_379
  • LPCXpresso1114_cmsis2.zipをインポート済み
      C:\nxp\LPCXpresso_7.7.2_379\lpcxpresso\Examples\NXP\LPC1000\LPC11xxにある
  • Redlib(none)
  • LPC-Link2(Semihostingを使う際は必要)

Semihostingを使う

LPC-Link2があるならSemihostingを使うとLPCXpresso上でprintfの結果が見える。
使い方も簡単でNew projectC Project (Semihosted)を選ぶだけ。

f:id:ginnyu-tei:20150429093708j:plain

出てきたサンプルコードを実行するとLPCXpresso上のConsoleにHello Worldが表示される。

f:id:ginnyu-tei:20150429094256j:plain

syscallを実装してUART経由でprintfを使う

New project->C Projectで作成したプロジェクト上でprintfを呼び出すと
Redlib(none)ではSystemcallが実装されていないのでリンク時エラーが発生する。

c:/nxp/lpcxpresso_7.7.2_379/lpcxpresso/tools/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m\libcr_c.a(fpprintf.o): In function `printf':
fpprintf.c:(.text.printf+0x38): undefined reference to `__sys_write'
c:/nxp/lpcxpresso_7.7.2_379/lpcxpresso/tools/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m\libcr_c.a(_deferredlazyseek.o): In function `__flsbuf':
_deferredlazyseek.c:(.text.__flsbuf+0x88): undefined reference to `__sys_istty'
c:/nxp/lpcxpresso_7.7.2_379/lpcxpresso/tools/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m\libcr_c.a(_writebuf.o): In function `_Cwritebuf':
_writebuf.c:(.text._Cwritebuf+0x16): undefined reference to `__sys_flen'
_writebuf.c:(.text._Cwritebuf+0x26): undefined reference to `__sys_seek'
_writebuf.c:(.text._Cwritebuf+0x3a): undefined reference to `__sys_write'
c:/nxp/lpcxpresso_7.7.2_379/lpcxpresso/tools/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m\libcr_c.a(alloc.o): In function `_Csys_alloc':
alloc.c:(.text._Csys_alloc+0xe): undefined reference to `__sys_write'
alloc.c:(.text._Csys_alloc+0x12): undefined reference to `__sys_appexit'
c:/nxp/lpcxpresso_7.7.2_379/lpcxpresso/tools/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m\libcr_c.a(fseek.o): In function `fseek':
fseek.c:(.text.fseek+0x18): undefined reference to `__sys_istty'
fseek.c:(.text.fseek+0x3c): undefined reference to `__sys_flen'
collect2.exe: error: ld returned 1 exit status

Systemcallの定義はlibconfig-arm.hに記述されているので適当に実装してみた。
以下サンプルコード

#ifdef __USE_CMSIS
#include "LPC11xx.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "uart.h"

#include <cr_section_macros.h>

// TODO: insert other include files here

// TODO: insert other definitions and declarations here
void uart_putc(const char);

////////////////////////// Systemcall //////////////////////////
int __sys_write(int iFileHandle, char *pcBuffer, int iLength)
{
    int n;
    for (n = 0; n < iLength; n++) {
        if (pcBuffer[n] == '\n'){
            uart_putc('\r');
        }
        uart_putc(pcBuffer[n]);
    }
    return iLength;
}
int __sys_istty(int handle)
{
    return 1;
}
int __sys_flen(int handle)
{
    return 0;
}
int __sys_seek(int handle, int pos)
{
    return 0;
}
void __sys_appexit (void)
{

}
////////////////////////// UART ////////////////////////
// send a character via TXD
void uart_putc(const char c) {
    // Wait for TX buffer empty
    while (!(LPC_UART->LSR & LSR_THRE));
    // Put a character
    LPC_UART->THR = c;
}
////////////////////////// Main //////////////////////////
int main(void) {
    UARTInit(115200);
    // Force the counter to be placed into memory
    volatile static int i = 0 ;
    // Enter an infinite loop, just incrementing a counter
    while(1) {
        i++ ;
        if (i % 1000000 == 0){
            printf("i = %d(dec) ",i);
            printf("%08x(hex)\n",i);
        }
    }
    return 0 ;
}

uart_putc関数はエレキジャックのHPにあったコード*1
そのまま使わせて頂きました。

実行するとUARTからprintfの結果が送られてくる。

i = 1000000(dec) 000f4240(hex)
i = 2000000(dec) 001e8480(hex)
i = 3000000(dec) 002dc6c0(hex)
i = 4000000(dec) 003d0900(hex)

scanf

printfが動作したので、ついでにscanfも実装しようとしたがうまく動かなかった。

__sys_read__sys_read_cを追加で実装したが
デバック実行でブレイクポイントを設定しても止まらなかった。

Redlib内の動きが分かれば良いのだが
ソースコードは公開されていない為scanfの実装はあきらめる。

参考にしたサイト

mbed + GCCでprintfを使う: Todotaniのはやり物Log

CypressでEZ-USB FX3 のキットを買ってみた

 ZedboardとPC間で高速大容量通信の実験をしようと思いUSB3.0のキットを買ったので購入方法などのメモ

 買ったのはCypressの「EZ-USB FX3 SuperSpeed Explorer Kit」$49と安価なのでこれにした。

CYUSB3KIT - 003 EZ - USB® FX3™ SuperSpeed Explorer Kit - Cypress

 ただし、このキットはピンヘッダで外部と接続する為そのままではZedboardには接続できない。
CypressでFMCやHSMCに変換するボードが別途販売しているのでそれも買う必要がある。

CYUSB3ACC - 006 HSMC Interconnect Board for the EZ - USB® FX3™ SuperSpeed Explorer Kit - Cypress

CYUSB3ACC - 005 FMC Interconnect Board for the EZ - USB® FX3™ SuperSpeed Explorer Kit - Cypress

 今回はAlteraSocでも試せる様に両方入手した。

 Explorer KitはDigikeyで販売しているが、FPGAに接続するボードは見つからなかったのですべてCyptessから直接購入した。
 Cypressで購入するにはそのままカートに追加していけば良い。

f:id:ginnyu-tei:20141206162941j:plain

 購入にはユーザーアカウントが必要だが、ZedBoardでUARTを使う為Cypressでドライバをダウンロード した際に作成していたのでそのアカウントを使用した。
 送料は$29か$39を選べ$39だと早く到着する様です。今回は$39にしてみた。
支払いはクレジットカードが使えたので簡単に決済できた。  注文を12/1のお昼頃にして、12/5帰宅するとFedExの不在票が入っていて明日、日通にて再配達すると書いてあり12/6に受け取った。

Explorer Kitの箱

f:id:ginnyu-tei:20141206164913j:plain

ZedboardにFMEボードを介して接続した写真

f:id:ginnyu-tei:20141206165031j:plain

Simple framebufferを有効にする

Linux上からディスプレイに出力する方法を調べているとSimple framebufferという物がある様なのでqemu上で試したメモ。

qemuは前回のqemu2svの環境を流用。

qemu側の変更点

  • hw_if.cからソケット通信を削除して単体で動作する様に修正
  • ベースアドレスの変更
    qemu-2.1.2/hw/arm/xilinx_zynq.c
sysbus_create_simple("hw_if", 0xE1000000, NULL);

kernel側の変更点

  • make menuconfigを実行
    Simple framebuffer supportを有効にする。
    Device Drivers→Graphics support→Support for frame buffer deviceと選択する。
    f:id:ginnyu-tei:20141118225529j:plain

  • device treeに以下を追加

ps7_fb: framebuffer@E1000000 {                                                                
    compatible = "simple-framebuffer";                                                        
    reg = <0xE1000000 (800 * 600 * 2)>;                                                       
    width = <800>;                                                                            
    height = <600>;                                                                           
    stride = <(800 * 2)>;                                                                     
    format = "r5g6b5";                                                                        
};

ひな形は↓にあった。
Documentation/devicetree/bindings/video/simple-framebuffer.txt

qemu起動

起動後にdmesgを実行するとログ内でsimple framebufferが認識しているのがわかる。

simple-framebuffer e1000000.framebuffer: framebuffer at 0xe1000000, 0xea600 bytes, mapped to 0xf010000
0                                                                                                     
simple-framebuffer e1000000.framebuffer: format=r5g6b5, mode=800x600x16, linelength=1600

また、/dev/fb0が追加されている。

もうちょっと確認する

hw_if.cを修正する。
hw/char/hw_if.c

static void hw_if_write(void *opaque, hwaddr offset,uint64_t value, unsigned size){
    if (value != 0) {  
        printf("write::%lx %lx %x\n",offset >> 2,value,size);                                           
    }
}

value != 0がないと黒のドットがすべてprintされるので0以外を出力している。
この状態でecho -ne "a" > /dev/fb0とやってみた。

zynq> echo -ne "a" > /dev/fb0
write::0 61 1

0x61はASCIIコードで"a"なのでライトできてる様です。

実機に実装する際はデュアルポートメモリを置いて片方は0xE1000000にバスからライトして片方はVGAなりHDMIなりに同期信号付けて渡せば動作しそう。
リフレッシュレートの設定が見つからないけどLinuxは同期をどうしてるんだろう?書けるだけ書いているのかな?