3目並べを利用したAlphaGoの学習 tensorflow-1編

1.概要

この記事は2023年の記事です。3目並べをプログラムすることでAlphaGoの学習をすることが目標でした。Tensorflow-1.0を利用した最適解の計算手順は当時とても苦労をしたので備忘録として残すことにしました。

AlphaGoの勉強過程で3目並べを学んでいます。前回までプログラムロジックを作成していましたが、今回から3目並べの全ての組合せデータを利用してAIでプログラミングに勝てるかを試します。DeepLearningとしてtensorflowの1.xと2.xの両方を試してみます。

DeepLearningでは、3目並べのフィールドを3×3のイメージと考え、手書き文字認識の手法を応用します。本記事では、以下の3つのステップを順に実装していきます。

(1) minimax法の解析を利用して学習用入力データを作成
(2) 作成した学習用入力データを利用して、tensorflowでモデル作成
(3) tensorflowのモデルを利用して実際に対戦

追加部分には、Tesnsorflowをnvidia GPUを利用するために工夫した内容を追記しています。
・CUDA-10.0を利用する方法
・nVIDIA Container Toolkitを利用する方法
・miniconda3を利用する方法

2.詳細

(a) 概要

3目並べのフィールドを3☓3のイメージと考えて、手書き文字認識の手法を利用します。最初に利用する環境はtensorflow-1.15です。入力データはminimax法で活用したすべての手順(9!=362880)の組み合わせの中から勝負が決まった時点の3目並べのフィールド情報と結果(勝ち、負け、引き分け)を利用します。

tensorflowで利用できる形式に変換し、学習をしてモデルを作成し、モデルを利用して3目並べの対戦をします。元情報がminimax法で解析した情報なので、tensorflowによる学習結果がminimax法まで到達できると最高の結果です。大まかな手順は以下の通りです。

(b) 詳細

(1) minimax法の解析を利用して学習用入力データを作成

dlmakedata.pyを作成します。この中で利用するtictactoe.pyはmontecarlo版を利用します。
titactoeのコードは本ブログのTictactoe like the montecarlo(2023/12/30参照)

学習用データは、dl1_data.npy(フィールドデータ)、dl2_data.npy(結果データ)です。
プログラムを実行した結果、学習用データの件数は、255,168件でした。
これは9!=362880よりも少なくなります。最低5手で勝負が決着する場合などがあるためです。

from tictactoe import Tictactoe
import numpy as np

def minimax_select(actions):

    r1 = []
    r2 = []

    for action in actions:
        score  = obj.do_game(action)
        minimax(obj.next_action(), r1, r2)
        obj.undo_game(action)

    return [r1, r2]

def minimax(actions, r1, r2):

    for action in actions:
        score = obj.do_game(action)
        if    score == 1:
                 s1 = ",".join(map(str,obj.fields))
                 s2 = "1,0,0"
                 r1.append(s1)
                 r2.append(s2)
        elif  score == -1:
                 s1 = ",".join(map(str,obj.fields))
                 s2 = "0,1,0"
                 r1.append(s1)
                 r2.append(s2)
        elif  score == 0:
                 s1 = ",".join(map(str,obj.fields))
                 s2 = "0,0,1"
                 r1.append(s1)
                 r2.append(s2)
        else:
              minimax(obj.next_action(), r1, r2)

        obj.undo_game(action)

def string_to_array(strlist):
    
    r1 = []

    for item in strlist:
        f1 = item.split(",")
        l1 = []
        for s1 in f1:
            l1.append(int(s1))
        r1.append(l1)

    a1 = np.array(r1)
    a2 = a1.astype(np.float32)

    return a2

if  __name__  ==  "__main__":
    obj = Tictactoe()
    actions = [0,1,2,3,4,5,6,7,8]
    result  = minimax_select(actions)

    print(len(result[0]), len(result[1]))

    r1 = string_to_array(result[0])
    r2 = string_to_array(result[1])
    print(r1, r1.dtype, r1.shape)
    print(r2, r2.dtype, r2.shape)

    np.save('dl1_data', r1)
    np.save('dl2_data', r2)

    y1 = np.load('dl1_data.npy')
    y2 = np.load('dl2_data.npy')

    print(y1, y1.dtype, y1.shape)
    print(y2, y2.dtype, y2.shape)

