极简神经网络-深度强化学习预备知识

  |  

摘要: 极简神经网络知识

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文我们极简地过一下神经网络的必要知识。虽然是深度学习的内容,但是本文主要是为【使用神经网络实现强化学习的框架】铺垫必要的神经网络知识。主要内容也来自《用Python动手学强化学习》这本书。

主要内容如下:

  • 从 y = ax + b 引入,用 TensorFlow 实现这个式子,并实现批量地将数据输入这个式子。
  • 单层全连接网络,了解单层神经网络与 y = ax + b 是等价的,然后用 Keras 实现单层全连接网络。
  • 多层全连接网络,用 Keras 实现多层全连接网络,并实现批量将数据输入 Keras 多层全连接网络。
  • 神经网络的学习 — 误差的反向传播,通过链式法则计算各个参数的梯度。然后通过 TensorFlow 的动态图机制 tf.contrib.eager.gradients_function 求特定函数对某一输入的偏导数。
  • 通过波士顿房价的例子看一下 Keras 多层全连接网络的训练和推理过程。
  • 基本的 CNN 网络,以及其在 DQN 中的应用。用 Keras 实现 CNN 并通过 Mnist 手写数字识别看一下训练和推理的流程。
  • 强化学习中引入神经网络的优缺点。

1. y = ax + b

考虑【通过含参数的函数(神经网络),实现价值近似】的方法。

如果我们用 $y = ax + b$ 作为价值近似,则 x 就是 Agent 的状态,y 就是 Agent 动作空间中各种动作的价值。

例如考虑一个网格环境,Agent 的状态为其坐标 (x1, x2),Agent 的动作空间有上下左右,它们的价值记为 (y1, y2, y3, y4)。此时 y = ax + b 可以展开:

我们学习的目标是缩小价值的统计误差(TD误差)。这里需要通过优化参数 a, b 的方式学习。

TensorFlow 实现 y = ax + b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
import tensorflow as tf

# 权重 (row=4 x col=2).
a = tf.Variable(np.random.rand(4, 2))

# 偏置 (row=4 x col=1).
b = tf.Variable(np.random.rand(4, 1))

# 输入(x) (row=2 x col=1).
x = tf.compat.v1.placeholder(tf.float64, shape=(2, 1))

# 输出(y) (row=4 x col=1).
y = tf.matmul(a, x) + b


with tf.compat.v1.Session() as sess:
# 初始化参数
init = tf.compat.v1.global_variables_initializer()
sess.run(init)

# 为 x 生成输入
x_value = np.random.rand(2, 1)

# 执行计算
y_output = sess.run(y, feed_dict={x: x_value})
print(y_output.shape) # Will be (4, 1)

简单改为批量输入(枚举批中的数据,依次预测):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
with tf.compat.v1.Session() as sess:
# 初始化参数
init = tf.compat.v1.global_variables_initializer()
sess.run(init)

# 生成数据批次
batch = []
for i in range(3):
x_value = np.random.rand(2, 1)
batch.append(x_value)

# 执行计算
y_outputs = []
for x_value in batch:
y_output = sess.run(y, feed_dict={x: x_value})
y_outputs.append(y_output)

y_output = np.array(y_outputs)
print(y_output.shape) # 变成(3, 4, 1)

2. 全连接网络

(1) 单层全连接网络

上面的公式就是一个单层神经网络,它是输入值乘以权重并加上偏置,输入与权重称为层。

Keras 实现单层全连接网络

对于单层神经网络,Keras 的实现如下

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import tensorflow.keras as keras

model = keras.Sequential([
keras.layers.Dense(units=4, input_shape=((2,))),
])

weight, bias = model.layers[0].get_weights()
x = np.random.rand(1, 2)
y = model.predict(x)
print("x: {}\ny: {}".format(x, y))

其中,Sequential 是集成多层 NN 的模块。Dense 是其中的一层,做的是将输入乘以权重并加上偏置。

