深度神经网络_深度神经网络原理与实践
理论基础
什么是神经网络
该领域中的深度学习被视为机器学习的重要组成部分,在数据特征提取方面具有独特优势。基于人工神经网络构建的模型,在数据特征提取方面具有独特优势。在深度学习框架下发展起来的一种专门模型,在 wikipedia 上的解释如下:
深度神经网络(Deep Neural Networks, DNN)是一种基于判别式的模型,并包含至少一个隐藏层的人工神经网络;其参数可通过反向传播算法完成系统性训练。权重更新可通过式(1)实现以优化计算效率
首先我们知道深度神经网络是一种判别模型。其核心在于已知变量 x 通过判别模型能够推断出 y 的值。例如在机器学习领域中常见的应用场景包括识别手写数字等实例

在深度神经网络中,“深度”指的是层级结构中的连续表示数量,在模型中包含了多少个层级就被称为模型的层次规模。“通过这些层级结构我们可以实现对数据进行高层次抽象的效果”。如上所示图形化示意图中所示的内容,“深度神经网络由一个输入层级、多个(至少一个)隐藏层级以及一个输出层级组成,并且输入层级与输出层级的数量并不一定是对称配置关系”。每一个层级内部都包含一定数量的神经元节点,在各个不同节点之间存在连接权重参数配置。

还是上面的案例,在之前的内容中已经详细阐述了如何将这些图像形式的手写数学符号转换为计算机能够处理的数据形式。具体来说,在之前的章节里我们学习到了通过使用深度学习算法对图像中的字符进行识别和分类的方法,并且也了解到具体的实现步骤包括提取特征、训练模型以及进行预测等环节。

神经元如何输入输出
在神经网络中每一个神经元都被视为一个基本的线性函数,在这种模型下我们可以进一步探讨其复杂性与应用潜力

如上图所示,n1 可以表示为:

其中符号 $w_{i,j}$ 表示各神经元之间的连接权重(weight coefficient),而b 是一个固定数值(constant value),充当函数的作用偏移量(offset term)。较小的权重系数能够减弱某一个体神经元对另一个层中相邻神经元的影响程度(degree of influence),而较大的权重系数会增强信号传输的效果(transmission effect)。例如设定 $w_{i,j} = 0.1$ 和 $w_{3,1} = 0.7$ ,则表明输入值x_3传递给中间层的第一个单元n_₁时所产生的作用会比x₁更为显著(significantly more prominent)。你可能会疑惑为什么每个神经元需要与其他所有层的神经元产生联系?
这里主要由两个原因:
- 完全连接的形式相对容易的编写成计算机指令。
在神经网络训练过程中,会降低真正无用的连接(即某些连接权重逐渐趋向于零)。
实际上,在获得 n_1 之后, 即无法立即用于后续的计算, 还需经过一个激活函数处理(通常使用 sigmod 函数)。


该方法的主要目的是引入非线性因素。即使神级网络仅采用上述那种线性函数,不管多少层的结果始终是线性的。
实际案例
为了简化计算过程,在本节中我们将搭建一个仅包含两层的神经网络架构,并具体阐述其运算步骤

先通过线性函数求得一个 x 值,再把 x 值带入激活函数,得到 y1 的值。


矩阵乘法
这一部分的运算流程可以通过矩阵乘法来表示,在线性代数中这是一个标准的操作方式。矩阵作为一种数学工具,在这里简单来说就是一个二维数组结构,在线性代数中具有重要的应用价值。为了直观理解其作用机制,在此给出了具体的示意图;它展示了典型的矩阵构造形式。

那么矩阵的乘法可以表示为:

矩阵的乘法一般被称为点积或者内积运算。如果我们把矩阵中的数值替换为我们的神经网络输入与权重,会发现这些计算其实异常简单。

获得点积后,只需要代入到激活函数,就能获得输出了。

通过矩阵计算过程可以表示为:

实际案例
下面通过矩阵来表示一个三层神经网络的计算过程。

上图仅展示了输入层至隐层的计算流程,值得探究的是自己动手完成隐层至输出层的运算步骤。隐层到输出层的权重矩阵如下:

反向传播
通过一轮神经网络计算得到了输出值, 通常与我们的预期结果存在差异, 这个误差即为训练数据提供的正确答案与实际输出之间的差额。然而这一误差是由多个节点共同作用产生的, 我们应该采用何种方法来更新各连接权重的问题? 此时我们需要运用反向传播的方法来求取各个节点所对应的误差数值

下面我们代入具体值,进行一次计算。

在上图中观察到,在数学表达式中 $e_1$ 的误差值主要源自 $w_{1,1}`` 和 w_{2,1}`` 两项的贡献,则该误差量应均匀地分配到这两条连接路径上;具体操作方法是依据这两条连接各自的权重系数来划分对应的误差量 `e_1$` 的具体数值。

同样地,对误差 e2 进行分割操作;接着将这两个连接点处的误差值相加计算;即可得出输出点前馈节点处的总误差。

随后依照先前的方法将该误差传递至前面的层;直至所有节点均获得自身的误差值时,则可称此过程为反向传播
使用矩阵乘法进行反向传播误差
上面如此繁琐的操作,我们也可以通过矩阵的方式进行简化。

在该矩阵中仍需处理一些较为棘手的分数问题。那么我们是否可以采取更加激进的方法来归一化分母呢?这种做法仅会调整反馈误差的规模,并不会改变其本质上的比例关系。


通过细致观察可以发现,在我们之前计算每层输出值的过程中所形成的矩阵具有相似性。然而,在这一特定情况下, 权重矩阵仅进行了转置——即右上角的位置变为左下角的位置。这使得我们可以将其称为转置矩阵, 并用符号 w^T 来表示
反向传播误差的矩阵可以简单表示为:

梯度下降
在每个点都得到误差后,我们该按照何种方式来更新权重呢?
这个时候就要使用到机器学习中常用的方式:梯度下级。

更多细节可以参考我之前写的博客:梯度下降与线性回归
经过持续不断的训练, 我们就能建立起多层次神经网络模型, 其其实质是持续地调整各个权重参数, 以便降低神经网络预测结果与真实值之间的误差值。最终能够建立起一个多层次神经网络模型, 使其通过输入数据进行有效的预测
实战
环境准备
为了更好地完成任务目标,请先访问 Python 官方网站获取所需组件。建议优先选择最新版本以确保兼容性和稳定性。避免在系统中同时存在 Python2 环境。请在完成 Python 环境搭建后继续进行下一步操作。随后,请按照指示依次进行虚拟环境的创建以及必要的附加组件的配置与下载。
``
# 升级 pip 到最新版本
``
pip3 install --upgrade pip
``
# 安装 virtualenv ,用于配置虚拟环境
``
pip3 install --user --upgrade virtualenv
``
通常情况下,在安装 Python 包时
``
# 配置虚拟环境
``
cd ~/ml
``
virtualenv env
``
# 启动虚拟环境
``
# linux
``
source env/bin/activate
``
# windows
``
./env/Scripts/activate
``
启动后,如下
``
(env) λ
``

在虚拟环境下安装所有模块依赖。
``
# 安装模块和依赖
``
(env) λ pip3 install --upgrade jupyter matplotlib numpy scipy
``
Jupyter是一种基于网页的应用程序用于交互式计算。它可用于各个阶段的计算:开发、撰写文档、编写代码以及呈现成果;其优势在于支持多种编程语言,并且能够实时生成输出结果;通过Jupyter可以在不同领域中进行探索性数据分析、算法开发以及技术演示等;无论是在线教学还是科学研究都广泛使用Jupyter笔记本,在线环境下的强大功能使其成为数据科学者的首选工具;
numpy是Python编程语言中的一个功能强大的工具包,在科学计算领域具有重要地位。它能够处理多维数据的运算,并包含丰富的数学函数集合
它是以numpy为基础开发的一个扩展包,并且新增的功能模块涉及数值积分、最优化算法以及一些专用函数。
matplotlib:以numpy为基础的扩展包,在科学计算领域中提供了丰富的数据可视化工具,并且其主要功能是生成各种统计图表。
scikit-learn作为开源Python机器学习库,在强大的数值计算和科学计算库Numpy及Scipy的基础上提供了丰富的数据挖掘与分析工具,并涵盖数据预处理相关内容。这些工具不仅包括经典的模型评估方法(如交叉验证),还涵盖多种算法以及可视化技术的一系列接口设计。
启动 jupyter
``
jupyter notebook
``
jupyter 会在8888端口起一个服务,并自动打开浏览器。