(2) 作成した学習用入力データを利用して、tensorflowでモデル作成

dltensorflow.pyを作成します。動作環境はtensorflow-1.15の環境です。

学習用データは、r1_data.npy(フィールドデータ)、r2_data.npy(結果データ)で、件数は、255,168件です。これを75%のトレーニングデータと25%のテストデータに分割して動作確認後、再度100%のデータを利用して、もう一度トレーニングをしてモデルを作成、保存します。

下記コードのコメント部分が最初のトレーニング部分です。現在は100%でトレーニングの状態になっています。
完成したモデルは、dlmodel.h5で保存します。

import tensorflow as tf
from tensorflow.keras.layers import Activation, Dense, Dropout
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.optimizers import SGD

import numpy as np
import matplotlib.pyplot as plt

images = np.load('r1_data.npy')
labels = np.load('r2_data.npy')
print(images.shape, labels.shape)

count = int(images.shape[0] * 0.75)

train_images, test_images = np.split(images, [count])
print(train_images.shape, test_images.shape)

train_labels, test_labels = np.split(labels, [count])
print(train_labels.shape, test_labels.shape)

model = Sequential()
model.add(Dense(64, activation='sigmoid', input_shape=(9,)))
model.add(Dense(32, activation='sigmoid'))
model.add(Dropout(rate=0.5))
model.add(Dense(3, activation='softmax'))

model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1),
        metrics=['acc'])

#history = model.fit(train_images, train_labels, batch_size=500,
#        epochs=50, validation_split=0.2)

history = model.fit(images, labels, batch_size=500,
        epochs=100, validation_split=0.2)

plt.plot(history.history['acc'], label='acc')
plt.plot(history.history['val_acc'], label='val_acc')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(loc='best')
plt.show()

model.save('dlmodel.h5')
model = load_model('dlmodel.h5')

test_loss, test_acc = model.evaluate(test_images, test_labels)
print('loss: {:.3f}\nacc: {:.3f}'.format(test_loss, test_acc))

(3) tensorflowのモデルを利用して実際に対戦

ttttensorflow.pyを作成します。動作環境はtensorflow-1.15環境です。この中で利用するtictactoe.pyはmontecarlo版を利用します。

トレーニングしたモデル(dlmodel.h5)をロードします。最初、モデルの結果だけを利用したのですが、minimax法と同様にリーチ目を認識できません。そこで、alphabeta法で利用したis_reach()も利用しています。感触的にはminimax法と同等程度の手を打つようです。

from tictactoe import Tictactoe
import random

import tensorflow as tf
from tensorflow.keras.models import load_model
import numpy as np

def random_select(actions):
    index = random.randint(0, len(actions) - 1)
    return actions[index]

def input_select(actions):
    while True:
        print(actions)
        action = int(input('select actions='))
        if action in actions:
            break
        else:
            print('input again')
    return action
            
def tensorflow_select(actions):
    model = load_model('dlmodel.h5')
    if (len(actions) % 2) == 1:
        flg = 1
    else:
        flg = 2
    result = []
    for action in actions:
        reach  = obj.is_reach()
        if reach != None:
            print("reach action ", reach)
            return reach
        score  = obj.do_game(action)
        f1 = [obj.fields]
        a1 = np.array(f1)
        a2 = a1.astype(np.float32)
        predictions = model.predict(a2)
        l1 = predictions.tolist()
        l1[0].append(action)
        result.append(l1[0])
        obj.undo_game(action)

    maxvalue  = -1
    maxaction = None
    for item in result:
        value = item[flg-1]
        if value > maxvalue:
            maxvalue  = value
            maxaction = item[3]

    return maxaction

