のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

Chainerの学習の様子をリモートで確認するExtensionを作った

皆さんこんにちは
お元気ですか。私はGWでリフレッシュして、生き返りました。

Kaggleをやっているとき(特に画像などの長い場合)にリモートで
今学習されているかどうか、誤差はどうかなどのモデルの
様子が気になることはありませんか?

私は画像認識系のコンペを実際に行っている時に、気になることがあります。
これどうしようかと考えていたのですが、歩いている時にふと思いついたので実装しました。
このアイデアの実装のために、新しいChainerのExtensionを開発しました。(Trainerを使う想定です)

イデア

Slackであれば外出中も見れると考えました。
そのため、学習の途中経過(lossなど)を投稿すれば見れる!
実装イメージは次の図に掲載しました。

f:id:tereka:20170509235100j:plain

コードを見た限りだと、Extensionで実装できそうだったので、トライしました。

Extensionの実装方法

Extensionの実装ですが、先に必要な情報を__init__に実装します。
そして、__call__が呼び出され、処理をする仕組みとなっています。

今回は既に実装されているPrintReportを参考に実装します。

前準備

SlackのWeb APIのIncoming Webhooksと投稿するChannelとusernameが必要です。
SlackのAPIに必要な情報は以下のurlから遷移して取得してください。

api.slack.com

コード

早速Extensionを実装しました。
表示情報は既に実装されているPrintReportと同じにしました。
_throw_slackにSlackに投稿する部分を定義しています。
__init__に初期設定で必要な情報、__call__に表示する部分を記載しました。

# coding:utf-8
import os
import sys
from chainer.training import extension
from chainer.training.extensions import log_report as log_report_module
import requests
import json


class SlackReport(extension.Extension):
    def __init__(self, entries, log_report='LogReport', username="", url="",channel="",out=sys.stdout):
        self._entries = entries
        self._log_report = log_report
        self._log_len = 0  # number of observations already printed
        self._out = out

        # format information
        entry_widths = [max(10, len(s)) for s in entries]

        header = '  '.join(('{:%d}' % w for w in entry_widths)).format(
            *entries) + '\n'
        self._header = header  # printed at the first call

        templates = []
        for entry, w in zip(entries, entry_widths):
            templates.append((entry, '{:<%dg}  ' % w, '  ' * (w + 2)))
        self._templates = templates
        self.username = username
        self.url = url
        self.channel = channel

    def __call__(self, trainer):
        if self._header:
            self._throw_slack(self._header)
            self._header = None

        log_report = self._log_report
        if isinstance(log_report, str):
            log_report = trainer.get_extension(log_report)
        elif isinstance(log_report, log_report_module.LogReport):
            log_report(trainer)  # update the log report
        else:
            raise TypeError('log report has a wrong type %s' %
                            type(log_report))

        log = log_report.log
        log_len = self._log_len
        while len(log) > log_len:
            self._observation_throw_slack(log[log_len])
            log_len += 1
        self._log_len = log_len

    def _throw_slack(self, text):
        try:
            payload_dic = {
                "text": text,
                "username": self.username,
                "channel": self.channel,
            }
            requests.post(self.url, data=json.dumps(payload_dic))
        except:
            self._out.write("error!")

    def serialize(self, serializer):
        log_report = self._log_report
        if isinstance(log_report, log_report_module.LogReport):
            log_report.serialize(serializer['_log_report'])

    def _observation_throw_slack(self, observation):
        text = ""
        for entry, template, empty in self._templates:
            if entry in observation:
                text += template.format(observation[entry])
            else:
                text += empty
        self._throw_slack(text)

使い方

APIに必要な情報の定義とExtensionにSlackReportを追加するのみです。
例えば、公式のMNISTサンプルでは次のExtensionを追記してください。

username,channel,urlはそれぞれ自分の設定を指定してください。

    trainer.extend(SlackReport(['epoch', 'main/loss', 'validation/main/loss',
                                'main/accuracy', 'validation/main/accuracy', 'elapsed_time'],
                               username="YOUR USER NAME",
                               channel="YOUR SLACK CHANNEL",
                               url="SLACK API URL",
                               ))

Slackへの投稿結果

ExampleのMNISTで実験したら、こんな感じになります。ちょっとガタガタなのはご愛嬌。
外出時に気になる場合は皆さんも使ってみましょう。

設定が間違っているときの処理は…、結構適当です。

f:id:tereka:20170509233656p:plain

CUDAを使ってGPUプログラミングに挑戦してみた。

