うごくものづくりのために

技術的な備忘録がメインです。

DeepLearningの勉強メモ

明けましておめでとうございます。
正月休み暇なので、「人工知能でなんとかなりませんか」おじさんに対抗できるよう理論武装しておきたいと思います。
なお、この記事は自分用のメモであるため、読んでもあまり参考にはならないかと思います。。。

書籍「ゼロから作るDeepLearning」をベースに進めていきます。

勉強は書籍を一通り嘗めてやる予定ですが、ブログには新しく学んだことや忘れそうなことだけ書いておくことにします。


3章 ニューラルネットワーク

パーセプトロンニューラルネットワークの違い

パーセプトロン:ステップ関数
ニューラルネットワーク:シグモイド関数

出力層の活性化関数

解決したい問題によって変更する
回帰問題は恒等関数(出力層への入力をそのまま出力する)
2クラス分類はシグモイド等
多クラス分類はソフトマックス

ソフトマックス関数


y_k = \frac{\exp (a_k)}{\sum_{i=1}^{n} \exp(a_i)}

出力の総和が1になるので、各出力を確率として扱うことができる

\exp(n)が、nの値によってすぐ巨大な数になるので、オーバーフロー対策が必要

学習ステップでは使うけど、推論ステップでは用いない
入力と出力の各要素の大小関係は変動しないので、使う意味がほぼ無い

4章 ニューラルネットワークの学習

勾配降下法


\boldsymbol{x_{n+1}} = \boldsymbol{x_n}  - \eta \frac{\partial f}{\boldsymbol{\partial x_n}}

あるサンプル点 \boldsymbol{x_n}偏微分を計算(数値微分)して、傾きを求める
その傾きに学習率  \eta を掛けて、次のサンプル点 \boldsymbol{x_{n+1}} とする

学習率 \etaはハイパーパラメータ
ハイパーパラメータのチューニングは人力が多いが、自動化の手法もある (グリッドサーチ、ベイズ最適化等)

損失関数が重みの関数として表されるので、損失関数の極小値を求めることで重みを最適化(?)できる

重みの初期値

正規分布で初期化するのが標準的みたい
重みの初期値は学習がうまくいくかどうかの重要なパラメータになるので、必ずしも正規分布で初期化するのが正しいとはいえない

5章 誤差逆伝播

計算グラフ

演算をノード、数値を矢印として表現し、グラフで計算を表す
ある値Aに対する値Bの偏微分は、矢印を逆順に辿り、各矢印の偏微分係数をかけ合わせていくだけで計算できる
矢印ごとの偏微分係数は、その矢印を通るすべての演算において再利用可能

出力層の活性化関数と損失関数の組み合わせ

正しい組み合わせを使うと、逆伝播される値が出力信号と教師信号の差分 \boldsymbol{y} - \boldsymbol{t}というシンプルな数式になる。
そうなるように、損失関数が設計されている。

多クラス分類:「ソフトマックス関数」と「交差エントロピー誤差」の組み合わせ
回帰    :「恒等関数」と「二乗和誤差」の組み合わせ

勾配確認

誤差逆伝播法による微分は実装ミスが起こりやすいため、数値微分の結果と比較することで実装の正しさを検証する

6章 学習に関するテクニック

最適化手法

  • SGD 普通の勾配降下法
    最適化対象の関数に等方性が無い場合、収束が遅くなる

  • Momentum 最適化の探索に慣性を導入するイメージ
    探索点の移動のさせ方について、SGDでは勾配 = 速度だが、Momentumでは勾配 = 加速度 になるイメージ

重みの初期値

重みの初期値は重要なハイパーパラメータ
重みの初期値によっては、学習が全く進まないことがある(勾配消失、表現力の制限など)

活性化関数の種類によって、適切な重みの初期値が変わる
ReLUの場合:Heの初期値
 stddiv. \sigma =\sqrt{ \frac{2}{n}} | n : 前層のノード数