在页面右上方的新选项中点击即可建立一个新的项目。创建项目后,在该页面上执行 Python 代码以查看其输出结果会很方便。

准备数据
MNIST数据集是由美国高中生及其人口普查局职员提供的手写数字(0至9)图像构成。为了使我们的程序能够从这些图像中提取出有效的信息并识别出输入图像所代表的数字含义,请指导我们的程序进行学习。听起来似乎有些挑战性,请指导我们按步骤推进。
MNIST 数据集中的训练样本被准备好,在其中变量 train_100 被定义为训练集合。而变量 test_10 则被指定为测试集合。在机器学习中通常会将整个数据集划分为两部分:一部分用于模型的训练(占80%),另一部分则用于模型的验证。作为 hello world 类型的操作仅采用了 100 个样本用于模型训练,在实际应用中这种情况的数据规模明显偏小。
-
mnisttrain100.csv
-
mnisttest10.csv
如果想用完整的数据进行训练,可以下载这个 csv 文件。
https://pjreddie.com/media/files/mnist_train.csv
观察数据
在获取数据之后,在编程环境中调用 Python 软件实现对这些文件的读取操作。随后,在编程环境中调用 Python 软件实现对这些文件的读取操作。将带有逗号分隔值格式的数据文件放置于 datasets 存储目录中
``
data_file = open("datasets/mnist_train_100.csv", 'r')
``
data_list = data_file.readlines() # 该函数用于获取文件中全部行,并返回一个数组
``
data_file.close()
``
len(data_list) # 数组长度为100
``
打印第一行文本,看看数据的格式是怎么样的
``
print(data_list[0])
``
len(data_list[0].split(',')) # 使用 , 进行分割,将字符串转换为数组
``

可以看出该数据集共计拥有785条样本。其中第一列表示该手写数字的真实值(在机器学习领域被称为标签),其余784列则代表一个28×28像素尺寸的图像特征。通常情况下,常用的图像处理软件会采用每位像素占用8位二进制空间来表示其亮度信息(即0至255之间的数值范围)。这样一来整个图像便被划分为256个灰度级别每个级别对应特定亮度的变化范围
我们导入 numpy 库用于数据处理,并提取 values[1:] 数组中的一维所有元素生成一个新的一维数组;接着使用 numpy.asfarray 将该数组转换为一个浮点类型的 ndarray;随后将每个数值依次除以 255 后再乘以 9 转换为介于 0 到 9 的个位数字;并将其转换为整数类型的数据;最后通过 reshape((28, 28)) 将其转变为一个二维数组形状。
如果想了解更多 numpy 的资料,可以查看它的文档。
``
import numpy as np
``
values = data_list[3].split(',')
``
image_array = (np.asfarray(values[1:]) / 255 * 9).astype(int).reshape(28,28)
``

这样看不够直观,接下来使用 matplotlib ,将像素点一个个画出来。
``
import matplotlib.pyplot
``
%matplotlib inline
``
matplotlib.pyplot.imshow(
``
np.asfarray(values[1:]).reshape(28,28),
``
cmap='Greys',
``
interpolation='None'
``
)
``

