grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


ラベル 機械学習 の投稿を表示しています。 すべての投稿を表示
ラベル 機械学習 の投稿を表示しています。 すべての投稿を表示

2018年8月5日日曜日

tf.kerasでDCGAN(Deep Convolutional Generative Adversarial Networks)

前回VAE(Variational Autoencoder)を試して見たので、今回はDCGAN(Deep Convolutional Generative Adversarial Networks)をKerasで実装しつつ理解を深めたいと思います。使用データはMNISTです。 元々GANによる画像生成に興味があったのですが、約10ヶ月前にサンプルを試したときには、二つの敵対するネットワークによって画像生成するという大まかな流れしか理解できませんでした。
チュートリアルなどでは、
・Autoencoder(AE)
・Variational Autoencoder(VAE)
・Generative Adversarial Networks(GAN)
という順番で説明されていることが多く、VAE(潜在空間、ベイズ推定、KLダイバージェンスなど)を理解しないことにはGANを理解することも難しいかなと勝手に思っていましたが、そもそもAEとVAEも大きく異なるしGANもまた別のアルゴリズムという感じで、基本のAEが分かればGANを理解することはできそうです。
GANの派生型はいろいろありますが、とりあえず今回はDCGANを理解しようと思います。


上の画像はDCGANの構造で、左半分がGeneratorで右半分がDiscriminatorです。最終的にはGenerator層の右端(上画像中央:64x64x3)に画像が生成されます。
まずGeneratorで画像生成する前に、Discriminatorの左端から訓練画像を入力してDiscriminatorだけを教師あり学習します。その後、GeneratorとDiscriminatorを連結させたネットワークで教師あり学習させます。このときDiscriminatorの学習を停止させておいてGeneratorだけが学習するようにします。そうすると既に学習されているDiscriminatorを利用しながらGeneratorだけが学習し、その結果として画像が生成されます。この交互に学習させる手順がわかりにくいので難しく見えるのかもしれません。
GeneratorはAEやVAEのdecoder層だけで構成されている感じで、最初のノイズ画像はVAEで言う潜在空間と呼びますが、途中でReparameterization TrickやKLダイバージェンスなどの複雑な計算を使うこともないので、潜在空間というよりは単なるノイズ画像(np.random.normalで生成)と捉えたほうがよさそうです。


GANの訓練の特長:
先ほどの訓練手順についてですが、GANの訓練では、以下のようにそれぞれ別々に訓練させるようです。
・Discriminatorの本物画像識別の訓練(訓練画像を利用)
・Discriminatorの偽物画像識別の訓練(Generator生成画像を利用)
・Generatorの本物画像生成の訓練(Discriminator層も利用するが訓練を一時停止)

GANの説明では、Discriminatorは本物か偽物を見分けると書いてあり、Discriminatorに入力した画像が最終的に1か0に判定されるような層になっています。訓練用画像(本物)を入力した際にはラベルを1とし、ノイズ画像(偽物)を入力した際にはラベルを0として固定して(教師データとして)、それぞれを分けて学習させていきます。そうすることで、Discriminator層には本物/偽物を見分ける重み付けが徐々に形成されていきます。

一方Generatorでは、ノイズ画像を本物画像に近づくように訓練しなければいけないのですが、AEやVAEのように具体的な訓練画像を目指してdecodeしていくわけではないので、一体どうやって本物に近づけていくのだろうと疑問に思っていました。
最終的にはGeneratorのノイズ画像が、Discriminator層の最後の1次元の出力層で1(本物)になるようにGenerator層が学習していけばいいということになります。そのためには、Generator単独で訓練するのではなく、Discriminator層も連結してラベル(教師データ)を1に固定して訓練させます。画像を教師データにして訓練するのではなく、本物かどうかというラベルを教師データにして訓練する点がGANの特長だと思います(それでも画像生成は可能)。ただし、二つを連結させると両方とも訓練してしまうので、二つのうちGenarator層だけを訓練させるために、

discriminator.trainable=False

を挿入してDiscriminatorの訓練を一時停止しておく必要があります。
この部分に注意すれば、あとはそれほど難しいアルゴリズムが登場してくることはないかと。解説を読むと数式や抽象的な概念が出てきますが、アルゴリズム的に訓練の手順を理解すればそれほど難しいものではないような気がします。AEではモデル全体は真ん中がくびれていますが、GANの場合は始まりと終わりが細くて真ん中が太くなっているので(decoderとencoderを逆につなげたように)一見わかりにくいという印象です。しかしよくみれば、100次元のノイズを入力元として、decoder(Genarator)で28*28次元のMNIST画像に拡大し(生成画像)、またそれをencoder(Discriminator)で1次元まで落として、最後はsigmoidで0/1判定するという流れになっています。


DCGAN実装:

環境:
Ubuntu 18.04.1
GTX 1060
CUDA 9.0
Python 3.6
Tensorflow 1.9 (Keras 2.1.6)
Jupyter Notebook 5.6


まずはモジュールのインポート。今回もJupyter Notebookで。
import tensorflow as tf
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Input, Dense, Reshape, Flatten, BatchNormalization
from tensorflow.keras.layers import Conv2D, Conv2DTranspose, Activation, LeakyReLU
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

tf.logging.set_verbosity(tf.logging.ERROR)
警告がでるので、tf.logging.set_verbosity()で非表示にしています。
次に、各種変数とGenerator層。
img_rows = 28
img_cols = 28
channels = 1
img_shape = (img_rows, img_cols, channels)
latent_dim = 100

def generator_model():
    model = Sequential()
    model.add(Dense(1024, input_shape=(latent_dim,)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Dense(7*7*128))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Reshape((7,7,128)))
    model.add(Conv2DTranspose(64, kernel_size=5, strides=2, padding='same'))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Conv2DTranspose(1,kernel_size=5, strides=2, padding='same'))
    model.add(Activation('tanh'))
    
    return model
Generator層では、BatchNomarization、LeakyReLU、Conv2DTransposeを入れてみました。DCGANを安定させる方法としていろいろ工夫があるようですが、いくつか試したなかで今回はこの方法で。LeakyReLUのalpha値をデフォルトにするだけでも結果が変わってしまうので、このへんのパラメーターチューニングは難しそう。

次に、Discriminator層。
def discriminator_model():
    model = Sequential()
    model.add(Conv2D(32, kernel_size=5, strides=2,padding='same', input_shape=img_shape))
    model.add(LeakyReLU(alpha=0.01))
    model.add(Conv2D(16,kernel_size=5,strides=2, padding='same'))
    model.add(BatchNormalization())              
    model.add(LeakyReLU(alpha=0.01))
    model.add(Flatten())
    model.add(Dense(784))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    
    return model
こちらもLeakyReLU、BatchNomalizationを入れています。
Dropoutを入れて試してみましたが逆効果となってしまったので、今回はなし。

次は、GenaratorとDiscriminatorの連結層。
def combined_model():
    discriminator.trainable = False
    model = Sequential([generator, discriminator])
    
    return model
Generatorを訓練する際にこの連結層を使用します。そのため事前に、discriminator.trainable=Falseにしておきます。こうすることでGeneratorだけの訓練になります。

まずは、MNISTデータの読み込みと正規化(-1〜1)。そして、Discriminator、Generator、Combined(G + D)モデルの定義。Adamで最適化。
(x_train, _), (_, _) = mnist.load_data()
x_train = (x_train.astype('float32') - 127.5) / 127.5
x_train = x_train.reshape(-1, 28, 28, 1)

# Discriminator Model
discriminator = discriminator_model()
discriminator.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5), metrics=['accuracy'])

# Generator Model
generator = generator_model()

# Combined(G + D) Model
combined = combined_model()
combined.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.00015, beta_1=0.5))

そして訓練ループ。
まずDiscriminatorの訓練をリアル画像とフェイク画像に分けて行います。
訓練はfitではなくtrain_on_batchでバッチごとに行うといいようです。その際にDiscriminatorの場合は、フェイク:0とリアル:1の二つのラベルを教師データとして与えておき、それぞれを別々に訓練し、最後にそのロスを合算しておきます。
次のGeneratorの訓練では、教師データをリアル:1として与えておき、Discriminatorの訓練を一時停止した状態で連結したcombinedモデルを訓練させます。そうすると出力がリアル:1になるようにGeneratorの重み付けが形成されます。この部分がGAN特有の訓練のさせ方だと思います。
batch_size = 32
real = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))

epochs = 10000
Loss_D = []
Loss_G = []

import time
start = time.time()

for epoch in range(epochs):
    
    # shuffle batch data
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    imgs = x_train[idx]
    
    # Train Discriminator
    # sample noise images to generator
    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    gen_imgs = generator.predict(noise)

    # train discriminator real and fake
    d_loss_real = discriminator.train_on_batch(imgs, real)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train Generator
    g_loss = combined.train_on_batch(noise, real)
 
    Loss_D.append(d_loss[0])
    Loss_G.append(g_loss)

    if epoch % 100 == 0:
        print("%04d [D loss: %f, acc.: %.2f%%] [G loss: %f] %.2f sec" % (epoch, d_loss[0], 100*d_loss[1], g_loss, time.time()-start))

    if epoch == epochs - 1:
        r, c = 5, 5
        noise = np.random.normal(0, 1, (r * c, latent_dim))
        gen_imgs = generator.predict(noise)
        gen_imgs = 0.5 * gen_imgs + 0.5

        fig, axs = plt.subplots(r, c)
        cnt = 0
        for i in range(r):
            for j in range(c):
                axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
                axs[i,j].axis('off')
                cnt += 1
        plt.show() 