sigmoid, tanhの場合:Xavierの初期値
 stddiv. \sigma =\sqrt{ \frac{1}{n}} | n : 前層のノード数

Batch Normalization

層の出力に対して、平均が0, 分散が1となるように正規化を施す

効果

  • 学習を早く進行させられる
  • 初期値にそれほど依存しない
  • 過学習を抑制する

過学習

訓練データのみに適応したネットワークになってしまうこと
ネットワークの表現力が高かったり、訓練データが少ない際に起こりやすい
大きすぎる重みによって、過学習が引き起こされることが多い

対策 - Weight decay 損失関数に重みの2乗ノルムを追加する
重みが大きいと損失関数が大きくなるので、重みが小さくなるように学習が進む

  • Dropout 学習時に隠れ層のニューロンを消去して学習する
    推論時は全ての層を使って計算するが、各ニューロンの出力に対して学習時に消去した割合を乗算する
    仮想的にアンサンブル学習を行っているものとみなせる

ハイパーパラメータの探索

ハイパーパラメータの探索用のパラメータとして、検証データを用意する

DeepLearningで用いるデータセットは - 訓練データ - 検証データ - テストデータ の3つになる

テストデータでハイパーパラメータの探索をしない理由…
テストデータに適したハイパーパラメータになってしまう… → 過学習的なことが起こる

ノウハウで行う部分が大きい
ベイズ最適化という手法で、かっこよくハイパーパラメータの最適化ができる(らしい)

実験計画法とか使えないのかな…?

7章 畳み込みニューラルネットワーク

 データの次元

普通のNNは入力データを1次元化してしまうので、2次元画像に含まれる特徴が失われる可能性がある
CNNはデータの形状を維持したまま入力できる

Orange Pi 比較表を作った

OrangePi、いいですよね。安い。

が、買おうと思ってもどれがどれだかよくわからなかったので、比較表を作りました。
(公式からデータコピペしてきただけですが、、、)

docs.google.com

それだけ。

はじめまして、ROS2

めでたくROS2がリリースされたので、Macにインストールして遊んでみようと思います。

環境

OS : Mac OS Sierra 10.12.6
CPU : Core i5 2GHz
RAM : 8GB

インストール

以下のページを参考にしながら進めます。

github.com

事前に必要なもののインストール

ROS2本体をインストールする前に、いくつかインストールが必要なものがあります。

まず初めにbrewをインストール。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brewがインストールできたら以下のコマンドを実行し、brewを用いて他に必要なものをインストールします。

brew install python3

# install asio and tinyxml2 for Fast-RTPS
brew install asio

# ardent was compiled and released using tinyxml2 5.0.1
brew uninstall --force tinyxml2
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/0b3ee2f67043af1b4270096c13350420e3290399/Formula/tinyxml2.rb
brew pin tinyxml2

# install dependencies for robot state publisher
brew install tinyxml eigen pcre

# OpenCV isn't a dependency of ROS 2, but it is used by some demos.
brew install opencv

# install OpenSSL for DDS-Security
brew install openssl

# install Qt for RViz
brew install qt freetype assimp

次にこのコマンドを実行し、コマンドラインツールに関連するランタイムを入れます。

# Install additional runtime dependencies for command-line tools:
python3 -m pip install pyyaml setuptools argcomplete

System Integrity Protection(SIP)の無効化

System Integrity Protection(SIP)なる機能を無効化します。 OS X の version >=10.11 はSIPがデフォルトでONになっていて、そのままだと環境変数がうまく読み込めないらしいです。
以下の手順に従います。リカバリーOSでの作業が必要なので、手順を紙にメモる等してください。

Configuring System Integrity Protection

  1. Macを再起動します。起動時にCmd + Rキーを長押しして、リカバリーOSを立ち上げます。
  2. ユーティリティメニューから端末を立ち上げます。
  3. 以下のコマンドを実行します。
csrutil disable
reboot

再起動後、念のためのSIPが無効化されているかを確認します。