def montecarlo_select(actions):
    if (len(actions) % 2) == 1:
        flg = 1
    else:
        flg = 2
    result = []
    for action in actions:
        reach  = obj.is_reach()
        if reach != None:
            print("reach action ", reach)
            return reach
        score  = obj.do_game(action)
        init   = [action,0,0,0]
        minimax(obj.next_action(), init)
        result.append(init)
        obj.undo_game(action)

    print(result)

    maxvalue  = -1
    maxaction = None
    maxlist   = []
    for item in result:
        value = item[flg]
        if value > maxvalue:
            maxvalue  = value
            maxaction = item[0]
            maxlist   = [item[0]]
        elif value == maxvalue:
              maxlist.append(item[0])
    print('maxlist ', maxlist)
    if len(maxlist) != 1:
        maxaction = maxlist[random.randint(0, len(maxlist) - 1)]
        print('maxaction ', maxaction)
    return maxaction

def alphabeta_select(actions):
    if (len(actions) % 2) == 1:
        flg = 1
    else:
        flg = 2
    result = []
    for action in actions:
        reach  = obj.is_reach()
        if reach != None:
            print("reach action ", reach)
            return reach
        score  = obj.do_game(action)
        init   = [action,0,0,0]
        minimax(obj.next_action(), init)
        result.append(init)
        obj.undo_game(action)

    print(result)

    maxvalue  = -1
    maxaction = None
    for item in result:
        value = item[flg]
        if value > maxvalue:
            maxvalue  = value
            maxaction = item[0]

    return maxaction

def minimax_select(actions):
    if (len(actions) % 2) == 1:
        flg = 1
    else:
        flg = 2
    result = []
    for action in actions:
        score  = obj.do_game(action)
        init   = [action,0,0,0]
        minimax(obj.next_action(), init)
        result.append(init)
        obj.undo_game(action)

    print(result)

    maxvalue  = -1
    maxaction = None
    for item in result:
        value = item[flg]
        if value > maxvalue:
            maxvalue  = value
            maxaction = item[0]

    return maxaction

def minimax(actions, result):
    for action in actions:
        score = obj.do_game(action)
        if    score == 1:
                result[1] += 1
        elif  score == -1:
                result[2] += 1
        elif  score == 0:
                result[3] += 1
        else:
              minimax(obj.next_action(), result)
        obj.undo_game(action)

if __name__ == "__main__":

    obj = Tictactoe()
    actions = [0,1,2,3,4,5,6,7,8]

    for i in range(9):

        if  obj.myturn == True:
            print('my turn')
            action = tensorflow_select(actions)
        else:
            print('other turn')
            action = random_select(actions)

        print(actions)
        print("select", action)
        result = obj.do_game(action)
        print(obj.game_state())

        if result == 1:
            print("o Win")
            break;
        if result == -1:
            print("x Win")
            break;
        if result == 0:
            print("Draw")
            break;

        actions = obj.next_action()

4.所見

学習結果でも強い手を打ちますが、チョンボもします。プログラムロジック程度の簡単なことは、DeepLearningを利用して計算で学習できるということです。

3.追加

Tensorflow-1.0環境を構築する上で、当時悩んだことを追記します。

(1) CUDA-10.0

囲碁AIのalphaGOを勉強していますが、環境としてtensorflow-1.xが必要でした。そこで、tensorflow-1.15の環境構築を過去情報を利用して作業を開始したのですが、様々な問題が発生しました。今回はこのことを記載します。少し古いバージョンの環境構築はとても難しいですね。

発生した問題と対応内容

