《神经网络与PyTorch实战》——2.2.2 迷你AlphaGo的完整实现
2.2.2 迷你AlphaGo的完整实现
在这一节我们来看一个基于PyTorch的迷你AlphaGo的完整实现。完整实现分为4个部分。
* 确定神经网络结构。这个部分确定神经网络中有几个神经元,神经元之间是怎么连接的。
* 构造测试函数。这个部分确定要针对哪个函数来选择人工神经元的权重。显然,函数不同,设置的权重值就不同。
* 设置人工神经元的权重值。根据测试函数,让PyTorch自动设置合适的神经元权重值。完成这步后,神经网络就完全确定了。
* 测试人工神经网络的效果。这步验证之前实现的人工神经网络能够很好地完成“迷你AlphaGo”的功能。
接下来依次来看这四部分的完整代码。将这四部分完整代码依次拼接在一起,就得到了完整的程序(而不需要再添加其他任何代码)。代码下载地址参见本书前言部分。
注意:如果你觉得这些代码难以理解,请不用担心。这里只是希望你有一个初步的概念。本书的后续章节会逐步介绍如何理解或编写这些代码。等你读完本书,你也能写出这样的代码。
首先来看第一部分代码。如代码清单2-1所示,这部分代码的主要功能是确定神经网络的结构。具体而言,以“import”和“from”开头的第1行代码告诉计算机我要用PyTorch了,让机器做好准备。接下来用一个序列构造神经网络。在这个序列中,有表示连接的Linear类,也有表示非线性运算的ReLU类。在这个序列中一共有3个Linear类实例,说明这个神经网络有3层。第一个Linear类实例用参数3和8构造,这两个参数说明每个神经元都有3个输入,一共有8个神经元。这个序列中有两个ReLU类实例,也就是说,其中两个层的神经元的非线性函数都是。
你可能会有疑问:为什么这个神经网络的最后一层没有使用非线性函数?这是因为,我们希望将要制作的“迷你AlphaGo”应用既能输出的结果,也能输出的结果。如果在最后的输出中设置了,那就不可能输出的结果。因此这里最后一层的神经元没有使用非线性函数。
代码清单2-1 神经网络结构搭建代码
from torch.nn import Linear, ReLU, Sequential
net = Sequential(
Linear(3, 8), # 第1层有8个神经元
ReLU(), # 第1层神经元的非线性函数是max(·,0)
Linear(8, 8), # 第2层有8个神经元
ReLU(), # 第2层的神经元的非线性函数是max(·,0)
Linear(8, 1), # 第3层有1个神经元
)
接下来看第二部分代码。在这部分中,将定义函数。当然,这个函数的形式可以是任意的,而且神经网络不需要知道这个函数的具体形式。但是,神经网络权重的确定和函数有关。函数不同,神经网络就需要不同的权重值。因此,给出函数是有必要的。这部分代码如代码清单2-2所示。这里的代码需要用到一些PyTorch的编程知识,我们目前只需要知道这段代码定义了函数
其中,。
代码清单2-2 函数的定义
def g(x, y):
x0, x1, x2 = x[:, 0] ** 0, x[:, 1] ** 1, x[:, 2] ** 2
y0 = y[:, 0]
return (x0 + x1 + x2) * y0 - y0 * y0 - x0 * x1 * x2
接下来看第三部分代码。这部分代码见代码清单2-3。在这部分代码中,在PyTorch的帮助下为神经网络中的每个神经元找到合适的权重。在这段代码中,我们没有显式地为每个神经元指定权重值,而是使用了一个优化器。代码第3行构造了优化器optimizer。这个优化器每次可以改良所有权重值,但是这个改良不是一步到位的。我们需要让优化器反复改良很多次,才能让神经网络的所有权重都合适。在这段代码中以“for”开头的语句说明整个过程需要循环很多次(实际上是1000次),而后面缩进的语句都是要循环的内容。在循环的内容中,我们需要告诉优化器每次改良的依据是什么。因此后面缩进的代码先告诉优化器改良的依据,然后通过“optimizer.step()”语句完成权重的改良。完成循环后,神经网络中所有的权重值就都比较合适了。这时候我们就训练好了人工神经网络。
代码清单2-3 使用优化器确定神经元的权重值
import torch
from torch.optim import Adam
optimizer = Adam(net.parameters())
for step in range(1000):
optimizer.zero_grad()
x = torch.randn(1000, 3)
y = net(x)
outputs = g(x, y)
loss = -torch.sum(outputs)
loss.backward()
optimizer.step()
if step % 100 == 0:
print ('第{}次迭代损失 = {}'.format(step, loss))
那么,实现好的人工神经网络是不是正确完成了“迷你AlphaGo”的任务目标呢?第四部分的代码就来验证。这部分代码见代码清单2-4。这部分的代码又可以细分为三小部分。
* 生成测试数据。随机生成一些数据作为输入,我们将看到人工神经网络是不是能很好地计算出对应的。生成的输入数据被以“print”开头的语句输出在电脑屏幕上。
* 调用人工神经网络,看看在给定输入下神经网络的输出是什么。神经网络的输出和对应的函数的值被打印了出来。
* 计算理论最优结果作为参考。这部分并没有用到人工神经网络,只是用这个结果和神经网络的输出做比较。如果的具体形式为
,那么这个实际上是一个关于的二次函数,它的开口向下。利用二次函
数的知识,可以知道,这个二次函数在处取到最大值。因此,理论上的最佳输出为。这部分代码将理论上的最佳输出和最佳
输出对应的函数的值打印了出来。
代码清单2-4 验证生成的人工神经网络
# 生成测试数据
x_test = torch.randn(2, 3)
print ('测试输入: {}'.format(x_test))
# 查看神经网络的计算结果
y_test = net(x_test)
print ('人工神经网络计算结果: {}'.format(y_test))
print ('g的值: {}'.format(g(x_test, y_test)))
# 根据理论计算参考答案
def argmax_g(x):
x0, x1, x2 = x[:, 0] ** 0, x[:, 1] ** 1, x[:, 2] ** 2
return 0.5 * (x0 + x1 + x2)[:, None]
yref_test = argmax_g(x_test)
print ('理论最优值: {}'.format(yref_test))
print ('g的值: {}'.format(g(x_test, yref_test)))
由于第四部分验证代码的输入是随机确定的,所以每次运行的输入和输出都不一样。下面给出某次运行结果:
测试输入: tensor([[ 0.2487, 0.3399, -0.4967],
[ 1.0140, 0.1038, -0.1002]])
人工神经网络计算结果: tensor([[ 0.8289],
[ 0.4975]])
g的值: tensor([ 0.5442, 0.3056])
理论最优值: tensor([[ 0.7933],
[ 0.5569]])
g的值: tensor([ 0.5455, 0.3091])
比较神经网络的输出结果和理论计算结果,可以断定,我们的人工神经网络已经正确地输出了最优结果,实现了“迷你AlphaGo”的功能。
- 点赞
- 收藏
- 关注作者
评论(0)