遗传算法与深度学习实战(22)——使用Numpy构建神经网络
0. 前言
我们已经学习了如何使用进化算法来优化深度学习网络中的超参数,与简单的随机或网格搜索算法相比,使用进化算法可以改善对超参数的搜索。多种进化算法的变体,如粒子群优化、进化策略和差分进化,可以用于搜索和优化超参数。神经进化涵盖了所有用于改进深度学习的进化算法,在本节中,我们使用 NumPy
构建一个简单的多层感知器 (multi-layer perceptron
, MLP
) 作为神经进化的基础。
1. 神经网络基础
1.1 简单神经网络的架构
人工神经网络受到人脑运作方式的启发。从本质上讲,它是对线性回归和逻辑回归的一种改进,神经网络在计算输出时引入了多种非线性函数。此外,神经网络在修改网络体系结构以利用结构化和非结构化数据跨多个域解决问题方面具有极大的灵活性。函数越复杂,网络对于输入的数据拟合能力就越大,因此预测的准确性就越高。神经网络的典型结构如下:
神经网络中的层 (layer
) 是一个或多个节点(或称计算单元)的集合,层中的每个节点都连接到下一层中的每个节点。输入层由预测输出值所需的输入变量组成。输出层中节点的数量取决于我们要预测连续变量还是分类变量。如果输出是连续变量,则输出层一个节点。
如果输出结果是 n n n 个类别的预测类的分类,则输出层中将有 n n n 个节点。隐藏层用于将输入层的值转换为高维空间中的值,以便我们可以从输入中了解数据的更多特征。隐藏层中节点的工作方式如下:
在上图中, x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn 是自变量, x 0 x_0 x0 是偏置项,类似于线性方程 y = k x + b y=kx+b y=kx+b 里的 b b b, w 1 , w 2 , . . . , w n w_1, w_2, ..., w_n w1,w2,...,wn 是赋予每个输入变量的权重。如果 a a a 是隐藏层中的节点之一,则计算方法如下所示:
a = f ( ∑ w i N w i x i ) a=f(\sum _{w_i} ^N w_ix_i) a=f(wi∑Nwixi)
f f f 函数是激活函数,用于在输入和它们相应的权重值的总和上引入非线性。可以通过使用多个隐藏层实现更强的非线性能力。
综上,神经网络是相互连接的层中节点权重的集合。该集合分为三个主要部分:输入层,隐藏层和输出层。神经网络中可以具有 n n n 个隐藏层,术语“深度学习”通常表示具有多个隐藏层的神经网络。 当神经网络需要学习具有复杂上下文(例如图像识别)或上下文不明显的任务时,就必须具有隐藏层,隐藏层也被称为中间层。
1.2 神经网络的训练
训练神经网络实际上就是通过重复两个关键步骤来调整神经网络中的权重:前向传播和反向传播。
- 在前向传播中,我们将一组权重应用于输入数据,将其传递给隐藏层,对隐藏层计算后的输出使用非线性激活,通过若干个隐藏层后,将最后一个隐藏层的输出与另一组权重相乘,就可以得到输出层的结果。对于第一次正向传播,权重的值将随机初始化。
- 在反向传播中,尝试通过测量输出的误差,然后相应地调整权重以降低误差。神经网络重复正向传播和反向传播以预测输出,直到获得令误差较小的权重为止。
2. 使用 Numpy 构建神经网络
2.1 网络架构
在本节中,我们将实现基本的深度学习系统,使用 NumPy
编写多层感知器,本节并未使用类似 Keras
或 PyTorch
的框架,以便可以清楚地可视化神经网络内部过程。
一个简单的多层感知器 (multi-layer perceptron
, MLP
) 网络如下所示,可以看到反向传播是如何通过网络传播计算得到的损失的,以及神经进化优化是如何将网络的每个权重/参数替换为基因序列中的值的。实际上,这种进化搜索与超参数搜索类似。
2.2 实现神经网络
为了进一步理解 MLP
及其内部工作原理,我们将使用 NumPy
实现 MLP
。然后,研究这个简单网络在各种分类问题上的训练过程。
(1) 使用 sklearn
的 make_datasets
函数构建数据集:
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
import matplotlib.pyplot as plt
number_samples = 100 #@param {type:"slider", min:100, max:1000, step:25}
difficulty = 1 #@param {type:"slider", min:1, max:5, step:1}
problem = "circles" #@param ["classification", "blobs", "gaussian quantiles", "moons", "circles"]
number_features = 2
number_classes = 2
middle_layer = 5 #@param {type:"slider", min:5, max:25, step:1}
epochs = 25000 #@param {type:"slider", min:1000, max:50000, step:1000}
def load_data(problem):
if problem == "classification":
clusters = 1 if difficulty < 3 else 2
informs = 1 if difficulty < 4 else 2
data = sklearn.datasets.make_classification(
n_samples = number_samples,
n_features=number_features,
n_redundant=0,
class_sep=1/difficulty,
n_informative=informs,
n_clusters_per_class=clusters)
if problem == "blobs":
data = sklearn.datasets.make_blobs(
n_samples = number_samples,
n_features=number_features,
centers=number_classes,
cluster_std = difficulty)
if problem == "gaussian quantiles":
data = sklearn.datasets.make_gaussian_quantiles(mean=None,
cov=difficulty,
n_samples=number_samples,
n_features=number_features,
n_classes=number_classes,
shuffle=True,
random_state=None)
if problem == "moons":
data = sklearn.datasets.make_moons(
n_samples = number_samples)
if problem == "circles":
data = sklearn.datasets.make_circles(
n_samples = number_samples)
return data
data = load_data(problem)
X, Y = data
plt.figure("Input Data")
plt.scatter(X[:, 0], X[:, 1], c=Y, s=40, cmap=plt.cm.Spectral)
下图显示了最高难度级别的数据集示例,修改问题类型以观察每个数据集的变化。对于简单的 MLP
网络来说,最困难的数据集是圆形数据集。
模型参数选项如下表所示:
参数 | 描述 | 取值范围 |
---|---|---|
number_samples | 数据集样本数 | 100-1000 |
difficulty | 问题难度系数 | 1-5 |
problem | 定义数据集所用的函数 | classification = make_classification |
moons = make_moons | ||
circles = make_circles blobs = make_blobs Gaussian | ||
quantiles = make_gaussian_quantiles | ||
middle_layer | 网络隐藏层数 | 5-25 |
epochs | 训练迭代次数 | 1000–25000 |
(2) 作为基线,比较 sklearn
的简单逻辑回归(分类)模型:
def show_predictions(model, X, Y, name=""):
""" display the labeled data X and a surface of prediction of model """
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01), np.arange(y_min, y_max, 0.01))
X_temp = np.c_[xx.flatten(), yy.flatten()]
Z = model.predict(X_temp)
plt.figure("Predictions " + name)
plt.contourf(xx, yy, Z.reshape(xx.shape), cmap=plt.cm.Spectral)
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(X[:, 0], X[:, 1],c=Y, s=40, cmap=plt.cm.Spectral)
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X, Y)
show_predictions(clf, X, Y, "Logistic regression")
LR_predictions = clf.predict(X)
print("Logistic Regression accuracy : ", np.sum(LR_predictions == Y) / Y.shape[0])
下图显示了 show_predictions()
函数的输出,该函数用于模型对数据的分类情况的可视化。
(3) 创建 MLP
网络,对其进行训练,并可视化运行结果:
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
## Neural Network
class Neural_Network:
def __init__(self, n_in, n_hidden, n_out):
# Network dimensions
self.n_x = n_in
self.n_h = n_hidden
self.n_y = n_out
# Parameters initialization
self.W1 = np.random.randn(self.n_h, self.n_x) * 0.01
self.b1 = np.zeros((self.n_h, 1))
self.W2 = np.random.randn(self.n_y, self.n_h) * 0.01
self.b2 = np.zeros((self.n_y, 1))
def forward(self, X):
""" Forward computation """
self.Z1 = self.W1.dot(X.T) + self.b1
self.A1 = np.tanh(self.Z1)
self.Z2 = self.W2.dot(self.A1) + self.b2
self.A2 = sigmoid(self.Z2)
def back_prop(self, X, Y):
""" Back-progagate gradient of the loss """
m = X.shape[0]
self.dZ2 = self.A2 - Y
self.dW2 = (1 / m) * np.dot(self.dZ2, self.A1.T)
self.db2 = (1 / m) * np.sum(self.dZ2, axis=1, keepdims=True)
self.dZ1 = np.multiply(np.dot(self.W2.T, self.dZ2), 1 - np.power(self.A1, 2))
self.dW1 = (1 / m) * np.dot(self.dZ1, X)
self.db1 = (1 / m) * np.sum(self.dZ1, axis=1, keepdims=True)
def train(self, X, Y, epochs, learning_rate=1.2):
""" Complete process of learning, alternates forward pass,
backward pass and parameters update """
m = X.shape[0]
for e in range(epochs):
self.forward(X)
loss = -np.sum(np.multiply(np.log(self.A2), Y) + np.multiply(np.log(1-self.A2), (1 - Y))) / m
self.back_prop(X, Y)
self.W1 -= learning_rate * self.dW1
self.b1 -= learning_rate * self.db1
self.W2 -= learning_rate * self.dW2
self.b2 -= learning_rate * self.db2
if e % 1000 == 0:
print("Loss ", e, " = ", loss)
def predict(self, X):
""" Compute predictions with just a forward pass """
self.forward(X)
return np.round(self.A2).astype(np.int)
nn = Neural_Network(2, middle_layer, 1)
nn.train(X, Y, epochs, 1.2)
show_predictions(nn, X, Y, "Neural Network")
nn_predictions = nn.predict(X)
print("Neural Network accuracy : ", np.sum(nn_predictions == Y) / Y.shape[0])
MLP
网络的训练结果如下所示,使用 MLP
网络的结果比 sklearn
的逻辑回归模型性能更好。但这个简单的网络仍然难以解决所有问题数据集。
下图显示了 MLP
网络在圆圈问题集进行训练后的输出。可以看到,圆圈问题的准确率为 50%
,而月亮问题的准确率为 89%
。
可以通过完成以下问题进一步理解 MLP
及其训练过程:
- 增加或减少样本数量,然后重新运行代码
- 更改问题类型和难度,然后在每次更改后重新运行代码
- 更改模型参数和中间层,然后重新运行
小结
在本文中, 我们了解了神经网络的相关基础知识,同时利用 Numpy
从零开始实现了神经网络的训练过程——前向传播和反向传播,了解了神经网络的通用训练流程。
系列链接
遗传算法与深度学习实战(1)——进化深度学习
遗传算法与深度学习实战(2)——生命模拟及其应用
遗传算法与深度学习实战(3)——生命模拟与进化论
遗传算法与深度学习实战(4)——遗传算法(Genetic Algorithm)详解与实现
遗传算法与深度学习实战(5)——遗传算法中常用遗传算子
遗传算法与深度学习实战(6)——遗传算法框架DEAP
遗传算法与深度学习实战(7)——DEAP框架初体验
遗传算法与深度学习实战(8)——使用遗传算法解决N皇后问题
遗传算法与深度学习实战(9)——使用遗传算法解决旅行商问题
遗传算法与深度学习实战(10)——使用遗传算法重建图像
遗传算法与深度学习实战(11)——遗传编程详解与实现
遗传算法与深度学习实战(12)——粒子群优化详解与实现
遗传算法与深度学习实战(13)——协同进化详解与实现
遗传算法与深度学习实战(14)——进化策略详解与实现
遗传算法与深度学习实战(15)——差分进化详解与实现
遗传算法与深度学习实战(16)——神经网络超参数优化
遗传算法与深度学习实战(17)——使用随机搜索自动超参数优化
遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化
遗传算法与深度学习实战(19)——使用粒子群优化自动超参数优化
遗传算法与深度学习实战(20)——使用进化策略自动超参数优化
遗传算法与深度学习实战(21)——使用差分搜索自动超参数优化