将棋AIで学ぶディープラーニング on Mac and Google Colab

AI

[方策ネットワーク]
第7章8

GPUとCPUの自動使い分け

PC環境をgethostname()で自動判別しiMac環境の場合GPU不使用、Google Colab環境の場合GPU使用とする処理を追加。

import socket
host = socket.gethostname()
# IPアドレスを取得
# iMac          : xxxxxxxx
# Lenovo        : yyyyyyyy
# google colab  : ランダム

if host == 'xxxxxxxx':
    gpu_en = 0
elif host == 'yyyyyyyy':
    gpu_en = 0
else:
    gpu_en = 1
if gpu_en == 1:
    from chainer import cuda, Variable
if gpu_en == 1:
    model.to_gpu()
    if gpu_en == 1:
        return(Variable(cuda.to_gpu(np.array(mini_batch_data, dtype=np.float32))),
               Variable(cuda.to_gpu(np.array(mini_batch_move, dtype=np.int32))))
    elif gpu_en == 0:
        return np.array(mini_batch_data, dtype=np.float32), np.array(mini_batch_move, dtype=np.int32)

pickleのプロトコル

google colabでのpickle作成が遅いため、一旦iMac側でpickle作成まで行った後、そのpickleファイルをcolabで読み込ませて学習させようとしたらプロトコル非対応のエラーが出た。
原因は下記。
pickleはPython 3.8以上でプロトコル5に対応した。
iMacはPython 3.8.2
colabはPython 3.6.9
iMacでpickle.dump(positions_train, f, pickle.HIGHEST_PROTOCOL)するとプロトコル5のpickleファイルが作成される。
このpickleファイルを使ってColabで学習しようとしてもプロトコル未対応のエラーが出てColabが読み込めない。
pickle.HIGHEST_PROTOCOLを消すとデフォルトのプロトコル4でpickleファイルが作成される。
これでColabで読み込める。

train_policy.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 環境設定
#-----------------------------
import socket
host = socket.gethostname()
# IPアドレスを取得
# iMac          : xxxxxxxx
# Lenovo        : yyyyyyyy
# google colab  : ランダム

if host == 'xxxxxxxx':
    gpu_en = 0
elif host == 'yyyyyyyy':
    gpu_en = 0
else:
    gpu_en = 1

# その他注意事項
# google colabでのpickle作成が遅く、ローカルでpickleを作成してgoogle colabで読み込ませたい場合、
# pickle化のプロトコルオプションを消す(デフォルトにする)。
# colabのPython verが3.8になっていないためHighestのプロトコルが非対応のため。
#  pickle.dump(positions_train, f, pickle.HIGHEST_PROTOCOL)の行
#-----------------------------

import numpy as np
import chainer
if gpu_en == 1:
    from chainer import cuda, Variable
from chainer import optimizers, serializers
import chainer.functions as F

from pydlshogi.common import *
from pydlshogi.network.policy import PolicyNetwork
from pydlshogi.features import *
from pydlshogi.read_kifu import *

import argparse
import random
import pickle
import os
import re

import logging

parser = argparse.ArgumentParser()
parser.add_argument('kifulist_train', type=str, help='train kifu list')
parser.add_argument('kifulist_test', type=str, help='test kifu list')
parser.add_argument('--batchsize', '-b', type=int, default=32, help='Number of positions in each mini-batch')
parser.add_argument('--test_batchsize', type=int, default=512, help='Number of positions in each test mini-batch')
parser.add_argument('--epoch', '-e', type=int, default=1, help='Number of epoch times')
parser.add_argument('--model', type=str, default='model/model_policy', help='model file name')
parser.add_argument('--state', type=str, default='model/state_policy', help='state file name')
parser.add_argument('--initmodel', '-m', default='', help='Initialize the model from given file')
parser.add_argument('--resume', '-r', default='', help='Resume the optimization from snapshot')
parser.add_argument('--log', default=None, help='log file path')
parser.add_argument('--lr', default=0.01, type=float, help='learning rate')
parser.add_argument('--eval_interval', '-i', default=1000, type=int, help='eval interval')
args = parser.parse_args()

logging.basicConfig(format='%(asctime)s\t%(levelname)s\t%(message)s',
                    datefmt='%Y/%m/%d %H:%M:%S', filename=args.log, level=logging.DEBUG)

model = PolicyNetwork() #外部モジュールで自作のモデル

if gpu_en == 1:
    model.to_gpu()

optimizer = optimizers.SGD(lr=args.lr) #SGDクラスのインスタンスを生成
# optimizer = optimizers.MomentumSGD(lr=args.lr, momentum=0.9) #MomentumSGDクラスのインスタンスを生成
optimizer.setup(model)

# Init/Resume
if args.initmodel:
    logging.info('Load model from {}'.format(args.initmodel))
    serializers.load_npz(args.initmodel, model)
if args.resume:
    logging.info('Load optimizer state from {}'.format(args.resume))
    serializers.load_npz(args.resume, optimizer)

logging.info('read kifu start')

# 保存済みのpickleファイルがある場合、pickleファイルを読み込む
# train data
# 訓練用棋譜リスト(リストの中身ではなくリスト本体)の拡張子を検索し、削除し、.pickleを付けた文字列を変数に格納する。
train_pickle_filename = re.sub(r'\..*?$', '', args.kifulist_train) + '.pickle'
if os.path.exists(train_pickle_filename):
    with open(train_pickle_filename, 'rb') as f: # train_pickle_filenameに入っているのはread_kifuの出力position[([piece_bb 15要素], [occupied 2要素], [pieces_in_hand 2要素], [move_label 1要素], [win 1要素]), (同じセット), ・・・が局面数 x 対局数]
        positions_train = pickle.load(f)
    logging.info('load train pickle')
