空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

テストを学ぼう (1) ! ~ユニットテストについて~

ユニットテストってなんでするの?

書いたコードがちゃんと動くか不安だから。

テストを書いておくと安心を得ることができます。安心大事。

また、大胆にリファクタリングだってできちゃう。

以下の考え方はユニットテストが基盤

  1. 継続的テスト
  2. テスト駆動開発
  3. ビヘイビア駆動開発

継続的テストとは

テストを早い段階から(できれば自動的に)回すことが大事。理由は、手戻り、リグレッション(テストに失敗→修正→新たな問題が発生)が発生するからです。

ユニットテストは最小クラスの部品などを対象とするため、早い段階から回すことができます。また、自動化がしやすく、ローコストで繰り返し行うことができます。

こうすることで、修正による影響がすぐにフィードバックされ、安心してリリースができる。

参考:継続的テスト: IBM の見解  継続的テストの利点

テスト駆動開発とは

プロダクションコードを書く前にテストを書く方法。テストファースト。目的が明確になるため、目的にあったすっきりとしたコードがかける。

参考:@IT:特集 「テスト駆動開発」はプログラマのストレスを軽減するか?

ビヘイビア駆動開発とは

仕様を起点とした開発手法。テストファーストに対してスペックファーストという。

pythonでは「behave」という振舞駆動開発ツールがある。

参考: 振る舞い駆動開発入門  ビヘイビア駆動開発 - Wikipedia  Welcome to behave! — behave 1.2.5 documentation  ビヘイビア駆動開発ツール 

ユニットテストを書いてみよう

pythonでunittestを用いるものとする

参考: 26.4. unittest — ユニットテストフレームワーク — Python 3.6.1 ドキュメント Pythonでエラーが発生することをテストする - Misc Notes

以下のプロダクトコードとテストコードを同じフォルダに配置する

プロダクトコード

数値微分をしてみる

以下をnumerical_diff.pyとして保存

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

# 2次関数の数値微分を行うクラス                                                 
class Diff:
    # 2次関数                                                                   
    def f(self,x):
        y = x**2
        return y

    # 数値微分                                                                  
    def numerical_diff(self,f,x,dx):
        if dx == 0:
            raise ValueError('division by zero')
        else:
            x_diff = (f(x+dx)-f(x-dx))/(2*dx)
            return x_diff

if __name__ == '__main__':
    diff = Diff()
    print diff.numerical_diff(diff.f,3,0.001)

テストコード

以下をtest_numerical_diff.pyとして保存

# -*- coding:utf-8 -*-                                                 
import unittest, numerical_diff

class TestDiff(unittest.TestCase):
    # 前処理                                                           
    def setUp(self):
        self.diff = numerical_diff.Diff()

    # 後片付け                                                         
    def tearDowm(self):
        pass

    # 3の2乗のテスト                                                   
    def test_func1(self):
        self.assertEqual(self.diff.f(3),9)

    # 4の2乗のテスト                                                   
    def test_func2(self):
        self.assertEqual(self.diff.f(4),16)

    # 数値微分ができているかのテスト                                   
    def test_numerical_diff(self):
        self.assertAlmostEqual(self.diff.numerical_diff(self.diff.f,3,0.001),6.0, delta=1e-8)
        self.assertEqual(round(self.diff.numerical_diff(self.diff.f,3,0.001)),6.0) # 良くない例

    # 0で割られたときの例外のテスト                                    
    def test_numerical_diff_zero_error(self):
        with self.assertRaises(ValueError) as cm:
            self.diff.numerical_diff(self.diff.f,3,0.0)
        exception = cm.exception
        self.assertEqual(exception.message, 'division by zero')

if __name__ == "__main__":
    unittest.main()

参考:2. 組み込み関数 — Python 3.6.1 ドキュメント

実行方法

以下を上記フォルダ内で実行

python -m unittest test_numerical_diff

もしくはターミナルで

$ ipython
$ run test_numerical_diff.py

実行結果

....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

 今後勉強したいツール

nose

小型USBにubuntu16.04をいれてみる(macbookから起動できるように編)

myenigma.hatenablog.com