皆さんこんにちは
お元気ですか。私は家の近くに一風堂ができて感動しています。
本日はCUDAを使ったGPUプログラミングに挑戦します。

最近、ふと思うことがあります。
GPUをよく使いますが、GPUの心みたいなものがわからない状態です。
そこで、GPUプログラミングに挑戦し、コードを書きながらGPUのことを
知ることができたらと思って書いてみました。

GPUわからないとTwitter呟いていたら、
@さんにおすすめの書籍を紹介してもらいました!
GPUがどんな仕組みで動作しているのか非常にわかりやすいです。

CUDA C プロフェッショナル プログラミング impress top gearシリーズ

CUDA C プロフェッショナル プログラミング impress top gearシリーズ

そもそもGPUって何?

第1回 GPUコンピューティングおよびCUDAについて | G-DEPより引用します。

GPUとはGraphics Processing Unit の略称で、その名の通りPCやワークステーションにおいて画像処理を担当する主要な部品の一つです。高速のVRAM(ビデオメモリ;グラフィックボード上のGPU専用メモリ)と接続され、グラフィクスシェーディングに特化したプロセッサが多く集まった構造を持っています。一つ一つのプロセッサの構造は単純なためその機能はCPUに比べて限定されたものですが、大量のデータを複数のプロセッサで同時かつ並列処理することができます。

近年、DeepLearingの演算で利用されることの多いのは最後にある大量のデータを
複数のプロセッサで同時かつ並列処理できる点でしょう。
これにより特に行列演算において、CPUより高速に演算を行えます。

ただし、GPUは条件分岐処理が含まれるとCPUより処理が遅くなる傾向があるとのこと。
条件分岐があると逐次処理になり、処理自身を並列にできないようです。

CUDAとは

第1回 GPUコンピューティングおよびCUDAについて | G-DEPより引用します。

CUDA(クーダ)とはCompute Unified Device Archtectureの略称で、半導体メーカーNVIDIA社が提供するGPUコンピューティング向けの統合開発環境です。プログラム記述、コンパイラ、ライブラリ、デバッガなどから構成されており、プログラム言語はC言語ベースに拡張を加えたものであるため、C言語によるプログラミングの経験があれば扱いやすくなっています。

GPUコンピューティング向けの環境であるようです。(そのまま)
CUDAはC言語ベースに拡張を加えたものであるため、C言語を扱ったことがある人であれば
簡単に実装することができるそう。

さっそく、CUDAを使ったGPUプログラミングに挑戦しましょう。

準備

GPUとCUDA、GPUドライバーを準備してください。

nonbiri-tereka.hatenablog.com

GPUHello Worldを書く

まずは、GPUHello Worldです。
次のソースコードを「hello_world.cu」のファイル名で作成しましょう。

#include <stdio.h>

int main( void ) {
  printf("Hello, World!\n");
  return 0;
}

そして、コンパイルします。コンパイラgccではなくnvccを使いましょう。

nvcc hello_world.cu

nvccは「ソースの中のCPUで動く部分とGPUで動く部分を分け、CPU用コードをCコンパイラに渡し、GPU用コードをコンパイル(正確にはPTXコードに変換)する」コンパイラとのことです。
そのため、Cのみでも、実行可能となります。

さて、実行しましてみましょう。

$ ./a.out
Hello, World!

