sein's blog

ディープラーニング, 人工知能, 自然言語処理 ~ Deep Learning, AI, NLP ~

iPhoneバッテリー残量100%が長い理由をLirumデバイス情報で調べる

新しいiPhoneを使用しているとバッテリー残量100%が長続きする気がする。99%->98%よりも100%->99%がやたら長い。Lirumデバイス情報というアプリでバッテリーの状態を調べると、この謎が解明できた。

結論から先に書くと、

  • iPhoneのバッテリー実残量は103%や104%の状態がありうる
  • 満充電で充電を続けると、充電されない状態に移行する

今回テストしたデバイスは下記の2機種だ。

バッテリーの詳細情報を得るためのアプリはこちら。

Lirum デバイス 情報 Lite

Lirum デバイス 情報 Lite

  • Rogerio Hirooka
  • ユーティリティ
  • 無料

アプリを起動して画面左上のボタンを押すとメニューが現れる。 UIの翻訳がひどいので、日本語の時は[Settings]で[English]を選択する方が良い。

バッテリーの状態はメニューから [Tools] -> [Battery] -> [Check Battery Internal Reports]

と進む。

f:id:kanemura:20180213172929p:plain
iPhone 6のバッテリー内部レポート

上から順に説明すると

  • Battery Health 104%: 充電可能容量/端末仕様容量

  • Battery Wear Level: 上と同じだが、カギカッコ内は100%が上限

  • Battery is Charging: 充電中はYesとなる。スクリーンショットはケーブルを刺した状態だがNoとなっている。詳細は後述。

  • Precise Charge: 充電済容量/充電可能容量

注意すべきは、画面右上のバッテリー残量が100%を示していることだ。普段目にするiPhoneのバッテリー残量はどうやらPrecise Chargeの値のようだ。この値の分母はバッテリーの状態で変化する。

Battery Wear Levelの分母は端末の仕様で決まっているバッテリー容量なので固定値、一方、実バッテリー容量は端末仕様より少し多いため100%が長く続くと感じる。ちなみに、手持ちのiPhone XではBattery Healthが103%で、3年以上使ったiPad mini 2は80%だった。

また、最下部のPrecise Chargeが100%となった状態で約1時間程度充電を続けるとBattery is ChargingがYesからNoに変化する。ケーブルが刺さっているのに、バッテリーに給電されていないことになる。どうやらこの状態になると充電が止まった状態でライトニングケーブルからの電源を直接使用してiPhoneが動いているようだ。

最後に注意書き。Lirumデバイス情報はリフレッシュを頻繁にしない場合がある。その場合は画面を遷移させるか、アプリを完全終了(タスク終了)してからアプリを再起動するとバッテリーの最新状態が確認できる。

【PyCUDAでDeep Learningその1】PyCUDAから作るニューラルネットワーク

はじめに

PyCUDAを使ったニューラルネットワーク実装例を示します。Tensorflow, Chainer, KerasなどのDeep LearningライブラリからGPUを使う方法が一般的です。しかし、PyCUDAを使ったGPGPUプログラミングを経験することで、より理解が深まると思います。用いるニューラルネットワークは入力層を含めて3層パーセプトロンの簡易版です。また、MNIST手書き数字の学習および識別を行います。

f:id:kanemura:20170818121344p:plain

対象者

前提条件

  • Python3
  • Numpy
  • PyCUDA実行環境

Mac使いでPyCUDAの実行環境がまだの人は以下の記事を参考にしてください。

Windows/Linuxの人はググれば結構ヒットします。

注意事項

ここで用いるニューラルネットワークはかなりテキトーなので、真面目に勉強したい人は他のサイトや書籍を参考にしてください。

  • バイアスなし
  • 活性化関数はReluのみ
  • Softmaxすらない

ネットワークについて詳しくは数式なしでDeep Learning入門1 多層パーセプトロンをご覧ください。

実装

PyCUDA実装の要点

Numpyを用いた実装例は数式なしでDeep Learning入門1 多層パーセプトロンmlp.pyとして示してあります。ここで示すPyCUDA版と比較してみてください。

