本章包括
- 下载和处理实际的围棋游戏记录
- 了解存储围棋游戏的标准格式
- 训练一个使用这样的数据进行落子预测的深度学习模型
- 运行自己的实验并评估它们
在前一章中,您看到了构建深度学习应用程序的许多基本要素,并构建了一些神经网络来测试您所学到的工具。而关键的是你仍然缺少好的数据来学习。一个监督式的深度神经网络需要你提高好的数据——但目前为止,你只拥有自己生成的数据。
在这一章中,您将了解围棋数据最常见的数据格式-----SGF。您可以从几乎每个流行的围棋服务器中获得SGF游戏记录。为了加强深度神经网络的落子预测能力,在本章中,您将从围棋服务器中下载许多SGF文件,用智能的方式对它们进行编码,并使用这些数据训练神经网络。由此产生的经过训练的神经网络,将比以前的任何模型都要强得多。
图7.1说明了到本章结尾时可以构建的内容。
在本章的末尾,您可以使用复杂的神经网络运行自己的试验,完全独立地构建一个强大的AI。要开始,您需要访问真实的围棋数据。
7.1. 引入围棋数据
到目前为止,您使用的所有围棋数据都是由你自己生成的。在上一章中,你训练了一个深度神经网络来预测生成的数据的落子。你所希望实你的网络可以完美地预测这些落子,在这种情况下,网络将像生成数据的树搜索算法一样发挥作用。在某种程度上,你输入的数据奥克提供了一个深度学习机器人训练的上限。机器人不能超过产生的数据。如果利用强大的人类棋手游戏记录作为深层神经网络的输入,就可以大大提高您的机器人的水平。现在您将使用KGS围棋服务器(以前称为Kiseido GoServer)的游戏数据,这是世界上最流行的围棋游戏平台。在介绍如何从KGS下载和处理数据之前,我们将首先向你介绍围棋数据的数据格式。
7.1.1 SGF文件格式
SGF,80年代后期就开始开发。它目前的第四个主要版本(表示FF[4])是在90年代后期发布的。SGF是基于文本的一种格式,可以用来表达围棋游戏及围棋游戏的变体(例如,围棋高手的游戏评论)以及其他棋盘游戏。章节的剩下部分,你将假设你正在处理的SGF文件是由围棋游戏组成,没有其他任何别的东西。在本节中,我们会教你一些关于这个丰富游戏格式的基本知识,但如果你想学习更多关于它的知识的话,请去https://senseis.xmp.net/?SmartGameFormat。
SGF主要包括游戏情况和落子数据,是通过两个指定的大写字母包在两个大括号里面。例如,在SGF中,一个大小为9×9的围棋盘将被编码为SZ[9]。围棋落子将会如下编码,在第三行和第三列上的一个交叉点上落白棋将是W[cc],而在第七行和第三列上的一个交叉点上落黑棋将被表示成B[gc];字母B和W代表棋子的颜色,行和列的坐标按字母顺序索引。若要表示pass,请使用空步骤B[]和W[]。
下面的SGF文件示例取自第二章9*9棋盘上的完整对局。它显示了一个围棋游戏(GM[1]代表是围棋),HA[0]表示让子数为0,KM[6.5]表示贴目6.5,R U[Japanese]表示规则是日本规则,RE[W9.5]表示白赢了6.5目
一个SGF文件被组织成一个节点列表,节点由分号分隔。第一个节点包含有关游戏的信息:棋盘大小、使用的规则、游戏结果和其他背景信息。后面的每个节点表示游戏中的一个落子。最后,你也可以看到属于白棋地盘的点,列在TW之下,以及属于黑棋地盘的点,列在TB之下。
7.1.2.从KGS下载和回放Go游戏记录
如果你进入到https://ugo.net/gamerecords/,你会看到一张表格,上面有可供下载的各种格式游戏记录。这个游戏数据是从KGS 围棋服务器收集的,所有这些游戏都是在19×19的棋盘上进行的,而在第六章中,我们为了减少计算而只使用了个9×9的棋盘。
这是一个令人难以置信的强大数据集,可以用于围棋落子预测,您将在本章中使用该数据集来为强大的深度学习机器人提供动力。您需要可以自动通过获取单个文件的链接下载,然后解压文件,最后处理其中包含的SGF游戏记录。
作为使用这个数据作为深度学习模型的输入第一步,你可以在主dlgo模块中创建一个名为data的新子模块,并像往常一样提供一个空的_init_.py。这个子模块将包含所有这本书所需的数据处理。
接下来,要下载游戏数据,您可以在数据子模块中添加新文件index_processor.py中,并创建一个名为KGSIndex的类,然后实现其中的download_files方法
7.2 为深度学习准备数据
在第6章中,您看到了一个简单的围棋数据编码器,该编码器已经表示了在第3章中介绍的Board和GameState类。当使用SGF文件时,您首先需要对内容进行回放,产生对应的一个对局,得到必要的游戏信息。
7.2.1.根据SGF记录重放围棋对局
读取SGF文件的游戏信息意味着要理解和实现格式规范。虽然这并不是特别难做到(只是强加一个规则在一串文本上),这也不是构建围棋AI最令人兴奋的方面,需要大量的努力和时间才能做到完美无缺。出于这些原因,我们将引入另一个子模块gosgf到dlgo中,它负责处理SGF文件所需的所有逻辑。gosgf模块是从Gomill Python库改编而来的,地址是https://mjw.woodcraft.me.uk/gomill/
您将需要一个来自gosgf的实体,它足以处理您需要的所有内容:sgf_game。让我们看看如何使用SGF_Game加载一个SGF游戏,逐步读出游戏信息,并将落子应用于Game State对象。图7.2显示了围棋游戏的开始,用SGF命令表示。
从SGF文件中重放游戏记录。原来的SGF文件编码游戏移动与字符串,如B[ee]。Sgf_game类解码这些字符串并将它们作为Python元组返回。你就可以将这些落子应用到GameState对象以重建游戏
# 先从新的gosgf模块导入Sgf_game类
from dlgo.gosgf import Sgf_game
from dlgo.goboard_fast import GameState, Move
from dlgo.gotypes import Point
from dlgo.utils import print_board
# 定义示例SGF字符串,此内容稍后会来自下载的数据
sgf_content = "(;GM[1]FF[4]SZ[9];B[ee];W[ef];B[ff]" + ";W[df];B[fe];W[fc];B[ec];W[gd];B[fb])"
# 使用from_string方法,您可以创建一个SGF_game
sgf_game = Sgf_game.from_string(sgf_content)
game_state = GameState.new_game(19)
# 重复游戏的主要顺序,你忽略了棋局变化和评论
for item in sgf_game.main_sequence_iter():
# 这个主序列中的项是(颜色,落子)对,其中"落子"是一对坐标。
color, move_tuple = item.get_move()
if color is not None and move_tuple is not None:
row, col = move_tuple
point = Point(row + 1, col + 1)
move = Move.play(point)
# 将读出的落子应用到棋盘上
game_state = game_state.apply_move(move)
print_board(game_state.board)
从本质上讲,在您有了一个有效的SGF字符串之后,您就可以根据它得到主要序列,而这些序列你可以通过迭代得到。上面代码是本章的核心,它给出了一个粗略的大纲,告诉你将如何继续处理深度学习所需的数据:
- 下载并解压缩围棋游戏文件。
- 遍历这些文件中包含的每个SGF文件,读取文件中的内容变成字符串,然后从这些字符串中创建一个Sgf_game。
- 读出每个SGF字符串的围棋游戏的主要顺序,确保处理重要的细节,如放置棋子,并将产生的落子数据输入到GameState对象中。
- 对于每一个落子,棋盘局面采用编码器进行编码成特征,并将落子本身存储为标签,然后将其放置在棋盘上。这样,您将创建落子预测数据,以便在后面训练中进行深入学习。
- 5.将生成的特征和标签以合适的格式存储起来,这样您就可以稍后将其添加到神经网络中。
在接下来的几节中,您将非常详细地处理这五个任务。处理完这些数据后,您可以回到您的落子预测应用程序,看看如何让数据影响落子预测精度。
7.2.2.构建围棋数据处理器
在本节中,您将构建一个围棋数据处理器,该处理器可以将原始SGF数据转换为机器学习算法的特征和标签。这将是一个相对较长的实现,因此我把它分成几个部分。当你完成的时候,你就可以准备好在真实数据上运行一个深度学习模型。
要开始,先在data模块下新建一个名为processor.py的新文件,让我们导入几个核心Python库,除了用于数据出来的NumPy之外,您还需要相当多的包来处理文件。
import os.path
import tarfile
import gzip
import glob
import shutil
import numpy as np
from keras.utils import to_categorical
至于dlgo本身所需要的功能,您需要导入到目前为止构建的许多核心类。
from dlgo.gosgf.sgf import Sgf_game
from dlgo.agent.FastRandomAgent.goboard_fast import Board, GameState, Move
from dlgo.gotypes import Player, Point
from dlgo.Encoder.Base import get_encoder_by_name
from dlgo.data.index_processor import KGSIndex
from dlgo.data.sampling import Sampler # 从文件中采样训练和测试数据
我们还没有后面两个引入,但我们将在构建围棋数据处理器中引入它们。现在继续使用processor.py,GoDataProcessor初始化是通过提供一个Encoder作为字符串和一个存储围棋书记路径的data_directory
class GoDataProcessor:
def __init__(self,encoder="OnePlaneEncoder",data_directory="data"):
self.encoder = get_encoder_by_name(encoder,19)
self.data_directory = data_directory
接下来,您将实现主要的数据处理方法,称为load_go_data。在此方法中,您可以指定要处理的游戏数量以及要加载的数据类型,即训练或测试数据。load_go_data将从KGS中下载在线游戏记录,对指定数量的游戏进行采样,通过创建功能和标签进行处理,然后将结果持久化到本地作为NumPy数组。
并行处理parallel.py
"""
将sgf文件转成可被机器学习使用的格式
"""
# 文件相关类 begin
import os.path
import tarfile
import gzip
import glob
import shutil
# 文件相关类 end
import os
from os import sys
import multiprocessing
import numpy as np
from keras.utils import to_categorical
from dlgo.gosgf.sgf import Sgf_game
from dlgo.agent.FastRandomAgent.goboard_fast import Board, GameState, Move
from dlgo.gotypes import Player, Point
from dlgo.Encoder.Base import get_encoder_by_name
from dlgo.data.index_processor import KGSIndex
from dlgo.data.Sample import Sampler
from dlgo.data.generator import DataGenerator
def worker(jobinfo):
try:
clazz, encoder, zip_file, data_file_name, game_list = jobinfo
clazz(encoder=encoder).process_zip(zip_file, data_file_name, game_list)
except (KeyboardInterrupt, SystemExit):
raise Exception('>>> Exiting child process.')
class GoDataProcessor:
def __init__(self,encoder="OnePlaneEncoder",data_directory="data"):
self.encoder = get_encoder_by_name(encoder,19)
self.data_directory = data_directory
self.encoder_string = encoder
# 加载