热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

神经网络——一个很好的解释以及简单实现ImplementingaNeuralNetworkfromScratchinPython–AnIntroduction

代码均在这里:Getthecode:Tofollowalong,allthecodeisalsoavailableasaniPythonnotebookonGithu


代码均在这里:
Get the code: To follow along, all the code is also available as an iPython notebook on Github.


在这篇文章中,我们将从头开始实现一个简单的3层神经网络。
我们不会推导出所有需要的数学内容,但是我会尝试直观地解释我们正在做什么,我也会给出资源给你详细阅读用。

在这里,我假设你熟悉基本的微积分和机器学习概念,例如,你知道什么是分类和正则化。理想情况下,你还知道一些关于如何使用梯度下降优化的技术。但是即使你不熟悉上述任何列出来的,仍然可以是很有趣的 : )

然而,为什么要从头开始实现一个神经网络呢?即使您计划在未来使用像PyBrain这样的神经网络库,至少一次从头开始实现一个网络是非常有价值的练习。它可以帮助您了解神经网络的工作原理,这对设计有效的模型至关重要。

有一点需要注意的是,这里的代码示例并不是非常有效。它们被设计成比较容易理解。在即将发布的帖子中,我将探讨如何使用Theano编写一个高效的神经网络实现。 (更新:now available)



Generating a dataset 生成数据集

让我们从生成一个数据集给我们使用开始。幸运的是,scikit-learn有一些有用的数据集生成器,所以我们不需要自己编写代码。 我们将使用make_moons函数。

import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt
%matplotlib inline# Generate a dataset and plot it
np.random.seed(0)
X, y = sklearn.datasets.make_moons(200, noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)

dataset1

我们生成的数据集有两个类,绘制为红色和蓝色点。你可以将蓝点作为男性患者,将红点视为女性患者,X轴和Y轴是医学测量。

我们的目标是训练一个机器学习分类器,根据给出的xy坐标来预测正确的分类(男性或女性)。请注意数据不是线性分离的,我们不能绘制分开两个类的直线。这意味着线性分类器像逻辑回归,将无法适应数据,除非你手工设计适用于给定数据集的非线性特征(如多项式)。

事实上,这是神经网络的主要优点之一。 您不需要担心特征工程。 神经网络的隐藏层将为你学习feature。



Logistic Regression 逻辑回归

为了说明我们训练一个逻辑回归分类器,它的输入是xy值,输出预测类(0或1)。为了方便简单,我们从scikit-learn中使用 Logistic Regression class.

!!! 代码是GitHub上的那个文件 simple_classification.py

# Train the logistic rgeression classifier
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X, y)# Plot the decision boundary
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")

这里写图片描述
该图显示了由Logistic回归分类器学到的判定界限。 它将数据分离成可以使用直线,但它无法捕获我们的数据的“月亮形状”。



Training a Neural Network

现在我们来构建一个三层神经网络,一个输入层,一个隐藏层和一个输出层。
输入层中的节点数由我们的数据的维度决定,就是2;
类似地,输出层中的节点数由我们所拥有的类的数量所决定,也是2。(因为我们只有2个类, 实际上可以只有一个输出节点预测0或1,但是有2个可以更容易地将网络扩展到更多的类)。
网络的输入将是x和y坐标,其输出将是两个概率,一个用于class 0(“female”),一个用于class 1(“male”)。
看起来像这样:

神经网络图示

我们可以选择隐藏层的维数(节点数)。我们放入隐藏层中的节点越多,我们将能够适应的更复杂的功能。但更高的维度会带来成本。
首先,需要更多的计算来进行预测并学习网络的参数。
更多的参数也意味着我们更容易过度拟合我们的数据。

那么如何选择隐藏层的大小呢?虽然有一些一般的指导方针和建议,但它总是取决于你的具体问题,更像是一门艺术而不是一门科学。稍后我们会着手于隐藏的节点数,看看它是如何影响我们的输出的。