PyCUDAでGPGPUプログラミングするときの要点は以下の通りです。

  • GPU関連の初期化 まずGPUを初期化して使える状態にします。
import pycuda.autoinit
linalg.init()
  • NumpyのndarrayからPyCUDAのgpuarrayへの変換 一旦Numpyのndarrayを作ってから、gpuarrayに変換します。この際、データ変換と転送が発生します (CPU->GPU)。
w_hid = gpuarray.to_gpu(np.random.rand(n_hid, n_inp).astype(np.float32)/n_inp)

データタイプはfloat32を用いるのが無難です。逆変換 (GPU->CPU)は

w_hid_cpu = w_hid.get()

とするだけです。print()などで結果を表示したり、CPU側で計算したいときはこの処理が必要です。

  • 内積外積を計算する際はreshapeする必要があります。例えば、w.shapeが(3, 2)、x.shapeが(2, 0)とします。
y = linalg.dot(w, x)

はエラーとなります。

y = lialg.dot(w, x.reshape(2, 1))

とする必要があります。

PyCUDA版の実装例

変数名や関数名のpostfixに「gpu」がついているものはGPU用です。ただし、重みについては付けていません。少しくどくなるからです。

mlp_gpu.py

import numpy as np
from sklearn.datasets import fetch_mldata
from skcuda import linalg
from pycuda import gpuarray

# GPU初期化
import pycuda.autoinit
linalg.init()

# ネットワーク定数
n_inp = 784
n_hid = 1024
n_out = 10

# 学習定数
batch = 100
learn_rate = 0.01
update_rate = learn_rate/batch

# 重み
w_hid = gpuarray.to_gpu(np.random.rand(n_hid, n_inp).astype(np.float32)/n_inp)
w_out = gpuarray.to_gpu(np.random.rand(n_out, n_hid).astype(np.float32)/n_hid)
dw_hid = gpuarray.to_gpu(np.zeros(w_hid.shape).astype(np.float32))
dw_out = gpuarray.to_gpu(np.zeros(w_out.shape).astype(np.float32))

# MNIST
n_train = 60000
mnist = fetch_mldata('MNIST original')
data = mnist.data.astype(np.float32)
target = mnist.target.astype(np.int32)
x_train, x_test = np.split(data, [n_train])
d_train, d_test = np.split(target, [n_train])
n_test = d_test.size

# 入力値を0~1に正規化
x_train /= 255
x_test /= 255

# 正解 (GPU)
ans_gpu = []
for i in range(n_out):
    x = np.zeros(n_out).astype(np.float32)
    x[i] = 1
    ans_gpu.append(gpuarray.to_gpu(x).reshape((n_out, 1)))

# GPU転置行列
def T_gpu(x_gpu):
    return linalg.transpose(x_gpu)

# GPU内積
def dot_gpu(a_gpu, b_gpu):
    return linalg.dot(a_gpu, b_gpu)

# GPU外積
def outer_gpu(a_gpu, b_gpu):
    c_gpu = b_gpu.reshape(b_gpu.shape[1], b_gpu.shape[0])
    return linalg.mdot(a_gpu, c_gpu)

# 誤差微分
def diff_err_gpu(x_gpu, e_gpu):
    z_gpu = gpuarray.to_gpu(np.zeros(x_gpu.shape).astype(np.float32))
    return gpuarray.if_positive(x_gpu > z_gpu, e_gpu, z_gpu)

# relu関数
def relu_gpu(x_gpu):
    z_gpu = gpuarray.to_gpu(np.zeros(x_gpu.shape).astype(np.float32))
    return gpuarray.if_positive(x_gpu > z_gpu, x_gpu, z_gpu)

# 各層の出力を計算
def output_gpu(x):
    x_gpu = gpuarray.to_gpu(x).reshape((n_inp, 1))
    hid_gpu = relu_gpu(dot_gpu(w_hid, x_gpu))
    out_gpu = relu_gpu(dot_gpu(w_out, hid_gpu))
    return x_gpu, hid_gpu, out_gpu

# 学習していない数字を正しく識別できるか予想
def predict():
    score = 0
    for i in range(0, n_test):
        _, hid_gpu, out_gpu = output_gpu(x_test[i])
        out = out_gpu.get().reshape(n_out)
        if d_test[i] == np.argmax(out):
            score += 1
    print('test: {0}%'.format(score / 100))