这里输入的尺寸是 2,也就是坐标。输出的尺寸为 4,也就是动作空间可选的数量。

运行结果如下:

1
2
3
4
5
6
weight shape: (2, 4)
Bias shape: (4,)
x shape: (1, 2)
y shape: (1, 4)
x: [[0.6721442 0.42029987]]
y: [[ 0.82700586 0.36359882 -0.6589408 -0.48637265]]

(2) 多层全连接网络

多层神经网络中,前一层输出和下一层输入之间有一个激活函数。数据进行前向传播的时候,一般是将多个样本打包,例如 batch_size 为 3 的时候,状态数据汇总后是类似与下面这样。

Keras 实现多层全连接网络

对于多层神经网络,batch_size 为 3,Keras 的实现方式如下:

1
2
3
4
5
6
7
8
9
10
model = keras.Sequential([
keras.layers.Dense(units=4, input_shape=((2,)), activation="sigmoid"),
keras.layers.Dense(units=4),
])

batch_size = 3
x = np.random.rand(batch_size, 2)
y = model.predict(x)
print("x shape: {}\ny shape: {}".format(x.shape, y.shape))
print("x: {}\ny: {}".format(x, y))

运行结果如下:

1
2
3
4
5
6
7
8
x shape: (3, 2)
y shape: (3, 4)
x: [[0.24683531 0.29956362]
[0.58713644 0.78199902]
[0.53817021 0.53613088]]
y: [[-1.5012160e-04 5.5887900e-02 -1.6480932e-02 -1.8530276e-02]
[-2.1517833e-04 1.2561302e-01 -2.9820628e-02 -5.0184965e-02]
[-1.1718285e-03 1.6173606e-01 -9.1260657e-02 -2.1278965e-03]]

(3) 神经网络的学习 — 反向传播

学习的过程是根据输出侧的误差,调整各层的权重和偏置,这个过程称为误差的反向传播。

各层参数具体是如何调整的,取决于梯度。下面我们以 y = ax + b 为例,看一下参数 a, b 的梯度如何求出。

手算 y = ax + b 的梯度

假设当前 a = 3.0, b = -1.0, 输入 x = 2.0,其前向传播过程如下:

按照上面的前向传播过程,我们可以求出 y = 5.0。

现在我们要求参数 a, b 的梯度。计算过程如下图。

根据链式法则,$\frac{\partial y}{\partial a}$ 可以拆分为 $\frac{\partial y}{\partial z}\frac{\partial z}{\partial a}$。

反复使用链式法则,即可求出各层的梯度。

参数 a, b 的梯度求出后,我们就知道了 a, b 变化时,输出 y 对应的变化 $\frac{\partial y}{\partial a}$, $\frac{\partial y}{\partial b}$。

对误差计算梯度,就能知道各个参数对误差的影响如何,下面我们用 TensorFlow 动态图机制 tf.contrib.eager 实现 y = ax + b 的梯度。

TensorFlow 动态图机制实现 y = ax + b 的梯度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tensorflow as tf


tf.enable_eager_execution()
tfe = tf.contrib.eager


def f(x, a, b):
return tf.add(tf.multiply(x, a), b)


def grad(f):
return lambda x, a, b: tfe.gradients_function(f)(x, a, b)


x = 2.0
a = 3.0
b = -1.0

print(f(x, a, b))
print(grad(f)(x, a, b))

梯度下降法

解决反向传播,也就是解决如何使参数根据梯度方向移动。最有代表性的是 SGD,有时也会用自适应矩估计 Adam。

将 SGD, Adam 等算法的实现一般称为 optimizer。

算好梯度后,传入 Optimizer 用于更新参数,这就是学习的基本流程。

(4) 例子: 波士顿房价 Keras

下面我们以波士顿房价数据来看一下,全连接网络的训练和预测的流程,以及一些注意点。