MyEnigmaさんの記事に刺激を受け、小型USBにubuntu16.04を入れてみました。

手順は少し異なります。

準備

  1. 適当USB 4GB
  2. 小型USB 64GB↓

安い!小さい!

https://www.amazon.co.jp/SANDISK-USB3-0%E3%83%95%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5-64GB-SDCZ43-064G-ULTRA/dp/B00LLEODCK/ref=pd_bxgy_147_img_2?_encoding=UTF8&psc=1&refRID=CJARWHRR2CQR1XTV6RKC

手順

  1. UNetbootin - Homepage and Downloadsのインストール
  2. ubuntu16.04のISOファイルのダウンロード
  3. unetbootinを用いてISOファイルを適当USBへイン
  4. この適当USBと小型USBをMacbookに指しoptionキーを押しながら再起動
  5. ubuntuを選択し、インストールなしで実行を選択
  6. Ubuntu14.04LTSをUSBメモリにインストールする - 余白を参考に小型USBのパーティションを分ける
  7. ここで、分けたパーティションにubuntu16.04をインストール
  8. 再起動
  9. rEFIndを小型USBに作成したFAT32パーティションへコピー(上記MyEnigmaさんのブログ参考)
  10. リカバリーモード、command+rキーを押しながら再起動
  11. ターミナルを開いて$ /Volumes/~/refind-installを実行
  12. 再起動でUbuntuMacを選択できるように!!!

以上の手順でMacbookの環境を汚さずにUSBからUBUNTUが起動できるようになりました。 やった〜!!!

便利なツール

SDカードフォーマッター - SD Association

Ubuntu14.04(CPU only)でSegNetを使ってみる

手順

  • caffe-segnetのダウンロード
git clone https://github.com/alexgkendall/caffe-segnet.git
  • 以下の手順でインストー

Caffe | Installation: Ubuntu

CPU only の設定に変更

Caffe | Installation

For CPU-only Caffe, uncomment CPU_ONLY := 1 in Makefile.config.

caffe-segnetの中のpythonフォルダ

export PYTHONPATH={適切なpathの設定}/caffe-segnet/python:$PYTHONPATH 
cd /
git clone https://github.com/alexgkendall/SegNet-Tutorial.git
mv SegNet-Tutorial SegNet
cd /SegNet/Example_Models
wget http://mi.eng.cam.ac.uk/projects/segnet/models/segnet_weights_driving_webdemo.caffemodel
  • サンプルプログラムを以下のように修正

SegNet/Script/webcam_demo.pyを写真から読みこむ設定に変更

名前はpicture_demo.pyに変更

import numpy as np
import matplotlib.pyplot as plt
import os.path
import scipy
import argparse
import math
import cv2
import sys
import time

sys.path.append('/usr/local/lib/python2.7/site-packages')
# Make sure that caffe is on the python path:
caffe_root = '/SegNet/caffe-segnet/'
sys.path.insert(0, caffe_root + 'python')
import caffe

# Import arguments
parser = argparse.ArgumentParser()
parser.add_argument('--model', type=str, required=True)
parser.add_argument('--weights', type=str, required=True)
parser.add_argument('--colours', type=str, required=True)
args = parser.parse_args()

net = caffe.Net(args.model,
                args.weights,
                caffe.TEST)

#caffe.set_mode_gpu()

input_shape = net.blobs['data'].data.shape
output_shape = net.blobs['argmax'].data.shape

label_colours = cv2.imread(args.colours).astype(np.uint8)

cv2.namedWindow("Input")
cv2.namedWindow("SegNet")

cap = cv2.VideoCapture(0) # Change this to your webcam ID, or file name for your video file

rval = True

start = time.time()
frame = cv2.imread('rimocon.jpg', 1)

end = time.time()
print '%30s' % 'Grabbed camera frame in ', str((end - start)*1000), 'ms'

start = time.time()
frame = cv2.resize(frame, (input_shape[3],input_shape[2]))
print frame.shape
input_image = frame.transpose((2,0,1))
print input_image.shape
input_image = np.asarray([input_image])
end = time.time()
print '%30s' % 'Resized image in ', str((end - start)*1000), 'ms'