# 学習用の数字で重みを更新
for epoch in range(1, 2):
    print('epoch: {0}'.format(epoch))
    # ランダムな順序に学習
    perm = np.random.permutation(n_train)
    # 100個単位で学習
    for i in range(0, n_train, batch):
        x_batch = x_train[perm[i:i+batch]]
        d_batch = d_train[perm[i:i+batch]]
        # 学習データを100個みせる
        for j in range(batch):
            x_gpu, hid_gpu, out_gpu = output_gpu(x_batch[j])
            # 誤差計算
            e_out = ans_gpu[d_batch[j]] - out_gpu
            e_hid = T_gpu(dot_gpu(T_gpu(e_out), w_out))
            e_hid = diff_err_gpu(hid_gpu, e_hid)
            dw_out += outer_gpu(e_out, hid_gpu)
            dw_hid += outer_gpu(e_hid, x_gpu)
        # 100個みせたので重み更新
        w_out += dw_out * update_rate
        w_hid += dw_hid * update_rate
        dw_out *= 0.9
        dw_hid *= 0.9
        # 学習が進んでいるか1000個ごとに識別予想
        if (i%1000) == 0:
            predict()

結果

基本的にはCPU版のmlp.pyの時と同等の結果が得られました。

epoch: 1
test: 9.8%
test: 17.06%
test: 10.2%
test: 10.2%
test: 24.61%
test: 41.79%
test: 42.53%
test: 60.8%
test: 59.03%
test: 59.77%
test: 62.69%
...
epoch: 10
test: 96.89%
test: 96.96%
test: 96.87%
test: 96.93%
test: 96.87%
test: 96.98%
test: 96.98%
test: 96.92%
test: 96.97%
test: 97.02%
test: 97.05%

使用したマシンは以下の通りです。

次にパフォーマンスについて調べてみます。

CPU v.s. GPU

ここでは、CPU版のmlp.pyとGPU版のmlp_gpu.pyを速度比較してみましょう。時間がかかるので、epoch数は1とします。

for epoch in range(1, 11):

for epoch in range(1, 2):

と変更してください。

また、最後のpredict()をコメントアウトして、学習時間だけを計測します。

#if (i%1000) == 0:
    #predict()
$ time python3 mlp.py
...
$ time python3 mlp_gpu.py

で計測します。

1 epoch, 学習のみ

n_hid=64, 0m5.363s
n_hid=1024, 1m12.998s
n_hid=4096, 4m17.055s
n_hid=8192, 11m52.893s
n_hid=64, 1m38.650s
n_hid=1024, 4m17.613s
n_hid=4096, 5m6.054s
n_hid=8192, 7m17.181s

ニューロン数が少ないとCPU演算の方が高速なことがわかります。むやみやたらとGPUを用いるとかえってパフォーマンスが落ちます。3層の単純なニューラルネットワークでは隠れ層のニューロンを多くしても今回の課題に対して良い結果をもたらしません。このようなケースではCPU演算で十分です。

入力データであるMNIST(具体的にはx_testとx_train)を事前にgpuarrayに変換して保持することで、多少のパフォーマンス改善が得られます。興味のある方は試してみてください。

おすすめ書籍

数式なしでDeep Learning入門1 多層パーセプトロン

はじめに

対象者

この記事はDeep Learningの基礎を勉強してみたが、すぐに挫折したという方が対象です。

前提条件

Python3とNumpyが使える

注意事項

ここで紹介しているニューラルネットワークは何かと簡略化されています。ちゃんと勉強したい人は他のサイトも参考にしてください。一般的でない事項を列挙します。

  • パーセプトロンのバイアスと活性化関数について言及していない
  • 出力層にSoftmaxなどの活性化関数を使用していない
  • 評価関数 (誤差関数)について言及がない
  • 確率的勾配降下法について説明がない

理論

多層パーセプトロン (MLP: Multi Layer Perceptron)

f:id:kanemura:20170818120510p:plain

