Keras/TensorFlowでDNNな囲碁の評価関数を作ってみる

「囲碁をディープラーニングするのは面白い」という噂なので(笑)、私も試しに一度やってみることにしました。作るならやっぱり評価関数。それも、その時の形勢を「目数」で教えてくれるやつがなんかいいですよね? とりあえず今回は19路盤用です。

まずは学習に使うデータについてです。とりあえず評価する局面は、COSUMIで打たれた19路盤互先の作り碁の棋譜から作りました。GNU Go、強い人、弱い人、意図した序盤早々の連続パス、意図しないクリックミスの混ぜ合わさった様々なよく分からない局面が出現しそうで、まあ良いのではないかと…(笑) まず、最後のパスパスを取り除き、1手から最終手の間の一様乱数にまで棋譜の手数を短くして、さらに対称形を考慮しない完全な重複分を取り除き、残った棋譜の最終局面を使うことにしました。

そして次に、その局面に付けるラベル、今回の場合は「目数単位の形勢判断」ですが、うーん、これが本当にどうするのが良いのか… とりあえず、今回の作成方法は以下のとおりです。

  • まず先ほど作った局面を、コミ6目でRayの2k playoutに考えさせます
  • 返ってきたwin rateが0.5に近づく方向にコミを10目ずらして、もう一度Rayに考えさせます
  • それをwin rateが0.5の反対側に行くまで、繰り返します
  • 0.5をまたいだ2点を結んで、0.5と交わるところを「大体の形勢」とします
  • 再度、コミを「大体の形勢」として、今度はRayの20k playoutに考えさせます
  • 返ってきたwin rateが0.5に近づく方向に、今度はコミを4目ずらして、もう一度Rayに考えさせます
  • 先ほどと同じように、それをwin rateが0.5の反対側に行くまで、繰り返します
  • 先ほどと同じように、0.5をまたいだ2点を結んで、0.5と交わるところを「最終的な形勢」とします

あまりにも素朴すぎる気はしますが、こんな感じで作りました。前半は消費リソースを減らすためにやっているだけなので、後半だけを行っても当然似たようなラベルができるはずです。

最初のころは、これを10,000局面分作っていろいろ試していたのですが、ちょっと遊んでみたいだけとはいえ、それではあまりにも少なすぎたので、50,000局面分まで増やしました。そしてそれを対称形に8倍して、ここでもう一度重複分を除去し、きりの良い数字にまで少し減らして399,000局面分できました。今回は、その内80%の319,200局面分を学習用に、残りの20%の79,800局面分を検証用に使用します。

ここまで、学習データは用意できましたので、次に実際に学習を始めます。

今回の実行環境は、

  • Amazon EC2 p2.xlarge
  • Ubuntu 16.04 LTS
  • CUDA 8.0
  • cuDNN 5.1

です。最初、手元のGPUなしのWindowsマシン(CPU:Intel Core i5-3470S メモリ:16GB)でいろいろ試していたのですが、実際に学習が動き始めると、さすがにやはりちょっと遅すぎるので、EC2使いました。学習内容によって結構変わってくるみたいですが、だいたい12倍ほど速かったです。もう少し速いとうれしいのですが、しかたないでしょうか?

DNNのフレームワークには、バックエンドにTensorFlowを使ったKerasを使ってみました。

Keras
https://keras.io/

TensorFlow
https://www.tensorflow.org/

Kerasはとても分かりやすくて、私のような素人には本当にありがたい。かなりおすすめです。TensorFlowもですが、本家のドキュメントがしっかりしているのがいいですよね。例えば、今回のケースだと、こんな感じのコードになります。

import numpy as np
from keras.models import Sequential
from keras.layers import Activation, AveragePooling2D, Conv2D, Flatten
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.optimizers import Adam

BATCH_SIZE   = 200
EPOCHS       = 20

x_train = np.load('x_train.npy');
y_train = np.load('y_train.npy');
x_test  = np.load('x_test.npy');
y_test  = np.load('y_test.npy');

model = Sequential()

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())

model.summary()

model.compile(loss      = 'mean_absolute_error',
              optimizer = Adam())

model.fit(x_train, y_train,
          batch_size      = BATCH_SIZE,
          epochs          = EPOCHS,
          verbose         = 1,
          validation_data = (x_test, y_test))

驚くほどシンプルに書けます。

今回は、以下すべての場合において、

バッチサイズ 200
エポック数 20
損失関数 平均絶対誤差(Mean Absolute Error)
最適化アルゴリズム Adam(パラメータはKerasのデフォルト)