csrutil status
=> System Integrity Protection status: disabled.

ROS2のインストール

満を持して。

まずは、以下のリリースページからOS X向けの最新のパッケージをダウンロードします。
Releases · ros2/ros2 · GitHub

いくつかOS X向けのパッケージがありますが、今回はros2-ardent-package-osx-fastrtps-x86_64.tar.bz2を選択しました。 どうやらROS2に組み込まれているDDSの種類によってパッケージが分かれているようです。 ちなみに、opensliceと含まれているものをインストールする場合は、opensliceを別途インストールする必要があります。
DDSに関して、詳しくは以下のページを参照してください。
DDS and ROS middleware implementations · ros2/ros2 Wiki · GitHub

DLはそこそこ時間がかかります。200MB強ある上にサーバの回線が細いらしく、僕の環境だと1時間ぐらいかかりました。のんびり待ちましょう。

DLが完了したら、解凍します。

mkdir -p ~/ros2_install
cd ~/ros2_install
tar xf ~/Downloads/ros2-ardent-package-osx-fastrtps-x86_64.tar.bz2

ROS2の環境設定

以下のコマンドを実行します。行末の zshの部分は、使っているターミナルソフトに合わせて変更してください。僕はzshなので。
. ~/ros2_install/ros2-osx/setup.zsh


以上でインストールは完了!

サンプルを動かしてみる

端末を立ち上げて、以下のコマンドを実行します。

 . ~/ros2_install/ros2-osx/setup.zsh
ros2 run demo_nodes_cpp talker

次に、別の端末で以下のコマンドを実行します。

 . ~/ros2_install/ros2-osx/setup.zsh
ros2 run demo_nodes_cpp listener

f:id:tilt_silvie:20171223113518p:plain

動いた〜!


2017/12/23 追記 公式のInstallationが修正されたので、記事も修正しました。
Mac OS Binary Installation fail caused by version difference of tinyxml2 · Issue #440 · ros2/ros2 · GitHub

Pythonのbytesの生成に関して

勘違いしていたので備忘録。

bytes(255)

b'¥xff'

とはならず、

b'¥x00¥x00¥x00 ... ¥x00'

と、255個の¥x00で満たされたbytesになる。

a = 255
a.to_bytes(1, "big")

とすれば

b'¥xff'

が得られる。

参考

python3ならintとbytesの変換が楽勝になる - BlankTar

4. 組み込み型 — Python 3.6.1 ドキュメント

Python pandasのメモ

Python pandasが難しいので、メモ。

データ構造

Series : 1次元のデータ
DataFrame : 2次元のデータ

時間系

Time Series / Date functionality — pandas 0.20.3 documentation

TimestampとPeriodの2種類がある。 Timestampは、ある時点を表す。 Periodは、ある期間を表す。

TimestampをDataFrameのIndexとして取ると、DateTimeIndexとなる。 時系列データはこれで表すと良い。 DateTimeIndexで表現されるデータを処理するための関数群が用意されている

リサンプリング

pandas.DataFrame.resample()を使う。 アップサンプリング・ダウンサンプリングともに可能。

リサンプリング時のデータの補完/集計方法はpandas.Resamplerのメソッドとして定義されている

アップサンプリングの例(線形補間)

import pandas as pd

time_index = pd.date_range("2017-10-01 10:00:00", periods=3, freq="S")
print(time_index)

df = pd.DataFrame([0, 100, 300], index=time_index)

print("-----------------------")
print("Before resampling")
print(df)

df_resampled = df.resample("500ms").interpolate("linear")

print("-----------------------")
print("After resampling")
print(df_resampled)
DatetimeIndex(['2017-10-01 10:00:00', '2017-10-01 10:00:01',
               '2017-10-01 10:00:02'],
              dtype='datetime64[ns]', freq='S')
-----------------------
Before resampling
                       0
2017-10-01 10:00:00    0
2017-10-01 10:00:01  100
2017-10-01 10:00:02  300
-----------------------
After resampling
                             0