まず、パーセプトロンの説明から。パーセプトロンは入力に重みをかけた値を全部足します。足した値が0より大きいとその値を出力します。そうでない場合は0を出力します。これをPythonで表現すると

import numpy as np
x = np.array([0, 1, 1]).astype(np.float32)
w = np.random.rand(3, 3).astype(np.float32)
y = np.maximum(0, np.dot(w, x))

f:id:kanemura:20170818121344p:plain

パーセプトロンを複数用意して各層をつなぎます。これが多層パーセプトロンです。パーセプトロン神経細胞の振る舞いを単純化していて「ニューロン」とも呼ばれます。以下では単にニューロンと呼びます。

ニューロンの振る舞いを「投票」のようにも解釈することができます。入力を全部足して出力を決定するからです。ただし入力に重みをかけるため、「投票」される一票は平等ではありません。入力の大きさを「声の大きさ」、一票の重みを「信頼度」と解釈してみましょう。ニューロンはつながっている他のニューロンの声の大きさに信頼度を上乗せして、自身の声の大きさを決めます。

以下では、入力の大きさを「声」、重みを「信頼」と置き換えて説明していきます。

学習方法: 誤差逆伝播法 (Back Propagation)

次に学習についてです。学習とは「信頼」を調整することです。正解との「ずれ」があるとそのぶん「信頼」を「上げたり」「下げたり」します。われわれ人間社会でも同じことを日常的にしているかもしれません。

f:id:kanemura:20170818121605p:plain

「信頼」の調整は、正解との「ずれ」すなわち出力層の誤差をもとに行われます。出力層から入力層に向けて誤差が伝わるので、誤差逆伝播という名前がついています。

出力層のあるニューロンの正解が1とします。実際の計算結果が0.3とします。誤差は0.7となります。このニューロンはもっと大きく反応すべきです。そのためには「信頼」を大きくする必要があります。ではどの程度「信頼」を修正すれば良いのでしょうか?まず、誤差が大きいと修正量もまた大きくなります。さらに「信頼」が大きいほど修正量も大きくなります。

f:id:kanemura:20170821111452p:plain

Pythonで実装します。正解をans、出力層の誤差をe_out、隠れ層の誤差をe_hidとします。

ans = np.array([1, 0, 0]).astype(np.float32)
e_out = ans - y_out
e_hid = np.dot(e_out, w_out) * (y_hid > 0)

y_hidとy_outはそれぞれ隠れ層と出力層の出力です。

前層の影響は「信頼」と「声」で決まりました。そこで、「信頼」が大きいものほど誤差に寄与したと考えます。np.dot(w_out.T, e_out)がこれに相当します。ただし、「声」が0の場合は誤差に貢献していなので、(y_hid > 0)をかけています。

続いて「声」の影響を考慮します。単純に誤差に「声」をかけるだけです。これで入ってきた「声」に応じて修正量が大きくなります。出力層と隠れ層の「信頼」の変化量をそれぞれdw_outとdw_hidとします。また、「声」をinpとします。

dw_out += np.outer(e_out, y_hid)
dw_hid += np.outer(e_hid, inp)

dw_outとdw_hidの初期値はどちらも0です。

学習は「信頼」を少しづつ変化させることで進めます。

w_out += dw_out * 0.01
w_hid += dw_hid * 0.01

0.01を学習率と呼びます。

学習は繰り返し行われます。全種類の「声」をネットワークに見せることを1 epochと呼びます。20回なら20 epochです。先人の知恵でepochごとの重み変化に0.9をかけて足すことで、学習が上手くいくことがわかっています。そこで、1 epochごとに

dw_out = dw_out*0.9
dw_hid = dw_hid*0.9

とします。0.9をモーメンタムと呼びます。

バッチ学習

ニューラルネットワークを学習させるには学習用のデータとテスト用のデータを必ず分ける必要があります。テスト用のデータを用いて学習することは決して行ってはいけません。ネットワークの性能をテスト用のデータを用いて計ります。

バッチ学習とは学習用のデータをある程度まとめてネットワークに「みせる」(入力する)ことです。100個程度まとめることが一般的なようです。手書き数字による実装例では学習用のデータを100個みせて重みを更新することを繰り返します。