Discriminatorの訓練(2種類別々で訓練し最後に合算):
d_loss_real=ノイズ-->Generator-->Discriminator-->realラベル(教師データ)
d_loss_fake=ノイズ-->Generator-->Discriminator-->fakeラベル(教師データ)
Discriminator_loss=0.5*(d_loss_real+d_loss_fake)

Generatorの訓練:
g_loss=ノイズ-->Generator-->Discriminator(訓練停止)-->realラベル(教師データ)
という手順でそれぞれを訓練しています。
教師データとなるreal/fakeラベルはbatch_size分用意しておき、train_on_batch()に代入します。

合計10000エポック回して、100エポックごとに各Lossを表示。最後に最終画像を表示。

生成画像結果:
生成画像(10000エポック)。

途中の画像も見てみましたが、5000エポックくらいでもそこそこ識別できるレベルにはなりましたが、10000エポックくらい回したほうがよさそうです(GTX1060で約6分、Macだと1時間はかかりそう)。モード崩壊(似たような画像ばかりになる現象)は発生していないようです。

生成画像(5000エポック)。やや不明瞭??

生成画像(2500エポック)。

生成画像(2000エポック)。このあたりだとやはり不鮮明。


以下のコードでLossを表示。
plt.plot(np.arange(epochs), Loss_D, 'r-')
plt.plot(np.arange(epochs), Loss_G, 'b-')

赤:Discriminator Loss、青:Generator Loss
これをみてもよくわからない。3000エポック以降はあまりかわっていないようなので5000エポックくらいの訓練でもいいのかもしれない。


まとめ:
DCGANは思っていたよりもシンプルな構造で、GeneratorとDiscriminatorをつくれば、あとはそれぞれの訓練の手順を間違わないようにコーディングしていけばいいという感じです。どちらかというとVAEのほうが難しかったという印象です。
ただしDCGANで難しいのは、GeneratorとDiscriminatorの中身の層をどうするか?ということかもしれません。ここを参考にすると、LeakyReLUやBatchNormを使った方がいいらしいのですが、層の順番やパラメータが少し違うだけでも生成画像がノイズのままで終わってしまうので、安定的に画像生成させるにはいろいろ試してみる必要がありそうです。GANの派生型はたくさんあるので、DCGAN以外のGANも試して比較してみたほうがよさそうです。


参考にしたサイト:
https://towardsdatascience.com/having-fun-with-deep-convolutional-gans-f4f8393686ed
https://elix-tech.github.io/ja/2017/02/06/gan.html
https://qiita.com/triwave33/items/1890ccc71fab6cbca87e
https://qiita.com/t-ae/items/236457c29ba85a7579d5


直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ
Posted with Amakuri at 2018.12.21
Antonio Gulli, Sujit Pal
オライリージャパン
販売価格 ¥3,672

2018年7月25日水曜日

tf.kerasでVAE(Variational Autoencoder)

Tensorflowもあっというまに1.9までバージョンアップしており、トップページが日本語表示になっていました。Get started with Tensorflowという最初のチュートリアルも変わったようで、Keras、Eager、EstimatorがHigh Level APIとして前面にでてきています。Pytorchも試していましたが、Tensorflowがますます便利になっていくのでTensorflowに戻りつつあります。書きやすくなったEagerやtf.layersも試してみましたが、結局Kerasがシンプルでわかりやすいという結論に達し、Keras自体もバージョンアップしたようなのでTensorflowというよりもKerasでVAEを試してみようかと。

VAEは中間層で突然正規分布が登場して、ベイズ的な手法で画像生成していくアルゴリズムが興味深く、固定値を確率に変換して表現するという部分がずっと気になっていました(最初に試したのは約10ヶ月前)。
潜在空間、ベイズ推定、Reparameterization trick、KL-divergenceなど、画像生成に通じるテクニックを勉強するにはちょうどいいサンプルだと思います(かなり難しいですが)。

TensorflowでKerasをインポートする際に、以前はtensorflow.python.kerasだったけど、Tensorflow 1.9からは、tensorflow.kerasで使えるようになったようです。

Kerasの書き方:
Kerasの場合いくつか書き方があり、
Sequential()の中に各層をそのまま並べて行く方法。
model = Sequential([
    Dense(32, input_shape=(784,)),
    Activation('relu'),
    Dense(10),
    Activation('softmax'),
])
Sequential()でモデルを定義してから各層をaddで追加していく方法。
model = Sequential()
model.add(Dense(32, input_shape=(784,))
model.add(Activation('relu'))
model.add(Dense(10))
model.add(Activation('softmax'))
これらの方法はSequentialモデルと呼ばれ、各層をそのまま重ねていけばいいのでわかりやすい。

このほか、functional APIというモデルがあり、各層に変数をつけて行末の()に前の層を代入し、最後にモデルを定義する方法。
inputs = Inputs(shape=(784,))
layer1 = Dense(32, activation='relu')(inputs)
outputs = Dense(10, activation='softmax')(layer1)

model = Model(inputs,outputs)
行末の()なしで各層を連結させないで書くには以下。あとから連結式(代入式)を書いて、先ほどの結果(Model)と同じになります。
inputs = Inputs(shape=(784,))
l1 = Dense(32, activation='relu')
l2 = Dense(10, activation='softmax')

layer1 = l1(inputs)
outputs = l2(layer1)

model = Model(inputs,outputs)
Sequentialモデルのほうがすっきりしてわかりやすいけれども、VAEの場合だと少し複雑になるので、今回はfunctional APIで各層を別々に書いていくタイプを使います。
そのほか、これらModelクラスをサブクラス化する書き方もあるけれど、一行ずつ順を追ってベタに書いていったほうが理解しやすいので、今回はサブクラス化せずにJupyter Notebookに書いていこうと思います。

VAEの実装:

環境:
Ubuntu 18.04
GTX 1060
CUDA 9.0
python 3.6
tensorflow 1.9
Jupyter Notebook 5.6


まずは各モジュールのインポート。
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras import losses, backend as K
from tensorflow.keras.layers import Dense, Input, Lambda
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

そして、今回使用するmnistデータセットの読み込みと正規化、28x28の画像を784の1次元へ平坦化。
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
このへんはサンプルなどでもお馴染みの方法。

Encoderと潜在変数z:
そして、encoder層。
# encoder
inputs = Input(shape=(784,))
encoder_h = Dense(256, activation='relu')(inputs)
z_mu = Dense(2, activation='linear')(encoder_h)
z_log_sigma = Dense(2, activation='linear')(encoder_h)
encoderは、784次元に平坦化された画像を入力とし、reluを通して256次元に変換、その後さらに2次元へ変換し正規分布のパラメータとなる平均muと分散logΣに分けておきます。分散をΣではなくlogΣにしているのは、encoderからの出力が負の場合もあるため、Σ=σ2が常に正であるのに対し、logをつけることで負の値であっても成立するようにしているらしい(論文p11でもlogσ2と書いてある)。
要は、計算から求められる固定値を正規分布という確率分布に置換してから演算することで画像生成を可能にしているようです。

平均と分散をもとに正規分布から値を取り出すには通常サンプリング(ある確率に従ってランダムに値を取り出す)が必要となり、数式では以下のようにあらわします。

z~N(μ,Σ)

このサンプリング式をnumpyであらわすと、

z=np.random.normal(loc=μ, scale=Σ, size=1)

になりzを求めることは可能ですが、サンプリングすると後々バックプロパゲーションが不可能(微分不可能)となるため、Reparameterization trickという代替演算法をつかうようで、

z=μ+Σ0.5

に置き換えて(平均値μに誤差εを掛け合わせた分散Σを足し合わせるという感じ)、
上の式中のΣをlogΣに変換するには、

Σ=exp(logΣ)

であるから、最終的には、

z=μ+exp(0.5*logΣ)*ε

という式になるようです。この部分が以下のdef sampling()の内容です。

def sampling(args):
    z_mu, z_log_sigma = args
    epsilon = K.random_normal(shape=(K.shape(z_mu)[0], K.int_shape(z_mu)[1]))
    return z_mu + K.exp(0.5 * z_log_sigma) * epsilon

z = Lambda(sampling)([z_mu, z_log_sigma])

# encoder model
encoder = Model(inputs, [z_mu, z_log_sigma, z])
encoder.summary()
潜在変数zを求めるLambda(keras.layers.Lambdaクラス)の部分はKerasのモデルの一部に組み込むために必要で、そのままsampling()関数からの戻り値を受け取るだけだと、モデルの一部としてバックプロパゲーションなどしてくれなくなるようです。
最後にsummary()でこのモデルの各層を確認できます(以下)。
_______________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 784)          0                                            
__________________________________________________________________________________________________
dense (Dense)                   (None, 256)          200960      input_1[0][0]                    
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 2)            514         dense[0][0]                      
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 2)            514         dense[0][0]                      
__________________________________________________________________________________________________
lambda (Lambda)                 (None, 2)            0           dense_1[0][0]                    
                                                                 dense_2[0][0]                    
==================================================================================================
Total params: 201,988
Trainable params: 201,988
Non-trainable params: 
先程のLambdaの計算部分もモデルに組み込まれているのがわかります。最初pythonのlambda式と勘違いしており意味がわかりませんでしたが、これはKerasレイヤーのLambdaということです。

Decoder:
そして残りのdecoder層。decoder層は訓練用と画像生成用の2種類のモデルをつくっておきます。これは訓練用の方です。
# decoder
d_h = Dense(256, activation='relu')
d_out = Dense(784, activation='sigmoid')

decoder_h = d_h(z)
outputs = d_out(decoder_h)

