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

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

pytorch,keras,chainer x ROSのDockerfileを作ってみる

やりたいこと

ros x deep learningのいろいろなDockerfileを作ってどんな環境でもすぐに開発ができるようにする

以下

  • ubuntu16.04

  • GPU

  • ros-kinetic

をベースとしている

chainer

cupy==1.0.3 chainer==2.1.0

Dockerfile

FROM nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    python-dev \
    python-setuptools \
    python-pip && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN pip install --upgrade pip
RUN pip install cupy==1.0.3 chainer==2.1.0
RUN pip install matplotlib

# install packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    dirmngr \
    gnupg2 \
    && rm -rf /var/lib/apt/lists/*

# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 421C365BD9FF1F717815A3895523BAEEB01FA116

# setup sources.list
RUN echo "deb http://packages.ros.org/ros/ubuntu xenial main" > /etc/apt/sources.list.d/ros-latest.list

# install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    python-rosdep \
    python-rosinstall \
    python-vcstools \
    && rm -rf /var/lib/apt/lists/*

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# bootstrap rosdep
RUN rosdep init \
    && rosdep update

# install ros packages
ENV ROS_DISTRO kinetic
RUN apt-get update && apt-get install -y \
    ros-kinetic-ros-core=1.3.1-0* \
    && rm -rf /var/lib/apt/lists/*

# install cv2
RUN apt-get update && apt-get install -y ros-kinetic-cv-bridge

# install editor and tools
RUN apt-get install -y emacs 
RUN apt-get install -y tmux
RUN apt-get install -y python-tk

# setup entrypoint
COPY ./ros_entrypoint.sh /

ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]

docker images: https://hub.docker.com/r/einstein25/chainer-ros-gpu/

pytorch

gpuが使えているかは

print torch.cuda.is_available()

で確認

Dockerfile

FROM nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    python-dev \
    python-setuptools \
    python-pip && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN pip install --upgrade pip
RUN pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp27-cp27mu-linux_x86_64.whl
RUN pip install torchvision 
RUN pip install matplotlib

# ros install
# install packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    dirmngr \
    gnupg2 \
    && rm -rf /var/lib/apt/lists/*

# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 421C365BD9FF1F717815A3895523BAEEB01FA116

# setup sources.list
RUN echo "deb http://packages.ros.org/ros/ubuntu xenial main" > /etc/apt/sources.list.d/ros-latest.list

# install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    python-rosdep \
    python-rosinstall \
    python-vcstools \
    && rm -rf /var/lib/apt/lists/*

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# bootstrap rosdep
RUN rosdep init \
    && rosdep update

# install ros packages
ENV ROS_DISTRO kinetic
RUN apt-get update && apt-get install -y \
    ros-kinetic-ros-core=1.3.1-0* \
    && rm -rf /var/lib/apt/lists/*

# install cv2
RUN apt-get update && apt-get install -y ros-kinetic-cv-bridge

# setup entrypoint
COPY ./ros_entrypoint.sh /

# install editor and tools
RUN apt-get install -y emacs
RUN apt-get install -y tmux
RUN apt-get install -y python-tk

ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]

docker images: https://hub.docker.com/r/einstein25/pytorch-ros-gpu/

keras

Dockerfile

FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    python-dev \
    python-setuptools \
    python-pip && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN pip install --upgrade pip
RUN pip install matplotlib
RUN pip install tensorflow-gpu keras

# ros install
# install packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    dirmngr \
    gnupg2 \
    && rm -rf /var/lib/apt/lists/*

# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 421C365BD9FF1F717815A3895523BAEEB01FA116

# setup sources.list
RUN echo "deb http://packages.ros.org/ros/ubuntu xenial main" > /etc/apt/sources.list.d/ros-latest.list

# install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    python-rosdep \
    python-rosinstall \
    python-vcstools \
    && rm -rf /var/lib/apt/lists/*

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# bootstrap rosdep
RUN rosdep init \
    && rosdep update

# install ros packages
ENV ROS_DISTRO kinetic
RUN apt-get update && apt-get install -y \
    ros-kinetic-ros-core=1.3.1-0* \
    && rm -rf /var/lib/apt/lists/*

# install cv2
RUN apt-get update && apt-get install -y ros-kinetic-cv-bridge

# setup entrypoint
COPY ./ros_entrypoint.sh /

# install editor and tools
RUN apt-get install -y emacs
RUN apt-get install -y tmux
RUN apt-get install -y python-tk

ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]

docker images: https://hub.docker.com/r/einstein25/keras-ros-gpu/

動作確認

(container)# apt install wget
(container)# wget https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py
(container)# python mnist_cnn.py

Dockerhubを使ってみる

やりたいこと

dockerhubでimageを管理したい

dockerhubにpushする手順

  1. アカウントの登録: https://hub.docker.com/

  2. Create Repository

  3. imageのrepository nameをdockerhubのrepository nameと揃える

    • ex: $ docker tag [image ID] einstein25/chainer-ros-gpu:v1
  4. pushする

    • ex: $ docker push einstein25/chainer-ros-gpu:v1

That's all ! 簡単!!

ROS x Dockerのお勉強

やりたいこと

ROSをDockerで動かしたい。

参考資料

Docker + ROS(kinetic)でチュートリアル - Qiita

https://hub.docker.com/r/_/ros/

docker - ROS Wiki

dockerでROSを試したい - Qiita

ROS-Docker-tutorial.md · GitHub

docs/ros at master · docker-library/docs · GitHub

Mac版DockerでROSを動かしてみたメモ - Qiita

ロボット向けアプリケーションをDockerコンテナで動かす - Qiita

第8回ROS勉強会へ行ってきました - memoメモ

Ubuntu16.04上でDocerを使いROSを動かす: たこ104のブログ

ros.youtalk.jp — ROS 2 + Dockerによるフォールトトレランス実践 (1)

参考動画

ROS Docker Demo - YouTube

Dockerfile

FROM ros:kinetic
# install ros tutorials packages
RUN apt-get update && apt-get install -y
RUN apt-get install -y ros-kinetic-ros-tutorials \
    ros-kinetic-common-tutorials \
    && rm -rf /var/lib/apt/lists/

Build

  • docker build --tag ros:ros-tutorials .

Tutorial

master, talker, listenerの3つを動作させ,talkerが発するメッセージをlistenerが聞く

  1. networkの作成

    • docker network create rosnet
  2. masterの起動

    • docker run -it --net rosnet --name master ros:ros-tutorials roscore
  3. talkerの起動

    • docker run -it --net rosnet --name talker --env ROS_HOSTNAME=talker --env ROS_MASTER_URI=http://master:11311 ros:ros-tutorials rosrun roscpp_tutorials talker
  4. listenerの起動

    • docker run -it --net rosnet --name listener --env ROS_HOSTNAME=listener --env ROS_MASTER_URI=http://master:11311 ros:ros-tutorials rosrun roscpp_tutorials listener

Docker Compose

Now that you have an appreciation for bootstrapping a distributed ROS example manually, lets try and automate it using docker-compose.

rosでいうlaunchファイル的なもの

docker-compose.yml

version: '2'
services:
  master:
    build: .
    container_name: master
    command:
      - roscore

  talker:
    build: .
    container_name: talker
    environment:
      - "ROS_HOSTNAME=talker"
      - "ROS_MASTER_URI=http://master:11311"
    command: rosrun roscpp_tutorials talker

  listener:
    build: .
    container_name: listener
    environment:
      - "ROS_HOSTNAME=listener"
      - "ROS_MASTER_URI=http://master:11311"
    command: rosrun roscpp_tutorials listener

docker build -> runを実行

  • 起動方法
    • docker-compose up

Docker composeの参考

docker-composeを使うと複数コンテナの管理が便利に - Qiita

ROS x Docker x GUI

docker/Tutorials/GUI - ROS Wiki

xhostコマンドの使い方

手順

  1. xhost +

  2. 以下コマンドを実行

docker run -it \
    --env="DISPLAY" \
    --env="QT_X11_NO_MITSHM=1" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    osrf/ros:indigo-desktop-full \
    rqt
export containerId=$(docker ps -l -q)

ROS(kinetic) x Docker x GPU

docker/Tutorials/Hardware Acceleration - ROS Wiki

1: Dockerfileの準備

FROM osrf/ros:kinetic-desktop-full
# nvidia-docker hooks
LABEL com.nvidia.volumes.needed="nvidia_driver"
ENV PATH /usr/local/nvidia/bin:${PATH}
ENV LD_LIBRARY_PATH /usr/local/nvidia/lib:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}

2: build

docker build --tag ros:nvidia .

3: run

nvidia-docker run -it \
    --env="DISPLAY" \
    --env="QT_X11_NO_MITSHM=1" \
    --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
    ros:nvidia \
    bash -c "roscore & rosrun rviz rviz"

f:id:robonchu:20171217192721p:plain

うごけばおけぃ

Deep Learningフレームワークをいろいろ動かす

Dockerfile

以下を追記

RUN apt-get update
RUN apt-get install emacs
RUN apt-get install tmux
RUN apt-get install wget
RUN apt-get install -y python-pip
RUN pip install tensorflow-gpu keras
RUN pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp27-cp27mu-linux_x86_64.whl
RUN pip install torchvision

kerasを動かす

  1. docker cp cuda-8.0/ [コンテナID]:/usr/local/

  2. コンテナ内でPATHの設定

  3. wget https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py

  4. python mnist_cnn.py

pytorchを動かす

  1. python pytorch_sample.py

線形回帰をといてみる

TODO

torch.nn — PyTorch master documentation

chainer

FROM nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    python-dev \
    python-setuptools \
    python-pip && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN pip install --upgrade pip
RUN pip install cupy==1.0.3 chainer==2.1.0

# install packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    dirmngr \
    gnupg2 \
    && rm -rf /var/lib/apt/lists/*

# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 421C365BD9FF1F717815A3895523BAEEB01FA116

# setup sources.list
RUN echo "deb http://packages.ros.org/ros/ubuntu xenial main" > /etc/apt/sources.list.d/ros-latest.list

# install bootstrap tools
RUN apt-get update && apt-get install --no-install-recommends -y \
    python-rosdep \
    python-rosinstall \
    python-vcstools \
    && rm -rf /var/lib/apt/lists/*

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# bootstrap rosdep
RUN rosdep init \
    && rosdep update

# install ros packages
ENV ROS_DISTRO kinetic
RUN apt-get update && apt-get install -y \
    ros-kinetic-ros-core=1.3.1-0* \
    && rm -rf /var/lib/apt/lists/*

# install cv2
RUN apt-get update && apt-get install -y ros-kinetic-cv-bridge

# install editor and tools
RUN apt-get install emacs 
RUN apt-get install tmux 

# setup entrypoint
COPY ./ros_entrypoint.sh /

ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]

TODO

  1. TX2 x ROS上でpytorchを動かす

Dockerのお勉強2(+α:for DeepLearning)

やりたいこと

Dockerfileを使ってPC環境を汚さずに、いろいろなDeep Learningフレームワークを使いたい

公式ドキュメント(日本語)

Docker ドキュメント日本語化プロジェクト — Docker-docs-ja 17.06.Beta ドキュメント

Dockerfileの書き方

例: keras

FROM nvidia/cuda:8.0-cudnn6-runtime

RUN apt update && apt install -y python3-pip
RUN pip3 install tensorflow-gpu keras

Dockerfileのビルド

  • nvidia-docker build -t docker-gpu-keras-workspace ./

コンテナの作成とログイン

  • nvidia-docker run -it docker-gpu-keras-workspace

サンプルの実行

(container)# apt install wget
(container)# wget https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py
(container)# python3 mnist_cnn.py

sudo権限をつける

sudo usermod -aG docker $USER

起動中のコンテナに入る

  • sudo nvidia-docker start [コンテナID]

  • sudo nvidia-docker exec -it [コンテナID] /bin/bash

コンテナからイメージの作成

  • sudo nvidia-docker stop [コンテナID]

  • sudo nvidia-docker commit [コンテナID] custom-docker-gpu-keras-workspace

コンテナとイメージの削除方法

コンテナ

  • docker ps -a

  • docker rm [コンテナID]

コンテナの一括削除

  • sudo docker rm `sudo docker ps -a -q`

イメージ

  • docker images

  • docker rmi -f [イメージ名]

コンテナ名の変更

docker rename old_contena_name new_contena_name

ホスト<->コンテナ間のやりとり

sudo docker cp my.cnf <コンテナID>:/etc/my.cnf

参考: Dockerでホストとコンテナ間でのファイルコピー

Docker imageのtagを取得する方法

以下をbashrcに記載

function docker-taglist {
    curl -s https://registry.hub.docker.com/v1/repositories/$1/tags | sed "s/,/\n/g" | grep name | cut -d '"' -f 4
}

使い方

$ docker-taglist ubuntu

参考: DockerHubのイメージのタグ一覧をコマンドで取得する | Mazn.net

Dockerイメージのコピーと読み込み

コピー

docker save sample-image > sample-image.tar

読み込み

docker load < sample-image.tar

参考:Dockerでイメージをインポート・エクスポートする | UX MILK

Dockerからデバイスを読み込む方法

# docker run -it --device=/dev/video0:/dev/video0 --name test01 [image name] /bin/bash

自分のPCの場合はインカメがvideo0 , usbにさしたwebカメラがvideo1になっている。

第17回 Dockerで植物が育つ様子を自動録画してみよう――その1 (2/2) - ITmedia エンタープライズ

DockerからGUIを表示する方法

1.$ xhost +

  1. $ docker run -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix/:/tmp/.X11-unix [image name] /bin/bash

Start a GUI-Application as root in a Ubuntu Container - General Discussions - Docker Forums

docker/Tutorials/GUI - ROS Wiki

Docker コンテナ上で Matplotlib を動かす

DockerでGUIのアプリ動かすためのメモ

Dockerを用いたGUIアプリケーションの実行 | POSTD

Docker上でwebカメラを読み込みOpenCVのimshowなどをGUIで表示する方法

  1. $ xhost +

  2. $ nvidia-docker run -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix/:/tmp/.X11-unix --device=/dev/video1:/dev/video1 --name test1 output_disp

参考:docker run | Docker Documentation

おまけ1: pytorchでも動かしてみる

NVIDIA GPU CLOUDからイメージをダウンロード

  1. アカウントの登録: ディープ ラーニングと HPC のための GPU 対応クラウド (NGC) - NVIDIA

  2. API Keyの登録

  3. docker pull nvcr.io/nvidia/pytorch:17.12

exampleの実行

  1. sudo nvidia-docker run -it nvcr.io/nvidia/pytorch:17.12

  2. cd examples/mnist

  3. pip install -r requirements.txt

  4. export CUDA_VISIBLE_DEVICES=1

  5. python main.py

動けばOK!

結果

Test set: Average loss: 0.1240, Accuracy: 9620/10000 (96%)

pytorchで線形回帰をかいてみる

参考: ChainerとPyTorchのコードを比較する - 線形回帰編

プログラミング練習: PyTorch練習 02日目 2

PyTorchでニューラルネットワーク、RNN、CNNを実装してみた – データ分析エンジニアが気まぐれに更新するブログ

matplotlibが表示されない時

[短文メモ]matplotlibで、showしても何も表示されない→backend設定を確認

おまけ2: chainerでも動かしてみる

  1. docker pull chainer/chainer

  2. pip install mock

  3. TODO

参考

Dockerイメージの理解とコンテナのライフサイクル

Dockerの作業済みコンテナからイメージを作って移植を楽にする

Docker ノウハウ集

機械学習のお勉強(多層パーセプトロン)

人口知能で人の仕事は奪われるの?

f:id:robonchu:20171209080830p:plain

My answer is ... the end of this blog ↓

教科書

GitHub - scikit-learn/scikit-learn: scikit-learn: machine learning in Python

単層ニューラルネットワーク(ADALINE)の復習

f:id:robonchu:20171209080857p:plain

  • 重みの更新式
    •  w :=  w + \nabla w ,  \nabla w = - \eta \nabla J(w)
  • コスト関数(誤差平方和)J(w)
    •  J(w) = \frac{1}{2} \Sigma_{i} (y^{(i)} - a^{(i)} )^{2}
    •  \frac{\partial J(w)}{\partial w_{j}} = - \Sigma_{i} (y^{(i)} - a^{(i)} )  x_{j}^{(i)} 
    •  \phi(z) = z = a ← ADLINEの場合こうなる
    • y : クラスラベル、 a : activation,ニューロンの活性(線形関数)、 \phi : 活性化関数 
    •  z = \Sigma_{j} w_{j} x_{j} = \bf {w^{T} x} 、
  • クラスラベルの予測
    •   \hat{y} = \begin{cases}
1 \ \ (z >= 0) \\
-1  \ \ (otherwise)
\end{cases}

多層パーセプトロン(Multi-Layer Perceptron )のアーキテクチャ

f:id:robonchu:20171209085301p:plain

上図のように隠れ層がひとつ以上あるネットワークは「ディープ」人口ニューラルネットワークと呼ばれる。

隠れ層の数を増やすとどうなるの?

隠れ層を増やすと表現力は増すが、学習計算の過程で勾配消失問題等も発生する。 如何に表現力を増しつつ、学習過程で誤差情報を伝播させていくかが重要。

f:id:robonchu:20171209091009p:plain

参考:Mind で Neural Network (準備編2) 順伝播・逆伝播 図解 - Qiita

MLPの学習のプロセス

  1. 入力層を出発として訓練データのパターンを順伝播し、出力生成。

  2. 出力を使ってコスト関数を用いて、誤差を計算。この誤差の最小化が目的。

  3. 誤差を逆伝播し、ネットワークの各重みに関する偏導関数を求め、モデルの更新

予測時はこの重みを用いて順伝播で出力を生成する。

つまり、学習の目的は最適関数を最小化するようなネットワークの重み(パラメータ)を計算すること。

順伝播によるニューラルネットワークの活性化

 z^{(2)}_{1} = a^{(1)}_{0} w^{(1)}_{1,0}  +  a^{(1)}_{1} w^{(1)}_{1,1}  + ・・・ + a^{(1)}_{m} w^{(1)}_{1,m}

 a^{(2)}_{1} = \phi {(z^{(2)}_{1} )}

ここでのポイントは学習のために、活性化関数 \phi (・)微分可能でなければならない

複雑な問題を解く際は、非線形の活性化関数であるシグモイド関数等が使われる。

 \phi(z) = \frac{1}{1+e^{-z}}

f:id:robonchu:20171209093822p:plain

これを微分すると

 \phi ' = (1 - \phi) \phi

ととても綺麗な形になるよ。

参考: シグモイド関数を微分する - のんびりしているエンジニアの日記

他にどんな活性化関数があるの?

f:id:robonchu:20171209094222p:plain

このRelu関数がよく使わている。 But ... 2017/10~にswishと呼ばれる新たな活性化関数が発表され、効果がよさ気↓

f:id:robonchu:20171209094401p:plain

式は  \phi(z) = x ・ \sigma(x) と簡単。微分も簡単。

MLPを使って手書き文字を分類してみる

mnistのダウンロードとロード

MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

import os
import struct
import numpy as np
import gzip
 
def load_mnist(path, kind='train'):
    """Load MNIST data from `path`"""
    labels_path = os.path.join(path, 
                               '%s-labels-idx1-ubyte.gz' % kind)
    images_path = os.path.join(path, 
                               '%s-images-idx3-ubyte.gz' % kind)
        
    with gzip.open(labels_path, 'rb') as lbpath:
        lbpath.read(8)
        buffer = lbpath.read()
        labels = np.frombuffer(buffer, dtype=np.uint8)

    with gzip.open(images_path, 'rb') as imgpath:
        imgpath.read(16)
        buffer = imgpath.read()
        images = np.frombuffer(buffer, 
                               dtype=np.uint8).reshape(
            len(labels), 784).astype(np.float64)
 
    return images, labels

X_train, y_train = load_mnist('mnist/', kind='train')
print('Rows: %d, columns: %d' % (X_train.shape[0], X_train.shape[1]))

X_test, y_test = load_mnist('mnist/', kind='t10k')
print('Rows: %d, columns: %d' % (X_test.shape[0], X_test.shape[1]))

表示結果

Rows: 60000, columns: 784
Rows: 10000, columns: 784

クラスラベルが0~9の画像を表示

import matplotlib.pyplot as plt

fig, ax = plt.subplots(nrows=2, ncols=5, sharex=True, sharey=True,)
ax = ax.flatten()
for i in range(10):
    img = X_train[y_train == i][0].reshape(28, 28)
    ax[i].imshow(img, cmap='Greys', interpolation='nearest')

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
# plt.savefig('./figures/mnist_all.png', dpi=300)
plt.show()

f:id:robonchu:20171209102025p:plain

クラスラベルは7の画像を表示

fig, ax = plt.subplots(nrows=5, ncols=5, sharex=True, sharey=True,)
ax = ax.flatten()
for i in range(25):
    img = X_train[y_train == 7][i].reshape(28, 28)
    ax[i].imshow(img, cmap='Greys', interpolation='nearest')

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
# plt.savefig('./figures/mnist_7.png', dpi=300)
plt.show()

f:id:robonchu:20171216100923p:plain

前処理、モデル構築を行う上で、ここのデータの特性をしることがとても重要!!!

多層パーセプトロンの実装

import numpy as np
from scipy.special import expit
import sys


class NeuralNetMLP(object):
    """ Feedforward neural network / Multi-layer perceptron classifier.

    Parameters
    ------------
    n_output : int
        Number of output units, should be equal to the
        number of unique class labels.
    n_features : int
        Number of features (dimensions) in the target dataset.
        Should be equal to the number of columns in the X array.
    n_hidden : int (default: 30)
        Number of hidden units.
    l1 : float (default: 0.0)
        Lambda value for L1-regularization.
        No regularization if l1=0.0 (default)
    l2 : float (default: 0.0)
        Lambda value for L2-regularization.
        No regularization if l2=0.0 (default)
    epochs : int (default: 500)
        Number of passes over the training set.
    eta : float (default: 0.001)
        Learning rate.
    alpha : float (default: 0.0)
        Momentum constant. Factor multiplied with the
        gradient of the previous epoch t-1 to improve
        learning speed
        w(t) := w(t) - (grad(t) + alpha*grad(t-1))
    decrease_const : float (default: 0.0)
        Decrease constant. Shrinks the learning rate
        after each epoch via eta / (1 + epoch*decrease_const)
    shuffle : bool (default: True)
        Shuffles training data every epoch if True to prevent circles.
    minibatches : int (default: 1)
        Divides training data into k minibatches for efficiency.
        Normal gradient descent learning if k=1 (default).
    random_state : int (default: None)
        Set random state for shuffling and initializing the weights.

    Attributes
    -----------
    cost_ : list
      Sum of squared errors after each epoch.

    """
    def __init__(self, n_output, n_features, n_hidden=30,
                 l1=0.0, l2=0.0, epochs=500, eta=0.001,
                 alpha=0.0, decrease_const=0.0, shuffle=True,
                 minibatches=1, random_state=None):

        np.random.seed(random_state)
        self.n_output = n_output
        self.n_features = n_features
        self.n_hidden = n_hidden
        self.w1, self.w2 = self._initialize_weights()
        self.l1 = l1
        self.l2 = l2
        self.epochs = epochs
        self.eta = eta
        self.alpha = alpha
        self.decrease_const = decrease_const
        self.shuffle = shuffle
        self.minibatches = minibatches

    def _encode_labels(self, y, k):
        """Encode labels into one-hot representation

        Parameters
        ------------
        y : array, shape = [n_samples]
            Target values.

        Returns
        -----------
        onehot : array, shape = (n_labels, n_samples)

        """
        onehot = np.zeros((k, y.shape[0]))
        for idx, val in enumerate(y):
            onehot[val, idx] = 1.0
        return onehot

    def _initialize_weights(self):
        """Initialize weights with small random numbers."""
        w1 = np.random.uniform(-1.0, 1.0,
                               size=self.n_hidden*(self.n_features + 1))
        w1 = w1.reshape(self.n_hidden, self.n_features + 1)
        w2 = np.random.uniform(-1.0, 1.0,
                               size=self.n_output*(self.n_hidden + 1))
        w2 = w2.reshape(self.n_output, self.n_hidden + 1)
        return w1, w2

    def _sigmoid(self, z):
        """Compute logistic function (sigmoid)

        Uses scipy.special.expit to avoid overflow
        error for very small input values z.

        """
        # return 1.0 / (1.0 + np.exp(-z))
        return expit(z)

    def _sigmoid_gradient(self, z):
        """Compute gradient of the logistic function"""
        sg = self._sigmoid(z)
        return sg * (1.0 - sg)

    def _add_bias_unit(self, X, how='column'):
        """Add bias unit (column or row of 1s) to array at index 0"""
        if how == 'column':
            X_new = np.ones((X.shape[0], X.shape[1] + 1))
            X_new[:, 1:] = X
        elif how == 'row':
            X_new = np.ones((X.shape[0] + 1, X.shape[1]))
            X_new[1:, :] = X
        else:
            raise AttributeError('`how` must be `column` or `row`')
        return X_new

    def _feedforward(self, X, w1, w2):
        """Compute feedforward step

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ----------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        z3 : array, shape = [n_output_units, n_samples]
            Net input of output layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.

        """
        a1 = self._add_bias_unit(X, how='column')
        z2 = w1.dot(a1.T)
        a2 = self._sigmoid(z2)
        a2 = self._add_bias_unit(a2, how='row')
        z3 = w2.dot(a2)
        a3 = self._sigmoid(z3)
        return a1, z2, a2, z3, a3

    def _L2_reg(self, lambda_, w1, w2):
        """Compute L2-regularization cost"""
        return (lambda_/2.0) * (np.sum(w1[:, 1:] ** 2) +
                                np.sum(w2[:, 1:] ** 2))

    def _L1_reg(self, lambda_, w1, w2):
        """Compute L1-regularization cost"""
        return (lambda_/2.0) * (np.abs(w1[:, 1:]).sum() +
                                np.abs(w2[:, 1:]).sum())

    def _get_cost(self, y_enc, output, w1, w2):
        """Compute cost function.

        Parameters
        ----------
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        output : array, shape = [n_output_units, n_samples]
            Activation of the output layer (feedforward)
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        cost : float
            Regularized cost.

        """
        term1 = -y_enc * (np.log(output))
        term2 = (1.0 - y_enc) * np.log(1.0 - output)
        cost = np.sum(term1 - term2)
        L1_term = self._L1_reg(self.l1, w1, w2)
        L2_term = self._L2_reg(self.l2, w1, w2)
        cost = cost + L1_term + L2_term
        return cost

    def _get_gradient(self, a1, a2, a3, z2, y_enc, w1, w2):
        """ Compute gradient step using backpropagation.

        Parameters
        ------------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        grad1 : array, shape = [n_hidden_units, n_features]
            Gradient of the weight matrix w1.
        grad2 : array, shape = [n_output_units, n_hidden_units]
            Gradient of the weight matrix w2.

        """
        # backpropagation
        sigma3 = a3 - y_enc
        z2 = self._add_bias_unit(z2, how='row')
        sigma2 = w2.T.dot(sigma3) * self._sigmoid_gradient(z2)
        sigma2 = sigma2[1:, :]
        grad1 = sigma2.dot(a1)
        grad2 = sigma3.dot(a2.T)

        # regularize
        grad1[:, 1:] += self.l2 * w1[:, 1:]
        grad1[:, 1:] += self.l1 * np.sign(w1[:, 1:])
        grad2[:, 1:] += self.l2 * w2[:, 1:]
        grad2[:, 1:] += self.l1 * np.sign(w2[:, 1:])

        return grad1, grad2

    def predict(self, X):
        """Predict class labels

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.

        Returns:
        ----------
        y_pred : array, shape = [n_samples]
            Predicted class labels.

        """
        if len(X.shape) != 2:
            raise AttributeError('X must be a [n_samples, n_features] array.\n'
                                 'Use X[:,None] for 1-feature classification,'
                                 '\nor X[[i]] for 1-sample classification')

        a1, z2, a2, z3, a3 = self._feedforward(X, self.w1, self.w2)
        y_pred = np.argmax(z3, axis=0)
        return y_pred

    def fit(self, X, y, print_progress=False):
        """ Learn weights from training data.

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.
        y : array, shape = [n_samples]
            Target class labels.
        print_progress : bool (default: False)
            Prints progress as the number of epochs
            to stderr.

        Returns:
        ----------
        self

        """
        self.cost_ = []
        X_data, y_data = X.copy(), y.copy()
        y_enc = self._encode_labels(y, self.n_output)

        delta_w1_prev = np.zeros(self.w1.shape)
        delta_w2_prev = np.zeros(self.w2.shape)

        for i in range(self.epochs):

            # adaptive learning rate
            self.eta /= (1 + self.decrease_const*i)

            if print_progress:
                sys.stderr.write('\rEpoch: %d/%d' % (i+1, self.epochs))
                sys.stderr.flush()

            if self.shuffle:
                idx = np.random.permutation(y_data.shape[0])
                X_data, y_enc = X_data[idx], y_enc[:, idx]

            mini = np.array_split(range(y_data.shape[0]), self.minibatches)
            for idx in mini:

                # feedforward
                a1, z2, a2, z3, a3 = self._feedforward(X_data[idx],
                                                       self.w1,
                                                       self.w2)
                cost = self._get_cost(y_enc=y_enc[:, idx],
                                      output=a3,
                                      w1=self.w1,
                                      w2=self.w2)
                self.cost_.append(cost)

                # compute gradient via backpropagation
                grad1, grad2 = self._get_gradient(a1=a1, a2=a2,
                                                  a3=a3, z2=z2,
                                                  y_enc=y_enc[:, idx],
                                                  w1=self.w1,
                                                  w2=self.w2)

                delta_w1, delta_w2 = self.eta * grad1, self.eta * grad2
                self.w1 -= (delta_w1 + (self.alpha * delta_w1_prev))
                self.w2 -= (delta_w2 + (self.alpha * delta_w2_prev))
                delta_w1_prev, delta_w2_prev = delta_w1, delta_w2

        return self

one-hot 表現

    def _encode_labels(self, y, k):
        """Encode labels into one-hot representation

        Parameters
        ------------
        y : array, shape = [n_samples]
            Target values.

        Returns
        -----------
        onehot : array, shape = (n_labels, n_samples)

        """
        onehot = np.zeros((k, y.shape[0]))
        for idx, val in enumerate(y):
            onehot[val, idx] = 1.0
        return onehot

参考: One hot表現を実装してみた - Qiita

重みの初期化

    def _initialize_weights(self):
        """Initialize weights with small random numbers."""
        w1 = np.random.uniform(-1.0, 1.0,
                               size=self.n_hidden*(self.n_features + 1))
        w1 = w1.reshape(self.n_hidden, self.n_features + 1)
        w2 = np.random.uniform(-1.0, 1.0,
                               size=self.n_output*(self.n_hidden + 1))
        w2 = w2.reshape(self.n_output, self.n_hidden + 1)
        return w1, w2

ランダムに重みを初期化。

NumPyの使い方(12) 乱数、random - Remrinのpython攻略日記

ここが重要だったりする。非線形性が強く、局所解がたくさんあるため。

少ない画像から画像分類を学習させる方法(kerasで転移学習:fine tuning)

シグモイド関数とその微分

    def _sigmoid(self, z):
        """Compute logistic function (sigmoid)

        Uses scipy.special.expit to avoid overflow
        error for very small input values z.

        """
        # return 1.0 / (1.0 + np.exp(-z))
        return expit(z)

    def _sigmoid_gradient(self, z):
        """Compute gradient of the logistic function"""
        sg = self._sigmoid(z)
        return sg * (1.0 - sg)

他にもたくさんの活性化関数があるよ

最新の性能の良い、活性化関数はswish

参考:

活性化関数ReLUについてとReLU一族【追記あり】 - Qiita ← Good!!!

活性化関数のまとめ(ステップ、シグモイド、ReLU、ソフトマックス、恒等関数) - Qiita

活性化関数 Swish - Qiita

順伝播

    def _feedforward(self, X, w1, w2):
        """Compute feedforward step

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ----------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        z3 : array, shape = [n_output_units, n_samples]
            Net input of output layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.

        """
        a1 = self._add_bias_unit(X, how='column')
        z2 = w1.dot(a1.T)
        a2 = self._sigmoid(z2)
        a2 = self._add_bias_unit(a2, how='row')
        z3 = w2.dot(a2)
        a3 = self._sigmoid(z3)
        return a1, z2, a2, z3, a3

L1正規化、L2正規化

    def _L2_reg(self, lambda_, w1, w2):
        """Compute L2-regularization cost"""
        return (lambda_/2.0) * (np.sum(w1[:, 1:] ** 2) +
                                np.sum(w2[:, 1:] ** 2))

    def _L1_reg(self, lambda_, w1, w2):
        """Compute L1-regularization cost"""
        return (lambda_/2.0) * (np.abs(w1[:, 1:]).sum() +
                                np.abs(w2[:, 1:]).sum())

参考:

RでL1 / L2正則化を実践する - 六本木で働くデータサイエンティストのブログ

コスト関数

ロジスティック関数

  •  J(w) = - [ \Sigma_{i=1}^{n} y^{(i)} log (a^{(i)}) + (1 - y^{(i)}) log (1- a^{(i)}) ]  + \lambda \Sigma_{j=1}^{m} w_{j}^{2}  + \lambda \Sigma_{j=1}^{m} | w_{j} |

  •  a^{(i)} = \phi (z^{(i)})

参考: ロジスティック回帰のコスト関数を眺める - y_uti のブログ

    def _get_cost(self, y_enc, output, w1, w2):
        """Compute cost function.

        Parameters
        ----------
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        output : array, shape = [n_output_units, n_samples]
            Activation of the output layer (feedforward)
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        cost : float
            Regularized cost.

        """
        term1 = -y_enc * (np.log(output))
        term2 = (1.0 - y_enc) * np.log(1.0 - output)
        cost = np.sum(term1 - term2)
        L1_term = self._L1_reg(self.l1, w1, w2)
        L2_term = self._L2_reg(self.l2, w1, w2)
        cost = cost + L1_term + L2_term
        return cost

誤差逆伝播

上記のコスト関数を最小化することが目的であるので、ネットワークのすべての層の重み事に、行列w偏微分係数を計算する必要がある。

  •  \frac{\partial J(w)}{\partial w_{j,i}^{(l)}}

ここで誤差逆伝播を用いると、コスト関数を最小化するための偏微分係数が計算できる。

w は複数の行列で構成されている ↓

f:id:robonchu:20171216104025p:plain

誤差逆伝播の計算方法

  1. フォワードプロパゲーション f:id:robonchu:20171216104521p:plain

  2. フォワードプロパゲーションの出力とラベルにより、誤差ベクトルを計算(証明は省略)

    •  \sigma^{(3)} = a^{(3)} - y
    •   \sigma^{(2)} = (w^{(2)})^{T} \sigma^{(3)}  * \frac{\partial \phi(z^{(2)})}{\partial z^{(2)}} (*は要素ごとの掛け算)
      •  \frac{\partial \phi(z^{(2)})}{\partial z^{(2)}} = (a^{(2)} * (1 - a^{(2)}) )
  3. コスト関数の変微分係数を求める(正規化なしバージョン)

    •  \frac{\partial J(w)}{\partial w_{j,i}^{(l)}}  = a_{j}^{(l)} \sigma_{i}^{(l+1)}
  4. 重みの更新

    •  w^{(l)} := w^{(l)} - \eta \frac{\partial J(w)}{\partial w^{(l)}}

f:id:robonchu:20171216111730p:plain

参考: Pythonで 多層パーセプトロン を実装する | ZABURO app

    def _get_gradient(self, a1, a2, a3, z2, y_enc, w1, w2):
        """ Compute gradient step using backpropagation.

        Parameters
        ------------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        grad1 : array, shape = [n_hidden_units, n_features]
            Gradient of the weight matrix w1.
        grad2 : array, shape = [n_output_units, n_hidden_units]
            Gradient of the weight matrix w2.

        """
        # backpropagation
        sigma3 = a3 - y_enc
        z2 = self._add_bias_unit(z2, how='row')
        sigma2 = w2.T.dot(sigma3) * self._sigmoid_gradient(z2)
        sigma2 = sigma2[1:, :]
        grad1 = sigma2.dot(a1)
        grad2 = sigma3.dot(a2.T)

        # regularize
        grad1[:, 1:] += self.l2 * w1[:, 1:]
        grad1[:, 1:] += self.l1 * np.sign(w1[:, 1:])
        grad2[:, 1:] += self.l2 * w2[:, 1:]
        grad2[:, 1:] += self.l1 * np.sign(w2[:, 1:])

        return grad1, grad2

誤差逆伝播に対する直感を養う

自動微分(順伝播、逆伝播)の考え方↓ ものすごくわかりやすい!

計算グラフの微積分:バックプロパゲーションを理解する | POSTD

一言で言うと、誤差逆伝播を用いると計算コストが低くなる。

参考: 誤差逆伝播法を計算グラフを使って分かりやすく解説する - DeepAge

予測

    def predict(self, X):
        """Predict class labels

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.

        Returns:
        ----------
        y_pred : array, shape = [n_samples]
            Predicted class labels.

        """
        if len(X.shape) != 2:
            raise AttributeError('X must be a [n_samples, n_features] array.\n'
                                 'Use X[:,None] for 1-feature classification,'
                                 '\nor X[[i]] for 1-sample classification')

        a1, z2, a2, z3, a3 = self._feedforward(X, self.w1, self.w2)
        y_pred = np.argmax(z3, axis=0)
        return y_pred

最後argmaxで予測ラベルを算出しているが、softmaxを用いるとそれぞれのクラスの確率を出すことも可能。

参考: Softmaxって何をしてるの? - 画像処理とか機械学習とか

データのシャッフル

            if self.shuffle:
                idx = np.random.permutation(y_data.shape[0])
                X_data, y_enc = X_data[idx], y_enc[:, idx]

トレーニングデータをシャッフルすることでアルゴリズムを循環しないようにする

適応学習率

            # adaptive learning rate
            self.eta /= (1 + self.decrease_const*i)

参考: 勾配降下法の最適化アルゴリズムを概観する | POSTD

重みの更新方法(勾配法)

                delta_w1, delta_w2 = self.eta * grad1, self.eta * grad2
                self.w1 -= (delta_w1 + (self.alpha * delta_w1_prev))
                self.w2 -= (delta_w2 + (self.alpha * delta_w2_prev))
                delta_w1_prev, delta_w2_prev = delta_w1, delta_w2
  1. SGD

    •  W = W - \eta \frac{\partial J(W)}{\partial W}
  2. Momentum

    •  W = W_{i} + \alpha \Delta W_{i-1} - \eta \frac{\partial J(W_{i})}{\partial W_{i}}

参考:

勾配降下法の最適化アルゴリズムを概観する | POSTD ←Good!!!

AdaGrad, RMSProp, Adam, ND-Adam, AMSGrad - Qiita

最適化手法について—SGDからYellowFinまで— | moskomule log

トレーニング

nn = NeuralNetMLP(n_output=10, 
                  n_features=X_train.shape[1], 
                  n_hidden=50, 
                  l2=0.1, 
                  l1=0.0, 
                  epochs=1000, 
                  eta=0.001,
                  alpha=0.001,
                  decrease_const=0.00001,
                  minibatches=50, 
                  shuffle=True,
                  random_state=1)

nn.fit(X_train, y_train, print_progress=True)

コスト関数の推移

import matplotlib.pyplot as plt

plt.plot(range(len(nn.cost_)), nn.cost_)
plt.ylim([0, 2000])
plt.ylabel('Cost')
plt.xlabel('Epochs * 50')
plt.tight_layout()
# plt.savefig('./figures/cost.png', dpi=300)
plt.show()

f:id:robonchu:20171214231240p:plain

ミニバッチごとに平均化したコスト関数の推移

batches = np.array_split(range(len(nn.cost_)), 1000)
cost_ary = np.array(nn.cost_)
cost_avgs = [np.mean(cost_ary[i]) for i in batches]

plt.plot(range(len(cost_avgs)), cost_avgs, color='red')
plt.ylim([0, 2000])
plt.ylabel('Cost')
plt.xlabel('Epochs')
plt.tight_layout()
#plt.savefig('./figures/cost2.png', dpi=300)
plt.show()

f:id:robonchu:20171214231434p:plain

モデルの性能

y_train_pred = nn.predict(X_train)

if sys.version_info < (3, 0):
    acc = ((np.sum(y_train == y_train_pred, axis=0)).astype('float') /
           X_train.shape[0])
else:
    acc = np.sum(y_train == y_train_pred, axis=0) / X_train.shape[0]

print('Training accuracy: %.2f%%' % (acc * 100))

y_test_pred = nn.predict(X_test)

if sys.version_info < (3, 0):
    acc = ((np.sum(y_test == y_test_pred, axis=0)).astype('float') /
           X_test.shape[0])
else:
    acc = np.sum(y_test == y_test_pred, axis=0) / X_test.shape[0]

print('Test accuracy: %.2f%%' % (acc * 100))

結果

Training accuracy: 97.59%

Test accuracy: 95.62%

上記結果からわずかに過学習していることがわかる。

パラメータの最適化に関して、ベイズ的最適化をためしてみたい。

参考: ベイズ的最適化・実験計画法のお勉強 - 空飛ぶロボットのつくりかた

ベイズ的最適化(Bayesian Optimization)の入門とその応用

予測結果を見てみる

miscl_img = X_test[y_test != y_test_pred][:25]
correct_lab = y_test[y_test != y_test_pred][:25]
miscl_lab = y_test_pred[y_test != y_test_pred][:25]

fig, ax = plt.subplots(nrows=5, ncols=5, sharex=True, sharey=True,)
ax = ax.flatten()
for i in range(25):
    img = miscl_img[i].reshape(28, 28)
    ax[i].imshow(img, cmap='Greys', interpolation='nearest')
    ax[i].set_title('%d) t: %d p: %d' % (i+1, correct_lab[i], miscl_lab[i]))

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
# plt.savefig('./figures/mnist_miscl.png', dpi=300)
plt.show()

f:id:robonchu:20171216100649p:plain

ネットワークのデバッグ

ニューラルネットワークの実装をする際、かなり複雑になるため、逆伝播が正しく実装されているかを手動で確認できると安心。

方法は、解析的な勾配数値勾配の比較になる。

以下の図のように数値的に近似された勾配と比較する

f:id:robonchu:20171216121048p:plain

  • 近似式 :  (J(w + \epsilon) - J(w - \epsilon)) / 2\epsilon

  • 比較の仕方 : 正規化されたL2ベクトルノルムが計算することが推奨

    • 相対誤差  = \frac{|| J _n - J_a ||_2}{ || J_n||_2 + ||J_a||_2}
  • 比較時のしきい値はモデルに依存するため、適切に設定する必要がある

参考: ノルムの意味とL1,L2,L∞ノルム | 高校数学の美しい物語

実装

import numpy as np
from scipy.special import expit
import sys


class MLPGradientCheck(object):
    """ Feedforward neural network / Multi-layer perceptron classifier.

    Parameters
    ------------
    n_output : int
        Number of output units, should be equal to the
        number of unique class labels.
    n_features : int
        Number of features (dimensions) in the target dataset.
        Should be equal to the number of columns in the X array.
    n_hidden : int (default: 30)
        Number of hidden units.
    l1 : float (default: 0.0)
        Lambda value for L1-regularization.
        No regularization if l1=0.0 (default)
    l2 : float (default: 0.0)
        Lambda value for L2-regularization.
        No regularization if l2=0.0 (default)
    epochs : int (default: 500)
        Number of passes over the training set.
    eta : float (default: 0.001)
        Learning rate.
    alpha : float (default: 0.0)
        Momentum constant. Factor multiplied with the
        gradient of the previous epoch t-1 to improve
        learning speed
        w(t) := w(t) - (grad(t) + alpha*grad(t-1))
    decrease_const : float (default: 0.0)
        Decrease constant. Shrinks the learning rate
        after each epoch via eta / (1 + epoch*decrease_const)
    shuffle : bool (default: False)
        Shuffles training data every epoch if True to prevent circles.
    minibatches : int (default: 1)
        Divides training data into k minibatches for efficiency.
        Normal gradient descent learning if k=1 (default).
    random_state : int (default: None)
        Set random state for shuffling and initializing the weights.

    Attributes
    -----------
    cost_ : list
        Sum of squared errors after each epoch.

    """
    def __init__(self, n_output, n_features, n_hidden=30,
                 l1=0.0, l2=0.0, epochs=500, eta=0.001,
                 alpha=0.0, decrease_const=0.0, shuffle=True,
                 minibatches=1, random_state=None):

        np.random.seed(random_state)
        self.n_output = n_output
        self.n_features = n_features
        self.n_hidden = n_hidden
        self.w1, self.w2 = self._initialize_weights()
        self.l1 = l1
        self.l2 = l2
        self.epochs = epochs
        self.eta = eta
        self.alpha = alpha
        self.decrease_const = decrease_const
        self.shuffle = shuffle
        self.minibatches = minibatches

    def _encode_labels(self, y, k):
        """Encode labels into one-hot representation

        Parameters
        ------------
        y : array, shape = [n_samples]
            Target values.

        Returns
        -----------
        onehot : array, shape = (n_labels, n_samples)

        """
        onehot = np.zeros((k, y.shape[0]))
        for idx, val in enumerate(y):
            onehot[val, idx] = 1.0
        return onehot

    def _initialize_weights(self):
        """Initialize weights with small random numbers."""
        w1 = np.random.uniform(-1.0, 1.0,
                               size=self.n_hidden*(self.n_features + 1))
        w1 = w1.reshape(self.n_hidden, self.n_features + 1)
        w2 = np.random.uniform(-1.0, 1.0,
                               size=self.n_output*(self.n_hidden + 1))
        w2 = w2.reshape(self.n_output, self.n_hidden + 1)
        return w1, w2

    def _sigmoid(self, z):
        """Compute logistic function (sigmoid)

        Uses scipy.special.expit to avoid overflow
        error for very small input values z.

        """
        # return 1.0 / (1.0 + np.exp(-z))
        return expit(z)

    def _sigmoid_gradient(self, z):
        """Compute gradient of the logistic function"""
        sg = self._sigmoid(z)
        return sg * (1.0 - sg)

    def _add_bias_unit(self, X, how='column'):
        """Add bias unit (column or row of 1s) to array at index 0"""
        if how == 'column':
            X_new = np.ones((X.shape[0], X.shape[1] + 1))
            X_new[:, 1:] = X
        elif how == 'row':
            X_new = np.ones((X.shape[0]+1, X.shape[1]))
            X_new[1:, :] = X
        else:
            raise AttributeError('`how` must be `column` or `row`')
        return X_new

    def _feedforward(self, X, w1, w2):
        """Compute feedforward step

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ----------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        z3 : array, shape = [n_output_units, n_samples]
            Net input of output layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.

        """
        a1 = self._add_bias_unit(X, how='column')
        z2 = w1.dot(a1.T)
        a2 = self._sigmoid(z2)
        a2 = self._add_bias_unit(a2, how='row')
        z3 = w2.dot(a2)
        a3 = self._sigmoid(z3)
        return a1, z2, a2, z3, a3

    def _L2_reg(self, lambda_, w1, w2):
        """Compute L2-regularization cost"""
        return (lambda_/2.0) * (np.sum(w1[:, 1:] ** 2) +
                                np.sum(w2[:, 1:] ** 2))

    def _L1_reg(self, lambda_, w1, w2):
        """Compute L1-regularization cost"""
        return (lambda_/2.0) * (np.abs(w1[:, 1:]).sum() +
                                np.abs(w2[:, 1:]).sum())

    def _get_cost(self, y_enc, output, w1, w2):
        """Compute cost function.

        Parameters
        ----------
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        output : array, shape = [n_output_units, n_samples]
            Activation of the output layer (feedforward)
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        cost : float
            Regularized cost.

        """
        term1 = -y_enc * (np.log(output))
        term2 = (1.0 - y_enc) * np.log(1.0 - output)
        cost = np.sum(term1 - term2)
        L1_term = self._L1_reg(self.l1, w1, w2)
        L2_term = self._L2_reg(self.l2, w1, w2)
        cost = cost + L1_term + L2_term
        return cost

    def _get_gradient(self, a1, a2, a3, z2, y_enc, w1, w2):
        """ Compute gradient step using backpropagation.

        Parameters
        ------------
        a1 : array, shape = [n_samples, n_features+1]
            Input values with bias unit.
        a2 : array, shape = [n_hidden+1, n_samples]
            Activation of hidden layer.
        a3 : array, shape = [n_output_units, n_samples]
            Activation of output layer.
        z2 : array, shape = [n_hidden, n_samples]
            Net input of hidden layer.
        y_enc : array, shape = (n_labels, n_samples)
            one-hot encoded class labels.
        w1 : array, shape = [n_hidden_units, n_features]
            Weight matrix for input layer -> hidden layer.
        w2 : array, shape = [n_output_units, n_hidden_units]
            Weight matrix for hidden layer -> output layer.

        Returns
        ---------
        grad1 : array, shape = [n_hidden_units, n_features]
            Gradient of the weight matrix w1.
        grad2 : array, shape = [n_output_units, n_hidden_units]
            Gradient of the weight matrix w2.

        """
        # backpropagation
        sigma3 = a3 - y_enc
        z2 = self._add_bias_unit(z2, how='row')
        sigma2 = w2.T.dot(sigma3) * self._sigmoid_gradient(z2)
        sigma2 = sigma2[1:, :]
        grad1 = sigma2.dot(a1)
        grad2 = sigma3.dot(a2.T)

        # regularize
        grad1[:, 1:] += self.l2 * w1[:, 1:]
        grad1[:, 1:] += self.l1 * np.sign(w1[:, 1:])
        grad2[:, 1:] += self.l2 * w2[:, 1:]
        grad2[:, 1:] += self.l1 * np.sign(w2[:, 1:])

        return grad1, grad2

    def _gradient_checking(self, X, y_enc, w1, w2, epsilon, grad1, grad2):
        """ Apply gradient checking (for debugging only)

        Returns
        ---------
        relative_error : float
          Relative error between the numerically
          approximated gradients and the backpropagated gradients.

        """
        num_grad1 = np.zeros(np.shape(w1))
        epsilon_ary1 = np.zeros(np.shape(w1))
        for i in range(w1.shape[0]):
            for j in range(w1.shape[1]):
                epsilon_ary1[i, j] = epsilon
                a1, z2, a2, z3, a3 = self._feedforward(X,
                                                       w1 - epsilon_ary1, w2)
                cost1 = self._get_cost(y_enc, a3, w1-epsilon_ary1, w2)
                a1, z2, a2, z3, a3 = self._feedforward(X,
                                                       w1 + epsilon_ary1, w2)
                cost2 = self._get_cost(y_enc, a3, w1 + epsilon_ary1, w2)
                num_grad1[i, j] = (cost2 - cost1) / (2.0 * epsilon)
                epsilon_ary1[i, j] = 0

        num_grad2 = np.zeros(np.shape(w2))
        epsilon_ary2 = np.zeros(np.shape(w2))
        for i in range(w2.shape[0]):
            for j in range(w2.shape[1]):
                epsilon_ary2[i, j] = epsilon
                a1, z2, a2, z3, a3 = self._feedforward(X, w1,
                                                       w2 - epsilon_ary2)
                cost1 = self._get_cost(y_enc, a3, w1, w2 - epsilon_ary2)
                a1, z2, a2, z3, a3 = self._feedforward(X, w1,
                                                       w2 + epsilon_ary2)
                cost2 = self._get_cost(y_enc, a3, w1, w2 + epsilon_ary2)
                num_grad2[i, j] = (cost2 - cost1) / (2.0 * epsilon)
                epsilon_ary2[i, j] = 0

        num_grad = np.hstack((num_grad1.flatten(), num_grad2.flatten()))
        grad = np.hstack((grad1.flatten(), grad2.flatten()))
        norm1 = np.linalg.norm(num_grad - grad)
        norm2 = np.linalg.norm(num_grad)
        norm3 = np.linalg.norm(grad)
        relative_error = norm1 / (norm2 + norm3)
        return relative_error

    def predict(self, X):
        """Predict class labels

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.

        Returns:
        ----------
        y_pred : array, shape = [n_samples]
            Predicted class labels.

        """
        if len(X.shape) != 2:
            raise AttributeError('X must be a [n_samples, n_features] array.\n'
                                 'Use X[:,None] for 1-feature classification,'
                                 '\nor X[[i]] for 1-sample classification')

        a1, z2, a2, z3, a3 = self._feedforward(X, self.w1, self.w2)
        y_pred = np.argmax(z3, axis=0)
        return y_pred

    def fit(self, X, y, print_progress=False):
        """ Learn weights from training data.

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.
        y : array, shape = [n_samples]
            Target class labels.
        print_progress : bool (default: False)
            Prints progress as the number of epochs
            to stderr.

        Returns:
        ----------
        self

        """
        self.cost_ = []
        X_data, y_data = X.copy(), y.copy()
        y_enc = self._encode_labels(y, self.n_output)

        delta_w1_prev = np.zeros(self.w1.shape)
        delta_w2_prev = np.zeros(self.w2.shape)

        for i in range(self.epochs):

            # adaptive learning rate
            self.eta /= (1 + self.decrease_const*i)

            if print_progress:
                sys.stderr.write('\rEpoch: %d/%d' % (i+1, self.epochs))
                sys.stderr.flush()

            if self.shuffle:
                idx = np.random.permutation(y_data.shape[0])
                X_data, y_enc = X_data[idx], y_enc[idx]

            mini = np.array_split(range(y_data.shape[0]), self.minibatches)
            for idx in mini:

                # feedforward
                a1, z2, a2, z3, a3 = self._feedforward(X[idx],
                                                       self.w1,
                                                       self.w2)
                cost = self._get_cost(y_enc=y_enc[:, idx],
                                      output=a3,
                                      w1=self.w1,
                                      w2=self.w2)
                self.cost_.append(cost)

                # compute gradient via backpropagation
                grad1, grad2 = self._get_gradient(a1=a1, a2=a2,
                                                  a3=a3, z2=z2,
                                                  y_enc=y_enc[:, idx],
                                                  w1=self.w1,
                                                  w2=self.w2)

                # start gradient checking
                grad_diff = self._gradient_checking(X=X_data[idx],
                                                    y_enc=y_enc[:, idx],
                                                    w1=self.w1,
                                                    w2=self.w2,
                                                    epsilon=1e-5,
                                                    grad1=grad1,
                                                    grad2=grad2)


                if grad_diff <= 1e-7:
                    print('Ok: %s' % grad_diff)
                elif grad_diff <= 1e-4:
                    print('Warning: %s' % grad_diff)
                else:
                    print('PROBLEM: %s' % grad_diff)

                # update weights; [alpha * delta_w_prev] for momentum learning
                delta_w1, delta_w2 = self.eta * grad1, self.eta * grad2
                self.w1 -= (delta_w1 + (self.alpha * delta_w1_prev))
                self.w2 -= (delta_w2 + (self.alpha * delta_w2_prev))
                delta_w1_prev, delta_w2_prev = delta_w1, delta_w2

        return self

nn_check = MLPGradientCheck(n_output=10, 
                            n_features=X_train.shape[1], 
                            n_hidden=10, 
                            l2=0.0, 
                            l1=0.0, 
                            epochs=10, 
                            eta=0.001,
                            alpha=0.0,
                            decrease_const=0.0,
                            minibatches=1, 
                            shuffle=False,
                            random_state=1)

nn_check.fit(X_train[:5], y_train[:5], print_progress=False)

結果

Ok: 2.55068505986e-10
Ok: 2.93547837023e-10
Ok: 2.37449571314e-10
Ok: 3.08194323691e-10
Ok: 3.38249440642e-10
Ok: 3.57890221135e-10
Ok: 2.19231256383e-10
Ok: 2.36583740198e-10
Ok: 3.43584860701e-10
Ok: 2.13345208113e-10

Tips

  • チェックのコードはできるだけシンプルに

  • 計算コストが高いためデバッグ目的のみで使用しよう

ニューラルネットの収束

以下の図のように次元数が多いため、コスト局面は複雑で適切に学習を行うためには、パラメータ(学習率)の調節が重要。

f:id:robonchu:20171216162938p:plain

おまけ

手書き文字のデータを見ると文字が歪んでいたり、傾いていたりする

歪補正(deskew)

共分散の大きさを用いて、アフィン変換で歪みを補正。

データの前処理でこのようなことをおこなうことで学習結果がよくなったりする。

参考:

せん断写像 - Wikipedia

アフィン変換とは - 大人になってからの再学習

Deskewing

teaching-ncso/91-mnist-deskew.ipynb at master · tmbdev/teaching-ncso · GitHub

3層パーセプトロン

参考: Pythonで3層パーセプトロンの誤差逆伝播を実装してみる - TadaoYamaokaの日記

感想

深層学習で認識や音声など様々な分野での性能は向上するが、様々な汎用的なタスクをひとつのモデルでこなすのは現状は難しく、人間による前処理やタスクに適切なモデル選択、設計が必要。

なので、思考や動作を伴う汎用、複合的なタスクをロボットやシステムができるようになるのはもう少し先という印象。

おまけ

CNN

機械学習のお勉強(CNN) - 空飛ぶロボットのつくりかた

定番のConvolutional Neural Networkをゼロから理解する - DeepAge

RNN

RNN:時系列データを扱うRecurrent Neural Networksとは - DeepAge

全体参考

バイトオーダ - ビッグエンディアン/リトルエディアン

機械学習のお勉強(Webアプリケーション)

教科書

GitHub - rasbt/python-machine-learning-book: The "Python Machine Learning (1st edition)" book code repository and info resource

robonchu.hatenablog.com の内容を実行している前提

例:http://raschkas.pythonanywhere.com/results

学習済みのscikit-learn推定器をシリアライズする

  • モデルの永続化のひとつの方法はpickleを使う

  • Numpyが含まれている場合はjoblibのほうが効率的らしい

import pickle
import os

dest = os.path.join('movieclassifier', 'pkl_objects')
if not os.path.exists(dest):
    os.makedirs(dest)

pickle.dump(stop, open(os.path.join(dest, 'stopwords.pkl'), 'wb'), protocol=2)  # 4)   
pickle.dump(clf, open(os.path.join(dest, 'classifier.pkl'), 'wb'), protocol=2)  # 4)

シリアライズ

import pickle
import re
import os
from vectorizer import vect

clf = pickle.load(open(os.path.join('pkl_objects', 'classifier.pkl'), 'rb'))

import numpy as np
label = {0:'negative', 1:'positive'}

example = ['I love this movie']
X = vect.transform(example)
print('Prediction: %s\nProbability: %.2f%%' %\
      (label[clf.predict(X)[0]], clf.predict_proba(X).max()*100))

上記スクリプトで使っている関数

from sklearn.feature_extraction.text import HashingVectorizer
import re
import os
import pickle

cur_dir = os.path.dirname(__file__)
stop = pickle.load(open(
                os.path.join(cur_dir, 
                'pkl_objects', 
                'stopwords.pkl'), 'rb'))

def tokenizer(text):
    text = re.sub('<[^>]*>', '', text)
    emoticons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',
                           text.lower())
    text = re.sub('[\W]+', ' ', text.lower()) \
                   + ' '.join(emoticons).replace('-', '')
    tokenized = [w for w in text.split() if w not in stop]
    return tokenized

vect = HashingVectorizer(decode_error='ignore',
                         n_features=2**21,
                         preprocessor=None,
                         tokenizer=tokenizer)

SQLiteデータベースをセットアップ

Webアプリケーションの予測に対するユーザーからのフィードバックを収集する

SQLite

f:id:robonchu:20171126101921p:plain

参考:

SQLite Home Page

SQLite入門

batteries included

pythonのbatteries includedの考え方: 標準ライブラリだけでも様々なことができる

参考: What “Batteries Included” Means | Musings of an Anonymous Geek

データベースの作成

batteries includedの考え方からsqlite3というSQLiteを操作できる標準APIがある

11.13. sqlite3 — SQLite データベースに対する DB-API 2.0 インタフェース — Python 2.7.14 ドキュメント

  • データベースの作成
import sqlite3
import os

if os.path.exists('reviews.sqlite'):
    os.remove('reviews.sqlite')

conn = sqlite3.connect('reviews.sqlite')
c = conn.cursor()
c.execute('CREATE TABLE review_db (review TEXT, sentiment INTEGER, date TEXT)')

example1 = 'I love this movie'
c.execute("INSERT INTO review_db (review, sentiment, date) VALUES (?, ?, DATETIME('now'))", (example1, 1))

example2 = 'I disliked this movie'
c.execute("INSERT INTO review_db (review, sentiment, date) VALUES (?, ?, DATETIME('now'))", (example2, 0))

conn.commit()
conn.close()
  • データベースの内容確認
conn = sqlite3.connect('reviews.sqlite')
c = conn.cursor()

c.execute("SELECT * FROM review_db WHERE date BETWEEN '2015-01-01 10:10:10' AND DATETIME('now')")
results = c.fetchall()

conn.close()

print(results)

結果

[(u'I love this movie', 1, u'2017-11-26 01:06:38'), (u'I disliked this movie', 0, u'2017-11-26 01:06:38')]

Firefoxのバージョン 57.0ではSQLite Managerのpluginがつかえず...

f:id:robonchu:20171126101144p:plain

FlaskでのWebアプリ開発

f:id:robonchu:20171126101934p:plain

参考:

Quickstart — Flask Documentation (0.12)

Flaskの簡単な使い方 - Qiita

[Python] 軽量WebフレームワークのFlaskに入門(準備、起動、HTML、静的ファイル、GET、POSTなど) - YoheiM .NET

なれるための単純な開発

1st_flask_app_1/
├── app.py  <- メインのコード
└── templates  
    └── first_app.html  <- webブラウザでレンダリングする静的なHTMLファイル

app.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('first_app.html')

if __name__ == '__main__':
    app.run(debug=True)

first_app.html

<!doctype html>
<html>
  <head>
    <title>First app</title>
  </head>
  <body>

  <div>
    Hi, this is my first Flask web app!
  </div>

  </body>
</html>

HTMLのわかりやすい参考: HTML | MDN

実行手順

  1. python app.py

  2. ブラウザでhttp://127.0.0.1:5000/にアクセス

  3. 「Hi, this is my first Flask web app! 」とでていることを確認

WTFormsでデータ収集

参考: WTForms Documentation

Flask にフォームがないから WTForms 使ってみた - present

WTFormsを学ぶ その1 - 学んだことをメモする日記

WTFormsを学ぶ その2 - 学んだことをメモする日記

WTFormsを学ぶ その3 - 学んだことをメモする日記

下記のような名前を入力して挨拶をするものをつくる

f:id:robonchu:20171126110945p:plain

f:id:robonchu:20171126110949p:plain

1st_flask_app_2/
├── app.py
├── static
│   └── style.css
└── templates
    ├── _formhelpers.html
    ├── first_app.html
    └── hello.html

app.py

from flask import Flask, render_template, request
from wtforms import Form, TextAreaField, validators

app = Flask(__name__)

class HelloForm(Form):
    sayhello = TextAreaField('',[validators.DataRequired()])

@app.route('/')
def index():
    form = HelloForm(request.form)
    return render_template('first_app.html', form=form)

@app.route('/hello', methods=['POST'])
def hello():
    form = HelloForm(request.form)
    if request.method == 'POST' and form.validate():
        name = request.form['sayhello']
        return render_template('hello.html', name=name)
    return render_template('first_app.html', form=form)

if __name__ == '__main__':
    app.run(debug=True)

_formhelpers.html

Jinja2というテンプレートエンジンを用いている

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
  </dt>
{% endmacro %}

style.css : CSScascading style sheets)ファイル

body {
    font-size: 2em;
}

first_app.html

<!doctype html>
<html>
  <head>
    <title>First app</title>
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body>

{% from "_formhelpers.html" import render_field %}

<div>What's your name?</div>
<form method=post action="/hello">

  <dl>
      {{ render_field(form.sayhello) }}
  </dl>

  <input type=submit value='Say Hello' name='submit_btn'>

</form>

  </body>
</html>

hello.html

<!doctype html>
<html>
  <head>
    <title>First app</title>
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body>

<div>Hello {{ name }}</div>


  </body>
</html>

Jinja2

テンプレートエンジン. pythonの結果からHTMLを作成したいときなどに便利.

Welcome to Jinja2 — Jinja2 Documentation (2.11)

jinja2 — it-note 1.0 ドキュメント

CSScascading style sheets)ファイル

HTMLドキュメントのルック&フィール(コンピュータの操作画面の見た目や操作感のこと)が指定できる

映画レビュー分類器をwebアプリとして実装

下記のようなレビューを書いて、その予測結果をフィードバックし、感謝を伝えるツールを作る

http://raschkas.pythonanywhere.com/results

f:id:robonchu:20171126110953p:plainf:id:robonchu:20171126111000p:plainf:id:robonchu:20171126111153p:plain

movieclassifier_with_update/
├── app.py
├── pkl_objects
│   ├── classifier.pkl
│   └── stopwords.pkl
├── reviews.sqlite
├── static
│   └── style.css
├── templates
│   ├── _formhelpers.html
│   ├── results.html
│   ├── reviewform.html
│   └── thanks.html
├── update.py
└── vectorizer.py

app.py

from flask import Flask, render_template, request
from wtforms import Form, TextAreaField, validators
import pickle
import sqlite3
import os
import numpy as np

# import HashingVectorizer from local dir
from vectorizer import vect

app = Flask(__name__)

######## Preparing the Classifier
cur_dir = os.path.dirname(__file__)
clf = pickle.load(open(os.path.join(cur_dir,
                 'pkl_objects',
                 'classifier.pkl'), 'rb'))
db = os.path.join(cur_dir, 'reviews.sqlite')

def classify(document):
    label = {0: 'negative', 1: 'positive'}
    X = vect.transform([document])
    y = clf.predict(X)[0]
    proba = np.max(clf.predict_proba(X))
    return label[y], proba

def train(document, y):
    X = vect.transform([document])
    clf.partial_fit(X, [y])

def sqlite_entry(path, document, y):
    conn = sqlite3.connect(path)
    c = conn.cursor()
    c.execute("INSERT INTO review_db (review, sentiment, date)"\
    " VALUES (?, ?, DATETIME('now'))", (document, y))
    conn.commit()
    conn.close()

######## Flask
class ReviewForm(Form):
    moviereview = TextAreaField('',
                                [validators.DataRequired(),
                                validators.length(min=15)])

@app.route('/')
def index():
    form = ReviewForm(request.form)
    return render_template('reviewform.html', form=form)

@app.route('/results', methods=['POST'])
def results():
    form = ReviewForm(request.form)
    if request.method == 'POST' and form.validate():
        review = request.form['moviereview']
        y, proba = classify(review)
        return render_template('results.html',
                                content=review,
                                prediction=y,
                                probability=round(proba*100, 2))
    return render_template('reviewform.html', form=form)

@app.route('/thanks', methods=['POST'])
def feedback():
    feedback = request.form['feedback_button']
    review = request.form['review']
    prediction = request.form['prediction']

    inv_label = {'negative': 0, 'positive': 1}
    y = inv_label[prediction]
    if feedback == 'Incorrect':
        y = int(not(y))
    train(review, y)
    sqlite_entry(db, review, y)
    return render_template('thanks.html')

if __name__ == '__main__':
    app.run(debug=True)

reviewform.html

<!doctype html>
<html>
  <head>
    <title>Movie Classification</title>
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body>

<h2>Please enter your movie review:</h2>

{% from "_formhelpers.html" import render_field %}

<form method=post action="/results">
  <dl>
    {{ render_field(form.moviereview, cols='30', rows='10') }}
  </dl>
  <div>
      <input type=submit value='Submit review' name='submit_btn'>
  </div>
</form>

  </body>
</html>

results.html

<!doctype html>
<html>
  <head>
    <title>Movie Classification</title>
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body>

<h3>Your movie review:</h3>
<div>{{ content }}</div>

<h3>Prediction:</h3>
<div>This movie review is <strong>{{ prediction }}</strong>
     (probability: {{ probability }}%).</div>

<div id='button'>
      <form action="/thanks" method="post">
        <input type=submit value='Correct' name='feedback_button'>
        <input type=submit value='Incorrect' name='feedback_button'>
        <input type=hidden value='{{ prediction }}' name='prediction'>
        <input type=hidden value='{{ content }}' name='review'>
      </form>
</div>

<div id='button'>
      <form action="/">
        <input type=submit value='Submit another review'>
      </form>
</div>

  </body>
</html>

style.css

body{
    width:600px;
}

.button{
    padding-top: 20px;
}

thanks.html

<!doctype html>
<html>
  <head>
    <title>Movie Classification</title>
   <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body>

<h3>Thank you for your feedback!</h3>

<div id='button'>
      <form action="/">
        <input type=submit value='Submit another review'>
      </form>
</div>

  </body>
</html>

実行

python app.py

Webアプリケーションをパブ陸Webサーバーにデプロイ

これまでのアプリのローカルでのテストが完了すればパブリックでデプロイする

PythonAnywhere

Host, run, and code Python in the cloud: PythonAnywhere

  1. アカウントの登録

  2. Add a new web app

  3. File Upload or do something

そうすれば<ユーザー名>.pythonanywhere.comでアクセスできる

参考:

Flask Tutorial (part 5) - deploying to PythonAnywhere - YouTube

Simple Flask Hosting: PythonAnywhere

映画レビュー分類器の更新

サーバーがクラッシュした際にデータを消えないようにするには更新されるたびにclfオブジェクトをシリアライズすることが考えられる。

しかし、ユーザーの数が増えるに従い、計算効率が悪くなる、また、同時に更新した際にはファイルが壊れるかもしれない。

なので、もうひとつの方法として、 SQLiteデータベースに収集されたフィードバックデータを使った予測モデルの更新 がある。

実装

update.py *

import pickle
import sqlite3
import numpy as np
import os

# import HashingVectorizer from local dir
from vectorizer import vect

def update_model(db_path, model, batch_size=10000):

    conn = sqlite3.connect(db_path)
    c = conn.cursor()
    c.execute('SELECT * from review_db')

    results = c.fetchmany(batch_size)
    while results:
        data = np.array(results)
        X = data[:, 0]
        y = data[:, 1].astype(int)

        classes = np.array([0, 1])
        X_train = vect.transform(X)
        model.partial_fit(X_train, y, classes=classes)
        results = c.fetchmany(batch_size)

    conn.close()
    return model

cur_dir = os.path.dirname(__file__)

clf = pickle.load(open(os.path.join(cur_dir,
                  'pkl_objects',
                  'classifier.pkl'), 'rb'))
db = os.path.join(cur_dir, 'reviews.sqlite')

clf = update_model(db_path=db, model=clf, batch_size=10000)

# Uncomment the following lines if you are sure that
# you want to update your classifier.pkl file
# permanently.

# pickle.dump(clf, open(os.path.join(cur_dir,
#             'pkl_objects', 'classifier.pkl'), 'wb')
#             , protocol=4)

これをapp.pyで呼び出すようにする

以下の二箇所をapp.pyに追加

from update import update_model
if __name__ == '__main__' :
    clf = update_model(dp_path=db, model=clf, batch_size=10000)

softkineticをros(kinetic)で動かしみる

f:id:robonchu:20171125212310p:plain

f:id:robonchu:20171125212320j:plain

softkinetic(ToFセンサ)を3つゲットしたので動かしてみる

https://www.softkinetic.com/

softkinetic - ROS Wiki

softkineticのSDKのダウンロード

  1. アカウント登録

  2. SDKのダウンロード:https://www.softkinetic.com/language/fr-BE/Support/Download/EntryId/517

  3. 上記SDKを/opt以下に配置

  4. ~/.bashrcにpathを追加:export LD_LIBRARY_PATH=/opt/softkinetic/DepthSenseSDK/lib/:$LD_LIBRARY_PATH

rosのsoftkineticのpackageをインストール&demoの実行

  1. git clone https://github.com/ipa320/softkinetic.git

  2. branchをkinetic-devに

  3. ワークスペースを作ってcatkin_make

  4. source devel/setup.bash

  5. roslaunch softkinetic_camera softkinetic_camera_demo.launch

f:id:robonchu:20171125212350p:plain

イエイ、動いた。

これを使ってPCLのお勉強とNewロボットを作ろう。

Error: No space left on device

これが出たら以下の記事を参考に対処

ln -s /opt/softkinetic/DepthSenseSDK/lib/libDepthSensePlugins.so.1 /opt/softkinetic/DepthSenseSDK/lib/libDepthSensePlugins.so ln -s /opt/softkinetic/DepthSenseSDK/lib/libDepthSense.so.1 /opt/softkinetic/DepthSenseSDK/lib/libDepthSense.so

をしないと行けない可能性あり。