実装例

MNIST

MNISTは28x28ピクセルの0~9の手書きデータです。中には「a」にしか見えない「2」があったりします。入力層のニューロン数は28x28=784となります。MNISTを使用するにはsklearnをインストールします。

sklearn Installation

$ pip3 install sklearn

sklearn Download

$ python3
>>> from sklearn.datasets import fetch_mldata
>>> mnist = fetch_mldata('MNIST original')

初めての場合、ダウンロードが始まるので結構時間がかかります。ホームディレクトリのscikit_learn_data/mldata/mnist-original.matがデータの実体です。

mlp.py

いよいよ実装です。python3用のスクリプトです。入力層は784個でした。隠れ層はとりあえず64個とします。出力層は0から9の数字を識別するので10個とします。

正解の配列をあらかじめ作っておく方式に変更しました。

import numpy as np
from sklearn.datasets import fetch_mldata

# ネットワーク定数
n_inp = 784
n_hid = 64
n_out = 10

# 学習定数
batch = 100
learn_rate = 0.01

# 重み
w_hid = np.random.rand(n_hid, n_inp).astype(np.float32)/n_inp
w_out = np.random.rand(n_out, n_hid).astype(np.float32)/n_hid
dw_hid = np.zeros(w_hid.shape).astype(np.float32)
dw_out = np.zeros(w_out.shape).astype(np.float32)

# MNIST
n_train = 60000
mnist = fetch_mldata('MNIST original')
data = mnist.data.astype(np.float32)
target = mnist.target.astype(np.int32)
x_train, x_test = np.split(data, [n_train])
d_train, d_test = np.split(target, [n_train])
n_test = d_test.size

# 入力値を0~1に正規化
x_train /= 255
x_test /= 255

# 正解配列
ans = []
for i in range(n_out):
    x = np.zeros(n_out).astype(np.float32)
    x[i] = 1
    ans.append(x)

# 各層の出力を計算
def output(x):
    y_hid = np.maximum(0, np.dot(w_hid, x))
    y_out = np.maximum(0, np.dot(w_out, y_hid))
    return y_hid, y_out

# 学習していない数字を正しく識別できるか予想
def predict():
    score = 0
    for i in range(0, n_test):
        inp = x_test[i]
        y_hid, y_out = output(inp)
        if d_test[i] == np.argmax(y_out):
            score += 1
    print('test: {0}%'.format(score / 100))

# 学習用の数字で重みを更新
for epoch in range(1, 11):
    print('epoch: {0}'.format(epoch))
    # ランダムな順序に学習
    perm = np.random.permutation(n_train)
    # 100個単位で学習
    for i in range(0, n_train, batch):
        x_batch = x_train[perm[i:i+batch]]
        d_batch = d_train[perm[i:i+batch]]
        # 学習データを100個みせる
        for j in range(batch):
            inp = x_batch[j]
            y_hid, y_out = output(inp)
            # 誤差計算
            e_out = ans[d_batch[j]] - y_out
            e_hid = np.dot(e_out, w_out)*(y_hid > 0)
            dw_out += np.outer(e_out, y_hid)
            dw_hid += np.outer(e_hid, inp)
        # 100個みせたので重み更新
        w_out += dw_out * learn_rate / batch
        w_hid += dw_hid * learn_rate / batch
        dw_out *= 0.9
        dw_hid *= 0.9
        # 学習が進んでいるか1000個ごとに識別予想
        if (i%1000) == 0:
            predict()

実行結果

$ python3 mlp.py
epoch: 1
test: 10.28%
test: 10.32%
test: 10.32%
test: 10.66%
test: 26.44%
test: 35.07%
test: 44.5%
test: 58.98%
test: 64.65%
...
epoch: 10
test: 97.03%
test: 96.96%
test: 97.03%
test: 97.06%

学習が進むにつれ、正解率が上がっていきます。10 epoch程度で97%の正解率が得られました。

さいごに

mlp.pyにはいくつかパラメーターがあります。色々いじってみると理解が深まります。

  • n_hid: 隠れ層のニューロン数を16とか128にしてみる
  • learn_rate: 学習率を0.1とか0.001にしてみる
  • 重みを初期化する際に入力数で割るのではなく、np.sqrt(入力数)で割ってみる

