从零开始在Python中实现神经网络:入门指南与数学公式推导
翻译如下

**
在 SCRATCH采用python 上实现一种神经网络
**注:Scratch是由麻省理工学院(MIT)设计和开发的一款针对青少年的简单编程工具。在这里写下链接内容。本文翻译自《IMPLMENTING A NEURAL NETWORK FROM SCRATCH IN PYTHON – AN INRODUCTION》。原始链接在这里。在这里写下链接内容。另外,我这里会给出原文数学公式的推导以及原文一些概念的修正;在这里,我将展示一个简单的三层神经网络。我不会详细推导与本文相关的所有数学公式。为了以直观的形式表达我的想法,我还将提供一些相关链接,以便读者更好地理解本文的细节。
我认为你对基本的微积分知识和机器学习概念有一定的了解。同样,您应该能够理解分类和回归的概念。如果你能理解优化的概念,比如梯度下降的原理就更好了。如果您不理解本文的概念,您可能仍然会觉得本文非常有趣。
但为什么要使用scratch来实现神经网络呢?即使以后使用PyBrain等其他软件平台,仅仅用scratch做一次神经网络也是非常有价值的。它可以帮助您了解神经网络的工作原理,这对于构建模型很有用。至关重要。
我想指出的是,这里的代码示例效率不是很高,但是很容易理解。在以后的文章中,我将展示使用 Theano 的更高效的神经网络。
生成一组数据 让我们开始生成一组数据。幸运的是,scikit-learn 有一个非常有用的数据生成器。我们不需要自己编写代码来生成数据。我们使用 make_moons 函数。获取数据
#输出:数据集, 对应的类别标签
#描述:生成一个数据集和对应的类别标签
np.random.seed(0)
X, y = sklearn.datasets.make_moons(dim, noise=cnoise)
plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
我们得到两种类型的数据,分别绘制为红点和蓝点。您可以认为蓝点代表男性患者,红点代表女性患者,X 轴和 Y 轴分别代表治疗措施。我们的目标是训练一个机器分类器,让机器分类,然后给出X和Y的坐标。需要注意的是,这些数据不是线性可分的。我们不能用一条直线将这两组数据分为两类。这意味着逻辑回归等算法不能用于对此类数据进行分类,除非您使用更适合分类的手动非线性回归(例如多项式回归)算法。
事实上,神经网络的优点之一是你不必担心特征收集,隐藏层会自动学习特征。
逻辑回归(逻辑回归)
为了证明这一点,我们训练一个Logistic回归分类器,输入X、Y值并输出分类(0或1)
import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt
def generate_data():
np.random.seed(0)
X, y = datasets.make_moons(200, noise=0.20)
return X, y
def visualize(X, y, clf):
# plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
# plt.show()
plot_decision_boundary(lambda x: clf.predict(x), X, y)
plt.title("Logistic Regression")
def plot_decision_boundary(pred_func, X, y):
# Set min and max values and give it some padding
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
h = 0.01
# Generate a grid of points with distance h between them
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# Predict the function value for the whole gid
Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# Plot the contour and training examples
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.show()
def classify(X, y):
clf = linear_model.LogisticRegressionCV()
clf.fit(X, y)
return clf
图为Logistic回归分类器已经绘制出分类边界。它尽力用直线将数据分为两类,但无法捕获月亮形状的数据。
训练神经网络(训练神经网络)
我们将构建一个三层神经网络,由输入层、输出层和隐藏层组成。输入层(2)的节点数由数据的维度决定(数据是二维的,包括X、Y坐标),输出层(2)的节点数由数据类型(我们只有两种类型的数据,我们实际上只能设置一个输出节点来预测0或1,但是当设置两个节点时,可以扩展神经网络,使网络可以计算更多的分类)。输入层只需要输入X和Y的坐标,输出层应该输出二分类的概率。
我们可以设置隐藏层的节点数。隐含层节点越多,分类效果越好。不过这样做的成本也比较高。首先,网络需要学习更多的参数进行预测,这导致计算量的增加。其次,大量的参数很容易导致过拟合。如何确定隐藏层的大小?尽管有一些一般规则和建议,但通常取决于实际问题的情况。隐藏层中的节点数量更多地是一个艺术问题,而不是一个科学问题。我们将改变隐藏层的节点数量来看看输出的效果。
我们需要为隐藏层选择一个激活函数。该激活函数将层的输入值转换为输出值。非线性激活函数使我们能够完成非线性预测。激活函数一般选择,tanh,sigmoid函数,或者ReLUs。 /字体>
我们将使用 tanh,它在很多情况下都很有效。这些函数都有一个很好的优点,即它们的导数可以用它们的原始函数来表示。例如
这样的函数非常有用,因为我们只需要计算一次函数值,然后再次使用该函数值即可得到其导数值
因为我们希望网络最终输出概率,所以我们选择输出层的激活函数为softmax。 Softmax可以将原始数据转换为概率。如果您熟悉逻辑函数,您可以将其视为逻辑函数的推广。译者注:这个神经网络的算法本质上是一个BP神经网络,但是它和一般的BP网络有区别:
1.一般课本上的BP网络都使用相同的激活函数。在下面的代码中,隐藏层的激活函数为tanh,输出层的激活函数为softmax。为什么使用softmax作为输出层的激活函数,我会在下面解释。
HOW OUR NETWORK MAKES PREDICTIONS(我们的网络如何预测)
我们的网络使用前馈神经网络,它是一系列矩阵相乘,然后使用我们之前定义的激活函数。如果x是一个二维数组,输入到神经网络,输出值为
它也是二维数据。
\begin{对齐} z_1 & = xW_1 + b_1 \ a_1 & = \tanh(z_1) \ z_2 & = a_1W_2 + b_2 \ a_2 & = \hat{y} = \mathrm{softmax}(z_2) \end{对齐}
zi是第i层的输入值,ai是第i层通过激活函数的输出值,w1,b1,w2,b2是神经网络的参数。这些参数随着数据的学习而不断修改(w1是输入层与隐藏层之间的权重矩阵,b1是隐藏层的偏置,w2是隐藏层与输出层之间的权重矩阵,b1是输出层的偏差)。你也可以将其视为层与层之间的变换矩阵。通过将上述矩阵相乘,我们可以看到每个矩阵的维度。如果我们在隐藏层设置500个节点,那么
现在你可以看到,如果我们增加隐藏层的节点,我们将需要更多的参数
学习参数
通过最小化误差来不断修正每层的参数(w1,b1,w2,b2,)。但如何定义误差呢?我们称这个函数为误差损失函数; softmax 函数的误差函数称为交叉熵损失(也称为对数似然成本函数)。如果我们有 N 个训练样本和 C 个个人分类,那么对数似然成本函数如下,
是我们的预测值(计算值),y是标签值,(目标值)
这个公式看起来很复杂,但它只是将所有示例的误差相加。如果我们预测分类不正确,则 y 为(目标值),
是预测值(计算值)。这两个值之间的差异越大,误差函数就越大。通过训练数据最大化似然函数,使误差最小化,同时不断修正网络参数。
我们可以使用梯度下降法来寻找最小值。我将使用常用的梯度下降法,也称为具有固定学习速度的批量梯度下降法。它的变体是随机梯度下降和小批量梯度下降,两者都表现得很好。如果你对这个算法感兴趣,可以尝试各种算法。
输入时,梯度下降法需要输入梯度,(
),损失函数的向量导数。为了计算梯度,我们使用著名的BP算法。该算法是一种计算梯度的有效算法。我不想展示BP算法的推导细节。
译者注:
1、BP神经网络的推导详情请参见这里。在这里写下链接内容。这是一篇非常好的文章。还有这篇文章。在这里写下链接内容。 2、下面的代码还涉及到softmax的求导。请在此写下链接内容。好好看看上面三篇文章,你会对BP算法有更深入的了解。
通过误差反向传播算法,我们可以得到下面的公式(相信我,下面的公式是正确的)
译者注:那我就把上面所有的公式推导一下! ! !
BP算法1.0
表示L-1层第i个神经元与层第j个神经元之间的连接权值;
表示第L层第i个神经元的输入
表示第L层第i个神经元的输出
表示输出层的激活函数。在本文中,激活函数是softmax函数。 2.0的代价函数是最大似然函数,如下:
是我们的预测值(计算值),y是标签值,(目标值)
3.0 首先定义第L层第i个神经元到第i层产生的误差的倒数为
公式1(计算损失函数对输出层节点输入值的导数)
公式2(从后到前,计算损失函数对上一层节点的输入值的导数):
公式3(计算损失函数相对于当前层和当前层最接近的权重的偏导数):
公式4(计算损失函数对于当前层某个偏置的偏导数):
4.0
BP算法伪代码
1. 输入数据
2.初始化权重和偏置iw,b
3.前向传播
3.计算损失函数对输出层某个节点的输入值的偏导数
4.计算损失函数对隐藏层某个节点的输入值的偏导数
5.计算每层每个节点的损失函数的权重和偏置的梯度,使用梯度下降法最小化似然函数
到这里我们已经知道了BP算法的原理。我们要感谢在这里写下链接的博主。他用精炼的数学语言向我们展示了BP算法。他的推送有问题,我在文章中更正了。
但要理解下面的代码,一个很重要的问题就是softmax函数的求导。下面我们就来探讨一下这个问题。
1、Softmax函数及其推导;我们要感谢在这里编写链接内容的博主。他用精炼的数学语言向我们展示了softmax求导算法。
软最大函数
在,
表示第L层(通常是最后一层)第j个神经元的输入,
表示L层第j个神经元的输出,e表示自然常数。注意,
表示 L 层所有神经元的输入总和。
softmax函数最明显的特点是,它将每个神经元的输入与当前层所有神经元的输入之和的比值作为该神经元的输出。这使得输出更容易解释:神经元的输出值越大,该神经元对应的类别是真实类别的概率就越高。
另外,softmax不仅将神经元输出构造成概率分布,还起到归一化的作用,适用于很多需要归一化的分类问题。
由于ANN算法中softmax的推导结果比较特殊,所以分为两种情况。希望对正在学习此类算法的朋友有所帮助。推导过程如下:
训练ANN时,二次成本函数可能会导致训练速度变慢。也就是说,初始输出值距离真实值越远,训练速度就越慢。这个问题可以通过使用交叉熵成本函数来解决。其实这个问题还可以用另一种方法来解决,就是用softmax激活函数和对数似然成本函数来解决。
对数似然成本函数的公式为: 在之前的博文《交叉熵成本函数》中提到,二次成本函数在训练ANN时可能会导致训练速度变慢。也就是说,初始输出值距离真实值越远,训练速度就越慢。这个问题可以通过使用交叉熵成本函数来解决。其实这个问题还可以用另一种方法来解决,就是用softmax激活函数和对数似然成本函数来解决。
对数似然成本函数的公式为:
以下是罗干注释的代码
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 12 14:57:00 2017
@author: Denny Britz
@Translater:luogan 罗干
@luogan has make some coment on the code
@ 我爱婷婷和臭臭
"""
import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt
class Config:
nn_input_dim = 2 #数组输入的维度是2(x,y两个坐标当然是二维啊)
nn_output_dim = 2#数组输出的维度是2(分为两类当然是二维啊)
epsilon = 0.01 # 梯度下降学习步长
reg_lambda = 0.01 # 修正的指数?
def generate_data():
np.random.seed(0)#伪随机数的种子0,当然也可以是1,2啊
X, y = datasets.make_moons(200, noise=0.20)#产生200个数据,噪声误差为0.2
return X, y
def visualize(X, y, model):
plot_decision_boundary(lambda x:predict(model,x), X, y)#好好看这个代码,函数名字做参数哦
plt.title("Logistic Regression")
def plot_decision_boundary(pred_func, X, y):
#把X的第一列的最小值减掉0.5赋值给x_min,把X的第一列的最大值加0.5赋值给x_max
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
h = 0.01
# 根据最小最大值和一个网格距离生成整个网格,就是在图上细分好多个点,画分类边界的时候要用这些点
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.show()
def predict(model, x):
#这是字典啊
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
z1 = x.dot(W1) + b1# 输入层向隐藏层正向传播
a1 = np.tanh(z1) # 隐藏层激活函数使用tanh = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
z2 = a1.dot(W2) + b2# 隐藏层向输出层正向传播
exp_scores = np.exp(z2)#这两步表示输出层的激活函数为softmax函数哦
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
return np.argmax(probs, axis=1)
def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
num_examples = len(X)
np.random.seed(0)#初始化权值和偏置
W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
b1 = np.zeros((1, nn_hdim))
W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
b2 = np.zeros((1, Config.nn_output_dim))
model = {}
for i in range(0, num_passes):
z1 = X.dot(W1) + b1# 输入层向隐藏层正向传播
a1 = np.tanh(z1)# 隐藏层激活函数使用tanh = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
z2 = a1.dot(W2) + b2# 隐藏层向输出层正向传播
exp_scores = np.exp(z2)#这两步表示输出层的激活函数为softmax函数哦
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
delta3 = probs
#下面这才是delta3,为损失函数对z2求偏导数,y-y^
delta3[range(num_examples), y] -= 1
dW2 = (a1.T).dot(delta3)#损失函数对w2的偏导数
db2 = np.sum(delta3, axis=0, keepdims=True)#损失函数对b2的偏导数
delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))#损失函数对z1的偏导数
dW1 = np.dot(X.T, delta2)#损失函数对w1的偏导数
db1 = np.sum(delta2, axis=0)#损失函数对b1的偏导数
#个人认为下面两行代码完全没有必要存在
dW2 += Config.reg_lambda * W2#w2梯度增量的修正 屁话
dW1 += Config.reg_lambda * W1#w1梯度增量的修正 屁话
#更新权值和偏置
W1 += -Config.epsilon * dW1
b1 += -Config.epsilon * db1
W2 += -Config.epsilon * dW2
b2 += -Config.epsilon * db2
model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
return model
def main():
X, y = generate_data()
model = build_model(X, y, 8)
visualize(X, y, model)
if __name__ == "__main__":
main()
#2017.3.20 罗干注释于同济大学图书馆
[外部链接图像传输失败。源站可能有防盗链机制。建议保存图片直接上传(img-yTVs91UX-1596200021425)()]
首先,我要感谢丹尼布里茨。这是 dennybritz 的 github。
(高中辍学。谷歌大脑,斯坦福大学,伯克利分校。进入初创公司,深度学习。在 wildml.com 和 dennybritz.com 上写作。住在日本和한국)
该死的,他从高中退学,去了斯坦福大学和伯克利大学。
dennybritz 在 GitHub 上分享他的代码和文章。我的这篇博客是dennybritz原文章的翻译,但他的文章不包括BP网络和softmax函数的推导。
我在这篇文章中插入了数学推导,主要参考了这里天才博客上这位天才的文章。为了让读者更好的理解BP,我直接引用了。
特殊代码,研究神经网络,毕业推迟了,但还是值得的
未完待续,我会修改一下