です。バッチサイズは、実行速度などに影響がかなり大きいです。エポック数は、収束していなくても、過学習していても、なにがあっても、今回は一定でいきたいと思います。

ネットワークへの入力は、とりあえず最初、「次の手番のプレーヤーの石」と「相手のプレーヤーの石」の2面(19,19,2)、数値は0と1です。ちなみにですが、今回の学習データのラベルは、平均5.7、標準偏差32.9、平均偏差21.5ぐらいです。なので、とりあえず盤面見ないで「黒5.7目形勢が良い」って答えておけば、Lossは21.5にはなりますので(どちらが黒か教えませんので、実際はもう少し難しいはずですが)、最終的にその数字がどれくらい0に近づくのか、っていう感じで見てもらうと良いと思います。

それではいってみましょう。まず最初に考えたのはこんなネットワーク構成でした。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Activation('relu'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())

model.summary()が吐いてくれるネットワークの要約がこちら。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 13, 13, 1)         33        
_________________________________________________________________
activation_4 (Activation)    (None, 13, 13, 1)         0         
_________________________________________________________________
average_pooling2d_1 (Average (None, 1, 1, 1)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 19,137.0
Trainable params: 19,137.0
Non-trainable params: 0.0

パタパタパタと畳んで、ペタンを押しつぶして、フワーと見るイメージなんですが(笑)、ところがこれ、全く学習してくれません。

Epoch 1/20
319200/319200 [==============================] - 29s - loss: 22.3445 - val_loss: 21.9808
Epoch 2/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 3/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 4/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 5/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 6/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 7/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 8/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 9/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 10/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 11/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 12/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 13/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 14/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 15/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 16/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 17/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 18/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 19/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 20/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808

試しに、活性化関数をtanhに変更してみます。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('tanh'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('tanh'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('tanh'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Activation('tanh'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 29s - loss: 22.2877 - val_loss: 21.9071
Epoch 2/20
319200/319200 [==============================] - 26s - loss: 22.2663 - val_loss: 21.8958
Epoch 3/20
319200/319200 [==============================] - 26s - loss: 22.2548 - val_loss: 21.8842
Epoch 4/20
319200/319200 [==============================] - 26s - loss: 22.2462 - val_loss: 21.8796
Epoch 5/20
319200/319200 [==============================] - 26s - loss: 22.2417 - val_loss: 21.8763
Epoch 6/20
319200/319200 [==============================] - 26s - loss: 22.2386 - val_loss: 21.8739
Epoch 7/20
319200/319200 [==============================] - 26s - loss: 22.2370 - val_loss: 21.8727
Epoch 8/20
319200/319200 [==============================] - 26s - loss: 22.2341 - val_loss: 21.8692
Epoch 9/20
319200/319200 [==============================] - 26s - loss: 22.2325 - val_loss: 21.8677
Epoch 10/20
319200/319200 [==============================] - 26s - loss: 22.2305 - val_loss: 21.8665
Epoch 11/20
319200/319200 [==============================] - 26s - loss: 22.2290 - val_loss: 21.8653
Epoch 12/20
319200/319200 [==============================] - 26s - loss: 22.2273 - val_loss: 21.8669
Epoch 13/20
319200/319200 [==============================] - 26s - loss: 22.2259 - val_loss: 21.8618
Epoch 14/20
319200/319200 [==============================] - 26s - loss: 22.2245 - val_loss: 21.8624
Epoch 15/20
319200/319200 [==============================] - 26s - loss: 22.2234 - val_loss: 21.8621
Epoch 16/20
319200/319200 [==============================] - 26s - loss: 22.2221 - val_loss: 21.8590
Epoch 17/20
319200/319200 [==============================] - 26s - loss: 22.2208 - val_loss: 21.8604
Epoch 18/20
319200/319200 [==============================] - 26s - loss: 22.2197 - val_loss: 21.8587
Epoch 19/20
319200/319200 [==============================] - 26s - loss: 22.2191 - val_loss: 21.8621
Epoch 20/20
319200/319200 [==============================] - 26s - loss: 22.2178 - val_loss: 21.8557

ちょびっとだけ数字が動いた…(笑) 今度はLeakyReLUに。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 34s - loss: 21.7510 - val_loss: 20.9956
Epoch 2/20
319200/319200 [==============================] - 30s - loss: 21.1638 - val_loss: 20.7552
Epoch 3/20
319200/319200 [==============================] - 30s - loss: 20.8117 - val_loss: 20.4443
Epoch 4/20
319200/319200 [==============================] - 30s - loss: 20.5938 - val_loss: 20.1891
Epoch 5/20
319200/319200 [==============================] - 30s - loss: 20.3966 - val_loss: 19.9154
Epoch 6/20
319200/319200 [==============================] - 30s - loss: 20.1918 - val_loss: 19.6722
Epoch 7/20
319200/319200 [==============================] - 30s - loss: 20.0177 - val_loss: 19.7719
Epoch 8/20
319200/319200 [==============================] - 30s - loss: 19.8868 - val_loss: 19.3948
Epoch 9/20
319200/319200 [==============================] - 30s - loss: 19.7355 - val_loss: 19.4068
Epoch 10/20
319200/319200 [==============================] - 30s - loss: 19.5322 - val_loss: 19.0625
Epoch 11/20
319200/319200 [==============================] - 30s - loss: 19.3279 - val_loss: 19.1659
Epoch 12/20
319200/319200 [==============================] - 30s - loss: 19.1512 - val_loss: 18.8860
Epoch 13/20
319200/319200 [==============================] - 30s - loss: 18.8963 - val_loss: 18.6369
Epoch 14/20
319200/319200 [==============================] - 30s - loss: 18.6399 - val_loss: 18.4589
Epoch 15/20
319200/319200 [==============================] - 30s - loss: 18.4826 - val_loss: 18.1423
Epoch 16/20
319200/319200 [==============================] - 30s - loss: 18.3363 - val_loss: 18.1451
Epoch 17/20
319200/319200 [==============================] - 30s - loss: 18.1859 - val_loss: 18.0372
Epoch 18/20
319200/319200 [==============================] - 30s - loss: 18.0898 - val_loss: 17.8348
Epoch 19/20
319200/319200 [==============================] - 30s - loss: 18.0122 - val_loss: 17.7273
Epoch 20/20
319200/319200 [==============================] - 30s - loss: 17.9057 - val_loss: 17.9030

おお、がっつり動き始めました! ここで、なんとなく分かりましたよ。現在のネットワーク構成では、一番最後の活性化関数の後ろに、もう畳み込み層や全結合層がありません。活性化関数がひとつ余分なんですね。ReLUは正の値しか出力しないので、それを平均してもまた正の値の出力しか出てきませんが、ラベルの方には負の値(次の手番側が形勢悪い)もあります。その時にパラメータの更新ができないとか、たぶんそういう話です(合ってるかな?)。ということで、活性化関数をReLUに戻して、一番最後のは削ります。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 29s - loss: 21.7389 - val_loss: 20.8649
Epoch 2/20
319200/319200 [==============================] - 25s - loss: 20.8634 - val_loss: 20.4426
Epoch 3/20
319200/319200 [==============================] - 25s - loss: 19.7709 - val_loss: 19.0222
Epoch 4/20
319200/319200 [==============================] - 25s - loss: 19.1506 - val_loss: 18.5475
Epoch 5/20
319200/319200 [==============================] - 25s - loss: 18.7009 - val_loss: 18.1697
Epoch 6/20
319200/319200 [==============================] - 25s - loss: 18.3530 - val_loss: 17.9657
Epoch 7/20
319200/319200 [==============================] - 25s - loss: 18.1615 - val_loss: 17.7496
Epoch 8/20
319200/319200 [==============================] - 25s - loss: 18.0063 - val_loss: 17.8551
Epoch 9/20
319200/319200 [==============================] - 25s - loss: 17.9094 - val_loss: 17.5887
Epoch 10/20
319200/319200 [==============================] - 25s - loss: 17.8051 - val_loss: 17.4792
Epoch 11/20
319200/319200 [==============================] - 25s - loss: 17.7149 - val_loss: 17.4250
Epoch 12/20
319200/319200 [==============================] - 25s - loss: 17.6149 - val_loss: 17.3268
Epoch 13/20
319200/319200 [==============================] - 25s - loss: 17.5354 - val_loss: 17.7732
Epoch 14/20
319200/319200 [==============================] - 25s - loss: 17.4814 - val_loss: 17.6514
Epoch 15/20
319200/319200 [==============================] - 25s - loss: 17.3799 - val_loss: 17.4220
Epoch 16/20
319200/319200 [==============================] - 25s - loss: 17.3349 - val_loss: 17.0786
Epoch 17/20
319200/319200 [==============================] - 25s - loss: 17.2229 - val_loss: 17.1846
Epoch 18/20
319200/319200 [==============================] - 25s - loss: 17.1549 - val_loss: 16.9264
Epoch 19/20
319200/319200 [==============================] - 25s - loss: 17.1092 - val_loss: 17.0422
Epoch 20/20
319200/319200 [==============================] - 25s - loss: 17.0327 - val_loss: 18.2891

OKのようです。

LeakyReLUってなんとなく好きなんですが、ReLUの方がやはり軽いみたいなので、ここから先はひとまずReLUを使います。

次に、ネットワークを深くしていきます。3×3の畳み込み層を全部で4層に。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(11, 11)))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 11, 11, 1)         33        
_________________________________________________________________
average_pooling2d_1 (Average (None, 1, 1, 1)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 28,385.0
Trainable params: 28,385.0
Non-trainable params: 0.0

そして5層、6層、7層、8層、と増やしていって、最後に全部で9層。今回はパディングを入れていないので、どんどん畳み込まれていって、3×3の畳み込みのみで1×1のサイズに。そうなると、最後の平均プーリングはもう意味がありませんので削除します。1×1の畳み込み層も、実質、ただの全結合になってしまいました。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 74,625.0
Trainable params: 74,625.0
Non-trainable params: 0.0

3層~9層すべてのTrain Lossをグラフにしてみます。カッコ内の秒数は、2エポック目に掛かった時間です。だいたいこれが、1エポックあたりの平均の実行時間になります。

ネットワークが深くなるにつれ、どんどん賢くなっていくのがよく分かります。しかし、小さく畳み込まれたのをさらに畳み込んでいっているので、学習時間はあまり増えていきません。とはいえ、パラメータ数はどんどん増えていくので、過学習しやすくなったりはしてそうです。

パディングを入れれば、3×3の畳み込みをもっと重ねていくことは可能ですが、ここから先はひとまず9層で続けていきます。

次は、畳み込み層のフィルターの数を増やしていきたいと思います。まずは48に。

model.add(Conv2D(48, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 48)        912       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 48)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 48)        20784     
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 48)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 48)        20784     
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 48)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 48)        20784     
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 48)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 48)          20784     
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 48)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 48)          20784     
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 48)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 48)          20784     
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 48)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 48)          20784     
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 48)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 48)          20784     
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 48)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           49        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 167,233.0
Trainable params: 167,233.0
Non-trainable params: 0.0