冒頭で申し上げた通り、ここで紹介したニューラルネットワークはテキトーです。なんとなくわかった気になったら、他のサイトや書籍できちんと勉強してください。この記事がディープラーニング入門の手助けになれば幸いです。

おすすめ書籍

ニューラルネットワーク自作入門

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

イラストで学ぶ ディープラーニング (KS情報科学専門書)

深層学習 (機械学習プロフェッショナルシリーズ)

PyCharmを使ってPyCUDAプログラミング

PyCharmはPython統合開発環境で、Pythonコードをデバッグ(Step in/over/over)することができます。PyCUDAを使用したGPGPU演算を含むソースコードをPyCharmでデバッグするときのPyCharmの設定を紹介します。

  • [Run]-[Edit Configurations]とメニューを辿ります。
  • Environment Variablesに下記の項目を追加します。
    • DYLD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-8.0/lib:/usr/local/cuda/lib
    • Environment VariablesPath=/Developer/NVIDIA/CUDA-8.0/bin:/usr/bin:/usr/sbin

以上です。

【PyCUDA環境構築その4】 PyCUDAをMacOS 10.12 Sierraにインストール

この記事は、PyCUDAでDeep LearningをするためのMac環境構築その4です。PyCUDAをMacにインストールする手順を説明します。

前提条件は以下の通りです。

  • NVIDIAGPUを搭載したMacで、GPUがCUDA8に対応
  • Python3、Numpy、Scipyがインストール済み
  • CUDA8がインストール済み
  • Xcode7がインストール済み

上述でインストールがまだのものがありましたら、下記の記事リストからインストールしてください。

開発環境

使用したMacのスペックは以下の通り。
Macbook Pro, 2014 Mid
MacOS Sierra 10.12.6
Core i7 2.8GHz/16GBメモリ
NVIDIA GeForce GT 750M 2GB GDDR5
Xcode 7.3.1 & Command Line Tools
(Xcode 8.xはCUDA8が対応していません)

DYLD_LIBRARY_PATHとPATHを設定

.bashrcに追加します。

export DYLD_LIBRARY_PATH=/usr/local/cuda/lib:/Developer/NVIDIA/CUDA-8.0/lib:$DYLD_LIBRARY_PATH
export PATH=/Developer/NVIDIA/CUDA-8.0/bin:$PATH

変更内容を反映。

# source .bashrc

常にNVIDIA GeForceを使用するように設定

Macbook Proには省電力のためにGPUが自動で切り替わるモデルがあります。常にNVIDIAGPUが使われるように設定を変更します。
[システム環境設定]-[省エネルギー]-[グラフィックスの自動切り替え]オフ

Xcode7に切り替え

CUDA8はXcode8に対応していません。Xcode7.x (執筆時Xcode7.3.1が最新)およびCommand Line Toolsが必要となります。Xcode8とXcode7をインストールしている場合は、xcode-selectコマンドでXcode7に切り替えます。

PyCUDAのインストール

# pip3 install pycuda

PyCUDAの確認

# python3 -c "import pycuda.autoinit

scikit-cuda (skcuda)をインストール

# pip3 install scikit-cuda

テストスクリプトで動作確認

import pycuda.autoinit
import pycuda.driver as cuda
from pycuda import gpuarray
from skcuda import linalg
import numpy as np

linalg.init()

a = np.random.rand(2, 1).astype(np.float32)
b = np.ones(2).astype(np.float32).reshape(1, 2)

a_gpu = gpuarray.to_gpu(a)
b_gpu = gpuarray.to_gpu(b)

c_gpu = linalg.dot(a_gpu, b_gpu)
c = c_gpu.get()

print(a)
print(b)
# 内積をCPUで計算した結果
print(np.dot(a, b))
# 内積をGPUで計算した結果
print(c)


上のスクリプトをtest_gpu.pyとして保存して実行します。

# python3 test_gpu.py
[[ 0.85600704]
 [ 0.02441464]]
[[ 1.  1.]]
[[ 0.85600704  0.85600704]
 [ 0.02441464  0.02441464]]
