书名:神经网络算法与实现——基于Java语言
ISBN:978-7-115-46093-6
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [巴西]Fábio M.Soares Alan M.F. Souza
译 范东来 封 强
责任编辑 胡俊英
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright ©2016 Packt Publishing. First published in the English language under the title Neural Network Programming with Java.
All rights reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
人工神经网络是由众多连接权值可调的神经元连接而成,具有大规模并行处理、分布式信息存储、良好的自组织自学习能力等特点,能够完成模式识别、机器学习以及预测趋势等任务。
本书通过9章内容,并结合Java编程语言,由浅入深地介绍了神经网络算法的应用。书中涉及神经网络的构建、神经网络的结构、神经网络的学习、感知机、自组织映射等核心概念,并将天气预测、疾病诊断、客户特征聚类、模式识别、神经网络优化与自适应等经典案例囊括其中。本书在附录中详细地指导读者进行开发环境的配置,帮助读者更加顺利地进行程序开发。
本书非常适合对神经网络技术感兴趣的开发人员和业余读者阅读,读者无需具备Java编程知识,也无需提前了解神经网络的相关概念。本书将从零开始为读者进行由浅入深地讲解。
Fábio M. Soares拥有帕拉联邦大学(Universidade Federal do Pará,UFPA)的计算机应用专业硕士学位,目前是该所大学的在读博士生。他从2004年开始就一直在设计神经网络解决方案,在电信、化学过程建模等多个领域开发了神经网络技术的应用,他的研究主题涉及数据驱动建模的监督学习。
他也是一名个体经营者,为巴西北部的一些中小型公司提供IT基础设施管理和数据库管理等服务。在过去,他曾为大公司工作,如Albras(世界上最重要的铝冶炼厂之一)和Eletronorte(巴西的一个大型电源供应商)。他也有当讲师的经历,曾在亚马逊联邦农业大学(Federal Rural University)和卡斯塔尼亚尔的一个学院授课,两所学校都在帕拉州,所教的学科涉及编程和人工智能。
他出版了许多作品,其中许多都有英文版,所有作品都是关于针对某些问题的人工智能技术。他在众多权威会议上发表了一系列学术文章,如TMS(矿物金属和材料学会)、轻金属学会和智能数据工程、自动学习学会等学术会议。他还为Intech写过两章内容。
我特别庆幸自己能获得丰富的神经网络方面的知识,而我也只是单纯地喜欢做研究。特别感谢我的家人,我的父亲Josafá和母亲Maria Alice,希望他们能因为这本书而感到非常自豪,还有我的兄弟Flávio、我的姨妈Maria Irenice,以及在我学习期间总是以某种方式支持我的所有亲戚。我还要感谢Roberto Limão教授的支持,他邀请我参与了许多关于人工智能和神经网络的项目。此外,特别感谢我的合作伙伴和来自Exodus Sistemas的前合作伙伴,他们帮助我应对编程和IT基础设施方面的各种挑战。最后,我要感谢我的朋友Alan Souza,他邀请我成为本书的作者,并一起完成了本书。
Alan M.F. Souza是来自亚马逊高级研究所(Instituto de Estudos Superiores da Amazônia,IESAM)的计算机工程师。他拥有软件项目管理的研究生学位以及帕拉联邦大学(Universidade Federal do Pará,UFPA)的工业过程(计算机应用)硕士学位。自2009年以来,他一直从事神经网络方面的工作,并从2006年开始与巴西的IT公司合作进行Java、PHP、SQL和其他编程语言的开发。他热衷于编程和计算智能。目前,他是亚马逊大学(Universidade da Amazônia,UNAMA)的教授和帕拉联邦大学的在读博士生。
在我小的时候,就想写一本书。所以,本书是我一个梦想的实现,也是我努力工作的结果。我要感谢这个机会,也要感谢我的父亲Célio、我的母亲Socorro、我的妹妹Alyne和我了不起的妻子Tayná。他们能够理解我不能时常陪伴他们,还经常为我担心。我感谢所有家庭成员和朋友在困难时期一直支持我,并期待我的成功。我要感谢所有出现在我生命中的教授,特别是Roberto Limão教授,他第一个传授给我神经网络的概念。我还要向Fábio Soares表示感谢,感谢这次伟大的合作和这段伟大的友谊。最后,感谢Packt出版社这个不知疲倦的团队,感谢他们在整个出版过程中给予的帮助。
范东来
北京航空航天大学硕士,BBD(数联铭品)大数据技术部负责人,大数据平台架构师。技术图书作者和译者,著有《Hadoop海量数据处理》,译有《解读NoSQL》《NoSQL权威指南》。研究方向:大规模并行图挖掘、去中心化应用。
封强
毕业于北京化工大学计算机科学系、法国图尔工程师学院,获法国工程师学位,目前是BBD(数联铭品)大数据技术部工程师。研究方向:数据仓库、商业智能。
Saeed Afzal,也被称为Smac Afzal,是定居在巴基斯坦的专业软件工程师和技术爱好者。他专注于解决方案架构和可扩展的高性能应用的实现。
他热衷于为不同Web业务需求提供自动化解决方案。他目前的研究和工作主要是对下一代Web开发框架的实现,这会减少开发的时间和成本,并在默认情况下提供具有许多必要的强大功能的高效网站。他希望在2016年推出这个技术。
他还参与了Packt出版社出版的Cloud Bees Development一书的审校。
你可以在http://sirsmac.com
上找到更多关于他技能和经验的信息,也可以通过sirsmac@gmail.com
联系他。
我想感谢我的父母和妻子,感谢Zara Saeed博士,感谢他们给予的所有鼓励。
程序员的生活就是一条持续的、永无止境的学习之路。程序员总是面临新技术或新方法的挑战。一般来说,在生活中,虽然我们习惯了做重复的事情,但总是会去接受并学习新的东西。学习的过程是最有趣的科学话题之一,人们做了许多尝试来描述或重现人类的学习过程。
面对新内容并且掌握新内容这样的挑战指导着本书的写作。虽然“神经网络”这个名字可能看起来很奇怪,甚至给人以本书是关于神经学的一种感觉,但我们努力把焦点集中在你决定购买本书的原因上,来简化这些细微差别。我们打算建立一个框架,以显示神经网络实际上是简单易懂的,并且保证读者无需预先了解这个题目,也能充分理解本书提出的概念。
所以,我们鼓励你全面地探究本书的内容,在面对重大问题时,要看到神经网络的强大力量,但要始终保持一个初学者的视角。本书中讲述的每一个概念都是用简单的技术性语言来解释的。本书的宗旨是让你可以使用简单语言来编写智能应用程序。
最后,感谢所有直接或间接对本书做出贡献的人,是他们从一开始就支持我们,感谢帕拉联邦大学、数据和组件提供者巴西气象学会(Brazilian Institute of Meteorology,INMET)、Proben1和JFreeCharts。特别感谢我们的顾问Roberto Limão教授,他给我们介绍神经网络这个主题,我们合作在这个领域发表了许多论文。还要感谢参考文献中引用的几位作者所做的工作,他们拓宽了我们对神经网络的视野,并启发我们深入思考如何指导读者将神经网络应用于Java编程。
我们希望你有一个非常愉快的阅读体验,鼓励你下载源代码,并遵循本书中引用的代码示例。
第1章,初识神经网络,这是神经网络基础知识及其用途介绍。你将了解到本书所涉及的基本概念以及对Java编程语言的简要回顾。在所有随后的小节中,还包括神经网络的Java代码实现。
第2章,神经网络是如何学习的,涵盖神经网络的学习过程,并展示如何使用数据来达到这一目的。这里介绍了学习算法的完整结构和设计。
第3章,运用感知机,介绍感知机的使用。感知机是最常用的神经网络架构之一,本书提出一个包含神经元层的神经网络结构,并展示它们在基础问题上如何通过数据来学习。
第4章,自组织映射,展示了一个无监督的神经网络架构(自组织映射),它被应用于在记录中寻找模式或聚类。
第5章,天气预测,这是第一个关于实践的章节,展示了神经网络在预测,即天气数据预测方面的一个有趣的应用。
第6章,疾病诊断分类,涵盖另一个实用的且神经网络非常擅长的分类任务。在本章中,你将看到一个非常有说服力而且有趣的疾病诊断应用。
第7章,客户特征聚类,介绍神经网络如何能够在数据中找出模型,包含一个常见的应用开发,涉及对具有相同购买属性的客户进行分组。
第8章,模式识别(OCR案例),一谈到有趣且惊人的模式识别功能,肯定包括光学字符识别,本章探讨如何使用Java语言的神经网络来完成此任务。
第9章,神经网络优化与自适应,介绍了关于如何优化和增加神经网络的适应性,从而增强神经网络的能力。
你需要Netbeans (www.netbeans.org
) 或 Eclipse (www.eclipse.org
),它们都是免费的且可以在其官方网站下载。
本书面向神经网络技术的专业开发人员和业余爱好者,读者无需具备Java编程知识。阅读本书不需要有神经网络的相关知识,本书将从头开始教。如果读者熟悉神经网络或其他机器学习技术,即使缺乏Java经验,本书也能提升其开发能力,帮助读者开发出实用的应用程序。当然,如果读者知道基本的编程概念,将会从这本书中获益良多,但无需以前的经验。
本书采用不同的文本样式区分不同种类的信息。这里列举部分示例并对其含义作出解释。
文本中的代码、数据库表名、文件夹名称、字符串、文件扩展名、路径名、虚拟URL、用户输入和Twitter句柄如下所示:“在Java项目中,这些值的计算是通过Classification类完成的。”
代码块如下所示:
Data cardDataInput = new Data("data", "card_inputs_training.csv");
Data cardDataInputTestRNA = new Data("data", "card_inputs_test.csv");
Data cardDataOutputTestRNA = new Data("data", "card_output_test.csv");
新的术语和重要的文字以粗体展示:
![]()
警告信息或重要注释出现在这样的方框里。
![]()
温馨提示和小技巧出现在这样的框里。
我们非常欢迎来自读者的反馈。让我们知道你对本书的看法,喜欢什么或不喜欢什么。读者的反馈很重要,因为它有助于我们开发出真正有价值的图书内容。
如果读者要向我们发送一般性反馈,只需发送电子邮件至feedback@packtpub.com
,并在邮件主题中注明图书的标题。
如果你对于一个主题有非常专业的知识,并且对编写或贡献一本书感兴趣,请参阅我们的作者指南www.packtpub.com/authors
。
现在,你已经是一名Packt图书的尊享者,我们有一些东西可以帮助你获取更多的信息。
你可以访问http:// www. packtpub.com
,在账户下载所有已购买的Packt图书的示例代码文件。如果你是从其他地方购买本书,则可以访问http://www.packtpub.com/support
并注册,我们将以电子邮件的形式直接发送给你。
虽然我们已经很谨慎,以确保内容的准确性,但是错误仍在所难免。如果你在我们的任何一本书中发现了错误,无论是文本或代码中的错误,如果你能向我们报告这些错误,我们将不胜感激。通过这样的方式,可以解决其他读者的困惑,并帮助我们改进本书的后续版本。如果你发现任何勘误,请通过访问http://www.packtpub.com/submit-errata
,选择要报告的图书,单击勘误提交表单链接,并输入勘误的详细信息。一旦你的勘误被验证成功,提交将被接受,勘误表将被上传到我们的网站或添加到该标题的勘误表下任何一个现有的勘误表里。
在互联网上,盗版是所有媒体正在面对的问题。在Packt,我们非常重视版权保护和版权许可。如果你在互联网上发现任何形式的Packt图书的非法副本,请立即向我们提供网址或网站名称,以便我们可以采取补救措施。
请通过copyright@packtpub.com
向我们提供可疑盗版材料的链接。
感谢你帮助我们保护作者,以及保护我们为你带来有价值内容的能力。
如果你对本书的任何方面有问题,可以通过questions@packtpub.com
与我们联系,我们将尽最大努力为你解决问题。
本章介绍神经网络及其用途,主要讲解神经网络的基本概念,为后续章节打下基础。其主要内容包含以下几个部分:
首先,“神经网络”这个词会在我们脑海里创建出一种模拟人脑的景象,特别是刚刚接触到神经网络的人可能产生这样的想法。实际上,这也是正确的,大脑就是一个巨大的自然神经网络。然而,人工神经网络又是什么呢?其实“人工”是相对于自然神经网络中的“自然”而言的,一提到“人工”一词,首先映入脑海的往往是一张人工大脑或机器人的图片。我们受人脑结构的启发创造了与人脑相似的神经网络结构,所以这也可以被叫作人工智能。读到这里,之前对人工神经网络没有什么经验的读者可能认为这本书是教大家怎么创造智能系统的,例如人造大脑就可以用Java代码模拟人类大脑思考的过程,不是吗?当然,我们不会涉及像黑客帝国里人工智能机器人那样的产物,但是,我们会谈到神经网络可以实现很多不可思议的功能。充分利用Java编程语言框架的优势,为读者提供定义和创建神经网络结构的完整Java代码。
在不清楚人工神经网络的起源,包括一些人工神经网络的专业术语时,我们不宜马上展开讨论。在本书中,我们认为神经网络(neural network,NN)和人工神经网络(artificial neural network,ANN)是同一个概念,尽管神经网络的概念更加宽泛,它还包括自然神经网络。那么,到底什么是ANN?让我们来简单探索这个术语的历史。
20世纪40年代,神经生理学家Warren McCulloch和数学家Walter Pits设计了第一个包含神经系统学基础和数学操作的人工神经元的数学实现模型。在那时,很多研究的方向是弄清楚人类大脑的结构和它是否可以被模拟以及如何模拟,但是这些研究都只停留在神经系统学科领域内。McCulloch和Pits的想法是非常创新的,因为他们引入了数学的元素。另外,想想我们的大脑由数十亿计的神经元组成,每个神经元又和其他数百万神经元相连接,最终得到数万亿计的神经元连接,这是一个多么巨大的神经网络。然而,每个神经元单元是非常简单的,仅仅是作为一个能够汇总求和以及传递信号的处理器。
基于此,McCulloch和Pits 设计了一个简单的单个神经元模型,最初用于模拟人类的视觉。那时候的计算器和计算机还是非常少的,但是它们善于快速处理数学操作。另外,即使是现在,如果不用一些特殊的编程框架,视觉处理和声音识别的编程也是很难的,这恰恰与计算机能快速处理函数和数学操作的事实相反。然而,如表1-1所示,人脑在视觉处理和声音识别方面比计算器和计算机更高效,这确实激励了很多科学家和研究人员从事这方面的研究。
因此,人们认为人工神经网络能够很好地完成诸如模式识别、机器学习,以及预测趋势等任务,就像一个专家可以根据基础知识进行工作一样。而与那些需要执行一系列步骤来完成一个既定目标的传统算法方法恰恰相反,人工神经网络反而有能力通过它高度互联的网络结构去学习怎样自己解决问题。
表1-1
人类能快速解决的任务 |
计算机能快速解决的任务 |
---|---|
图片分类 |
复杂计算 |
声音识别 |
语法错误纠正 |
人脸识别 |
信号处理 |
通过经验预测事件 |
操作系统管理 |
可以说人工神经网络是受自然启发的结构,它和人脑有很多相似之处。图1-1展示了一个自然神经元结构,它由神经元细胞核、树突和轴突组成,轴突分出很多分支来与其他神经元的树突相连而形成突触。
图1-1
所以,人工神经元有相似的结构,它也包含一个核(处理单元)、多个树突(类似于输入)以及一个轴突(类似于输出),如图1-2所示。
图1-2
在所谓的神经网络中,神经元之间的连接类似于自然神经结构中的突触。
自然神经元被证实为一个信号处理器,这是由于它可以在树突端接收微信号,根据信号的强度或者大小,会在轴突触发一个信号。我们可以认为神经元在输入端有一个信号接收器,在输出端有一个响应单元,它可以根据不同的强度和量级触发一个可以向前传递至其他神经元的信号。因此,我们可以定义一个人工神经元结构,如图1-3所示。
图1-3
提示:
在自然神经元中,有一个阈值(threshold),当刺激达到这个阈值,就会产生神经冲动并沿着轴突把信号传递到其他神经元。我们用激活函数来模拟这种神经冲动,在表示神经元的非线性行为中,这是很有用的。
神经元的输出是通过激活函数得到的,激活函数为神经网络处理加入了非线性特征,由于自然神经元具有非线性行为,所以非线性特征是非常必要的。激活函数往往把输出信号限制在一定范围内,因此,激活函数常常是非线性函数,但是在一些特殊情况下,也可能是线性函数。
如下是4种最常用的激活函数:
表1-2展示的是与这些函数对应的公式及图像。
表1-2
函数 |
公式 |
图像 |
---|---|---|
Sigmoid |
|
|
Hyperbolic tangent |
|
|
Hard limiting threshold |
|
|
Linear |
|
|
在神经网络中,权值代表着神经元之间的连接并且它可以放大或减小神经元信号,例如扩大信号从而改变信号。因此,通过改变神经网络信号,神经系统权值有能力影响神经元的输出信号,所以一个神经元的激活依赖于输入和相应的权值。倘若输入信号来自其他神经元或外界,那么权值可以认为是在神经元之间建立的神经网络连接。由于权值是神经网络的内部因素并且可以影响它的输出,我们可以把权值当成神经网络的知识,只要更改了权值,将会改变神经网络的功能和相应动作。
人工神经元可以拥有一个独立的元素,它可以把外部信号添加到激活函数,这个独立的元素被称为偏置。
就像输入信号有一个对应的权值,偏置也拥有一个对应的权值,这个特性可以使神经网络知识表示成一个更纯粹的非线性系统。
自然神经元以层的方式组织,每一层都有自己的处理方式,例如输入层接受外界直接刺激,输出层产生可以影响外界的神经冲动。在这些层之间,有很多隐藏层,意味着它们不会和外界直接产生相互影响。在人工神经网络中,同一层的所有神经元共享相同的输入和激活函数,如图1-4所示。
图1-4
神经网络由多个相连接的层组成,形成多层网络。这些神经系统层可以分成以下3种基本类型:
实际上,接受外界刺激的抽象神经系统层可以作为一个附加层,以此来增强神经网络对更加复杂的知识的表现力。
提示:
每个神经网络至少含有一个输入层/输出层,不管神经网络的层数是多少。在多层神经网络的情况下,在输入层和输出层之间的都被称为隐藏层。
一般来说,神经网络含有不同的结构,这取决于神经元或者神经元层之间的连接方式。每一种神经网络结构都针对特定问题。神经网络可以用来处理很多问题,我们根据问题的不同特征来设计神经网络结构,可以更有效地处理这个问题。
主要有两类神经网络结构模型:
在单层神经网络结构中,所有的神经元位于相同层,形成一个单一层,如图1-5所示。
图1-5
神经网络接收输入信号并将其传递给神经元,然后神经元产生输出信号。神经元之间的连接可以是重复的,也可以是不重复的。这种结构可用于单层感知机、自适应机、自组织映射、Elman网络和Hopfield神经网络。
对于多层神经网络,神经元被分成多个神经元层,每一层对应一个共享相同输入数据的并行神经元结构,如图1-6所示。
图1-6
径向基函数和多层感知机是多层神经网络结构典型的实例,这种网络结构非常适合用于逼近函数所表示的真实数据。此外,由于多层神经网络结构含有多个处理层,它比较适合于训练一组非线性数据,然后可以区分数据或更简单地确认数据复制或数据识别的知识。
神经网络中的信号流可以是单向的,也可以是循环的。在第一种情况下,我们把这种神经网络结构称为前馈神经网络,从输入信号进入输入层到信号被处理后,信号都是向前传递到下一层的,如图1-6所示。多层感知机和径向基函数同样是前馈神经网络很好的实例。
当神经网络中出现一些内部递归回路时,即信号在经过处理之后又被传回到接收和处理过该信号的神经元或神经元层,这种网络就是反馈型网络,如图1-7所示。
图1-7
在神经网络中添加递归回路的理由是引入动态行为,特别是在处理涉及时间序列或模式识别的时候,这就需要一个内部存储来增强学习过程。但是,这种神经网络的训练是特别艰难的,最终可能训练失败。大多数递归神经网络是单层的(例如Elman网络和Hopfield神经网络),但是也可以创建递归多层神经网络(例如echo和递归多层感知机网络)。
神经网络通过调整神经元之间的连接(即权值)以达到学习的目的。就像在1.3节中提到的,权值代表了神经网络所蕴含的知识。对于同一个输入,不同的权值将会产生不同的结果。神经网络可以根据某种学习规律来调整其权值,以提升预测结果质量。学习过程的一般模式如图1-8所示。
图1-8
图1-8所描绘的过程被称为监督学习,这是因为输出是某种预期的值,但是神经网络也可以仅通过其输入数据进行学习而不依赖任何期望输出(监督)。在第2章中,我们将深入了解神经网络的学习过程。
本书将会用Java编程语言来实现一个神经网络的整个过程。Java于20世纪90年代由太阳微系统公司(Sun Microsystems)的工程师发明,是一种面向对象的编程语言,其所有权于2010年被甲骨文公司(Oracle)获得。如今,Java运行在许多设备中,这些设备已成为我们日常生活的一部分。
在Java等面向对象语言中,我们同类和对象打交道。类是真实世界中某些事物的模板,而对象则是这种模板对应的实例,有点像汽车(代表所有以及各种车的类)和我的汽车(代表某辆特定的车)。Java类往往由属性和方法(或者函数)构成,它包含了面向对象编程(object-oriented programming,OOP)的概念。由于本书的目标只是从实践的角度来设计和创建神经网络,所以我们只会对这些概念进行一个简短的回顾,而不做深入的探究。在这个过程中,需要了解4个相关概念。
根据本章介绍的神经网络和面向对象编程的概念,我们接下来将设计第一个实现神经网络的类集。神经网络包含层、神经元、权值、激活函数和偏置,层的类型主要有3种:输入层、隐藏层和输出层。每一层包含一个或者多个神经元。每个神经元都被连接到某个神经输入/输出或者另一个神经元,这些连接称为权值。
要特别强调一点,神经网络可以有许多隐藏层,也可以一个都没有,每一层的神经元数量也会变化。但是,输入和输出层的神经元数量分别与神经输入和神经输出的数量相等。
接下来,让我们开始实现。首先,我们来定义6个类,细节如表1-3所示。
表1-3
类名:Neuron |
|||||
属性 |
|||||
|
一个实数 |
||||
|
一个实数 |
||||
方法 |
|||||
|
用伪随机实数初始化 |
||||
参数:None |
|||||
返回值:一个伪随机实数 |
|||||
|
用一个实数集合设置 |
||||
参数:将被存储在类对象里的实数列表 |
|||||
返回值:None |
|||||
|
用一个实数集合设置 |
||||
参数:将被存储在类对象里的实数列表 |
|||||
返回值:None |
|||||
|
返回神经元集合的输入权值 |
||||
参数:None |
|||||
返回值:存储在 |
|||||
|
返回神经元集合的输出权值 |
||||
参数:None |
|||||
返回值:存储在 |
|||||
Java实现类:Neuron.java文件 |
|||||
类名:Layer |
|||||
注意:该类为抽象类且不能被实例化 |
|||||
属性 |
|||||
|
一个 |
||||
|
用来存储层的神经元数量的整数 |
||||
方法 |
|||||
|
返回层的神经元集合 |
||||
参数:None |
|||||
返回值:一个 |
|||||
|
用一个Neuron集合设置 |
||||
参数:将被存储的Neuron集合 |
|||||
返回值:None |
|||||
|
返回层的神经元的数量 |
||||
参数:None |
|||||
返回值:层的神经元数量 |
|||||
|
设置层的神经元数量 |
||||
参数:层的神经元数量 |
|||||
返回值:None |
|||||
Java实现类:Layer.java文件 |
|||||
类名:InputLayer |
|||||
注意:该类从Layer类继承其属性和方法 |
|||||
属性 |
|||||
None |
|||||
方法 |
|||||
|
用伪随机数初始化输入层 |
||||
参数:一个 |
|||||
返回值:None |
|||||
|
打印层的输入权值 |
||||
参数:一个 |
|||||
返回值:None |
|||||
Java实现类:InputLayer.java文件 |
|||||
类名:HiddenLayer |
|||||
注意:该类从Layer类继承其属性和方法 |
|||||
属性 |
|||||
None |
|||||
方法 |
|||||
|
用伪随机实数初始化隐藏层 |
||||
参数:一个 |
|||||
返回值:None |
|||||
|
打印层的权重 |
||||
参数:一个元素对象为 |
|||||
返回值:None |
|||||
Java实现类:HiddenLayer.java文件 |
|||||
类名:OutputLayer |
|||||
注意:该类从Layer类继承其属性和方法 |
|||||
属性 |
|||||
None |
|||||
方法 |
|||||
|
用伪随机数初始化输出层 |
||||
参数:一个 |
|||||
返回值:None |
|||||
|
打印层的权值 |
||||
参数:一个OutputLayer 类的对象 |
|||||
返回值:None< | |||||
Java实现类:OutputLayer.java文件 |
|||||
类名:NeuralNet |
|||||
注意:神经网络拓扑结构在该类中是固定的(输出层有两个神经元,两个隐藏层,每层分别有3个神经元,输出层有一个神经元) 提示:这是第一个版本 |
|||||
属性 |
|||||
|
一个 |
||||
|
一个 |
||||
|
一个元素类型为 |
||||
|
一个 |
||||
|
存储隐藏层的层数量的整数 |
||||
|
将神经网络作为一个整体初始化。层会被构建,神经元的权值的每个集合会被随机构建 |
||||
参数:None |
|||||
返回值:None |
|||||
|
将神经网络作为一个整体打印。每一层的每个输出和输入权值都会被展示 |
||||
参数:None |
|||||
返回值:None |
|||||
Java实现类:NeuralNet.java |
OOP语言的一个优势是易于在统一建模语言(UML)中用文档来表现程序。UML类图以一种简单而直观的方法展现了类、属性、方法和不同的类之间的关系,这样就能帮助程序员及其利益相关者将整个项目作为一个整体来理解。图1-9展现了该项目的最初版本类图。
图1-9
现在,让我们来应用这些类得到一些结果。下面展示的代码有一个测试类、一个main
方法和一个名为n
的NeuralNet
类的对象。当这个方法被调用(通过类执行时),它会从对象n
中调用initNet()
和printNet()
方法,生成如图1-10所示的结果。它表示了一个神经网络,输入层有两个神经元,隐藏层有3个神经元,输出层有一个神经元:
public class NeuralNetTest {
public static void main(String[] args) {
NeuralNet n = new NeuralNet();
n.initNet();
n.printNet();
}
}
需要记住的是每次运行代码,会生成相应的新的伪随机权值。所以,当你运行这段代码时,其他值将会出现在控制台中,如图1-10所示。
图1-10
在本章中,我们了解了神经网络的定义、用途和基本概念。我们还了解了用Java编程语言实现的一个非常基础的神经网络,通过编码神经网络的每个元素,在实践中应用了神经网络理论。在进入高级概念之前,了解这些基本概念很重要。这同样适用于Java实现的代码。在下一章中,我们将深入研究神经网络的学习过程,并且以简单的实例来探索不同的学习类型。
本章将展示神经网络从数据中获取知识而执行的学习过程。我们将介绍训练、测试和验证等概念,并且展示如何用Java实现它们。本章还会演示一些方法,这些方法用来评估某个神经网络在学习方面的表现以及学习算法的参数。本章涉及以下概念:
神经网络真正令人惊叹的是其从周围环境中学习的能力,就像那些有天赋的人能做的一样。我们人类通过观察和重复练习来体验学习过程,直到某些任务或者概念被完全掌握。从生理学的角度来说,在人脑中的学习过程是对节点(神经元)之间的神经连接进行重新设置,这个过程导致了一个新思维定式的产生。
神经网络的连接特性使得神经网络将学习过程分布到整个结构,这个特性使得这种结构可以足够灵活来学习各种各样的知识。与那些只能执行预先设定好模式的任务的数字计算机相反,神经系统能够根据预设的期望标准来提升和执行新任务。换句话说,神经网络不需要预先设定模式;它们能自己学习模式。
考虑到每个需要解决的任务可能有很多理论上可行的解,神经网络的学习过程旨在寻找一种能够生成满意结果的最优解。像人工神经网络这样的结构被鼓励使用,这是由于它们可以严格从输入刺激中获取任意类型的知识,也就是说,和数据相关的任务或问题。首先,人工神经网络会生成一个随机结果和误差,然后根据这个误差,其参数会进行调整。
提示:
我们可以认为人工神经网络的参数(权值)是解的一部分。假设单个解表示在解的多维空间中的一个点。每个解产生一个误差度量,它告诉我们这个解与最优解的差距。在每次迭代中,学习算法都会寻找一个解,这个解产生的误差更小并且由此更加接近于最优解。
神经网络主要有两种学习类型,称为监督学习和非监督学习。人类思维的学习类型无外乎也是这两种方式。我们无需任意种类的目标模式就可以从观察中学习(无监督),或者遵循老师给我们展示的正确模式(监督)。这两种范式的不同,主要体现在目标模式的相关性上,对不同的问题也不尽相同。
监督学习处理形如X、Y的键值对,其目标是通过函数f将X映射到Y上:f:X→Y。Y在这里就是监督者,是期望的目标输出,而X数据是独立于源的数据,它可以生成Y数据。这类似一位老师,正在教授某人将要被执行的特定任务,如图2-1所示。
图2-1
该学习范式的一个特性是存在一个直接的误差参考,也就是将目标和实际结果相比较。神经网络的参数会被输入到一个代价函数,它可以量化期望和实际输出之间的误差。
提示:
在最优化问题中,代价函数只是一个需要最小化的度量标准。这意味着人们将寻找使代价函数为最低可能值的参数。代价函数将在本章后面进行更详细的介绍。
监督学习非常适合那些已经提供了某种模式、某个需要达到目标的任务。例如后面的一些案例:图像分类、语音识别、函数逼近和预测。注意,一些先验知识,如独立的输入值(X)和分类标注的输出值(Y)需要提供给神经网络。有标注的输出值是监督学习的必要条件。
如图2-2所示,在无监督学习中,我们处理那些没有任何标注或者分类的数据;换句话说,神经结构会试图举一反三并只考虑从输入数据X中提取知识。
图2-2
这类似于自我学习,就像某人通过自己的经验和一些支持标准来学习。在无监督学习里,我们不需要定义一个期望的模式来应用到观测值里,但是神经结构能够在没有任何监督的情况下自己生成一个模式。
提示:
这里,代价函数扮演了一个重要的角色。它将在很大程度上影响所有神经属性以及输入数据之间的关系。
无监督学习可以应用到如下场景:聚类、数据压缩、统计建模和语言建模。这种学习范式在第4章中会详细讨论。
我们已经从理论上定义了学习过程和它的工作原理。接下来必须继续深入探讨学习算法本身的数学逻辑。学习算法是驱动神经网络学习过程的手段,它在很大程度上由神经网络架构来决定。从数学的观点上说,它旨在寻找一个能使代价函数C(X,[Y])尽可能小的W权值的最优解。
通常,学习算法的执行流程如图2-3所示。
图2-3
就像我们希望编写的程序那样,我们应该定义目标。所以,在这里,我们关注的是学习知识的神经网络。应该将知识(或者环境信息)呈现给人工神经网络,并检查它的响应结果,虽然结果通常是毫无意义的。网络响应的结果接下来将与期望结果相比较,并且结果将被代入到代价函数C中。代价函数将决定如何更新权值W。接下来学习算法将计算ΔW项,这意味着会增加权值的变化。权值将按照以下等式进行更新。
W(k+1)=W(k)+ΔW
k代表第k次迭代,W(k)代表第k次迭代的神经权值,同理,k +1代表下一次迭代。
当学习过程开始,神经网络必须提供越来越接近于期望值的结果,直到最后,结果达到了期望的标准,学习过程就被认为结束了。
那么,我们现在或许会问神经网络是否已经从数据中学习到了知识,但是我们如何证明它已经从数据中有效地学习到了知识?答案就像要求学生参加的考试一样,我们需要检验训练过后网络响应的结果。但是等等!你认为这就像一个老师总是在考试中设置那些出现在课堂中的那些问题?使用已知的案例来评估某人的学习效果没有什么意义,或者一个怀疑的老师会得出学生可能只是死记硬背而非理解这些内容。
好了,让我们来解释一下这一部分。这里正在讨论的是测试,已经涉及到的学习过程被称为训练。在训练完某个神经网络后,我们应该测试它是否真的学到了知识。对于测试,必须使用另外一部分数据,这部分数据和神经网络在学习时所用到的数据来自于相同环境。这是必需的,因为如果就像学生一样,只用那些呈现的数据集,神经网络才可以正确地响应,这被称为过度训练。为了检查神经网络是否被过度训练,我们必须用其他数据集来验证其响应。
图2-4举例说明了过度训练的问题。想象我们的网络被设计为接近于某些未定义函数f(x)。神经网络代入f(x)的一些数据,并产生如图2-4的左图所示的结果。但是,当我们扩大测试域时,可以注意到网络的响应并没有遵循数据的规律。
图2-4
在这个例子中,我们看到了神经网络学习整个环境(函数f(x))的失败案例。可能是由以下几个原因导致此次失败:
在本书中,我们将介绍这个过程以防止这种情况的发生以及训练中可能出现的其他问题。
学习过程是可控的。一个重要的参数就是学习率,经常表示为希腊字母η。这个参数规定了神经网络权值在权值的多维空间中变化的强度。设想一个简单的神经网络,有两个输入单元和一个神经元以及一个输出单元。因此我们得到了两个权值w1和w2。现在假设我们想要训练这个神经网络,那么是否可以评估每对权值的误差?假设我们应用的是一个如图2-5中所示的表面。
图2-5
学习率负责调节权值在表面移动的距离。该参数可能加速学习过程但也可能导致一组比前一个更差的权值。
另一个重要的参数是停止条件。通常,当误差达到广义平均误差时,训练停止。但是在有些情况下,神经网络无法学习,权值几乎没有变化。此时,停止的条件是最大迭代次数。
误差度量和代价函数对于监督学习中训练过程的成败极其重要。假设我们有为神经网络准备的N条记录的数据集,包含了X和T变量,其中X为输出独立的值,T为依赖于X的目标值。将神经网络认为是一个数学函数ANN(),当它得到X值时会生成Y作为输出。
Y=ANN(x)
对于每一个给定到ANN的x值,都会生成一个y值,与t值相比较得到一个误差e。
E=y−t
但是,这仅仅是每一个数据点独立的误差度量。我们还应该考虑一个通用的度量,它覆盖了所有N个数据对,这是因为我们想要神经网络学习所有数据点,并且相同的权值必须能生成覆盖整个训练集的数据。这就是代价函数C所扮演的角色。
X是输入,T是目标输出,W是权值,x[i]是第i个即时输入,t[i]是第i个即时目标输出。该函数的结果是目标输出和神经输出之间误差的总体度量,并且这应该被最小化。
将前面所提到的理论内容整合到一个简单的学习算法例子中。本章将探索两个神经架构:感知机和自适应线性元。它们都非常简单,只包含一层。
感知机学习只需要考虑目标和输出的误差以及学习率。权值更新规律如下:
wi连接了第i个输入和神经元,t[k]是第k次样本数据的目标输出,y[k]是第k次样本的神经网络输出,xi[k]是第k次样本的第i个输入,η是学习率。可以看到规则刻意地简化并且不用考虑那些非线性激活函数的感知机,它只是简单地往误差的反方向逼近并理想地认为这能使网络接近于目标。
有一个基于梯度下降的更优化的算法被开发出来以考虑非线性及其导数。这个算法增加到感知器规则中的是激活函数g(h)的导数,h是在应用到激活函数之前的所有神经元输入的加权综合,所以权值的更新规律如下:
现在,是时候使用面向对象编程的理念开发一个神经网络,并且解释下相关理论了。前面章节中出现的项目适用于感知机和线性适应元规则,以及Delta规则。
前面章节NeuralNet类已经被更新到包含训练数据集(输入和输出)、学习参数和激活函数设置。InputLayer
类也被更新并包含了一个方法。我们增加了Adaline
、Perceptron
、Training
类的设计,每个类的实现在代码中能够找到。但是,现在,让我们将神经学习和Training
类的Java实现联系起来。
Training
类应该被用于训练神经网络。本章,我们将使用该类来训练Perceptron
和Adaline
。另外,那些将在神经网络中使用的激活函数也需要考虑到。所以让我们定义两个枚举集来处理这些设置:
public enum TrainingTypesENUM {
PERCEPTRON, ADALINE;
}
public enum ActivationFncENUM {
STEP, LINEAR, SIGLOG, HYPERTAN;
}
除了这些参数,我们还需要定义停止条件、误差、MSE误差和迭代次数,如下面代码所示:
private int epochs;
private double error;
private double mse;
学习率已经在NeuralNet
类中定义过了,这里将会使用。
最后,需要一个方法来更新某个指定神经元的权值,所以让我们先来看看CalcNewWeight
方法:
private double calcNewWeight(TrainingTypesENUM trainType,
double inputWeightOld, NeuralNet n, double error,
double trainSample, double netValue) {
switch (trainType) {
case PERCEPTRON:
return inputWeightOld + n.getLearningRate() * error * trainSample;
case ADALINE:
return inputWeightOld + n.getLearningRate() * error * trainSample
* derivativeActivationFnc(n.getActivationFnc(), netValue);
default:
throw new IllegalArgumentException(trainType
+ " does not exist in TrainingTypesENUM");
}
}
这个方法中有一个switch
语句,可以根据训练类型(Adaline
或者Perceptron
)。我们还看见inputWeightOld
(老的权值)、n
(神经网络训练次数)、error
(目标和神经输出的误差)、trainsample
(给权值的输入)、netValue
(激活函数处理之前的加权总和)等参数。学习率通过调用NeuralNet
类的getLearningRate()
方法进行获取。
一个有趣的细节是激活函数的导数被Adaline
训练类型所调用,这就是Delta
规则。所有的激活函数都在Training
类中被实现为方法,它们相应的导数也在Training
类中实现。derivativeActivationFnc
方法帮助调用作为参数传入的激活函数相应的导数。
在Training
类中实现的两个特殊方法:一个是用来训练神经网络,另一个是用来训练某些层的神经元。尽管这在本章不是必需的,但为将来的例子和更新做好代码准备总是好的。让我们来快速浏览下这个训练方法:
public NeuralNet train(NeuralNet n) {
ArrayList<Double> inputWeightIn = new ArrayList<Double>();
int rows = n.getTrainSet().length;
int cols = n.getTrainSet()[0].length;
while (this.getEpochs() < n.getMaxEpochs()) {
double estimatedOutput = 0.0;
double realOutput = 0.0;
for (int i = 0; i < rows; i++) {
double netValue = 0.0;
for (int j = 0; j < cols; j++) {
inputWeightIn = n.getInputLayer().getListOfNeurons().get(j)
.getListOfWeightIn();
double inputWeight = inputWeightIn.get(0);
netValue = netValue + inputWeight * n.getTrainSet()[i][j];
}
estimatedOutput = this.activationFnc(n.getActivationFnc(),
netValue);
realOutput = n.getRealOutputSet()[i];
this.setError(realOutput - estimatedOutput);
if (Math.abs(this.getError()) > n.getTargetError()) {
// fix weights
InputLayer inputLayer = new InputLayer();
inputLayer.setListOfNeurons(this.teachNeuronsOfLayer(cols,
i, n, netValue));
n.setInputLayer(inputLayer);
}
}
this.setMse(Math.pow(realOutput - estimatedOutput, 2.0));
n.getListOfMSE().add(this.getMse());
this.setEpochs(this.getEpochs() + 1);
}
n.setTrainingError(this.getError());
return n;
}
这个方法接收一个神经网络作为入参,然后返回另一个神经网络,它的权值已经被训练过。接下来看while
子句,当迭代次数不等于Training
类中设置的最大迭代次数时,将继续循环。在这个循环中,有一个for
子句,迭代了神经网络所有的训练样本,并在当前迭代中开始计算对应输入的神经输出的过程。
当得到了真实的神经网络输出,就将其与估计的输出比较下,并计算误差。检查误差,如果误差高于最小误差,则通过调用teachNeuronsOfLayer
开始更新,代码如下:
inputLayer.setListOfNeurons(this.teachNeuronsOfLayer(cols,
i, n, netValue));
这个方法的实现在本章所附的代码中可以找到。
接下来,这个过程会不断重复,直到神经网络接收了所有的样本数据,当达到最大迭代次数,循环结束。
表2-1展示了本章设计的所有类的字段和方法。
表2-1
类名:Training | ||
注意:该类为抽象类且不能被实例化 | ||
属性 | ||
private int epochs |
用来存储训练周期的次数,被称为epoch | |
private double error |
存储预期输出和实际输出的实数 | |
private double mse |
存储均方误差的实数(MSE) | |
枚举 | ||
注意:枚举有助于控制不同类型 | ||
public enum TrainingTypesENUM { PERCEPTRON, ADALINE; } |
存储项目支持的训练类型(Perceptron 和Adaline ) |
|
public enum ActivationFncENUM { STEP, LINEAR, SIGLOG, HYPERTAN; } |
存储项目支持的激活函数类型(阶跃、线性、sigmoid logistics、双曲正切) | |
方法 | ||
public NeuralNet train(NeuralNet n) |
训练神经网络 | |
参数:NeuralNet 对象(未训练的神经网络) |
||
返回值:NeuralNet 对象 (训练过的神经网络) |
||
public ArrayList<Neuron> teachNeuronsOfLayer(int numberOfInputNeurons, int line, NeuralNet n, double netValue) |
使某层的神经元计算并改变其权值 | |
参数:输入神经元的数量、样本数量、NeuralNet 对象、神经网络净输出 |
||
返回值:ArrayList 集合,元素类型为Neuron |
||
private double c alcNewWeight(TrainingTypesENUM trainType, double inputWeightOld, NeuralNet n, double error, double trainSample, double netValue) |
计算某个神经元的新权值 | |
参数:训练类型的枚举值、老的输入权值、NeuralNet 对象、误差值、训练样本值、净输出值 |
||
返回值:代表新权值的实数 | ||
public double activationFnc ( ActivationFncENUM fnc, double value) |
决定要使用的激活函数,并调用其计算方法 | |
参数:激活函数枚举值、实数值 | ||
返回值:激活函数计算结果值 | ||
public double derivativeActivationFnc ( ActivationFncENUM fnc, double value) |
决定选择哪个激活函数并调用计算其导数的方法 | |
参数:激活函数枚举值、实数值 | ||
返回值:激活函数的导数的计算结果 | ||
p rivate double fncStep (doublev) |
计算阶跃函数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double fncLinear (double v) |
计算线性函数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double fncSigLog (double v) |
计算sigmoid logistics函数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double fncHyperTan (double v) |
计算双曲正切函数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double derivativeFncLinear (double v) |
计算线性函数的导数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double derivativeFncSigLog (double v) |
计算sigmoid logistics函数的导数 | |
参数:实数值 | ||
返回值:实数值 | ||
private double derivati veFncHyperTan (doublev) |
计算双曲正切函数的导数 | |
参数:实数值 | ||
返回值:实数值 | ||
public void printTrainedNetResult (NeuralNet trainedNet) |
输出训练过的神经网络并显示其结果 | |
对象:NeuralNet 对象 |
||
返回值:None | ||
public int getEpochs() |
返回训练的迭代次数 | |
public void setEpochs (int epochs) |
设置训练的迭代次数 | |
public double getError() |
返回训练误差(估计值与真实值比较) | |
public void setError (double error) |
设置训练误差 | |
public double getMse() |
返回MSE | |
public void setMse (double mse) |
设置MSE | |
Java实现类Training.java文件 | ||
类名:Perceptron | ||
注意:该类从Training 类继承其方法和属性 |
||
属性 | ||
None | ||
方法 | ||
public NeuralNet train(NeuralNet n) |
使用感知机算法训练神经网络 | |
参数:NeuralNet 对象(未训练的神经网络) |
||
返回值:NeuralNet 对象(通过感知机算法训练后的NeuralNet 对象) |
||
Java实现类Perceptron.java文件 | ||
类名:Adaline | ||
注意:该类从Training 类继承其方法和属性 |
||
属性 | ||
None | ||
public NeuralNet train(NeuralNet n) |
使用 adaline算法训练神经网络 | |
参数:NeuralNet 对象(未训练的神经网络) |
||
返回:NeuralNet 对象(通过adaline算法训练后的NeuralNet 对象) |
||
Java实现类:Adaline.java文件 | ||
类名:InputLayer | ||
注意:该类在前面的版本已经出现过,现更新如下 | ||
属性 | ||
None | ||
方法 | ||
public void setNumberOfNeuronsInLayer( int numberOfNeuronsInLayer) |
设置输出层的神经元数量,由于偏置的存在,它是一个一个增加的 | |
Java实现类:InputLayer.java文件 | ||
类名:NeuralNet | ||
注意:该类在前面的版本已经出现过,现更新如下 | ||
属性 | ||
private double[][] trainSet |
存储输入数据的训练集矩阵 | |
private double[] realOutputSet |
存储输出数据的训练集向量 | |
private int maxEpochs |
存储神经网络训练的最大迭代次数 | |
private double learningRate |
存储学习率的实数 | |
private double targetError |
存储目标误差的实数 | |
private double trainingError |
存储训练误差的实数 | |
private TrainingTypesENUM trainType |
训练神经网络的训练类型枚举值 | |
private ActivationFncENUM activationFnc |
用于训练的激活函数枚举值 | |
private ArrayList<Double> listOfMSE = new ArrayList<Double>() |
存储每次迭代的MSE的实数ArrayList |
|
方法 | ||
public NeuralNet trainNet (NeuralNet n) |
训练神经网络 | |
参数:NeuralNet 对象(未训练的神经网络) |
||
返回值:NeuralNet 对象( 训练的神经网络) |
||
public void printTrainedNetResult ( NeuralNet n) |
输出神经网络并显示其结果 | |
参数:NeuralNet 对象 |
||
返回值:None | ||
public double[][] getTrainSet() |
返回输入数据的训练集矩阵 | |
public void setTrainSet(double[][] trainSet) |
设置输入数据的训练集矩阵 | |
public double[] getRealOutputSet() |
返回输出数据的训练集向量 | |
public void setRealOutputSet(double[] realOutputSet) |
设置输出数据的训练集向量 | |
public int getMaxEpochs() |
返回神经网络将要训练的最大迭代次数 | |
public void setMaxEpochs(int maxEpochs) |
设置神经网络将要训练的最大迭代次数 | |
public double getTargetError() |
返回目标误差 | |
public void setTargetError(double targetError) |
设置目标误差 | |
public double getLearningRate() |
返回训练中使用的学习率 | |
public void setLearningRate(double learningRate) |
设置训练中使用的学习率 | |
public double getTrainingError() |
返回目标误差 | |
public void setTrainingError(double trainingError) |
设置目标误差 | |
public ActivationFncENUM getActivationFnc() |
返回训练中使用的激活函数枚举值 | |
public void setActivationFnc( ActivationFncENUM activationFnc) |
设置训练中使用的激活函数枚举值 | |
public TrainingTypesENUM getTrainType() |
返回训练中使用的训练类型枚举值 | |
public void setTrainType( TrainingTypesENUM trainType) |
设置训练中使用的训练类型枚举值 | |
public ArrayList<Double> getListOfMSE() |
返回存储每次迭代的MES误差的实数列表 | |
public void setListOfMSE( ArrayList<Double> listOfMSE) |
设置存储每次迭代的MES误差的实数列表 | |
Java实现类:NeuralNet.java文件 |
更新的类图如图2-6所示,此处省略前一章节已经解释的属性和方法。此外,还省略了新属性(setter和getter)的配置方法。
图2-6
现在,让我们来看看这些简单的神经网络架构的应用实例。
为了有助于理解感知机,让我们来分析一个基本的报警系统。它基于“逻辑与(AND)”关系。有两种传感器,它们的报警逻辑如下:
图2-7展示了基本的警报系统。
图2-7
为了对这个问题进行编码,输入如表2-2所示。0代表禁用,1代表启用。输出如表2-2所示。0代表启用,1代表禁用。表2-2简要地总结如下。
表2-2
样本 |
传感器 1 |
传感器2 |
报警 |
---|---|---|---|
1 |
0 |
0 |
0 |
2 |
0 |
1 |
0 |
3 |
1 |
0 |
0 |
4 |
1 |
1 |
1 |
基本的报警系统示意图解释了神经元和层是怎样被组织起来解决这个问题的。这是神经网络的架构,如图2-8所示。
图2-8
现在,让我们使用先前引用的类。在测试类中已经创建了两个方法:testPerceptron()
和testAdaline()
。让我们来分析第一个:
private void testPerceptron() {
NeuralNet testNet = new NeuralNet();
testNet = testNet.initNet(2, 0, 0, 1);
System.out.println("---------PERCEPTRON INIT NET---------");
testNet.printNet(testNet);
NeuralNet trainedNet = new NeuralNet();
// first column has BIAS
testNet.setTrainSet(new double[][] { { 1.0, 0.0, 0.0 },
{ 1.0, 0.0, 1.0 }, { 1.0, 1.0, 0.0 }, { 1.0, 1.0, 1.0 } });
testNet.setRealOutputSet(new double[] { 0.0, 0.0, 0.0, 1.0 });
testNet.setMaxEpochs(10);
testNet.setTargetError(0.002);
testNet.setLearningRate(1.0);
testNet.setTrainType(TrainingTypesENUM.PERCEPTRON);
testNet.setActivationFnc(ActivationFncENUM.STEP);
trainedNet = testNet.trainNet(testNet);
System.out.println();
System.out.println("---------PERCEPTRON TRAINED NET---------");
testNet.printNet(trainedNet);
System.out.println();
System.out.println("---------PERCEPTRON PRINT RESULT---------");
testNet.printTrainedNetResult(trainedNet);
}
首先,创建一个NeuralNet
对象。之后,用这个对象来初始化神经网络,这个神经网络输入层有两个神经元,隐层有一个神经元,输出层有一个神经元。接下来,一条消息和未训练的神经网络会显示在屏幕上。在那之后,testNet
对象设置训练输入数据集(第一列为偏置)、训练输出数据集、最大迭代次数、目标误差、学习率、学习类型(感知机)和激活函数(阶跃函数)。然后,调用trainNet
方法训练该神经网络。最后,打印感知机训练网络结果。这些结果被显示在图2-9所示的屏幕截图上。
图2-9
---------PERCEPTRON INIT NET---------
### INPUT LAYER ###
Neuron #1:
Input Weights:
[0.179227246819473]
Neuron #2:
Input Weights:
[0.927776315380873]
Neuron #3:
Input Weights:
[0.7639255282026901]
### OUTPUT LAYER ###
Neuron #1:
Output Weights:
[0.7352957201253741]
---------PERCEPTRON TRAINED NET---------
### INPUT LAYER ###
Neuron #1:
Input Weights:
[-2.820772753180527]
Neuron #2:
Input Weights:
[1.9277763153808731]
Neuron #3:
Input Weights:
[1.76392552820269]
### OUTPUT LAYER ###
Neuron #1:
Output Weights:
[0.7352957201253741]
---------PERCEPTRON PRINT RESULT---------
1.0 0.0 0.0 NET OUTPUT: 0.0 REAL OUTPUT: 0.0 ERROR: 0.0
1.0 0.0 1.0 NET OUTPUT: 0.0 REAL OUTPUT: 0.0 ERROR: 0.0
1.0 1.0 0.0 NET OUTPUT: 0.0 REAL OUTPUT: 0.0 ERROR: 0.0
1.0 1.0 1.0 NET OUTPUT: 1.0 REAL OUTPUT: 1.0 ERROR: 0.0
根据结果,就可以检查权值是否改变,并总结出神经网络学习如何对何时启用和禁用警报进行分类。提醒:获得的知识蕴含在这些权值里:[-2.820772753180527]
、[1.9277763153808731]
、[1.76392552820269]
。另外,用伪随机数来初始化神经元,每次代码运行,结果可能变化。
为了展示自适应线性元算法,假设城市的某一小部分有一条大街,3 条街道通向这条大街。在大街上,交通情况拥挤,发生了不少交通事故。假设政府交通部门决定开发一个预测和报警的新系统。这个系统旨在预测交通拥堵,提醒驾驶员,并采取必要的措施来减少已发生的损失,如图2-10所示。
图2-10
为了开发这个系统,我们收集了每条街道和大街近一周的信息:每条路每分钟的车流量,如表2-3所示。
表2-3
样本 |
街道A(每分钟车流量) |
街道B(每分钟车流量) |
街道C(每分钟车流量) |
大街(每分钟车流量) |
---|---|---|---|---|
1 |
0.98 |
0.94 |
0.95 |
0.80 |
2 |
0.60 |
0.60 |
0.85 |
0.59 |
3 |
0.35 |
0.15 |
0.15 |
0.23 |
4 |
0.25 |
0.30 |
0.98 |
0.45 |
5 |
0.75 |
0.85 |
0.91 |
0.74 |
6 |
0.43 |
0.57 |
0.87 |
0.63 |
7 |
0.05 |
0.06 |
0.01 |
0.10 |
接下来,解决这个问题的神经网络架构的设计方案如图2-11所示。
图2-11
接下来,我们来分析下第二个测试方案:testAdaline()
。如下:
private void testAdaline() {
NeuralNet testNet = new NeuralNet();
testNet = testNet.initNet(3, 0, 0, 1);
System.out.println("---------ADALINE INIT NET---------");
testNet.printNet(testNet);
NeuralNet trainedNet = new NeuralNet();
// first column has BIAS
testNet.setTrainSet(new double[][] { { 1.0, 0.98, 0.94, 0.95 },
{ 1.0, 0.60, 0.60, 0.85 }, { 1.0, 0.35, 0.15, 0.15 },
{ 1.0, 0.25, 0.30, 0.98 }, { 1.0, 0.75, 0.85, 0.91 },
{ 1.0, 0.43, 0.57, 0.87 }, { 1.0, 0.05, 0.06, 0.01 } });
testNet.setRealOutputSet(new double[] { 0.80, 0.59, 0.23, 0.45, 0.74, 0.63, 0.10 });
testNet.setMaxEpochs(10);
testNet.setTargetError(0.0001);
testNet.setLearningRate(0.5);
testNet.setTrainType(TrainingTypesENUM.ADALINE);
testNet.setActivationFnc(ActivationFncENUM.LINEAR);
trainedNet = new NeuralNet();
trainedNet = testNet.trainNet(testNet);
System.out.println();
System.out.println("---------ADALINE TRAINED NET---------");
testNet.printNet(trainedNet);
System.out.println();
System.out.println("---------ADALINE PRINT RESULT---------");
testNet.printTrainedNetResult(trainedNet);
System.out.println();
System.out.println("---------ADALINE MSE BY EPOCH---------");
System.out.println( Arrays.deepToString( trainedNet.getListOfMSE().
toArray() ).replace(" ", "\n") );
}
自适应线性元的测试逻辑与感知机的非常相似。不同的参数如下:输入层的3个神经元、测试数据集、输出数据集、训练类型集合如自适应线性元、激活函数激活如线性激活函数。最后,输出自适应线性元训练结果和自适应线性元均方误差,如图2-12所示。
The complete results are displayed via following code:
---------ADALINE INIT NET---------
### INPUT LAYER ###
Neuron #1:
Input Weights:
[0.39748670958336774]
Neuron #2:
Input Weights:
[0.0018141925587737973]
图2-12
Neuron #3:
Input Weights:
[0.3705005221910509]
Neuron #4:
Input Weights:
[0.20624007274978795]
### OUTPUT LAYER ###
Neuron #1:
Output Weights:
[0.16125863508860827]
---------ADALINE TRAINED NET---------
### INPUT LAYER ###
Neuron #1:
Input Weights:
[0.08239521813153253]
Neuron #2:
Input Weights:
[0.08060471820877586]
Neuron #3:
Input Weights:
[0.4793193652720801]
Neuron #4:
Input Weights:
[0.259894055603035]
### OUTPUT LAYER ###
Neuron #1:
Output Weights:
[0.16125863508860827]
---------ADALINE PRINT RESULT---------
1.0 0.98 0.94 0.95 NET OUTPUT: 0.85884 REAL OUTPUT: 0.8
ERROR: 0.05884739815477136
1.0 0.6 0.6 0.85 NET OUTPUT: 0.63925 REAL OUTPUT: 0.59
ERROR: 0.04925961548262592
1.0 0.35 0.15 0.15 NET OUTPUT: 0.22148 REAL OUTPUT: 0.23 ERROR:
-0.008511117364128656
1.0 0.25 0.3 0.98 NET OUTPUT: 0.50103 REAL OUTPUT: 0.45
ERROR: 0.05103838175632486
1.0 0.75 0.85 0.91 NET OUTPUT: 0.78677 REAL OUTPUT: 0.74
ERROR: 0.046773807868144446
1.0 0.43 0.57 0.87 NET OUTPUT: 0.61637 REAL OUTPUT: 0.63
ERROR: -0.013624886458967755
1.0 0.05 0.06 0.01 NET OUTPUT: 0.11778 REAL OUTPUT: 0.1 ERROR:
0.017783556514326462
---------ADALINE MSE BY EPOCH---------
[0.04647154331286084,
0.018478851884998992,
0.008340477769290564,
0.004405551259806042,
0.0027480838150394362,
0.0019914963464723553,
0.0016222114177244264,
0.00143318844904685,
0.0013337070214879325,
0.001280852868781586]
根据上述的结果,可以得出结论:神经网络在一个特定区域学习了预测交通拥堵。这可以通过改变权值和均方误差(MSE)列表来证明。图2-13是使用均方误差(MSE)数据进行的数据可视化,很容易发现当迭代次数升高,均方误差(MSE)则下降。
图2-13
本章向读者介绍了神经网络的整个学习过程。我们展现了学习的基础,它的灵感来自于人类自身的学习。为了用实践来说明这个过程,我们用Java实现了两个学习算法并将其应用到两个实例中。通过这些,读者可以对神经网络如何学习有一个虽然基础但是有用的理解,甚至可以系统地描述这个学习过程。这是阅读下一章的基础,下一章将会展示更多复杂的例子。