搭建神经网络
我们简单勾勒出神经网络的大概样子,至少需要三个函数:
-
初始化函数——设定输入层、隐藏层、输出层节点的数量,随机生成的权重。
-
训练——学习给定的训练样本,调整权重。
-
查询——给定输入,获取预测结果。
框架代码如下:
``
# 引入依赖库
``
import numpy as np
``
import scipy.special
``
import matplotlib.pyplot
``
# 神经网络类定义
``
class neuralNetwork:
``
# 初始化神经网络
``
def __init__():
``
pass
``
# 训练神经网络
``
def train():
``
pass
``
# 查询神经网络
``
def query():
``
pass
``
初始化神经网络
接下来让我们进行第一步操作,初始化一个神经网络。
``
# 初始化神经网络
``
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
``
# 设置输入层、隐藏层、输出层节点的数量
``
self.inodes = inputnodes
``
self.hnodes = hiddennodes
``
self.onodes = outputnodes
``
# 连接权重,随机生成输入层到隐藏层和隐藏层到输出层的权重
``
self.wih = np.random.rand(self.hnodes, self.inodes) - 0.5
``
self.who = np.random.rand(self.onodes, self.hnodes) - 0.5
``
# 学习率
``
self.lr = learningrate
``
# 将激活函数设置为 sigmoid 函数
``
self.activation_function = lambda x: scipy.special.expit(x)
``
pass
``
生成权重
通过调用 numpy 库来生成连接权重,并且该库支持高维数组及其矩阵运算。利用 numpy.random.rand(x,y) 能够高效地创建一个形状为 (x,y) 的二维数组,在其中每个元素都是在0到1之间的均匀分布随机数。值得注意的是,在导入 numpy 库时我们采用了标准的导入语法:import numpy as np ,因此在代码编写过程中可以直接使用 np 替代全称 numpy。

该案例是利用 numpy.random.rand 函数产生了 3*3 大小的矩阵。为了使生成的所有权重都落在 -0.5 至 0.5 的范围内,并将其转换为均匀分布。

激活函数
scipy.special 模块内集成了丰富的函数库集合体,在该模块内我们可以非常容易地构建出一个激活函数。
``
activation_function = lambda x: scipy.special.expit(x)
``
查询神经网络
``
# 查询神经网络
``
def query(self, inputs_list):
``
# 将输入的数组转化为一个二维数组
``
inputs = np.array(inputs_list, ndmin=2).T
``
# 计算输入数据与权重的点积
``
hidden_inputs = np.dot(self.wih, inputs)
``
# 经过激活函数的到隐藏层数据
``
hidden_outputs = self.activation_function(hidden_inputs)
``
# 计算隐藏层数据与权重的点积
``
final_inputs = np.dot(self.who, hidden_outputs)
``
# 最终到达输出层的数据
``
final_outputs = self.activation_function(final_inputs)
``
return final_outputs
``
查询神经网络的操作较为简便,在具体实现时可以通过将两个矩阵代入 numpy 的 dot 方法来完成
这一知识点值得注意的是它涉及到了 numpy 的数据类型转换功能具体来说 numpy.array 方法能够将 python 中的数组转为一个 N 维数组对象 Ndarray 其主要作用是实现这种转换而该方法的第二个参数则主要用于指定转换后的维度设置。

上图为一个普通的一维数组 [1,2,3] 经过该方法转换后生成二维数组 [[1,2,3]]。其核心功能在于调用 numpy 的 transpose 方法实现按指定轴进行交换位置操作,并将一维转为二维或提升维度;如下图所示。

通过转置我们就能得到一个合适的输入矩阵了。