这个模型根据 13 个特征(X)输出一个值(y),学习过程通过调整参数使得预测价格与实际价格的差距变小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_boston
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow import keras as K


# 数据预处理 + 特征工程
dataset = load_boston()

y = dataset.target
X = dataset.data

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33
)

# 模型
model = K.Sequential([
K.layers.BatchNormalization(input_shape=(13,)),
K.layers.Dense(units=13, activation="softplus", kernel_regularizer="l1"),
K.layers.Dense(units=1)
])
model.compile(loss="mean_squared_error", optimizer="sgd")
# 训练
model.fit(X_train, y_train, epochs=8)

# 预测
predicts = model.predict(X_test)

# 画图
result = pd.DataFrame({
"predict": np.reshape(predicts, (-1,)),
"actual": y_test
})
limit = np.max(y_test)

result.plot.scatter(x="actual", y="predict", xlim=(0, limit), ylim=(0, limit))
plt.show()
  • BatchNormalization 是对数据进行归一化为平均值为 0,方差为 1。
  • kernel_regularizer 是正则化,用于限制网络层中的权重的值。
  • loss 为最小化的对象,用的是均方误差 mean_squared_error
  • optimizer 用的是 SGD。

实际价格和预测价格的散点图:


3. CNN

普通的神经网络并不算强大,经常需要根据任务设计神经网络。

例如适合图像识别的 CNN,适合时序数据的 RNN。设计时会针对数据和特征采用特别的权重和传播方式。

应用于强化学习时,CNN 可以通过输入画面来获取行动,例如 DQN。DQN 可以将画面直接用作状态,基于画面进行学习也符合人类的认知,人类往往也是通过观察画面完成工作的。

(1) CNN 基础

CNN 受人类的感受野的启发而设计,也就是对图像的部分区域的信息汇总处理,分级进行。

其中对图像部分区域进行信息汇总的过程称为卷积,其中卷积层负责对图像数据执行特殊的加权,池化层负责信息汇总。

处理到最后将得到的所有处理结果都展开排列(flatten),然后切换到与普通神经网络相同的传播方式来输出图像的预测结果。

(2) 例子: 手写数字

下面我们以 Mnist 数据来看一下,CNN 网络的训练和预测的流程,以及一些注意点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_digits
from sklearn.metrics import classification_report
from tensorflow import keras as K


# 数据预处理 + 特征工程
dataset = load_digits()
image_shape = (8, 8, 1)
num_class = 10

y = dataset.target
y = K.utils.to_categorical(y, num_class)
X = dataset.data
X = np.array([data.reshape(image_shape) for data in X])

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33)

# 模型
model = K.Sequential([
K.layers.Conv2D(
5, kernel_size=3, strides=1, padding="same",
input_shape=image_shape, activation="relu"),
K.layers.Conv2D(
3, kernel_size=2, strides=1, padding="same",
activation="relu"),
K.layers.Flatten(),
K.layers.Dense(units=num_class, activation="softmax")
])
model.compile(loss="categorical_crossentropy", optimizer="sgd")
# 训练
model.fit(X_train, y_train, epochs=8)

# 预测
predicts = model.predict(X_test)
predicts = np.argmax(predicts, axis=1)
actual = np.argmax(y_test, axis=1)
print(classification_report(actual, predicts))
  • keras.utils.to_categorical 将数值转换为 one-hot。
  • keras.layers.Conv2D 是卷积层的定义。
  • Flatten 将三维特征图转换为 1 维向量,然后由全连接层得到输出向量。
  • categories_crossentropy 是一种误差函数,用于比较输出的概率值与 one-hot 标签。

运行结果如下:


4. 强化学习中引入神经网络的优缺点

使用神经网络的优点:

  • 可以用图像,声音等人类实际观察到的状态数据进行智能体的学习。

使用神经网络的弱点:

  • 计算成本很高。

Share