else:
    positions_train = read_kifu(args.kifulist_train)

# test data
test_pickle_filename = re.sub(r'\..*?$', '', args.kifulist_test) + '.pickle'
if os.path.exists(test_pickle_filename):
    with open(test_pickle_filename, 'rb') as f:
        positions_test = pickle.load(f)
    logging.info('load test pickle')
else:
    positions_test = read_kifu(args.kifulist_test)

# 保存済みのpickleが無い場合、上記elseで読み込んだ変数の中身をpickleファイルにダンプ(入れる)して保存する
if not os.path.exists(train_pickle_filename):
    with open(train_pickle_filename, 'wb') as f: # 空のpickleファイルを開く
        pickle.dump(positions_train, f, pickle.HIGHEST_PROTOCOL) # 開いたpickleファイルにデータを入れる。
    logging.info('save train pickle')
if not os.path.exists(test_pickle_filename):
    with open(test_pickle_filename, 'wb') as f:
        pickle.dump(positions_test, f, pickle.HIGHEST_PROTOCOL)
    logging.info('save test pickle')
logging.info('read kifu end')

logging.info('train position num = {}'.format(len(positions_train))) # positionsの一番外の要素数 = 局面数が出力される
logging.info('test position num = {}'.format(len(positions_test)))

# mini batch
def mini_batch(positions, i, batchsize):
    mini_batch_data = []
    mini_batch_move = []
    for b in range(batchsize):
        features, move, win = make_features(positions[i + b]) # positionsの一番外の要素番号、つまり局面をforループする
        mini_batch_data.append(features) # 局面ごとのfeaturesをappendしていく = 方策ネットワークの入力データ
        mini_batch_move.append(move)    # 局面ごとの指し手をappendしていく = 方策ネットワークの教師データ
    if gpu_en == 1:
        return(Variable(cuda.to_gpu(np.array(mini_batch_data, dtype=np.float32))),
               Variable(cuda.to_gpu(np.array(mini_batch_move, dtype=np.int32))))
    elif gpu_en == 0:
        return np.array(mini_batch_data, dtype=np.float32), np.array(mini_batch_move, dtype=np.int32)

def mini_batch_for_test(positions, batchsize):
    mini_batch_data = []
    mini_batch_move = []
    for b in range(batchsize):
        features, move, win = make_features(random.choice(positions)) # positionsからランダムに選ぶ
        mini_batch_data.append(features)
        mini_batch_move.append(move)
    if gpu_en == 1:
        return(Variable(cuda.to_gpu(np.array(mini_batch_data, dtype=np.float32))),
               Variable(cuda.to_gpu(np.array(mini_batch_move, dtype=np.int32))))
    elif gpu_en == 0:
        return np.array(mini_batch_data, dtype=np.float32), np.array(mini_batch_move, dtype=np.int32)

# ↑学習の準備

# # ↓学習のループ
logging.info('start training')
itr = 0
sum_loss = 0
for e in range(args.epoch):
    positions_train_shuffled = random.sample(positions_train, len(positions_train))
    # positions_trainは[([piece_bb 15要素], [occupied 2要素], [pieces_in_hand 2要素], [move_label 1要素], [win 1要素]), (同じセット), ・・・が局面数 x 対局数]
    # random.sample(a,b) aからランダムにb要素を返す

    itr_epoch = 0
    sum_loss_epoch = 0
    for i in range(0, len(positions_train_shuffled) - args.batchsize, args.batchsize):
        # 順伝搬
        x, t = mini_batch(positions_train_shuffled, i, args.batchsize) # x:局面図=入力、t:指し手=教師データ
        y = model(x) # modelは外部モジュールで自作のモデル
        model.cleargrads() # 勾配の初期化
        # 損失計算
        loss = F.softmax_cross_entropy(y, t)
        # 逆誤差伝搬
        loss.backward() # 勾配を計算
        # 勾配を使ってパラメータを更新
        optimizer.update()

        itr += 1
        sum_loss += loss.data
        itr_epoch += 1
        sum_loss_epoch += loss.data

        # 一定間隔置きに評価(eval_intervalごとに実行)
        # print train loss and test accuracy
        if optimizer.t % args.eval_interval == 0: # a % b はaをbで割った余りを返す。tは更新ステップ。update()でインクリされる。
            x, t = mini_batch_for_test(positions_test, args.test_batchsize) # x = 局面図、t = 指し手
            y = model(x)
            logging.info('epoch = {}, iteration = {}, loss = {}, accuracy = {}'.format(
                        optimizer.epoch + 1, optimizer.t, sum_loss / itr, F.accuracy(y,t).data))
            itr = 0
            sum_loss = 0

    # validate test data
    logging.info('validate test data')
    itr_test = 0
    sum_test_accuracy = 0
    for i in range(0, len(positions_test) - args.batchsize, args.batchsize): # positions_testはkifulist_testからread_kifuした結果を代入したもの
        x, t = mini_batch(positions_test, i, args.batchsize) # 訓練のときと全く同じ
        y = model(x)
        itr_test += 1
        sum_test_accuracy += F.accuracy(y, t).data
    logging.info('epoch = {}, iteration = {}, train loss avr = {}, test accuracy = {}'.format(
                  optimizer.epoch + 1, optimizer.t, sum_loss_epoch / itr_epoch, sum_test_accuracy / itr_test))

    # 1エポック終了したらオプティマイザに次のエポックを処理することを通知
    optimizer.new_epoch()

# 全てのエポックの学習が終わった後、モデルとオプティマイザの状態を保存
logging.info('save the model')
serializers.save_npz(args.model, model)
print('save the optimizer')
serializers.save_npz(args.state, optimizer)

コメント

タイトルとURLをコピーしました