これでGPU計算の第一歩を踏み出しました(ぶっちゃけ、GPU使ってない

GPU上で計算させてみる。

先程実行したのは出力をする簡単なものです。
GPUを使って計算させてみましょう。
次のコードは2つの配列を合計する計算をする処理を行います。

#include <stdio.h>
#include<stdlib.h>

#define N 2000000000

__global__
void sum_of_array(float *arr1, float *arr2, float *arr3, int size){
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    arr3[i] = arr1[i] + arr2[i];
}

void initialize_array(float *arr, int size){
    for (int i = 0; i < size; i++){
        arr[i] = (float)rand();
    }
}

int main(void){
    float *arr1, *arr2, *arr3, *d_arr1, *d_arr2, *d_arr3;
    size_t n_byte = N * sizeof(float);

    arr1 = (float *)malloc(n_byte);
    arr2 = (float *)malloc(n_byte);
    arr3 = (float *)malloc(n_byte);

    initialize_array(arr1, n_byte);
    initialize_array(arr2, n_byte);
    initialize_array(arr3, n_byte);

    printf("start cudaMalloc\n");
    cudaMalloc((void**)&d_arr1, N);
    cudaMalloc((void**)&d_arr2, N);
    cudaMalloc((void**)&d_arr3, N);
    printf("finish cudaMalloc\n");

    printf("start cudaMemcpy\n");
    cudaMemcpy(d_arr1, arr1, n_byte, cudaMemcpyHostToDevice);
    cudaMemcpy(d_arr2, arr2, n_byte, cudaMemcpyHostToDevice);
    cudaMemcpy(d_arr3, arr3, n_byte, cudaMemcpyHostToDevice);
    printf("finish cudaMemcpy\n");

    printf("start kernel function\n");
    sum_of_array<<<(N+255)/256, 256>>>(d_arr1, d_arr2, d_arr3, n_byte);
    printf("finish kernel function\n");
    cudaMemcpy(arr3, d_arr3, n_byte, cudaMemcpyDeviceToHost);
}

GPU上で演算するには、次の4つの手順が必要になります。

  1. GPUのメモリを確保する・・cudaMalloc
  2. GPUのメモリにコピーする・・cudaMemcpy(cudaMemcpyHostToDevice)
  3. 計算を実行する・・カーネル関数
  4. CPUのメモリに結果をコピーする・・cudaMemcpy(cudaMemcpyDeviceToHost)

まずは、GPUメモリを確保します。これはcudaMallocを使って行います。
次にGPUのメモリにCPUで確保したデータをコピーします。そして、カーネル関数と呼ばれる
関数で計算し、最後結果をCPUに書き戻します。

これにより、GPUを使った演算ができます。
CUDAにはまだまだライブラリがあるので、どんどん使ってみましょう!

Anacondaを使ったPythonの環境構築

皆さんこんにちは
お元気ですか。私は暇です(だと信じています)。
最近ブログタイトルと本人の忙しさが一致していないと言われるので
とりあえず暇ですといってみます。

本日は、Anacondaについて書きます。

Anacondaについて

Anacondaとは

AnacondaはPythonのデータサイエンスプラットフォームで
100以上のライブラリを簡単に導入できます。

Pythonのバージョンを仮想環境を使って切り替えることや
科学技術演算ライブラリ(numpyなど)のインストールを行うことを簡単にできます。
Windowsを全く利用していなかった頃は気にしていなかったのですが、
numpyやscipyを簡単に使えます。(pipで入れるとC環境の関係でハマったことがあります。)

インストール方法

まずは、インストールです。インストール用パッケージは
Anacondaの公式サイトで公開されています。
インストール先のOSによって媒体が異なるので、適切な媒体を選択しましょう。

Download Anaconda Now! | Continuum

GUIだとクリックするとインストールがはじまりますのでその手順に従いましょう。
また、CUIの場合は、公式にも掲載されている以下のコマンドを
利用すればインストールできます。
後半のshのファイルパスはインストール環境に併せて変更してください。

$ bash Anaconda3-4.2.0-Linux-x86_64.sh

但し、このコマンドは対話的に設定を行うので、Dockerfileのコマンドなど
対話的で行えない環境の場合は追加でオプションが必要です。
その場合は次のコマンドを利用しましょう。

$ bash Anaconda3-4.2.0-Linux-x86_64.sh -b -p /root/anaconda3

オプションですが、-bはバッチモードです。
例えば、Dockerfileに書く時のように対話的に実行できない場合に利用します。

  • pはAnacondaのインストール先のパスを設定します。

最後に環境変数を設定します。Anaconda環境を標準の初期設定にしたい場合は、
.bashrcに予めexport文を書いておきましょう。

$ export PATH=/root/anaconda3/bin:$PATH

そして、「python」とコマンドを叩き、以下のようになると成功です。
特に確認する必要がある項目はAnaconda 4.2.0が表示されていることです。

$ python
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  2 2016, 17:53:06)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

condaを使って環境を構築・確認する

Anacondaは付属のcondaでPythonの環境を作ったり、
新しいライブラリをインストールできます。
実際に構築してみましょう。

ライブラリのインストール

Anacondaにライブラリをインストールする時は、condaコマンドを使います。
まずは、conda searchを使い、インストールライブラリを確認します。
例えば、tensorflowをインストールする場合、次のようなコマンドを実行します。

$ conda search tensorflow
Fetching package metadata .......
tensorflow                   0.10.0rc0           np111py27_0  defaults
                             0.10.0rc0           np111py34_0  defaults
                             0.10.0rc0           np111py35_0  defaults

これらのパッケージをインストールするには、installコマンドを使います。