2017-10-01 10:00:00.000    0.0
2017-10-01 10:00:00.500   50.0
2017-10-01 10:00:01.000  100.0
2017-10-01 10:00:01.500  200.0
2017-10-01 10:00:02.000  300.0

リサンプリングの注意点

サンプリング周期によっては、元データと同じ時間がリサンプリング後に現れない場合がある。(1秒間隔のデータを300ms周期でリサンプリングした場合など )
その場合、リサンプリング後に現れなかった時間のデータは補完されない。

import pandas as pd
import matplotlib.pyplot as plt

time_index = pd.date_range("2017-10-01 10:00:00", periods=5, freq="S")
print(time_index)

df = pd.DataFrame([0, 100, 300, 50, 0 ], index=time_index)

df_500ms_resample = df.resample("500ms").interpolate("time")
print("-----------------------")
print("500ms resampling")
print(df_500ms_resample)


df_300ms_resample = df.resample("300ms").interpolate("time")
print("-----------------------")
print("300ms resampling")
print(df_300ms_resample)

plt.clf()
df_500ms_resample.plot()
df_300ms_resample.plot()
plt.show()
DatetimeIndex(['2017-10-01 10:00:00', '2017-10-01 10:00:01',
               '2017-10-01 10:00:02', '2017-10-01 10:00:03',
               '2017-10-01 10:00:04'],
              dtype='datetime64[ns]', freq='S')
-----------------------
500ms resampling
                             0
2017-10-01 10:00:00.000    0.0
2017-10-01 10:00:00.500   50.0
2017-10-01 10:00:01.000  100.0
2017-10-01 10:00:01.500  200.0
2017-10-01 10:00:02.000  300.0
2017-10-01 10:00:02.500  175.0
2017-10-01 10:00:03.000   50.0
2017-10-01 10:00:03.500   25.0
2017-10-01 10:00:04.000    0.0
-----------------------
300ms resampling
                            0
2017-10-01 10:00:00.000   0.0
2017-10-01 10:00:00.300   5.0
2017-10-01 10:00:00.600  10.0
2017-10-01 10:00:00.900  15.0
2017-10-01 10:00:01.200  20.0
2017-10-01 10:00:01.500  25.0
2017-10-01 10:00:01.800  30.0
2017-10-01 10:00:02.100  35.0
2017-10-01 10:00:02.400  40.0
2017-10-01 10:00:02.700  45.0
2017-10-01 10:00:03.000  50.0
2017-10-01 10:00:03.300  50.0
2017-10-01 10:00:03.600  50.0
2017-10-01 10:00:03.900  50.0



<matplotlib.figure.Figure at 0x11268e7f0>

500msサンプリング時

f:id:tilt_silvie:20171001165109p:plain

300msサンプリング時

f:id:tilt_silvie:20171001165117p:plain

これに対する今のところの対策は、一回十分に短いサンプリング周期でアップサンプリングして、その後所望のサンプリング周期にダウンサンプリングするという手法でなんとかなる。

Trelloのカード移動をSlackに通知してくれるbotを作った。

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(1) - うごくものづくりのために

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(2) - うごくものづくりのために

上記の記事で作成したシステムを整理して、botとしてまとめてGithubで公開しました。

github.com

できること

Trelloの指定のボードで、ToDoリストからDoingリストへ、またはDoingリストからDoneリストへのカードの移動があったときに、Slackに任意のメッセージをPostする

できないこと

上記以外のこと

インストール方法、使い方等

Githubのreadmeに書いてあるので、読んでください。 英語がつらいという人はTwitter(@silvie_c)に教えてください… 日本語のreadmeを作るかもしれません。

機能追加はある?

あるかもしれませんが、あまり期待しないで待っていてください。

Python3 + Flask + py-trello + slackerでTrelloの更新情報をslackに流す(2)

前回 の記事で、py-trelloでwebhookの登録をし、Flaskでサーバを立ててTrelloの通知を受け取ることができました。

