本文主要介绍训练“确定性神经网络连子棋”模型的软件的开发,以及训练的对比分析,包括最简单的控制台版本单个数据的训练、批量数据的训练、反复迭代训练、多目录数据训练、不同棋盘大小的数据混合训练,也包括MFC对话框版本的批量数据训练,每个部分单独成篇,每个部分都可以独自运行,不需要将所有内容全部读完才能进行训练。
其中,控制台版本的训练要求所有数据文件的名称使用连续的数字进行命名,而MFC对话框版本则对数据文件的名称没有限制。单个数据的训练是指用一条数据进行模型的训练,通常用于在已有模型上进行追加训练;批量数据的训练是指用一批数据进行训练,通常用于从空模型开始训练,也可用于追加训练;反复迭代训练通常用于批量数据训练,指的是在所有数据上一遍一遍迭代,直到所有数据不再让模型产生增长为止;多目录数据训练是在反复迭代训练的基础上支持将数据分拆存储于多个目录下,尤其适合数据分属于不同日期、不同提供者、不同棋盘大小等场景;不同棋盘大小的数据混合训练指的是训练用的数据允许来自不同的场景,例如:15x15棋盘上对弈产生的数据、19x19棋盘上对弈产生的数据等等,但是,五连子和六连子这类不同游戏规则的数据不能混合在一起训练;对话框版本的训练可以支持控制台版本的所有操作,可自行实现,本文中的对话框版本仅实现反复迭代训练,但目录下的所有文件使用遍历查找得到,不再按照数字增长指定数据文件。
1. 单个数据的训练
(1)创建项目
参考前面的文章,创建控制台版本的空项目test,然后新建一个文件test.cpp用于编码。最后在test.cpp文件中添加如下代码:
#include "stdio.h"
void main(void)
{
}
编译并运行,如果没有报错,则说明项目创建成功了。
(2)导入SDK
参考前面的文章,将SDK文件拷贝到test.cpp所在的目录,并将Inter.h文件添加到解决方案中。最后,在test.cpp头部添加如下代码来引入SDK:
#include "Inter.h"
#pragma comment(lib, "AIWZQDll.lib")
如下图所示:
注:此处使用的SDK是1.1.4.0版本(向下兼容的),旧版本不包含网络训练的接口。而且,Inter.h中需要注释掉与界面相关的函数定义,即编译时会报错的那几个函数,如下图所示:
(3)编码
整体代码分为四部分,即神经网络初始化、训练数据读取、网络模型训练、模型保存,对于本例而言,每部分的内容都很少,所以直接给出所有代码,然后再详细说明:
void main(void)
{
FILE* pFile = NULL;
char strFileName[256] = { 0 };
int nPieces = 0; //训练数据中(一局对弈)的棋子数
int narrSteps[1000] = { 0 }; //对弈中从第一子到最后一子的信息:X坐标、Y坐标、落子者(黑为-1,白为1)
InitWithoutModelFile(15, 15, 5);
sprintf(strFileName, "D:/Data/data.txt");
if ((pFile = fopen(strFileName, "r")) == NULL)
return;
nPieces = 0;
int* parrSteps = narrSteps;
while (fscanf(pFile, "%d %d %d", parrSteps, parrSteps + 1, parrSteps + 2) == 3)
{
parrSteps += 3;
nPieces++;
}
fclose(pFile);
TrainNetwork(narrSteps, nPieces, 15, 15);
SaveModel("D:/Model/model.mod");
}
首先,调用SDK的函数InitWithoutModelFile(15,15,5)进行神经网络初始化,此处表示从空模型初始化为15x15棋盘上的五子棋,若想在已有模型上进行追加训练,则调用的是InitFromModelFile()函数。若想训练的是六连子模型,则只需将第三个参数从5改为6即可。
然后,通过fopen()函数打开训练数据文件data.txt,并使用fscanf()读取数据到narrSteps数组中,一个文件中存放一个训练数据,即一次对弈数据,它包含一次对弈从第一步到最后一步的坐标(X和Y)及落子者(黑子为-1,白字为1),如下所示:
6 8 -1
6 5 1
13 3 -1
7 5 1
11 13 -1
8 5 1
9 5 -1
8 4 1
6 10 -1
8 6 1
8 3 -1
6 4 1
1 6 -1
9 7 1
8 2 -1
10 8 1
这里每一行表示一次落子,总计有16行就表示有16颗棋子,第一颗棋子落子X=6,Y=8的位置,是黑子(-1)。坐标X和Y均从0开始计数,左上角为零点,即从左到右的X坐标依次为0、1、2 ......,从上到下的Y坐标依次为0、1、2 ...... 。
再之后,调用SDK函数TrainNetwork()来训练神经网络模型,其中的第一个参数为落子信息,第二个参数为棋子数,第三个和第四个参数分别为训练数据所用棋盘的宽度和高度。
最后,在模型训练结束之后调用SDK函数SaveModel()保存训练得到的模型。
(4)测试
至此为止,第一个例子已经编码完成,运行程序之后可以在D盘的Model目录下看到新训练出的模型文件model.mod,可以将该模型用于之前文章中介绍的“神经网络五子棋(家庭版)”等软件中进行测试。
(5)后续
第一个网络模型训练例程已经介绍完毕,其它的例程请继续关注后续文章。
本例的完整代码如下所示:
#include "stdio.h"
#include "Inter.h"
#pragma comment(lib, "AIWZQDll.lib")
void main(void)
{
FILE* pFile = NULL;
char strFileName[256] = { 0 };
int nPieces = 0; //训练数据中(一局对弈)的棋子数
int narrSteps[1000] = { 0 }; //对弈中从第一子到最后一子的信息:X坐标、Y坐标、落子者(黑为-1,白为1)
InitWithoutModelFile(15, 15, 5);
sprintf(strFileName, "D:/Data/data.txt");
if ((pFile = fopen(strFileName, "r")) == NULL)
return;
nPieces = 0;
int* parrSteps = narrSteps;
while (fscanf(pFile, "%d %d %d", parrSteps, parrSteps + 1, parrSteps + 2) == 3)
{
parrSteps += 3;
nPieces++;
}
fclose(pFile);
TrainNetwork(narrSteps, nPieces, 15, 15);
SaveModel("D:/Model/model.mod");
}
本例中所用的Inter.h文件的完整代码如下所示:
typedef signed char TBOARD;
//以下函数在程序运行开始的时候调用,仅调用一次
extern "C" __declspec(dllexport) bool Login(char* strLoginName, char* strPassword); //登录
extern "C" __declspec(dllexport) bool InitFromModelFile(char* strModelFileName); //使用模型文件初始化
extern "C" __declspec(dllexport) bool InitWithoutModelFile(int nBoardWidth, int nBoardHeight, int nWinLen); //无模型文件时初始化
//以下函数在每局游戏开始的时候调用,每局游戏调用一次
extern "C" __declspec(dllexport) bool StartNewGame(); //开始游戏,并重置相关数据
//以下函数在每步落子的时候调用,每步落子调用一次
extern "C" __declspec(dllexport) bool SetPieceWithCoord(int nX, int nY); //根据坐标落子
//extern "C" __declspec(dllexport) bool SetPieceWithGUI(CStatic* pCtrlBoard, int nCursorXInCtrl, int nCursorYInCtrl); //根据界面组件及屏幕鼠标位置落子
extern "C" __declspec(dllexport) bool SetPieceByAI(void); //AI落子,该函数返回失败表示所登录的用户积分不足
//extern "C" __declspec(dllexport) bool SetPieceByAIAndShow(CStatic* pCtrlBoard); //AI落子并在界面上显示
extern "C" __declspec(dllexport) bool IsGameOver(); //游戏是否已经结束
extern "C" __declspec(dllexport) int GetWinner(); //获胜者
//extern "C" __declspec(dllexport) bool DrawBoard(CStatic* pCtrlBoard); //绘制棋盘
//extern "C" __declspec(dllexport) bool DrawPieces(CStatic* pCtrlBoard); //绘制所有棋子
//以下函数在需要的时候调用,非必须调用
extern "C" __declspec(dllexport) TBOARD* GetBoardData(int* pnBoardWidth, int* pnBoardHeight); //获得当前棋局数组的内存区首地址(及矩阵的宽和高,如果需要的话)
extern "C" __declspec(dllexport) int GetPoint();
extern "C" __declspec(dllexport) bool SaveSteps(char* strDataFileName); //保存棋局数据
extern "C" __declspec(dllexport) bool SaveModel(char* strModelFileName); //保存模型数据
//以下函数用于“五子棋水平等级评估”
extern "C" __declspec(dllexport) bool SetLayers4Pred(int nLayers); //设置用于预测的层数,与之同时,将禁止神经网络的学习进化,仅使用指定的模型进行预测,可保证公平性
//以下函数用于训练确定性神经网络
/*
pnarrData: 棋局数据,即从第一步到最后一步落子的位置(X和Y)及落子者(-1表示黑子,1表示白子)。
pnarrData[0]表示第一颗子的X坐标(下标从0开始计),pnarrData[1]表示第一颗子的Y坐标,pnarrData[2]表示第一颗子的落子者,pnarrData[3]表示第二颗子的X坐标,依此类推。
坐标从棋盘左上角开始计:从左向右的X坐标依次为0、1、2 ......;从上往下的Y坐标依次为0、1、2 ......
nPieces: 棋局中棋子的个数
nBoardWidth: 棋盘的宽度,例如:15x15的棋盘中,宽度和高度均为15
nBoardHeight: 棋盘的高度
*/
extern "C" __declspec(dllexport) bool TrainNetwork(int* pnarrData, int nPieces, int nBoardWidth, int nBoardHeight);