# vae: encoder + decoder
vae = Model(inputs, outputs)
vae.summary()
画像生成時にもこのレイヤーを使い回すので、それぞれのレイヤーごとに分けて書いておき、次の行でzと隠れ層を代入します。そして、encoderの入力からdecoderの出力までを足し合わせてvaeモデル(訓練用)をつくります。vae.summary()で先程と同様にモデルの各層を確認します(以下)。

Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 784)          0                                            
__________________________________________________________________________________________________
dense (Dense)                   (None, 256)          200960      input_1[0][0]                    
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 2)            514         dense[0][0]                      
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 2)            514         dense[0][0]                      
__________________________________________________________________________________________________
lambda (Lambda)                 (None, 2)            0           dense_1[0][0]                    
                                                                 dense_2[0][0]                    
__________________________________________________________________________________________________
dense_3 (Dense)                 (None, 256)          768         lambda[0][0]                     
__________________________________________________________________________________________________
dense_4 (Dense)                 (None, 784)          201488      dense_3[0][0]                    
==================================================================================================
Total params: 404,244
Trainable params: 404,244
Non-trainable params: 0
入力784次元、256次元、2次元(z_mu, z_log_sigma, z)、256次元、784次元という各層があることがわかります。基本的にはz_muとz_log_sigmaの二つだけでいいのですが、比較もしたいためにzも組み込んでおきました。

Generator:
上記のvaeモデル(encoder+decoder)でz値を通して訓練用画像(x_train)で学習しますが、訓練後はz_muとテスト画像(x_test)を用いてpredict(予測/画像生成)します。
その画像生成する際のgeneratorのコードが以下。
# generator
generator_in = Input(shape=(2,))
generator_h = d_h(generator_in)
generator_out = d_out(generator_h)

generator = Model(generator_in, generator_out)
generator.summary()
後々使うのですが、とりあえす先につくっておきます。

Loss function:
つぎは、ロスの計算です。この部分はVAE特有の難しいアルゴリズムで、Reconstruction lossの最大化とKL-divergence loss最小化を組み合わせることになりますが論文や解説などを参考にするしかないと言う感じ。KL-divergenceは二つの分布の比較の値を計算してくれるようです。差が少ないほど0に近づくので最小化していくには便利。

ネットで探してみるとKerasのバージョンによっても違いがあるのか計算方法や関数が微妙に異なっており、いろいろ試した結果この方法に(参考はここ)。recon内で784を掛けていますがK.sum()でもいいのかもしれません。

def vae_loss(inputs, outputs):
    recon = 784 * losses.binary_crossentropy(inputs, outputs)
    kl = - 0.5 * K.sum(1 + z_log_sigma - K.square(z_mu) - K.exp(z_log_sigma), axis=-1)
    return K.mean(recon + kl)

vae.compile(optimizer='adam', loss=vae_loss)

epochs = 10
vaefit = vae.fit(x_train, x_train, 
                 shuffle=True,
                 epochs=epochs,
                 batch_size=64,
                 validation_data=(x_test, x_test),
                 callbacks=[])
今回はadamで最適化してみました。vae.fit()内のcallbacks=[]を加えることで訓練中のロス値を呼び出すことができるようで、それを利用してグラフを描くことができるようです。Tensorboardも利用できるようですが、今回はmatplotlibで。
# plot loss
loss = vaefit.history['loss']
val_loss = vaefit.history['val_loss']

plt.plot(range(1,epochs), loss[1:], marker='.', label='loss')
plt.plot(range(1,epochs), val_loss[1:], marker='.', label='val_loss')
plt.legend(loc='best', fontsize=10)
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
このコードを書き加えると以下のグラフが描けます。
100エポック回したときのロスの変化です。まだ下がりそうですが、100エポックでやめてしまいました。GTX1060で1エポック3秒前後(batch_size=64)。
隠れ層のユニット数やbatch_sizeを調整したほうがいいのかもしれませんが続行。

視覚化:
つぎは、結果の出力。
hidden_imgs = encoder.predict(x_test)
model_imgs = generator.predict(hidden_imgs[0])
vae_imgs = vae.predict(x_test)

s = 0
n = 10
plt.figure(figsize=(10, 3.1))
plt.subplots_adjust(wspace=0, hspace=0)

for i in range(n):
    #original
    ax = plt.subplot(3, n, i + 1)
    plt.imshow(x_test[i+s].reshape(28, 28))
    plt.axis('off')
    plt.gray()

    #reconstruction
    ax = plt.subplot(3, n, i + 1 + n)
    plt.imshow(model_imgs[i+s].reshape(28, 28))
    plt.axis('off')
    
    #vae model
    ax = plt.subplot(3, n, i + 1 + n + n)
    plt.imshow(vae_imgs[i+s].reshape(28, 28))
    plt.axis('off')
    
plt.show()
最初にencoder層をpredictし、その結果(hidden_imgs[0]はz_muによる出力)をgenarator層(生成用モデル)に渡して画像を得ています。同様にvae(訓練用モデル)も使って画像生成してみました(こちらはz経由での出力)。
結果の画像。オリジナル、encoder/z_mu/generator生成画像、vaeモデル:encoder/z/decoder生成画像。
4と9のような画像が多いので、まだ改良の余地がありそうです。

そして、2次元の潜在空間(z_mu)での各数字の分布。二つの値がそれぞれ横軸と縦軸に割り当てられそれを座標上に表したものです。
plt.figure(figsize=(10,10))
plt.scatter(hidden_imgs[0][:,0] ,hidden_imgs[0][:,1], marker='.', c=y_test, cmap=plt.get_cmap('tab10'))
plt.colorbar()
plt.grid()
cmapでtab10を用いることで10段階で色分けしています。結果の画像は以下。
これを見ると数字の5(茶色)が、かろうじてy=0より少し上に横に細長く並んでいるのがわかります。0、1、3、7は、領域がはっきり分かれているため認識しやすそうですが、それ以外は中央に重なるように集中しているので識別しにくそうです。

さらに、この分布をグリッド状の画像に置き換えるコード。
n = 20
digit_size = 28
figure = np.zeros((digit_size * n, digit_size * n))
grid_x = np.linspace(-2, 2, n)
grid_y = np.linspace(-2, 2, n)[::-1]

for i, yi in enumerate(grid_y):
    for j, xi in enumerate(grid_x):
        z_sample = np.array([[xi, yi]])
        x_decoded = generator.predict(z_sample)
        digit = x_decoded[0].reshape(digit_size, digit_size)
        figure[i * digit_size: (i + 1) * digit_size, j * digit_size: (j + 1) * digit_size] = digit

plt.figure(figsize=(10, 10))
start_range = digit_size // 2
end_range = n * digit_size + start_range + 1
pixel_range = np.arange(start_range, end_range, digit_size)
sample_range_x = np.round(grid_x, 1)
sample_range_y = np.round(grid_y, 1)
plt.xticks(pixel_range, sample_range_x)
plt.yticks(pixel_range, sample_range_y)
plt.xlabel("z [0]")
plt.ylabel("z [1]")
#plt.imshow(figure, cmap='gnuplot')
plt.imshow(figure, cmap='Greys_r')
plt.show()
先程の分布のグラフはx:-4〜4、y:-4〜4の範囲ですが、このコード内の4、5行目のgrid_xとgrid_yのnp.linspaceの範囲を-2〜2に変えることで、その範囲での数の分布を見ることができます。以下がその結果。
これは分布グラフの範囲をx:-2,2、y:-2,2に限定して出力したものです。先程のドットの分布で5が水平に細長く分布していたように、この画像においても中央右寄りに細長く水平に分布しています。一応一通り0〜9が存在していますが、分布領域が広範囲な数と狭い範囲にしかない数があるのがわかります。
ただ、このような結果から1と3と5の中間に8が位置していたりと、その特性を利用して面白い画像生成ができそうです。

まとめ:
VAEは以前Tensorflowのサンプルを試しましたが、単なるAutoencoderに比べると潜在変数やReparameterization trick、さらにはロス関数の部分の理解が難しいという印象でした。今回あらためてKerasで書いてみると、Kerasのシンプルな構造のおかげか、かなり理解が深まりました。特に最後の2つの分布的なグラフについてはどう表示するのかと思っていましたが、どこをいじればどうなるかが分かりました。
通常のAutoencoderの場合なら入力から出力までそのまま層を重ねて行けばいいのですが、VAEの場合だと中間層で正規分布からサンプリングするため、そのままだと訓練時にバックプロパゲーションができなくなってしまうことからReparameterization trickで微分計算可能な経路につくりかえます。訓練後はReparameterization trickは必要ないので、encoderからそのまま分布の中心位置となるz_mu経由でgeneratorを通り出力するということになっています。

訓練時(x_train):
encoder
z_mu, z_log_sigma
z(Reparameterization trick)
decoder

訓練後(x_test):
encoder
z_mu
generator


参考にしたサイト:
https://qiita.com/kenchin110100/items/7ceb5b8e8b21c551d69a
https://wiseodd.github.io/techblog/2016/12/10/variational-autoencoder/
https://www.kaggle.com/rvislaywade/visualizing-mnist-using-a-variational-autoencoder
https://blog.csdn.net/A_a_ron/article/details/79004163

関連:
tf.kerasでDCGAN(Deep Convolutional Generative Adversarial Networks)



直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ
Posted with Amakuri at 2018.12.21
Antonio Gulli, Sujit Pal
オライリージャパン
販売価格 ¥3,672

2018年5月2日水曜日

Bayesian Optimization / Gaussian Process

前回、matplotlibのアニメーションについては大体使えるようになったので、「ベイズ最適化・ガウス過程」を実験してみました。いろいろと専用のライブラリがあるのですが(GPy、GPFlow、Edward、Pyroなど)、今回はscikit-learn Gaussian Processesを使っています。

ベイズ最適化はハイパーパラメータチューニングに使われるようで、ブラックボックス関数における最大値(あるいは最小値)を全領域をスキャンしなくても探し出すことができる方法のようです。