今回は、拾ってきたTrelloの通知情報をslackに投げるところを作りたいと思います。

ゴール

Trelloのウェルカムボードのカードの移動を、Flaskで作ったサーバにWebhookで通知させる。 サーバに通知がきたら、slackにPostする。

Trelloのwebhookから必要な情報だけを抜き出す

カードの移動に関する通知だけを抜き出す

Trelloのwebhookは、登録したボードに関する更新情報をすべて通知します。 今回必要なのはカードの移動だけなので、通知のうちカードの移動に関するものだけを抽出します。

通知がTrelloのどのアクションと紐付いているかは、webhookで届くデータの以下に格納されています。

action -> display -> translationKey

カードの移動に関する通知の場合は、このフィールドが action_move_card_from_list_to_list になります。

詳細な情報を抜き出す

今回は、以下の4つのデータを取ってくることにします。

  • 移動されたカード名
  • 移動前のリスト名
  • 移動後のリスト名
  • カードを移動させた人の名前

カード移動に関する通知を受け取ると、上の4つの情報を表示するように webhook_server.pywebhook()を変更しました。

webhook_server.webhook()

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        action_type = request.json["action"]["display"]["translationKey"]

        if action_type == "action_move_card_from_list_to_list":
            card_name = request.json["action"]["data"]["card"]["name"]
            before_list = request.json["action"]["data"]["listBefore"]["name"]
            after_list = request.json["action"]["data"]["listAfter"]["name"]
            user_name = request.json["action"]["memberCreator"]["username"]
    
            message = user_name + "さんが「" +card_name + "」を「" + before_list + "」から「" + after_list + "」へ移動しました!"
            print(message)

        return '', 200
    else:
        abort(400)

動かすとこんな感じ。

f:id:tilt_silvie:20170922160809p:plain

いい感じですね。

slackerを用いてslackに投稿する

Slack API Tokenを取得する

以下のを参考に、SlackBotアカウントの作成を行います。

PythonでSlackbotを作る(1) – ビットログ

Slackbotのアカウントができたら、APIトークンを secret_token.pyに追加しておきます。

slack_token = "#Your slack token HERE#"

trello_api = "#Your trello API HERE#"
trello_token = "#Your trello token HERE#"

実装

slackerライブラリを用いてSlackに投稿する仕組みは、以下の記事を参考にします。

PythonでSlackbotを作る(3) – ビットログ

先程実装したwebhook()の中で表示したメッセージをそのままslackにPOSTしてみます。

webhook_server.py

rom flask import Flask, request, abort
from slacker import Slacker
import secret_tokens

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        action_type = request.json["action"]["display"]["translationKey"]

        if action_type == "action_move_card_from_list_to_list":
            card_name = request.json["action"]["data"]["card"]["name"]
            before_list = request.json["action"]["data"]["listBefore"]["name"]
            after_list = request.json["action"]["data"]["listAfter"]["name"]
            user_name = request.json["action"]["memberCreator"]["username"]

            message = user_name + "さんが「" +card_name + "」を「" + before_list + "」から「" + after_list + "」へ移動しました!"

            # print(message)
            slack.chat.post_message("sandbox", message, as_user=True)

        return '', 200
    else:
        abort(400)

@app.route("/webhook", methods=["HEAD"])
def webhook_creation():
    if request.method == "HEAD":
        return "", 200
    else:
        abort(400)


if __name__ == '__main__':
    slack = Slacker(secret_tokens.slack_token)
    app.run(port=5000, host="0.0.0.0")

結果

f:id:tilt_silvie:20170922185149p:plain

Trelloでカードを動かした情報を無事Slackに投稿できました!

ゴール達成!

参考

PythonのslackbotライブラリでSlackボットを作る - Qiita

PythonでSlackbotを作る(1) – ビットログ

PythonでSlackbotを作る(2) – ビットログ

PythonでSlackbotを作る(3) – ビットログ