(a) ubuntu-20.04環境にtensorflow-1.15を導入すると、python3 version問題で導入できません。
(b) ubuntu-18.04環境にtensorflow-1.15を導入でき、CPU環境が構築でき一歩前進しました。
(c) GPUを利用するために、ubuntu-18.04にCUDA-10.2を導入して、tensorflow-gpu-1.15を導入しました。しかし、GPU認識を確認すると、10.0のライブラリエラーが発生して、GPUを認識できません。
(d) tensorflow-gpu-1.15は、CUDA-10.0が必須と判明。しかし、nvidiaの情報に従って作業しても、CUDA-10.0は導入できません(Depends: cuda-10-0 (>= 10.0.130) but it is not going to be installed)
(e) 様々な変更を加えても改善しないので、Docker Hubからtensorflow-1.15:5-gpuのimageを利用。ubuntu-20.04のdocker環境で構築できました。
(f) 更にネットを調べると、nvidaのCUDA-10.0の説明資料と異なる点を見つけて、ubuntu-18.04で再実行。これにより、CUDA-10.0が導入でき、無事にtensorflow-gpu-1.15を導入できました。
(g) サンプルプログラムでGPU利用で実行できないコードが見つかり、コードの一部修正が必要と判明しました。

ubuntu-18.04を利用してtensorflow-gpu-1.15をCUDA-10.0の組み合わせで利用できますが、導入後の更新処理やubnutu-20.04環境の優位性などを踏まえると、上記(e)の手順のDocker環境がベストであると感じています。

(2) Deep Learning環境構築をnVIDIA Container Toolkit利用に変えました

GPU利用に関して、nVIDIAのGPU利用には下記製品を導入して利用していました。
・nVIDIAのデバイスドライバー
・CUDA(Compute Unified Device Architecture)
・cuDNN(The NVIDIA CUDA Deep Neural Network library)
・GPU対応のtensorflow
しかし、ubuntuのkernelが更新されると整合性が取れなくなり、再構築をしていました。

nVIDIAはnVIDIA Container Toolkitを提供して、環境構築の問題を改善しています。nVIDIA Container ToolKitに関して記述します。

ubuntu-20.04.4でnVidia 1050Tiを利用します。導入手順は以下の通りです。

(a) ubuntu-20.04.4の標準driver(nouveau)を無効にする
(b) ubuntu-20.04.4にnVidiaの最新ドライバーを導入する
(c) nVIDIA Container Toolkitを導入して、nvidia-smi試験(参考資料)
(d) nVIDIA Runtimeを設定して、docker上でnvidia-smi試験(参考資料)

ここでは、(a)(b)の記述は省略して、(c)(d)に関して記述します。

(c) nVIDIA Container Toolkitを導入して、nvidia-smi試験

Installation Guideを参照します。

・Dockerを導入します(ubuntu 20.04の標準を利用します)
・Setting up NVIDIA Container Toolkit

distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
      && curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
      && curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
            sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
            sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt-get update
sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker
sudo docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi

nvidia-smiの実行結果が表示されます

(d) nVIDIA Runtimeを設定して、docker上でnvidia-smi試験

User Guideを参照します。
記述内容通りでなく、/etc/docker/deamon.jsonを以下のように作成します。
私はvimで作成しました。