今回の場合は、グラフ内の赤破線の最大値がどこにあるか探索しています。赤破線を見ると、左と右にピークが二箇所ありますが、微妙に右側のほうが高いという設定にしてあります。それを間違えないように探し出せるかという実験です。
右側の赤丸が正解(最大値)の地点です。この地点は、x座標-2〜2までの範囲を0.1刻みで200回スキャンし、その結果から最大値を求めています。
しかし、ベイズ最適化の場合は、200回スキャンしなくても約10回程度で、どこが最大値なのかを効率よく探してくれるようです。

今回の手順として:
・カーネル:Matern、獲得関数:UCBを選択
・xの範囲(-2〜2)からランダムにまず2点(青丸:start印)選んで初期観測値とする
・その観測値から、全域のμ(青破線)とσ(青塗)を求める
・μとσを獲得関数(UCB)に代入
・獲得関数の結果(緑実線)から最大値の箇所を見つける
・獲得関数の最大値から、それに相当するxの位置を見つける
・xの位置から、それに相当するyの位置を求める
・ここで求めたxとyの座標を次回の探索箇所にする
・次回探索箇所を観測値リストに加える
・あとは繰り返し

グラフ下部(緑実線)にあるのが獲得関数UCBの値です。緑実線の一番高いところが、次の探索地のx座標となります。
今回使用したUCBの中身は単純で、

UCB最大値 = mu + kappa * sigma

となっています。係数kappa=1.0の場合は、μ値(青破線)にσ値(青塗)を足した値がそのままUCB最大値となるので、グラフ内の青塗を含めた一番高い箇所が次の探索箇所となります。μ値が高いところほど選ばれがちですが、kappaを大きくするとσ値も大きくなるので、μ値が低い箇所でも探索される可能性があがります。


%matplotlib inline
import numpy as np
from matplotlib import pyplot as plt
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern
import matplotlib.animation as animation
from IPython.display import HTML

np.random.seed(111)

def f(x):
    return -np.sin(x*4.5)*x*0.3+0.02*x                  # 真の関数(赤破線表示)

x = np.linspace(-2,2,200)                               # x値範囲
y = f(x)                                                # 真のy値

fig = plt.figure(figsize=(12,6))                        # 画面サイズ設定
ax = plt.subplot()                                      # サブプロットを用意
ax.axis([x.min(),x.max(),y.min()*2,y.max()*2])          # グラフ範囲設定(x,yの値に依存)
ax.set_aspect(1.0)                                      # アスペクト比設定

ax.plot(x,y,'r--',alpha=0.8)                            # 真の関数曲線をプロット(赤破線)
ax.plot(x[np.argmax(y)],y.max(), 'ro',alpha=0.8)        # y正解値;x全域スキャンによるy最大値(赤丸)

init_pt = 2                                             # 初期観測値個数
xi = np.random.uniform(x.min(),x.max(),init_pt)         # 初期観測値:xの範囲から2箇所ランダム選択
yi = f(xi)                                              # 初期観測値:yの値
ax.plot(xi,yi,'bo')                                     # 初期観測値プロット(青丸)
for i in range(len(xi)):
    ax.text(xi[i],yi[i],'  start'+str(i),color='b')     # 初期観測値:'start'文字表示

def UCB(mu_x, sigma_x, kappa=1.0):                      # 今回使用する獲得関数:upper_confidence_bound
    return mu_x + kappa * sigma_x                       # kappa増大-->σ増大-->探索領域拡大

kernel = Matern(length_scale_bounds="fixed")            # 今回使用するカーネル:Matern
gp = GaussianProcessRegressor(kernel=kernel,alpha=0.01) # scikit-learnガウス過程回帰オブジェクト

ims = []                                                # アニメ用リスト
itr = 15                                                # 探索ループ回数

for i in range(itr):
    gp.fit(xi[:,None],f(xi))                                 # 観測値フィッティング(xiをリシェイプして代入)
    mu_x, sigma_x = gp.predict(x[:, None], return_std=True)  # μ(x)とσ(x)を獲得(事後分布)

    a_x = UCB(mu_x, sigma_x, kappa=2.0)                      # UCBでa(x)最大値を得る
    index = np.argmax(a_x)                                   # a(x)最大値のindexを得る
    x_next = x[index]                                        # a(x)最大値のindexから次のx探索値を決定
    y_next = f(x_next)                                       # y探索値決定
    
    P1 = ax.plot(x, mu_x, 'b--')                                               # μ値(青破線)
    P2 = ax.fill_between(x, mu_x-sigma_x, mu_x+sigma_x, color='b', alpha=0.2)  # σの範囲(青塗)
    P3 = ax.plot(xi,yi,'bx',markersize=8)                                      # 探索済み箇所(青クロス)
    P4 = ax.plot(x_next, y_next, 'mo')                                         # 現在探索箇所(マジェンタ丸)
    P5 = ax.plot([x_next,x_next],[y.min()*2, y_next] ,'m-', alpha=0.6)         # 現在探索箇所(マジェンタ縦線)
    P6 = ax.plot(x_next,(a_x[index]-a_x.min())/2+y.min()*2, 'go',alpha=0.8)    # a(x)最大値:σ(青丸)
    P7 = ax.plot(x, (a_x-a_x.min())/2+y.min()*2, 'g-')                         # 獲得関数値:a(x)
    P8 = ax.plot([xi,xi],[[y.min()*2]*len(yi),yi], 'b-', alpha=0.2)            # 探索済み箇所(青縦線)
       
    xi = np.r_[xi,x_next]                                  # 現在探索箇所をリストに加える(r_: concatenateの代用)
    yi = np.r_[yi,y_next]
    
    ims.append(P1 + [P2] + P3 + P4 + P5 + P6 + P7 + P8)    # アニメ用リストへ各プロットを入れる
    
print('x :',x[y.argmax()], '   y :',y.max())               # 正解値出力(y値)
print('xi:',xi[yi.argmax()], '   yi:',yi.max())            # 最終最大値出力(yi値)

ani = animation.ArtistAnimation(fig, ims, interval=1000)   # アニメ関数
#ani.save("gp_sim.gif", writer = "imagemagick")            # GIFアニメ保存
plt.close()
HTML(ani.to_html5_video())                                 # HTML5 Video 出力(mp4ダウンロード可)
#HTML(ani.to_jshtml())                                     # JavascriptHTML 出力

GaussianProcessRegressor()にはカーネルを指定する箇所があり、今回は、scikit-learnのMaternを使っています。ちなみにカーネルを指定せずデフォルトのままだと、以下のような感じになります。
σ(青塗)の領域が結構変わります。
scikit-learnのこのページにカーネルの違いについて書いてあります。

ハイパーパラメータチューニングと言っても、カーネルの選び方でもかなり違いがでてきます。
また、獲得関数もUCB以外にいくつかあるので、それも選ばなければいけないという感じです。


参考にしたサイト:
Scikit-learn Gaussian Processes
llSourcell/hyperparameter_optimization_strategies
ベイズ最適化入門
機械学習のハイパーパラメータ探索 : ベイズ最適化の活用

関連:
GPyOpt: Digital Recognizer(MNIST) CNN Keras ハイパーパラメータ最適化

2018年4月9日月曜日

Tensorflow 1.7へバージョンアップ

Tensorflowは少し前に1.6へバージョンアップしていたにもかかわらず、pytorchを使っていたため1.5のままでした。しかし1.6のあとすぐに1.7へバージョンアップしており、eager executionが改良された模様。



TensorflowはさすがGoogleだけあって開発スピードがはやいのか、内容に追いついていくのが大変。
Tensorflowといえば、

tf.placeholder()
tf.Variable()
tf.Session()

でコーディングしていくイメージでしたが、より使いやすくするために

tflearn : easy to learn, high level API
slim : a lightweight library for defining, training and evaluating models
estimator : a high-level TensorFlow API that simplifies ML programming
keras : a higher API wrapper
eager : define by run
lite : mobile, RPi

などが次々と導入されたため、どれがいいのかわかりにくくなってきたという感じ。
kerasが使えるようになってからは、シンプルなコーディングが可能になったけれども、pytorchのようなdefine by runタイプが便利になってきたこともあって、Tensorflowもeagerを導入してdefine by runも普通に使えるようになった感じ。

なんでもありとなってきたTensorflowという感じで、やはり機械学習フレームワークとしてはTensorflowがなんだかんだいって便利なのかもしれません。
ということで、使い勝手が改良されたTensorflowも再度使おうと、1.5から一気に1.7へバージョンアップしてみました。

GPUを使用しているので、

pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0-cp35-cp35m-linux_x86_64.whl

のpipコマンドでAnaconda仮想環境内にインストールしてあったtensorflow-gpu-1.5.0をアップグレード。
eager executionが便利そうなので、少し慣れておこうかと。

最近は、ベイズ推定などの確率的プログラミングに興味があって、

GPy
Pyro
Edward

などを勉強していまずがTensorflowにも、

Tensorflow Probability

というライブラリができたようで、Tensorflowだけであらゆることが可能となってきた感じ。



2018年1月31日水曜日

Bayesian Deep Learningの図解

事前確率や事後確率、そして確率分布を用いるBayesian Deep Learningは文章で説明されると理解しにくく、以前Variational Autoencoderを学んだとき(確率分布が登場してくる)もイメージとして把握するのが困難でした。一行ずつコーディングしていくことで、なんとかその仕組み/手順はわかったものの、GANなどのように構造が複雑になっていくとイメージが追いつかず、また分かりにくくなっていました。
そこで、以下の動画を見たら非常に分かりやすかったので、参考としてメモ。


ここでは確率分布を使わない通常の方法と確率分布を使う方法とを図で比較しながら説明してくれます。動画途中にでてくるスライド(イメージ)がわかりやすかったです。

線型回帰の場合、

X*w+b=Y

