chainerでモデルを入れ子にしたら重みが更新されなかった話

概要

chainerのmodel(Chainクラス)を入れ子にして使っていたら重みが更新されなかった.
Chainクラスで重みの更新がされるのは self.init_scope()内に書いている linkオブジェクトだけだったことが判明し,
with self.init_scope():以下に書くとちゃんと更新された.

状況

version

chainer==3.0.0

やりたかったこと

あるmodelAlayerNを追加して,新たに modelBを作成したかった.

だめなコード

計算グラフを出力すると,ちゃんとmodelA -> layerN という風に接続されていたので,これでうまく接続されているものだと思っていた.
が,実際に学習中に都度重みを出力してみると,modelA内の重み(l1, l2, l3の重み)が全く更新されていないことがわかった.

# example/train_mnist.pyから拝借
class modelA(chainer.Chain):
    def __init__(self, n_units, n_out):
        super(modelA, self).__init__()
        with self.init_scope():
            self.l1 = L.Linear(None, n_units)  # n_in -> n_units
            self.l2 = L.Linear(None, n_units)  # n_units -> n_units
            self.l3 = L.Linear(None, n_out)  # n_units -> n_out

    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

class modelB(chainer.Chain):
    def __init__(self, n_out, modelA):
        super(modelB, self).__init__()
        self.modelA = modelA
        with self.init_scope():
            self.layerN = L.Linear(None, n_out)
    
    def __call__(self, x):
        h1 = F.relu(self.modelA(x))
        h2 = self.layerN(h1)
        return h2

よいコード

まあちゃんとドキュメント見ればそれっぽいことは書いてあるんだが,まったく気づかなかった..
init_scope内に書くと,context managerとやらに登録されるらしい.
chainer.Chain — Chainer 3.0.0 documentation

# example/train_mnist.pyから拝借
class modelA(chainer.Chain):
    def __init__(self, n_units, n_out):
        super(modelA, self).__init__()
        with self.init_scope():
            self.l1 = L.Linear(None, n_units)  # n_in -> n_units
            self.l2 = L.Linear(None, n_units)  # n_units -> n_units
            self.l3 = L.Linear(None, n_out)  # n_units -> n_out

    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

class modelB(chainer.Chain):
    def __init__(self, n_out, modelA):
        super(modelB, self).__init__()
        # self.modelA = modelA
        with self.init_scope():
            self.modelA = modelA # ここに書くのが正解
            self.layerN = L.Linear(None, n_out)
    
    def __call__(self, x):
        h1 = F.relu(self.modelA(x))
        h2 = self.layerN(h1)
        return h2

まとめ

重み更新したい linkオブジェクトは init_scope内に書きましょう.
逆に,fine-tuningとかで重みを更新したくない場合は, init_scope内に書かなければ更新されないようなので,便利だなーと思った.

ドキュメントはちゃんと読みましょう.

機械学習など時間のかかる処理をするときに便利なBotを作成した

3行

  • 機械学習など時間のかかる処理の終了を待つのが面倒だった
  • 処理が終わればSlackに通知してくれるBotを作成した
  • 皆さん使ってください

作ったもの

github.com

README

なんだこれ

お知らせ蜂bot
slackに任意のコマンドの終了を可愛くお知らせしてくれるので通知の度に幸せになれる
f:id:johshisha:20171025053947p:plain

イメージ図

Successのときは緑,Errorのときは赤で,わかりやすく表示してくれる親切さ. 機械学習等の実行に時間のかかるプログラムを実行するときに利用するつもりで作成した

セットアップ

$ git clone https://github.com/johshisha/bee
$ cd bee
$ chmod 777 bee.sh
$ echo "alias bee=\"`pwd`/bee.sh\"" >> ~/.zshrc
$ source ~/.zshrc

bee.sh内の以下の設定を自分の好きなように変える

WEBHOOKURL="WebhockのURL入れてね"
#slack 送信チャンネル
CHANNEL=${CHANNEL:-"#general"}
#メッセージ
WEBMESSAGE="Command: ${COMMAND}\nStart time: ${start}\nEnd time: ${end}"
#メンションするユーザ
MENTION_USER="@channel"

使い方

$ bee $COMMAND

COMMANDは任意のコマンド

e.g.,

$ bee python train.py --epoch 50

研究で使うGPU環境をサクッと使えるDocker Imageを作った

研究を進めるにあたって便利だと感じたのでDocker環境下で実験をすることにした.
その際に作成した,Docker環境を公開します.