$ conda install tensorflow

但し、conda searchで見つからない見つからないライブラリはpipでインストールしましょう。

condaで入れたライブラリの一覧

conda listを入力するとライブラリの一覧を確認できます。

$ conda list
# packages in environment at /root/anaconda3:
#
_license                  1.1                      py35_1
_nb_ext_conf              0.3.0                    py35_0
alabaster                 0.7.9                    py35_0

仮想環境の構築と削除

仮想環境を構築します。今回は試しにPython3.6の仮想環境を構築します。

$ conda create -n py36 python=3.6 anaconda

構築した環境の確認は以下のようにできます。

$ conda info -e
# conda environments:
#
py36                  *  /root/anaconda3/envs/py36
root                     /root/anaconda3

また、構築した仮想環境ですが、removeコマンドで削除できます。

$ conda remove -n py36 --all
$ conda info -e
# conda environments:
#
root                  *  /root/anaconda3

仮想環境にライブラリをインストールする

インストールした環境にライブラリがない場合があります。
その場合、インストール方法は2つあります。

  1. conda searchを使い、検索し、インストールをする。
  2. pipを使ってインストールをする。

基本的には、conda searchしても見つからない場合はpipを使ってインストールします。

仮想環境の切り替え

この環境をactivateするには、sourceでこのコマンドを動かします。

$ source activate py36

逆に環境を無効化したい場合はdeactivateを使います。

$ source deactivate

condaのupdate

conda自身を更新することができます。
まずは、versionを確認しましょう。

$ conda --version
conda 4.2.9

次にversionを更新します。非常に簡単で、conda updateを使います。

$ conda update --prefix /root/anaconda3 anaconda

conda自身の情報

conda自身がどのような状態かは、conda infoを使えば確認できます。

$ conda info
Current conda install:

               platform : linux-64
          conda version : 4.3.13
       conda is private : False
      conda-env version : 4.3.13
    conda-build version : 2.0.2
         python version : 3.5.2.final.0
       requests version : 2.12.4
       root environment : /root/anaconda3  (writable)
    default environment : /root/anaconda3
       envs directories : /root/anaconda3/envs
                          /root/.conda/envs
          package cache : /root/anaconda3/pkgs
                          /root/.conda/pkgs
           channel URLs : https://repo.continuum.io/pkgs/free/linux-64
                          https://repo.continuum.io/pkgs/free/noarch
                          https://repo.continuum.io/pkgs/r/linux-64
                          https://repo.continuum.io/pkgs/r/noarch
                          https://repo.continuum.io/pkgs/pro/linux-64
                          https://repo.continuum.io/pkgs/pro/noarch
            config file : None
           offline mode : False
             user-agent : conda/4.3.13 requests/2.12.4 CPython/3.5.2 Linux/4.9.4-moby debian/jessie/sid glibc/2.19
                UID:GID : 0:0

最後に

一通りAnacondaを使った環境構築をやってみました!
・・・大変だった。。

Cookiecutterを使ってテンプレートからプロジェクトを作成する

皆さんこんにちは
お元気ですか。ユニクロの極暖Tシャツ着ているとかなり暑い。。。冬なのに

今日は、Cookiecutterを使ったテンプレートからの複製生成を試みます。
テンプレートからちょっと変えたい部分を用意することで、簡単に
似たプロジェクトを作成できます。

Cookiecutterについて

Cookiecutterは雛形となるテンプレートプロジェクトから
プロジェクトを生成するツールです。
雛形となるプロジェクトを作ってしまえば、少し変更したい箇所を指定して
新しいプロジェクトを作ります。

github.com

インストール

cookiecutterをpypiから取得できます。

sudo pip install cookiecutter

Cookiecutterを使った複製をやってみる。

頻繁に利用がありそうなテンプレートの雛形は既に用意されています。
とりあえず、何も考えずに作ってみましょう。

GitHub - ionelmc/cookiecutter-pylibrary: Enhanced cookiecutter template for Python libraries.の雛形を使用する場合は
次のように実行します。

cookiecutter gh:ionelmc/cookiecutter-pylibrary

途中で色々と聞かれるので必要のある箇所は入力します。