X:データ
w:ウェイト
b:バイアス
Y:予測値
 通常(非ベイズ)は、上図のそれぞれのマスに固有の数値が入りますが、

ベイズ推定を用いると(上図)、Xはデータなのでそれぞれ固有の数値ですが、w、bには正規分布などの確率分布が入り、その結果出力されるYも確率分布となります。
単に、固有の値であったwとbを正規分布に置き換えればいいというわけです。


Logistic Regressionの場合であっても、

ベイジアンでは以下のようになるだけ。
たしかに分かりやすい図解となっています。
さらに、多層のDeep Learningであったとしても、


という仕組みが、ベイジアンでは以下のようになるだけ。



Deep Learningを勉強していくと、ただですら複雑なマトリクス計算や数多くの層を計算しなければいけないので、コーディングする手順や仕組みを理解するにも一苦労です。さらには、確率分布をつかった式も途中に出てくるので、なおさら仕組みが難解になりがちですが、このようなイメージをつかむことができると理解しやすくなります。

以下の本も読んでベイズ推定を勉強していますが、文章や式だけでの表現だと頭の中にイメージするのが徐々に困難になってきて、理解するにはどうしても時間がかかっていました。やはり、イメージしやすい図解があると頭の中にも入ってきやすいという感じです。


また、Udemyには「ベイズ推定とグラフィカルモデル」という無料のコースもあります。動画内に登場する図解がわかりやすいかもしれません。覚えなければいけない式などかなりありますが、初心者にとっては難しい専門書で勉強するよりはいいかもしれません。
Youtubeでも動画は公開されており、日本語なので試しに見てみてもいいかもしれません。

2017年12月6日水曜日

Pytorch CUDA 9.0

ようやく正式にCUDA9.0のPytorch0.3.0がリリースされたようです。前回、慌ててソース版をコンパイルする必要もなかったようです。早速入れ替えてみようと思います。環境:Ubuntu16.10、GTX1060、CUDA9.0、cuDNN7.0。

無難にPython3.5で。pyenv仮想環境内にさらにAnaconda仮想環境をつくり、py359というAnaconda仮想環境内にインストールしています。ちなみに現在はpy27(python2.7、cuda8.0)とpy35(python3.5、cuda8.0)という仮想環境も別にインストールしてあります。

インストール後、Pythonを起動し、
import torch
torch.version.cuda
で、CUDAのバージョン確認、'9.0.176'になっています。
torch.backends.cudnn.version()
で、cuDNNバージョン確認、7004になっています。

いつものPytorch/examples/dcgan/cifar10を動かしてみると(1イテレーション=784回)、
1:15.80=75.8秒くらい。このサンプルでCUDA8.0と比較すると2秒くらい速くなったかもしれません。

2017年11月25日土曜日

Deep Learning用ノートパソコン(GPU:GTX1060)のエディタと実行環境

Deep Learning用ゲーミングノートパソコンの設定は一段落したのですが、使っているうちに幾つか気になる点がでてきたので少しだけ以下の環境を見直してみました。

・Python用エディタの設定
・Deep Learning用ライブラリ、プラットフォームの選定
・デュアルブートパーティションによるUbuntuへの割当量

まずはエディタに関してです。

エディタ:Pycharm/Spyder/VS Code/Atom
Pycharmが使いやすいという評判から試してみましたが、案外Anacondaに同包されているSpyder IDEというエディタのほうが使いやすかったです。しかしどちらもややカスタマイズしにくい。それでは人気のあるVS Codeはどうかというと、拡張性もありカスタマイズできそうだし、なにより動きが軽快。ということで、VS Codeで環境を整えていましたが慣れないこともあり、いまいち上手くいかない。それで、いままで使っていたAtomに戻って設定し直してみると、望んでいるような使い方ができるとわかり、結局のところAtomを使うことに。AtomはVS Codeに比べてやや重い感じもしますが、パッケージの豊富さとかなりのカスタマイズ自由度があって使いやすい。
Spyder IDE: 
Anaconda Navigatorからすぐにアップロードでき、比較的使いやすい印象。ただカスタマイズしにくい。ダークのthemeを選んでいるにもかかわらずフレーム部分だけは白いままで変えられない。オープンソースなので、元のソースを変えればカスタマイズ可能だろうけれども面倒。

Anaconda仮想環境に合わせたエディタ:
Atomを選んだ切っ掛けとして、Anacondaの仮想環境に対して設定がしやすいということが一つありました。Anaconda自体はかなり便利なのですが、切り替え可能な仮想環境とエディタをつなげる部分がやや面倒。要は仮想環境PATHへつなぐ設定法がそれぞれのエディタで異なっており、Atomの場合比較的簡単で自由度があったということです。
AtomとAnaconda Navigator:
Anacoda Navigatorでグラフィカルに複数の仮想環境を見比べることができるので便利。インストール/アンインストールもこの画面内で可能。仮想環境切り替えや接続に関してはAtomが比較的使いやすい。

最終的にはAnacondaの仮想環境は上画像のようにrootとpy35(Python3.5)だけにしました。それまでの仮想環境は:

・root(万が一のために備え、ここにはインストールしない/使わない)
・py35(安定版Tensorflow1.4、Pytorch0.2.0など/メインで使う用)
・py27(Python2.7にしか対応していないサンプルなどあるときに使う)
・cuda90(CUDA9に対応したα版Tensorflow1.5やソース版Pytorchなど/最新版実験用)

という感じで複数の仮想環境を使い分けて、どの環境が使いやすいか試していました。ライブラリやパッケージを入れすぎて不具合が出ても、仮想環境ごと消してしまえばいいのでシステムに影響を与えずに済みます。
Tensorflowなどを最新版(α版)にすれば最速になるのかと思いましたが、結局のところまだ不具合があったりサンプルも対応していなかったりとあまり使い勝手がよくない。ということから、サンプルも豊富な安定版を使ったほうがいいという結論に達しました。

Atomのパッケージ:
主には仮想環境の切り替えと接続が容易ということからAtomでの環境整備。MacBook Proのほうでは便利そうなパッケージをどんどん入れていましたが、今回はシンプルに最低限必要なパッケージだけをインストールしました。いまのところ標準装備のパッケージ以外3つだけで充分です。

・autocomplete-python
・platformio-terminal(このターミナルが使いやすい/Python RUN用)
・script(Python RUN用)

ネットで検索するとほぼ英語圏のサイトが多いことから英語表記のほうがわかりやすいため、日本語化しないことにしました。既存のOne Dark:Themeを使っていますが、フォントサイズや見た目的なカスタマイズは、Edit>Stylesheetからstyles.lessファイルで上書き変更しています。

PythonスクリプトをRUNさせる環境:
ここが一番悩んだところで、AtomのなかにもいくつかPythonスクリプトを実行できるパッケージがあり、ただRUNさせるだけであればどれでもいいのですが、ターミナルを使って実行させる方法とボタンやショートカット一発で実行できるものということ、そして仮想環境や作業ディレクトリの切り替えが即座にできるということから、platformio terminalatom-scriptを選びました。

Anaconda仮想環境切り替え方法:
Anacondaの仮想環境を使っていると、毎回ターミナルで入力が必要になります。通常は以下の状態(Anaconda root:Python3.6):
そして、以下を入力すると、
$ source activate py35
先頭に(py35)という表示がでて、仮想環境py35:Python3.5に入ったことになります。
追記:現在は「conda activate py35」に変更済み

which pythonを打てば、どのPython interpreterを使うかという違いが分かります。いわゆるPythonのPATHについてです。
これは、which pythonをつかって両方の環境のPATHを確認してみた画面です。最初のwhich pythonがAnaconda/rootのPATHです。つぎのwhich pythonがAnaconda/py35のPATHになります。
実際のところ仮想環境はAnacondaだけでなく、その手前にpyenvでも仮想環境をつくっています。anacondaで色々試しているため、anaconda内部で不具合が起きてしまったら、pyenvでanacondaごとアンインストールしてしまえばいいというわけです(実際一度anacondaを入れ替えました)。

platformio terminalの設定(かなり便利):
platformio自体は以前ESP32を試したときに使ったことがあるのですが、今回のようなPython用には設定していないので、あらためて使い直すという感じです。
Packages>Settings>Manage Packagesでインストール済みのパッケージリストからplatformioのsettingsボタンを押して各種設定に入ります。
主には、一番上のほうに出てくるこのあたりを設定します。
特にToggles欄の上から4番目の「Run Inserted Text」と5番目の「Select To Copy」にはチェックを入れておきます。
次にCore欄の、「Auto Run Command」に先程の仮想環境を切り替えるコマンドを入れておきます。ここではさらに、「which python」でPATHの確認、ディレクトリ内のリスト表示させる「ls」もセミコロンで区切っていれてあります。
その下の「Map Terminals To」でタブから「File」を選びます。
こうすると、platformio terminalが自動的に仮想環境に入って、しかも選択ファイルのディレクトリに移動して開いてくれます。
ファイルを切り替えるごとに、
source activate py35
which python
cd ${File}
ls
これらを自動的にやってくれて、仮想環境移行/PATH確認/作業ディレクトリへ移動/ディレクトリ内のリスト表示が一気にでてきて、実行する前の確認や手続きが一発で済みます。
ほかにもターミナルのパッケージがありますが、ここまでできるのはplatformio terminalくらいかもしれません。かなり便利です。
あとはプログラムをRUNさせるだけでいいのですが、もう一つ問題があります。

コマンドオプション付きのPythonプログラムの実行:
PytorchサンプルなどをRUNさせるときによくあることですが、Python Argparseでコマンドオプション(CUDAの有無やイテレーションの設定など)を追記する必要があります。

Pytorch examples/dcgan/main.pyをRUNさせる場合(コマンドオプション):

