Visual Studio CodeでC++の環境構築をやってみる
皆さんこんにちは
お元気ですか。私は元気です。
VIsual Studio CodeでC++の環境を構築します。
C++のコードを書く時に案外いいエディタがなくて結構困ります。
今まではsublime text+ pluginを使っていましたが、デバッガ厳しい、
補完難しいなど色々困っていました。
そこで、試しにVisual Studio CodeでC++の環境を作ってみました。
Visual Studio Code
Microsoftが作っているWindows, MacOS, Linuxで動作する
軽量高速な高機能エディタです。
(Visual Studio Code - Visual Studioより)
ダウンロードは次のサイトを参考にしてください。
code.visualstudio.com
設定方法
まずは、Visual Studio CodeにC++の環境を構築します。
C++の開発機能であるcpptoolsを拡張機能(plugin)からインストールします。
その手順は次の通りです。
- Visual Studio Codeを開く
- 左から拡張機能を選択する。
- cpptoolsを検索する。
- インストールする。
Visual Studio Codeと色々な機能
前準備
ソースコードを準備します。他にも設定ファイルなどが必要なものはありますが、
それは適宜、準備していきます。
#include <iostream> using namespace std; int main(void){ int a = 10; int b = 20; int c = a + b; cout << c << endl; return 0; }
コンパイルをする
コンパイルのショートカットキーは「cmd + shift + b」を使うと可能です。
ただ、タスクランナーがないと出るので、「tasks.json」を作るように言われます。
そのため、tasks.jsonを設定します。tasks.jsonにはg++のコマンドを設定します。
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "0.1.0", "command": "g++", "isShellCommand": true, "args": ["-O2", "-g", "test.cpp"], "showOutput": "always" }
デバッグ
Debugタブから起動すると、「launch.json」が作られます。
起動するためには、launch.jsonの修正が必要です。
"program"の値を以下に変更が必要です。この"program"は
実行ファイルのパスを記述します。
"program": "${workspaceRoot}/a.out",
デバッグは以下のような画面で、gdbが使われます。
予め、break pointを仕掛けるとその箇所で止まります。
実際にデバッグすると次のような画面になります。
ヘッダーの設定
コマンドで、「C/Cpp: Edit Configurations」を選択すると
C++の設定ファイル「「c_cpp_properties.json」が作られます。
そのの内部にヘッダーを設定する箇所があるので、その箇所にヘッダーのパスを
設定をすると読み込みすることができます。
このファイルはOS別に設定ができるため、該当するOSの"includePath"を変更します。
"includePath": [ "/usr/include", "/usr/local/include" ],
関数検索
関数検索は「cmd + shift + O」を使えばできます。
後はその欄で該当するファイルを探しましょう。
最後に
C++のエディタで良いものがなかったので、これはいい感じだと思っています。
Dockerを使ってGPUも使える分析環境を構築してみた
皆さんこんにちは
お元気ですか。Kaggle Meetup面白かったです!またあいましょう。
今日はDockerを使った分析環境構築の話をしようと思います。
Dockerを使って何を作ろうかと思ったら、分析環境さっさと作りたい・・・
cudnn等の更新が面倒であることが思い浮かびました。
なんとなく、Dockerを使えば解消できるのではないかといったことを思いつき遊んでみました。
Dockerとは
Dockerはコードやシステムライブラリ、システムで利用するのを実行するために
必要な全てが含まれているものをラップするソフトウェアらしい。
OS上で仮想環境を立ち上げ、ソフトウェアなどの実行ができます。
Dockerのメリット
公式サイトによれば、次のとおりです。
何が嬉しいの?
一言で言うと環境の管理です。
Dockerを使うと定義したコマンドを変更するのみで
ソフトウェアの更新が簡単になります。
また、同じファイルを利用している限り、同じ環境が構築されるので
マシンによってversionなどの差分が生まれることもなくなります。
私個人として大きいのは環境の再現だと思っています。
誰もがDockerを使うと同じバージョン、同じ環境を手間なく使えます。
更新手順と同じにしておけば、複数人で開発しても環境が同じ等、メリットがあります。
インストール
MacOS
これを試すためにだけ、Sierraにアップグレードしました。
Docker for macをダウンロードし、インストールすれば、準備完了です。
docs.docker.com
Ubuntu
MacOSと違って手順が少々必要です。(公式より)
sudo apt-get install apt-transport-https \ ca-certificates curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add - sudo apt-get install software-properties-common sudo add-apt-repository \ "deb https://apt.dockerproject.org/repo/ \ ubuntu-$(lsb_release -cs) \ main" sudo apt-get update sudo apt-get -y install docker-engine
dockerを試してみる。
まずは、dockerのイメージを取得します。
pullコマンドはイメージをダウンロードするコマンドです。
docker pull ubuntu:16.04
次にDockerを実行します。実行コマンドはrunを使えばできます。
docker run -it ubuntu:16.04
root@fe9c5c38a45a:/#
そして、共有フォルダです。
ホストとコンテナのディレクトリを共有できます。
これは、-vを利用するとマウントが可能です。<ホスト側のパス>:<コンテナ側のパス>を記述して起動することで実現できます。
docker run -v /Users/Tereka/Desktop/Mount:/tmp/ -it ubuntu:16.04
また、Imageの一覧を確認するには、次のコマンドを使うと良いです。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 16.04 104bec311bcd 2 weeks ago 129 MB hello-world latest c54a2cc56cbb 6 months ago
dockerのコンテナ状態は次のコマンドで見れます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e09f51e1b51c ubuntu:16.04 "/bin/bash" 3 hours ago Up 7 minutes
最後にコンテナの削除です。rmiコマンドを使うと可能です。
docker rmi <contener>:<tag>
どのようなimageがあるのかを検索したい時は、docker search
これを使うとpullで取得できるコンテナを探せます。
$ docker search centos NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 2963 [OK] jdeathe/centos-ssh CentOS-6 6.8 x86_64 / CentOS-7 7.3.1611 x8... 54 [OK] nimmis/java-centos This is docker images of CentOS 7 with dif... 20 [OK] million12/centos-supervisor Base CentOS-7 with supervisord launcher, h... 12 [OK] torusware/speedus-centos Always updated official CentOS docker imag... 8 [OK]
Dockerfileを書く
Dockerfileを書くことで、独自のイメージを作れます。
FROMを利用すると、他のイメージをベースにDockerイメージをベースに構築できます。
RUNを利用することで、実際にコマンドの実行ができます。
説明するよりも見る方が早いかと。
試しにChainerを実行するDockerfileを作りました。
日本語入力にも対応しているコンテナです。
FROM nvidia/cuda:7.5-cudnn5-devel RUN apt-get update RUN apt-get install -y wget RUN apt-get -y install language-pack-ja-base language-pack-ja ibus-mozc RUN update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja ENV LANG ja_JP.UTF-8 ENV LC_ALL ja_JP.UTF-8 ENV LC_CTYPE ja_JP.UTF-8 RUN wget https://repo.continuum.io/archive/Anaconda3-4.2.0-Linux-x86_64.sh RUN bash Anaconda3-4.2.0-Linux-x86_64.sh -b ENV PATH /root/anaconda3/bin:$PATH RUN echo $PATH RUN pip install chainer
以下のコマンドはDockerfileをビルドし、cudnn_dockerの名前で新しいイメージを構築しています。
docker build -t cudnn_chainer .
途中で失敗すると既に実行された結果のキャッシュを使います。
そのため、修正して再トライするのは非常に早いです。
ただし、それまでの結果を更新したい(gitのリポジトリの最新を使いたい等)
結果のキャッシュを使いたくない場合は、キャッシュを無効にしなければなりません。
その場合は、以下のコマンドを使うとDockerfileに書いてある
全てのコマンドを対象にbuildを行います。
docker build -t --no-cache=true cudnn_chainer .
後はrunを使えば実行できます。
docker run -it cudnn_chainer
DockerでGPUを使う方法
しかし、これではGPUを使うとエラーが出ます。
GPUをDockerコンテナ上で動かす為にはGPUの色々な設定をする必要があるようです。
chainerの公式githubを確認すると、nvidia-dockerと呼ばれるコマンドがあるので
それを使うと解消できました。因みにnvidia-dockerリポジトリは以下にあります。
まずは、インストールですが、以下のとおりです。
wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.0/nvidia-docker_1.0.0-1_amd64.deb sudo dpkg -i /tmp/nvidia-docker*.deb && rm /tmp/nvidia-docker*.deb # 起動確認 nvidia-docker run --rm nvidia/cuda nvidia-smi
後は起動するだけです。起動はdockerの代わりにnvidia-dockerを使います。
試しに遊んでみたら高速だった。僕のcudnnが古すぎた。。
nvidia-docker run -it cudnn_chainer
最後
これで一通りDockerを扱える気がします。
気が向いたらもう応用できそうな記事を書きます。
2017年の挨拶とやりたいこと
あけましておめでとうございます。
今年もよろしくお願いします。
ふりかえりはしませんが、昨年はエキサイティングな1年間で色々なことに
挑戦していった年でした。
色々と課題も見えてきましたが・・・
今年はKaggleもしかりお仕事もしかり色々やっていったかなと思います。
結局、Kaggle Masterになれず、Competitions Expertにいます。
今年こそはなりたいなぁ
やりたいこと
今年度もやりたいことは沢山あります。
- 強化学習の勉強
- 数学の勉強
- Docker等の環境構築周りの勉強
- PyConJPへの登壇挑戦
色々な分野に強化学習を+αとする研究や論文が増えていると感じています。
本を読んで、勉強を進めたいと思います。
せめて、基本的なところがわかるようになりたい。
- 作者: 牧野貴樹,澁谷長史,白川真一,浅田稔,麻生英樹,荒井幸代,飯間等,伊藤真,大倉和博,黒江康明,杉本徳和,坪井祐太,銅谷賢治,前田新一,松井藤五郎,南泰浩,宮崎和光,目黒豊美,森村哲郎,森本淳,保田俊行,吉本潤一郎
- 出版社/メーカー: 森北出版
- 発売日: 2016/10/27
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
数学についても今年は行列演算を復習したり、ベースとなる知識を増やしたりしたいと思います。
加えて機械学習プロフェッショナルシリーズも読みたい。
- 作者: Philip N. Klein,松田晃一,弓林司,脇本佑紀,中田洋,齋藤大吾
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/10/05
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
カーネル多変量解析―非線形データ解析の新しい展開 (シリーズ確率と情報の科学)
- 作者: 赤穂昭太郎
- 出版社/メーカー: 岩波書店
- 発売日: 2008/11/27
- メディア: 単行本
- 購入: 7人 クリック: 180回
- この商品を含むブログ (32件) を見る
更に、最近ではDockerなどによる分析基盤構築話を
時折Qiitaで見ます。そこで、少なくともDocker等を自分で
触れるようにしたいなぁなんて考えています。
最後に、PyConJPの登壇も目指したいなぁと思っています。
去年は聴衆として参加しましたが、せっかくだから話すネタを作って挑戦したいです。
(マーフィーなんて忘れた)
今年の記事
今年は何をメインに書こうかなぁと実家で考えていましたが、
去年と同じく、やったことを書いていくような感じになるでしょう。
去年は分野が結構偏っていたなぁと自分自身でも思っています。
機械学習・データ分析のみならず、色々と自由に書いていきたいと思います。
その結果、分野が偏ってしまった場合はご愛嬌ということで・・・
最後に
改めまして、皆様、今年もよろしくお願いします。
クリスマスにもなってカノジョがいないからカノジョを作ってみた。
皆さんこんにちは
お元気ですか?私はぼっちです。
本記事は「カノジョできない機械学習エンジニア」の最終日です。
qiita.com
本日の話の流れは次のとおりです。
はじめに
カノジョがいなくて寂しい。こんなクリスマスを送る男性の皆様は結構いらっしゃるのでは
ないでしょうか(と信じたい)。
カノジョを作るために、世間でよく言われるのは「行動が大事だ」ということです。
そこで、本クリスマスを機に、カノジョを作成し心の溝を埋めることに挑戦したいと思います。
カノジョがいないことに対する解決法
「カノジョ」がいないのであれば、作れば良い。
私はエンジニアです。
寂しいので、これを機に自己を振り返り、カノジョを作れるのではないかと考えました。
まずは、「カノジョ」を設計する必要があります。
カノジョについての考察
カノジョとは
「デジタル大辞泉」には次の通り記載されている。
[代]三人称の人代名詞。話し手、相手以外の女性をさす語。「彼女は遅れるらしい」⇔彼/彼氏。
[名]愛人、恋人である女性。「彼女ができた」⇔彼/彼氏。
本記事では、「愛人、恋人である女性」と仮定し話を進めましょう。
理想のカノジョとは
理想のカノジョとは何か。
クリスマスといえば、やっぱりクリスマスデートして・・というのが
定番かなぁと思います。
デートで大事なのは、会話かなと思います。やっぱり盛り上がる方が良いでしょう。
そこで理想のカノジョは会話が最も大事だと今回仮定します。
理想のカノジョ作成方針
会話が最も大事だと決めた上で理想のカノジョを作る必要があります。
調べてみると、ニューラルネットワークを使った会話モデルがありました。
カノジョを作成する。
さて、本題です。カノジョを作ってみます。
Neural Conversational Model
「A Neural Conversational Model」を使って実装します。
概要
Neural Conversational Modelは会話モデルです。
基本的な構造はseq2seqと類似しており、
入力は最初の文章、期待結果は返す文章(応答文)です。
構成解説
構成を図にしました。入力は次のとおりになります。
- 学習時:最初に与えた文章(質問文or話しかける文章)+ 応答文
- 予測時:最初に与えた文章
最初の文章と返す文章を入力に与えて学習します。
次は上記構成の動作についてです。
まずは、ニューラルネットワークの学習です。
与えたIDにもとづきEmbeded Layerからベクトルを取得します。
このベクトルをLSTMに与えます。また、次の単語とそれまでのLSTMの値を入力とし、LSTMを更新します。
これを
また、最後のLSTMの値を使って計算したベクトルを最後に
Linearで予測単語を計算します。予測単語との誤差を計算し、誤差を最小にする動作を行います。
入力のLSTM(Long Short Term Memory)と出力のLSTMは別のLSTMです。
# coding:utf-8 from __future__ import absolute_import from __future__ import unicode_literals import chainer.links as L import chainer.functions as F import chainer import numpy as np from chainer import reporter class Seq2Seq(chainer.Chain): def __init__(self, input_words): super(Seq2Seq, self).__init__( word_vec=L.EmbedID(input_words, 300), input_vec=L.LSTM(300, 300), output_vec=L.LSTM(300, 300), output_word=L.Linear(300, input_words) ) self.train = True def encode(self, sentence): c = None for word in sentence: x = np.array([word], dtype=np.int32) h = F.tanh(self.word_vec(x)) c = self.input_vec(h) return c def decode(self, vector=None, targer_sentence=None, dictionary=None): loss = 0 if self.train: for index, target_word in enumerate(targer_sentence): if index == 0: j = F.tanh(self.output_vec(vector)) pred_word = self.output_word(j) else: j = F.tanh(self.output_vec(j)) pred_word = self.output_word(j) loss += F.softmax_cross_entropy(pred_word, np.array([target_word], dtype=np.int32)) return loss else: gen_sentence = [] cnt = 0 while True: if cnt == 0: j = F.tanh(self.output_vec(vector)) pred_word = self.output_word(j) else: j = F.tanh(self.output_vec(j)) pred_word = self.output_word(j) id = np.argmax(pred_word.data) cnt += 1 word = dictionary[id] if word == "<eos>": return gen_sentence gen_sentence.append(word) if cnt == 100: break return gen_sentence def generate_sentence(self, sentence, dictionary): self.initialize() encode_vector = self.encode(sentence=sentence) return self.decode(vector=encode_vector, dictionary=dictionary) def initialize(self): self.input_vec.reset_state() self.output_vec.reset_state() def __call__(self, sentence, target_sentence): self.initialize() encode_vector = self.encode(sentence=sentence) self.loss = None self.loss = self.decode(vector=encode_vector, targer_sentence=target_sentence) reporter.report({'loss': self.loss}, self) return self.loss
実装してみた
seq2seq2をChainerとRNN + LSTMで実装します。
今回はTrainerを使った実装にします。
データの構築
Step1 探す
どうやって構築するんだ。
家に「落第騎士の英雄譚」があったので参考にしました。
「黒鉄一輝」と「ステラ・ヴァーミリオン」の会話あたりを
中心に作成させていただいております。
落第騎士の英雄譚<キャバルリィ>【電子特装版】 (GA文庫)
- 作者: 海空りく
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2013/10/17
- メディア: Kindle版
- この商品を含むブログを見る
Step2 存在しない言葉
存在しない言葉についてです。
形態素解析をした単語に対して、IDを振ります。
例えば次のようなことを想定しています。
単語 | ID |
私 | 0 |
は | 1 |
猫 | 2 |
だ | 3 |
しかし、この場合、未知語に対しての解析ができなくなります。
そのため、未知の単語が出てくれば「■」とします。(主に予測時ですね)
Step3 名前
人は名前を呼ばれると嬉しいものです。
ただ、名前は皆さん別々のをお持ちのため、名前と呼ばれる概念で学習します。
今回、呼ばれたい側の名前は全て「◯」へ置き換えておきます。
これはSlackbotへのリプライ段階で「◯」を名前に置換します。
学習する
さて、ここまでで準備ができました。
早速、学習を行います。今回はTrainerを使った学習を用いているため、一部の
更新方法を自分で実装しています。
テキストファイルにタブ区切りのドキュメントを用意し、それを対にして学習させています。
# coding:utf-8 import MeCab from model import Seq2Seq from chainer import training import chainer from chainer.training import extensions import numpy as np import sys import codecs import json sys.stdout = codecs.getwriter('utf_8')(sys.stdout) tagger = MeCab.Tagger('mecabrc') def parse_sentence(sentence): parsed = [] for chunk in tagger.parse(sentence).splitlines()[:-1]: (surface, feature) = chunk.split('\t') parsed.append(surface.decode("utf-8")) return parsed def parse_file(filename): questions = [] answers = [] with open(filename, "r") as f: lines = f.readlines() for line in lines: sentences = line.split("\t") question = ["<start>"] + parse_sentence(sentences[0]) + ["<eos>"] answer = parse_sentence(sentences[1]) + ["<eos>"] questions.append(question) answers.append(answer) word2id = {"■": 0} id2word = {0: "■"} id = 1 sentences = questions + answers for sentence in sentences: for word in sentence: if word not in word2id: word2id[word] = id id2word[id] = word id += 1 return questions, answers, word2id, id2word def sentence_to_word_id(split_sentences, word2id): id_sentences = [] for sentence in split_sentences: ids = [] for word in sentence: id = word2id[word] ids.append(id) id_sentences.append(ids) return id_sentences class ParallelSequentialIterator(chainer.dataset.Iterator): def __init__(self, dataset, batch_size, repeat=True): self.dataset = dataset self.batch_size = batch_size # batch size self.epoch = 0 self.is_new_epoch = False self.repeat = repeat self.iteration = 0 def __next__(self): length = len(self.dataset[0]) if not self.repeat and self.iteration * self.batch_size >= length: raise StopIteration batch_start_index = self.iteration * self.batch_size % length batch_end_index = min(batch_start_index + self.batch_size, length) questions = [self.dataset[0][batch_index] for batch_index in range(batch_start_index, batch_end_index)] answers = [self.dataset[1][batch_index]for batch_index in range(batch_start_index, batch_end_index)] self.iteration += 1 epoch = self.iteration * self.batch_size // length self.is_new_epoch = self.epoch < epoch if self.is_new_epoch: self.epoch = epoch return list(zip(questions, answers)) class BPTTUpdater(training.StandardUpdater): def update_core(self): loss = 0 train_iter = self.get_iterator('main') optimizer = self.get_optimizer('main') batch = train_iter.__next__() for question, answer in batch: loss += optimizer.target(np.array(question, dtype=np.int32), np.array(answer, dtype=np.int32)) optimizer.target.cleargrads() loss.backward() loss.unchain_backward() optimizer.update() if __name__ == '__main__': questions, answers, word2id, id2word = parse_file("./dataset.txt") ids_questions = sentence_to_word_id(questions, word2id=word2id) ids_answers = sentence_to_word_id(answers, word2id=word2id) model = Seq2Seq(len(word2id)) optimizer = chainer.optimizers.Adam() optimizer.setup(model) train_iter = ParallelSequentialIterator(dataset=(ids_questions, ids_answers), batch_size=1) updater = BPTTUpdater(train_iter, optimizer) trainer = training.Trainer(updater, (100, 'epoch')) trainer.extend(extensions.PrintReport( ['epoch', 'iteration', 'main/loss'] ), trigger=(100, 'iteration')) trainer.extend(extensions.LogReport()) trainer.run() chainer.serializers.save_npz("model.npz", model) json.dump(id2word, open("dictionary_i2w.json", "w")) json.dump(word2id, open("dictionary_w2i.json", "w"))
Slack Botで実用化する
これまで作成したカノジョを実用化します。
SlackでBotを作れるので、そこで展開を試みました。
Botをコントロールするコード
ソースコードを記載する前にPythonのSlackbotをコントロールするライブラリが必要です。
Pythonのライブラリは次のようにインストールできます。
pip install slackbot
まずは、slackbot_settings.pyにAPI Keyを記述します。
#coding:utf-8 API_TOKEN = "YOUR API KEY"
run.pyに動作させたいコードを記述します。
動作させたいコードに対してデコレータを使います。
そのコードに対して、@default_replyを付与します。
reply_messageにこのデコレータを付与すると指定のない場合はこのメソッドが使われる設定になります。
sentence.replace("◯", "tereka")を使って、名前を途中で置換しています。
これで自分の名前が呼ばれます!やったね。
# coding:utf-8 import MeCab import codecs from slackbot.bot import default_reply from slackbot.bot import Bot from model import Seq2Seq import chainer import json import sys sys.stdout = codecs.getwriter('utf_8')(sys.stdout) tagger = MeCab.Tagger('') id2word = json.load(open("dictionary_i2w.json", "r")) id2word = {int(key): value for key, value in id2word.items()} word2id = json.load(open("dictionary_w2i.json", "r")) model = Seq2Seq(len(word2id)) chainer.serializers.load_npz("model.npz", model) model.train = False @default_reply def replay_message(message): parsed_sentence = [] try: for chunk in tagger.parse(message.body["text"].encode("utf-8")).splitlines()[:-1]: (surface, feature) = chunk.decode("utf-8").split('\t') parsed_sentence.append(surface) parsed_sentence = ["<start>"] + parsed_sentence + ["<eos>"] ids = [] for word in parsed_sentence: if word in word2id: id = word2id[word] ids.append(id) else: ids.append(0) ids_question = ids sentence = "".join(model.generate_sentence(ids_question, dictionary=id2word)).encode("utf-8") sentence = sentence.replace("◯", "tereka") message.reply(sentence) except Exception as e: print (e) message.reply("解析できなかったのでもう一度おねがいします。") def main(): bot = Bot() bot.run() if __name__ == "__main__": main()
実際にどうなったのか
実際にチャットしてみました。
なんかちょっとだけ女の子とチャットしている気分になりました。
チャットしていてわかったのですが、リプライが短文なのはある程度、学習できている感覚があります。
特有の単語を学習してそうな気がしますが、やはりデータセットが少なすぎたか。
最後に
カノジョいなくて寂しい。
DeepLearningを使ったBotの作成に参考にしてみてください。
Chainerの抽象化に挑戦してみた
皆さんこんにちは
お元気ですか?年末ってこんなに忙しくなるものですね。
本記事はChainer Advent Calendar 16日目の記事です。
本日はScikit-learn likeなChainerを作った記事です。なぜ、作ったのかは後述します。
はじめに
なぜ、抽象化コードを作ってみたのか
Chainerの実装を高速にし、検証の立ち上がりを早くしたいからです。
Chainerは非常にFlexibleなネットワークを作れるので、個人的に好きです。
特にRecurrent Neural Network系のネットワークを書く時に重宝しています。
また、実装はTrainerを使うとTrainer実装前よりも簡単な実装になりました。
しかし、手軽さと最初の立ち上がりの早さが、Scikit-learnと比べるとまだ劣ると感じています。
せっかくなので、アドベントカレンダーを機に抽象化をある程度頑張ってみようかなと思った次第です。
Scikit-learnのWrapperを実装するメリット
Wrapper実装メリットは3つあると考えています。
- 「うまくいかないこと」を早く知る
- 使いやすい
- 他のアルゴリズムと取り替えやすい
「うまくいかないこと」を早く知る
私が考える最も大きな理由は「うまくいかないこと」を早く知れることです。
正直、実装初回からうまくいくとは思っていません。
そのため、早めになぜ、うまくいかないのか、
更には「うまくいかない」ことがわかりたいです。
これを早く知れると次はどうしようかといった手を打ちやすいです。
何ができれば嬉しいか
必要な作業の洗い出しをまずやります。
Chainerの機能をフルに活用するのはRecurrent Neural Network込みにすると難しいと思っています。
そのため、一部の機能に絞れば、きっと実装できるはず・・・
ということで必要最低限の機能を真面目に考えてみました。
最低限必要な項目
- モデル定義・・・ニューラルネットワーク定義を入力する。
- ハイパーパラメータ・・・学習用パラメータ
- GPU or CPU・・・ハードのモードの切り替え
- 学習機能・・・モデル学習機能(fit)
- 出力・・・確率分布での出力(predict_proba)、ラベル出力(predict)
- save load機能(weight)・・・学習済みモデルの読み出し、書き出し
実際に作ってみた
こんな感じになりました。
可能な限り、既存のChainerの機能を活かすようにしています。
ソースコード(chainer_neural_network.py)
# coding:utf-8 from __future__ import absolute_import from __future__ import unicode_literals from sklearn.base import BaseEstimator import chainer import numpy as np from chainer.training import extensions import cupy class ChainerNeuralNetwork(BaseEstimator): """ Chainer Neural Network Scikit learn Wrapper """ def __init__(self, model, optimizer, extensions=[], gpu=-1, batch_size=16, epochs=10, result="result"): self.model = model self.optimizer = optimizer self.extensions = extensions self.gpu = gpu self.batch_size = batch_size self.epochs = epochs self.result = result self.xp = np if gpu >= 0: chainer.cuda.get_device(gpu).use() self.model.to_gpu() self.xp = cupy self.optimizer.setup(model) super(ChainerNeuralNetwork, self).__init__() def convert_list_to_tuple(self, X, y): """ convert list to tuple :param X: X :param y: y :return: tuple dataset """ return chainer.datasets.TupleDataset(X, y) def fit(self, X, y=None, valid_X=None, valid_y=None): """ fit model :param X: tuple dataset or matrix X :param y: expected vector :param valid_X: validation X :param valid_y: validation y :return: """ if isinstance(X, chainer.datasets.TupleDataset): train_dataset = X else: train_dataset = self.convert_list_to_tuple(X, y) train_iter = chainer.iterators.SerialIterator(train_dataset, self.batch_size) updater = chainer.training.StandardUpdater(train_iter, self.optimizer, device=self.gpu) trainer = chainer.training.Trainer(updater, (self.epochs, 'epoch'), out=self.result) if valid_X is not None: if isinstance(valid_X, chainer.datasets.TupleDataset): valid_dataset = valid_X else: valid_dataset = self.convert_list_to_tuple(valid_X, valid_y) test_iter = chainer.iterators.SerialIterator(valid_dataset, self.batch_size, repeat=False, shuffle=False) self.extensions.append(extensions.Evaluator(test_iter, self.model, device=self.gpu)) for extend in self.extensions: trainer.extend(extend) trainer.run() def _iter_predict(self, X): """ iteration for prediction :param X: np.ndarray :return: prediction of batch """ for index in range(0, len(X), self.batch_size): batch_x = chainer.Variable(self.xp.array(X[index: index + self.batch_size]).astype(self.xp.float32), volatile=False) if index == 0: predicts = np.array(chainer.cuda.to_cpu(self.model.predict(batch_x).data)) else: predicts = np.vstack([predicts, chainer.cuda.to_cpu(self.model.predict(batch_x).data)]) return np.array(predicts) def predict(self, X): return np.argmax(self.predict_proba(X), axis=1) def predict_proba(self, X): return self._iter_predict(X) def load_weights(self, filepath): chainer.serializers.load_npz(filepath, self.model) def save_weights(self, filepath): chainer.serializers.save_npz(filepath, self.model)
- 基本的にはfit, predict, predict_probaの基本機能を実装しました。
- 学習して保存できるようload_weights, save_weights機能を作ってみました。
- X, yを別々にした場合でも、Dataset(Tuple)でも使えるようにしています。
MNISTで遊ぶ
Wrapperを使ってチュートリアルのMNISTコードを抽象化します。
簡単なニューラルネットワークであればこの程度抽象化すれば実施可能です。
ただし、構築モデル(Chain)はモデルを使うためにpredict関数の実装をしなければなりません。
そのため、なければ実装してください。
本プログラムは、chainer.links.Classifierを継承し、ClassifierWrapperを作りました。
このクラスにpredict関数を実装しています。
ソースコード
# coding:utf-8 from __future__ import absolute_import from __future__ import unicode_literals import argparse import chainer import chainer.functions as F import chainer.links as L from chainer import training from chainer.training import extensions from chainer_neural_network import ChainerNeuralNetwork import chainer.serializers import numpy as np class MLP(chainer.Chain): def __init__(self, n_units, n_out): super(MLP, self).__init__( l1=L.Linear(None, n_units), l2=L.Linear(None, n_units), l3=L.Linear(None, n_out), ) def __call__(self, x): h1 = F.relu(self.l1(x)) h2 = F.relu(self.l2(h1)) return self.l3(h2) class ClassifierWrapper(L.Classifier): def predict(self, x): return F.softmax(self.predictor(x)) def main(): parser = argparse.ArgumentParser(description='Chainer example: MNIST') parser.add_argument('--batchsize', '-b', type=int, default=100, help='Number of images in each mini-batch') parser.add_argument('--epoch', '-e', type=int, default=20, help='Number of sweeps over the dataset to train') parser.add_argument('--gpu', '-g', type=int, default=-1, help='GPU ID (negative value indicates CPU)') parser.add_argument('--out', '-o', default='result', help='Directory to output the result') parser.add_argument('--unit', '-u', type=int, default=1000, help='Number of units') args = parser.parse_args() print('GPU: {}'.format(args.gpu)) print('# unit: {}'.format(args.unit)) print('# Minibatch-size: {}'.format(args.batchsize)) print('# epoch: {}'.format(args.epoch)) print('') model = ClassifierWrapper(MLP(args.unit, 10)) optimizer = chainer.optimizers.Adam() extensions_list = [ extensions.PrintReport( ['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy']), extensions.LogReport(), extensions.dump_graph('main/loss'), extensions.ProgressBar() ] train, test = chainer.datasets.get_mnist() X, y = [pair[0] for pair in train], [pair[1] for pair in train] test_X, tedt_y = [pair[0] for pair in test], [pair[1] for pair in test] test_X, tedt_y = np.array(test_X), np.array(tedt_y) neural_network = ChainerNeuralNetwork(model=model, optimizer=optimizer, extensions=extensions_list, gpu=args.gpu, batch_size=args.batchsize, epochs=args.epoch, result=args.out) neural_network.fit(X=X, y=y, valid_X=test) print(neural_network.predict_proba(test_X).shape) print(neural_network.predict(test_X).shape) neural_network.save_weights("model.npz") neural_network.load_weights("model.npz") if __name__ == '__main__': main()
このモデルにverboseと呼ばれるオプションをつけると抽象化できるかも・・。
例えば、LogReport周りはこのオプションの追加で
extensions_listで宣言しなくてもログ出力ができると思います。
ただ、今回はそこまでは作っていません。
最後に
不足している機能はあると思いますが、これでもだいぶ遊べます。
さくっと遊ぶにはこの機能で多分、十分です。
誰かもっと高機能にしてください。そしてメンテしてください。