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

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

PyTorchでCIFAR10を既存のCIFAR10のDataset Classを使わずに分類する

皆さんこんにちは。
お元気ですか。雨天が増えてきて、出かけるのが億劫になっています。

PyTorchを使って画像認識データセットCIFAR10を分類しました。
KaggleでPyTorchユーザが増えてきたこともあり、勉強しました。

最近、この手のチュートリアルやExamplesに良しなにできる
データ処理専用クラスを予め作っていることがあります。

この状態は新しいデータセットを試したい場合に不便なので、
そのような内容が含まれないCIFAR10のコードを記述しました。

PyTorch

PyTorchとは

Deep Learningフレームワークです。
柔軟なネットワーク構築やGPUを利用した高速な行列演算を得意としています。
Chainerをforkして作られたので、実装方法が非常に似ています。
噂ではございますが、Chainerより高速だったりそうでなかったり。

http://pytorch.org/

インストー

上記の公式サイトを参考にしてください。
OS、Pythonのversion、CUDAの有無、バージョンによってコマンド変化しますが
公式にいけばそれらの選択ができ、その環境に応じたコマンドを提示します。

そのコマンドを入力すれば、環境の構築が完了します。
例えば、OSX, Python 2.7, pip, CUDAなしの場合、次のコマンドを実行します。

pip install http://download.pytorch.org/whl/torch-0.1.12.post2-cp27-none-macosx_10_7_x86_64.whl 
pip install torchvision 

CIFAR10を分類するコードを作ってみる。

ニューラルネットワークで設定する項目は
他のフレームワーク(TensorFlow, Chianerなど)と同じです。
Chainerと比較的似ている実装になっています。
ソースコード全体は次のgistに記載しています。

cifar10_example.py · GitHub

Import

はじめにimport文です。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data.dataset import Dataset
from torch.utils.data import DataLoader
from torchvision import transforms
import cPickle
import numpy as np
import os

CIFAR10の読み込み

CIFAR10を読み込むコードです。
同じディレクトリにCIFAR10のPython versionをダウンロードし、展開すれば完了です。
ソースコードは次を参考にさせていただきました。
今回用に少々修正を入れています。

qiita.com

def unpickle(file):
    fo = open(file, 'rb')
    dict = cPickle.load(fo)
    fo.close()
    return dict


def conv_data2image(data):
    return np.rollaxis(data.reshape((3, 32, 32)), 0, 3)


def load_cirar10(folder):
    """
    load cifar10

    :return: train_X, train_y, test_X, test_y
    """
    for i in range(1, 6):
        fname = os.path.join(folder, "%s%d" % ("data_batch_", i))
        data_dict = unpickle(fname)
        if i == 1:
            train_X = data_dict['data']
            train_y = data_dict['labels']
        else:
            train_X = np.vstack((train_X, data_dict['data']))
            train_y = np.hstack((train_y, data_dict['labels']))

    data_dict = unpickle(os.path.join(folder, 'test_batch'))
    test_X = data_dict['data']
    test_y = np.array(data_dict['labels'])

    train_X = [conv_data2image(x) for x in train_X]
    test_X = [conv_data2image(x) for x in test_X]

    return train_X, train_y, test_X, test_y

入力データの扱い

DataLoaderを使った方法はDatasetクラスを継承し、必要なメソッドを
作成したクラスに継承させる必要があります。

必要なメソッドは__getitem__と__len__です。
__getitem__にindexが与えられた時のデータの処理、
__len__にはデータの数を計算する処理を記載します。

class DataSet(Dataset):
    def __init__(self, x, y, transform=None):
        self.x = x
        self.y = y
        self.transform = transform

    def __getitem__(self, index):
        x = self.x[index]
        y = self.y[index]

        if self.transform is not None:
            x = self.transform(x)
        return x, y

    def __len__(self):
        return len(self.x)

それでは、DataLoaderを作ります。第一引数に先程作成したDataset、
第二引数にbatch_size, 第三引数にデータの順番をshuffleするかどうかを与えます。
DataLoaderの利用方法は後述します。
PyTorchには、transformsパッケージがあり、
このtransformsを利用すると0-1へのスケーリング、
正規化、ランダムで切り抜きを行うなどの処理を記載できます。

