之前一篇笔记: Python机器学习笔记:不得不了解的机器学习知识点(1)
1,什么样的资料集不适合用深度学习?
- 数据集太小,数据样本不足时,深度学习相对其它机器学习算法,没有明显优势。
- 数据集没有局部相关特性,目前深度学习表现比较好的领域主要是图像/语音/自然语言处理等领域,这些领域的一个共性是局部相关性。图像中像素组成物体,语音信号中音位组合成单词,文本数据中单词组合成句子,这些特征元素的组合一旦被打乱,表示的含义同时也被改变。对于没有这样的局部相关性的数据集,不适于使用深度学习算法进行处理。举个例子:预测一个人的健康状况,相关的参数会有年龄、职业、收入、家庭状况等各种元素,将这些元素打乱,并不会影响相关的结果。
2,softmax函数的数学推导及Python实现
softmax用于多分类过程中最后一层,将多个神经元的输出,映射到(0, 1)区间内,可以看成概率来理解,从而来进行多分类!
softmax函数如下:
更形象的如下图表示:
softmax 直白来说就是讲原来输出是 3, 1, -3 通过 softmax 函数一作用,就映射成为(0, 1)的值,而这些值的累和为1,那么我们就可以将其理解成概率,在最后选取输出节点的时候,我们可以选取概率最大的节点,作为我们的预测目标!
Python代码实现:
# _*_coding:utf-8_*_ import tensorflow as tf import numpy as np import math # softmax函数,或称归一化指数函数 def softmax(x, axis=1): # 为了避免求 exp(x) 出现溢出的情况,一般需要减去最大值 # 计算每行的最大值 row_max = x.max(axis=axis) # 每行元素都需要减去对应的最大值,否则求exp(x)会溢出,导致INF情况 row_max = row_max.reshpae(-1, 1) x = x - row_max x_exp = np.exp(x) # 如果是列向量,则axis=0 x_sum = np.sum(x_exp, axis=1, keepdims=True) s = x_exp / x_sum return s # 简单一些 def softmax(x): """Compute softmax values for each sets of scores in x.""" e_x = np.exp(x - np.max(x)) return e_x / e_x.sum() # 使用 tf的softmax函数 with tf.Session() as sess: tf_s2 = tf.nn.softmax(x, axis=axis) s2 = sess.run(tf_s2)
下面我们分析一下,减去最大值和不减去最大值是否有必要吗?首先看代码:
import numpy as np def softmax(x): """Compute softmax values for each sets of scores in x.""" e_x = np.exp(x - np.max(x)) return e_x / e_x.sum() def softmax1(x): """Compute softmax values for each sets of scores in x.""" return np.exp(x) / np.sum(np.exp(x), axis=0) scores = [3.0, 1.0, 0.2] print(softmax(scores)) print(softmax1(scores)) ''' 结果输出如下: [0.8360188 0.11314284 0.05083836] [0.8360188 0.11314284 0.05083836] '''
其实两个结果输出是一样的,即使第一个实现了每列和最大值的差异,然后除以总和,但是问题来了,实现在代码和时间复杂度方面是否相似?哪一个更有效率?
当然,他们都是正确的,但是从数值稳定性的角度来看,第一个是正确的,因为我们避免了求 exp(x) 出现溢出的情况,这里减去了最大值。我们推导一下:
# 转化公式: a ^(b – c)=(a ^ b)/(a ^ c) e ^ (x - max(x)) / sum(e^(x - max(x)) = e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x))) = e ^ x / sum(e ^ x)
3,欧氏距离和曼哈顿距离
欧氏距离(也称为欧几里得度量),是应用勾股定理计算两个点之间的直线距离,也就是指m维空间两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。
下面是欧式距离的公式(分别是二维空间,三维空间,n维空间):
曼哈顿距离表示的是两个点在标准坐标系上绝对轴距之和,是种使用在几何度量空间的几何学用语。
图中红线代表曼哈顿距离,绿色代表欧氏距离,也就是直线距离,而蓝色和黄色代表等价的曼哈顿距离。
欧氏距离和曼哈顿距离的Python实现:
import numpy as np def manhattan_distance(vec1, vec2): """ This method implements the manhattan distance metric :param p_vec: vector one :param q_vec: vector two :return: the manhattan distance between vector one and two """ return np.sum(np.fabs(vec1 - vec2)) def edclidean_distance(vec1, vec2): """ This method implements the edclidean distance metric :param vec1: vector one :param vec2: vector two :return: the edclidean distance between vector one and two """ # 方法一 distance = np.sqrt(np.sum(np.square(vec1 - vec2))) # method 2 dist = np.linalg.norm(vec1 - vec2) return distance
4,什么是数据埋点
数据埋点我们可以分为两类,其一是页面统计,其二是行为统计。
页面统计可以帮我们知晓某个页面被多少人访问了多少次,行为统计是指用户在界面上的操作行为,应用最多的是按钮的点击次数。
5,请简要说说一个完整的机器学习项目流程
5.1:抽象成数学问题
明确问题是进行机器学习的第一步。机器学习的训练过程通常都是一件非常耗时的事情,胡乱尝试时间成本是非常高的。
这里的抽象成数学问题,指的我们明确我们可以获得什么样的数据,目标是一个分类还是回归或者是聚类的问题,如果都不是的话,如果划归为其中的某类问题。
5.2:获取数据
数据决定了机器学习结果的上限,而算法只是尽可能逼近这个上限。数据要有代表性,否则必然会过拟合。而且对于分类问题,数据偏斜不能过于严重,不同类别的数据数量不要有数个数量级的差距。
而且还要对数据的量级有一个评估,多少个样本,多少个特征,可以估算出其对内存的消耗程度,判断训练过程中内存是否能够放得下。如果放不下就得考虑改进算法或者使用一些降维的技巧了。如果数据量实在太大,那就要考虑分布式了。
5.3 特征预处理与特征选择
良好的数据要能够提取出良好的特征才能真正发挥效力。
特征预处理、数据清洗是很关键的步骤,往往能够使得算法的效果和性能得到显著提高。归一化、离散化、因子化、缺失值处理、去除共线性等,数据挖掘过程中很多时间就花在它们上面。这些工作简单可复制,收益稳定可预期,是机器学习的基础必备步骤。
筛选出显著特征、摒弃非显著特征,需要机器学习工程师反复理解业务。这对很多结果有决定性的影响。特征选择好了,非常简单的算法也能得出良好、稳定的结果。这需要运用特征有效性分析的相关技术,如相关系数、卡方检验、平均互信息、条件熵、后验概率、逻辑回归权重等方法。
5.4:训练模型与调优
直到这一步才用到我们上面说的算法进行训练。现在很多算法都能够封装成黑盒供人使用。但是真正考验水平的是调整这些算法的(超)参数,使得结果变得更加优良。这需要我们对算法的原理有深入的理解。理解越深入,就越能发现问题的症结,提出良好的调优方案。
5.5:模型诊断
如何确定模型调优的方向与思路呢?这就需要对模型进行诊断的技术。
过拟合、欠拟合判断是模型诊断中至关重要的一步。常见的方法如交叉验证,绘制学习曲线等。过拟合的基本调优思路是增加数据量,降低模型复杂度。欠拟合的基本调优思路是提高特征数量和质量,增加模型复杂度。
误差分析 也是机器学习至关重要的步骤。通过观察误差样本,全面分析误差产生误差的原因:是参数的问题还是算法选择的问题,是特征的问题还是数据本身的问题……
诊断后的模型需要进行调优,调优后的新模型需要重新进行诊断,这是一个反复迭代不断逼近的过程,需要不断地尝试, 进而达到最优状态。
5.6:模型融合
一般来说,模型融合后都能使得效果有一定提升。而且效果很好。
工程上,主要提升算法准确度的方法是分别在模型的前端(特征清洗和预处理,不同的采样模式)与后端(模型融合)上下功夫。因为他们比较标准可复制,效果比较稳定。而直接调参的工作不会很多,毕竟大量数据训练起来太慢了,而且效果难以保证。
5.7:上线运行
这一部分内容主要跟工程实现的相关性比较大。工程上是结果导向,模型在线上运行的效果直接决定模型的成败。 不单纯包括其准确程度、误差等情况,还包括其运行的速度(时间复杂度)、资源消耗程度(空间复杂度)、稳定性是否可接受。
这些工作流程主要是工程实践上总结出的一些经验。并不是每个项目都包含完整的一个流程。这里的部分只是一个指导性的说明,只有大家自己多实践,多积累项目经验,才会有自己更深刻的认识。
6,全连接神经网络网络结构
(此题参考:https://blog.csdn.net/cuiyuan605/article/details/84307323)
神经网络算法,是使用计算机模拟生物神经系统,来模拟人类思维方式的算法。它的基本单位就是人工神经元。通过相互连接形成一张神经网络。对于神经网络有些了解的盆友可能都知道,神经网络其实就是一个输入 X(向量) 到输出 Y(向量)的映射函数:f(x) = Y,函数的系数就是我们所要训练的网络参数 W,只要函数系数确定下来,对于任何输入xi,我们就能得到一个与之对应的输出 yi,至于 yi 是否符合我们的预期,这就是输入如何提高模型性能方面的问题。
生物神经网络中,每个神经元与其他神经元连接,当它“激活”时,会传递化学物质到相连的神经元,改变其他神经元的电位,当电位达到一定“阈值”,那么这个神经元也会被激活。
单个人工神经元的计算公式如下:
其中:
为输入参数向量,表示其他神经元输入的信号。
为每个输入参数的权重值,表示对应神经元信号的权重。
theta 为阈值或者偏差值,是指该激活神经元的难易程度。
y 为神经元的输出值,表示该神经元是否被激活。
Act() 为激活函数,理想的激活函数如下图(a)中的跃阶函数,“1” 为神经元兴奋,“0”为神经元抑制,但由于跃阶函数具有不是连续可导等不好的性质,因此一般采用下面(b) 图的 Sigmoid 函数作为激活函数:
下面定义一个全连接神经网络:
全连接神经网络,就是指每一层的每个神经元都和下一层的每个神经元项连接。
Layer:0 为输入层
Layer:L 为输出层
其他L-1 个Layer 为隐层
输入 x :,我们称一个输入值 x 为一个样本
输出 y :,变量的上标(L)表示该变量出于神经网络的那一层。
表示第 L 层编号为 i 的神经元
表示第 L 层的神经元数量
7,全连接神经网络的前向传播
前向传播比较简单,就是向量点乘,也就是加权求和,然后经过一个激活函数。也就是网络如何根据输入 X 得到输出 Y的。
记 为第 l-1 层第 k个神经元到第 l 层第 j 个神经元的权重,
为第 l 层 第 j 个神经元的偏置,
为第 l 层第 j 个神经元的激活值(激活函数的输出)。不难看出
的值取决于上一层神经元的激活:
将上面重写为矩阵形式:
为了方便表示,记 为每一层权重输入,矩阵形式则变为
利用矩阵形式可以一层层计算网络的激活值,最终能根据输入X 得到相应的输出 。
8,随机梯度下降法
(此题参考:https://blog.csdn.net/qq_38150441/article/details/80533891 和 https://blog.csdn.net/qq_39037383/article/details/89156894)
梯度下降算法的思想就是根据人类在渐进学习中,不断从错误中纠正自己的认知的过程中感触到的。
8.1 梯度下降
简单来说,梯度下降就是从山顶找一条最短的路走到山底最低的地方。但是因为选择方向的原因,我们找到的最低点可能不是真正的最低点。如图所示,黑色标注的路线所指的方向并不是真正的地方。(因为梯度下降是一种思想,没有严格的定义,所以用一个比喻来解释什么是梯度下降)
既然是选择一个方向下山,那么这个方向该如何选?每次该怎么走?
先说选的方向,在算法中是以随机方式给出的,这也是造成有时候走不到真正最低点的原因。如果选定了方向,以后每走一步,都选择的时最陡的方向,直到最低点。总结起来就是:随机选择一个方向,然后每次都选择最陡的方向,直到这个方向上能达到的最低点。
在机器学习算法中,有时候需要对原始的模型构建损失函数,然后通过优化算法对损失函数进行优化,以便寻找到最优的参数,使得损失函数的值最小。而求解机器学习参数的优化算法中,使用最多的就是基于梯度下降的优化算法(Gradient Descent GD)。
梯度下降的优缺点:
- 优点:效率。在梯度下降法的求解过程中,只需求解损失函数的一阶导数,计算的代价比较小,可以在很多大规模数据集上应用。
- 缺点:求解的是局部最优值,即由于方向选择的问题,得到的结果不一定是全局最优步长选择,过小使得函数收敛速度慢,过大又容易找不到最优解。
8.2 随机梯度下降
随机梯度下降(SGD)是一种简单但非常有效地方法,多用于支持向量机,逻辑回归等凸损失函数下的线性分类器的学习。并且SGD已经成功应用于文本分类和自然语言处理中经常遇到的大规模和稀疏机器学习问题。SGD 既可以用于分类计算,也可以用于回归计算。
随机梯度下降法不是对每个样本集进行求梯度更新参数,而是对一个或者多个样本进行求梯度,更新参数,采集多个样本为样本集再进行如下操作:
1.初始化参数为任意值(可以取到面上任意一点) 2.对样本集里每个样本进行遍历如下操作 1.求解梯度值 2.更新参数 3.若达到指定迭代次数或者收敛条件,则训练结束
随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快。
对于一个样本的目标函数为:
对目标函数求偏导:
参数更新:
随机梯度下降的优缺点:
- 优点:由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上损失函数,这样每一轮参数的更新速度大大加快。
- 缺点:准确度下降,由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。可能会收敛到局部最优,而单个样本并不能代表全体样本的趋势,而且不易于并行实现。
9,LR的原理和Loss的推导
首先,LR是一个分类模型,讨论二分类情况下,在这个基础上我们假设样本服从伯努利分布(0~1)分布。做了假设分布后下一步就是求分布参数,这个过程一般采用极大似然估计MLE(Maximum Likelihood Estimation),具体的方法就是求该假设分布在训练样本上的联合概率(样本带入连乘),然后求其关于 theta 的最大值,为了方便计算所以一般取 -log,单调性保持不变,所有就有了 logLoss: L(Y, P(Y|X)) = - logP(Y|X)。
10,机器学习中,为何要经常对数据做归一化
(参考文献:https://blog.csdn.net/abc_138/article/details/82798674)
一般做机器学习应用的时候大部分时间是花费在特征处理上,其中很关键的一步就是对特征数据进行归一化。
首先要明白归一化的目的是什么,其目的是为了避免数值较大的特征A变化掩盖了数值较小的特征B变化,最终希望让特征AB都能对结果有影响。
那么为什么要做归一化呢?
维基百科给出的解释:1,归一化后加快了梯度下降求最优解的速度。2,归一化有可能提高精度。
解释:归一化为什么能提高梯度下降法求解最优解的速度?
如下图所示(来自:斯坦福机器学习视频)
蓝色的圈圈图代表的是两个特征的等高线。其中左图两个特征 X1和 X2的区间差别非常大,X1区间为[0, 2000] ,x2区间是 [1, 5],像这种有的数据那么大,有的数据那么小,两类之间的幅度相差这么大,其所形成的等高线非常尖。当使用梯度下降法寻求最优解时,很有可能走“之字型”路线(垂直等高线走),从而导致需要迭代很多次才能收敛。而右图对两个原始特征进行了归一化,其对应的等高线显得很圆,在梯度下降进行求解时能较快的收敛,因此如果机器学习模型使用梯度下降法求最优解时,归一化往往非常有必要,否则很难收敛,甚至不能收敛。
解释:归一化有可能提高精度
一些分类器需要计算样本之间的距离(如欧式距离),例如KNN。如果一个特征值域范围非常大,那么距离计算就主要取决于这个特征,从而与实际情况相悖(比如这时实际情况是值域范围小的特征更重要)。
归一化的类型
1,线性归一化
这种归一化方法比较适用于在数值比较集中的情况。这种方法有个缺陷,如果max和min 不稳定,很容易使得归一化结果不稳定,使得后续使用效果也不稳定。实际使用中可以用经验常量值来替代 max和 min。
2,标准差标准化
经过处理的数据符合标准正态分布,即均值为0,标准差为1。
3,非线性归一化
经常用在数据分化比较大的场景,有些数值很大,有些很小。通过一些数学函数,将原始值进行映射。该方法包括 log、指数,正切等。需要根据数据分布的情况,决定非线性函数的曲线,比如log(V, 2)还是log(V, 10)等。
11,batch
深度学习中频繁出现batch这个词语,所以我们有必要了解一下。
深度学习中 的优化算法,说白了就是梯度下降。每次的参数更新有两种方式。
第一种,遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度,更新梯度。这张方式每更新一次参数都要把数据集里的所有样本都看一遍,计算量开销大,计算速度慢,不支持在线学习,这称为 Batch gradient descent,批梯度下降。
另一种,每看一个数据就算一下损失函数,然后求梯度更新参数,这个称为随机梯度下降, stochastic gradient descent。这个方法速度比较快,但是收敛性能不太好,可能在最优点附近晃来晃去, hit 不到最优点。两次参数的更新也有可能互相抵消掉,造成目标函数震荡的比较剧烈。
为了克服两种方法的缺点,现在一般采用的时一种折中手段,mini-batch gradient decent,小批的梯度下降,这种方法把数据分为若干个批,按批来更新参数。这样一个批中的一组数据共同决定了本次梯度的方向,下降起来就不容易跑偏,减少了随机性。另外一方面因为批次的样本数与整个数据集相比少了很多,计算量也不是很大。
基本上现在的梯度下降都是基于 mini-batch的,所以Keras的模块中经常会出现 batch_size,就是指这个。
12,关于机器学习拟合问题
12.1 什么是机器学习过拟合?
所谓过拟合,就是指模型在训练集上的效果很好,在测试集上的预测效果很差。
12.2 如何避免过拟合问题?
1,重采样Bootstrap
2,L1,L2 正则化
3,决策树的剪枝操作
4,交叉验证
12.3 什么是机器学习的欠拟合?
所谓欠拟合就是模型复杂度低或者数据集太小,对模型数据的拟合程度不高,因此模型在训练集上的效果就不好。
12.4 如何避免欠拟合问题?
1,增加样本数量
2,增加样本特征的数量
3,可以进行特征维度扩展
12.5 算法的误差一般是由那几个方面引起的?
1,因模型无法表达基本数据的复杂度而造成的偏差(bias)——欠拟合
2,因模型过度拟合训练集数据而造成的方差(variance)——过拟合
13,为什么朴素贝叶斯如此“朴素”?
贝叶斯算法简单高效,在处理分类问题上,是首先要考虑的方法之一。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。公式如下:
该公式最大的优点就是可以忽略AB 的联合概率直接求其条件概率分布。
而朴素贝叶斯为什么如此朴素,因为他假定所有的特征在数据集中的作用是同样重要和独立的。正如我们所知,这个假设在现实世界中是很不真实的,因此说朴素贝叶斯真的很“朴素”。
朴素贝叶斯分类是一种非常简单的分类算法,其思想是朴素的。即:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,那个最大,就认为此待分类项属于那个类别。
理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小的时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
14,反向传播算法(BP算法)的推导及其Python实现
下面学习如何调整一个神经网络的参数,也就是误差反向传播算法(BP算法)。以得到一个能够根据输入,预测正确输出的模型。
14.1,首先我们要了解优化的目标
根据人工神经元的定义,有以下三个公式:
其中,Act() 是激活函数,之前学习过。
根据上面两个公式,可以得出各个神经元之间的通用公式,如下:
其中上式是人工神经网络正向传播的核心公式。
那么,我们根据什么来调整神经网络的参数,以得到一个能够正确预测结果的模型呢?请看下面的公式:
上式用来计算我们期望的输出和实际输出的“差别”,其中cost() 叫做损失函数。我们的期望是损失函数值达到最小。
但是只根据一次输出的损失值,对参数进行调整,无法使模型适应所有输入样本。我们需要的是,调整参数,使得所有输入样本,得到输出的总损失值最小,而不是只让妻子一个样本的损失值最小,导致其他样本损失值增大。因此有下面公式:
上式表示一个 batch 的所有样本输出的总损失值的平均值。其中,bn 表示一个 batch中样本的数量。
为什么不用所有的样本计算损失值,而将所有样本分成一个个的 batch呢?因为所有的训练样本数量太大了,可能有数以百万计,将所有的样本损失值都一起进行运算,计算量过于庞大,大大降低了模型计算的速度。
而计算总的损失值 C,其中是一个以所有的连接权重 W 和 所有的阈值 theta 未为变量的多元函数。我们想要的模型就是求得 C 最小时,所有 W 和 theta 的值。直接计算显然是不可能的,因为对于一个大的深度神经网络,所有的参数变量,可能数以万计。
在这里我们使用梯度下降算法来逐步逼近 C的最小值,也即是先随机得到一组参数变量的值,然后计算参数变量当前的梯度,向梯度的反方向,也就是C变小最快的方向,逐步调整参数值,最终得到 C 的最小值,或者近似最小值。
而将所有样本,随机分成一个个固定长度的 batch,以得到近似的梯度方向,叫做随机梯度下降算法。
14.2 开始求梯度
那么根据梯度的定义,接下来的任务,就是求取各个参数变量相对于 C 的偏导数。我们将使用误差反向传播算法来求取各个参数变量的偏导数。
求取偏导数的方法和神经网络正向传播(根据样本计算输出值)的方式类似,也是逐层求解,只是方向正好相反,从最后一层开始,逐层向前。
首先,我们先求神经网络最后一层,也即是输出层的相关参数的偏导数。为了降低推导的复杂性,我们只计算相对一个样本的损失值函数 Cbi 的偏导数,因为相对于总损失值函数 C 的偏导数值,也不过是把某个参数的所有相对于 Cbi 偏导数值加起来而已。
根据上面公式,以及 复合函数求导法则,可以得到输出层(L层)某个神经元的权值参数 W 的偏导数,计算公式如下:
根据前面三个公式求导如下:
将这三个公式代入上面公式,可以得到:
我们令:
则:
将上式代入损失函数求导的公式中可以得到:
这样我们就得到了输出层 L 相关的权重参数 W 的偏导数计算公式!
接下来,同理可以求得输出层 L 相关的阈值 theta 的偏导数计算公式为:
而根据第二个公式可以得到:
将上式代入到上上式可以得到:
这就是 输出层 L 相关的阈值 theta 的偏导数计算公式!
14.3 根据 L 层,求前一层参数的偏导函数
从下面公式,可知,一个权重参数 W 只影响一个 L-1 层的神经元:
因此可以得到有下面公式:
将上式代入到上上式可以得到:
根据假设:
我们可以得到:
将上式代入到上上式,可以得到:
同理,我们可以得到:
根据14.3 第一个公式可以得到:
将上式代入到上上式,可以得到:
这样我们就得到了 L-1 层神经元相关参数的计算公式。
下面我们还需要推导一下 之间的关系,根据下面公式:
我们可以得到:
同理可得:
将上式代入到上上式,可以得:
我们知道,一个权重参数 W 只影响一个 L-1 层的神经元,但这个 L-1 层神经元影响了所有 L层的神经元。因此,根据多元复合函数求导法则。有:
根据我们之前的假设,可以得到:
将上式代入到上上式,可以得到:
我们可以知道:
将上式代入到上上式,可以得到:
最后将上式代入之前的公式,可以得到:
这样我们就得到了反向传播,逐层推导的通用公式:
这里, W 和 Z 都是整箱传播过程中已经算好的常数,而 可以从 L层开始逐层向前推导,直到第1层,第0层是输入层,不需要调整参数,而第L层的参数可以参考下面公式:
下面是全连接神经网络的Python实现代码:
#coding=utf-8 import numpy as np import matplotlib.pylab as plt import random class NeuralNetwork(object): def __init__(self, sizes, act, act_derivative, cost_derivative): #sizes表示神经网络各层的神经元个数,第一层为输入层,最后一层为输出层 #act为神经元的激活函数 #act_derivative为激活函数的导数 #cost_derivative为损失函数的导数 self.num_layers = len(sizes) self.sizes = sizes self.biases = [np.random.randn(nueron_num, 1) for nueron_num in sizes[1:]] self.weights = [np.random.randn(next_layer_nueron_num, nueron_num) for nueron_num, next_layer_nueron_num in zip(sizes[:-1], sizes[1:])] self.act=act self.act_derivative=act_derivative self.cost_derivative=cost_derivative #前向反馈(正向传播) def feedforward(self, a): #逐层计算神经元的激活值,公式(4) for b, w in zip(self.biases, self.weights): a = self.act(np.dot(w, a)+b) return a #随机梯度下降算法 def SGD(self, training_data, epochs, batch_size, learning_rate): #将训练样本training_data随机分为若干个长度为batch_size的batch #使用各个batch的数据不断调整参数,学习率为learning_rate #迭代epochs次 n = len(training_data) for j in range(epochs): random.shuffle(training_data) batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)] for batch in batches: self.update_batch(batch, learning_rate) print("Epoch {0} complete".format(j)) def update_batch(self, batch, learning_rate): #根据一个batch中的训练样本,调整各个参数值 nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] #计算梯度,并调整各个参数值 self.weights = [w-(learning_rate/len(batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(learning_rate/len(batch))*nb for b, nb in zip(self.biases, nabla_b)] #反向传播 def backprop(self, x, y): #保存b和w的偏导数值 nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] #正向传播 activation = x #保存每一层神经元的激活值 activations = [x] #保存每一层神经元的z值 zs = [] for b, w in zip(self.biases, self.weights): z = np.dot(w, activation)+b zs.append(z) activation = self.act(z) activations.append(activation) #反向传播得到各个参数的偏导数值 #公式(13) d = self.cost_derivative(activations[-1], y) * self.act_derivative(zs[-1]) #公式(17) nabla_b[-1] = d #公式(14) nabla_w[-1] = np.dot(d, activations[-2].transpose()) #反向逐层计算 for l in range(2, self.num_layers): z = zs[-l] sp = self.act_derivative(z) #公式(36),反向逐层求参数偏导 d = np.dot(self.weights[-l+1].transpose(), d) * sp #公式(38) nabla_b[-l] = d #公式(37) nabla_w[-l] = np.dot(d, activations[-l-1].transpose()) return (nabla_b, nabla_w) #距离函数的偏导数 def distance_derivative(output_activations, y): #损失函数的偏导数 return 2*(output_activations-y) # sigmoid函数 def sigmoid(z): return 1.0/(1.0+np.exp(-z)) # sigmoid函数的导数 def sigmoid_derivative(z): return sigmoid(z)*(1-sigmoid(z)) if __name__ == "__main__": #创建一个5层的全连接神经网络,每层的神经元个数为1,8,5,3,1 #其中第一层为输入层,最后一层为输出层 network=NeuralNetwork([1,8,5,3,1],sigmoid,sigmoid_derivative,distance_derivative) #训练集样本 x = np.array([np.linspace(-7, 7, 200)]).T #训练集结果,由于使用了sigmoid作为激活函数,需保证其结果落在(0,1)区间内 y = (np.cos(x)+1)/2 #使用随机梯度下降算法(SGD)对模型进行训练 #迭代5000次;每次随机抽取40个样本作为一个batch;学习率设为0.1 training_data=[(np.array([x_value]),np.array([y_value])) for x_value,y_value in zip(x,y)] network.SGD(training_data,5000,40,0.1) #测试集样本 x_test = np.array([np.linspace(-9, 9, 120)]) #测试集结果 y_predict = network.feedforward(x_test) #图示对比训练集和测试集数据 plt.plot(x,y,'r',x_test.T,y_predict.T,'*') plt.show()