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
$ cp -i Dockerfile Dockerfile.armhf
$ 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"

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

インストール

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

SDのフォーマット

SDを挿入してから,DiskUtilityを開いて,該当SDを初期化します. 右クリックして,「削除」を選択し,名前をつけて削除すると完了.

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に書き込む.(10分ほど?適当に放置して待ちましょう)

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

以上を行うと準備は完了なので,RaspberryPiに挿入して電源を入れましょう

初期設定

初期のパスワード

user name: pi
password: raspberry

rootのパスワード設定

sudo passwd root

以下からはrootで行う

$ su -
password

Wifiの接続

$ 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の有効化

$ 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

で接続できるはずです

おまけ

SSHのポート変更

/etc/ssh/sshd_configのPort 22を変更する

ユーザの追加と削除

追加

$ sudo adduser hoge
$ sudo gpasswd -a hoge sudo

削除

$ sudo userdel 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/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

$ sudo apt-get install ufw -y
$ sudo ufw default deny
$ sudo ufw allow 80/tcp
$ sudo ufw allow [ssh port_number]
$ 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