[[ 0.85600704  0.85600704]
 [ 0.02441464  0.02441464]]


テストスクリプトtest_gpu.pyを説明します。
PythonGPUを利用するための初期化処理等の準備をします。

import pycuda.autoinit
...
linalg.init()


GPUで処理する前に一旦Numpyのndarrayを作ります。データタイプはfloat32を使用します。

a = random.rand(2, 1).astype(np.float32)
b = np.ones(2).astype(np.float32).reshape(1, 2)


次にndarrayをgpuarrayに変換します。

a_gpu = gpuarray.to_gpu(a)
b_gpu = gpuarray.to_gpu(b)

この処理でCPU用のメモリからGPU用のメモリに配列データが変換・転送されます(CPU→GPU)。


Numpyのnp.dotに対応するGPU内積演算です。

c_gpu = linalg.dot(ga, gb)


結果を確認するにはgpuarray (GPU)からndarray (CPU)に戻す必要があります。

c = c_gpu.get()
print(c)

今度はGPU→CPUの流れです。

PyCUDAプログラミングの注意点

PyCUDAを利用したプログラミングでは、CPUとGPUを行き来するため、変数がndarrayかgpuarrayかを常に意識することが重要です。また、CPU/GPU間のデータ変換はパフォーマンスに大きな影響を与えるため、最小限に止めることが望ましいです。

便利な関数

gpuarrayとlinalgには下記のような便利な関数があります。

  • linalg.dot(x, y): 内積
  • linalg.transpose(x): 転置行列
  • gpuarray.max(x): 最大値
  • abs(x): 絶対値
  • x.shape: ndarrayのshapeと同じ
  • gpuarray.if_positive(x > z, x, z): zが0ならrelu

ただし、xをgpuarrayとします。

関連書籍

Python Parallel Programming Cookbook
洋書だがPyCUDAについて解説した数少ない書籍。PyCUDAについて約50ページが割かれている。

はじめてのCUDAプログラミング―驚異の開発環境[GPU+CUDA]を使いこなす! (I・O BOOKS)
CUDAの入門におすすめ。初心者にわかりやすいと定評がある。

CUDA C プロフェッショナル プログラミング (impress top gear)
CUDA中〜上級者におすすめ。マルチGPUについての解説あり。

【PyCUDA環境構築その3】 MacにCUDA8をインストール

注意 この記事はNVIDIAGPUを搭載していないMacbookは対象外です。

Macbook ProにCUDA8.0をインストールする手順を説明します。CUDAはNVIDIAが提供するGPGPUのためのライブラリ、コンパイルなどの統合開発環境です。要はNVIDIA製のGPUを使って並列計算するためのツール群です。

この記事は、PyCUDAでDeep LearningをするためのMac環境構築その3です。PyCUDAがCUDAを必要とするためCUDA8.0をインストールします。

開発環境

使用したMacbook Proのスペックは以下の通り。
Macbook Pro, 2014 Mid
MacOS Sierra 10.12.6
Core i7 2.8GHz/16GBメモリ
NVIDIA GeForce GT 750M 2GB GDDR5
Xcode 8.3.2 & Command Line Tools
Xcode 7.3.1 & Command Line Tools

CUDAをダウンロード及びインストール

CUDA Toolkit Download | NVIDIA DeveloperからOS、バージョンを選んでパッケージをダウンロードします。インストーラーの指示通りインストールします。

CUDAをアップデート

MacOSシステム環境設定からCUDAを選んでInstall CUDA Updateをクリック

cudnnをインストール

NVIDIA cuDNN | NVIDIA Developerからcudnnをダウンロードします。メンバー登録が必要な場合があります。

ダウンロードしたcudnn-8.0-osx-x64-v6.0.tgzを解凍します。

# tar zxvf cudnn-8.0-osx-x64-v6.0.tgz

ファイルをシステムにデプロイします。

# sudo mv <解凍フォルダ>/lib/libcudnn* /usr/local/cuda/lib
# sudo mv <解凍フォルダ>/include/cudnn.h /usr/local/cuda/include

DYLD_LIBRARY_PATHとPATHを設定

.bashrcに追加します。