3行

  • DeepLearningに必要なGPUの環境を構築済みのDocker Imageを作成した
  • docker pullしてくるだけでGPU環境を作れる(nvidia-dockerさえあれば)
  • 研究の引き継ぎ&公開をうまくやろう!

作ったもの

github.com

メリット

Dockerを利用して研究を進めるメリットは以下の2点
- ゼロからGPU環境を作るのが楽になる
- 研究成果の引き継ぎ&公開が簡単

ゼロからGPU環境を作るのが楽になる

私は初めてGPU環境を構築したときに,かなり苦戦して,数日戦ったことがある.
また,なぜか起動しなくなったことがあり,再インストールする機会が何度かあり,毎度苦戦していた.(そのおかげで今はなんとかなるようにはなったが...)

Dockerを利用することで,いくつかのコマンドを叩くだけで,簡単にGPU環境が構築できるので,もう辛い思いはしなくていい!

研究成果の引き継ぎ&公開が簡単

研究を引き継ぐ際に,PCごと引き継げは簡単なのだが,実際はそうはいかず,スクリプトだけ引き継ぐことが多いと思う.
その際に問題になるのが,環境の問題で実行できないことである.
(例えばMecabopencvGPU環境のセットアップができていないなど)

そこで,Dockerを使うことで,環境の差異による実行できない問題は解決され,引き継ぎが楽になる!
さらに,研究成果を公開する際に,最近はgithub repositoryも一緒に論文に載せることが多いと思うが,Dockerfileを添えてあげるだけで,再現実験の環境が一発で整うので,再現実験のハードルが下がり,citation数の増加にも繋がる!!というお得しかない状況が発生する(かもしれない).

ということで

研究はDocker環境下でやることが個人的におすすめなので,ぜひ今回作成したツールを使って,快適な研究生活を送っていただけると幸いです!!
まだ,作って間もないので,不具合等あればご一報いただけると幸いです.

使い方

Dockerfileを作って,自分の環境用にbuildするとGPU環境が出来上がる.

例:サンプルのDockerfileを用意した

$ wget https://raw.githubusercontent.com/johshisha/docker_for_research/master/sample/Dockerfile # Dockerfileのサンプルを取ってくる
$ touch requirements.txt # 必要なら編集して自分の必要なライブラリを書く
$ docker build -t sample/sample . # 自分用の名前をつけてbuildする
$ nvidia-docker run --rm -it -p 8888:8888 -v "$PWD":/home/docker/work sample/sample jupyter-notebook --ip=0.0.0.0 # docker内のjupyter-notebookを起動
# `http://0.0.0.0:8888/?token=~~~`にアクセス

ちなみにDocker Imageに以下のライブラリはデフォルトでインストールしてあります.

cupy
pandas
jupyter
matplotlib
keras
tensorflow-gpu
chainer
sklearn

使う前の準備

使う際には,以下のインストールが必要です.

インストール方法

(動作の検証はubuntu16.04でしましたが,クリーンインストールはしていないので,多少違いがあるかもしれないです..)

github.com

  • dockerのインストール
    dockerのインストールは簡単です
$ sudo su
root# curl -fsSL https://get.docker.com/ | sh
root# gpasswd -a [username] docker
root# exit
-- logout user
$ docker run hello-world # 動作確認
  • nvidia-dockerのインストール
    これは地味に詰まりました(環境によって少し違うかもしれません)
$ wget -P ./tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb
$ sudo dpkg -i ./tmp/nvidia-docker*.deb && rm -f ./tmp/nvidia-docker*.deb
$ nvidia-docker run --rm nvidia/cuda:8.0 nvidia-smi # 動作確認

↑基本はこれだけ

でも私の環境ではいろいろとエラーがでました

  • Error1: libcuda.soのファイルが既にある
$ nvidia-docker run --rm nvidia/cuda:8.0 nvidia-smi
docker: Error response from daemon: create nvidia_driver_375.82: VolumeDriver.Create: internal error, check logs for details.
See 'docker run --help’.

$ systemctl status nvidia-docker
…
Error: link /usr/local/cuda-8.0/targets/x86_64-linux/lib/stubs/libcuda.so /var/lib/nvidia-docker/volumes/nvidia_driver/375.82/lib64/libcuda.so: file exists
$ sudo mv /usr/local/cuda-8.0/lib64/stubs/libcuda.so /usr/local/cuda-8.0/lib64/stubs/libcuda.so.backup