次は64に。

model.add(Conv2D(64, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 64)        1216      
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 64)        36928     
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 64)        36928     
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 64)        36928     
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 64)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 64)          36928     
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 64)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 64)          36928     
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 64)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 64)          36928     
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 64)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 64)          36928     
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 64)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           65        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 296,705.0
Trainable params: 296,705.0
Non-trainable params: 0.0

フィルター数の違いを、グラフにしてみます。

これも増やせば増やすほど、賢くなっていきますが、学習時間の増え方もすごいですね。フィルター数64の時のTotal paramsは30万近くに… 身の丈に合っていないような気がするので(笑)、ここから先はひとまずフィルター数は32で続けていきます。

ここまでは、ネットワーク構成をいろいろ試してきましたが、ここで一度、ネットワークに対する入力を変更してみたいと思います。今現在は、石の配置の2面だけですが、これに「その場所の石のダメの数」を加えた3面にしてみました。数値はtanh(ダメの数*0.05)して0と1の間に収めました(0~1に正規化するのは、Kerasのサンプルがそうなっていたので)。ダメの数/256min(1, ダメの数/32)など、まあ何でもいいような気はします。ところで19路盤の最大ダメ数っていくらなんでしょう?

ダメなしとダメありとでの違いを、グラフにしてみます。