我们还需要给我们的隐藏层选择激活函数。激活函数(The activation function)将层的输入转换为其输出。非线性激活函数是允许我们拟合非线性假设的。常见选择的激活函数是tanh, the sigmoid function, 或者ReLUs.
我们将使用tanh,在许多情况下表现相当好。这些函数的一个很好的属性是它们的倒数可以使用原始函数值来计算。
例如,tanhx的导数就是1tanh2x. 这是有用的,因为它允许我们计算tanhx一次,然后重新使用这个tanhx值来获得导数。

因为我们希望我们的network输出概率,输出层的激活函数将是softmax,一种方法简单地将原始score转换为概率。
如果你熟悉logistic function,you can think of softmax as its generalization to multiple classes.(这句还是放原文比较好 T_T).



How our network makes predictions

我们的网络使用正向传播去预测,正向传播只是一堆矩阵乘法和上面定义的激活函数的应用。
x是我们的网络的二维输入,那么我们如下计算我们的预测y (也是二维的):

z1=xW1+b1
a1=tanh(z1)
z2=a1W2+b2
a2=y=softmax(z2)

zi 是第i层的输入,ai 是 第i层 应用激活函数 之后的输出。
W1,b1,W2,b2是我们网络的参数,这些参数我们需要从training data中学习(learn from our training data). 你可以把它们当做是矩阵,在网络层之间转换数据的矩阵。
看着上面的矩阵乘法,我们可以计算出这些矩阵的维数:
如果我们使用500个节点在隐含层,那么 W1R(2500),b1R(500),W2R(5002),b2R(2)
现在你可以看到了为什么我们需要更多的参数,如果我们提高了隐藏层的size。

Learning the Parameters

学习我们网络参数意味着我们需要找到能最小化错误,在我们的训练数据上。(the error on our training data)。不过,我们该如何定义我们的error?
We call the function that measures our error the loss funtion. 我们叫它 loss function。
softmax输出的一个常见的选择是分类交叉熵损失(也称负对数似然值)。
the categorical cross-entropy loss

如果我们有N个训练样本 和 C个类,那么 预测值 y’ 相对于 真实的 labels y 的损失(loss):