$ nvidia-docker run --rm nvidia/cuda:8.0 nvidia-smi
 Tue Oct 17 11:44:46 2017
 +-----------------------------------------------------------------------------+
 | NVIDIA-SMI 375.82                 Driver Version: 375.82                    |
 |-------------------------------+----------------------+———————————+
 …
  • Error2: nvidia-modprobeがインストールされてないんじゃない?
$ nvidia-docker run --rm nvidia/cuda:8.1 nvidia-smi
...
Error: Could not load UVM kernel module. Is nvidia-modprobe installed?
$ sudo apt-get install nvidia-modprobe
$ nvidia-docker run --rm nvidia/cuda:8.0 nvidia-smi
 Tue Oct 17 11:44:46 2017
 +-----------------------------------------------------------------------------+
 | NVIDIA-SMI 375.82                 Driver Version: 375.82                    |
 |-------------------------------+----------------------+———————————+
 …

まとめ

研究はDocker環境下でやるとみんな幸せになれる!(はず)

最低限のjavascript開発環境を作って公開した

三行

  • yarn, gulp, babel, webpack, React, ESlintを使って,最低限のjavascript開発環境を作った
  • 最低限 = ES6で書いたものがコンパイルされてブラウザで見れる状態
  • 最適な環境なのかはわからないが,とりあえず開発できる

作ったもの

github.com

使い方

$ git clone https://github.com/johshisha/js_development
$ cd js_development
$ yarn
$ yarn start
$ open http://localhost:8080/webpack-dev-server/index.html

buildの仕方

$ yarn start build

参考文献

基本的にこれを参考にさせてもらった
これの8章までに webpack-dev-server を加えた形 qiita.com

webpack-dev-server dackdive.hateblo.jp 補助:
Webpack-stream WITH Gulp WITH webpack-dev-server?? · Issue #121 · shama/webpack-stream · GitHub usage with gulp

HMR

blog.mismithportfolio.com

詰まった点

Hot Module Replacement (HMR)がうまく動作しない

webpack-dev-serverでHMRをしようとおもい, hot: trueにしたが,うまく動作しなかった
HMR: リロードせずとも,変更のあった部分だけ更新してくれる機能

  • 原因
    設定が正しく出来ていなかった

  • 解決法
    github.com

それでもHot Module Replacement (HMR)がうまく動作しない

f:id:johshisha:20171013045720p:plain こういう変更をリロード無しで更新してくれることを期待していた

Before

const App = () => (
  <div>
    Hello world!
  </div>
);

After

const App = () => (
  <div>
    Hello world!!!!!!!!!!!!!!!!!
  </div>
);
  • 原因
    HMRを万能なものと思いすぎていた
    すべての変更に対応しているわけではなく,HMRに対応している変更としていない変更がある

  • 解決法
    ファイルを分割してReact ComponentにすることでComponent内の変更はHMRに対応することができた
    このあたりの仕組みまでは理解できていないので,理解する必要がある.
    ↓こんな感じに分割した https://github.com/johshisha/js_development/blob/master/src/client/Message.jsx

class Message extends Component {
  render() {
    return (
      <div>
        Hello world!
      </div>
    );
  }
}

コンポーネントとか期待してたのに, undefinedが返ってきてるじゃん!というエラー

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.
  • 原因
    importの仕方を間違っていた

  • 解決法

- import { Message } from './Message';
+ import Message from './Message';

RaspberryPiにDocker環境を整える[docker+docker-compose]

参考資料 blog.hisurga.com

この資料にそってやるのがいいかと

一応コマンドだけ残しておく(同じことしている)

dockerのインストール

$ curl -sSL https://get.docker.com | sh
$ sudo usermod -aG docker [username] # root以外でもdocker使えるようにする

一旦ログアウトする

動作確認

$ docker run -d -p 80:80 hypriot/rpi-busybox-httpd

clientから,ブラウザでraspberry piのIPにアクセスすると画像が表示されればOK

docker-composeのインストール

なぜかエラーが出た.. が,docker rmiしてからやり直すといけた

$ git clone https://github.com/docker/compose.git
$ cd compose
$ sed -i -e 's/^FROM debian\:/FROM armhf\/debian:/' Dockerfile.armhf
$ sed -i -e 's/x86_64/armel/g' Dockerfile.armhf
$ docker build -t docker-compose:armhf -f Dockerfile.armhf .   # めっちゃ時間かかる
$ docker run --rm --entrypoint="script/build/linux-entrypoint" -v $(pwd)/dist:/code/dist -v $(pwd)/.git:/code/.git "docker-compose:armhf"