export DYLD_LIBRARY_PATH=/usr/local/cuda/lib:/Developer/NVIDIA/CUDA-8.0/lib:$DYLD_LIBRARY_PATH
export PATH=/Developer/NVIDIA/CUDA-8.0/bin:$PATH

変更内容を反映。

# source .bashrc

常にNVIDIA GeForceを使用するように設定

Macbook Proには省電力のためにGPUが自動で切り替わるモデルがあります。常にNVIDIAGPUが使われるように設定を変更します。
[システム環境設定]-[省エネルギー]-[グラフィックスの自動切り替え]オフ

Xcode7に切り替え

CUDA8はXcode8に対応していません。Xcode7.x (執筆時Xcode7.3.1が最新)およびCommand Line Toolsが必要となります。Xcode8とXcode7をインストールしている場合は、xcode-selectコマンドでXcode7に切り替えます。わからない場合は、下のエントリーを参考にしてください。

[PyCUDA環境構築 その2] Xcode7とXcode8を共存させて、切り替える方法

CUDAサンプルプログラム

サンプルプログラムでインストールを確認できます。ただしコンパイルに時間が結構かかります。

# /Developer/NVIDIA/CUDA-8.0/bin/cuda-install-samples-8.0.sh ~/Downloads
# cd ~/Downloads
# make -C 1_Utilities/deviceQuery
# 1_Utilities/deviceQuery/deviceQuery
# make -C 5_Simulations/smokeParticles
# 5_Simulations/smokeParticles/smokeParticles

コンパイルエラーが出る場合は、Xcodeの切り替えDYLD_LIBRARY_PATHPATHの設定、省エネルギーの設定を疑ってみてください。

【PyCUDA環境構築その2】 Xcode7とXcode8をインストールして、切り替える方法

Xcode7.xとXcode8.xを共存させる方法を紹介します。

この記事は、PyCUDAでDeep LearningをするためのMac環境構築その2です。PyCUDAはCUDAに依存しています。CUDAはXcode8に対応しておらず、Xcode7をインストールする必要があります。

記事リスト

以下のリンクから各記事に飛ぶことができます。
[PyCUDA環境構築 その1] MacにPython3とNumpyをインストール
[PyCUDA環境構築 その2] この記事
[PyCUDA環境構築 その3] MacにCUDA8をインストール
[PyCUDA環境構築 その4] PyCUDAをMacOS 10.12 Sierraにインストール

開発環境

使用したMacのスペックは以下の通り。
Macbook Pro, 2014 Mid
MacOS Sierra 10.12.6
Core i7 2.8GHz/16GBメモリ
NVIDIA GeForce GT 750M 2GB GDDR5
Xcode 8.3.2 & Command Line Tools
Xcode 7.3.1 & Command Line Tools

Xcode 8.xのインストール

MacOSApp StoreからXcodeをインストールします。

ターミナルを開いて、

# sudo mv /Applications/Xcode.app /Applications/Xcode8.app

Xcode 7.xのインストール

記事執筆時ではXcode 7.3.1がXcode7では最新でした。Downloads for Apple Developer からSee more downloadsを選んで、検索ボックスに「Xcode 7」と入力します。Xcode 7.3.1Command Line Tools for Xcode 7.3.1をダウンロードします。

ダウンロードしたdmgファイルを開いてXcode 7.3.1をインストールします。続いてCommand Line Tools for Xcode7.3.1もインストールします。

ターミナルを開いて、

# sudo xcode-select -s /Applications/Xcode.app

次にCommand Line Toolsのdmgを開いてをインストールします。

Xcodeの切り替え

上述の手順通り進めていたら、Xcode7がアクティブになっています。Xcode8に切り替えるには、

# sudo mv /Applications/Xcode.app /Applications/Xcode7.app
# sudo mv /Applications/Xcode8.app /Applications/Xcode.app
# sudo xcode-select -s /Applications/Xcode.app

Xcode7に切り替えるには

# sudo mv /Applications/Xcode.app /Applications/Xcode8.app
# sudo mv /Applications/Xcode7.app /Applications/Xcode.app
# sudo xcode-select -s /Applications/Xcode.app