神经网络编程实战:Java语言实现(原书第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-16 23:07:32

点击下载

作者:(巴西)法比奥·M.索尔斯(Fabio M.Soares),(巴西)艾伦·M.F.索萨(Alan M. F. Souza),王彩霞,夏妍

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

神经网络编程实战:Java语言实现(原书第2版)

神经网络编程实战:Java语言实现(原书第2版)试读:

前言

程序员需要持续不断地学习,而且经常会面对新技术和新方法的挑战。生活中人们虽然习惯了重复的事情,但也会经历新的事情。学习过程是科学界最有趣的话题之一,很多尝试都试图描述或者再现人类的学习过程。

本书的主要挑战是学习并掌握业界最新的内容。虽然神经网络这个名字可能看起来很奇怪,甚至可能误认为它是关于神经学的,但是我们通过把重点放在你决定购买这本书的原因上来简化这些细微差别。我们打算建立一个框架,告诉你神经网络其实很简单,很容易理解,你不需要有足够的先验知识,就完全可以理解本书提到的概念。

因此,我们希望你充分掌握本书的内容,在面对棘手问题时,能始终以初学者的态度运用神经网络的功能来解决。本书对提到的每个概念都用简单的语言进行解释,但理解它也需要一定的技术背景。本书的目的是让你了解智能应用可以通过简单语言编写。各章概览

第1章主要介绍神经网络的概念,解释基本神经元结构(单层感知机、学习机),以及激活函数、权重和学习算法。此外,该章还演示了用Java创建基本神经网络的整个过程。

第2章主要介绍神经网络学习过程的细节,解释几个有用的概念,如训练、测试和验证,演示如何实现训练和验证算法、如何进行误差评估。

第3章主要讨论感知机和监督学习的特性,展示这类神经网络的训练算法,以及如何用Java实现这些特性。

第4章主要介绍无监督学习和自组织映射,即Kohonen神经网络在分类和聚类问题中的应用。

第5章主要阐述如何用神经网络解决天气预报的问题,你会看到来自不同地区、不同时间的历史天气数据记录,并学习如何在神经网络训练之前对数据进行预处理。

第6章主要介绍分类问题,这属于监督学习的范畴。运用患者的数据构建基于神经网络的专家系统,专家系统能够根据患者的症状给出诊断结果。

第7章讨论如何应用无监督学习算法和神经网络实现聚类,进而实现客户画像聚类。

第8章主要介绍另一种涉及神经网络的常见任务:光学字符识别(OCR)。OCR非常有用,它显示了神经网络强大的学习能力。

第9章主要介绍神经网络优化的相关技术,如输入选择,切分训练数据集、验证数据集和测试数据集的较优方法,以及数据过滤和隐含神经元个数的选择。

第10章主要介绍神经网络领域的新技术动态,启发你理解并设计出适用于更复杂问题的新策略。

附录内容为在线内容,可以通过以下链接下载:https://www.packtpub.com/sites/default/files/downloads/Neural_Network_Programming_with_Java_SecondEdition_Appendices.pdf。附录内容主要涉及搭建Netbeans开发环境的详细步骤,搭建Eclipse开发环境的详细步骤。阅读准备

需要Netbeans(www.netbeans.org)或者Eclipse(www.eclipse.org)软件,两者都是免费的,可以从官方网站下载。本书读者对象

本书适合以下Java开发者:想知道如何运用神经网络的功能开发更智能的应用。同时本书适用于那些处理大量复杂数据并希望在日常应用中有更高效率的人士。本书的读者最好具有一些统计计算的基础知识。下载示例代码

可以从网站http://www.packtpub.com或华章网站www.hzbook.com下载本书中的示例代码。  第1章 神经网络入门

本章将介绍神经网络及其设计目的。作为后续章节的基础,本章主要介绍神经网络的基本概念。本章将讨论以下主题:

·人工神经元

·权重和偏置

·激活函数

·神经元层

·使用Java实现神经网络1.1 探索神经网络

听到神经网络这个词,从直觉上我们会想到大脑,的确,我们可以将大脑看成一个大型的天然神经网络。然而,人工神经网络又是什么呢?人工是一个与天然相对的词,我们首先想到的就是人工大脑或者机器人,这就是所谓的人工。在这种情况下,受人脑的启发,我们创建出一个和人脑相似的结构,称之为人工智能。

ANN初学者可能认为本书是讲如何构建智能系统的,例如人工大脑,智能系统能用Java代码模拟人类思维,是这样吗?答案是肯定的,但是,我们不会像电影《黑客帝国》中那样讨论如何创造人工思考机器;而会介绍人工神经网络解决方案的设计过程,它能够利用整个Java框架,从原始数据抽象知识。1.2 人工神经网络

在不了解神经网络的起源和相关术语的情况下,无法讨论神经网络。本书中神经网络(NN)和人工神经网络(ANN)是同义词,尽管NN因涵盖自然神经网络而更加通用。那么,什么是ANN呢?下面探究这个词的历史。

20世纪40年代,神经生理学家Warren McCulloch和数学家Walter Pitts将神经学基础和数学运算结合起来,设计了人工神经元的第一个数学实现。当时,人脑被大量研究以弄懂那些潜在及神秘的行为,不过主要在神经学领域。众所周知,生物神经元结构有一个细胞核和多个树突(接收来自其他神经元传入的信号),以及一个轴突(将信号传递给其他神经元),如图1-1所示。图1-1 生物神经元结构

McCulloch和Pitts的创新是在神经元模型中加入了数学成分,他们假设神经元是一个简单的处理器,用于合并所有输入信号并产生新的信号以激活其他神经元,如图1-2所示。

此外,考虑到大脑由数十亿个神经元组成,每个都与成千上万个其他神经元相联系,产生了数万亿的连接,因此我们讨论的是一个巨大的网络结构。基于这个事实,McCulloch和Pitts为单个神经元设计一个简单的模型,最初用来模拟人类视觉。当时可用的计算器或计算机资源虽然有限,但能很好地处理数学运算。即使今天,像视觉和声音识别这类任务,没有特殊的框架仍然很难编程实现。然而,相比于复杂的数学运算,人脑可以更高效地识别声音和图像,这激起了科学家和研究人员的兴趣。图1-2 神经元模型

然而,众所周知,人脑执行的所有复杂活动都基于所学知识,为了克服传统算法在人类易于解决的问题上所面临的困难,我们设计了ANN解决方案,期望它具备基于外部刺激(数据),通过自学习来解决问题的能力(见表1-1)。表1-1 人类和计算机可解决的任务1.2.1 神经网络是如何组织的

结合人脑的特点和结构,可以说ANN是一种自然启发的方法。每个神经元与许多其他神经元相接,这些神经元又会和其他大量神经元相连,形成一个高度互连的结构。本书后续章节将会介绍,神经元之间的连通性解释了学习能力,因为每个连接都可以根据刺激和期望目标进行配置。1.2.2 基本元素——人工神经元

我们来看看最基本的人工神经元素——人工神经元。已证明生物神经元是信号处理器,神经元中的树突会根据接收到信号的强度和振幅,发送信号到轴突。可以这样认为,神经元在输入上有一个信号收集器,在输出上有一个激活单元,它可以触发一个新的信号,然后传递给其他神经元,如图1-3所示。图1-3 人工神经元提示 生物神经元中,存在一个潜在的阈值,一旦达到该值,会触发轴突并传递信号到其他神经元。这一行为可以通过激活函数来模拟,已经证明,激活函数在表示神经元的非线性行为方面非常有用。1.2.3 赋予神经元生命——激活函数

激活函数是指一个神经元根据输入信号,执行计算并产生输出。从数学方面讲,激活函数用于为神经网络模型的处理加入非线性因素,从而提供人工神经网络的非线性行为,这对模拟生物神经元的非线性特征非常有用。激活函数通常是一个非线性函数,输出限制在某个区间范围内,但某些特定情况下,也可以是线性函数。

虽然任何函数都可以用作激活函数,但是本章主要介绍常用的几种,见表1-2。表1-2 常见激活函数

在这些函数和图形中,系数a可以通过激活函数进行设置。1.2.4 可变参数——权重

尽管神经网络的结构能固定,但通过神经元之间的连接权重能够增强或减弱接收到的神经信号,所以可以通过修改权重影响神经元的输出。因此,神经元的激活不仅依赖输入信号,还依赖权重。如果输入来自其他神经元或者外部世界(刺激),权重可以看成神经网络在神经元之间建立的连接。由于权重是神经网络的内部组件且影响输出,因此也可将权重视为神经网络的认知,即改变权重将会改变神经网络对外界刺激的应答。1.2.5 额外参数——偏置

作为一个独立组件,偏置主要为激活函数增加一个额外信号,这对人工神经元非常有用。它的作用类似一个输入,只是这个输入等于一个固定值(通常是1)乘以相关性权重。这一特性有助于神经网络认知表现为一个更纯粹的非线性系统,假设当所有输入都为零时,神经元的输出不仅可以不为零,反而可以由偏置和相关权重触发一个不同的值。1.2.6 由部分到整体——层

为抽象化处理层次,如我们大脑处理问题的方式,神经元按层组织。输入层接收外部世界的直接刺激,输出层触发一些行为,对外部世界产生直接影响。输入层和输出层之间,有许多隐含层,某种意义上,这些隐含层对外部世界不可见。在人工神经网络中,同一层的所有神经元具有相同的输入和激活函数,如图1-4所示。图1-4 神经网络层

神经网络由几个互相连接的层组成,形成所谓的多层网络。神经层因此可以分为输入层、隐含层和输出层。

事实上,追加一个神经层可以增强神经网络表达复杂知识的能力。提示 无论层数多少,每个神经网络都至少有一个输入/输出层。一个多层网络中,输入和输出层之间的层叫作隐含层。1.2.7 神经网络体系结构

神经网络可以有不同的布局,主要取决于神经元或层之间是如何连接的。每一个神经网络体系结构都是为特定目标而设计。神经网络可以应用于许多问题,根据问题的性质,神经网络旨在更高效地解决问题。

神经网络体系结构分类如下:

·神经元连接

·单层网络

·多层网络

·信号流

·前馈网络

·反馈网络1.2.8 单层网络

单层网络体系结构中,所有神经元都处于同一层,形成单个层,如图1-5所示。图1-5 单层网络

神经网络接收输入信号并送入神经元,进而产生输出。神经元可以彼此高度连接,无须递归。如单层感知机、学习机(Adaline)、自组织映射、Elman、Hopfield神经网络等都属于单层网络体系结构。1.2.9 多层网络

多层网络中,神经元分成多个层,每层对应神经元的一个平行布局,每层神经元都共享相同的输入数据,如图1-6所示。图1-6 多层网络

径向基函数(radial basis function)和多层感知机都是这种体系结构的典型例子。这种网络对于设计能近似表达真实数据的函数非常有用。此外,因为经过多层处理,所以这些网络适用于非线性数据的学习,能够从原始的非线性数据提取或识别知识,以便于更好地分类或决策。1.2.10 前馈网络

神经网络中的信号流动可以是单向的,也可以是递归的。对于第一种结构,称之为前馈网络,如图1-6所示,输入信号被送入输入层,经过处理后向前传递到下一层。多层感知机和径向基函数也是前馈网络的好例子。1.2.11 反馈网络

当神经网络中有某种内部递归时,这意味着信号会反向传递到已经接收或已经处理过信号的神经元或层,这种网络类型称为反馈网络,如图1-7所示。

在网络中加入递归,主要为了产生动态行为,特别是当网络处理涉及时间序列或者模式识别的问题时,就需要内部记忆来加强学习过程。然而,这种网络训练起来特别困难,因为除了准备训练数据之外,训练过程终归是一个递归行为(例如,一个神经元的输出反过来可能又会成为它的输入)。大多数反馈网络都是单层的,比如Elman网络和Hopfield网络。也有少数是递归的多层网络,比如echo和递归多层感知机网络。图1-7 反馈网络1.3 从无知到认知——学习过程

神经网络通过调整神经元之间的连接进行学习,也就是权重。如1.2.4节所述,权重代表神经网络的认知。同样的输入、不同的权重会导致网络产生不同的结果。因此,根据某种学习规则调整权重,可以改善神经网络的输出结果。学习过程的通用模式如图1-8所示。

因为神经网络有期望输出,所以图1-8描述的过程叫作监督学习,但是神经网络也可以没有任何期望输出(无监督),而是通过输入数据学习而得。第2章将深入探究神经网络的学习过程。图1-8 通用学习模式1.4 开始编程——神经网络实践

本书将介绍用Java编程语言实现神经网络的整个过程。Java是一种面向对象的编程语言,由Sun公司的一小部分工程师在20世纪90年代创建,后Sun公司于2010年被Oracle收购。现在,Java应用在许多设备中,已经成为我们日常生活的一部分。

面向对象语言(比如Java),主要处理的是类和对象。类是现实世界中事物的抽象,对象是这个抽象事物的一个实例,有点类似汽车(类指所有及任何汽车)和我的汽车(对象指定的汽车——我的)的关系。Java类通常由属性和方法(或函数)组成,包含面向对象编程(OOP)思想。这里简要回顾一些概念,而不会深入探究,因为本书的目标仅仅是从实用角度来设计和创建神经网络。与这个过程相关的主要有4个概念。

·抽象:将现实世界的问题或规则映射到计算机领域,只考虑其相关特性,不考虑阻碍开发的细节。

·封装:类似于产品封装,其中,有些相关特性是公开的(公有方法),有些特性隐藏在作用域内(私有的或者受保护的),因此避免了信息误用或者信息过多。

·继承:在现实世界中,对象的多个分类以分层的方式共享属性和方法,例如,车辆可以是轿车和卡车的超类。因此,在OOP中,一个类可以继承另一个类的所有特性,从而避免了代码重写。

·多态:与继承几乎相同,不同之处在于,相同函数名的方法在不同的类上表现出不同的行为。

利用本章介绍的OOP思想和神经网络思想,我们将设计实现神经网络的第一个类集。如前所述,神经网络由层、神经元、权重、激活函数和偏置组成。层主要包括三类:输入层、隐含层和输出层。每层都有一个或者多个神经元,每一个神经元都和神经输入/输出连接,或者和另一个神经元连接,这些连接就是权重。

需要重点强调一下,一个神经网络可能有许多隐含层,也可能一个都没有,因为每层的神经元数目可能不同。然而,输入层和输出层的神经元个数分别等于神经输入/输出的个数。

所以,让我们开始实现吧!首先,定义以下类。

·Neuron:定义人工神经元。

·NeuralLayer:抽象类,定义一个神经元层。

·InputLayer:定义神经输入层。

·HiddenLayer:定义输入层和输出层之间的层。

·OutputLayer:定义神经输出层。

·InputNeuron:定义神经网络输入中出现的神经元。

·NeuralNet:将前面定义的所有类组合成一个ANN结构。

除了这些类之外,也要为激活函数定义一个IActivationFunction接口。这是必要的,因为激活函数与方法类似,需要作为神经元的一个属性进行分配。所以要为激活函数定义类,这些类需实现IActivationFunction接口:

·Linear

·Sigmoid

·Step

·HyperTan

第1章的编码基本完成。除此之外,还需要定义两个类。一个用于异常处理(NeuralException),另一个用于产生随机数(RandomNumberGenerator)。最后,将这些类分别放到两个包。

·edu.packt.neuralnet:与神经网络相关的类(NeuralNet、Neuron、NeuralLayer等)

·edu.packt.neuralnet.math:与数学相关的类(IActivationFunction、Linear等)

为了节约篇幅,本书不会对每个类进行完整描述,只重点讨论最重要类的关键特性。欢迎读者浏览代码的Javadoc文档以便获得实现的更多细节。1.5 神经元类

神经元类是本章代码的基础类。根据理论,人工神经元有如下属性:

·输入

·权重

·偏置

·激活函数

·输出

定义一个在后续例子中很有用的属性也很重要,比如激活函数之前的输出。接下来,实现以下属性:public class Neuron { protected ArrayList weight; private ArrayList input; private Double output; private Double outputBeforeActivation; private int numberOfInputs = 0; protected Double bias = 1.0; private IActivationFunction activationFunction; ...}

当实例化神经元时,需要指定输入数据的个数以及激活函数。构造函数如下:public Neuron(int numberofinputs,IActivationFunction iaf){ numberOfInputs=numberofinputs; weight=new ArrayList<>(numberofinputs+1); input=new ArrayList<>(numberofinputs); activationFunction=iaf;}

注意,为偏置定义另一个权重。一个重要的步骤是初始化神经元,也就是为权重赋初始值。这主要在init()方法中完成,通过随机数生成器静态类RandomNumberGenerator生成随机数,赋值权重。注意,设置权重值时需要防止权重数组越界:public void init(){ for(int i=0;i<=numberOfInputs;i++){ double newWeight = RandomNumberGenerator.GenerateNext(); try{ this.weight.set(i, newWeight); } catch(IndexOutOfBoundsException iobe){ this.weight.add(newWeight); } }}

最后,看看如何在calc()方法中计算输出值:public void calc(){ outputBeforeActivation=0.0; if(numberOfInputs>0){ if(input!=null && weight!=null){ for(int i=0;i<=numberOfInputs;i++){ outputBeforeActivation+=(i==numberOfInputs?bias:input.get(i))*weight.get(i); } } } output=activationFunction.calc(outputBeforeActivation);}

首先需要对所有输入和权重的乘积进行求和(偏置乘最后一个权重,i==number-OfInputs),然后将得出的结果保存在属性outputBeforeActivation中。激活函数用这个值计算神经元的输出。1.6 NeuralLayer类

在这个类中,将把在同一层中对齐的神经元分成一组。因为一层需将值传递给另一层,也需要定义层与层之间的连接。类的属性定义如下:public abstract class NeuralLayer { protected int numberOfNeuronsInLayer; private ArrayList neuron; protected IActivationFunction activationFnc; protected NeuralLayer previousLayer; protected NeuralLayer nextLayer; protected ArrayList input; protected ArrayList output; protected int numberOfInputs; ...}

这个类是抽象的,真正可实例化的层类是InputLayer、HiddenLayer和Outp-utLayer。创建一个层时,必须使用其中一个类的构造函数,这几个类具有相似的构造函数:public InputLayer(int numberofinputs);public HiddenLayer(int numberofneurons,IActivationFunction iaf,int numberofinputs);public OutputLayer(int numberofneurons,IActivationFunction iaf, int numberofinputs);

层的初始化和计算都和神经元一样,它们也实现了init()方法和calc()方法。声明为protected类型,确保了只有子类可以调用或覆盖这些方法。protected void init(){ for(int i=0;i

在定义NeuralNetwork类之前,先看接口的Java代码示例:public interface IActivationFunction { double calc(double x); public enum ActivationFunctionENUM { STEP, LINEAR, SIGMOID, HYPERTAN }}

calc()方法属于实现IActivationFunction接口的特定的激活函数类,例如Sigmoid函数:public class Sigmoid implements IActivationFunction { private double a=1.0; public Sigmoid(double _a){ this.a=_a; } @Override public double calc(double x){ return 1.0/(1.0+Math.exp(-a*x)); }}

这是多态性的一个示例,即在相同的函数名下,类和方法呈现不同的行为,产生灵活的应用。1.8 神经网络类

最后,定义神经网络类。到目前为止,我们已经知道,神经网络在神经层中组织神经元,且每个神经网络至少有两层,一个用来接收输入,一个用来处理输出,还有一个数量可变的隐含层。因此,除了具有和神经元以及NeuralLayer类相似的属性之外,NeuralNet还将拥有这几个属性,如numberOfInputs、numberOfOutputs等。public class NeuralNet { private InputLayer inputLayer; private ArrayList hiddenLayer; private OutputLayer outputLayer; private int numberOfHiddenLayers; private int numberOfInputs; private int numberOfOutputs; private ArrayList input; private ArrayList output; ...}

这个类的构造函数比前面类的参数更多:public NeuralNet(int numberofinputs,int numberofoutputs, int [] numberofhiddenneurons,IActivationFunction[]hiddenAcFnc, IActivationFunction outputAcFnc)

如果隐含层的数量是可变的,我们还应该考虑到可能有多个隐含层或0个隐含层,且对每个隐含层来说,隐藏神经元的数量也是可变的。处理这种可变性的最好方法就是把每个隐含层中的神经元数量表示为一个整数向量(参数numberofhiddenlayers)。此外,需要为每个隐含层定义激活函数,包括输出层,完成这个目标所需的参数分别为hiddenActivationFnc和outputAcFnc。

为了节约篇幅,本章不会展示这个构造函数的完整实现,只展示层的定义和层与层之间的连接。首先,输入层的定义如下:input=new ArrayList<>(numberofinputs);inputLayer=new InputLayer(numberofinputs);

隐含层的定义取决于隐含层的位置,如果恰好在输入层后,定义如下:hiddenLayer.set(i,new HiddenLayer(numberofhiddenneurons[i],hiddenAcFnc[i],inputLayer.getNumberOfNeuronsInLayer()));inputLayer.setNextLayer(hiddenLayer.get(i));

如果不在输入层之后,需要获得前一个隐含层的引用:hiddenLayer.set(i, new HiddenLayer(numberofhiddenneurons[i],hiddenAcFnc[i],hiddenLayer.get(i-1).getNumberOfNeuronsInLayer()));hiddenLayer.get(i-1).setNextLayer(hiddenLayer.get(i));

至于输出层,它的定义与隐含层的后一种情况比较相似,除了OutputLayer类和可能没有隐含层的事实。if(numberOfHiddenLayers>0){ outputLayer=new OutputLayer(numberofoutputs,outputAcFnc, hiddenLayer.get(numberOfHiddenLayers-1). getNumberOfNeuronsInLayer() ); hiddenLayer.get(numberOfHiddenLayers-1).setNextLayer(outputLayer);}else{ outputLayer=new OutputLayer(numberofinputs, outputAcFnc,numberofoutputs); inputLayer.setNextLayer(outputLayer);}

calc()方法执行从输入到输出的信号传递:public void calc(){ inputLayer.setInputs(input); inputLayer.calc(); for(int i=0;i

现在应用上述类。如下是一段测试类的代码,在main方法中创建NeuralNet类的对象nn。这是一个简单的神经网络,有两个输入,一个输出,以及一个包含3个神经元的隐含层:public class NeuralNetConsoleTest { public static void main(String[] args) { RandomNumberGenerator.seed=0; int numberOfInputs=2; int numberOfOutputs=1; int[] numberOfHiddenNeurons= { 3 }; IActivationFunction[] hiddenAcFnc = { new Sigmoid(1.0) } ; Linear outputAcFnc = new Linear(1.0); System.out.println("Creating Neural Network..."); NeuralNet nn = new NeuralNet(numberOfInputs,numberOfOutputs, numberOfHiddenNeurons,hiddenAcFnc,outputAcFnc); System.out.println("Neural Network created!"); nn.print(); ...}

对于上述代码,为神经网络提供两组数据,看会产生什么输出:double [] neuralInput = { 1.5 , 0.5 };double [] neuralOutput;System.out.println("Feeding the values ["+String.valueOf(neuralInput[0])+" ; "+ String.valueOf(neuralInput[1])+"] to the neuralnetwork");nn.setInputs(neuralInput);nn.calc();neuralOutput=nn.getOutputs();neuralInput[0] = 1.0;neuralInput[1] = 2.1;...nn.setInputs(neuralInput);nn.calc();neuralOutput=nn.getOutputs();

输出如图1-9所示。图1-9 控制台输出

需要记住,除非你使用相同的seed值,否则每次运行代码都会生成新的伪随机权重值。如果运行上述代码,图1-9中的输出也会出现在你的控制台中。1.10 本章小结

本章主要介绍了神经网络是什么、它的用途和相关的基本概念。另外,我们练习了如何应用神经网络基础理论,通过Java对每个神经网络元素进行编程,实现了一个很基础的神经网络。在继续学习高级概念之前,理解基本概念很重要。

第2章将深入研究神经网络的学习过程,并通过简单的例子来探讨不同类型的学习。  第2章 神经网络学习

第1章介绍了神经网络,现在来了解神经网络的学习过程。本章将探讨神经网络学习的相关概念及其对应的Java实现。我们将对神经网络学习过程的基础和背后的思想进行回顾,以指导我们用Java实现学习算法,从而应用到神经网络实现代码中。本章将讨论以下主题:

·学习能力

·学习模式

·监督学习

·无监督学习

·学习过程

·优化基础

·损失函数

·误差度量

·学习算法

·δ规则

·Hebbian规则

·学习机/感知机

·训练、测试和验证

·数据集切分

·过度拟合和过度训练

·泛化2.1 神经网络的学习能力

神经网络真正令人惊奇的是从环境中学习的能力,就像有智力的人类一样。我们人类通过观察和重复进行学习,直到完全掌握某些任务或者概念。从生理学角度来看,人脑的学习过程是对节点(神经元)之间神经连接的重新配置,从而形成一个新的思维结构。

神经网络链接本质上是在整个结构中执行学习过程,这一特性使神经网络能足够灵活地学习到各种各样的知识。与普通的数字计算机只能执行编码赋予的任务不同,神经系统能够根据设定的满意度标准自动改进和完成新的任务。换句话说,神经网络不需要编写程序指令,它们可以自己学习程序。如何通过学习解决问题

考虑到每个待解决的任务都可能有大量理论上可行的解决方案,学习过程试图从中寻找一种能产生满意结果的最优方法。鼓励使用诸如人工神经网络(ANN)这类结构,因为它通过输入刺激(问题/任务相关的数据),就完全有能力学习到任意类型的知识。首先,ANN会产出一个随机结果和相应的误差,基于这个误差,ANN再进行参数调整。提示 可以将ANN参数(权重)看作解决方案的组成部分。假设每个权重对应一个维度,单个解决方案表示解决方案超空间中的一个点。每个解决方案都会进行误差度量,用于评估这个解决方案离满意标准还有多远。然后,学习算法通过不断迭代寻找最接近满意度标准的解决方案。2.2 学习模式

神经网络学习分为两类:监督学习和无监督学习。实际上,人脑的学习也以这两种方式运作。可以无目的地从观察中建立认知(无监督),也可以从一个老师讲授的正确模式中学习认知(监督)。两种模式的不同点主要在于是否依赖特定的模式,而且随着问题不同,目标模式也不同。2.2.1 监督学习

监督学习类型处理成对的xs(自变量)和ys(因变量),目标是将它们映射成一个函数。其中数据Y是监督者,是目标期望的输出;X是原始独立数据,联合起来共同生成数据Y。这类似一个老师教学生执行某项特定的任务,如图2-1所示。图2-1 监督学习示例

监督学习模式的特点是有一个直观的误差参照,即当前实际输出和目标之间的比较。用损失函数量化期望输出和实际输出之间的这种失配程度,进而指导网络参数的学习。提示 在优化问题中,损失函数只是要最小化的度量函数。这意味着我们只需要尽可能找到将损失函数降到最低值时的参数。

对于损失函数,本章后面将有更详细的讨论。

监督学习适用于模式已知的任务,比如图像分类、语音识别、函数近似或者预测。注意,对于监督学习,应该为神经网络提供先验知识,同时包含输入自变量(X)和输出因变量(Y)。输出因变量Y的存在是监督学习的必要条件。2.2.2 无监督学习

在无监督学习中,只处理不带任何标签或类别的数据(见图2-2)。因此,我们试图通过自变量数据(X)来进行推断或者提取知识。图2-2 无监督学习示例

这个过程类似于人类根据个人经验和配套准则进行自我学习。无监督学习,没有明确的期望模式,而是运用给定的数据无监督地推断出因变量输出Y。提示 在无监督学习中,自变量数据之间越接近,它们的输出就应该越相似。损失函数应该考虑这个规律,而监督模式则无须考虑。

无监督学习可以应用于聚类、数据压缩、统计建模和语言建模中。第4章将会对该学习模式进行更详细的介绍。2.3 学习过程

到目前为止,我们已经从理论上定义了学习过程,以及如何执行这一过程。但为了在实践中实现该学习算法,我们必须深入研究它背后的数学逻辑。为简单起见,本章主要讨论监督学习案例。然而,这里仍然会介绍一种运用无监督学习更新权重的规则。学习算法是一种驱动神经网络学习过程执行的程序,主要由神经网络结构决定。从数学角度看,我们希望能找到驱动损失函数C(X,Y)达到最小可能值的最优权值W。然而,有时学习过程无法找到一组满足验收标准的权重,此时必须设置停止条件,以防止神经网络一直学习,从而导致Java程序进入死循环。

一般来说,学习过程的执行方式如图2-3所示。图2-3 学习过程的流程图2.3.1 寻找损失函数最优下降方向

现在详细讨论损失函数的作用。为简单起见,这里将讨论二元函数的损失函数,其形状是一个超曲面。这里只考虑了两个权重(用二维空间和高度来表示损失函数)。假定损失函数的形状如图2-4所示。

从图2-4中可以看到有个最优点,在最优点处损失函数接近于零。但是如何通过编程实现呢?可以将其看成一个数学优化问题,此时损失函数是一个优化问题:

根据费马优化定理,我们知道最优解位于所有维度斜率都为零的位置,即偏导数应该为零,且应该是凸函数(对于极小值情况)。最优解的搜索从任意解W开始,沿着梯度下降的方向,这就是所谓的梯度法。图2-4 损失函数2.3.2 在学习过程中更新权重

根据所用的损失函数,更新规则将决定如何改变权重,从而使损失函数值随权重的更新越来越小。

W(k+1)=W(k)+ΔW

这里,k指第k次迭代,W(k)指第k次迭代的神经网络权重,k+1指下一次迭代。

权重更新可以通过在线或批处理方式执行。这里在线意味着权重在数据集中每个记录输入后更新。批量更新意味着,权重在数据集中的所有记录全部输入神经网络后更新。这将在本章末尾的代码中详细探讨。2.3.3 计算损失函数

神经网络学习时,会从一个环境中接收数据并根据目标调整神经网络的权重。该数据称为训练数据,其有多个样本。“训练”背后的思想在于调整神经网络权重,就像在神经网络中训练它们以得出期望响应一样。神经网络学习过程中,在监督学习情况下,目标输出(Y)和神经网络实际输出之间存在一个误差:

e=Y-Ŷ提示 一些关于神经网络的文献用字母T表示目标变量,Y表示神经网络的输出,而本书中用Y表示期望输出,为了不让读者迷惑,此处依然用Y表示目标输出。

考虑到训练集有很多条数据,且每条记录都会有N个误差值。因此,如何得到总体误差呢?一种直观的方法是取所有误差的平均值,但这是一种误导。误差向量既可以取正值,也可以取负值,因此无论误差测量值多大,所有误差的平均值很可能接近0。用绝对值生成平均误差似乎是一种更明智的方法,但这个函数在原点不连续,计算导数时不好处理(如图2-5所示)。图2-5 总体误差

所以,利用误差平方和的均值计算总体误差是一个合理的选择,即所谓的均方误差(MSE):2.3.4 一般误差和总体误差

在进一步探讨之前,需要弄清楚一件事。神经网络是一个多输出结构,我们必须处理多输出情况,这种情况将用误差矩阵代替误差向量:

此种情况下,无论对于一个特定输出、一个特定记录还是整个数据集,可能都需要处理大量误差。为了便于理解,将针对特定记录的误差称为一般误差(general error),在此基础上,为每个输出误差均设置一个标量值,进而得到一般输出误差。同时,将涉及所有数据的误差称为总体误差。

单输出网络的一般误差仅仅是期望目标和真实输出之间的差值,但在多输出情况下,一般误差需要由每个输出的误差组成。正如我们所见,平方误差是一种归纳误差度量的合适方法,因此,一般误差可以用每个输出误差的平方来计算:

至于总体误差,它实际上考虑的是数据集中所有记录的一般误差。由于数据集很大,因此计算总体误差时最好先计算一般误差的平方,再计算MSE。2.3.5 神经网络的迭代学习什么时候停止比较好

随着学习过程的执行,神经网络的结果必须越来越接近期望值,直到最终达到可接受的标准或者迭代次数的限制,我们称之为训练次数。只有至少满足其中一个条件,学习过程才算完成。

·满意度标准:根据学习模式不同,可以是最小总体误差或者最小权重距离。

·最大训练次数。2.4 学习算法示例

现在,用简单的例子来展示已探讨的学习算法。本章将主要考虑单层神经网络,多层神经网络将在下一章介绍。

在Java代码中,创建新的父类LearningAlgorithm,放在新包edu.packt.neural.learn中。同时创建新包edu.packt.neural.data,主要用于存放神经网络处理数据集的代码,即类NeuralInputData和NeuralOutputData,这两个类都被类Neural-DataSet引用。为了节约篇幅,建议读者阅读代码文档,以便了解这些类是如何组织的。

类LearningAlgorithm有以下属性和方法:public abstract class LearningAlgorithm { protected NeuralNet neuralNet; public enum LearningMode {ONLINE,BATCH}; protected enum LearningParadigm {SUPERVISED,UNSUPERVISED}; // ... protected int MaxEpochs=100; protected int epoch=0; protected double MinOverallError=0.001; protected double LearningRate=0.1; protected NeuralDataSet trainingDataSet; protected NeuralDataSet testingDataSet; protected NeuralDataSet validatingDataSet; public boolean printTraining=false; public abstract void train() throws NeuralException; public abstract void forward() throws NeuralException; public abstract void forward(int i) throws NeuralException; public abstract Double calcNewWeight(int layer,int input,int neuron) throws NeuralException; public abstract Double calcNewWeight(int layer,int input,intneuron,double error) throws NeuralException; // ...}

对象neuralNet是神经网络的一个引用,它将由这个学习算法训练。enum定义了学习模式和学习方式。定义训练参数(MaxEpochs、MinOverallError、LearningRate)并在学习过程中考虑数据集。

每个学习算法的实现类都应该覆盖train()方法,所有训练过程都由这个方法完成。forward()方法处理神经网络的所有输入数据,而forward(int k)方法处理输入数据的第k条记录。最后,calcNewWeight()方法负责更新输入和特定层的神经元之间的连接权重。calcNewWeight()方法允许为更新操作提供一个特定的参数变量error。2.4.1 δ规则

该算法主要根据损失函数对权重进行更新。在梯度法之后,人们想知道哪些权重能使损失函数值更低。注意,我们可以通过计算损失函数在每个权重上的偏导数来找到方向。为了便于理解,考虑简单的方法——只有一个神经元、一个权重以及一个偏置的简单情况,输出如下:

这里g是激活函数,X是包含x值的向量,Y是神经网络生成的输出向量。第k个样本的一般误差很简单:

然而,可以将此误差定义为平方误差、N-degree误差或MSE。但是,为了简单起见,将简单误差差值看成一般误差。现在,计算总体

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载