$ ls -l dist/
$ sudo cp dist/docker-compose-Linux-armv7l /usr/local/bin/docker-compose
$ sudo chown root:root /usr/local/bin/docker-compose
$ sudo chmod 0755 /usr/local/bin/docker-compose

動作確認

$ docker-compose version

Raspberry Pi 3インストールから初期設定まで

インストール

今回は,2017-04-10-raspbian-jessie-lite.imgを使用しました. 各自公式からダウンロードしてきてください.

www.raspberrypi.org

該当SDの確認

$ diskutil list
...
/dev/disk4 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.0 GB    disk4
   1:                 DOS_FAT_32 RASPBIAN                31.0 GB    disk4s1

該当のSDを見つけます.

SDのフォーマット

$ diskutil eraseDisk MS-DOS RASPBIAN /dev/disk4
...
Finished erase on disk4

RASPBIANの部分はフォーマット後の名前なのでお好みで設定してください

SDへ書き込み

以下のコマンドを実行し,SDに書き込む.(10分ほど?適当に放置して待ちましょう)

$ diskutil unmountDisk /dev/disk4
$ sudo dd if=path/to/file/2017-04-10-raspbian-jessie-lite.img of=/dev/disk4 bs=1m

以上を行うと準備は完了なので,RaspberryPiに挿入して電源を入れましょう 最初はWifi等の設定を行うまでネットに接続できないので,LANケーブルを挿しました.

初期設定

初期のパスワード

user name: pi
password: raspberry

rootのパスワード設定

sudo passwd root

以下からはrootで行う

$ su -
password

Update

とりあえず最新にします

$ apt-get update && apt-get upgrade
$ apt-get install vim # 後々の設定ファイル編集のために入れておく

Wifiの接続

SSIDとパスワードを設定ファイルに保存しておきます. このファイルはパスワードが記載されているので,外部に公開しないようにしましょう.

$ export PASSWORD=password
$ export SSID=wifi-ssid
$ wpa_passphrase $SSID $PASSWORD >> /etc/wpa_supplicant/wpa_supplicant.conf

Time Zoneの設定

とりあえず日本時間に変更します.

$ raspi-config

「4 Localisation Options」-> 「I2 Change Timezone」->「Asia」-> 「Tokyo」

SSHの有効化

デフォルトではSSHで接続できないので,設定をONにします.

$ raspi-config

「5 Interfacing Options」-> 「P2 SSH」->「Yes」

接続先IPは以下で確認

$ ip route
default via 192.168.1.1 dev wlan0  metric 303
192.168.1.0/24 dev wlan0  proto kernel  scope link  src 192.168.1.42  metric 303

今回の場合は192.168.1.42となります.

$ ssh pi@192.168.1.42

で接続できるはずです

おまけの設定

RaspberryのIPを固定する

上記の設定で,SSHすることができましたが,このままだとRaspberry piを再起動した際に,IPが変わってしまいSSHできなくなる可能性があります. そのたびにディスプレイを挿して,IPを確認して...という作業はめんどうなので,IPを固定化します.

上記の例では,サブネットマスクが,192.168.1.0/24なので,左から24bit分の192.168.1までがネットワークアドレスであり,これ以降の8bitは好きに使用して良いことがわかります.

好きに設定はできますが,特にこだわりがなければ,とりあえず今のIP( 192.168.1.42 )で固定すればよいかと思います. 以下のファイルを編集し,設定を追記していきます.

$ sudo vim /etc/dhcpcd.conf

以下の設定を追記

interface wlan0
static ip_address=192.168.1.42
static routers=192.168.1.1
static domain_name_servers=192.168.1.1

これで固定化できました. 再起動してもIPが変わらないことを確認してください.

SSHのポート変更

22番のままだと悪意のあるユーザに推測されやすいためセキュリティの面で不安があります. 変更する際は,/etc/ssh/sshd_configのPort 22を変更するとSSHの接続Portを変更できます.

ユーザの追加と削除

デフォルトのpiユーザ名はすべてのRaspberry piで共通なので,悪意のあるユーザに推測されやすいです.(パスワードも共通なので,変更する必要があります) 外部からアクセスできるようにする際は,ユーザを新たに作り直して,攻撃されにくくしておくとよいです.