train_d_loader = DataLoader(
    DataSet(
        x=train_X,
        y=train_y,
        transform=transforms.Compose(
            [
                transforms.ToTensor()
            ]
        )
    ), batch_size=64, shuffle=True
)

test_d_loader = DataLoader(
    DataSet(
        x=test_X,
        y=test_y,
        transform=transforms.Compose(
            [
                transforms.ToTensor()
            ]
        )
    ), batch_size=64, shuffle=False
)

Neural Networkの作り方

Convolutional Neural Networkを作りました。下記のコードを参考にしてください。
Chainerをベースにしているので、Chainerに類似している実装になります。
Chainerでは、__call__に宣言していましたが、PyTorchはforwardになります。

class ConvolutionalNeuralNetwork(nn.Module):
    def __init__(self):
        super(ConvolutionalNeuralNetwork, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
        self.conv4 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
        self.conv5 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.conv6 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(512 * 4, 256)
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        h = F.relu(self.conv1(x))
        h = F.relu(self.conv2(h))
        h = F.max_pool2d(h, 2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pool2d(h, 2)
        h = F.relu(self.conv5(h))
        h = F.relu(self.conv6(h))
        h = F.max_pool2d(h, 2)

        h = h.view(-1, 512 * 4)
        h = F.relu(self.fc1(h))
        h = F.dropout(h, training=self.training)
        h = F.log_softmax(self.fc2(h))
        return h

その他学習のためのOptimizerの準備

Optimizerの定義が必要です。特筆すべきところはありません。

optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)

学習

Chainerに非常に似ているコードです。
Variableを用いているのも、Chainerそっくりです。
少々前で宣言したDataLoaderを使っています。forで使うことで、
指定したバッチの数ずつ、データを取り出せます。

学習コード

for epoch in range(epochs):
    model.train()
    train_loss = 0.0
    for index, (batch_train_x, batch_train_y) in enumerate(train_d_loader):
        train_variable = torch.autograd.Variable(batch_train_x)
        output = model(train_variable)
        loss = F.nll_loss(output, torch.autograd.Variable(batch_train_y))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        train_loss += loss.data[0]

    print ("training epoch: {} loss: {}".format(epoch, train_loss / len(train_d_loader)))

評価コード

学習部分とそこまで変わらない評価コードです。
Variableを使い、誤差と正答率を計算するコードです。
モデルの学習用と評価用の挙動は.train(), .eval()で切り替えられます。

    model.eval()
    test_loss = 0.0
    correct = 0
    for index, (batch_test_x, batch_test_y) in enumerate(test_d_loader):
        output = model(torch.autograd.Variable(batch_test_x))
        test_loss += F.nll_loss(output, torch.autograd.Variable(batch_test_y)).data[0]

        pred = output.data.max(1)[1]
        correct += pred.eq(batch_test_y).cpu().sum()

    print ("testing epoch: {} loss: {} accuracy: {}".format(epoch, test_loss / len(test_d_loader),
                                                            float(correct) / float(len(test_d_loader.dataset))))

標準出力

途中まで出力しました。後は省略しております。

training epoch: 0 loss: 2.19472245665
testing epoch: 0 loss: 1.85379125782 accuracy: 0.3254
training epoch: 1 loss: 1.68773823439
testing epoch: 1 loss: 1.42734462385 accuracy: 0.4772
training epoch: 2 loss: 1.36909840554
testing epoch: 2 loss: 1.21328892304 accuracy: 0.5669
training epoch: 3 loss: 1.1273400925
testing epoch: 3 loss: 0.970653118036 accuracy: 0.6634
training epoch: 4 loss: 0.941422916153
testing epoch: 4 loss: 0.885185660931 accuracy: 0.6911
training epoch: 5 loss: 0.811293978132
testing epoch: 5 loss: 0.770014607583 accuracy: 0.7351
training epoch: 6 loss: 0.713843686805
testing epoch: 6 loss: 0.762587684126 accuracy: 0.7432
training epoch: 7 loss: 0.640705172528
testing epoch: 7 loss: 0.716414518535 accuracy: 0.7554
training epoch: 8 loss: 0.59412489799
testing epoch: 8 loss: 0.745007351374 accuracy: 0.7571
training epoch: 9 loss: 0.559192490898
testing epoch: 9 loss: 0.786619110325 accuracy: 0.7396
training epoch: 10 loss: 0.522159374087
testing epoch: 10 loss: 0.766681446102 accuracy: 0.7504
training epoch: 11 loss: 0.500029139583
testing epoch: 11 loss: 0.739471812218 accuracy: 0.7623
training epoch: 12 loss: 0.489205267016
testing epoch: 12 loss: 0.755763524399 accuracy: 0.7664
training epoch: 13 loss: 0.479558758423
testing epoch: 13 loss: 0.801054440939 accuracy: 0.7527
training epoch: 14 loss: 0.4715372192
testing epoch: 14 loss: 0.862275337164 accuracy: 0.731
training epoch: 15 loss: 0.476616473731
testing epoch: 15 loss: 0.895243975301 accuracy: 0.7489
training epoch: 16 loss: 0.489420878252
testing epoch: 16 loss: 0.818035978717 accuracy: 0.7625
training epoch: 17 loss: 0.476847741119
testing epoch: 17 loss: 0.85643605626 accuracy: 0.7505
training epoch: 18 loss: 0.490220740435
testing epoch: 18 loss: 0.871974576729 accuracy: 0.7412
training epoch: 19 loss: 0.482988453422
testing epoch: 19 loss: 0.795684698529 accuracy: 0.7606
training epoch: 20 loss: 0.500881570677
testing epoch: 20 loss: 0.783078642747 accuracy: 0.7542
training epoch: 21 loss: 0.522443722843
testing epoch: 21 loss: 0.903285436023 accuracy: 0.7377
training epoch: 22 loss: 0.532127012151
testing epoch: 22 loss: 0.875951717123 accuracy: 0.739
training epoch: 23 loss: 0.544996414693
testing epoch: 23 loss: 0.914666230258 accuracy: 0.7358
training epoch: 24 loss: 0.555458197228
testing epoch: 24 loss: 0.860163988682 accuracy: 0.7438
training epoch: 25 loss: 0.577152646749
testing epoch: 25 loss: 0.969825881072 accuracy: 0.7164
training epoch: 26 loss: 0.574859146698
testing epoch: 26 loss: 0.866168110325 accuracy: 0.7408
training epoch: 27 loss: 0.574894333672
testing epoch: 27 loss: 0.920369815046 accuracy: 0.7318
training epoch: 28 loss: 0.603822341083
testing epoch: 28 loss: 0.969683260583 accuracy: 0.7406
training epoch: 29 loss: 0.630050289644
testing epoch: 29 loss: 0.859408840299 accuracy: 0.7507
training epoch: 30 loss: 0.642022043729
testing epoch: 30 loss: 0.890207239328 accuracy: 0.7272

終わりに

Chainerと類似しているので非常に学びやすく、
正直学習コストは殆どなかったかなと思います。

今後、どっちを使うかはケースバイケースですが、時々使っていこうと思います。

Rundeckを使ってリッチに定期ジョブを作った

皆さんこんにちは
お元気ですか。昨日食べた中華そばおいしかったです。

定期的に実行するJobを作成・実行する場合何を使っているでしょうか?
メジャーなツールであれば、cronがあります。

しかし、このツールはCUI、かつ、
他に色々追加したいこと(リトライ、通知)などを
設定したい場合に大変で、面倒です。

そこで、Rundeckです。RCO&RLSさんの勉強会に触発されて使ってみました。

Rundeckとは?

一言で表すとジョブスケジューラです。
ジョブを作成し、実行間隔の管理、通知、実行方式などを管理できます。

GUIで実行コマンドを管理できるので、非常に管理しやすく
過去のログも確認しやすい、成功失敗で通知ができるなど、様々なメリットがあります。

個人的に思いつく使い道は機械学習バッチ処理ですね!

Rundeckの起動

早速、実行用のjarファイルをダウンロードしましょう。

rundeck.org

このjarファイルを実行すれば立ち上がります。起動コマンドは次のとおりです。

java -jar rundeck-launcher-2.8.2.jar

起動時に必要な設定ファイル群も作られます。
まずは、config/rundeck-config.propertiesの7行目のgrails.serverURLを変更します。
但し、起動時に作られるconfigファイルはserverのURLが誤っているため、画面の遷移ができません。
ローカルで起動する場合は、localhostで大丈夫です。localhostの例を次に示します。

#rss.enabled if set to true enables RSS feeds that are public (non-authenticated)
rss.enabled=false
grails.serverURL=http://localhost:4440
dataSource.dbCreate = update
dataSource.url = jdbc:h2:file:/Users/Tereka/Programing/Software/Rundeck/server/data/grailsdb;MVCC=true

設定後に改めてサーバを起動しましょう。

Rundeckを使ってみる

まずは、アクセスします。http://localhost:4440へアクセスしましょう。
このアドレスを入力するとログイン画面に遷移します。こちらはID, PASS共に、adminを入力します。

f:id:tereka:20170520192935p:plain

ログインすると、プロジェクト管理画面に遷移します。

プロジェクトを生成する。

まずは、プロジェクトが存在しないため、プロジェクトを作成します。
緑色のボタン「New Project」をクリックします。

f:id:tereka:20170520193842p:plain

次にプロジェクトの設定をします。今回は「Project Name」を入れるのみで良いです。
下の方にある作成ボタンを押します。

f:id:tereka:20170520193902p:plain

これで、プロジェクトが作られました。ここからプロジェクトのジョブを作ります。

Jobの作成

これから実際に実行するジョブを作成します。

f:id:tereka:20170521231130p:plain

Workflowで実行したいコマンドを設定します。
試しに「ls /tmp」のコマンドを実行するジョブを構築します。
「Wolkflow」のAdd StepからCommandをクリックし、
Commandに「ls /tmp」を入力しましょう。

f:id:tereka:20170521231704p:plain

そして、Createボタンを押します。

実行&結果表示

作成したジョブを実行します。
緑色のボタンである「Run Job Now」をクリックしましょう。

f:id:tereka:20170521231917p:plain

実行すると結果を確認できます。ちゃんと成功していますね!
結果のタブは以下の意味があります。

  1. Summary:ジョブが成功した失敗したの概要
  2. Report:結果のレポート
  3. Log Output:標準の出力
  4. Definition:ジョブの定義

f:id:tereka:20170521231925p:plain

2つのコマンドを使い、片方を失敗する場合

前回はシンプルな実行をするジョブでした。しかし、次は少々なジョブを作成します。
「cat /tmp」と「ls /tmp」のコマンドを組み合わせます。前回と同じように追加していきます。

今回のポイントは、「Run remaining steps before failling」です。
これを選択することで、前段のコマンドが失敗した場合も継続して後段が実行されます。
今回は「cat /tmp」が失敗、「ls /tmp」が成功します。

f:id:tereka:20170521233017p:plain

実行結果です。この結果から2つのコマンドが実行され、
1件のコマンドが成功、1件が失敗していることがわかります。

f:id:tereka:20170521232830p:plain

これで、2つ以上のコマンドが実行できることを確認できました。

定期的に実行する

本記事最後にRundeckを利用した定期実行を行ってみます。
まずは、ジョブを作ります。今回はdf -hのディスク確認コマンドを実行するタスクを作ります

今回は「Schedule to run repeatedly?」の項目をYesにします。
これをYesにすることで、定期実行ジョブの設定が可能となります。
crontab形式の設定が可能のため、その設定を行います。設定を行った画面は次になります。

このcrontabの設定だと毎分、実行するコマンドになります。

f:id:tereka:20170521234230p:plain

実行画面に遷移し、時々リロードすると、画面が更新されて定期的に実行されることがわかります。

f:id:tereka:20170521234240p:plain

これでリッチな定期実行タスクの設定ができますね。

最後に

まだまだRundeck使いこなせていませんが、リモートのジョブを実行する、
通知機能(拡張すればSlackも)の実行などを行うことができ、非常に便利です。

やっぱり機械学習バッチを作る時に便利かも・・

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を使った環境構築をやってみました!
・・・大変だった。。