parser.add_argument('--dataset', required=True, help='cifar10 | lsun | imagenet | folder | lfw | fake')
parser.add_argument('--dataroot', required=True, help='path to dataset')
parser.add_argument('--workers', type=int, help='number of data loading workers', default=2)
parser.add_argument('--batchSize', type=int, default=64, help='input batch size')
parser.add_argument('--imageSize', type=int, default=64, help='the height / width of the input image to network')
parser.add_argument('--nz', type=int, default=100, help='size of the latent z vector')
parser.add_argument('--ngf', type=int, default=64)
parser.add_argument('--ndf', type=int, default=64)
parser.add_argument('--niter', type=int, default=25, help='number of epochs to train for')
parser.add_argument('--lr', type=float, default=0.0002, help='learning rate, default=0.0002')
parser.add_argument('--beta1', type=float, default=0.5, help='beta1 for adam. default=0.5')
parser.add_argument('--cuda', action='store_true', help='enables cuda')
parser.add_argument('--ngpu', type=int, default=1, help='number of GPUs to use')
parser.add_argument('--netG', default='', help="path to netG (to continue training)")
parser.add_argument('--netD', default='', help="path to netD (to continue training)")
parser.add_argument('--outf', default='.', help='folder to output images and model checkpoints')
parser.add_argument('--manualSeed', type=int, help='manual seed')

このmain.pyをRUNさせるには、これだけコマンドオプションがあります。ほぼdefault(コマンドオプションなし)でもいいのですが、defaultが設定されていない--dataset、--dataroot、--cuda、--manualSeedについてはコマンドオプションが必要となります。ということから最低でも、

$ python main.py --dataset cifar10 --dataroot ./ --cuda --manualSeed 1

という感じでpython main.py以下に必要項目を追記してからRUNさせなければいけません。この部分が面倒で、すべてにdefault値を設けたコードに書き直したり、この一行をコピペできるようにコメントに書いておいたりしていましたが、platformio terminalやscriptだと比較的簡単にできました(まあ、ターミナルでそのまま打ち込んでもいいのですが)。

platformio terminalのコマンドオプション付き実行の場合:
まず、上記のようなコマンドオプション付きのコマンドをコメントに書いておきます。そして最初に設定しておいた「Select To Copy」機能によって、上記コマンド一行をマウスで選択した段階でコピーが済んでおり、あとはペーストするだけになります。さらにペーストをキーマッピングしておきます。

・platformio terminalのSettings画面で「Select To Copy」にチェックを入れておく
・platformio terminalのSettings画面の下のほうにある「Custom Texts」の「Custom test 1」に「python $F」を入力しておく


このSettings画面で設定した内容は、Edit>Config...>config.csonでも編集できます。以下のconfig.csonファイルで直接編集してもいいかもしれません。
ちなみにplatformio terminalの設定はいまのところこんな感じになっています。

・Edit>Keymap...でkeymap.csonを開きキーマップ割当をする
上記の内容をキーに割り当てることで、もう少し入力方法が簡単になります。
これはkeymap.cson内に書き込んであるキー割当のスクリプトです。こちらを参考にしました。
alt-zで選択したテキストを実行します。
alt-xでpython fileを実行します。
コマンドオプションがある場合(例:python main.py --cuda --niter 1)は、コマンドオプションを含めた一行をどこかに書いておいて、それを選択してalt-zで実行するという感じです。
コマンドオプションが不要な通常の方法(例:python main.py)の場合は、そのままalt-xで実行します。キーマッピングなので、都合の良いキーに割当られますが、意外と既にあるショートカットキーと重複することがあるので、試し打ちしてみて使っていないキーの組み合わせを探さないといけません。

実行中停止させるコマンド:
通常ターミナルでプログラム実行中に割り込む命令としては、
一時停止:ctrl-z
ジョブ名表示:jobs
再開:fg 1(ジョブ名)
終了:ctrl-c
となりますが、ctrl-cで終了させるために、kill -9 $$やskill --fullなどをキーマッピングしてみましたが上手く機能しません。もうすこしSIGNALやシェルの勉強が必要かもしれません。最悪、途中強制終了するにはplatformio terminalの画面を閉じてしまうかです。
もしかしたら、platformio terminalはエミュレータのターミナルなのでそこまでの処理ができないのかもしれません。この部分については調査中です。

atom-scriptの場合:
atom-scriptは様々な言語に対応しており人気のあるパッケージだと思います。いろいろ改良されており、jupyter notebookにも対応、一応コマンドオプション付きでRUNさせることもできます。ただ問題はそのままだと仮想環境には自動的に入ってくれないという部分です。Pythonなどの環境変数に対応させるために
atom .
と、ドットつきでターミナルからatomを起動しろと書いてあります。これが煩わしいので、専用のatom起動用アイコン(ランチャー)をつくり、ドット付きの起動を可能にしました。

atom仮想環境用起動アイコン(ランチャー):
・「/usr/share/applications」の中の「Atom」アイコンを右クリックでコピー
・デスクトップなどにペーストし、右クリックでプロパティ画面を開く
・名前を「AtomPY35」などと変える
・コマンド欄に以下のような環境変数とドット(2箇所あり)を含めたシェルコマンド書く
*シェルコマンドについてはまだ勉強不足で不確かな部分もあるのですが、一応以下でも動きました。sourceの代わりにドット(.)を使っていますが、もしかしたらexportなど使ったほうがいいのかもしれません。

bash -ic ". /home/mirrornerror/.pyenv/versions/anaconda3-5.0.1/bin/activate py35; atom .; exec bash"

・ターミナルで新アイコンが置いてあるDesktopへ移動

cd ./Desktop

・新アイコンを「/usr/share/applications」へコピー移動する

sudo cp AtomPY35.desktop /usr/share/applications

・一度「/usr/share/applications」内から「AtomPY35」をクリック起動し、ランチャーにアイコンが並んだらランチャーに登録しておく


画像の色も微妙に変えておくといいかもしれません。通常用のアイコンと仮想環境用のアイコンを複数つくっておけば状況に応じて使い分けも可能です。そうすれば煩わしい事前設定をせずにアイコンクリックで仮想環境でのatomが使えるようになります。

atom-scriptでのRUN方法:
Packages>Scriptの中には
Script: Run by Number (jupyter notebook用)
Run Script (python main.py/コマンドオプションなし)
Run with profile (python main.py --cuda --niter 1/コマンドオプション付き)
Stop Script
Configure Script(環境変数やコマンドオプションなどの設定)
Close Window and Stop Script
Copy Run Results

という感じでいろいろあります。
一度対象のプログラムにフォーカスを与えてから、「Configure Script」をクリックすると、
このような設定画面が現れるので、「Program Arguments:」にコマンドオプションとなる「--cuda --niter 1」を記入して右下の▶Runボタンを押せば実行できます。この場合、ターミナルは起動しなくても実行できるので比較的簡単な操作になると思います。この内容を「Save as profile」ボタンで保存しておけば、次回からボタン操作だけで実行可能となります。
一旦保存すれば、このプログラムの設定を含んだ「dcgan(名前は任意)」というボタンが生成され、Packages>Script>Run with Profile(Shift+Ctrl+Alt+B)から「dcgan」ボタンを押して▶Runボタンで実行できます。
Stop Script(Ctrl+Q)もあるので途中で終了も可能です。

platformio terminalとscriptの二つがあれば、ほぼ問題なく簡単に実行できると思います。というわけで、エディタと実行環境については思っていたようなことができるようになったので一安心です。

2017年11月16日木曜日

デュアルブートWindows10/Ubuntu16.10/NVIDIA-384/CUDA9.0/cuDNN7.0/Anaconda

前回の投稿にも書きましたが、現在使用しているMacBook Pro(2014年製)の後継機として、MSI GS43VR 7RE Phantom Proという14型のゲーミングノートパソコンを購入しました。GeForce GTX1060のGPU内蔵なので、Deep Learningの計算をさせればMacBook Proよりもずっと速いはずです。Windows10ですが、Deep Learningのプログラミング環境では、Linuxのほうが高速に処理してくれるらしいので、Linux(Ubuntu)とWindows10のデュアルブートにしました。
MSI GS43VR 7RE Phantom Pro ゲーミングノートパソコン:
プロセッサ:Intel Core i7-7700HQ
GPU: GeForce GTX1060(6GB)
RAM: 16GB
HD: 256GB(PCIEx Gen3) + 256GB(SSD/SATA:オプション変更)
画面:14インチ/1920x1080px
サイズ:345x245x22.8mm
重量: 1.8kg

Deep Learning用のパソコンにするには、以下のことが必要らしい。
・デュアルブート(万が一LinuxがダメならWindowsに戻るため)
Ubuntuのインストール(ほぼ定番)
NVIDIAドライバ(GPUドライバ)のインストール
CUDA(NVIDIA/GPU並列計算プラットフォーム)のインストール
cuDNN(NVIDIA/Deep Learning用ライブラリ)のインストール
Anaconda(Python統合環境/Python3.6含む)のインストール
Pytorch(Deep Learningライブラリ)のインストール
Tensorflow(Deep Learningライブラリ)のインストール
PyCharm(エディタ)のインストール

1日で終わればいいけど、そう簡単には終わらないはず。特にLinux(Ubuntu)で環境を整えるのは、バージョンの違いなどもあるためかなりハマりそう。ネットで調べてみてもトラブルが多い。
結果的には何とか動きましたが、何回もインストールし直し、思うようにいかず設定に数日かかってしまいました。