full_name [Ionel Cristian Maries]: tereka
email [contact@ionelmc.ro]:
website [https://blog.ionelmc.ro]:
github_username [ionelmc]:
project_name [Nameless]:
repo_name [python-nameless]:
package_name [nameless]:
distribution_name [nameless]:
project_short_description [An example package. Generated with cookiecutter-pylibrary.]:
release_date [today]:
Select year:
(略)

他にもflaskやdjango、restのプロジェクトがあります。
これで、確認すると変数となっていた箇所が指定した名前に
置き換わっていることを確認できます。
次はテンプレートを構築してみます。

テンプレートを作成に挑戦してみる。

準備されているテンプレートでは不足することもあるでしょう。
そんなときのために、自分で1からテンプレートを作ってみます。

ディレクトリの雛形は以下の通りです。

ディレクトリ構成

treeコマンドを使ってcookiecutterの雛形の構成を表示します。

.
└── TestProject
    ├── cookiecutter.json
    └── {{cookiecutter.project_name_lower}}
        └── README.md
cookiecutter.json

cookiecutter.jsonにcookiecutterの雛形で使う変数を定義します。

{
  "project_name" : "test-project",
  "create_document": "y",
  "license": ["MIT", "BSD-3", "GNU GPL v3.0", "Apache Software License 2.0"],
  "project_name_rower" : "{{ cookiecutter.project_name|replace('-', '_') }}"
}

{{ cookiecutter.<変数名> }}とすることで、その変数を埋め込めます。
また、埋め込んだ変数の文字を"-"→"_"に置換しています。
licenseの箇所はリストにしています。リストをjsonで入力した場合、
対話的に実行する時に候補を表示します。

README.md

プロジェクト内にあるREADME.mdにcookiecutterで
定義した変数を埋め込むテストをします。
今回、テストとして用意した文書は以下の通りです。

{{ cookiecutter.project_name}} document.

{% if cookiecutter.create_document == 'n' -%}
   No Document
{%- endif %}

{% if cookiecutter.create_document == 'y' -%}
{{cookiecutter.license}}

This is an apple.
{%- endif %}

このREADME内で、cookiecutterの変数を使えることは同じです。
追加で、if文を使った条件式を構築できます。上記の場合、
{% if cookiecutter.create_document == 'n' -%}はcreate_document変数がnの場合に動作し、
{% if cookiecutter.create_document == 'y' -%}はcreate_document変数がyの場合に動作します。

実行

コマンドラインからcookiecutter <ディレクトリ>で実行できます。
但し、ディレクトリ内にcookiecutter.jsonがないと実行できません。

$ cookiecutter TestProject/
project_name [test-project]: test-project
create_document [y]: y
Select license:
1 - MIT
2 - BSD-3
3 - GNU GPL v3.0
4 - Apache Software License 2.0
Choose from 1, 2, 3, 4 [1]: 2
project_name_lower [test_project]:

cookiecutter.jsonにリストで定義した設定は番号で選択できるようになっています。

作られたプロジェクトを確認する

最後に作ったプロジェクトを確認します。
まず、cookiecutterの変数になっていたディレクトリが
「test_project」の名前になりました。

.
└── test_project
    └── README.md
README.md
test-project document.



BSD-3

This is an apple.

README.mdを見ると、変数名が埋め込まれています。(test-project)
また、create_documentの条件が"n"となっている箇所が表示されていないことを確認できます。
更に、条件式が"y"の時に表示される文章を確認できました。

最後に

cookiecutterを使うと、基本となる雛形から
オリジナルのプロジェクトを簡単に作成できます。

ちょっとだけ変えたプロジェクトを作成したいことは結構あります。
意外に応用できそうなので、もっと使ってみたいと感じています。

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)からインストールします。
その手順は次の通りです。

  1. Visual Studio Codeを開く
  2. 左から拡張機能を選択する。
  3. cpptoolsを検索する。
  4. インストールする。

f:id:tereka:20170211164158p:plain

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を仕掛けるとその箇所で止まります。
実際にデバッグすると次のような画面になります。

f:id:tereka:20170211164547p:plain

ヘッダーの設定

コマンドで、「C/Cpp: Edit Configurations」を選択すると
C++の設定ファイル「「c_cpp_properties.json」が作られます。
そのの内部にヘッダーを設定する箇所があるので、その箇所にヘッダーのパスを
設定をすると読み込みすることができます。

このファイルはOS別に設定ができるため、該当するOSの"includePath"を変更します。

            "includePath": [
                "/usr/include",
                "/usr/local/include"
            ],

関数検索

関数検索は「cmd + shift + O」を使えばできます。
後はその欄で該当するファイルを探しましょう。

f:id:tereka:20170211162214p:plain

最後に

C++のエディタで良いものがなかったので、これはいい感じだと思っています。