{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

一度、PCを再起動します。

docker run --rm --gpus all nvidia/cuda nvidia-smi

上記手順では実行できません。
nvidia/cudaの参考資料に従って、

docker run --rm --gpus all nvidia/cuda:11.0.3-runtime-ubuntu20.04 nvidia-smi

nvidia-smiの動作確認できます。

(3) anaconda3でjupyter notebookのconda updateが終わらない

Dockerを利用してcontainer環境でjupyter notebookを利用しています。3月ころまで利用していた手順でcontainerとanacondaを利用したjupyter環境を構築すると、conda update --all がいつまでも終わりません。ネット環境で終了しない原因を調べても適切な情報が見つかりません。anacondaで調べるとdocker環境での利用方法が変わっていました。これに関して記述します。なお、Docker環境はubuntu-20.04の標準を利用しています。

(a) Docker環境

sudo apt install docker.io

(b) 以前の利用環境

dockerhubのcontinuumio/anaconda3の最新版を利用していました。参考資料のURLに記載されている通りに環境を構築することでjupyter notebookを利用できていました。

docker pull continuumio/anaconda3
docker run -i -t continuumio/anaconda3 /bin/bash

ここで、最新環境への更新をします。

conda update --all

しかし、Executing transaction が終わりません。
以前はこの後に、jupyterを導入して利用していました。

docker run -i -t -p 8888:8888 continuumio/anaconda3 /bin/bash -c "\
    conda install jupyter -y --quiet && \
    mkdir -p /opt/notebooks && \
    jupyter notebook \
    --notebook-dir=/opt/notebooks --ip='*' --port=8888 \
    --no-browser --allow-root"

(c) anacondaのサイト情報

サイト情報を参照するとcontinuumio/miniconda3を利用する記述があります。そこで、dockerhubを調べて、continuumio/miniconda3に変える必要があると思いました。

(d) 変更した利用環境

dockerhubのcontinuumio/miniconda3へ変更しました。参考資料のURLに記載されている通りに環境を構築することでjupyter notebookを利用できました。

docker pull continuumio/miniconda3
docker run -i -t continuumio/miniconda3 /bin/bash

最新環境への更新をします。

conda update --all

更新が終了して無事にjupyter notebookを利用できました

docker run -i -t -p 8888:8888 continuumio/miniconda3 /bin/bash -c "\
    conda install jupyter -y --quiet && \
    mkdir -p /opt/notebooks && \
    jupyter notebook \
    --notebook-dir=/opt/notebooks --ip='*' --port=8888 \
    --no-browser --allow-root"

(4) Anacondaの代替としてminiconda3を利用する

chatGPTに代表される生成AIが注目されていますが、基盤技術の深層学習(Deep Learning)を学習したいと考えている方も多いと思います。DeepLearningの代表格のTensorflowを利用するには様々なライブラリ依存性調整が必要です。そこで利用されているのがAnacondaですが、2020年4月から有償となっています。

Tensorflowを導入することは以前は非常に難しかったのですが、コンテナを利用できるようになって非常に簡単になりました。tensorflowのサイトに記載されているdockerを利用する方法でAVX2をサポートするCPUでは簡単に導入できます。

しかし、AVX2命令を利用できない低価格のCPUでは実行できません。Anacondaを利用するとAVX2を利用しないモジュールの調整を実施してくれます。無償で学習用途として低価格CPUでtensorflowを利用するにはminiconda3を利用します。この内容を記述します。

環境はubuntu-20.04.6です。
CPUは古いAMDのCPUでAVX2をサポートしていません。
下記コマンドで調べることができます。
cat /proc/cpuinfo

手順は以下のとおりです。

(a) dockerとdocker-composeのインストール

sudo apt -y install docker.io
sudo apt -y install docker-compose
sudo usermod -aG docker $(whoami)
docker --version
docker-compose --version

PCの再起動が必要です

(b) Dockerfile作成

FROM continuumio/miniconda3:latest
RUN conda install python=3.10
RUN conda install tensorflow
RUN conda install jupyter
CMD jupyter notebook \
    --notebook-dir=/home \
    --ip=0.0.0.0 \
    --port=8888 \
    --no-browser \
    --allow-root

(c) docker-compose.yaml作成

version: '3'
services:
  tensorflow:
    build:
         context: .
    ports:
         -  "8888:8888"
         
(d) コンテナ起動

docker-compose up -d

起動ログを参照します
docker-compose logs

(e) jupyter notebookでloginして実行

起動ログにはtokenの情報があります。
http://127.0.0.1:8888/tree?token=xxxxxxx15ff4d6d9bb6a81101ef27307xxxxxxx

Firefoxを利用してloginします。

http://localhost:8888
tokenを入力してjupyter notebookにloginします。

下記pythonプログラムで動作確認します。

import tensorflow as tf
print("tf version")
print(tf.__version__)
print(tf.reduce_sum(tf.random.normal([1000, 1000])))

最新版は利用できませんが、tensorflow-2.12が利用できるようです。

参考

参考書籍

AlphaZero 深層学習・強化学習・探索 人工知能プログラミング実践入門
布留川 英一 著 

コメント