これはUbuntu16.10上の画面です。通常Ubuntu16.04が最適なバージョンですが、MSiのゲーミングノートPCとは相性が悪いのかフリーズしたりほとんど使えない状態でした。やむを得ず、Ubuntu16.10にしてみて悪戦苦闘の結果なんとか動いたという感じです。Ubuntuのバージョンによって、対応するドライバやライブラリなどが変わってしまうので、その組み合わせが難しい。
Ubuntuを何回もインストールし直していると、コンピューター好きには楽しいのですが(コンピュータの構造、シェルコマンド、viエディタの使い方などに詳しくなってしまう)、いつのまにか環境構築が目的(達成感や克服感)となってしまい、本来の目的であるLinuxで何をするかということから次第に遠ざかってしまい、時間ばかり消費してしまうのであまり深追いはしないほうがいいと思います。

理想的な組み合わせ:
・Ubuntu16.04
・NVIDIAドライバ nvidia-384(多分低すぎなければいい)
・CUDA 8.0(対応:Ubuntu16.04/14.04、Tensorflowなども対応)
・cuDNN 6.0/7.0

最終的な組み合わせ:(なぜか無事動いた)
・Ubuntu16.10/Windows10デュアルブート(gcc 6.2.0)
・NVIDIAドライバ nvidia-384
・CUDA 9.0(対応:Ubuntu17.04/16.04、未対応:Tensorflow/Pytorch)
・cuDNN 7.0.4 for CUDA 9.0
・Anaconda 4.3.30(cuda80/cudnn6.0.21/Tensorflow-gpu1.3.0)
・Pytorch (Linux/conda/Python3.5/CUDA8.0)

矛盾するけれどもAnacondaで解消?
NVIDIA製CUDA8.0だとUbuntu16.04のgccバージョン5(6未満)でなければならず、Ubuntu16.10にしてしまうとgcc6.2になってしまう。仮にgccを5へダウングレードしても、CUDA8.0をUbuntu16.10にインストールするとgcc6.2へアップグレードされてしまうので、やむを得ずCUDA9.0ならびにcuDNNもCUDA9.0用をインストール。とりあえず、CUDA9.0とcuDNN7.0のマニュアルにある動作確認では問題なし。
ちなみにTensorflowやPytorchはまだCUDA9.0には未対応。
Tensorflow1.5からCUDA9.0に対応するらしい。いちおうベータ版Tensorflow1.5はあるけれども、ソースをコンパイルしてインストールしなければならない。またベータ版PytorchであればCUDA9.0でも動くらしい
しかしながらAnacondaをインストールしてみると、仮想環境のおかげなのかCUDA8.0+cuDNN6.0対応のTensorflow-gpu1.3とPytorchのサンプルがきちんと動きました。もしかしたらUbuntu17.10(最新)でも動くかもしれないけれど、またインストールし直すのは大変なのでやめました。

Tensorflow-gpu/CNN/CIFAR10での実験:
ということで、CIFAR10のサンプルで試してみました。以下がGPU(GeForce GTX1060)で動かしてみた内容。右端の数値を見ると0.018〜0.019sec/batchとなっています。これはかなり高速。
ちなみに、MacBook Pro(GPUなし)のほうでも同じように動かしてみると、以下。
0.680sec/batch前後という結果。比較すると、GPUのほうが約36倍速いということになります。あまりに違うので、いままでMacBook Proを使っていたのが馬鹿馬鹿しく感じてしまいます。その他VAEのサンプルで試すと約6.5倍の速さでした。CNNのサンプルに関しては、cuDNNを使っているためか、かなり効果があるようです。特にエラーもでないし、このような歴然とした結果からおそらくGPUはきちんと機能しているはずです。

ネットで設定例がたくさん見つかるので、ここでは細かな内容は省きますが、手順を一通り書いておきます。

デュアルブート下準備:
・Windowsの「ディスクの管理」でパーティションを区切り約120GBをUbuntu用に確保
・USBメモリースティック(8GB)をFAT32フォーマット化
・Ubuntuをダウンロード
Rufusソフトをダウンロード
・Rufusを使ってUbuntuをUSBメモリースティックへLiveCDとして書き込む
・再起動しBIOS画面にする(起動中Deleteキー連打/違う機種だとF2キー連打)
・BIOS画面内でFast boot無効、USBメモリースティックを最優先に起動順序を変える
ハマりどころ:
Windows10のボリュームはすでに5個くらいに分割されており、Ubuntu用に充分な領域を確保できないけれども、後から変えることもできるはず。それほどこの段階では問題はない。

Ubuntu:
・USBメモリースティックからUbuntuが立ち上がったらWifiとつないでおく
・Ubuntuのインストール(使用言語、地域、ユーザ名、パスワードなどの設定)
・インストールされれば再起動
・また起動中にBIOS画面にしてPCにインストールしたUbuntuを最優先起動に設定する
・Ubuntuとして起動したら、Wifiなど基本事項を設定
ハマりどころ:
Ubuntu17.10が最新バージョンだけれども、CUDAには正式に対応していないためUbuntu16.04を選ぶのがいいとされる。しかし16.04(日本語版)をインストールしてみたところディスプレイ解像度変更不可、ネットがつながらないなどの問題発生。オリジナル版もフリーズしたり機能上の不具合が多くで断念。つぎに16.10を試してみると何とか動いた。さらに次の候補となる17.04もフリーズ、17.10はかろうじて動くがたまにフリーズする。16.10くらいしかまともに動かないため、やむを得ず16.10で以下の作業を進めることに。しかし気がかりなのは、CUDAが17.04か16.04にしか正式に対応していないという点。

NVIDIAドライバ:
NVIDIAサイトからドライバのダウンロード
・Nouveauの無効化
・Ubuntu上で仮想コンソールへ移動してドライバのインストール
・BIOS画面でセキュアブート無効にする
ドライバを入れた後(CUDA無し)、ターミナルでnvidia-smiを入力してこの画面が出ればOK(しかしなかなか出なかった)。

ハマりどころ:
ほとんどはUbuntuのターミナルからインストールするけれども、一旦GUI表示を無効にして仮想コンソールへ場所を移して、そこでドライバのインストールをするというややこしい手順。また、viでNouveau無効化ファイル作成するため、viの使い方も知らないといけない。セキュアブート無効を忘れるとログインループにハマったりGUI画面が表示されなくなったりします。後から気づいたけれども、セキュアブート無効にすればこのような面倒な手順は不要かもしれない。最初nvidia-375をインストールしていましたが、他のソフトを入れると最新のnvidia-384に引き上げられてしまうようです。結果的には問題なし。

CUDA:
NVIDIAサイトからインストーラのダウンロード
・CUDAのインストール
・パスの追加
ハマりどころ:
作業事態は単純だけれども、どのバージョンをインストールしたらいいのか迷う。最新はCUDA9.0だけれども、まだTensorflowやPytorchは対応していない。それではCUDA8.0にすればいいかというと、Ubuntu16.10のgccのバージョンと対応しなくなる。現状では他の環境やライブラリがまだCUDA8.0にしか対応していないため、CUDA9.0を使わないほうがいいみたい。CUDA8.0にとどまるなら、Ubuntuを17.04にアップグレードするという手もあるけれども、このMSI GS43VRは17.04でもフリーズしてしまう。かろうじてUbuntu17.10では動くのだけれども、一旦ここまでインストールしてしまうと17.10へ変えにくい。というわけで、使用するパソコン、ライブラリ、ドライバ、プラットフォームのそれぞれのバージョンがうまく噛み合わなくて選択が難しい。CUDAのバージョンに振り回される。
CUDAでつまづくようなら、NVIDIAサイトからダウンロードせずに、
sudo apt-get install nvidia-cuda-toolkit
でインストールしたほうがいいかもしれません。

cuDNN
・メール登録が必要
・CUDAのバージョンに合わせてNVIDIAサイトからダウンロード
・Ubuntuへインストール
ハマりどころ:
特に難しくはない。このライブラリを使うとかなり高速になるようです。tarファイル版は一気にダウンロードできるけれども、なぜかマニュアルにある動作確認用サンプルが含まれていない。3つあるdeb版なら3つ目がサンプルなのでいいかもしれない。