ユーザ(hoge)の追加

$ sudo adduser hoge
$ sudo gpasswd -a hoge sudo

piユーザの削除

$ sudo userdel pi

公開鍵認証

パスワードでなく,公開鍵認証でログインできるようにします. ローカルのマシンで.以下のように,鍵を生成しRaspberry piに送信します.

$ ssh-keygen -t rsa
$ scp -P [port_number] ~/.ssh/id_rsa.pub [username]@[ip]:
$ ssh -P [port_number]  [username]@[ip]

raspberrypi側で,送られてきた鍵を登録します.

$ mkdir .ssh
$ mv ~/id_rsa.pub ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
$ sudo service ssh restart

ローカルのマシンの別ターミナルで確認します.

$ ssh -p [port_number] -i ~/.ssh/id_rsa [username]@[ip]

うまくsshできることが確認できたら成功です.

公開鍵認証でログインできるようになったので,パスワードでログインできないようにして,セキュリティを高めます. raspberry側で,設定ファイルを以下のように変更します.

$ sudo vim /etc/ssh/sshd_config

- の部分を + になるように書き換えます.

- # PasswordAuthentication no
+ PasswordAuthentication no

ssh configの設定

SSHする際に,ipなど,入力するコマンドが長くて面倒なのでSSHコマンドを簡略化します.

ローカルのマシンで,~/.ssh/configに以下を追記.

Host raspberry
    Hostname [ip]
    Port [port_number]
    User [username]
    IdentityFile ~/.ssh/id_rsa

これを記載することで,sshを簡略化することができます.

$ ssh -p [port_number] -i ~/.ssh/id_rsa [username]@[ip] # 通常はこれ
$ ssh raspberry # configを設定することで簡略化できる

firewall

セキュリティを高めるために,無駄なportにはアクセスできないようにしておきます.

$ sudo apt-get install ufw -y
$ sudo ufw default deny
$ sudo ufw allow 80/tcp
$ sudo ufw allow [ssh port_number] # SSH用のポートは開けておく
$ sudo ufw enable

docker関連のerrorと対処法集

dockerを勉強し始めて,詰まった部分などを書き溜めていく.
同じエラーで詰まった人の助けになれば幸いです.
エラーに遭遇する度に追加していく予定

tips

  • 何かしらのエラーでうまく動かないときは,docker logs [container id]でログを見ることができる.

  • unicornのエラーは標準出力には出力されなかったので,直接エラーログファイルを見に行く必要があった.
    例えば,以下のようなエラーの場合は,unicorn系のエラーだったのでrun bashで内部に入って,同様のコマンドを実行してエラーログファイルを参照した.

[error] 5#5: *11 connect() to unix:/share/unicorn.sock failed (111: Connection refused) while connecting to upstream, client: 192.168.99.1, server: , request: "GET / HTTP/1.1", upstream: "http://unix:/share/unicorn.sock:/", host: "192.168.99.104"
  • eval $(docker-machine env default)
    dockerの接続先を指定するためのコマンド.(この場合は指定先をdefaultにするコマンド)
    ターミナルが変われば,この接続先は再指定する必要がある.
    逆に,ターミナルごとに接続先を指定できる.
    docker-machine lsでACTIVEにマークがついているかちょくちょく確認したほうがよい.

docker-machine起動関連のエラー

  • error
$ docker-machine env default
Error checking TLS connection: Host is not running
  • 解決法
    docker-machineが起動していなかった.
$ docker-machine start default

  • error
$ docker-machine env default
Error checking TLS connection: Error checking and/or regenerating the certs: There was an error validating certificates for host "192.168.99.101:2376": x509: certificate is valid for 192.168.99.100, not 192.168.99.101
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which might stop running containers.
  • 解決法
    前起動していたときとIPが変わっているために発生するエラー.
    エラー文に書いているが,
$ docker-machine regenerate-certs default
Regenerate TLS machine certs?  Warning: this is irreversible. (y/n): y
Regenerating TLS certificates
Waiting for SSH to be available...
Detecting the provisioner...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...

これで起動する.


  • error
$ docker-machine regenerate-certs default
Regenerate TLS machine certs?  Warning: this is irreversible. (y/n): y
Regenerating TLS certificates
Waiting for SSH to be available...
Too many retries waiting for SSH to be available.  Last error: Maximum number of retries (60) exceeded
  • 解決法
    docker-machineを起動していない.
$  docker-machine start default && docker-machine regenerate-certs default