![loss](https://img-blog.csdn.net/20171129153846589?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VvaGFvX3poYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 这个公式看起来很复杂,但是它真正的作用是总结我们的训练样例,并且如果我们预测了不正确的类,那就增加了损失。 两个概率分布越远 y(正确的标签)和 y’(我们的预测),我们的损失就越大。 通过寻找 能最小化损失的参数,we maximize the likelihood of our training data. 我们可以使用**梯度下降 (gradient descent)**来找到最小值 我将实现梯度下降的最普通的版本,也称为批量梯度下降,具有固定的学习率(batch gradient descent with a fixed learning rate) Variations such as **SGD (stochastic gradient descent) or minibatch gradient descent** typically perform better in practice. 【变体 例如 SGD(随机梯度下降)或者小批次梯度下降 等 通常在实践中表现更好】 作为输入,梯度下降需要相对于我们的参数的损失函数的梯度(导数的向量): As an input, gradient descent needs the gradients (vector of derivatives) of the loss function with respect to our parameters:LW1,Lb1,LW2,Lb2 为了计算这些梯度,我们使用著名的**反向传播算法**,这是一种有效地计算从输出开始的梯度的方法。 我不会详细讨论反向传播是如何工作的,但是在网络上有很多很好的解释。

Implementation:

We start by defining some useful variables and parameters for gradient descent:

num_examples = len(X) # training set size
nn_input_dim = 2 # input layer dimensionality
nn_output_dim = 2 # output layer dimensionality# Gradient descent parameters (I picked these by hand)
epsilon = 0.01 # learning rate for gradient descent
reg_lambda = 0.01 # regularization strength

首先让我们来实现我们上面定义的损失函数。 我们用这个来评估我们的模型在做什么:

# Helper function to evaluate the total loss on the dataset
def calculate_loss(model):W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']# Forward propagation to calculate our predictionsz1 = X.dot(W1) + b1a1 = np.tanh(z1)z2 = a1.dot(W2) + b2exp_scores = np.exp(z2)probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)# Calculating the losscorect_logprobs = -np.log(probs[range(num_examples), y])data_loss = np.sum(corect_logprobs)# Add regulatization term to loss (optional)data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))return 1./num_examples * data_loss

我们还实现了一个辅助函数来计算网络的输出。 它按前面定义的那样进行前向传播,并以最高概率返回类。

# Helper function to predict an output (0 or 1)
def predict(model, x):W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']# Forward propagationz1 = x.dot(W1) + b1a1 = np.tanh(z1)z2 = a1.dot(W2) + b2exp_scores = np.exp(z2)probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)return np.argmax(probs, axis=1)

最后,这里是训练我们的神经网络的功能。 它使用我们在上面提到的反向传播实现批量梯度下降。

# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(nn_hdim, num_passes=20000, print_loss=False):# Initialize the parameters to random values. We need to learn these.np.random.seed(0)W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)b1 = np.zeros((1, nn_hdim))W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)b2 = np.zeros((1, nn_output_dim))# This is what we return at the endmodel = {}# Gradient descent. For each batch...for i in xrange(0, num_passes):# Forward propagationz1 = X.dot(W1) + b1a1 = np.tanh(z1)z2 = a1.dot(W2) + b2exp_scores = np.exp(z2)probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)# Backpropagationdelta3 = probsdelta3[range(num_examples), y] -= 1dW2 = (a1.T).dot(delta3)db2 = np.sum(delta3, axis=0, keepdims=True)delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))dW1 = np.dot(X.T, delta2)db1 = np.sum(delta2, axis=0)# Add regularization terms (b1 and b2 don't have regularization terms)dW2 += reg_lambda * W2dW1 += reg_lambda * W1# Gradient descent parameter updateW1 += -epsilon * dW1b1 += -epsilon * db1W2 += -epsilon * dW2b2 += -epsilon * db2# Assign new parameters to the modelmodel = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}# Optionally print the loss.# This is expensive because it uses the whole dataset, so we don't want to do it too often.if print_loss and i % 1000 == 0:print "Loss after iteration %i: %f" %(i, calculate_loss(model))return model

A network with a hidden layer of size 3

让我们看看如果我们训练隐藏层大小为3的网络会发生什么:

# Build a model with a 3-dimensional hidden layer
model = build_model(3, print_loss=True)# Plot the decision boundary
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")

这里写图片描述

这看起来很不错。 我们的神经网络能够找到一个成功分离类的决策边界。



Varying the hidden layer size 改变隐藏层的大小

在上面的例子中,我们选择了隐藏层大小3.现在让我们了解隐藏层大小如何改变结果。

plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):plt.subplot(5, 2, i+1)plt.title('Hidden Layer size %d' % nn_hdim)model = build_model(nn_hdim)plot_decision_boundary(lambda x: predict(model, x))
plt.show()

这里写图片描述

我们可以看到隐藏的低维度层很好地捕捉了我们数据的总体趋势。 更高的维度容易过度拟合。 他们是“记忆”数据而不是拟合一般的形状。 如果我们要在一个单独的测试集上评估我们的模型(而且您应该!),那么具有较小隐藏层大小的模型由于更好的泛化可能会更好。 我们可以用更强的正则化来抵消过度拟合,但是为隐藏层选择正确的大小是一个更“economical”的解决方案。

英文原博网址在:
http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/


推荐阅读
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了Linux Shell中括号和整数扩展的使用方法,包括命令组、命令替换、初始化数组以及算术表达式和逻辑判断的相关内容。括号中的命令将会在新开的子shell中顺序执行,括号中的变量不能被脚本余下的部分使用。命令替换可以用于将命令的标准输出作为另一个命令的输入。括号中的运算符和表达式符合C语言运算规则,可以用在整数扩展中进行算术计算和逻辑判断。 ... [详细]
author-avatar
aspzc
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有