Anaconda
・pyenvを最初にインストール(こちらを参考にしました
・pyenvにパス通す
・Anacondaをインストール(Python3.6含む)
・tensorflow-gpu1.3をインストール(Anaconda内)
・pytorchをインストール(Anaconda内:pytorch/torchvision/cuda80)
・cudnnをインストール(Anaconda内)
*Anaconda経由(あるいはanaconda-navigator)で、tensorflow-gpu1.3、pytorch、cuda80、cudnn6.0をインストールできるのでかなり楽。Anaconda cloudに登録し、cloud上で必要なライブラリを探すのもいいと思います。場合によってはpython2.7もインストールして仮想環境をつくっておくといいらしい。
結果的には、まだ未対応のCUDA9.0とcuDNN7.0(CUDA9.0用)をNVIDIAからインストールしたにもかかわらず、Anacondaの仮想環境とpytorch/cuda80/cucnn6.0のおかげで無事動いたのかもしれない。

PyCharm(エディタ):
サイトから無料のcommunity版をダウンロード
・解凍したあと、pycharm-community-2017.2.4/binへ移動し、./pycharm.shで起動。
・Anacondaのパスとつなげておく(確かデフォルトでつながっていたような)
・エディタ内でターミナル使用、git pullも可能
・pytorchのdcganサンプルなどにあるコマンドラインオプション/引数なども登録可能
*例えば、
python main.py --batchSize 128 --cuda --ngpu 1
などのmain.pyのうしろにつける設定項目(こちらを参考に)。

まとめ:
・Linuxと使用するパソコンとの相性次第(ダメならWindowsで)
・BIOS画面でFast BootとSecure Bootを必ずオフにする
・Anacondaを使う
前半は先が見えずかなり苦戦し、UbuntuがだめならWindows10にインストールするしかないとも考えていました。海外国内問わずネット上にはなかなか動かない例もたくさんあり、予想以上に面倒でした。Linux固有のトラブルと、ハード、ソフトそれぞれの対応が微妙にずれており、しかも今後リリースされるライブラリも徐々にバージョンアップしていくので、またインストールし直す時がくるので面倒です。Ubuntuは年2回(4月と10月)にバージョンアップするらしく、次回18.04は2018年4月という意味でLTS(Long Term Support/5年間)になる予定。
しかしながら、一度設定できればGPUのパワーはすごい。MacBook Proもそこそこ速いのではないかと思っていましたが、そんなことはありませんでした。やはりGPU搭載のパソコンは圧倒的に作業時間が短縮されていいと思います。特にCNNなどの層が厚いアルゴリズムでは効果てきめんという感じです。

Linuxはトラブルが多いと思うのですが(ドラブルがあれば自分で直すという姿勢からか)、一旦動き出せば、機能も見た目もシンプルだし元々軽快なためか計算速度も速いらしいでの、できるだけWindowsを使いたくありませんでした。WindowsはしばらくTensorflowにも対応しておらず、Pytorchにおいては公式ではLinuxとMac版だけ(WIndows版はAnacondaからインストール可能)。MicrosoftがCNTKをリリースしたものの、Windowsはやや一歩遅れというのも気になります。

また、DockerやAWSなどのクラウドへUbuntu16.04をインストールし(必要に応じてその他のライブラリも)、GPUで計算させるという方法もあるようです。どうしても時間を短縮したいという場合は有料(数百円)にはなるけれどもいいかもしれません。コーディングは普通のノートパソコンでやって、計算のときはクラウド利用するというのが一番スマートな方法(ものを持ちすぎない)だと思います。

Tensorflow1.4でKerasが取り込まれ、さらにはEager(まだpre-alpha版)も登場し(tf.learnやtf-slimはどうなってしまったのか)、まだまだ変化がありすぎる状況です。現在はCUDA8.0とcuDNN6.0/7.0が主流だと思うのですが、数ヶ月以内ですぐにCUDA9.0に対応してしまうと思います。半年後にはUbuntu18.04LTSがリリースされ、そのころにはまた新たなライブラリや環境が登場していると思うのできりがないという感じです。

2017年11月12日日曜日

Deep Learning用GPUノートパソコン

Deep Learningで学習する際に、現在使用しているMacbook Pro(2014年製)だとCPU計算のため数十時間とか普通にかかってしまいます。あまりにも効率が悪いので、GPU搭載のパソコンの必要性が高くなってきました。GPUパソコンを新規購入せずにクラウド(有料)で計算させる方法もありますが、外付けGPUという手段もあるようです。性能的な面から言えばデスクトップ型のGPUパソコンが一番効率良さそうですが、個人的には持ち歩きをするために、幾分スペックが落ちてもGPUノート型(ゲーミングノートパソコン)がいいかなと。

外付けGPU:
最近は、MacBookでも外付けできるGPUがあるらしい。

こちらはグラフィックボードは別売り。

GIGABYTE ビデオカード GTX1070搭載 外付けVGA BOX GV-N1070IXEB-8GD
Posted with Amakuri at 2017.11.10
日本ギガバイト
販売価格 ¥84,812
こちらはGeForce GTX1070も含まれた値段。Macで確実に動くかどうかは分からないけれども、10万円以下で高速になるのであればかなり助かります。

GPU比較:
ということで次はGPUの比較。最近のNVIDEAのGPUだと、
GeForce GTX 1080Ti : 約10万円(922)
GeForce GTX 1080 :約7万円(725)
GeForce GTX 1070 :約5万円(602)
GeForce GTX 1060 :約3万円(440)
当然数字が大きいほどパワフル。()内の数字はドスパラによるベンチマーク値。主にグラフィック処理に関する数値だと思うので、Deep Learningにおける並列計算でのベンチマークとは少し違うと思いますが一応目安として。
一番下のGTX 1060は比較的安価ですが、それでも現在のMacBookよりは遥かに速いはずです。個人的にはGTX1070が無難かなと思うので、外付けなら合計で8万円前後となりそうです。しかしながら外付けの場合は、ThunderBolt経由となるのでスピードがワンランク落ちるらしい。
ということから、GPUを外付けするよりも新たにGPU付きパソコンを購入したほうがよさそう(現在のMacBookも古くなってきたし)。

デスクトップかノートか:
新規購入となるとデスクトップ型かノート型かということになります。当然デスクトップ型のほうがコストパフォーマンスは高くなりますが、現在のMacBookのかわりに使うことにもなるので、ノートのほうが個人的には好ましい。ということで、GPU付きノートパソコンを探すことに。

GPU付きノートパソコン:
GeForce GTX 1070搭載ノートPCとなると20〜25万円のゲーミングノート。値段的には新しいMacBook Proとあまりかわらないけれども画面サイズが15型になってしまう。現在のMacBook Proは13.3型で非常にコンパクトなのですが、15型になると持ち運びするには大きすぎるし重量もあります。

14型以下のGPUノートパソコン:
ゲーミングノートパソコンのほとんどが15型以上となる中で、数少ないながらも14型以下もあるようです。しかし、そのほとんどがGeForce GTX 1060以下となってしまいます。とはいっても普通のMacBook Proよりは断然速い。一つ前もMacBook Proを使っており、現在もMacBook Pro、次もMacBook Proを購入することになると思っていましたが、せっかくなのでGPU付きに方向を変えてみようかと。そうすると以下の3つが候補。

Dell Alienware 13(194,378円/税込み)、画面13.3インチ、330x269x24mm、2.6kg
MSI GS43VR (195,000円/税込み)、画面14インチ、345x245x22.8mm、1.8kg
Razer Blade 14(234,144円/税込み)、画面14インチ、345x235x17.9mm、1.86kg

追記:
上記は2017年製の型番であり、2018年製であれば
MSI GS65 Stealth Thin 8RE-005JP(Arkで233,800円)、357.7×247.7×17.9mm、1.88kg
がいいかもしれません。


それぞれ以下のようなスペックでの比較にしてみました。
画面サイズ:14型未満
RAM:16GB
HD:SSD256GB
GPU:GeForce GTX 1060
*GeForce GTX 1060未満のゲーミングノートもあるのですが、候補外にしてしまいました。

Alienware 13は、Dellとは言え独立したブランドらしく、ほとんどDellらしさはありません。見た目的にはかっこいいゲーミングノートという感じ。タッチパッド部分が光るというのは珍しいけれども、左右クリックボタンがついているのが、個人的には邪魔という感じ。画面が13.3インチで一番コンパクトなのですが、なぜか重い。ゴツい排気口もあって頼もしそう。そのため少し縦長ですが、元々コンパクトなのでそれほど大きさは気にならないかもしれません。41,400円上乗せすれば、MacBook ProのRetinaディスプレイと同等の2560x1440の高解像度+タッチパネルに変更できるようです。ゲーム目的ではないのでそこまでは必要ないと思います。秋葉原にショールームもありサポート窓口になってくれるらしく、国内でもサービス面ではなんとか大丈夫そうです。
MSI GS43は、見た目はいかにもゲーミングという感じですが、中身的にはバランスがとれていそうです。排気もけっこうこだわっているようです。故障やサービス面については秋葉原のArkが窓口になるようです。通常ノートパソコンでは後からメモリ増設ができないものが多いのですが、MSIの場合Arkに持っていけば対応してくれるようです。重量も1.8kgなので、ゲーミングノートのなかでも軽量な方です。アメリカでも人気あるようです。それと保険が最初から2年ついています。

Razer Blade 14は、MacBook Proを真っ黒にしたようなシンプルでスタイリッシュな外観ですが、完全に外国製品という感じで取り扱っているところも少なく、故障の際も部品取り寄せなど面倒らしい。値段もやや高価。厚さも薄くスペック的にも悪くはないのですが、日本に窓口ができれば値段を引き下げることも可能だろうし、もう少し安心して購入できそうです。付属のACアダプターも小型化の工夫がされているようです。

まとめ:
おそらく現在使用しているパソコンと比較した場合に、それぞれの印象が異なると思います。個人的には、Alienware 13はサイズはいいのですが重いというのがひっかかります(現在13.3インチMacBook Proが重量1.57kgなので)。
外観においてはMSI GS43が一番かっこわるいのですが、2年保証付きで、後からメモリ増設可能であったり、サービス/サポート面や機能面においては一番優れていそう。
Razer Blade 14は確かに魅力的ですが、国内サポート面での不安定さとやや高価であるという部分がひっかかります。数万円高価になるなら、他の機種においてメモリを16GBから32GBに取り替えたほうがよさそうとも考えてしまいます。
ゲームをするわけではないので、やはり総合的に見るとMSI GS43がよさげです。見た目さえ我慢すればいいという感じです。

通常、デスクトップ型にGTX 1070を積むのがコストパフォーマンスに優れるのだと思いますが、現在使用しているMacBook Pro(13.3型)の代わりにもなるものとしてポータブルなものを選ぶと14型のGTX 1060ゲーミングノートパソコンとなってしまいます。そのため性能的な意味においては妥協しなければならないのですが、それでもMacBook Proよりは遥かに速いので不満は残らないでしょう。

AnacondaでPythonを管理してPytorchを使おうと考えているので、そうするとWindowsだとやや不都合があるため、OSはLinux/Ubuntuにしたほうがよさそうです。

追記:
最近はGoogle Colab(無料GPU/TPUクラウド)を使えば高速演算が可能なので、必ずしもGPU付きコンピュータが必要ではなくなったかもしれません。

関連:
GPU:GTX1060で再度DCGAN(CelebA)を試してみる
Google Colabの無料GPUで強化学習(Keras-RL)を試す


人気の投稿