start = time.time()
out = net.forward_all(data=input_image)
end = time.time()
print '%30s' % 'Executed SegNet in ', str((end - start)*1000), 'ms'

start = time.time()
segmentation_ind = np.squeeze(net.blobs['argmax'].data)
segmentation_ind_3ch = np.resize(segmentation_ind,(3,input_shape[2],input_shape[3]))
segmentation_ind_3ch = segmentation_ind_3ch.transpose(1,2,0).astype(np.uint8)
segmentation_rgb = np.zeros(segmentation_ind_3ch.shape, dtype=np.uint8)

cv2.LUT(segmentation_ind_3ch,label_colours,segmentation_rgb)
segmentation_rgb = segmentation_rgb.astype(float)/255

end = time.time()
print '%30s' % 'Processed results in ', str((end - start)*1000), 'ms\n'


cv2.imshow("Input", frame)
cv2.imshow("SegNet", segmentation_rgb)

while rval:
    key = cv2.waitKey(1)
    if key == 27: # exit on ESC
        break
cap.release()
cv2.destroyAllWindows()
  • サンプルの実行
python picture.py --model /SegNet/Example_Models/segnet_model_driving_webdemo.prototxt --weights /SegNet/Example_Models/segnet_weights_driving_webdemo.caffemodel --colours /SegNet/Scripts/camvid12.png
  • 実行結果

参考:

SegNet

OpenCV 備忘録: SegNetをUbuntuで試してみた

reveal.js参考まとめ(Markdownでプレゼン資料作り)

参考

reveal.js – The HTML Presentation Framework

Reveal.js、Markdown、Githubでスライドを作成する。 - Qiita

reveal.js で markdown を使う(2015年3月版) - Qiita

reveal.jsを使ってみる - Faster and Faster

プレゼンもmarkdownでね!reveal.jsで資料を作成してみた - Qiita

機械学習のお勉強(DeepLearning from 0)

参考図書

O'Reilly Japan - ゼロから作るDeep Learning

Deep Learning

end-to-endなmachine learningであり、入力から出力までを学習し、特徴量を抽出したり人が行う作業がない。

機械学習はデータが命。

  • 汎化能力が大事(誰かが書いた文字を認識でくできる)
  • 過学習、overfitting(ある人が書いた文字のみしか判断できない)👈シミュレーターなどを用いて集めたデータ等では起きやすい

パーセプトロン

簡単な実装

def AND(x1,x2):
    w1, w2, theta = 1, 1 ,1.5
    tmp = x1*w1 + x2*w2
    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1

w1=1,w2=1,theta=0.5でOR

w1=-1,w2=-1,theta=-1.5でNAND

が表現できる

重みとバイアスの導入

wが重み、bがバイアス

import numpy as np

def AND(x1,x2):
    x = np.array([x1,x2])
    w = np.array([1.0,1.0])
    b = -1.5
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

しかし、単層パーセプトロンではXORは表現できない

多層パーセプトロン

上記のANDど同様にOR,NANDを作ったとすると以下のように多層にすることでXORを表現することができる

def XOR(x1,x2):
    s1 = NAND(x1,x2)
    s2 = OR(x1,x2)
    y = AND(s1,s2)
    return y

1層目:x ⇒ 2層目:s ⇒ 3層目:y

ニューラルネットワーク

活性化関数

ステップ関数 👈パーセプトロン

def step_function(x):     
    if x > 0:
        return 1
    else:
        return 0

シグモイド関数 👈ニューラルネットワーク

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

ReLU関数 👈ニューラルネットワーク

def relu(x):
    return np.maximum(0,x)

重要なことは非線形の関数であるということ。 線形関数では層を深くしても隠れ層のないネットワークになり、多層にする意味がなくなってしまう。

出力層の活性化関数

恒等関数 👈回帰問題に使用

def identity_function(x):
    return x

ソフトマックス関数 👈分類問題に使用

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y
  • ソフトマックスの出力の総和は1になるという性質があり、この性質により出力を確率と解釈することができる。
  • 学習と推論のうち、学習の際にこの関数が関係してくる。

3層のニューラルネットワーク