うーん、ちょっと効果が薄いですね。実は劇的に良くなるかと期待していたのですが… ダメの数は特に必要な情報でないからなのか、石の配置を見ればそんなことは分かるからなのかちょっとはっきりしませんが、ネットワークへの入力でがんばれることは、意外とあんまり無いのかもしれません。とはいえ、効果が全く無いわけではないので、ここから先はひとまず入力はダメありの3面で続けていきます。

次は、みんな大好き(笑)Batch Normalizationです。私は当初、Batch Normalizationって畳み込み層や全結合層の前に置くものだと、完全に思い込んでいたのですが、どうやら活性化関数の前に置くのが正しい? そのあたりも含めて調べてみます。

まずは、活性化関数の前にBatch Normalizationを置くバージョン。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        896       
_________________________________________________________________
batch_normalization_1 (Batch (None, 17, 17, 32)        128       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
batch_normalization_2 (Batch (None, 15, 15, 32)        128       
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
batch_normalization_3 (Batch (None, 13, 13, 32)        128       
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
batch_normalization_4 (Batch (None, 11, 11, 32)        128       
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
batch_normalization_5 (Batch (None, 9, 9, 32)          128       
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
batch_normalization_6 (Batch (None, 7, 7, 32)          128       
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
batch_normalization_7 (Batch (None, 5, 5, 32)          128       
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
batch_normalization_8 (Batch (None, 3, 3, 32)          128       
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
batch_normalization_9 (Batch (None, 1, 1, 32)          128       
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 76,065.0
Trainable params: 75,489.0
Non-trainable params: 576.0

長い…(笑) 次は、活性化関数の後にBatch Normalizationを置くバージョン。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        896       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
batch_normalization_1 (Batch (None, 17, 17, 32)        128       
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
batch_normalization_2 (Batch (None, 15, 15, 32)        128       
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
batch_normalization_3 (Batch (None, 13, 13, 32)        128       
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
batch_normalization_4 (Batch (None, 11, 11, 32)        128       
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
batch_normalization_5 (Batch (None, 9, 9, 32)          128       
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
batch_normalization_6 (Batch (None, 7, 7, 32)          128       
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
batch_normalization_7 (Batch (None, 5, 5, 32)          128       
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
batch_normalization_8 (Batch (None, 3, 3, 32)          128       
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
batch_normalization_9 (Batch (None, 1, 1, 32)          128       
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 76,065.0
Trainable params: 75,489.0
Non-trainable params: 576.0

これも、グラフにします。今回はValidate Loss付きです。

もう少したくさん学習させてみないと、最終的に収束した時のLossが低くなるのか、学習が速いだけなのか、よく分かりませんが、なんにせよ、とりあえずBatch Normalizationはすばらしい! Validate Lossも10をはっきりと切ってきました。みんな大好きBatch Normalization、僕も大好きです(笑)。1エポックあたりの学習時間は大幅に増えて、たしかに重いのは重いんですが、学習が速くなるのであれば、それも少なくともある程度はペイしそうです。

そして、先ほどの「Batch Normalizationは活性化関数の前なのか後なのか問題」ですが、今回のケースでは、「活性化関数の後」が良さそうです。平均した数字だけ見てもそうなのですが、「活性化関数の前」のValidate Lossの上下にバタバタする感じがちょっと気持ち悪い… ただ、今回は検証用データの量が絶対的に少ないので、もう少しちゃんと調べないと、はっきりしたことは言えません。

今後は、ひとまず「活性化関数の後」にBatch Normalizationを置く形で続けていきます。

いやあ、それにしても長い記事になりました。しかも、まだぜんぜん終わってない… たぶん、大量に追記することになります。

ここまで、やってきて一番思うのは、「学習データって大事」ってことです。これは量も質もですね。最初始めた時、「学習データなんてなんでもいいよ。俺はディープなラーニングがしたいだけなんだよ」って思ってた自分を引っ叩いてやりたい(笑)。ただ、当初はここまで良い数字が出るとは、正直思ってなかったから、ということもあります。例えば、今現在のようなネットワーク構成でも、もう少しネットワークを深くして、もう少しフィルターを増やして、学習データ増やして、ワンコインぐらい課金すれば、Validate Lossが7ぐらいまでいけそうですが、そこまでいければ、今回の評価関数は(も?)同じような盤面は同じように形勢判断を間違えるのだろうと思うので、深さ1の全幅と組み合わせて、GNU Goぐらいならなんとか勝てないでしょうか? もし仮にそれができたら、Keras.js使って、もうこれは何て言うか一丁上がりなんですが、なかなか事前にそこまで夢みることはできませんでした。

そういう訳で、とにかく学習データです。今現在、COSUMIのサーバを使って学習データを大量に作成中です(負荷が低くなったら、自動的に作り始めるようにした)。それができあがったら、また引き続きいろいろ試してみたいと思います。

Amazon EC2でDarkForestを動かしてみた

Amazon EC2のg2.2xlargeでDarkForestを動かしてみました。G2はGPUなインスタンスファミリーです。基本的にドキュメントどおりで、あまり中身のある内容ではありませんが、以下、簡単に手順を書いていきたいと思います。

まずはともあれ、DarkForestのドキュメントに目を通しておきます(g2.2xlargeはそんなに安くありませんので(笑)、インスタンス立ち上げる前に準備をしっかりしとかないとね!)。

darkforestGo/README.md at master ・ facebookresearch/darkforestGo ・ GitHub
https://github.com/facebookresearch/darkforestGo/blob/master/README.md

EC2は初めて使ったのですが、インスタンスの使用数に制限があって、私の場合は、なんとg2.2xlarge0(!)でした。制限緩和のリクエストは可能ですが、承認されるのに私の場合で半日ほど掛かりましたので、使用の予定がある時は、早めに確認しておくことをお勧めします。

OSは、Ubuntu 16.04で今回はいきたいと思います。Ubuntu初めて触りました。初めてだらけです。こちらのページで、Version16.04 LTSを選び、Instanch Typehvm:ebs-ssdを選びして(他との違いがよく分かりませんが…)、最後にZoneus-west-2(オレゴン)なami-191fd379に決定しました。

インスタンスを起動したら、まず最初にこちらに書かれていることを全部やります。ただし、cuDNNはcudnn-7.5-linux-x64-v5.1-rc.tgzを使用してみました。cuDNNのダウンロードには、NVIDIAのAccelerated Computing Developer Programへの登録が必要です。

Ubuntu 16.04へのCUDAインストール方法 – Qiita
http://qiita.com/yukoba/items/3692f1cb677b2383c983

次にTorchです。こちらを参考にします。

Torch | Getting started with Torch
http://torch.ch/docs/getting-started.html

言われるようにやっていきます。

$ git clone https://github.com/torch/distro.git ~/torch --recursive
$ cd ~/torch
$ bash install-deps
$ ./install.sh
$ source ~/.bashrc
$ luarocks install class
$ luarocks install image
$ luarocks install tds
$ luarocks install cudnn

そして、本題のDarkForest。まずはコンパイル。

$ git clone https://github.com/facebookresearch/darkforestGo.git ~/darkforest --recursive
$ cd ~/darkforest
$ sh ./compile.sh

次に、モデルファイルを用意します。

$ mkdir ~/darkforest/models

作ったmodelsディレクトリにこちらのファイルを(よく分からんから全部)入れておきます。

Dropbox – df_models
https://www.dropbox.com/sh/6nm8g8z163omb9f/AABQxJyV7EIdbHKd9rnPQGnha?dl=0

次に、pipeファイル用のディレクトリをどこか適当な場所に作ります。

$ mkdir ~/df_pipe

ここまでで、準備は完了です。そして、実際にDarkForestを動かすためには、まずGPUサーバを動かします。ここで、先ほどのpipeファイル用のディレクトリを指定してください。

$ cd ~/darkforest/local_evaluator
$ sh cnn_evaluator.sh 1 ~/df_pipe

そして、本体を動かします。再度、先ほどのpipeファイル用のディレクトリを指定してください。

$ cd ~/darkforest/cnnPlayerV2
$ th cnnPlayerMCTSV2.lua --pipe_path ~/df_pipe

これでGTPコマンドを受け付けてくれるようになります。cnnPlayerMCTSV2.luaにはオプションがいろいろあるので確認してみてください。ただ、MCTSではないPure-DCNN playerCNNPlayerV3.luaっていうのもあるのですが、こいつが動いてくれません(本当は、こっちに興味があったのですが…)。df.binは、代わりにdf2.binとかを使えばいいのかもしれませんが、value_model.binっていうのがどこにも見当たりません。残念です。

最後に、gogui-twogtpで取った棋譜を3局載せておきます。3局とも、黒が--time_limit 10で、白が--time_limit 20です(実際の消費時間は、白が黒の約1.37倍)。


Sorry, your browser doesn’t support WGo.js.

Sorry, your browser doesn’t support WGo.js.

Sorry, your browser doesn’t support WGo.js.

はっきりとは棋力が分かりませんが、とりあえず私よりは間違いなく強そう…(笑) とはいえ、私はこの3局以外にも何局か棋譜を確認しましたが、あきらかにおかしな手が結構あります。一種の攻め合いのような時が多いように思えますが、例えば、3局目の291手目(同じく292手目、293手目、ついでに295手目!)とかやばすぎる… 「MCTSは攻め合いが…」とかそんなレベルではないと思うし、というか、これはもうただのアタリアタリですしね。なんか致命的なのが、コードに残っているような気がします。

あと、部分部分でDarkForestがものすごく好む形っていうのがいろいろありますね。いくつかの棋譜を続けて見ていたら、「あれっ、これ今さっき見たやつじゃない?」ってなるぐらい、部分的に同じような形のオンパレードになります。ひとつだけ例をあげると、星に小ゲイマに掛かられた時、ほぼ例外なくケイマか一間に受けて、周りの状況がどうであれ、ハサミ返すことはしません(私がざっと見たかぎり、約20回中0回でした)。モデル、ひいてはそれを作成するのに使った棋譜によるところが大きいのだろうし、これをDarkForestの特徴とは言っていいのかよく分かりませんが、Fuegoなどでは、あまり感じない傾向だと思います。

先ほどの3局は、並列ではなく一局ずつ打たせてて、全部で3時間以上掛かっています。そして、あまりよく分からないですが、GPUはだいたい使い切っているように見えます。もちろん、消費リソースをもっと絞って打たせることはできますが、やっぱりお金が掛かりますね。「DarkForestを、なんらかの形でCOSUMIで使えたら…」と思ったのですが、簡単ではないなあ… また、しばらく考えておきます。

/proc/loadavgの謎

CentOS 5.5なCOSUMIのサーバでcat /proc/loadavgとすると、ごくまれに最後の改行が1つではなく2つになっていることがあります。どうでもいいような細かい話ではありますが(笑)、しかしたぶんこれは意図されていない出力形式だと思います。

$ cat /proc/loadavg
5.02 4.74 4.32 4/245 6023
$ cat /proc/loadavg
5.02 4.74 4.32 9/257 6072

$ cat /proc/loadavg
5.02 4.74 4.32 12/267 6146

そもそもなぜこれに気づいたかというと、この出力内容をログに取り続けているからなんですが、最近、そのログを見ていた時に、偶然、改行が2つの時だけに見られる奇妙な規則性に気がつきました。その規則性とは、「4カラム目のスラッシュの左側(実行中のプロセス数)が9」ということです(ほんとよく気がついたと思う(笑))。

5.02 4.74 4.32 9/257 6072

簡単なスクリプトを書いてさらにもう少し詳しく調べたところ、分かったのは、

  • 89の時しか改行が2つにならない
  • 8の時に改行が2つになる確率は、だいたい1%ぐらい。ごくまれ
  • 9の時に改行が2つになる確率は、だいたい50%ぐらい。こっちは多い

ということです。さらにさらに、以前使ってたサーバのログなども調べてみると、同じような条件で同じく改行2つになっている時があるのはあるのですが、発生頻度が今より少なかったです。初代、二代目、三代目(現在)となるにつれて、改行2つになる確率が増えていきます。ついでにいうと、簡単にしか調べてませんが、手元のPCでは改行2つが再現しませんでした。

一体なぜこういうことになるんでしょうか? 皆さんの環境でも同じことが起こりますか? とにかく不思議で不思議でしかたありません…

さくら専用サーバエントリープランの使用感

COSUMIは、さくらの専用サーバエントリープランで現在運営しています。このサーバを借り始めてから半年近く経ったので、このあたりで使ってみた感想を記事にしておきたいと思います。と言っても、ごく単純なウェブサーバとして使っているだけなので、高度な内容の話はなにもありません。

専用サーバ|エントリー:月額7800円ではじめられるさくらの専用レンタルサーバ
http://server.sakura.ad.jp/dedicated/entry/

ハードウェアはCeleron 215、メモリ 1GB、HDD 80GBとかなりミニマムなスペックです(なんて言ったら贅沢か(笑))。Celeron 215ってのが調べてもあまりよく分かりませんが、一応こんな感じのCPUです。

$ cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 14
model name	: Intel(R) Celeron(R) CPU          215  @ 1.33GHz
stepping	: 8
cpu MHz		: 1333.439
cache size	: 512 KB
fdiv_bug	: no
hlt_bug		: no
f00f_bug	: no
coma_bug	: no
fpu		: yes
fpu_exception	: yes
cpuid level	: 10
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat clflush
 dts acpi mmx fxsr sse sse2 ss tm pbe nx constant_tsc up pni monitor tm2 xtpr
bogomips	: 2667.65

なにを見てそう勘違いしたのか、借りる前はデュアルコアのCPUだと思ってたのですが、ではないです(泣)。ただしこちらからダウンロードしてきたSuperπでベンチとってみると、そこそこ速そう。

Start of PI calculation up to 1048576 decimal digits
 End of initialization. Time=       0.651 Sec.
 I= 1 L=       0        Time=       2.072 Sec.
 I= 2 L=       0        Time=       2.379 Sec.
 I= 3 L=       1        Time=       2.381 Sec.
 I= 4 L=       2        Time=       2.378 Sec.
 I= 5 L=       5        Time=       2.383 Sec.
 I= 6 L=      10        Time=       2.377 Sec.
 I= 7 L=      21        Time=       2.381 Sec.
 I= 8 L=      43        Time=       2.377 Sec.
 I= 9 L=      87        Time=       2.380 Sec.
 I=10 L=     174        Time=       2.377 Sec.
 I=11 L=     349        Time=       2.380 Sec.
 I=12 L=     698        Time=       2.376 Sec.
 I=13 L=    1396        Time=       2.379 Sec.
 I=14 L=    2794        Time=       2.374 Sec.
 I=15 L=    5588        Time=       2.371 Sec.
 I=16 L=   11176        Time=       2.358 Sec.
 I=17 L=   22353        Time=       2.324 Sec.
 I=18 L=   44707        Time=       2.266 Sec.
 I=19 L=   89415        Time=       2.104 Sec.
 End of main loop
 End of calculation.    Time=      46.937 Sec.
 End of data output.    Time=       0.195 Sec.
 Total calculation(I/O) time=      47.132(       1.321) Sec.

COSUMIはCPUがボトルネックなので、このあたりの数字が結構気になるのですが、とりあえずOKかな? ただ、今現在さくらのサイトでは、CPUはIntel CeleronまたはIntel Atomってなっていますので注意してください。Atomにもいろいろ種類があると思うのですが、どうなんでしょうか?

OSはCentOS 5のみです。上位のプランだともっとたくさんの種類の中から選べるのですが、個人的には他の選択肢があったとしてもCentOS 5を選ぶと思うので、別に構いません。

OSは選択できませんが、パッケージ構成は、「標準構成」と「最小構成」の二つから選べます。うちは最小構成なんですが、今さくらのサイト見てみると、最初はウェブサーバも入ってなかったんですね。自分で入れた記憶がちょっと無いんですが…(笑) 参考までに今現在こんな感じ。

$ rpm -qa | wc -l
478

自分で入れたパッケージはそんなにたくさんはないはずです。

それから、標準サービスとしてトラフィックレポートを見ることができるのがGood! サーバでの設定等は一切必要ありません。

専用サーバ|トラフィックレポート – さくらインターネット
http://server.sakura.ad.jp/dedicated/service/traffic.html

全体的な感想としてはかなり満足です。とりあえず今までのところ安定して動いてますし、サーバのレスポンスも悪くないと思うので。価格もまあ妥当ではないでしょうか? 初期費用が0っていうのは、気軽に始められるのですごくいいです! だめならだめでどこかに乗り換えればいいだけですしね。月額10,000円以下の専用サーバを探している時は、候補に入れてみてはいかがでしょうか?

[追記]
他の方が書かれたこちらの記事も参考にしてみてください。

さくらの専用サーバーエントリープランが良かった6つの理由(+嫌な点3つ)
http://p0t.jp/archives/2008/10/post-35.html

Linuxでファイルサイズを0にする

2008.06.09  |  Linux  |  Comments (0)

今回は、最近いろいろとさわる機会の多かったLinuxの話です。

大きくなったログファイルなどを、一旦クリアしてしまいたい時ってありますよね。rmしてtouchとかでもできなくはないですが、パーミッションを設定し直したりしないといけなかったりするので、あまりスマートな方法ではなさそうです。こういう時、普通はどうするのかなと思って調べていたら、こんなページが見つかりました。

ファイルを空にする – 揮発性のメモ
http://d.hatena.ne.jp/iww/20071007/cat

$ :> foo.log

おお、こんなやり方があるんですね。知りませんでした。しかし、このスマイリーみたいな:>の意味が分かりません。とりあえず、試しに目と口の間に(笑)スペースを入れてみます。

$ : > foo.log

これも先ほどと同じ結果になりました。ということは:>は別々のようです。>はリダイレクトですが、では一体:ってなんなのでしょうか? 記号ってほんと検索しにくいのですが(笑)がんばって調べてみると、こちらのページ曰く、何もせず,0を返すということだそうです。ということは、こんなのと一緒でしょうか?

$ echo > foo.log

と思ったら、ファイルサイズが1バイトになってしまいます。テキストエディタで開くと、改行が一つ入っていました。こういう時は

$ echo -n > foo.log

-nオプションを付けるといいようです。このあたりで気づいたのですが、これでもいいようです。

$ > foo.log

シンプル! 勉強になりました。ちなみに普通はこうするらしい。

$ cp /dev/null foo.log

まあそうですよね。たぶんこれが一番安全です。

[追記]
こちらのサイトも参考になると思います。