训练神经网络
``
# 训练神经网络
``
def train(self, inputs_list, targets_list):
``
# 将输入数据与目标数据转为二维数组
``
inputs = np.array(inputs_list, ndmin=2).T
``
targets = np.array(targets_list, ndmin=2).T
``
# 通过矩阵点积和激活函数得到隐藏层的输出
``
hidden_inputs = np.dot(self.wih, inputs)
``
hidden_outputs = self.activation_function(hidden_inputs)
``
# 通过矩阵点积和激活函数得到最终输出
``
final_inputs = np.dot(self.who, hidden_outputs)
``
final_outputs = self.activation_function(final_inputs)
``
# 获取目标值与实际值的差值
``
output_errors = targets - final_outputs
``
# 反向传播差值
``
hidden_errors = np.dot(self.who.T, output_errors)
``
# 通过梯度下降法更新隐藏层到输出层的权重
``
self.who += self.lr * np.dot(
``
(output_errors * final_outputs * (1.0 - final_outputs)),
``
np.transpose(hidden_outputs)
``
)
``
# 通过梯度下降法更新输入层到隐藏层的权重
``
self.wih += self.lr * np.dot(
``
(hidden_errors * hidden_outputs * (1.0 - hidden_outputs)),
``
np.transpose(inputs)
``
)
``
pass
``
在训练神经网络的前半部分时,在中间阶段计算得到的误差差值会被通过矩阵点积的方式反向传播到各层神经元中,在此之后最终采用梯度下降法对权重进行调整以完成模型优化任务。其中self.lr表示梯度下降的学习率参数,在实际应用中该参数决定了影响梯度方向的速度,并且为了使模型达到最佳性能状态我们需要持续地优化这一参数设置以获得更好的预测效果
进行训练
``
# 设置每一层的节点数量
``
input_nodes = 784
``
hidden_nodes = 100
``
output_nodes = 10
``
# 学习率
``
learning_rate = 0.1
``
# 创建神经网络模型
``
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes, learning_rate)
``
# 加载训练数据
``
training_data_file = open("datasets/mnist_train_100.csv", 'r')
``
training_data_list = training_data_file.readlines()
``
training_data_file.close()
``
# 训练神经网络
``
# epochs 表示训练次数
``
epochs = 10
``
for e in range(epochs):
``
# 遍历所有数据进行训练
``
for record in training_data_list:
``
# 数据通过 ',' 分割,变成一个数组
``
all_values = record.split(',')
``
# 分离出图片的像素点到一个单独数组
``
inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
``
# 创建目标输出值(数字 0~9 出现的概率,默认全部为 0.01)
``
targets = np.zeros(output_nodes) + 0.01
``
# all_values[0] 表示手写数字的真实值,将该数字的概率设为 0.99
``
targets[int(all_values[0])] = 0.99
``
n.train(inputs, targets)
``
pass
``
pass
``
# 训练完毕
``
print('done')
``
验证训练结果
``
# 加载测试数据
``
test_data_file = open("datasets/mnist_test_10.csv", 'r')
``
test_data_list = test_data_file.readlines()
``
test_data_file.close()
``
# 测试神经网络
``
# 记录所有的训练值,正确存 1 ,错误存 0 。
``
scorecard = []
``
# 遍历所有数据进行测试
``
for record in test_data_list:
``
# 数据通过 ',' 分割,变成一个数组
``
all_values = record.split(',')
``
# 第一个数字为正确答案
``
correct_label = int(all_values[0])
``
# 取出测试的输入数据
``
inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
``
# 查询神经网络
``
outputs = n.query(inputs)
``
# 取出概率最大的数字,表示输出
``
label = np.argmax(outputs)
``
# 打印出真实值与查询值
``
print('act: ', label, ' pre: ', correct_label)
``
if (label == correct_label):
``
# 神经网络查询结果与真实值匹配,记录数组存入 1
``
scorecard.append(1)
``
else:
``
# 神经网络查询结果与真实值不匹配,记录数组存入 0
``
scorecard.append(0)
``
pass
``
pass
``
# 计算训练的成功率
``
scorecard_array = np.asarray(scorecard)
``
print("performance = ", scorecard_array.sum() / scorecard_array.size)
``
完整代码
要查看完整代码可以访问我的 github:deepneuralnetwork
总结
到此为止整个深度神经网络的模型原理与实践已经全面完成。尽管在某些方面概念阐述不够深入但你依然可以通过查阅其他参考资料来获取更多知识。感谢《Python神经网络编程》这本书因为正是它让我得以开始这篇博客如果你对此感兴趣也可以考虑购买这本书它确实以一种非常简单明了的方式解释了复杂的数学运算
如今人工智能领域正展现出蓬勃发展之势,并非仅仅停留在表面热度上。值得感兴趣的同学积极投入;但也要切勿忽视自身的优势所在,在过分追求最新趋势的同时不可迷失自我。