def init_network():
    network = {}
    network['W1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
    network['b1'] = np.array([0.1,0.2,0.3])
    network['W2'] = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
    network['b2'] = np.array([0.1,0.2])
    network['W3'] = np.array([[0.1,0.3],[0.2,0.4]])
    network['b3'] = np.array([0.1,0.2])

    return network

def forword(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = identity_function(a3)

    return y

network = init_network()
x = np.array([2.0,3.0])
y = forword(network,x)
print y #[ 0.32882937  0.72300564]

バッチ処理

一枚の画像データ784(28×28)を入力とするのではなく、百枚の画像データをまとめて100×784と入力するようなとき、このまとまりをバッチと呼ぶ。

このように行列計算(大きな配列)に落とし込むことで計算の高速化が可能。

学習

以下に述べる、損失関数、ミニバッチ、勾配、勾配降下法が重要である。

学習の手順(SGD:確率的勾配降下法

  1. ミニバッチ
  2. 勾配の算出
  3. パラメータの更新
  4. 繰り返す

損失関数

損失関数を設定するのは微分の役割が関係しています。

微分が0となりにくい性質があり、これはニューラルネットワークの学習において重要な性質である。

これによって、損失関数が小さくなるような重みとバイアスを探索(学習)することができる。

二乗和誤差
def mean_squared_error(y,t):
    return 0.5 * np.sum((y-t)**2)
  • one-hot表現

出力 = [0.3,0.6,0.1]👈ソフトマックス関数により総和 = 1

正解ラベル = [1,0,0]

交差エントロピー誤差
def cross_entropy_error(y, t):
    delta = 1e-7←発散防止
    return -np.sum(t * np.log(y+delta))

ミニバッチ学習

100000枚の写真から1000枚を無作為に選んで学習を行う。

np.random.choice(train_size, batch_size)

勾配

勾配(微分に負をかけたもの)が示す方向は関数の値を減らす方向になる。

↓数値微分。シンプルだが計算に時間がかかる。

def numerical_gradient(f,x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x)

        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2)/(2*h)
        x[idx] = tmp_val

    return grad

この勾配の計算を高速に求める手法として、誤差逆伝播法が存在する。

勾配降下法

勾配方向に一定距離だけ進み、そこで勾配を求め再度進む。これの繰り返しによって関数の値を減らす。

以下の式のlrは学習率(learning rate)はあらかじめ人が決める(試行錯誤的に決める)必要があり、ハイパーパラメータと呼ばれる。

このパラメータはバイアスや重みと異なり自動で決めることはできない、かつ、正しく学習するうえで重要。

def gradient_descent(f, init_x, lr = 0.01, step_num = 100):
    x = init_x
    
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
        
    return x

誤差逆伝播

学習の手順の勾配の算出で用いる。

 計算グラフ

局所的な計算の組み合わせで、複雑な計算を表現できる。微分を効率よく計算できる(チェインルールをうまく利用)。

  1. 計算グラフを構築
  2. 計算グラフ上で計算を左から右へ(順伝播)、右から左へ(逆伝播)進める
加算レイヤ
class AddLayer:
    def __init__(self):
        pass

    def forword(self, x, y):
        out = x + y
        return out

    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1
        return dx, dy
乗算レイヤ
class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None

    def forword(self, x, y):
        self.x = x
        self.y = y
        out = x * y

        return out

    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x

        return dx, dy
ReLUレイヤ
class ReluLayer:
    def __init__(self):
        self.mask = None

    def forword(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx
Sigmoidレイヤ
class Sigmoid:
    def __init__(self):
        self.out = None

    def forword(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out

        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx
バッチ版Affinレイヤ

順伝播で行う行列の内積はアフィン変換と呼ばれる

class Affine:
    def __init__(self, W , b):
        self.W = W
        self.b = b
        self.x = None
        self.dW = None
        self.db = None
        
    def forword(self, x):
        self.x = x
        out = np.dot(x, self.W) + self.b
        
        return out
    
    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis = 0)
        
        return dx
Softmax-with-Lossレイヤ(ソフトマックス関数と損失関数をまとめたレイヤ)

逆伝播の結果がびっくりするくらいキレイ。

逆にこうなるよう、交差エントロピー誤差がうまく設計されている。すばらしい。

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None
        self.t = None
        
    def forword(self,x ,t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss
    
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size
        
        return dx
勾配確認

数値微分誤差逆伝播

→数値微分誤差逆伝播法の実装が正しいかを確認するために用いられることが多い

畳み込みニューラルネットワーク(CNN)

これまで、全結合層をAffineレイヤという名前で実装。

全結合層の問題点はデータの形状が無視されてしまうこと。例えば、画像28×28ピクセルの形状を784個のデータとして入力する。

これは、画像の空間的な相関関係を無視しているため、形状に関する情報を生かすことができない。

一方、畳み込み層は形状を維持する。CNNでは入出力データを特徴マップという。

層が深くなるにつれて、抽出される情報はより抽象化(犬や車など高度な情報へと変化)されていくといわれている。

畳み込み演算、フィルター(カーネル)演算

フィルターのウインドをスライドさせながら、それぞれで積和演算を行う。

このフィルターパラメータがこれまでの重みに対応する。バイアスも同様に存在する。

パディング

入力データの周囲に固定データ(0など)を埋めること。これによって出力サイズを調整することができる。

これによって空間の圧縮により、畳み込みができなくなってしますことを防ぐ。

ストライド

フィルターのウインドをスライドさせる間隔。

三次元データの畳み込み演算

入力データのチェンネル数とフィルター数は同じにすること。

フィルター自体を複数用意すると多チャンネルの特徴マップを出力することができる。

よってフィルターの重みデータは4次元のデータ(チャンネル数4、サイズ5×5、フィルター数20)としてあらわすことができる。

バッチ処理

各層を流れるデータは四次元データとなる。

チャンネル数、サイズに加え、バッチ数が付け加えられる。

プーリング層

縦、横方向の空間(サイズ)を小さくする演算。

  • Maxプーリング:対象領域から最大値をとる演算
  • Averageプーリング:対象領域の平均をとる演算

画像認識の分野では主にMaxプーリングが用いられる。

  1. 入力データを展開する
  2. 行ごとに最大値を求める
  3. 適切な出力サイズに整形する

層の深さに関して

簡単な文字認識などでは層は深くないほうが高精度

大規模画像認識のコンペなどでは層が深いものは上位

そして、層を深くするとパラメータを減らすことができる

しかし、層を深くしすぎると性能が劣ることもあった

ResNet

層を深くすることができる手法で、スキップ構造を導入している。

これによって層を深くすることに比例して性能を向上させることができる。

転移学習

学習済みの重みをベースに学習を行う。

課題

如何に畳み込み層の計算を速く行うか。

つまり、いかに速く効率的に積和演算を行うか。

Tips

  • Numpyとforループは相性が悪い。
  • im2col(image to column)という2次元の行列に変換するツールがある。

参考:

GitHub - oreilly-japan/deep-learning-from-scratch: 『ゼロから作る Deep Learning』のリポジトリ

The MIT License (MIT)
Copyright (c) 2016 Koki Saitoh
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

DeepLearningが分かった気になる「ゼロから作るDeepLearning」ざっくりまとめ - SSSSLIDE

今後調べたい理論

画像の特徴量抽出
  • SIFT
  • SURF
  • HOG
識別機
物体検出
  • Faster R-CNN

[1506.01497] Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

  • SegNet

SegNet

  • Deep Q-Network

DQN | DeepMind

PyCharmの使い方

windowspythonの開発をするときとかに便利

ライブラリのインストール

  • File -> Setting -> Project Interpreter の+ボタンで追加

簡単にインストールできる。numpyとか検索してワンクリックでインストール

python2 or 3 の指定

キーマップの変更

  • File -> Setting -> key map

Emacsキーバインドもあるよ

データセットの作り方参考まとめ

chainerのデータセットの作り方 LinearやCNN - Qiita

機械学習で用いる顔画像データセットの一作り方(1:WebAPIサービスを用いて候補画像取得) - Qiita

【pylearn2】自分のデータセットを使ってカンタンにGRBMしよう - CORDEA blog