从AI模型到智能机器人:基于 Python 与 TensorFlow
从AI模型到智能机器人:基于 Python 与 TensorFlow
版权所有,侵权必究。
图书在版编目(CIP)数据
责任编辑:刘 伟
印 刷:北京季蜂印刷有限公司
装 订:北京季蜂印刷有限公司
出版发行:电子工业出版社
北京市海淀区万寿路 173 信箱 邮编:100036
开 本:720×1000 1/16 印张:18.5 字数:326 千字
版 次:2019 年 9 月第 1 版
印 次:2019 年 9 月第 1 次印刷
定 价:79.00 元
凡所购买电子工业出版社图书有缺损问题,请向购买书店调换。若书店售缺,请与本社发
行部联系,联系及邮购电话:(010)88254888,88258888。
质量投诉请发邮件至 [email protected],盗版侵权举报请发邮件至 [email protected]。
本书咨询联系方式:(010)51260888-819,[email protected]。
前 言
随着 AI(Artificial Intelligence,人工智能)技术及应用环境的不断革新,
其应用范围也随之扩大。Python 以其独特的兼容性,成为最受欢迎的编程语言
之一,同时,也成为众多编程爱好者入门的首选语言。Python 开发者要具备面
向对象(Object-Oriented)的思维和 AI 基础,这是非常有必要的。
写作初衷与图书特色
本书由中国台湾(下称台湾)知名的 IT 人士高焕堂先生所著。
高先生在进行 AI 技术培训的过程中,发现很多用户对利用 Python 和
TensorFlow 平台进行 AI 开发并不熟练,这其中包括华为、百度、腾讯(成都)
等国内知名科技公司的部分高级设计师和架构师。因此,他在授课答疑后,根
据大多数初级、中级用户的学习水平,倾注心血来编写此书,为大多数未能现
场听讲的读者普及 AI 技术知识。
本书主要特色如下。
理论完备:讲解了从 AI 思维简史到 Python、TensorFlow 平台的开发
流程与应用,如利用 Python 编写 AI 机器人进行机器学习训练、利用
TensorFlow 进行更深度的机器学习训练,以及利用神经网络训练模型
提高图片识别率等内容,全书内容详尽,理论完备。
浅显易懂:以 AI 基础技术理论为框架,以生活中常见的案例和浅显
易懂的语言来讲解,在逐一细化程序编写方法的同时,力求可操作性,
便于入门读者快速上手。
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
本书主要内容
作者简介
编者
2019 年 8 月
·IV·
目 录
·VI·
目 录
·VII·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
·VIII·
目 录
·IX·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
·X·
第1章
1
AI 与面向对象 Python
1.1 AI 思维简史
1.5 面向对象(Object-Oriented)入门
1.6 软件中的对象(Object)
1.7 对象与变量(Variable)
1.8 对象与函数(Function)
1.9 自然界的分类
1.10 软件的分类
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.1 AI思维简史
从 20 世纪 50 年代开始,许多专家就希望将人类的知识和思维逻辑植入到
机器(如计算机)里,让机器像人一样思考。当时就使用符号和逻辑来表示思
考(Thinking)和表现出智能(Intelligence)性,人类努力向机器输入符号化
的“思想”,并期望机器能够展现出像人一样的思考能力,然而这个期望并没
有成功。
后来,专家们另寻他途,转而采用 Rosenblatt 在 1957 年提出的“感知器”
(Perceptron)程序,使用重入函数设计的程序“训练”各种逻辑公式,实现初
步的机器“学习”,这称为“连结主义”
(Connectionism),创建了“神经网络”
(Neural Networks)这个名词。这个途径并不是向机器输入符号化的知识和逻
辑来让机器展现出像人一样的思考,而是尽量让计算机表现得有智能,但人们
并不关心机器是否真的“表现”出思考的逻辑。
AlphaGo 就是这项新途径的代表。2016 年,AlphaGo 在围棋比赛上击败了
人类的世界冠军。AlphaGo 的棋艺(智能)是建立在人类已有的经验和知识之
上,基于人类大量的历史棋谱,迅速学习和领悟人类的棋艺,从而进行自我训
练、不断升级后战胜了人类。到了 2017 年,DeepMind 团队的新一代人工智能
AlphaGo Zero,基于不同的学习途径,没有参考人类的经验知识,也没有依赖
人类历史棋谱的指导,完全从新开始自我学习,无师自通,其棋艺竟然远远超
过 AlphaGo,而且百战百胜,以 100∶0 的佳绩完胜它的前辈 AlphaGo。
1.2 Python语言与AI
·2·
第 1 章 AI 与面向对象 Python
习的链接库(Library),使得当今大部分 AI 深度学习框架都支持它,这让它成
为 AI 时代的主流计算机语言之一。
1.3 布置Python开发环境
图 1-1
图 1-2
·3·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 1-3
图 1-4
·4·
第 1 章 AI 与面向对象 Python
图 1-5
图 1-6
图 1-7
图 1-8
·5·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.4 开始编写Python程序
图 1-9
接下来,开始编写程序,首先新建一个文件。选择“File>New File”菜单,
如图 1-10 所示。
弹出一个新窗口,如图 1-11 所示。
图 1-10 图 1-11
·6·
第 1 章 AI 与面向对象 Python
图 1-12 图 1-13
图 1-14
图 1-15
·7·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 1-16
图 1-17
图 1-18
·8·
第 1 章 AI 与面向对象 Python
图 1-19
单击“DOWNLOAD”按钮,即可下载安装。安装完成后,编写 Python 代
码并运行,如图 1-20 所示。
图 1-20
除 PyCharm 外,用户还可以根据需要挑选适合自己的开发环境。
·9·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.5 面向对象(Object-Oriented)入门
1.5.1 对象(Object)
自然界中有各式各样的东西,如阳光、田野、动物等。随着阅历的增长,
人们对自然界的东西也认识越多。对个人而言,所认识的东西,都可以称为对
象(Object)。如李白心中最清楚的对象是他的诗,每一首诗都是一个对象。人
一旦认识某一样东西,一般就能说出其特点,并可以与其他对象进行比较,常
见的特点如下:
对象的属性(Attribute)。
对象的行为(Behavior)。
如玫瑰花的属性是:有刺、红色,代表爱慕等;其行为是:含苞待放、盛
开和散发爱意等。鸟儿的属性是:有翅膀、尾巴;其行为是:唱歌、会飞等。
了解一个东西的属性和行为,就表示对该东西有了认识和概念(Concepts)。
尽管有些东西并不存在,但只要对其有概念,就是对象,如古代神话中的龙、
凤凰、月中白兔、嫦娥等都是我们熟悉的对象。但对于没有听过嫦娥奔月故事
的外国人来说,嫦娥并不是对象。
1.5.2 消息(Message)
自然界的对象常互相沟通、交互,才产生多姿多彩的大自然景色。例如,
大家熟悉的诗句:
泪眼问花花不语,乱红飞过秋千去。
其对象包括女主角、花和秋千,女主角与花的沟通方式是“问”和“语”,
女主角和秋千的交互作用“荡”,花和秋千的交互作用“飞过”,无论“沟通”
或“交互作用”都表示它们在互相传递消息(Message)。女主角心中难过,传
递消息给花,哪知花儿不知如何回答,此时花儿传回消息给女主角,令女主角
更加伤感。
1.5.3 事件(Event)
有些对象的内部状态(State)容易受外来刺激而变化,如上节的女主角因
·10·
第 1 章 AI 与面向对象 Python
爱人远离而变得伤感,甚至流泪。当对象的状态改变(State Change),就表示
某“事件”(Event)发生了。如灯泡里的钨丝烧坏了,于是“灯泡烧掉”事件
发生了。一件事件的发生,常引发另一事件的发生。如红绿灯坏了,使十字路
口的汽车乱成一团,汽车也更容易互相碰到,甚至会引发一连串的事件。小到
细胞的分裂繁殖,大到地球上刮台风,都是大家所熟悉的事件。
台风吹倒大树,大树压到汽车,汽车撞到红绿灯等,这是生活中常见的现
象。春节到了,人们排队买火车票,坐火车回家过年,到银行取钱,给晚辈发
红包等,这是社会中常见的现象。这一连串的事件,都在互相影响。在“面向
(Object-Oriented Programming,简称 OOP)观念中,事件所涉及的
对象编程”
东西是对象,对象的内部状态变化是事件。像台风、树、汽车、红绿灯都是对
象。台风风速及方向的变化是事件,树干禁不起风的吹袭而产生变化是事件,
汽车被压而失去控制是事件,红绿灯坏了也是事件。总之,对象内部的变化,
产生事件,事件再触发其他对象的变化,引发其他事件往复循环。
事件,即对象内部状态的变化,如何影响别的对象呢?很简单:个体因内
部变化而促发对象的特殊行为(Behavior),对象的行为再激发其他对象内部的
变化,即触发别的事件影响其他对象。
“吹袭”是台风的行为,
“倒下”是大树
的行为,
“失控”是汽车的行为,
“不亮了”是红绿灯的行为。风的狂吹,是台
风对象的行为,促使大树枝干的断裂;倒下,是树的行为。树的行为“倒下”
促使汽车状态变化,产生失控行为,这行为促使红绿灯变化,而导致“不亮”
的行为,使得交通混乱。
1.6 软件中的对象(Object)
1.6.1 抽象的目的
牛津词典对“抽象”(Abstraction)的定义如下:“人们脑海中对重点与细
节的区分行动(The Act of Separating in Thought)。”抽象的主要目的有:
掌握重点(Essense),避免被复杂的细节(Detail)所迷惑。例如,准备
高考令考生千头万绪,“重点复习”令其事半功倍。
求同存异,找出对象之间的共同特性。例如,大象和鲸鱼有区别也有相同之
处,若不计较其不相同之处,可发现共同点:活的,因此属于同种类:生物。
·11·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.6.2 抽象表示
软件里的对象是自然界对象的抽象表示(Abstract Representation),即软件
内的对象逼真地表达了自然界的实际景象,但也仅表达重要的景象。因此,人
们心中构思的软件和眼中所见到的世界是一致的。软件是自然界实景的抽象表
示,其能简单明了地帮助人们了解和掌握真实景象。例如,航天中心借助软件
仿真与控制宇宙飞船的航行。所以,软件的目的是为真实事物建立抽象模式或
模型(Model the Reality)。
1.6.3 数据和函数
软件的对象是由数据(Data)和函数(Function)一起组成的。
数据表达自然界对象的属性,函数表达自然界对象的行为。因此,软件的
对象能抽象地表达自然界的对象,软件能逼真地表达自然界的真实情景。例如,
为了描述“泪眼问花花不语,乱红飞过秋千去”,软件中应该有 3 个对象—女
主角、花和秋千,如表 1-1 所示。
表 1-1 软件中的对象和数据、函数关系
对象 数据 函数
女主角 表达女主角的外表属性、内心状态等 表达“流泪”和“问”等行为
花 表达“花名”“颜色”等 表达“语”和“飞”等行为
秋千 表达秋千特性 表达“摆荡”的行为
因此,数据描述对象的静态特性:花是红色的;函数表达对象的动态特性:
人在流泪、花在飞舞等。
1.6.4 历史的足迹
传统上,数据与函数分而治之。“函数”代表计算机的动作,其动作的目
的是“处理”数据,如图 1-21 所示。
·12·
第 1 章 AI 与面向对象 Python
1.7 对象与变量(Variable)
1.7.1 数据类型
数据类型(Data Type)就是数据的种类。Python 有 3 种最常用的基本数据:
字符串、实数和整数。如花有 3 种属性。
Name:"Rose"。
Price:12.55 元。
Month:6 个月。
其中,"Rose"是字符串类型的数据、12.55 为实数(又称为浮点数)类型
的数据、6 为整数类型的数据,以 Python 程序表达如图 1-22 所示。
图 1-22
·13·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
每一种特性分别描述,而这违反了人们自然的“抽象”能力。使得人们心中装
满复杂的细节,无法提纲挈领,专注于相应的重点。
人们盼望计算机具有抽象的能力,能欣赏人们送玫瑰花时的心意!现在,
OOP 观念已改变了计算机的“个性”,它已能够接受数据的深刻含义了。在 OOP
观念中,上面的 Python 程序表达如下:
flower.Name = "Rose"
flower.Price = 12.55
flower.Month = 6
flower.Print
该程序有两个特点:
创造了新数据类型—“花”。flower 是“花”的变量,就像 Price 是实
数(又称浮点数)的变量一样。只是 flower 内含了 3 个小变量—Name、
Price 和 Month。同样,也可以创造“树”“山”“鸟”等新数据类型,
来描述自然界对象。“花”内包含 3 项基本数据类型,依此类推,也可
创造“公园”数据类型,其内含有“小山”“树木”“鸟”等数据类型。
因此,在 OOP 观念中,用户可以创造数据类型来描述自然界或心中任
意所想的对象。
提升沟通层次—本质上,用户编写程序时,是在与计算机对话;这儿
还可以升级为与 flower 对话。如使用命令:
flower.Print
1.7.2 变量即对象
在 OOP 观念中,变量就是对象,如图 1-23 所示。
图 1-23
·14·
第 1 章 AI 与面向对象 Python
其中 Name 是变量(Variable),内含字符串"Rose"。以传统眼光来看,这
“请计算机将"Rose"字符串存入 Name 变量中。”若以 OOP 眼
个命令的意义为:
光来看,其意义为:
“Name,这个"Rose"字符串给用户。”由原来与计算机的对
话,转变为与 Name 对象(即变量)对话,其中的“对话”是:
“这个"Rose"字符串给用户。”
就如同,妈妈对小珠说:
“这美丽的耳环给你。”我们称这对话的内容为“消
息”(Message),表述如下。
小珠是对象,妈妈将消息—“给耳环”送给小珠对象,小珠收到此信息,
欣然同意。同样,上述的 Python 命令,可解释如下:
“=”与“给”的角色相近,都代表一项行动(要求),即表达外界送消息
的目的。“"Rose"”与“耳环”的角色相近,都代表行动的参数,是外界送来
的数据。因此,消息常包含两项成分。
行动要求:如“=”表达了“存入”“给予”或“复制”的动作。
参数:如“"Rose"”表达了传来的数据。
消息的作用:刺激对象,令其改变内部状态。消息的目的:要求对象提供
服务。例如,在火车站将钱币投入售票机时,对售票机个体而言,用户的投币
或按键都可以认为是消息的到来,消息会改变售票机的内部状态—金额逐渐
增加。投足钱币时,售票机提供服务—送出火车票。
同样,小珠接获妈妈的消息时,也有所变化—内心喜悦且耳朵上多了耳
环,变得更漂亮。妈妈心中的要求也许是:
“在宴会中让妈妈有面子。”这是小
珠的服务。Name 对象接到消息时,其内部会有所变化—Name 内部的值变为
"Rose";其服务是“保存"Rose"字符串”。
·15·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 1-24
此时 Color 对象的内容为“"Red"”字符串。
Step 3:Color 对象接收另一个消息—“+"Rose"”类,同时送出信息给
Name 对象。
·16·
第 1 章 AI 与面向对象 Python
图 1-25
1.8 对象与函数(Function)
1.8.1 函数的角色
传统的程序直接由函数或子程序组成,OOP 软件则将函数纳入对象中,再
由对象组成庞大的程序。函数隶属于对象,与对象的数据密切联系在一起。
软件的建造理念和高楼大厦的建造观念一致,函数的角色如下:
从对象本身来看,函数表达了对象的动态行为。
从整个系统来看,函数支持中层组件—是“对象”的栋梁。
在“泪眼问花花不语,乱红飞过秋千去”的例子中,女主角的行为:“流
泪”(Cry)及“问”(Ask),花的行为有:“语”(Say)和“飞”(Fly),秋千
的行为有:“摆荡”(Swing)。以对象来组织这些函数如表 1-2 所示。
表 1-2 对象与行为
对象 行为
Cry()
女主角
Ask()
Say()
花
Fly()
秋千 Swing()
·17·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.8.2 事件驱动观念
常见的程序是主动式,如图 1-26 所示为一个 Python 程序。
图 1-26
在传统观念中,程序员决定程序的运行顺序和过程。而用户(User)只能
按照计算机的命令逐步做事,无权左右计算机的运行过程。计算机像主人,用
户像客人,
“客随主便”,主人安排客人的一切活动,让客人如处异乡,无宾至
如归的感觉。
反之,在 OOP 观念中,则采纳“主随客便”方式,创造宾至如归的感觉。
30 多 年 来 , 一 直 居 于 主 流 的 Windows 程 序 就 是 典 型 的 “ 事 件 驱 动 ”
(Event-Driven)软件,即是“主随客便”的软件。屏幕上的窗口(Window)
就像家中的客房,有各式各样的摆设与茶点,任客挑选。主人(计算机)并不
指挥客人应该做些什么,客人口渴可以先喝汽水,饿了可以吃苹果派等,其过
程和顺序由客人决定,计算机随时静候,“端”出客人所点的东西。
计算机如何创造友善的环境呢?OOP 是幕后功臣。屏幕上的东西都是对
象,当把鼠标(Mouse)的光标(Cursor)移到某对象上单击时,表示做了决
定(某事件发生),并通过按键传达消息给该对象,于是对象再启动(调用)
其内含的函数做相应的服务。所以,平时函数并不主动指示使用者,而是等待
用户传达的消息,其消息常因为外界事件而发生。
上述情形,称为“事件驱动”或“消息驱动”(Message Driven)。所以在
OOP 观念中,函数的任务:运行对象对消息的反应过程,即表达对象的行为。
函数处于被动位置,只有收到消息,受外界刺激时,对象才会呼叫函数对消息
做出反应。
目前大部分软件是事件驱动的,而写这类软件时,就需要应用 OOP 的观
念和方法。
·18·
第 1 章 AI 与面向对象 Python
1.9 自然界的分类
1.9.1 分类与抽象
自然界的东西分为许多种类(Class),人们将类似的东西归为一类。所谓
类似,就是在重要特性上相同,但不重要的特性有些区别。如“好人”表示拥
有善良的心的一群人,不论男、女、老、幼(细节),只要有善良的心(重点)
都归为“好人”一类。因此,类就是一个集合(Set),其内的元素(对象)具
有共同的重要特性,但细节不同。和类(Class)内的对象一样,重点相同,细
节不同。
在生物分类上,有动物、植物之分。狗、猫有共同特性—会到处跑,归
入动物类。相思树不会到处跑,其重点与狗、猫不同,所以归入另外一类。至
于什么是重点,依个人的兴趣而划分,大部分是一个人的主观看法。例如,生
物学家不可能把“竹马”归入“马”这个类别,因为它不是活的;但对艺术欣
赏者而言,竹马和真实的马可能同类,因为造型相同。
因此,人们对自然界的东西分门别类时,就在进行“抽象”动作,把东西
的重要属性抽离出来进行比较,若相同则归入一类中。分类的目的是让自己更
容易认识,以及掌握自然界的事物。例如,当有人告诉大家“阿鸿是坏蛋”时,
你立即对阿鸿有些认识,进而小心与他交往;反之如果阿鸿获得好人好事奖,
你可能会尊敬他。
1.9.2 对象与类
类是群体(或集合),而对象是类中的一分子。人们常用“一个”来表达
对象与类之间的关系。例如:月亮是一个星球、上海是一个美丽的大城市、毕
加索是一个画家、张大千是一个画家和贝多芬是一个音乐家等。
所以“月球”是对象,属于“星球”类的一分子。毕加索是对象,艺术家
是类,画家同样也是类,其中画家是艺术家群体中的小群体(部分集合)。毕
加索和张大千同属于“画家”类,所以具有共同特点—精于美术绘画。
·19·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1.9.3 类的体系
同类对象有共同的特性,若利用“抽象”观念进一步将这些共同特性细分
为重点与细节,会发现两类之间也有共同特性。例如:画家与音乐家是两个类,
画家有两个重点特性:有艺术天分,从事艺术创作;精于绘画。凡是画家都具
有这两项共同特性。
音乐家也有两个重点特性:有艺术天分,从事艺术创作;精于音乐。凡是
音乐家都具有这两项共同特性。
于是发现画家和音乐家具有一项共同特性—有艺术天分,从事艺术创
作。于是人们创造一个新类—艺术家,凡具有这项特性者都可归于此类。此
时,人们常用“是一种”来表达这种类之间的关系。例如:
画家是一种艺术家。
音乐家是一种艺术家。
如图 1-27 所示。
这其中呈现出有趣的现象—凡是画
家都为艺术家;凡是音乐家都为艺术家,
即凡是“画家”类的对象,必为“艺术家”
类的对象;凡是“音乐家”类的对象,必
为“艺术家”类的对象。例如,天才郎朗
是一个音乐家,他必然是个艺术家。
刚才是忽略掉画家和音乐家不相同的
图 1-27
特性,而得到更一般性的类—艺术家。
反过来,人们也常增加一些比较特殊的特性,例如:
有艺术天分,从事艺术创作。
精于音乐。
擅长钢琴。
同时具有这 3 项特性者,称为钢琴家。同样,可增加如下特性:
有艺术天分,从事艺术创作。
精于音乐。
擅长小提琴。
于是发现较特殊的类“小提琴家”,如图 1-30 所示。
·20·
第 1 章 AI 与面向对象 Python
图 1-28
这就构成一个类的体系(Class Hierarchy)。于是,可以这么说,吕思清是
一个小提琴家,也是一个音乐家,也是一个艺术家。人们这种习惯的分类与组
织方式,是 OOP 的重要方法。
1.10 软件的分类
1.10.1 类是数据类型
大家常说,3 是一个整数,这句话说明 3 是对象,而整数(Integer)是类
的意思。若用计算机的术语,就相当于 3 是一项数据,其类型是“整数”。当
我们说,Python 提供了字符串、整数及浮点数(即实数)这 3 种数据类型,即
Python 定义了 3 个类—字符串(String)、整数(Integer)及浮点数(Floating
Point),各代表一个群体。例如:
“Beer”是一个字符串:“Beer”是字符串类的对象。
888 是一个整数:888 是整数类的对象。
-25.25 是一个浮点数:-25.25 是浮点数类的对象。
在 OOP 语言(如 Python)中,用户能无限地创造新类,并完整地表达自
然界的各种对象。此外,在 Python 语言中,数据分为常数(Constant)与变量
(Variable),如图 1-29 所示。
·21·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 1-29
1.10.2 类的用途:描述对象的属性与行为
软件的对象为自然界对象的抽象表示,只表达其重要属性与行为,而忽略
细节部分。至于哪些是重要的属性和行为呢?用户在程序中必须加以说明。同
类的对象具有共同的重要属性与行为,因此可统一说明个体应表达哪些属性和
行为。也就是说,类统一说明了对象应包含哪些“数据”(Data)和哪些“函
数”(Function),如图 1-30 所示。
图 1-30
Python 已定义的“实数”类,其说明了“实数”的对象都含有+、-、*、/ 等
运算(行为),凡实数的对象都能做这些运算。如字符串类,其对象的共同行
为不包括 / 、 ^ 运算,所以如图 1-31 所示中的程序是错误的。
图 1-31
·22·
第 1 章 AI 与面向对象 Python
图 1-32
“/”运算并非字符串类内对象的共同行为,所以 a 无法接收消息—“/3”
类。同理,如果创造了新的类—“花(Flower)”,且其定义如图 1-33 所示。
图 1-33
这就是“花”类的定义,它说明如下内容。
“花”类内的对象都具有两项共同属性:name$、age$。
“花”类的对象都具有两项共同行为:Cry()、Say()。
同类的对象其属性和行为是一致的,所以只需在类定义中统一说明即可,
不用逐一说明。于是,能借“花”类来定义对象,如:
“花 rose”,此时,rose
对象如下所示。
·23·
第2章
2
Python 的对象与类
2.1 OOP 入门
2.2 对象的概念
2.3 对象分类与组合
2.5 对象行为与接口
第2章 Python 的对象与类
2.1 OOP入门
花开花谢和枫叶飘零等是自然界对象的常见行为。对象之间进行交互后,
形成了多姿多彩的大自然。软件的对象是自然界对象的抽象表示,软件逼真地
表达了自然界的实际景象,于是人们心中构思的软件和眼中所见到的世界是一
致的。
在现在的 OOP 观念中,软件开发者编写程序时,对象成为开发者脑海里
的主角。编程的核心工作在于描述对象、组织对象、安排对象间的沟通(传递
消息)方式。就如同“人”是社会中的主要对象,社会是有组织的人群,人们
之间会互相沟通、协调一样。
由于软件中的对象观念和实际社会中的对象观念一致,所以 OOP 观念使
软件与真实世界间的界限变得模糊,这也是 OOP 观念的重要特点。例如,传
统软件的核心观念—函数(Function),在人们的通常印象中,只是数学里的
概念而已。而在 OOP 观念中,实际社会的对象,如人、汽车、教室等都是软
件设计师脑海中的对象,也是程序中的对象。因此,不论是老板还是程序员,
他们脑海中都充满了对象的影子,而这些对象都是人们生活中真实的东西、物
体或大家耳熟能详的概念,使得软件的用户和设计者有共同的感觉,这不但提
升了用户体验,也让设计师更了解用户的需要。
2.2 对象的概念
当用户着手设计一个系统或程序时,第一个出现在脑海中的问题:对象那
么多,哪些跟系统或程序有关呢?例如设计一个销售系统,“顾客”是一个重
要对象,产品及订单也是重要对象;而原料的产地及供货商虽然是明显的对象,
但不一定与销售系统有关。反过来,若用户所设计的是采购或生产系统,原料、
产地及供货商就成为重要对象。在寻找对象的过程中,也会让用户对所要设计
的系统有更清晰的认识。
下面介绍寻找对象的实用方法。最常见的是从有关文件着手,在文件里会
发现以下线索,进而再找出对象。
(1)人(People)—人是系统中的重要角色,通常也是最容易找到的对
·25·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
象。例如公司有 5 位销售员,各负责一个地区的任务,并与该地区的顾客联系。
从这段叙述中,就可发现两种对象:销售员及顾客,每一位销售员都是对象;
每一个顾客也是对象。
(2)地点(Sites)—地点是很容易发现的对象。例如用户从订单上可以
看到产品将送达的目的地、顾客的所在地。以旅行社的行程为例,各旅行团在
不同的观光地点停留,各观光地点都是对象。
(3)事物(Things)—在可摸到或看到的事物中,很容易找到与系统有
关的对象,如产品是销售系统及生产系统的明显对象,原料项目是生产及库存
系统的重要对象。就旅馆管理系统而言,
“房间”是重要对象,
“书本”及“杂
志”为图书馆或书店管理系统的明显对象。
(4)事件(Events)—企业界最常见的事件是“交易”,当事件发生时,
我们会去记录发生的时间、金额等。值得注意的是,这些事件是已经发生的,
是一项行为或动作,所以在文件中,常常是一个句子的动词。如今天共有 3 种
原料已降至安全余量以下,所以共订购 3 种原料,这每一“订购”(Ordering)
事件都是对象。就飞机场的控制系统而言,每次飞机“起飞”或“降落”都是
重要对象。
(5)概念(Concepts)—与企业营运或机构管理有关的“构想”或各种
“计划”或其他观念;这些无形但决定企业活动的构想,常常是重要对象。如
公司正拟定 3 种广告策略,其中每一个策略就是企业营销系统的重要对象。如
公司正通过两种管道与小区居民沟通,管道也是抽象的对象。
(6)外部系统或设备(External Systems or Devices)—软件系统会与其
他系统交换信息。有时也由外部设备取得数据或把处理结果送往外部设备。这
些外部系统或设备也是对象。如库存系统与采购系统会互相沟通,对库存系统而
言,采购系统是对象;对采购系统而言,库存系统则为对象。如股票系统直接把
数据传送到交易市场的电视显示屏上,对股票系统而言,电视显示屏是对象。
(7)组织单位(Organization Units)—企业机构的部门或单位。如在学
校管理系统中,教务处及训导处等单位都是对象。
(8)结构(Structures)—有些对象会包含其他对象,所以在对象中常
能找到其他对象。如在学校的组织单位—教务处里面,含有小对象如注册组
及学籍组等。在汽车对象中可找到引擎、轮胎及座椅等对象。在“房屋”对象
中,会发现厨房、客厅、沙发等对象。
以上介绍的是常用的寻找对象方法,学会寻找对象后,要将对象分别归类,
·26·
第2章 Python 的对象与类
并了解类与类之间的关系,以便把它们组织起来。如在公司的人事结构中,可
发现人因扮演角色的不同而分为不同种类的对象,如销售员、司机、经理等。
汽车可分为跑车、公共汽车、旅行车等不同种类的对象。如何分类
(Classification),是 OOP 的重要观念。
2.3 对象分类与组合
2.3.1 类的永恒性
俗话说:物以类聚。
“物”和“类”说明对象与其所属“类”
(Class)的关
系,相似的对象常归为一类。如一个人是对象,人类是由个人所构成的类。
“狗”
这种动物是类,哈巴狗是对象。当用户获知公司有 A、B 两个销售员时,可得
知 A、B 两者都为对象;同时,联想到“销售员”(Salesman)是类,而 A、B
都是此类中的对象。
由于类比对象更具有永恒性,在设计软件的过程中,当用户找到对象时,
也必须掌握此对象的类,这样软件自然会更具有永恒性,即软件的寿命会更长。
在学校里,King 老师会换工作而离开学校,但“老师”类永远存在。因此,对
象及其所属的群体—类,都是 OOP 的核心观念。善于利用类来将一群对象
归类并组织起来,是面向对象编程的重要技术。在设计软件时,通常先决定有
关的类,并且弄清楚类之间的关系。下面介绍两种最常见的类关系:“父子关
系”和“整体/部分关系”。
2.3.2 将对象分门别类
人们从小就学习将东西分类,如分为“生物”及“无生物”,其中生物又
分为“动物”及“植物”等。无论动物、植物或生物都为类(Class)。动物是
一类(a kind of)生物,植物也是一类生物。此时,即称动物是生物的子类
(Subclass),植物也是生物的子类,而生物是动物及植物的父类(Superclass)。
这种父子类关系是软件中组织相关对象的重要方法。如汽车、马车、自行车都
为一种车。所以,车是父类,而汽车、马车及自行车都为车的子类。
设计软件时,当用户知道公司今天生产 5 辆公共汽车及 5 辆轿车时,用户
已找到 10 个对象了,其中每一辆车都为对象。它们分别属于不同的类—公
·27·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
共汽车及轿车;然而,因公共汽车及轿车都是汽车,所以找到更大的类—汽
车。利用父类—汽车把两个子类—公共汽车及轿车组织起来,其关系如
图 2-1 所示。
在我们设计的软件中,将包含 3 个类。日常生活中,父子关系是很常见的
类关系,通过这种关系,也很容易决定与软件有关的类。如一家公司正在生产
3 类鞋子—网球鞋、篮球鞋及慢跑鞋。此时我们已找到了 3 个种类—网球
鞋、篮球鞋及慢跑鞋。由于网球鞋及篮球鞋都为一类球鞋,进而找到父类—
球鞋。球鞋及慢跑鞋都为一类鞋子,所以又找到了它们的父类—鞋子。因此,
关系如图 2-2 所示。
鞋子
慢跑鞋 球鞋
网球鞋 篮球鞋
图 2-1 图 2-2
如果为这家公司设计软件,这 5 个类是软件中的重要类,同时这种父子类
关系,正是软件用来组织有关对象(鞋子)的好方法。在软件设计者的脑海中,
对象的组织方法和一般管理者脑海中的分类方法一致,这能提供软件的适用
性,从而,更好地满足用户提高软件的价值。
2.3.3 对象的组合关系
前面说过,对象常包含其他对象,从对象的结构(Structure)中能找到其
他对象。如一辆汽车含一个引擎及 4 个轮胎,如图 2-3 所示。
从汽车结构中发现两个类—引擎及轮胎,其类关系如图 2-4 所示。
·28·
第2章 Python 的对象与类
一辆汽车 汽车
一个引擎
引擎 轮胎
4 个轮胎
图 2-3 图 2-4
引擎是汽车的一部分,轮胎也是汽车的一部分,所以汽车是“整体”
(Whole),而引擎及轮胎是“部分”(Part)。
在实际的产品结构中,常见整体/部分关系。如图书含封面、目录及内容
等;计算机含屏幕、键盘、主存储器及磁盘驱动器等。在软件设计时,也常按
照这种结构组织类和对象。在程序中,可定义汽车、引擎及轮胎 3 个种类。
需要注意的是,上述关系中,其整体与部分间有共生的密切关系。如一个
灯泡破了或烧坏了,通常整个灯泡,包括其内部的灯帽、灯芯、玻璃球都会被
丢弃。于是,在软件系统中,这些部分对象(如灯芯)都会随着整体对象(如
灯泡)的消失而消失。反过来,司机对汽车而言是不可或缺的,但没有人认为
司机是汽车的组件,因为即使一辆汽车报废,司机还存在。然而,司机仍是汽
车的一部分,因为在空间上,汽车对象中包含司机对象。因此,汽车与司机之
间仍是整体/部分关系,如图 2-5 所示。
下面这种关系也很常见,如笔芯是自动铅笔的一部分,但笔芯并不与铅笔
共生共灭。同样,电池是手电筒的一部分,但两种对象并非共生共灭。这种整体/
部分的关系,让用户很容易找出相关的对象和类,同时也能利用这种关系把软件中
的对象组织起来,如救灾集装箱包括了药品和衣服两个对象,如图 2-6 所示。
汽车 救灾集装箱
图 2-5 图 2-6
·29·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
2.4 AKO抽象关系
OOP 的观念很大程度上降低了计算机软件的复杂程度,让人们更容易发展
及维护相应的软件。OOP 的两个最基本观念是:类(Class)及对象(Object)。
俗话说“物以类聚”,意味着类似的物品常常放在一堆,这一堆就是“类”,而
其组成元素就是“对象”。按照传统的程序写法,主要的编程工作在于设计命
令、叙述及函数等;在 OOP 的新观念中,写程序的主要工作在于设计类。即
设计各式各样的类后,就能使用类的对象,从而产生有价值的信息。
如对于树林中的树,想记录其 3 种属性(Attribute)—品种(Variety)、
年龄(Age)及高度(Height),就定义 Tree 类,其程序代码如下。
#
#Ex02-01
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
这儿告诉计算机,对一棵树,将记录 3 种属性:品种、年龄及高度。如有
一棵树,其属性如下。
品种:peach。
年龄:8 年。
高度:2.1 米。
这是树林中的一棵树,在计算机的 Tree 类中,就必须有一个“对象”和它
对应并存储它的属性。至于如何产生 Tree 的对象呢?其命令如下:
x = Tree("peach", 8, 2.1)
#Ex02-02
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
#---------------------------------
x = Tree("Rose", 2, 3.5)
print(x.variety, x.age, x.height)
果树 竹子
图 2-8 图 2-9
此为实物上的类关系。程序中的类关系必须和实物上的情况相呼应,程序
中的类定义格式如下。
class Tree:
品种。
年龄。
高度。
class FruitTree(Tree):
·31·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
成熟月份。
价格。
class Bamboo(Tree):
用途。
其 FruitTree 及 Bamboo 是 Tree 的子类;即 Tree 是 FruitTree 及 Bamboo 的
父类。在程序中,关系表示如下。
#Ex02-03
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
class FruitTree(Tree):
def __init__ (self, v, a, h, m, p):
self.variety = v
self.age = a
self.height = h
self.month = m
self.price = p
pass
class Bamboo(Tree):
def __init__ (self, v, a, h, u):
self.variety = v
self.age = a
self.height = h
self.usage = u
pass
FruitTree 类中含有两项新属性—“成熟月份”及“价格”,这等于告诉
计算机:对于果树,必须多存储两项数据。这两项数据是果树才有的,竹子就
没有。Bamboo 类中含有一项新属性,这等于告诉计算机:对于竹子,必须多
·32·
第2章 Python 的对象与类
存储一项数据—“用途”。这是竹子才有的数据,果树没有。如有一棵果树,
其数据如下。
品种:peach。
年龄:8 年。
高度:2.1 米。
成熟月份:3 月。
价格:20 元。
#Ex02-04
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
class FruitTree(Tree):
def __init__ (self, v, a, h, m, p):
self.variety = v
self.age = a
self.height = h
self.month = m
self.price = p
pass
·33·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
a:FruitTree
品种:peach
年龄:8
高度:2.1
成熟月份:3
价格:20
图 2-10 图 2-11
Tree
品种
年龄
高度
a:FruitTree
品种:peach
年龄:8
FruitTree Bamboo
高度:2.1
成熟月份:3 成熟月份 用途
价格:20 价格
图 2-12
同理,如果有一棵竹子,其数据为:
品种:green。
年龄:2 年。
高度:10.0 米。
用途:chopstick。
在计算机中,必须定义 Bamboo 类的对象来存储这些数据,如图 2-13 所示。
·34·
第2章 Python 的对象与类
Tree
品种
年龄
高度
b:Bamboo
品种:green
年龄:2
FruitTree Bamboo
高度:10.0
用途:chopstick 成熟月份 用途
价格
图 2-13
想产生此对象,程序可写为:
b = Bamboo("green", 2, 10.0, "chopstick")
此时,计算机就创建一个 b:Bamboo
Bamboo 类的对象 b。它可存储 4 项
品种:green
数据,如图 2-14 所示。 年龄:2
高度:10.0
其继承 Tree 类的 3 个属性,Python 用途:chopstick
代码如下。
#Ex02-05 图 2-14
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
class Bamboo(Tree):
def __init__ (self, v, a, h, u):
self.variety = v
·35·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
self.age = a
self.height = h
self.usage = u
pass
图 2-15
2.5 对象行为与接口
2.5.1 接口入门
前面已介绍过,我们可以对一个类、对象或系
堂用
服务接口 统做多种行为观点的抽象。如将电器中获取的电源
McDonald' s
餐厅
行为抽象出来,取名叫“插头”。将桌子提供电源的
各种行为抽象出来,取名叫“插座”等。这种为特
定顾客群体取得服务的窗口,通称为“接口”
免下车
服务接口 (Interface)。如 McDonald(麦当劳)的服务接口如
图 2-16 所示。
接口是系统整合的基础,所以精致的行为抽象
图 2-16
方法可以得到好的接口。
·36·
第2章 Python 的对象与类
2.5.2 消息传递与对象行为
树林中的树会长高或变矮(遇台风等),计算机中存储这些数据的对象必
须随之改变,即对象内的数据会改变。果树的果实售价改变,Fruit tree 类的对
象也要改变;这种对象内数据的变化是对象的“行为”(Behavior)。对象的基
本行为包括如下几种。
(1)把数据送入对象并存储起来。
(2)改变对象内的数据。
(3)拿对象数据做运算。
(4)从对象中输出数据。
如 30 千克 peach 的总金额是多少?可用对象 a 内的价格数据进行运算,如
图 2-17 所示。
我们的目的是要用对象 a 中的价格(20 元/kg)和重量(30kg)相乘,进
而算出其金额(600 元)。但在 OOP 观念中,则必须将其解释为—把 30kg 送
进对象 a,在对象内部做乘法运算,然后把总金额 600(元)送出来。其中,
600(元)是对象 a 接收外界的“消息”(Message)后做出的反应,它的反应
过程(乘法运算)在对象 a 内部完成。就如同一个电灯泡,当电流通过灯泡,
灯泡会发光。
这是人们日常生活中的经验,如计算机软件像灯泡一样一点就亮,可能会
有更多的人喜欢它。当你到火车站买车票时,只需把钱投入自动售票机中,经
过一些处理后,就会出现车票出来的现象,如图 2-18 所示。
钱
30kg
到达目的地
a:FruitTree
自动售票机
品种:peach
年龄:8
高度:2.1 600 元
成熟月份:3 找回零钱
价格:20
车票
图 2-17 图 2-18
·37·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
上述的实物对象(如自动售票机)接到消息后,经过内部工作后输出结果。
同样,程序内的对象接到消息时,其内部也对数据进行运算,并输出运算结果。
如想知道 peach 树的高度是多少?可将此消息送进对象 a 中,它会输出此树的
高度,如图 2-19 所示。
图 2-19
对象对消息产生反应,但并非对任何消息都产生反应。如灯泡只会对电流
有反应—发光,自动售票机必须投入钱才会有反应—送出票。
当我们使用灯泡或自动售票机时,能轻易学会其使用方法—知道输入什
么消息,也很清楚它们的反应。同样,在 OOP 程序中,你也能轻易学会对象
的使用方法—知道输入什么消息,及了解其反应。所以,使用程序内的对象,
就像使用灯泡一样简单、方便。
换个角度来说,如果用户是自动售票机的设计人,就必须负责设计自动售
票机内的处理过程(反应过程),并使输入消息更简单,而反应更清楚。同理,
如果用户是对象的设计师,就得负责设计对象内的运算过程(对消息的反应过
程)。
2.5.3 对象的运算行为
如果用户是手表的设计人,则需把电池放入手表中提供电力。此时,用户
既使用现成的对象—电池,也创造新对象—手表。同样,编写计算机程序
时,用户经常既是对象的使用人,也是对象的设计人。因此,利用已有对象去
创造其他对象,是 OOP 程序员的工作。设计手表时,用户心里清楚新手表有
什么功能,如何设定时间、表示时间(指针或数字)等。也就是说,用户一定
对这手表将呈现的“行为”有很清晰的定义。其行为包括两个方面。
·38·
第2章 Python 的对象与类
(1)它接收何种“消息”?如按键时间。
(2)对消息将会有何反应?如显示时间或日期等。
在周围世界中,各物体都有其固定的行为,所以我们能轻易地掌握它。如
手机,只有拨号后才能拨打电话。
设计程序中的对象时,也得设计它的“行为”,决定它接收何种消息,并
且对消息产生什么反应。然而因为用户是设计者,所以必须担任相应的工
作—设计对象内部的运作,使它对消息产生正确的反应,就像用户组织手表
内部的零件、手机内部的结构一样。这是对象设计者的主要工作,其目的是让
用户有个好用且易于掌握的对象!
在 OOP 时代前,人们心中的主角是“函数”或“子程序”。到了 OOP 时
代,就得把函数放入对象中,让对象有所行为,即对消息产生反应。当用户对
所设计的对象的行为有清晰的构想后,就自然知道应将哪些函数放入对象中,
如同手表设计者按照手表的功能决定应该用哪些零件一样。用户去购买现成的
零件,并创造新零件,然后选择适当的电池,接下来将这些零件组织于手表内。
编写程序时,用户使用既有的函数,并创造新函数,然后将这些函数和对象按
一定规则放到新对象中。
如何使用这些函数?又如何运用现有的对象?现在,回到 Tree 类案例。上
一节里,已经创建对象 a,如图 2-20 所示。
接下来,设计一个函数叫 computeAmount(weight),用来计算总金额。如
果把这个函数加入对象 a 中,那么对象 a 就有如下反应,如图 2-21 所示。
computeAmount(30)
a:FruitTree a:FruitTree
品种:peach 品种:peach
年龄:8 年龄:8
高度:2.1 高度:2.1 600 元
成熟月份:3 成熟月份:3
价格:20 价格:20
图 2-20 图 2-21
这如同设计灯泡者将钨丝及稀有气体按规则放入其中,灯泡才能发光。至
于灯泡的使用人,则不必为灯泡内部的结构及运作过程费脑筋。把材料和反应
·39·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
的过程“封装”于灯泡内,用户只需把电流(消息)传入灯泡(对象)中,它
就发光(反应)。
编写程序时,用户把函数放入对象中,用对象内的数据做运算,并且输出
结果。由于函数存在对象内,而数据的运算在函数内,所以,运算的过程(即
反应的过程)就被“封装”在对象里面。
如果计算机软件由对象组织而成,人们就会觉得软件简单又好用。就像灯
泡可装在车子上,也可装在房子内,一起构成更大的对象(车子、房子都是对
象)。这种编程的理念,即前面所说的“面向对象编程”。
假设用户已把 computeAmount(weight)及 inquireHeight()两个函数加入对象 a
中,就可把消息送给对象 a,命令可写为:
a.computeAmount(30)
说明如下。
把消息 computeAmount(30)送给对象 a。此时对象 a 内部的 computeAmount()
函数就会进行运算(把 30 和 20 相乘),并且输出总金额
a . inquireHeight()
600 元。用户可输入另一个消息,命令如图 2-22 所示。
此时,对象 a 内的 inquireHeight()函数进行运算(读
取高度 2.1)并输出该树的高度,即 2.1 米。下面请动手
对象 (消息)
练习,将上述的 FruitTree 案例,写成 Python 程序,代
图 2-22
码如下。
#Ex02-06
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
pass
class FruitTree(Tree):
def __init__ (self, v, a, h, m, p):
self.variety = v
self.age = a
·40·
第2章 Python 的对象与类
self.height = h
self.month = m
self.price = p
def computeAmount(self, weight):
return weight * self.price
pass
def inquireHeight(self):
return self.height
pass
amount = a.computeAmount(25)
height = a.inquireHeight()
print(amount, height)
amount = k.computeAmount(25)
height = k.inquireHeight()
print(amount, height)
图 2-23
·41·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
a . computeAmount(25) a . inquireHeight()
图 2-24 图 2-25
Tree
computeAmount()函数,以便处理这种消息。
综上所述,现在用户必须了解如下重点。
(1)如何表明用户所设计的类,以及类之
间的父子关系。
例如,类关系如图 2-26 所示。
FruitTree Bamboo
在程序里,从上层类开始,依照由上而下
的顺序逐一把各类叙述清楚。各类所属的数据
图 2-26
项(变量),也说明清楚。
(2)把函数加入类中,以支持对象的行为,使对象能接收消息、进行运算
并输出结果。
如 FruitTree 加入 4 个函数,使得 FruitTree 类的对象能接收并处理 4 种消
息,如图 2-27 所示。
为了让对象能接收并处理消息,必须把适当的函数加入类中,因此类内含
有两种重要成分:(1)数据项;(2)函数。
我们 称 数据 项为 类 的“ 数据 成 员”;并 称 函数 为类 的 “成 员函 数 ”。 如
FruitTree 类含两个数据成员:(1)成熟月份;(2)价格。
·42·
第2章 Python 的对象与类
把价格输入对象内
SetPrice()
询问高度 把高度输入对象内
inquireHeight() SetHeight()
对象:FruitTree
求算金额
computeAmount()
输出金额
输出高度
图 2-27
写成 Python 程序,代码如下。
#Ex02-07
class Tree:
def __init__ (self, v, a, h):
self.variety = v
self.age = a
self.height = h
#-----------------------------------------------
class FruitTree(Tree):
def __init__ (self, v, a, h, m, p):
self.variety = v
self.age = a
self.height = h
self.month = m
self.price = p
def computeAmount(self, weight):
return weight * self.price
def inquireHeight(self):
return self.height
def SetHeight(self, h):
self.height = h
·43·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
amount = a.computeAmount(25)
height = a.inquireHeight()
print(amount, height)
图 2-28
a = FruitTree("peach",8,2.1,3,20)
能以两种方法了解上述的命令:
把 FruitTree 视为类,则 a 就是对象;则此命令就定义一个“对象”。
把 FruitTtree 视为一种数据类型,则 a 就是 FruitTree 类型的变量,则此
命令定义一个“变量”。
(4)对象中含有哪些数据。
类的父子关系,决定了对象的“继承”关系,也决定对象中所含有的数据
项。如 FruitTree 是 Tree 的“子类”,则 FruitTree 类的对象继承 Tree 类内的数
·44·
第2章 Python 的对象与类
据项。命令如下:
消息
对象:类
结果 对象 . 消息
例如命令:
a.SetPrice(30)
a.SetHeight(2.6)
·45·
第3章
3
善 用 类
3.1 如何描述对象:善用类
3.2 如何创建软件对象
3.3 对象参考
3.4 构造函数
3.5 子类如何创建对象
第3章 善 用 类
3.1 如何描述对象:善用类
类是群体(或集合),而对象是类中的一分子。例如:
“月球”是对象,属
于“星球”类的一分子。
软件中的对象通常会描述自然界的对象,但只表达了其重要属性与行为,
而忽略了细节部分。至于哪些是重要属性和行为呢 ? 软件程序中必须加以说
明。如前面所说,同类的对象具有共同的重要属性与行为,因此可由类统一说
明对象应该表达哪些属性和行为。
类是一群具有共同重要特性的对象。类的定义就是说明这群对象具有什么
重要特性,特性包括对象的属性及行为,软件中的对象用数据来表达属性,用
函数来表达行为。定义类时,应考虑如下问题。
1. 我们想描述哪些对象
如想描述手中的一朵花,而此花是一朵玫瑰花,则可得知手上的花是对象,
而玫瑰花是类。为了描述手上的玫瑰花,就得定义一个类:Rose。
2. 对象有哪些重要属性
如果想描述它的价格,也想描述其最适合做哪几个月份的生日花﹔则可知
Rose 类应包含两项重要数据—Price 和 Month。
3. 对象有哪些重要行为
上述 Rose 的属性—Month,并非是自然界中玫瑰花与生俱来的,而是人
们对其所赋予的含义,所以对象并非单纯地描述自然界的天然特性,也包括人
们赋予的抽象含义。同样,软件中的对象除描述自然界对象的行为外,也会描
述人们所赋予的特殊行为。例如,自然界有石头、水牛和太阳,则软件中也可
以用石头、水牛和太阳对象来描述,但软件中的石头会点头、水牛会弹琴、太
阳会撒娇等,即所谓的“对象拟人化”。软件程序员在创造对象时,可把对象
想象为无比聪明的。例如,Rose 的对象,可能具有如下多种行为:散发浪漫的
情意、说出它代表人的心意、说出它的价钱、正在盛开或凋谢、飞过秋千去等。
因此,赋予人性后,Rose 的对象比实际玫瑰花更加浪漫。假设我们认为
Rose 的重要行为是说出它的颜色;则 Rose 类应增加一个函数—Say()。
·47·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
3.2 如何创建软件对象
类的目的是创造新数据类型。为描述自然界的事物,必须有各式各样的数
据类型,才能充分贴切地表达自然界的静态与动态的美。Python 提供多种基本
数据类型,用来表达人类社会或大自然的景象,但实际还不够。如果善加运用
Python 的“类”概念,就能很容易地解决这个问题。它让程序员可以定义与创
造自己的数据类型来描述心中所想、眼睛所看的任何自然景象。
Python 里提供的整数、浮点数、字符串等常被称为“基本数据类型”;通过
类创造出来的数据类型称为“抽象数据类型”。“抽象”意味着:类只描述自然
事物的重要属性和行为,而忽略不重要的细节。于是,形成不成文的规则。
由基本数据类型所定义的变量,称为变量。
由抽象数据类型(即类)所定义的变量,称为对象。
例如:定义类如下。
class Rose:
pass
Rose 就是我们新创建的数据类型,用来创建对象,以描述自然界的玫瑰花。
于是可创建对象如下:
rose = Rose()
price:
month: (内存)
rose
Rose 类的对象
图 3-1
·48·
第3章 善 用 类
Python 代码如下。
#Ex03-01
class Rose:
price = 10.25
month = 10
def say(self):
print("Color is RED")
#------------------------------------
rose = Rose()
rose.say()
rose.say()
say()消息
price:
month:
rose
Rose 的对象
图 3-2 图 3-3
3.3 对象参考
函数之间常通过引数(Argument)来相互传递数据。引数的类型除常见的
整数、浮点数、字符串等基本数据类型外,也可以是类数据类型。也就是说,
·49·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
我 们 能 够 将 对 象 传 递 给 函 数 , 这 就 是 “ 对 象 参 考 引 数 ”( Object Reference
Argument),简称为“参考引数”(Reference Argument)。在 Python 中,除基
本数据类型外,所有的对象都以传送参考值的方式来进行数据的传递,这就是
俗称的“参考调用”(Call by Reference) 方法。程序代码如下。
#Ex03-02
class Rose:
price = 0
month = 0
def display(x):
print("Month:", x.get_month())
pass
r1 = Rose(10.25, 10)
r2 = Rose(8.5, 6)
display(r1)
display(r2)
图 3-4
·50·
第3章 善 用 类
x 也是 Rose 类型的参考,刚好可接收主程序传递来的对象参考。命令如下:
display(r1)
display()函数里
主程序 x
get_month()消息
price: 10.25
month: 10
r1
Rose 的对象
图 3-5
以上介绍如何通过“参考传递法”把对象传递给函数,以增进程序的运行
速度;也许用户会联想到函数如何将对象传回给主程序。这与对象的传递有着
相同的考虑与做法;在传回对象时也可能因对象的内容太多而导致数据复制浪
费时间;若能传回对象的参考值,即可事半功倍。
由于对象也是变量,传回对象也就如同传回一般变量,非常简单;函数可
利用 return 指令把对象的参考值传回给主程序,程序代码如下。
#Ex03-03
class Rose:
price = 0
month = 0
·51·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
def create_object():
r = Rose(8.28, 3)
return r
pass
rose = create_object()
print("Month:", rose.get_month())
图 3-6
3.4 构造函数
编写程序时,设计类是一件重要工作,因为必须通过类来创建对象。创建
新对象时,会传回新对象的“参考值”。将此参考值存储在所定义的参考变量
(Reference Variable)里,未来可按照此值循线把消息传给该对象。一般的程序
语言(如 C++、Java 等 ),其常见指令的格式如下:
参考变量 = 类名称(初期值)
产生新对象后,
“=”运算把新对象的参考值存入参考变量里,于是此变量
代表这个新对象。在创建对象时,有个隐藏对象依照类的定义产生新对象,此
·52·
第3章 善 用 类
隐藏对象就是“构造”(Constructor) 函数,或称为“初始化”(Initialization)
函数。其主要功能如下。
(1)依照类的定义分配内存空间给所创建的对象。
(2)设定新对象的初始值(Object Initialization)。
利用 Python 创建构造函数,其格式如下:
class 类名称:
pass
def __init__ (self, 初期值参数)
案例代码如下所示。
#Ex03-04
class Rose:
price = 0
month = 0
rose = Rose(8.28, 3)
print("Month:", rose.get_month())
图 3-7
再看如下命令:
rose = Rose(8.28, 3)
·53·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
其调用__init__()构造函数做如下工作。
Step 1,在内存中创建对象。
Step 2,用数值 p(即 8.28)和 m(即 3)分别来设定 price 和 month 的初始值。
Step 3,返回此对象的参考值。
3.5 子类如何创建对象
前面说明如何对众多对象进行分类,从而形
Person
成一个类的继承体系。例如对学校人员加以分门
别类,而得出类继承体系,如图 3-8 所示。
若 A 类“继承”B 类,则称 A 为“子类”,
称 B 为“父类”,亦即 B 为 A 的父类,A 为 B
Teacher Student 的子类。也许用户觉得“继承”概念比较陌生,
不知用什么方法才能看出类间的继承关系。这里
图 3-8 有个简单方法:下列两种叙述的意义相同:
(1)A 为 B 的子类。
(2)A 为 B 的一种特殊种类。
所以,从图 3-8 可知,Teacher 类“继承”Person 类,也即 Teacher 是 Person
的子类;Teacher 和 Student 都是 Person 的一种特殊种类。
对软件开发者来说,除了能熟练地将对象分门别类,还必须学习如何将分
门别类得到的类继承体系,顺利地通过 Python 语言表达出来,成为软件系统
的重要部分。软件程序的表达过程如下:
Step-1,定义父类,代码如下:
class Person:
pass
Step-2,定义子类,代码如下:
pass
pass
·54·
第3章 善 用 类
#Ex03-05
class Person:
def __init__(self, na, a):
self.name = na
self.age = a
def birth_year(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, "B.Year:", self.birth_year())
pass
class Teacher(Person):
def __init__(self, na, a, s):
super().__init__(na, a)
self.salary = s
def print(self):
self.display()
print("Salary:", self.salary)
pass
图 3-9
·55·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
所谓继承数据,是继承数据项,而不是继承数据的值,需要注意。类定义
数据项,对象创建后,对象内才有数据值,所以“类继承”即继承类的定义,
不是继承对象的值。也就是说,若父类(如 Person 类)定义了 name 及 age 两个
数据项,则子类(如 Teacher 类)天生就拥有此两项数据,所以子类不需要再定
义它们。所谓继承函数,表示子类天生就拥有父类定义的函数,说明如下。
Person 的子类天生承袭 name 和 age 两项数据定义。
Person 的子类天生承袭 birth_year()和 display()两个函数。
于是在 Teacher 类的构造函数__init__()里,可以调用 Person 父类的构造函
数__init__()。现在,这个 Teacher 类含有 3 个数据项。
Name:从 Person 类继承而来。
Age:从 Person 类继承而来。
Salary:自定义。
此外,也含有 3 个成员函数。
birth_year():从 Person 继承而来。
display():从 Person 继承而来。
print():自定义。
Teacher 的__init__()能调用父类的__init__()设定 name 及 age 的数据值;之
后,Teacher 的__init__()用于自己设定 salary 的值。同理,print()也能直接调用
display()显示 name 及 age 的内容;之后,print()自己输出 salary 的值。也许用
户会问:子类自己定义的函数,是否能与父类的函数同名呢?答案是肯定的,
而且很常见。例如,下面案例与上述程序相同。
#Ex03-06
class Person:
def __init__(self, na, a):
self.name = na
self.age = a
def birth_year(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, "B.Year:", self.birth_year())
pass
·56·
第3章 善 用 类
class Teacher(Person):
def __init__(self, na, a, s):
super().__init__(na, a)
self.salary = s
def display(self):
super().display()
print("Salary:", self.salary)
pass
图 3-10
super().__init__(na, a)
这一行命令表示调用父类的构造函数。在 display()函数里的命令:
super().display()
这一行命令表示调用父类的 display()函数。
·57·
第4章
4
对象的组合
4.1 认识 self 参考
4.2 建立对象的包含关系
4.4 包容多样化物件
4.5 集合对象
第4章 对象的组合
4.1 认识 self 参考
#Ex04-01
class Fee:
def __init__(self, amt):
self.amount = amt
def disp(self):
print("Amount is:", self.amount)
#-----------------------
a = Fee(100)
b = Fee(80)
a.disp()
b.disp()
图 4-1
a.disp()
b.disp()
·59·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
a:Fee b:Fee
参考的 参考的
参考的 参考的
self a self b
图 4-2 图 4-3
4.2 建立对象的包含关系
对象的包含关系就是表达整体/部分之间的关系。在日常生活中,到处可看
到这种包含关系(即整体/部分关系)。例如:灯泡含有灯芯、灯帽及玻璃球;
图书含有封面、目录及内容等“部分”。
请注意:上述关系,其整体与部分之间是共生共灭的密切关系。例如:一
个灯泡破了或者烧坏了,通常整个灯泡,包括其内部的灯帽、灯芯、玻璃球一
齐都被丢弃。所以,在软件系统中,这些部分对象(如灯芯)都会随着整体对
象(灯泡)消失而一起消失。也就是说,在整体/部分的组合中,若部分一旦离
开了整体,则整体也将不存在。例如,
“双亲家庭”整体中的“父”
“母”是不
可或缺的,否则“双亲家庭”整体就不存在了。这种共生共灭的密切关系,称
为组合(Composite)关系。它也是限制较严格的一种包含关系,其限制了“部
分”只能隶属于唯一的“整体”,即整体具有“拥有权”
(Ownership),同时“部
分”与“整体”具有一样的寿命,如轮子与脚踏车,如图 4-4 所示。
下面再来看看,限制不严格的包含关系。其“部分”可参与两个以上的“整
体”。例如,一位兼职员工可任职于多个公司。这种关系很常见,如司机是汽
车的一部分,但司机并不与汽车共生共灭。这种关系,称为聚合(Aggregation)
关系,如图 4-5 所示。
·60·
第4章 对象的组合
脚踏车 汽车
轮子 司机
图 4-4 图 4-5
#Ex04-02
class Container:
child = None
rSize = 0
aDesk = Desk()
·61·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
aContainer = Container()
aContainer.setter(aDesk)
print("Container.size:", aContainer.getSize())
图 4-7
aContainer.setter(aDesk)
print("Container.size:", aContainer.getSize())
Container 对象
child 参考
Container 对象
child 参考
parent 参考
parent 参考
Desk 对象
Desk 对象
图 4-8 图 4-9
·62·
第4章 对象的组合
#Ex04-03
class Container:
child = None
rSize = 0
class Desk:
parent = None
dSize = 0
aDesk = Desk()
aContainer = Container()
aContainer.setter(aDesk)
print("Desk.size:", aDesk.getSize())
图 4-10
其中,有个重要的概念就是:self 参考值,代码如下。
·63·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
self.child = d_ref
self.rSize = 31.25
self.child.setter(self)
#Ex04-04
class Money:
def __init__(self, amt):
self.balance = amt
def add(self, saving):
self.balance += saving
def Display(self):
print("Balance is:", self.balance)
#------------------------------------------
orange = Money(100)
orange.add(300)
orange.add(80)
orange.Display()
图 4-11
·64·
第4章 对象的组合
add(300)消息
orange:Money
add(80)消息
图 4-12
命令如下:
orange.add(300)
orange.add(80)
add(300)消息 add(80)消息
(第一个消息) (第二个消息)
orange:Money
图 4-13
可以发现,这次的图形更具有次序感。所以,上述命令可以用如图 4-14
所示的形式展示。
对象
第一个 第二个
消息 消息
图 4-14
对于这样的效果,很多人应该不会陌生。有点像以前上小学时,班长喊:
“起立、敬礼、坐下”,是不是连续接收了 3 个消息?你看,我们已能设计出像
日常生活这般亲切的对象了。俗话说:“万丈高楼平地起”,我们还必须对 self
参考有充分了解,程序代码如下。
·65·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex04-05
class Money:
def __init__(self, amt):
self.balance = amt
def add(self, saving):
self.balance += saving
return self
def Display(self):
print("Balance is:", self.balance)
#--------------------------------------------
orange = Money(100)
orange.add(300).add(80)
orange.Display()
图 4-15
return self
传回目前对象的参考值—orange 对象的参考。例如,add()把目前对象的参
考值 self 传回。此刻,orange.add(300)的值也是参考值,与 orange 参考同一个对
象。于是,原来的命令—orange.add(300).add(80)就相当于—orange.add(80)。
不过,此时 orange 对象的 balance 变量值为 400
orange.add(300).add(80) 元,而非原来的 100 元。此 orange 再接收消息—
add(80),则 balance 值增加为 480 元,orange 接收
(相当于 orange) 第 2 条消息—add(80)时,计算机再运行 add()函
·66·
第4章 对象的组合
#Ex04-06
class Money:
def __init__(self, amt):
self.balance = amt
def add(self, saving):
self.balance += saving
return self
def Display(self):
print("Balance is:", self.balance)
#-------------------------------------------
orange = Money(100)
orange.add(300).add(80).Display()
图 4-17
add(300)
orange:Money
balance:100
图 4-18
·67·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
orange:Money
add(80)
balance: 400
orange orange.add(300)
this
oragne.add(300).add(80)
图 4-19
add(300)
orange:Money add(80)
orange orange.add(300)
this
oragne.add(300).add(80)
图 4-20
#Ex04-07
class Money:
def __init__(self, amt):
self.balance = amt
·68·
第4章 对象的组合
def Display(self):
print("Balance is:", self.balance)
#----------------------------------------------------
orange = Money(100)
orange.add(300).add(80).Display()
图 4-21
add(300)
orange:Money
Orange 对象
balance: 100
(复制一份内容)
对象:Money
orange.add(300)
参考的对象 balance: 400
图 4-22
·69·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
add(300)
orange:Money
orange
balance: 100
(复制一份内容)
add(80)
对象 Money
orange.add(300)
balance: 400
(复制一份内容)
Display()
对象 Money
orange.add(300).add(80)
balance: 480
图 4-23
由于每次运行 add()都产生一个新对象(虽然内容相同,但占用不同的内存
空间),其后的消息都传给 add()所创建的新对象,而非原来的 orange 对象,所
以不影响原来 orange 对象的内容。请注意:Display()并未传回对象的参考值,
则 Display()接收 消息之后就不能再接收其他消息了,因为 Display()不传回对象
的参考值,命令说明如图 4-24 所示。
orange.add(300).Display().add(80) 其后的消息—add(80)是错误的。如何改
正?很简单,只需通过 Display()函数传回 self(目
(不是对象参考)
前对象的参考值)或新对象的参考值即可,程序
图 4-24 如下。
#Ex04-08
class Money:
def __init__(self, amt):
self.balance = amt
·70·
第4章 对象的组合
def Display(self):
print("Balance is:", self.balance)
return self;
#---------------------------------------------------
orange = Money(100)
orange.Display().add(300).Display().add(80).Display()
图 4-25
4.4 包容多样化物件
Alvin
Kao
John
Coppin
图 4-26
·71·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Jamis
Container 对象
King
manager
coach David
players Wang
Jim
Lin
Alvin
Kao
John
Collection 对象 Coppin
图 4-27
这个独立出来的对象通称为“集合对象”
(Collection),再看一个例子,如
图 4-28 所示。
售票机 硬币
getValue
getValue
售票机
对象 getValue
图 4-28 集合对象包容不同类型的对象
实际生活中,一台售票机可以包容许多不同类型的硬币。在软件里,可以
设计售票机对象(即 Container 对象),它先包容一个集合对象,再通过该对象
去包容一群多样化的物体。
接下来详细介绍如何活用集合对象,建立能容纳多样化对象的 Container
对象,为整合大型系统奠定优良的基础。
·72·
第4章 对象的组合
4.5 集合对象
在软件中,常见的数组(Array),包含一群有次序的数据项。只是数组内
的数据项,其类型必须一致,是一元化的集合体。
例如:整数数组 x[10],就表示 x[0]、x[1]、…、x[9]均为整数类型,即此
数组只能包容同一类型的数据,不能容纳多种类型的数据或对象。为了包容不
同类型的数据或对象,需要多元且可伸展的集合对象(Collection Object)。
Python 提供了各式各样的集合类(Collection Class),可用来创建集合对象。
例如 Array、Dictionary 等类,其用途就是将相关的对象集合起来,并表达
对象之间的关系,使得计算机软件更易于掌握对象间的复杂关系。例如:一支
棒球队,含各种不同的角色,像经理、教练及球员等各有各的职责。由于属于
同一支球队,所以他们之间是息息相关的。
因此,可以通过一般数组来表达上述的棒球队,使用 Python 程序编写
如下。
#Ex04-09
class Person:
pname = None
def getName(self):
return self.pname;
def setName(self, value):
self.pname = value
#--------------------------------------------
class Baseball_team:
manager = None
coach = None
players = []
def __init__(self):
self.manager = Person()
self.coach = Person()
self.idx = 0
·73·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
self.manager.setName(m)
def display(self):
print("Manager: ", self.manager.getName())
print("Coach: ", self.coach.getName())
print("")
print("Players: ")
for i in range(0, self.idx , 1):
print(" ", self.players[i].getName())
#----------------------------------------------------
RedSock = Baseball_team()
RedSock.setManager("James Lin");
RedSock.setCoach("David Wang");
RedSock.addPlayer("Jim Lin");
RedSock.addPlayer("Alvin Kao");
RedSock.addPlayer("John Coppin");
RedSock.display();
图 4-29
·74·
第4章 对象的组合
球队对象就如同鸟巢,而队员对象就如同鸟蛋一般,鸟巢中包含一堆鸟蛋,
而同样球队(物件)内含一群球员(物件),所以球队就跟鸟巢一样,都是集
合类。
Baseball_team 类定义了 players[]数据数组,可以存放对象的参考,来参考
到成群的 Person 对象,即 Baseball_team 类的每个对象都含有一个 players[]数
组,准备参考成群的 Person 物件。而且 Baseball_team 类的对象内各含有一个
索引变量—idx,作为 players[]的标注(又称下标),命令如下:
players[idx]
players
getName()消息
Person 对象
图 4-30 集合对象
·75·
第5章
5
类的封装性
5.1 对象的封装性
5.2 类:创造对象的封装性
5.3 类的私有属性与函数
5.4 类级别的属性
5.5 类级别的函数
第5章 类的封装性
5.1 对象的封装性
俗话说,科学家从乱中找序,而设计师(艺术家)在规划序中有乱。无论
是“乱中有序”还是“序中有乱”,两者都首先要有顺序(Order),并包容变
化(Change),只不过手段不同而已。两种手段都能带来巨大的经济价值,精
通这两种手艺,是软件系统开发的成功关键。
只要我们能创造出“能包容变化”的东西(如货柜),呈现出次序(即一
致的行为),就能很“容易”(Easy)地掌握一切。
在软件里,我们也希望每一个对象都能“容易”使用,所以在编写 Python
程序时,就必须创造能“容易”使用的类,以便创建容易使用的对象。
在 OOP 里,这种适应变化(Accommodate Change)的特性,通称为“封装
性”(Encapsulation)
。当对象把复杂变化封装起来,就会呈现出简单的次序,也就
是接口。擅用对象封装性,才能设计出方便使用的接口,系统整合就会变得非常容
易。本章讲解 OOP 里的“封装性”概念,为用户建立扎实的 Python 编程技术基础。
5.2 类:创造对象的封装性
类(Class)的任务是把数据和函数组织并封装起来。类告诉计算机:“其
对象应含有哪些数据、应含有哪些函数并处理外界传来的消息。”类必须详细
地说明它的数据及函数,我们称此数据是类的“数据成员”(Data Member);
而称此函数是类的“成员函数”(Function Member)。有关类内容的叙述,即
类定义(Class Definition),格式如图 5-1 所示。
图 5-1
·77·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
类的主要用途是定义对象。案例代码如下。
#Ex05-01
class Tree:
pass
#-------------------------
a = Tree()
print("Object a Is Created.")
图 5-2
a = Tree()
#Ex05-02
class Tree:
def input(self, hei):
self.variety = None
self.age = None
self.height = hei
#-------------------------
a = Tree()
a.input(2.1)
print("Set a.height to ", a.height)
·78·
第5章 类的封装性
图 5-3
a.input(2.1)
self.height = hei
a:Tree a:Tree
variety: variety:
age: age:
height: height: 2.1
input() input()
图 5-4 图 5-5
#Ex05-03
class Tree:
def __init__(self):
·79·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
self.variety = None
self.age = None
def inquireHeight(self):
return self.height
#-------------------------
a = Tree()
a.input(2.1)
h = a.inquireHeight()
print("height= ", h, "米")
对象 . 函数成员(参数);
(消息)
图 5-6 图 5-7
也就是说,必须以消息的形式出现。例如:
a.input(2.1)
如成员函数不与对象配合时,计算机又会如何处理?如下例所示,代码
如下。
#Ex05-04
class Tree:
def __init__(self):
self.variety = None
self.age = None
·80·
第5章 类的封装性
def inquireHeight(self):
return self.height
#-------------------------
a = Tree()
a.input(2.1)
h = inquireHeight()
print("height= ", h, "米")
当运行如下命令时:
h = inquireHeight( )
图 5-8
因此,用户需要掌握一个原则,即成员函数的任务是支持对象的行为,必
须与对象配合使用。
5.3 类的私有属性与函数
前面说过,对象把数据及函数组织并“封装”起来,只有通过特定的方式
才能使用类的数据成员和成员函数。对象如同手提袋,只能从固定的开口才能
存取物品,否则用户一定不会把钱物放在手提袋中。对象像“防火墙”一样,
来保护类中的数据,使其不受外界的影响。想象一下,公园的围墙可以保护里
·81·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
面的动植物,但其并非完全封闭,而有几个出入口。对象和公园的围墙功能一
样,它保护其数据成员,但也有正常的数据存取管道:以成员函数来存取数据
成员。请看下例,代码如下。
#Ex05-05
class Tree:
def __init__(self):
self.variety = None
self.age = None
self.height = None
#-------------------------
a = Tree()
a.height = 2.1
print("height= ", a.height, "米")
图 5-9
a:Tree
variety:
age:
height: 2.1
对象. 数据成员
图 5-10 图 5-11
·82·
第5章 类的封装性
此外,用户还需要留意一种情形,代码如下。
#Ex05-06
class Tree:
def __init__(self):
self.variety = None
self.age = None
self.height = None
#-------------------------
a = Tree()
height = 2.1
print("height= ", a.height, "米")
height = 2.1
图 5-12
上述情形,是对象对其数据成员保护最为宽松的情形,因为对象所属类(即
Tree)之外的函数(如主程序部分)还能存取数据成员的内容,这统称为公有
成员,它包括公有数据成员(Public Data Member)和公有成员函数(Public
Member Function)两种。然而,这种保护宽松的情形就像一颗炸弹,除了引
信外,还有许多方法可以让炸弹爆炸。同理,Tree 类的数据—height 变量,
连类外部的主程序都可以随意改变它,那么,如果有一天 height 的内容出问题
了,将很难追查出错的原因,这种程序会让人大伤脑筋,因为已经无法掌握具
体的状况。
在如今的 Python 程序中,已采取较严密的保护措施,使用户能控制类内数
据的变化状况,这统称为私有成员,包括私有数据成员(Private Data Member)
和私有成员函数(Private Member Function)两种。例如,将上述的数据项名称
"height"改变成为"__height",就从原来的公有性,变为私有性了,计算机会对
·83·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Tree 类的数据成员采取严格的保护措施。其中,公有成员与私有成员的区别如下。
公有(Public):表示此类外的函数可以存取数据成员。
私有(Private):表示此类外的函数无法直接存取数据成员,只有成员
函数才能存取数据成员。
再看一例,代码如下。
#Ex05-07
class Tree:
def __init__(self):
self.variety = None
self.age = None
self.__height = 2.1
#-------------------------
a = Tree()
print("height= ", a.__height, "米")
运行主程序的命令:
print("height= ", a.__height, "米")
图 5-13
也许用户会问:这样岂不是无法存取类 成 员函 数
内的数据成员吗?答案如下:
“类内的成员函数(Member Function)
可存取私有性数据成员,类外的函数能通过 其 他类 的
外 界函 数
函数
成员函数来存取数据成员,如图 5-14 所示。”
这如同只有引信才能引起炸弹爆炸,人 图 5-14
·84·
第5章 类的封装性
们也只能通过引信才能引爆炸弹,让人们觉得使用炸弹既安全又简单。同样,
对象经由成员函数和外界沟通,可减少外界无意中破坏对象内的数据(无意中
引爆炸弹)的情况出现。继续看下例,代码如下。
#Ex05-08
class Tree:
def __init__(self):
self.variety = None
self.age = None
self.__height = 2.1
#-------------------------
a = Tree()
a.input(2.1)
a.disp()
图 5-15
对象. 函数成员(参数)
图 5-16
除了刚才谈到的私有性数据成员,还有私有性成员函数。如果将上述的函
数名称"input()"改为"__input()",就从原来的公有性,变成私有性,计算机也会
·85·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
对 Tree 类的函数采取严格的保护措施。例如下面的程序,代码如下。
#Ex05-09
class Tree:
def __init__(self):
self.variety = None
self.age = None
self.__height = 2.1
#-------------------------
a = Tree()
a.__input(2.1)
a.disp()
图 5-17
继续看下例,代码如下。
#Ex05-10
class Tree:
def __init__(self):
self.__variety = None
self.__height = 2.1
self.age = None
·86·
第5章 类的封装性
def ShowAge(self):
print("Age=", self.age)
#-------------------------
a = Tree()
a.age = 8
a.age += 2
a.ShowAge()
图 5-18
a.age = 8
a.age += 2
图 5-19 图 5-20
使用这种格式来调用 ShowAge()函数。由于类(即对象)的目的是保护数
据,并且提供成员函数来与外界沟通。通常情况下,数据成员都定义为私有性,
而成员函数都定义为公有性,即尽量不用或少用如图 5-21 所示的格式,而尽
量多地使用如图 5-22 所示的格式。
·87·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 5-21 图 5-22
范例代码如下。
#Ex05-11
class Tree:
def input(self, v, a, hei):
self.__variety = v
self.__age = a
self.__height = hei
def Show(self):
print(self.__variety, self.__age, self.__height)
#-------------------------
a = Tree()
b = Tree()
a.input("peach", 8, 2.1)
b.input("pineapple", 2, 0.5)
a.Show()
b.Show()
a.input("peach", 8, 2.1)
b.input("pineapple", 2, 0.5)
·88·
第5章 类的封装性
a:Tree b:Tree
variety: "peach" variety: "pineapple"
(数据成员) age: 8 (数据成员) age: 2
height: 2.1 height: 0.5
input() input()
(成员函数) Show() (成员函数) Show()
图 5-23 图 5-24
图 5-25
5.4 类级别的属性
类级别(Class-level)的数据,又称为共享(Shared)数据或静态(Static)
数据,这也是同一类里各对象所能共享的数据。前面我们在类里所定义的数据
项,其数据都封装于各对象内,别的对象无法获取。共享数据项的值是在对象
外,但封装在类内,只要是该类的对象都能获取该值。由于一般数据项的值封
装于对象内,就称为对象级别的变量(Instance-level Variable);而共享数据封
装于类,所以又称为类级别的变量(Class-level Variable)。如下例所示。
#Ex05-12
class Employee:
temp = 0;
def save_to_temp(self):
·89·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Employee.temp = self.salary
def load_from_temp(self):
self.salary = Employee.temp
def Display(self):
print("Name:", self.emp_name, ", Salary:", self.salary)
#--------------------------------------------------------------
tom = Employee("Tom", 7777.25)
peter = Employee("Peter", 1643.5)
tom.save_to_temp()
peter.load_from_temp()
peter.Display()
图 5-26
定义如下命令:
temp = 0;
·90·
第5章 类的封装性
temp
tom:Employee peter:Employee
图 5-27
tom.save_to_temp()
peter.load_from_temp()
7777.25 temp
tom:Employee peter:Employee
图 5-28
共享数据除了供对象之间沟通,还有一个重要的用途,即记录类的状况,
例如记录该类共创建了多少个对象。请看如下例子,代码如下。
·91·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex05-13
class Employee:
counter = 0;
sum = 0
def Display_Avg(self):
print("The number of employee:", Employee.counter)
print("Average salary:", Employee.sum / Employee.counter)
def Display(self):
print("Name:", self.emp_name, ", Salary:", self.salary)
#--------------------------------------------------------------
e1 = Employee("Tom", 25000.0)
e2 = Employee("Lily", 20000.0)
e1.Display()
e2.Display()
e1.Display_Avg()
图 5-29 图 5-30
·92·
第5章 类的封装性
当 e1 对象创建时,会去运行结构函数 Employee(),把各数据存入到对象
的私有数据中;同时也运行命令:counter = counter + 1,使共享变量 counter
值加 1。此外,也运行命令:sum = sum + salary,把 e1 对象的 salary 值加到 sum
里。此时共享数据的内容如图 5-31 所示。
此时,counter 值为 1,表示 Employee 内已创建一个对象。接着,创建对
象 e2,计算机又运行结构函数:Employee(),它把数据存入对象 e2 中,使 counter
加上 1,也把 e2 对象的 salary 值加到 sum 中。此时,共享数据的值如图 5-32
所示。
图 5-31 图 5-32
5.5 类级别的函数
前面介绍过共享的数据项,它是各对象的公有数据,但又不属于任何一个
对象,而是属于类的。除数据项以外,也有共享函数,它属于类,而不属于任
何对象,所以称其为“类级别函数”(Class-level Function)。它不能调用对象
内的数据,只能存取共享数据的值。一般函数均可调用共享函数,也可以存取
共享数据的值。如下例,代码如下。
#Ex05-14
class Employee:
counter = 0;
sum = 0
·93·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
@classmethod
def NumberOfObject(cls):
return cls.counter
@classmethod
def Average(cls):
return cls.sum / cls.counter
@classmethod
def Display_Avg(cls):
print("Average salary:", cls.Average())
def display(self):
print("Number of Employee:", Employee.NumberOfObject())
#--------------------------------------------------------------
e = Employee("Tom", 25000.0)
e = Employee("Lily", 20000.0)
e.display()
Employee.Display_Avg()
图 5-33
一般函数用来处理对象内的数据,调用一般函数的格式如下:
·94·
第5章 类的封装性
对象参考 . 一般函数
调用该函数的目的是处理此对象内的数据。共享函数的目的并非在于处理
对象的内容,而是存取共享数据或处理关于整个类的事情。因此,调用共享函
数的格式如下:
类 . 共享函数
例如,下面程序里的命令就是调用 Display_Avg()共享函数。
Employee.Display_Avg()
由于共享函数并非处理某个特定的对象值,所以不会去调用一般的函数。
不过,一般函数却可以调用共享函数,以便必要时通过共享函数取得有关整个
类的数据。例如,上述 display()是一般函数,程序代码如下。
def display(self):
此函数调用了共享的 NumberOfObject()函数,而共享函数不可调用一般函
数,如上述 Display_Avg()是共享函数,程序代码如下。
@classmethod
def Display_Avg(cls):
·95·
第6章
6
类的继承体系
6.1 继承的意义
6.2 建立类继承体系
6.3 函数覆写的意义
第6章 类的继承体系
6.1 继承的意义
人们从小就学习将东西分类,如将自然界的物品分为“生物”及“非生物”,
其中生物又分为“动物”及“植物”等,无论动物、植物或生物皆为类。动物
是一种生物,植物也是一种生物。此时,即称动物是生物的子类,植物也是生
物的子类,而生物是动物及植物的父类。这种父子类关系是人们将复杂事物分
门别类,找出一致的行为和“次序”的有效途径。
类之间,有些互为独立,有些具有密切关系。下面介绍类之间常见的关
系—“父子”关系,由于儿女常继承父母的生理或心理特征,所以又称此关
系为“继承”(Inheritance)关系。类之间的密切关系,把相关的类组织起来,
并且组织程序内的对象。若程序内的对象毫无组织,呈现一片散沙状态,就不
是一个好的程序。完美的 Python 程序,必须重视类之间的关系,而对象则有一
定的组织。
如果 A 类“继承”B 类,则称 A 为 B 的“子类”,也称 B 为 A 的“父类”。
在 Python 中,父类又称为“基础类”
( Base Class),子类又称为“衍生类”
( Derived
Class)。如果用户对“继承”观念不熟悉,无法看出类之间的继承关系,还有
一个简单方法:(1)A 为 B 的子类;(2)A 为 B 的一种特殊类。
根据叙述(2)能轻易找出父子关系。例如:肯尼思(Kennex)生产高质
量球拍,球拍分两种:网球拍与羽毛球拍。从此句子得知:网球拍为一种球拍,
羽毛球拍也为一种球拍。因此,网球拍为球拍的子类,羽毛球拍也为球拍的子
类,即球拍是父类,如图 6-1 所示。
这里 有 一 个 基 础 类 —球拍 , 以 及 两 个 衍 生
球拍
类 —网球拍及羽毛球拍,程序通过继承关系将这
三类组织起来。除了物品(如球拍、汽车等)外,
人也有继承关系。例如:学校人员(Person)包括学
生(Student)、老师(Teacher)及职员(Employee), 网球拍 羽毛球拍
老师又分为专职老师(Full-time Teacher)及兼职老
师(Part-time Teacher),如图 6-2 所示。 图 6-1
·97·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Person
Full_time Part_time
Teacher Teacher
图 6-2
6.2 建立类继承体系
前面的章节中,已经介绍了如何定义类,这一节介绍如何定义类的继承关
系,案例如图 6-3 所示。
Person
Teacher Student
图 6-3
程序的设计过程如下。
Step 1,定义基础类(父类),代码如下:
class Person:
………
Step 2,定义衍生类(子类),代码如下:
class Teacher(Person):
pass
class Student(Person):
·98·
第6章 类的继承体系
pass
#Ex06-01
class Person:
def setValue(self, na, a):
self.name = na
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
mike = Person()
mike.setValue("Mike", 45)
mike.display()
print("BirthYear:", mike.birthYear());
图 6-4
所谓继承数据,是表示继承数据成员的定义,而不是继承数据的值,需要
用户加以区别。类定义数据成员(含类型及名称),对象创建后,对象内才有
数据值。所以“类继承”是继承类的定义,而不是继承对象的值。也就是说:
若父类定义 Name 及 Age 两个数据成员,则子类天生就拥有这两个数据成员,
所以子类不需要定义它们。所谓继承函数,表示子类天生就拥有父类定义的成
·99·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex06-02
class Person:
def setValue(self, na, a):
self.name = na
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
class Teacher(Person):
pass
steven = Teacher()
steven.setValue("Steven", 35)
steven.display()
图 6-5
·100·
第6章 类的继承体系
#Ex06-03
class Person:
def setValue(self, na, a):
self.name = na
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
class Teacher(Person):
def tr_setValue(self, na, a, sa):
super().setValue(na, a)
self.salary = sa
def pr(self):
super().display()
print("Salary:", self.salary)
#--------------------------------------------------------
steven = Teacher()
steven.tr_setValue("Steven", 35, 35000)
steven.pr()
图 6-6
·101·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Salary:自定义。
此外,也含有五个成员函数。
setValue():从 Person 继承而来。
birthYear():从 Person 继承而来。
display():从 Person 继承而来。
tr_SetValue():自定义。
pr():自定义。
由于 setValue()为 Teacher 的成员函数,所以 tr_setValue() Person
能直接调用 setValue()来设定 Name 及 Age 的值;之后,用
tr_setValue()设定 Salary 的值。同理,pr()能直接调用 display()
来显示 Name 及 Age 的内容;之后,pr()自己输出 Salary 的值。
此 Python 程序已定义如图 6-7 所示的类关系。 Teacher
#Ex06-04
class Person:
def setValue(self, na, a):
self.name = na
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
class Teacher(Person):
def setValue(self, na, a, sa):
super().setValue(na, a)
self.salary = sa
def pr(self):
super().display()
·102·
第6章 类的继承体系
print("Salary:", self.salary)
#--------------------------------------------------------
class Student(Person):
def setValue(self, na, a, no):
super().setValue(na, a)
self.student_number = no
def pr(self):
super().display();
print("StudNo: ", self.student_number)
#--------------------------------------------------------
x = Person()
x.setValue("Alvin", 32)
y = Student()
y.setValue("Tom", 36, 11138)
x.display()
y.pr()
图 6-8
·103·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex06-05
class Person:
def __init__(self):
self.name = None
self.age = None
def setValue(self, name, a):
self.name = name
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
class Teacher(Person):
def __init__(self, na, a, sa):
super().setValue(na, a)
self.salary = sa
def pr(self):
super().display()
print("Salary:", self.salary)
#--------------------------------------------------------
class Student(Person):
def __init__(self, na, a, no):
super().__init__()
super().setValue(na, a)
self.student_number = no
def pr(self):
super().display();
print("StudNo: ", self.student_number)
#--------------------------------------------------------
x = Person()
x.setValue("Alvin", 32)
·104·
第6章 类的继承体系
图 6-10
y : Student
对象:Person
name:
对象:Person age:
name: (y 对象的继承部分)
age:
student_number:
图 6-11 图 6-12
·105·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
y : Student y : Student
对象:Person 对象:Person
name: “Tom” name: “Tom”
age: 36 age: 36
图 6-13 图 6-14
#Ex06-06
class Person:
def __init__(self, na, a):
self.name = na
self.age = a
def birthYear(self):
return 2019 - self.age
def display(self):
print("Name:", self.name, ", Age:", self.age)
#--------------------------------------------------------
class Teacher(Person):
def __init__(self, na, a, sa):
super().__init__(na, a)
self.salary = sa
def pr(self):
super().display()
print("Salary:", self.salary)
#--------------------------------------------------------
class Student(Person):
def __init__(self, na, a, no):
·106·
第6章 类的继承体系
super().__init__(na, a)
self.student_number = no
def pr(self):
super().display();
print("StudNo: ", self.student_number)
#--------------------------------------------------------
x = Person("Alvin", 32)
y = Student("Tom", 36, 11138)
x.display()
y.pr()
图 6-15
对象 :Person
name:
(由 Person 构造函数建立)
age:
图 6-16
对象:Person
name: “Tom”
(由 Person 构造函数
age: 36
内的命令设定)
图 6-17
·107·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
y : Student
对象:Person
name: “Tom”
age: 36
图 6-18
y : Student
对象:Person
name: “Tom”
age: 36
图 6-19
于是, y 物体创建完毕。
其中,需要特别留意的是,子类构造函数调用父类构造函数的方式。例如,
Student 构造函数内的 super.__init__(na, a)命令将 na 及 a 值传递给 Person 构造
函数,于是 Person 构造函数设定 Name 及 Age 的初始值。其除了调用 Person
构造函数,还运行自己的命令 student_number = no,来设定 student_number 的初
始值。
Student 类的对象含有 3 个数据成员,其中 Name 及 Age 是由 Person 类继
承而来,通过 Person 构造函数设定初始值。至于 Student 类自己定义的数据成
员 Student_Number,就由自己的命令 student_number = no 设定初始值。
6.3 函数覆写的意义
程序的发展是渐进的,软件内的类也随着企业的成长而不断扩充。类扩充
·108·
第6章 类的继承体系
一般有以下几种来源:功能增加或功能改变。两者都可以通过类继承来扩充或
修改已有的函数,从而达到这个目的。
在前面各章节里,已经看到子类具有自己定义的函数,那就是子类对父类
加以“扩充”功能的情形,在此不再赘述。这里特别说明如何“修正”父类的
已有函数,如果从父类继承得到的函数并不符合子类的需要,可设计同名函数
取代,如图 6-20 所示。
子 类 SalesManager 对 继 承 而 来 的
bonus() 函 数 不 满 意 , 因 而 定 义 自 己 的
bonus() 函数来取代它,此情形称为函数的
“覆写”( Override )。如一般销售员与销售
经理的红利计算方法不同; SalesPerson 类
的 bonus()函数无法计算 SalesManager 人员
的红利,所以 SalesManager 类必须定义自
己适用的 bonus()计算销售经理的红利。想
达到覆写的目标,子类自定义函数的名称、
参数均与父类原有函数相同,就能覆盖掉 图 6-20 函数之覆写
父类的函数。反之,如果名称、参数有所
不同,则无法覆盖掉父类的函数,而形成两个独立的函数。这种覆写函数的用
途很多,包括多态性、反向控制等,本书后续章节会详细说明。这儿先看一下
Python 语法的表达,程序如下。
#Ex06-07
class SalesPerson:
def __init__(self, t):
self.totalSales = t
def bonus(self):
return self.totalSales * 0.008
#--------------------------------------------
class SalesManager(SalesPerson):
def __init__(self, t):
super().__init__(t)
·109·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
def bonus(self):
return self.totalSales * 0.008 + 1000
#--------------------------------------------
Jim = SalesPerson(50000)
print("Jim's Bonus:", Jim.bonus())
Tom = SalesManager(45000)
print("Tom's Bonus:", Tom.bonus())
图 6-21
“覆写”是针对“父子”类之间,子类有“修正”或“取代”的意思时,
才定义同名函数取代父类的函数。父类的 bonus()和子类的 bonus()都表示同一
含义:计算红利,只是计算方法不同。因此,“覆写”着眼于以不同的运行过
程来取代父类的函数,但新旧函数之间,意义相同。
·110·
第7章
7
活用抽象类
7.1 抽象类与继承体系
7.3 从“抽象类”衍生“具象类”
7.4 抽象类的妙用:默认行为
7.5 默认函数的妙用:反向调用
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
7.1 抽象类与继承体系
电器
TCL Kolin
电视机 电视机
图 7-1
这是利用人们的抽象( Abstraction)能力,对物品加以分类,所以这个继
承关系中的父类又称为抽象类。“抽象”一词的反义词是“具象”( Concrete),
所以继承关系中的子类又称为具象类( Concrete Class),也称为具体类。
然而,Python 对于抽象类与具象类之间,有比较明确的区别,让我们能更
明确地叙述对物品的分门别类、找出序(即接口),创造出分合自如的空间和
机会。
在本章里,先介绍 Python 的抽象类概念和机制。而等到下一章,再说明
抽象的程序。
7.2 Python抽象类的表示法
7.2.1 一般具象类
抽象类的来源是:洞悉并分离出“变”
(或差异)的部分与“不变”
(或共
同)的部分,然后去掉差异部分,留下共同部分,并以类来表示,即为抽象类。
由于已经去掉了一部分,所以抽象类的本质是不完整的,预留一些有待填补的
·112·
第7章 活用抽象类
空间。例如,建房子时,师傅都会预留一些卡榫,用作未来可用的衔接点。
例如,一般类是具体而完整的,每一个函数都有完整的实现命令部分,我
们可以随意拿它来创建对象,实例代码如下。
#Ex07-01
class SalesPerson:
def GetTotal(self):
return self.base_fee * self.discount
def Display(self):
print( "Name:", self.name )
print( "Fee:", self.GetTotal())
#---------------------------------------------------------------
alice = SalesPerson("Alice", "Male")
alice.SetFee(2000, 0.8)
alice.Display()
图 7-2
这样的一般类通称为具象类。
·113·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
7.2.2 抽象类
如果抽掉 SalesEngineer 类里的某些函数的实现部分,就成为 SalesPerson
抽象类,案例如下所示。
#Ex07-02
from abc import ABC, abstractmethod
class SalesPerson(ABC):
@abstractmethod
def GetTotal(self): pass
@abstractmethod
def Display(self): pass
#---------------------------------------------
alice = SalesPerson("Alice", "Male")
alice.SetFee(2000, 0.8)
alice.Display()
其中,GetTotal()和 Display()函数的实现部分被抽掉,就称为抽象函数。由
于 GetTotal()和 Display()函数欠缺实现部分,如果用它创建对象,代码如下。
需要注意的是:使用这方法会发生严重的问题,即计算机运行命令的时候,
·114·
第7章 活用抽象类
将找不到实现命令,从而导致程序无法运行,代码如下。
alice.Display();
图 7-3
7.3 从“抽象类”衍生“具象类”
抽象类就如同一间空房子,添置一些家具(如函数的实现部分)就能让人
生活得很愉快。在 Python 里,使用子类来填补函数的实现部分,程序代码如下。
#Ex07-03
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def __init__(self, na, sx):
self.name = na
self.sex = sx
@abstractmethod
def GetTotal(self): pass
@abstractmethod
def Display(self): pass
·115·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#---------------------------------------------
class SalesEngineer(SalesPerson):
def __init__(self, na, sx):
super().__init__(na, sx)
def GetTotal(self):
return self.base_fee * self.discount
def Display(self):
print("Name:", self.name)
print("Fee:" , self.GetTotal())
#---------------------------------------------
alice = SalesEngineer("Alice", "Male")
alice.SetFee(2000, 0.8)
alice.Display()
图 7-4
上述简单例子中,说明了两个重要动作。
抽象:将一般类的变化部分去掉,留下来相同部分,即抽象类。
衍生:给有预留而不完整的抽象类添加一些特殊功能,成为具象类,再
创建对象。
抽象类,并非是某一个具体的类,也不能用来创建对象。其实它可
以衍生出无数个具体子类,创建出无数种对象。抽象类中的抽象函数内
容常是空的,“衍生”的动作则更进一步发挥这种效果。
人们很容易创造具象类,而不容易创造出抽象类。不过,当用户懂得善于
利用眼前的“无用”来换取长远的“有用”时,创造与使用抽象类就变得易如
反掌。
如果抽象类里的“所有”函数都欠缺实现部分,就称为纯粹抽象类。案例
·116·
第7章 活用抽象类
代码如下。
#Ex07-04
from abc import ABC, abstractmethod
class SalesPerson(ABC):
@abstractmethod
def AddFee(self, fee): pass
@abstractmethod
def GetTotal(self): pass
@abstractmethod
def Display(self): pass
#---------------------------------------------
class SalesEngineer(SalesPerson):
def __init__(self, na, sx):
self.name = na
self.sex = sx
self.base_fee = 1000
self.discount = 0.5
def AddFee(self, fee):
self.base_fee += fee
def GetTotal(self):
return self.base_fee * self.discount
def Display(self):
print("Name:", self.name)
print("Fee:" , self.GetTotal())
#---------------------------------------------
alice = SalesEngineer("Alice", "Male")
alice.AddFee(2000)
alice.Display()
·117·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 7-5
7.4 抽象类的妙用:默认行为
#Ex07-05
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def GetTotal(self):
return self.base_fee * self.discount
·118·
第7章 活用抽象类
def Display(self):
print("Name:", self.name)
print("Fee:" , self.GetTotal())
#---------------------------------------------
class SalesEngineer(SalesPerson):
def __init__(self, na, sx):
super().__init__(na, sx)
def Display(self):
print("Name:", self.name,", Fee:" , self.GetTotal())
#---------------------------------------------
class SalesSecretary(SalesPerson):
def __init__(self, na, sx):
super().__init__(na, sx)
def GetTotal(self):
return super().GetTotal() - 100
#---------------------------------------------
alice = SalesEngineer("Alice", "Male")
alice.AddFee(2000)
alice.Display()
图 7-6
·119·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
7.4.2 默认行为的意义
上述 SalesPerson 类的 GetTotal()扮演“默认函数”的角色,表达默认行为,
这也是软件设计的重要观念。像汽车的自动挡一样,优点:汽车会“自动”按
照速度换挡,即会自动维持汽车的平稳行驶。如你告诉出租车司机“到机场”,
司机会依照其经验习惯而选取路线,让你舒适地抵达机场。而且,你还可以指
导司机,按照你的意思而“修正”其习惯。因此,默认的重要特色如下。
让用户更加轻松:如汽车会自动换挡,司机就轻松许多,且司机会选择
理想的路线,乘客不必操心。
默认是可修正的:这只适合一般状况,若有特殊的状况发生,应该立即
修正。例如,波音 747 客机会依照程序起降,但遭遇特殊状况(如碰到
一大群鸽子),飞行员会立即修正。这时,飞行员的判断凌驾于程序之
上,达到修正的目的。在计算机软件上,操作系统( OS)包含了各种
函数,自动运行并协调硬件运行,降低应用程序的负担。在 Windows
的事件驱动观念中, Windows 会不断与应用程序沟通,不断修正其行
为,以对外界的事件提供迅速的反应和服务。默认函数扮演“备胎”的
角色,当子类并未覆写该函数时,就会使用备胎。
7.5 默认函数的妙用:反向调用
当子类继承父类,而且覆写父类的函数时,会产生反向调用的现象,也就
是父类的函数调用子类的函数。虽然父类(前辈)创建时,子类(晚辈)通常
还没有创建;但父类有时候可预知子类中的某个函数,并调用它。像 Android
这样的应用框架里的抽象类就是扮演父类的角色,只是含有一些高端型的类,
它提供通用但不完整的函数,是设计师刻意留给应用程序的子类补充的。一旦
补充(通过函数覆写的手段)完成,框架里的父类的函数就可以“反向调用”
子类里的函数。如下面的范例,代码如下。
·120·
第7章 活用抽象类
#Ex07-06
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def display(self):
print("Fee:", self.GetTotal())
@abstractmethod
def GetTotal(self): pass
#---------------------------------------------
class SalesSecretary(SalesPerson):
def __init__(self, na, sx):
super().__init__(na, sx)
def GetTotal(self):
return self.base_fee * self.discount - 100
#---------------------------------------------
linda = SalesSecretary("Linda Fan", "Female")
linda.SetFee(5000, 0.7);
linda.display();
·121·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 7-7
此程序运行如下:
linda.display()
def display(self):
print("Fee:", self.GetTotal())
然后,继续运行如下命令:
self.GetTotal()
此 时 由 于 SalesSecretary 类 覆 写 了 GetTotal() 函 数 , 于 是 转 而 运 行
SalesSecretary 类的 GetTotal()函数:
def GetTotal(self):
return self.base_fee * self.discount - 100
该程序显示“抽象类 + 默认函数”的组合,产生以下现象。
1. 程序运行时,主控权在抽象类手上
虽然主程序部分仍是程序的启动者,但主要的处理过程在 SalesPerson 的
display()函数内,是它决定调用 GetTotal()函数的。
2. 具象类的函数,主要供抽象类调用
3. 由于抽象类掌握主控权,复杂的命令都放在抽象类中
这种方式大大减轻了具象类开发者的负担。
下面再看一个复杂的范例,更凸显抽象类的主控地位,如图 7-8 所示。
·122·
第7章 活用抽象类
Product Person
PC
Customer
(抽象类)
TV VIP
图 7-8
以 Python 表示,代码如下。
#Ex07-07
from abc import ABC, abstractmethod
class Person(ABC):
def __init__(self, na):
self.name = na
@abstractmethod
def display(self): pass
class Customer(Person):
def display(self):
print("Customer:", self.name)
class Product:
def __init__(self, no):
self.pno = no
def soldTo(self, cobj):
self.pc = cobj
def inquire(self):
self.disp()
·123·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
print("sold to ...")
self.pc.display()
@abstractmethod
def disp(self): pass
#---------------------------------------------
class VIP(Customer):
def __init__(self, na, t):
super().__init__(na)
self.tel = t
def display(self):
super().display()
print("TEL:", self.tel)
class TV(Product):
def __init__(self, no, pr):
super().__init__(no)
self.price = pr
def disp(self):
print("TV No:", self.pno)
print("Price:", self.price)
#---------------------------------------------
t = TV(1100, 1800.5)
vp = VIP("Peter", "666-8899")
t.soldTo(vp)
t.inquire()
图 7-9
·124·
第7章 活用抽象类
该程序凸显抽象类的重要性:
⊙ 程序运行时,主控权在抽象类手上。
虽然主程序部分仍是启动者,但主要的控制逻辑都在 Product 类里面。
soldTo()负责搭配产品与顾客的关系。
Inquire()负责调用 TV 的 print()输出产品数据,并调用 VIP 的 display()
输出顾客数据。
⊙ 具象类 的成员函数,主要供抽象类调用。例如,TV 类的 print()供
Inquire()调用,而 VIP 类的 display()供 Inquire()调用。
⊙ 抽象类掌握主控权。复杂的命令都在抽象类中,其大幅简化了具象类
的函数内容。
⊙ 抽象类里的 Inquire()进行反向沟通,它调用子类的 print(),这是同体系
内的反向调用。
⊙ 抽象类里的 Inquire()反向调用不同体系的 display()。因 Product 与 VIP
分属不同的类体系,这是跨越体系的反向沟通。
其中,Product 父类设计在先,然后才衍生 TV 子类,而且由不同人设计。
那么,为什么 Product 类的 Inquire()能大胆地调用 TV 类的 disp()函数呢?万
一 TV 类并无 disp()时,怎么办?答案很简单:
① TV 类必须定义 disp()函数,才能成为具象类。因为 Product 里的 disp()
是抽象函数,代码如下。
@abstractmethod
def display(self): pass
其中的 abstract,提示子类必须补充后,才能成为具象类。
TV 类成为具象类,才能创建对象,有了对象才能调用 Inquire() 函数。
既然 TV 类已覆写 disp() 函数,Inquire()就可以调用它。于是,为 TV
类增添 Print()函数如下。
def disp(self):
print("TV No:", self.pno)
print("Price:", self.price)
运行时,就产生反向调用的现象。
② Product 类的 Inquire() 调用 VIP 类的 display()函数。Product 类与 VIP
类并非同一个类体系,说明如下。
·125·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
VIP 类必须是具象类,才能创建对象。
PC 变量必须参考刚创建的对象。
由 PC 所参考的对象来运行其 display()函数。
Inquire()就通过 PC 而成功地调用 VIP 类的 display()。
这过程有个先决条件:VIP 类必须定义 display()函数才行。否则将会调用
Customer 类的 display()函数,而不是调用 VIP 类的 display()函数。
·126·
第8章
8
发挥“多态性”
8.1 “多态性”的意义
8.2 多态函数
8.3 可覆写函数
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
8.1 “多态性”的意义
8.1.1 自然界的多态性
多态性(Polymorphism)在生物学上表示“多种”不同的“形状”,即同
种生物中,可分为更细的类,各类之间,外表及行为都有所差异。例如:蜜蜂
族群中,含有蜂后、雄蜂、工蜂等不同形状的蜜蜂,它们的职责功能并不相同。
此“同中有异”的现象,称为“多态性”,也就是一般生活上大家常说的“多
样性”。
日常生活中,常将纸币投入自动贩卖机购买可乐、汽水等。在火车站,很
多人从自动售票机上购买火车票。若设计自动售票的软件系统,则与此系统有
关的类是﹕售票机和纸币,如图 8-1 所示。
售票机 纸币
图 8-1
纸币和蜜蜂一样,具有多态性。此时,可设计多个函数,让售票机能接受
及分辨纸币体系的对象,如下。
售票机.sell(一元纸币)
售票机.sell(五元纸币)
售票机.sell(十元纸币)
这 sell()为“售票机”类的函数,能接受纸币对象的参考值,然后根据对
象的类而自动寻找适当的函数。这不但给予软件设计者方便;更重要的是它也
带给使用者很大的方便—无论使用者拥有一元、五元或十元的纸币,均可投
入售票机买票。我们可将售票机视为容器,其包容不同类型的纸币。有了多态
性,售票机就能对纸币类体系的对象一视同仁。
·128·
第8章 发挥“多态性”
由于售票机不需要判断纸币的币别,就能算出金额,这大幅降低了售票机
系统的复杂度。由于容器是系统整合的重要机制,所以善用多态性能大幅降低
系统整合的复杂度,而成为人人喜爱的工具。
8.1.2 多态性物体
(Function Overriding)观念用来创造多态性对象。函数覆写的
“函数覆写”
方法是:子类覆写父类的函数。由于父类、子类的继承关系,子类是父类中的
一种,所以父类、子类为同种 ;然而被覆写的函数,在父类与子类内的实现代
码不同,亦即父类、子类的对象,会有不同的行为。如同蜜蜂族群中,虽为同
种,但不同种类的蜂,其外形及职责有所不同。所以上述父类、子类内的对象,
称为多态性对象(Polymorphic Object)。如图 8-2 所示为销售人员(部门)与
销售经理、销售工程师的父类、子类关系图。
SalesPerson
bonus()
SalesEngineer SalesManager
bonus() bonus()
y:SalesEngineer
x:SalesPerson z:SalesManager
m:SalesEngineer
图 8-2
x.bonus();
·129·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
y.bonus();
z.bonus();
;
但它们分别调用不同的 bonus()函数,多态对象常存在一个数组中,如
图 8-3 所示。
y:SalesEngineer
m:SalesEngineer z:SalesManager
(参考的)
对象参考的数组
图 8-3
在此数组中,可知销售部门包括一位经理 z 与两位销售工程师 y 及 m。
因他们为同部门的人员,故应该放置于同一数组或串行中,然而他们却属于不
同的子类。多态性带给软件设计者极大的方便,因为计算机会根据对象的类而
自动寻找适当的函数。例如:
z.bonus()
8.2 多态函数
因为“函数覆写”(Function Overriding)观念创造多态性对象,因此通过
·130·
第8章 发挥“多态性”
类继承机制而形成的父子类之间的“可覆写函数”(Overridable Function),就
通称为多态函数(Polymorphic Function)。案例程序代码如下。
#Ex08-01
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def __init__(self, na, a):
self.name = na
self.total_amount = a
def bonus(self):
return self.total_amount * 0.008
class SalesEngineer(SalesPerson):
def bonus(self):
return super().bonus() + 500
class SalesManager(SalesPerson):
def bonus(self):
return super().bonus() + 1000
#-------------------------------------------
z = SalesManager("z's bonus:", 5000)
print(z.name, z.bonus())
图 8-4
·131·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
8.3 可覆写函数
#Ex08-02
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def __init__(self, a):
self.total_amount = a
def bonus(self):
return self.total_amount * 0.008
class SalesEngineer(SalesPerson):
def bonus(self):
return super().bonus() + 500
class SalesManager(SalesPerson):
def bonus(self):
return super().bonus() + 2000
#-------------------------------------------
p = []
peter = SalesManager(20000)
alvin = SalesEngineer(80000)
lily = SalesEngineer(90000)
p.append(peter)
p.append(alvin)
p.append(lily)
for i in range(0, 3 , 1):
print(p[i].bonus())
·132·
第8章 发挥“多态性”
图 8-5
print( p[i].bonus() )
peter:SalesManager
total_amount: 20000
alvin:SalesEngineer
(参考的) total_amount: 80000
lily:SalesEngineer
total_amount: 50000
图 8-6
·133·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex08-03
from abc import ABC, abstractmethod
class SalesPerson(ABC):
def __init__(self, a):
self.total_amount = a
def bonus(self):
return self.total_amount * 0.008
class SalesEngineer(SalesPerson):
def bonus(self):
return super().bonus() + 500
class SalesManager(SalesPerson):
def bonus(self):
return super().bonus() + 2000
#-------------------------------------------
def comp_bonus(sp):
print(sp.bonus())
p = []
peter = SalesManager(20000)
alvin = SalesEngineer(80000)
lily = SalesEngineer(90000)
p.append(peter)
p.append(alvin)
p.append(lily)
for i in range(0, 3 , 1):
comp_bonus(p[i])
·134·
第8章 发挥“多态性”
图 8-7
日常生活中,常将纸币投入自动贩卖机购买可乐、汽水等。若设计自动贩
卖机的软件系统,则与此系统有关的类是贩卖机和纸币,如图 8-8 所示。
图 8-8
纸币像蜜蜂一样,具有多态性,各子类都覆写了 value()函数。此时,可设
计 sell()函数,让贩卖机能接受及分辨纸币体系的对象。例如:
贩卖机.feedCoin(一美元纸币);
贩卖机.feedCoin(五美元纸币);
贩卖机.feedCoin(十美元纸币);
·135·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
该 feedCoin()函数为“贩卖机”类的函数,能接受纸币的对象参考,然后
根据对象的类而自动寻找适当的 onValue()函数。这不但给予软件设计者方便,
更重要的是它也给使用者带来了很大的方便,无论使用者拥有一美元、五美元,
还是十美元的纸币,都可以投入贩卖机购买物品。
现在,看看如何以 Python 程序代码来落实上面内容的多态性对象,代码
如下。
#Ex08-04
from abc import ABC, abstractmethod
class Coin(ABC):
@abstractmethod
def onValue(): pass
#-----------------------------------------------
class one_dollar(Coin):
def onValue(self):
return 1.0
class five_dollar(Coin):
def onValue(self):
return 5.0
class ten_dollar(Coin):
def onValue(self):
return 10.0
#-------------------------------------------------
class Wallet:
def __init__(self):
self.size = 0
self.coll = []
def feedCoin(self, c):
self.coll.append(c)
self.size += 1
def value(self):
return self.calculate_children_values()
def calculate_children_values(self):
·136·
第8章 发挥“多态性”
self.mSum =0;
for i in range(0, self.size , 1):
self.mSum += self.coll[i].onValue()
return self.mSum;
class VendingMachine:
def __init__(self):
self.mWallet = Wallet()
def feedCoin(self, c):
self.mWallet.feedCoin(c)
def showAmount(self):
print("amt:", self.mWallet.value())
#--------------------------------------------------
vm = VendingMachine()
coin = five_dollar()
vm.feedCoin(coin)
coin = ten_dollar()
vm.feedCoin(coin)
coin = one_dollar()
vm.feedCoin(coin)
vm.showAmount();
图 8-9
·137·
第9章
9
如何设计抽象类
9.1 抽象:抽出共同的现象
9.2 抽象的步骤
9.3 洞悉“变”与“不变”
9.4 着手设计抽象类
第9章 如何设计抽象类
9.1 抽象:抽出共同的现象
图 9-1 图 9-2
我们就称这个过程为“抽象”过程,并称此图形为“抽象图”,其只包含
共同的部分,而不同的部分没有显示出来。原有的直角及圆角方形,为完整的
图形,称为“具象图或实体图”。一旦有了抽象图,就可复用(Reuse)它衍生
出各种具象图,既方便又快捷。
用途 1 — 衍生直角方形。
复制一份抽象图,在图的四角分别加上┌、┘、└及┐,就成为直角图形,
如图 9-3 所示。
用途 2 — 衍生圆角方形。
·139·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
复制一份抽象图,在图的四角分别加上╭、╰、╯及╮,就成为圆角图形,
如图 9-4 所示。
用途 3 — 衍生球角方形。
复制一份抽象图,在图的四角各加上 ,结果如图 9-5 所示。
上述的例子中,说明了两个重要动作:
抽象—从相似的事物中,抽离出共同点,得到抽象的结构。
衍生—以抽象结构为基础,添加一些功能,成为具体事物或系统。
同样,在软件方面,也常常做类似的动作:
抽象—在同领域的程序中,常含有许多类,这些类有共同点。程序员
将类的共同结构抽离出来,称为抽象类。
衍生—基于通用结构里的抽象类,添加特殊功能,成为具象类,再创
建对象。
所以“抽象类”存在的目的,是衍生子类,而不是通过它本身创建对象。
由于抽象类本身不创建对象,所以有些函数并不完整。相反,如果类内的函数,
都是完整的,而且要用来创建对象,就称它为具象类。所谓不完整,就是函数
的内容有缺失,程序代码如下。
class Person:
………
@abstractmethod
def Display(self): pass
这个 Display()函数内的命令不完整,等待子类补充,代码如下。
#Ex09-01
from abc import ABC, abstractmethod
class Person:
·140·
第9章 如何设计抽象类
@abstractmethod
def Display(self): pass
#---------------------------------------
class Employee(Person):
def SetName(self, na):
super().SetName(na)
def Display(self):
print("Employee:", self.name)
#---------------------------------------
p = Employee()
p.SetName("Peter Chen")
p.Display()
图 9-6
9.2 抽象的步骤
那么,什么时候会出来像 Person::Display()这种抽象的(即内容缺失)函
数呢?答案是:在上述步骤中,抽离出共同点时,因为 Display()函数的内容不
同,只抽离出函数名称而已。如下例所示。
·141·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex09-02
from abc import ABC, abstractmethod
class Customer:
def SetName(self, na):
self.name = na
def Display(self):
print("Cust:", self.name)
class Employee:
def SetName(self, na):
self.name = na
def Display(self):
print("Emp:", self.name, ", SA:", self.salary)
#--------------------------------------------------
c = Customer()
c.SetName("Tom Lin")
c.Display()
p = Employee()
p.SetName("Peter Chen")
p.SetSalary(50000)
p.Display()
此程序的输出结果如图 9-7 所示。
图 9-7
·142·
第9章 如何设计抽象类
这个程序含有两个类:Customer 和 Employee,两者的命令有相同的地方。
首先关注其 相同部分, 把相同的数 据成员和成 员函数抽离 出来,归到 父 类
Person 中,代码如下:
class Person(ABC):
def SetName(self, na):
self.name = na
最后,将名称相同但内容不同的函数抽离出来,成为抽象函数,代码如下:
class Person(ABC):
def SetName(self, na):
self.name = na
@abstractmethod
def Display(self): pass
由于只抽出 Display()的名称,而缺少内容,这就是抽象函数。于是,Person
就成为抽象类。程序代码如下。
#Ex09-03
from abc import ABC, abstractmethod
class Person(ABC):
def SetName(self, na):
self.name = na
@abstractmethod
def Display(self): pass
#-------------------------------------------------
class Customer(Person):
def Display(self):
print("Cust:", self.name)
class Employee(Person):
def SetSalary(self, sa):
self.salary = sa
def Display(self):
·143·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#--------------------------------------------------
c = Customer()
c.SetName("Tom Lin")
c.Display()
p = Employee()
p.SetName("Peter Chen")
p.SetSalary(50000)
p.Display()
图 9-8
这个 Display()函数变成一个可覆写的函数,于是 Display()就自动成为此类
体系的多态函数。在抽象类 Person 里也可以设计一个 Disp()函数调用 Display
多态函数,这就是上一章介绍的“反向调用”。程序代码如下。
#Ex09-04
from abc import ABC, abstractmethod
class Person(ABC):
coll = []
counter = 0
def Disp(self):
for i in range(0, Person.counter, 1):
·144·
第9章 如何设计抽象类
self.coll[i].Display()
@abstractmethod
def Display(self): pass
#-------------------------------------------------
class Customer(Person):
def Display(self):
print("Cust:", self.name)
class Employee(Person):
def SetSalary(self, sa):
self.salary = sa
def Display(self):
print("Emp:", self.name, ", SA:", self.salary)
#--------------------------------------------------
c = Customer("Tom Lin")
p = Employee("Peter Chen")
p.SetSalary(50000)
c.Disp()
print("-------------------------")
p.Disp()
图 9-9
从上述范例中,可归纳出“抽象”的 3 个动作。
分辨—明察秋毫,把稳定与善变部分区分出来。
封藏—把差异部分的命令封藏于子类中。
·145·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
抽象—把类的共同点抽象出来,成为抽象类。
在 Python 程序上,抽象类必须与具象类合作,才能创建对象提供服务;
抽象类和具象类有密切的互动,因而必须熟悉如下两项重要的技能,才能完成
这样一个抽象过程:
产生抽象类。
加入默认(Default)命令,提高弹性,保持共通性。
现在,创建一个抽象类。从一个简单 Python 程序开始,代码如下。
#Ex09-05
from abc import ABC, abstractmethod
class Employee:
def __init__(self, na, sex):
self.name = na
self.sex = sex
def Display(self):
mFee = self.base_fee * self.discount
print(self.name, "'s fee:", mFee)
#-------------------------------------------------
class Customer:
def __init__(self, na, sex):
self.name = na
self.sex = sex
def Display(self):
·146·
第9章 如何设计抽象类
#--------------------------------------------------
emp = Employee("Tom", "M")
cust = Customer("Lily", "F")
emp.SetFee(1000, 0.9)
cust.SetFee(500, 0.75)
emp.Display()
cust.Display()
图 9-10
class Person:
def __init__(self, na, sex):
self.name = na
self.sex = sex
其他部分仍留在原类中,以下案例代码和上述程序类似。
#Ex09-06
from abc import ABC, abstractmethod
class Person:
·147·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#-----------------------------------------------
class Employee(Person):
def SetFee(self, fee, disc):
self.base_fee = fee
self.discount = disc
def Display(self):
mFee = self.base_fee * self.discount
print(self.name, "'s fee:", mFee)
#-------------------------------------------------
class Customer(Person):
def SetFee(self, fee, disc):
self.ann_fee = fee
self.discount = disc
def Display(self):
mFee = self.ann_fee * self.discount
print(self.name, "'s fee:", mFee)
#--------------------------------------------------
emp = Employee("Tom", "M")
cust = Customer("Lily", "F")
emp.SetFee(1000, 0.9)
cust.SetFee(500, 0.75)
emp.Display()
cust.Display()
图 9-11
·148·
第9章 如何设计抽象类
到此,抽象的结果是得到较高层的 Person 类。
#Ex09-07
from abc import ABC, abstractmethod
class Person:
def __init__(self, na, sex):
self.name = na
self.sex = sex
#-----------------------------------------------
class Employee(Person):
def SetFee(self, fee, disc):
self.base_fee = fee
self.discount = disc
def Display(self):
mFee = self.base_fee * self.discount
print(self.name, "'s fee:", mFee)
#-------------------------------------------------
class Customer(Person):
def SetFee(self, fee, disc):
self.ann_fee = fee
self.discount = disc
def Display(self):
·149·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#--------------------------------------------------
emp = Employee("Tom", "M")
cust = Customer("Lily", "F")
emp.SetFee(1000, 0.9)
cust.SetFee(500, 0.75)
emp.Display()
cust.Display()
图 9-12
两个类各有一个 Display()函数。仔细观察后,会发现不同点。
此时,两个类各定义一个 GetFee()函数来将不同点隐藏起来。于是,Display()
函数变为相同点了,并可置入抽象类中,这时多态函数就可以派上用场。现在
为这两个类各定义一个 GetFee()函数,程序代码如下。
#Ex09-08
class Person():
def __init__(self, na, sex):
self.name = na
self.sex = sex
def Display(self):
mFee = self.GetFee()
print(self.name, "'s fee:", mFee)
@abstractmethod
·150·
第9章 如何设计抽象类
#-----------------------------------------------
class Employee(Person):
def SetFee(self, fee, disc):
self.base_fee = fee
self.discount = disc
def GetFee(self):
return self.base_fee * self.discount
#-------------------------------------------------
class Customer(Person):
def SetFee(self, fee, disc):
self.ann_fee = fee
self.discount = disc
def GetFee(self):
return self.ann_fee * self.discount
#--------------------------------------------------
emp = Employee("Tom", "M")
cust = Customer("Lily", "F")
emp.SetFee(1000, 0.9)
cust.SetFee(500, 0.75)
emp.Display()
cust.Display()
图 9-13
·151·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Display()函数变为相同点,可置入抽象类中。此例是让用户了解,通过
GetFee()可覆写函数将差异点覆盖起来,然后将 Display()函数抽象出来,置入
到抽象父类里。
9.3 洞悉“变”与“不变”
想得出好的抽象类,就得做好抽象工作,此时的常规操作是洞悉及分离出
“变”
(或稳定)与“不变”
(或善变);也就是抽离出“稳定”的部分,以抽象
类表示,并以具象类表达剩下的“善变”部分。
在前文中,曾经介绍过火锅店案例,对火锅桌子加以观察,会发现除锅的
部分不同外,其余相同;于是将锅与桌子分离,得出一致的接口,如图 9-14
所示。
接口
桌子
(分离)
图 9-14
依据同样的思维,下面看一个稍微复杂一点的例子。如有 3 个客人需要的
桌子有部分不一样,如图 9-15 所示。
图 9-15
·152·
第9章 如何设计抽象类
因为不一样的部分(变化的部分),其界线并不一致,如果直接将不一样
的部分挖空,则无法得出一致的接口,使得架构难以搭配各式各样的多样化
小模块。此时可以想象用 3 个大碗将桌子上的不一致部分盖起来,如图 9-16
所示。
接着,以大碗的边缘为界线,得出一个接口,让多样化的部分通过该接口
与桌子结合,如图 9-17 所示。
客 人 #1 客 人 #2 客 人 #N
的需要 的需要 的需要
图 9-16
如同插座
如同房子
如同插头
如同冰箱
图 9-17
从这个简单的例子中,应该能体会到设计抽象类的基本思维:细心分清稳
定与善变的界线,将其“分”离开来成为两种不同的模块(即类),然后随着
客人多样性的需求而将模块组“合”成为各式各样的产品(即系统)。
火锅桌子模块是“实”的,在桌面上挖一个洞后,得到一个接口,此接口
塑造出一个“虚”的空间,此虚的空间可用来容纳多样性的小模块—石头火
锅、韩国碳烤等,而这些小模块也是“实”的。其积极效果就是日后可按照新
·153·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
如同插座
如同房子
如同插头
吹风机
冰箱 电视机 果汁机
图 9-18
例如畚箕的中间是空、虚的,才能装泥土、垃圾等各式各样的东西。此外,
畚箕中间的空间,创造了畚箕的复用性,装完了泥土并倒掉后,还可拿来装垃
圾,不断进行循环使用,一直到坏掉为止。用户也可以深刻体会到软件模块设
计的思维,将稳定与善变的部分“分”离开,在稳定的基础结构上塑造出虚的
空间,来容纳多样性的(善变的)小模块,组“合”成各式各样的产品。
例如,新的电器只要有统一的接口,就可以与房子结合,增加房子的新功
能。电器的生产者不必管房子的形式,只要接口符合就能尽情设计生产新的电
器,使得电器种类迅速增加。
同样,房子的开发商也不必管电器的种类,房子只要提供标准的接口就行,
所以房子也可以迅速增加,为电器创造更多的使用场合。
9.4 着手设计抽象类
上一节所举的例子,都是日常生活中的经验,其设计思维很容易对应到软
件上,如有两个 Python 类,各代表学生注册领域里的概念:“大学生”与“研
究生”,代码如下。
·154·
第9章 如何设计抽象类
#Ex09-09
from abc import ABC, abstractmethod
class UnderGraduate():
def __init__(self, na):
self.name = na
#-----------------------------------------------
class Graduate():
def __init__(self, na):
self.name = na
#--------------------------------------------------
Lily = UnderGraduate("Lily Wang")
t1 = Lily.ComputeTuition(5)
print("Lily:", t1)
·155·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 9-19
“大学生”类里的命令:(credit-1)×500。
“研究生”类里的命令:credit×700。
于是,该程序就能分离出:学生接口和学费接口、学生类和学费类等,代
码如下。
#Ex09-10
from abc import ABC, abstractmethod
class Tuition(ABC):
@abstractmethod
def GetValue(self, credit): pass
class UTuition(Tuition):
def GetValue(self, credit):
return (credit -1) * 500
class GTuition(Tuition):
def GetValue(self, credit):
return credit * 700
#-----------------------------------------------
class UnderGraduate():
def __init__(self, na):
self.name = na
·156·
第9章 如何设计抽象类
#-----------------------------------------------
class Graduate():
def __init__(self, na):
self.name = na
#--------------------------------------------------
Lily = UnderGraduate("Lily Wang")
under_tui = UTuition()
Lily.Setter(under_tui)
t1 = Lily.ComputeTuition(5)
print("Lily:", t1)
·157·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
包含 under_tui 对象。接着,运行如下命令:
t1 = Lily.ComputeTuition(5)
图 9-20
#Ex09-11
from abc import ABC, abstractmethod
class Tuition(ABC):
@abstractmethod
def GetValue(self, credit): pass
class UTuition(Tuition):
def GetValue(self, credit):
return (credit -1) * 500
class GTuition(Tuition):
def GetValue(self, credit):
return credit * 700
#-----------------------------------------------
class Student():
def __init__(self, na):
self.name = na
·158·
第9章 如何设计抽象类
#--------------------------------------------------
Lily = Student("Lily Wang")
under_tui = UTuition()
Lily.Setter(under_tui)
t1 = Lily.ComputeTuition(5)
print("Lily:", t1)
图 9-21
这就是多态函数的常见用法,其可以大幅降低软件系统的复杂程度。
·159·
第 10 章
10
接口与抽象类
10.1 接口的意义
10.3 接口设计实例一:并联电池对象
10.4 接口设计实例二:串联电池对象
10.1 接口的意义
变化是世界发展的本质,生物必须不断地进化才能适应外在环境的变化,
否则在“适者生存”的自然法则下,就会被淘汰。生物在适应环境变化的法宝
是:自身会明确分为“稳定”“常变化”及“快速变化”等不同组织,来与外
界环境互动并调整自己,以便在新环境中取得有利的生存空间。例如,树干很
稳定,树枝经常变化,树叶随着四季交替不断代谢。树枝支持树叶取得阳光最
充足的空间,树干则支持树枝不断成长。
仔细区分稳定与善变,会呈现出界线,就称为接口。例如车轮的外胎是善
变的部分;为了便于更换外胎,则轮框与外胎之间含有个明确的界线(接口)。
同理,汽车的轮胎是善变的部分;为了便于更换整个轮胎,则车体与轮胎
之间有个明确的界线(接口),就是轮盘。
在软件开发上,设计接口就是把软件里善变的部分封装于接口内,有必要
时,就把接口内的善变部分更换掉,为软件增添活力。接口规划良好,就易于
更换,也易于后期整合。
区分稳定与善变的重要性之后,剩下的就是如何找到善变的部分。留心观
察,就会发现在不同场合使用软件时,哪些部分必须经常更换,哪些部分偶尔
更换,哪些部分很稳定等。就像汽车行驶在一般街道、高速公路和雪地 3 种场
合时,轮子外胎明显是最经常更换的部分,外胎与整部车子就必须有明确的接
口,且越简单越容易更换。再以时间维度来言,随着时间的演变,哪些部分需
要时常更新。就像汽车虽在街道环境中行驶,轮子外胎易磨损所以必须经常更
换,更换就是把旧外胎“分”离出来,然后将新外胎与汽车整“合”起来。
由于沟通都通过接口,所以必须遵循接口的规定,使得对象内部细节与其
他对象的内部细节之间呈现出一定的独立性。一旦必须更换掉某个对象时,不
会造成牵一发而动全身的现象。既然对象易于更换,那么把软件中善变的部分
包装隐藏在对象的内部,对象接口就成为善变与稳定部分的明确界线,软件就
像有生命的树木一样,能在快速变化的气候环境中保持稳定。
在这里,把接口与类继承体系这两个概念紧密联系起来,特点如下。
接口代表一个空间,凡支持该接口的类体系对象都落在该空间中。例如,
凡支持两脚插头的电器对象都属于“两脚插头”所代表的空间。
凡属于该空间的对象都具有替代性。如台灯坏了,可以用落地灯来替换。
·161·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
该接口的客户端可与该空间的任何对象分合自如,从而创造无数个机会。
由于接口代表一个空间,例如插头和插座各代表一个空间或族群(如一个
类继承体系)。插头与插座两个族群能各自发展,只要接口一致,就能顺利对
接。例如电器从业者可以推出更多种类电器,使得电器体系能无限成长。这两
族群在独立发展的过程中,其接口维持不变,保持其替换性和整合性(即兼容
性)即可。所以,新型电器即可插在新型插座上,也可以和原来的插座搭配使
用。刚才是从消费者的立场考虑,其实这样对生产者也有好处,生产者不用再
考虑插座的内部构造,因为不管插座内部如何变化,只要接口一致即可。另一
方面,生产插座者也不必了解电器的种类及内部构造,只要提供一致的接
口—双脚插头即可。因此,消费者、电器制造者及插座制造者三方都从中获
得益处。
一个对象能有多个接口,而且数个对象可以使用同一接口。由于有不同的
接口,对象就适用于不同的环境,提高了对象的复用性。也由于数个对象有共
同的接口,这些对象就具有了互换性,让软件的维护更加容易。例如,类体系
如图 10-1 所示。
Animal
Bear Dog
a1:Animal
图 10-1
·162·
第 10 章 接口与抽象类
Animal
接口
a1:Animal
App#1
Animal 接口
Bear
接口
b1:Bear
App#2
图 10-2
#Ex10-01
from abc import ABC, abstractmethod
class IDance(ABC):
@abstractmethod
def dance(self): pass
@abstractmethod
def sing(self): pass
·163·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
class Bear(IDance):
def dance(self):
print("Bear is dancing.")
def sing(self):
print("Bear is singing.")
class Dog(IDance):
def dance(self):
print("Dog is dancing.")
def sing(self):
print("Dog is singing.")
#-----------------------------------
class DancerFactory:
def Create(self):
return Dog()
#-----------------------------------
factor = DancerFactory()
dancer = factor.Create()
dancer.dance()
dancer.sing()
图 10-3
#Ex10-02
from abc import ABC, abstractmethod
class IDance(ABC):
@abstractmethod
·164·
第 10 章 接口与抽象类
@abstractmethod
def sing(self): pass
class Bear(IDance):
def dance(self):
print("Bear is dancing.")
def sing(self):
print("Bear is singing.")
class Snoopy(IDance):
def dance(self):
print("Snoopy is dancing.")
def sing(self):
print("Snoopy is singing.")
#-----------------------------------
class DancerFactory:
def Create(self):
return Snoopy()
#-----------------------------------
factor = DancerFactory()
dancer = factor.Create()
dancer.dance()
dancer.sing()
图 10-4
·165·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Animal Animal
接口 接口
a:Bear b:Dog
App#1 App#1
App#2 App#2
图 10-5 图 10-6
·166·
第 10 章 接口与抽象类
10.3 接口设计实例一:并联电池对象
10.3.1 不理解原理但也能用
设计工作含有艺术成分,设计师不同设计的结果也不同。这个案例从技术角
度来分析设计带来的经济效益—“不理解原理但也能用”角度来讨论接口的设
计境界。
在编写 Python 程序时,经常会用到 Numpy、Matplotlib 或 Pandas 里的许
多类(或对象),它们也都伴随着接口。对 Python 程序员而言,设计类(也就
·167·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
是决定应该撰写那些类)并不难。然而,设计接口(也就是决定该定义那些接
口)就需要一定的创意,因此会难倒不少人。
事实上,只要用户理解“不知而亦能用”的经济效益,就可迎刃而解。对
象本身分为:稳定的外观接口与善变的内部实现。对象 A 的设计者,只需要思
考对象 B 的接口,但不需要理解对象 B 如何实现。
就像我们不知道 Intel CPU 的内部设计,但只要知道其接口就能使用。要
知道,Intel CPU 内有 Intel's Design Inside!其内部实现细节价值连城,Intel 可
以让用户拥有 CPU(也知道 CPU 接口的用法),但不会让用户掌握 CPU 内部
的设计思维!
日常生活中,大家应该都用过手电筒、电
池、灯泡等对象,手电筒是大对象,其内包含
电池 A 电池 B
有电池、灯泡等小对象,如图 10-7 所示。
手电筒 电池对象有其接口,所以能串联起来,也
图 10-7 能与手电筒衔接。灯泡也有接口,所以能跟手
电筒衔接,接口如图 10-8 所示。
电池 A 电池 B
灯泡 串联式手电筒
图 10-8
图 10-9
·168·
第 10 章 接口与抽象类
电池 A 电池 B
灯泡 并联式手电筒
图 10-10
接下来说明如何创造接口和对象,并享受“不理解原理但也能用”的妙处。
10.3.2 实现步骤
从图 10-10 所示可以看出手电筒、电池和灯泡之间的关系。首先从简单的
并联式电池对象入手,在并联式手电筒里,手电筒是电池对象的 Client,所以
电池对象只需提供接口给手电筒对象使用即可。
此实例中,采用纯粹抽象类,想象电池继承体系有个纯粹抽象类,如图 10-11
所示。
纯粹抽象类
手电筒
黑猫牌电池 国际牌电池
图 10-11
接着,将纯粹抽象类对应到接口,如图 10-12 所示
依据这个接口设计,用 Python 来实现如下功能。首先定义一个 IPower 接
口,定义两个类:PanasonicCell 和 CatCell,程序代码如下。
·169·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
接口:IPower
手电筒
黑猫牌电池 国际牌电池
图 10-12
#Ex10-03
from abc import ABC, abstractmethod
class IPower(ABC):
@abstractmethod
def GetPower(self): pass
class PanasonicCell(IPower):
def __init__(self):
self.pw = 2
def GetPower(self):
return self.pw
class CatCell(IPower):
def __init__(self):
self.pw = 3
def GetPower(self):
return self.pw
#----------------------------------------------
class FlashLight:
def __init__(self):
self.cell_list = []
self.index = 0
·170·
第 10 章 接口与抽象类
def Power(self):
mSum = 0;
for i in range(0, self.index, 1):
mSum += self.cell_list[i].GetPower()
return mSum
#----------------------------------------------
light = FlashLight()
cell = CatCell()
light.AddCell(cell)
print(light.Power())
print("-----------------")
cell = PanasonicCell()
light.AddCell(cell)
cell = CatCell()
light.AddCell(cell)
print(light.Power())
图 10-13
一样电池的接口设计,能适用于不同结构的并联式手电筒。也展示了不理
解原理但也能用的效果:
(1)电池以相同的接口把电流传给手电筒而使灯泡发光。如有一天电池没
电了,买个接口一样的新电池就能随时更换。
·171·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
(2)一样的电池对象适用于不同样式的手电筒,不论手电筒的厂家,也不
论手电筒的内部结构。
可以想象,电池对象与手电筒对象都是由互相之间不认识的人设计的。如
果你是电池对象的设计者,那么责任就是:设计通用的电池对象,适用于不同
厂家的手电筒;如果你是手电筒的设计者,你可以随时挑选不同厂家的电池对
象,装入你手机的手电筒里使用。
10.4 接口设计实例二:串联电池对象
10.4.1 基本设计
最常见的手电筒都是串联式,如图 10-14 所示。
图 10-14
cd Whole Product
Flashlight
Flashlight Body
Head
图 10-15
·172·
第 10 章 接口与抽象类
cd Part 2
Flashlight Body
+ Battery 1 - + Battery 2 -
图 10-16
FlashLight
1
*
Cell
<<串联>>
黑猫牌电池 国际牌电池
图 10-17
接下来,只要有精致的接口设计,就能随时组合出串联式手电筒。
10.4.2 实现步骤
此案例中,想象有两个纯粹抽象类,如图 10-18 所示。
接着,将纯粹抽象类对应到接口,如图 10-19 所示。
·173·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
纯粹抽象类 纯粹抽象类
手电筒
黑猫牌电池 国际牌电池
图 10-18
接口:ILight 接口:ICell
<<实现>>
图 10-19
#Ex10-04
from abc import ABC, abstractmethod
class ICell(ABC):
@abstractmethod
def GetPower(self): pass
@abstractmethod
def SetLinkToNext(self, nc): pass
class ILight(ABC):
@abstractmethod
def AddCell(self, cp): pass
@abstractmethod
·174·
第 10 章 接口与抽象类
class CatCell(ICell):
def __init__(self):
self.pw = 5
self.next_cell = None
def SetLinkToNext(self, nc):
self.next_cell = nc
def GetPower(self):
if self.next_cell == None:
return self.pw
else:
return self.pw + self.next_cell.GetPower()
#----------------------------------------------
class FlashLight:
def __init__(self):
self.head = None
self.tail = self.head
def AddCell(self, cp):
if self.head == None:
self.head = cp
self.tail = self.head
else:
self.tail.SetLinkToNext(cp)
·175·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
self.tail = cp
def Power(self):
return self.head.GetPower()
#----------------------------------------------
light = FlashLight()
cell = CatCell()
light.AddCell(cell)
print(light.Power())
print("-----------------")
cell = PanasonicCell()
light.AddCell(cell)
cell = CatCell()
light.AddCell(cell)
print(light.Power())
先创建一个手电筒对象,把电池装入手电筒里。例如,将一颗黑猫牌电池
装入手电筒,再装入一颗黑猫牌电池及一颗国际牌电池,则显示出的总电量
如图 10-20 所示。
图 10-20
10.4.3 总结
刚才设计的电池既能适用于并联式手电筒,也适用于串联式手电筒,且能
随时更换。电池不但提供接口给手电筒,也提供接口给别的电池,使得电池设
计人员进行接口设计时必须进行更多地考虑。
基于共同的 ICell 接口,各家电池厂商都能各自开发出符合接口的电池,
凡是符合 ICell 接口的电池,都具有多态性,所以能互相串联起来,也能互相
替换。这些电池对象也适用于并联的手电筒,因用途增多,电池的经济价值也
·176·
第 10 章 接口与抽象类
同步提升。
设计出多态性对象,像电池一样能随时更换。为达成这个目标,必须在设
计当初,就考虑替换时的要点,而不是想把它用到其他场合。自然界中,也喜
欢淘汰旧对象再生新对象,如壁虎巴被其他动物咬住尾巴时,会立即断尾逃生。
壁虎干净利落地丢弃旧对象,然后复用没有被咬住的身体,长出新尾巴(对象),
“恢复”成一只完整的壁虎。
这也符合工业法则,例如汽车的轮胎(对象)需要换掉,不要想去复用轮
胎对象。换掉坏轮胎,等于再生一部汽车,也即能再利用整部汽车的其他好组
件(又称模块),价值极大。换掉坏轮胎,而装上新轮胎,等于使用了未来所
有潜在可用的模块,所以积极换掉旧对象,等于积极再利用未来潜在的新对象。
能低成本地换掉坏组件,就能给软件系统带来弹性及生命力,其对象的新
陈代谢也就更为顺畅。
模式(Pattern)是某个领域(例如建筑业或软件业)里的专家针对该领域
经常出现的问题而给出的常见解决之道(Solution)。例如,围棋有棋谱、烹饪
有食谱、武功有招式等,都是专家和高手的经验心得。由于它经常出现,所
以具有学习和推广的价值。因为是从实务经验中提炼而得,所以具有良好的实
用价值。而且因为出自专家,所以解决方法的质量很高。模式确保对象设计的
质量,也是人们经验智慧的积累,构建出稳定的接口和弹性的系统。在本节中,
将使用 COR(Chain Of Responsibility)设计模式的接口设计技巧。从 Gamma
的 Design Patterns 一书里可得知 Chain Of Responsibility 模式的构造,如图
10-21 所示为 COR 模式的基本结构。
在这个结构里,对象也是串联的,图 10-21 所示的 successor 属性就相当于
前面电池对象例子里的 next_cell 属性,用来连接两个电池对象,以便形成电池
对象之间的串联关系。
·177·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
Handler successor
Client
HandleRequest()
ConcreteHandle1 ConcreteHandle2
HandleRequest() HandleRequest()
图 10-21
虽然上面是一般的类继承关系,但我们也能加上接口,形成继承和接口共
存的结构,这让 Client 程序或对象不必知道有 Handler 父类的存在,就能享受
Handler 父类的服务,这也是“不理解原理但也能用”的效果之一。如图 10-22
所示为使用 COR 模式的手电筒架构设计。
Handler successor
HandleRequest()
(接口和抽象类)
Panasonic 电池 Cat 电池
手电筒
HandleRequest() HandleRequest()
图 10-22
·178·
第 10 章 接口与抽象类
接口:ILight 接口:IHandle
successor
抽象类
手电筒
国际牌电池 黑猫牌电池
图 10-23
# Ex10-05
from abc import ABC, abstractmethod
class IHandle(ABC):
@abstractmethod
def HandleRequest(self, request): pass
@abstractmethod
def SetSuccessor(self, nc): pass
class ILight(ABC):
@abstractmethod
def AddCell(self, cp): pass
@abstractmethod
def Power(self, message): pass
#--------------------------------------------
class Handle(IHandle):
def __init__(self):
self.successor = None
self.pw = 0
·179·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
@abstractmethod
def RequestForMe(self, request): pass
class PanasonicCell(Handle):
def __init__(self):
super().__init__()
self.pw = 10
class CatCell(Handle):
def __init__(self):
super().__init__()
self.pw = 7
·180·
第 10 章 接口与抽象类
return False
#----------------------------------------------
class FlashLight:
def __init__(self):
self.head = None
self.tail = self.head
def AddCell(self, cp):
if self.head == None:
self.head = cp
self.tail = self.head
else:
self.tail.SetSuccessor(cp)
self.tail = cp
def Power(self, message):
if self.head == None:
return 0
else:
return self.head.HandleRequest(message)
#----------------------------------------------
light = FlashLight()
pp = PanasonicCell()
light.AddCell(pp)
cc = CatCell()
light.AddCell(cc)
cc2 = CatCell()
light.AddCell(cc2)
pp = PanasonicCell()
light.AddCell(pp)
print(light.Power("All"))
#print("-----------------")
print(light.Power("Cat"))
#print("-----------------")
print(light.Power("Pan"))
·181·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
一开始,把电池装入手电筒,而手电筒计算出总电量,并显示出来。两颗
黑猫牌电池的电量是 14,而两颗国际牌电池的电量是 20,所以总电量是 34,
结果如图 10-24 所示。
图 10-24
这个程序建立出下述的对象链(Object Chain),并负责不同的任务,所以
称为 Chain Of Responsibility,如图 10-25 所示为电池的对象链。
FlashLight 对象 PanasonicCell 对象
head successor
tail CatCell 对象
successor CatCell 对象
successor
图 10-25
每一个对象都利用一致的接口将后面的对象包起来,统称为把变化
(Change)封装起来,确保对象内部的变化不受外部的干涉,也不会对外部对
象产生“牵一发而动全身”的涟漪效应。
·182·
第 11 章
11
不插电学 AI
11.3 范例:一只老鼠的探索及学习
11.4 记录老鼠的探索选择及结果
11.1 “不插电学AI”的意义
所谓不插电或不接入网络(un-plugged or un-network),即在科技时代里,
不连接电或网络进行“运算思维”。
“运算思维”是现代人的重要素养之一,可
以增加个人的未来竞争力,对于提升个人的信息运算思维、技术、沟通、表达
和使用方法等能力很有帮助。通常信息教育多半从程序开始,不过计算机有时
是造成学习分心的主因,进而变成了解信息科技的一大障碍。
同样,熟练掌握 AI(人工智能)机器学习也是重要的信息科技素养之一,
不过太多的计算机程序涉及微积分、向量算法等,变成了学习 AI 科学的一大障碍。
因此建议大家断开计算机的电源,一起来掌握 AI 这门有趣学问的学习方法。
11.2 AlphaGo的惊人学习能力
11.3 范例:一只老鼠的探索及学习
有一只老鼠居住在一个房间里,这房间的只有 4 个可以出入的洞,而洞外
常常会有猫咪住在那里(如图 11-1 所示,房间有 4 个洞可进出)。当老鼠走出洞
·184·
第 11 章 不插电学 AI
时,若有猫咪住在洞外,老鼠就会害怕、不敢出去。
最近的情况是:老鼠听说房间外面来了几只猫,可能住在洞外,但老鼠并
不清楚到底哪些洞外没有猫,所以影响老鼠安全愉快地进出。
这时,老鼠只好勇敢地试错,以便从经验学习中判断。一开始,老鼠没有
任何经验和知识来采取抉择,它从任何一个洞出去,安全与危险的机会大概各
一半,也就是有 0.5 的概率不会碰到猫,如图 11-2 所示。
图 11-1 图 11-2
图 11-3 图 11-4
·185·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
继续展开探索行动,选择 C 洞,就很轻松地走出来了。玩一会后又安全
地回到房间,它知道自己原来预测的概率值 0.5(只有一半把握)也不对,就
把脑海里这个概率值调整为 1.0,如图 11-5 所示。
继续展开探索行动,选择 D 洞试试,突然有一只猫冲过来,幸运地逃回洞
里。它知道自己原来预测(D 洞)的概率值 0.5 是错的,就把脑海里这个概率
值调整为 0.0,如图 11-6 所示。
图 11-5 图 11-6
老鼠经过 4 次探索后,从经验中学习到相关技能,变得更加聪明。
11.4 记录老鼠的探索选择及结果
由于它(老鼠)担心过几天忘记了这些经验,所以就想把这些经验写在纸
张上。同时,如果有其他老鼠朋友来访,也可以把纸张给朋友们看,以免好朋
友们被猫抓去。
第 1 次探索时,选择了 A 洞而没有选择 B、C 和 D 洞,就以数学上的数组
来表示,就表示为:[1,0,0,0]。如图 11-7 所示。
接着,把第 2 次的探索经验也记录下来。这次探索选择了 B 洞而没有选择
A、C 和 D 洞,同样以数学的数组来表示,即[0,1,0,0]。同样,也把第 3、4 次
的探索经验记录下来,结果如图 11-8 所示。
接着,老鼠发现这样的纪录似乎仍不够完整,最好把“有没有看到猫”的
结果也记载下来,就更完美了,如图 11-9 所示。
·186·
第 11 章 不插电学 AI
图 11-7 图 11-8
其实,结果只有两种可能:“没有猫”或“有猫”。于是,老鼠就拿 1 与 0
来代表。也就是说,以 1 代表成功走出洞外,0 代表看到猫又逃回洞里,
图 11-9 所示的内容又可以演化为图 11-10 所示的结果。
图 11-9 图 11-10
这就包含两个数组,分别是:“探索的选择”数组与“探索结果”数组。并
且拿 X 来表示“探索的选择”数组,拿 T 来表示“探索结果”数组,如图 11-11
所示。
图 11-11
·187·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
11.5 老鼠当教练:训练AI机器人
11.5.1 以简单算数,让机器人表达智能
有一天,老鼠的两位朋友来访,想在老鼠家居住几天。这两位朋友是华硕
公司出产的 Zenbo 机器人,老鼠知道 Zenbo 机器人也很怕猫,所以很想把自己
的经验迅速传授给 Zenbo 机器人。
由于机器人朋友的命令周期更新很快,而且外界的动态(即猫的动态)可
能随时会有新的变化(老鼠会再去探索),老鼠就希望它的机器人朋友发挥其
优秀的运算和学习能力,迅速达到相应的智能程度,进行更好的选择和判断,
而不必花费时间探索每一个洞。
于是,老鼠就想让自己成为教练,把自己的探索经验和智能性传授给机器
人朋友。而且基于机器人的超快运算能力,可能经过不到几秒钟的学习,就有
很好的智能程度了。老鼠进一步思考:
如何教导(或训练)这些机器人朋友呢?
如图 11-12 所示。
老鼠开始想让这机器人能通过快速
(运算能力强)学习,迅速提升其智能。
由于机器人擅长数学运算,于是老鼠就拿
最简单的数学公式(只用数学中的加法和
乘法)来训练机器人。
图 11-12
在上一小节里,曾提到过类似的情
况。一开始,老鼠没有任何经验和知识来采取最好的抉择,它想从任何一个洞
出去,安全与危险的机会均等,也就是有 0.5 的概率不会碰到猫)。所以,一开
始预测各洞的概率值都是 0.5。就用一个简单的数学公式来表示为:
y = x1×0.5 + x2×0.5 + x3×0.5 + x4×0.5
其中,x1、x2、x3、x4 代表一次探索的选择。现在把这个数学公式写入到
Zenbo 机器人的脑海里,如图 11-13 所示。
例如第#0 次探索时选择了 A 洞,就是:
而 y 就代表这次探索的预测值,可以预测出这次能顺利走出房间的概率值,
·188·
第 11 章 不插电学 AI
图 11-13 图 11-14
经过机器人的快速运算,可以算出 y 值
为 : 0.5。 这 y 值 就 代 表 这 次 探 索 的 预 测
值,即是否能顺利走出房间的概率值,如
图 11-15 所示。
重复以上步骤,完成对老鼠智能的模
仿,让机器人运用智能来进行预测,以达到
三思而后行的效果。
图 11-15
11.5.2 机器人智能的提升过程
老鼠在房间里针对其探索的选择,在还
没有任何经验情况下,运用其现有智能预测
后,得出 0.5 预测值(即猜想有 50%是没有
猫)之后,展开行动走出 A 洞,却发现猫追
扑过来,赶快转身奔回洞内。
它回到洞 内 一想,以 它 现在记录 的 智
能,所预测的值 0.5 与实际值 0(即有猫)
图 11-16 两者比较后,存在很大的差异。机器人同样
可以模仿及表达,如图 11-16 所示。
走出 A 洞之前的预测值 0.5,与走出 A 洞时得到的实际值 0,两者比较以
后,发现所依赖的智能有待改进。
那么,如何让机器人表达智能的成长?即如何调整上一小节里记录的智能呢?
可以看看预测值和实际值的差距,即两者相减得到-0.5 的误差,如图 11-17 所示。
·189·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 11-17 图 11-18
图 11-19
·190·
第 11 章 不插电学 AI
非常准确,没有误差,这说明机器人智能提升了。
至此,老鼠对机器人朋友完成第#0 组数据的训练。其中,老鼠拿它记录下
来的经验作为训练数据(Training Data),包括 X[] 和 T[]两个数组:
T =[ 0, …. ] X = [[1,0,0,0],
……. ,
……. ,
……. ]]
通过这些训练数据,来驱动机器人对数学公式(又称:算法)的权重做修
正,让其预测更加准确。
11.5.3 一回生、两回熟
刚才老鼠拿它的第#0 次探索经验记录,作为给 Zenbo 机器人的训练资料,
展开第#0 组数据的训练,可以看到 Zenbo 的智能性有所提升。俗语说:一回生、
两回熟。多一些训练,会让 Zenbo 的智能性提升得更快。于是,老鼠准备给
Zenbo 展开第#1 组数据的训练,这次是以老鼠先前探索 B 洞的经验记录,来作
为训练数据。这次的 X[]数组如下。
[x1, x2, x3, x4] = [0, 1, 0, 0]
而 y 代表这次探索的预测值,于是把 X[]数组[0,1,0,0]带入数学公式里,如
图 11-20 所示。
经过机器人的快速运算,可以算出 y 值为:0.5。这 y 值代表这次能否顺利
走出房间的概率值,如图 11-21 所示。
依据老鼠的经验,它走出 B 洞之前的预测值 0.5,与走出 B 洞时得到的实
际值 0,两者拿来比较一下,发现其依赖的智能有待改进,所以老鼠经历这次
探索后,它的智能性有所提升。
图 11-20 图 11-21
·191·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 11-22 图 11-23
此时,已经修正了数学公式里的权重,修正后权重变为:0.0。而数学公式
也变为:
y = x1×0.0 + x2×0.0 + x3×0.5 + x4×0.5
由于这是基于老鼠的第#1 次探索经验,当时选择了 B 洞,就是:
[x1, x2, x3, x4] = [0, 1, 0, 0]
而 y 就代表这次探索的整体预测值,机器人可以预测出这次能顺利走出房
间的概率值。于是,把这个数组[0,1,0,0]带入数学公式里:
y = x1×0.0 + x2×0.0 + x3×0.5 + x4×0.5
= 0 × 0.0 + 1 × 0.0 + 0 × 0.5 + 0 × 0.5
=0
修正数学公式后,经由运算而得到的预测值是:0,而实际值也是 0,非常
准确。至此,老鼠已经对机器人朋友完成第#1 组数据的训练。其通过训练数据
来驱动机器人对数学公式做修正,让其预测更为准确。
11.5.4 三回变高手
刚才老鼠拿它的第#1 次探索经验记录,作为给 Zenbo 机器人的训练资料,
来展开第#1 组数据的训练,可以看到 Zenbo 的智能性继续提升。如果再提供更
多训练,将会让 Zenbo 的智能性提升更多。于是,老鼠准备给予 Zenbo 展开第
·192·
第 11 章 不插电学 AI
#2 组数据的训练,这次是基于老鼠先前探索 C 洞的经验记录,作为训练数据。
这回合的 X[]数组是:
[x1, x2, x3, x4] = [0, 0, 1, 0]
y 代表这次探索的预测值,机器人可以预测出这次能顺利走出房间的概率。
于是把 X[]数组[0,0,1,0]带入数学公式里,如图 11-24 所示。
经过机器人的快速运算,可以算出 y 值为:0.5。即这次能顺利走出房间的
概率值,如图 11-25 所示。
依据老鼠的经验,它走出 C 洞之前的预测值 0.5,与走出 C 洞时得到的实
际值 1,两者拿来比较一下,发现其所依赖的智能性还有待改进,所以老鼠经
历这次探索后,它的智能性会继续提升。
现在来看看预测值和实际值的差距有多大,两者相减得到误差值:0.5。接
着,继续拿这项误差值(即 0.5)修正机器人里的数学公式,如图 11-26 所示。
图 11-24 图 11-25
图 11-26 图 11-27
·193·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
此时,已修正数学公式里的权重。修正后权重变为:1.0。其数学公式变为:
y = x1×0.0 + x2×0.0 + x3×1.0 + x4×0.5
由于这是基于老鼠的第#2 次探索经验,当时选择了 C 洞,就是:
[x1, x2, x3, x4] = [0, 0, 1, 0]
而 y 代表这次探索的整体预测值,把这个数组[0,0,1,0]带入数学公式里,
如下:
y = x1×0.0 + x2×0.0 + x3×1.0 + x4×0.5
= 0 × 0.0 + 0× 0.0 + 1 × 1.0 + 0 × 0.5
= 1.0
所以,修正数学公式之后,经由运算得到的预测值是:1.0。与实际值一样,
说明机器人的智能性继续提升。
至此,老鼠已经对机器人朋友完成第#2 组数据的训练。通过训练数据来驱
动机器人对其数学公式做修正,让预测更加准确。
11.5.5 第四回合训练:迈向完美
刚才老鼠已经对 Zenbo 机器人展开三个回合的训练,也能看出 Zenbo 的智
能性持续提升。现在,老鼠准备对 Zenbo 展开第#3 组数据的训练,这次是基于
老鼠先前探索 D 洞的经验记录,来作为训练数据。这回合的 X[]数组:
[x1, x2, x3, x4] = [0, 0, 0, 1]
而 y 代表机器人可以预测出这次能顺利走出房间的概率值。于是把 X[]数
组[0,0,0,1]带入数学公式里,如图 11-28 所示。
经过机器人的快速运算,可以算出 y 值为:0.5。这 y 值就是机器人这次能
顺利走出房间的概率值,如图 11-29 所示。
图 11-28 图 11-29
·194·
第 11 章 不插电学 AI
图 11-30 图 11-31
y 就代表机器人可以预测出这次能顺利走出房间的概率值。于是,就把这
个数组[0,0,0,1]带入数学公式里,结果如下:
y = x1×0.0 + x2×0.0 + x3×1.0 + x4×0.0
= 0 × 0.0 + 0 × 0.0 + 0 × 1.0 + 1 × 0.0
=0
修正数学公式后,经由运算而得到的预测值是:0,而实际值也是 0,这说
明机器人的智能性继续提升。至此,老鼠已经对机器人完成第#3 组数据的训练。
其通过训练数据驱动机器人对数学公式做修正,让其预测更加准确。
11.5.6 重新检测一次
经过 4 回合的训练,Zenbo 机器人脑海里的数学公式是:
y = x1×0.0 + x2×0.0 + x3×1.0 + x4×0.0
其中的权重部分,可以表示为:
W = [w1, w2, w3, w4] = [ 0.0, 0.0, 1.0, 0.0]
·195·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
也就相当于:
y = x1×w1 + x2×w2 +x3×w3 + x4×w4
于是,老鼠来检测一下 Zenbo 机器人的智能,看看它依据数学公式而计算
出来的预测值,是否与实际值一致。
Step-0 老鼠读取训练资料 X[]和 T[],把其中的第#0 笔:
T =[ 0,0,1,0 ] X = [ [1,0,0,0 ],
……. ,
……. ,
……. ]
交给 Zenbo 机器人,它立即展开计算:
y = x1×w1 + x2×w2 +x3×w3 + x4×w4
= 1×0.0 + 0×0.0 + 0×1.0 + 0×0.0
=0
得出的预测值是 0,与实际值 0 一致。
Step-1 检测完第#0 笔资料,继续检验第#1 笔:
T =[ 0,0,1,0 ] X=[ ……. ,
[ 0,1,0,0 ] ,
……. ,
……. ]
交给 Zenbo 机器人,它立即展开计算:
y = x1×w1 + x2×w2 +x3×w3 + x4×w4
= 0×0.0 + 1×0.0 + 0×1.0 + 0×0.0
=0
得出的预测值是 0,与实际值 0 一致。
Step-2 检测完第#1 笔资料,继续检验第#2 笔:
T =[ 0,0,1,0 ] X=[ ……. ,
……. ,
[ 0,0,1,0 ] ,
……. ]
交给 Zenbo 机器人,它立即展开计算:
y = x1×w1 + x2×w2 +x3×w3 + x4×w4
= 0×0.0 + 0×0.0 + 1×1.0 + 0×0.0
= 1.0
·196·
第 11 章 不插电学 AI
·197·
第 12 章
12
撰写单层 Perceptron 程序
12.3 进行更多组数据的训练
12.4 加入学习率
12.1 开始“插电学AI”:使用Python
在上一章里,拿老鼠的探索与学习为例,说明动物们(包括人们)的学习
情境。后来,老鼠用自己的经验记录数据,作为训练数据开始训练它的机器人
朋友。
现在,来说明如何用 Python 代码来表示数学式,然后加载到机器人的脑
海里,让机器人发挥它的高速运算能力,进行快速学习。
在上一章里,曾拿一个简单的数学式表示:
y = x1×w1 + x2×w2 + x3×w3 + x4×w4
其中,数组[x1、x2、x3、x4]代表一次探索,而数组[w1, w2, w3, w4]代表
机器人在预测时的权重。机器人经过这
个公式,能快速得出预测值。也就是老
鼠估算(预测)从某一个洞出去时,可
以顺利出门的可能性,如图 12-1 所示。
这是一开始老鼠在没有任何经验情
况下,运用数学运算后,得出 0.5 预测值
(即猜想有 50%是没有猫)。现在来看如
何撰写 Python 程序表达上述的数学运
算。代码如下。
图 12-1
#Ex12-01
import numpy as np
class Perceptron:
def Dot(self, mx, mw):
self.X = mx
self.W = mw
self.y = np.dot(self.X, self.W)
return self.y
#-----------------------------------------------
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1] ])
·199·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
这个程序里的如下命令:
import numpy as np
图 12-2
12.2 展开第#0组数据的训练
和老鼠一样,机器人也能从经验中提升脑海里的数学公式,让自己的预测
值更接近实际值。上一章里提到,一开始老鼠在没有任何经验情况下,运用其
现有智能得出预测值 0.5。然而,展开行动走出 A 洞时,却发现猫追扑过来,
赶快转身奔回洞内。它回到洞内,想一想它自己所预测的值 0.5 与实际值 0(即
有猫)之间还有落差。就把两者相减,得到误差值为:-0.5。接着拿这项误差
值(即-0.5)来修正机器人里的数学公式,如图 12-3 所示。
此时,拿这项误差值(即-0.5)与权重相加,其计算是:-0.5 + 0.5 = 0.0。
因此,得到新的权重:0.0,如图 12-4 所示。
·200·
第 12 章 撰写单层 Perceptron 程序
图 12-3 图 12-4
#Ex12-02
import numpy as np
class Perceptron:
def __init__(self, mw):
self.W = mw
def Adjust(self):
self.W += np.multiply(self.X, self.error)
#---------------------------------------------
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
·201·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
[0,0,0,1] ])
T = np.array([0,0,1,0])
12.3 进行更多组数据的训练
·202·
第 12 章 撰写单层 Perceptron 程序
代码如下。
#Ex12-03
import numpy as np
class Perceptron:
def Adjust(self):
self.W += np.multiply(self.X, self.error)
#--------------------------- ---------------
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1] ])
T = np.array([0,0,1,0])
p = Perceptron(W)
#---第#0 笔---
p.Dot( X[0] )
p.Loss( T[0] )
·203·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
p.Adjust()
#---第#1 笔---
p.Dot( X[1] )
p.Loss( T[1] )
p.Adjust()
#---第#2 笔---
p.Dot( X[2] )
p.Loss( T[2] )
p.Adjust()
#---第#3 笔---
p.Dot( X[3] )
p.Loss( T[3] )
p.Adjust()
·204·
第 12 章 撰写单层 Perceptron 程序
print(" ")
print("总共训练 4 笔之后的估算值:")
在第#0 笔的训练部分,包含如下命令:
p.Dot( X[0] )
p.Loss( T[0] )
p.Adjust()
·205·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 12-6
12.4 加入学习率
刚才的数学式里,属于比较简单的学习情境,可以一次把误差值直接加入
权重,从而调整权重的值。在比较复杂的机器学习情境中,常常需要放慢学习
速度,小步前进、逐渐逼近最优值。于是,在数学式里加上学习率(Learning Rate)
元素。现在,修饰上一小节里的程序代码,让机器人的计算结果逐渐逼近最优
值,修饰后的 Python 程序代码如下。
#Ex12-04
import numpy as np
class Perceptron:
·206·
第 12 章 撰写单层 Perceptron 程序
self.X = mx
self.y = np.dot(self.X, self.W)
return self.y
def Adjust(self):
update = self.error * self.learning_rate
self.W += np.multiply(self.X, update)
#--------------------------- ---------------
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1] ])
T = np.array([0,0,1,0])
p = Perceptron(W, 0.5)
#---第#1 笔---
p.Dot( X[1] )
·207·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
p.Loss( T[1] )
p.Adjust()
#---第#2 笔---
p.Dot( X[2] )
p.Loss( T[2] )
p.Adjust()
#---第#3 笔---
p.Dot( X[3] )
p.Loss( T[3] )
p.Adjust()
print(" ")
print("训练 2 回合(Epoch)之后的估算值:")
·208·
第 12 章 撰写单层 Perceptron 程序
其中的命令:
update = self.error * self.learning_rate
self.W += np.multiply(self.X, update)
在这里是用来让误差值(error)与学习率(learning_rate)相乘,减小调整
的幅度。所以,可以看到慢慢逼近最优解的学习过程,例如范例呈现的前两个
回合(Epoch)的学习情境,输出的结果如图 12-7 所示。
图 12-7
从输出的结果看,可以观察到权重(即 w[])的持续调整过程。
12.5 增添一个Training类
·209·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
更好,在本节里增添一个新类—Training。调整后的代码如下。
#Ex12-05
import numpy as np
class Perceptron:
def __init__(self, mw, lr):
self.W = mw
self.learning_rate = lr
def Adjust(self):
update = self.error * self.learning_rate
self.W += np.multiply(self.X, update)
#-------------------------------------------------
class Training:
def __init__(self, mx, mt):
self.X = mx
self.T = mt
def Start(self):
for i in range(len(self.X)):
self.P.Dot( self.X[i] )
self.P.Loss( self.T[i] )
self.P.Adjust()
·210·
第 12 章 撰写单层 Perceptron 程序
def GetW(self):
return self.P.W
#--------------------------------------------------
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1] ])
T = np.array([0,0,1,0])
aa = Training(X, T)
aa.SetW(W, 0.5)
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("y:", '%.3f'%p, ", t:", T[row])
for i in range(2):
aa.Start()
print(" ")
print("训练 2 回合之后的预测值:")
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("y:", '%.3f'%p, ", t:", T[row])
·211·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
aa = Training(X, T)
aa.SetW(W, 0.5)
其设定W数组作为数学式的权重。然后,继续运行命令:
这几行命令是依据初期(训练前)的权重(即 W 数组)来计算各组数据
所对应的预测值。接着,运行命令:
for i in range(2):
aa.Start()
展开两个回合的训练,得出最终的权重。最后运行命令:
图 12-8
这里可以看到,训练之前各笔的预测值(y)与实际值(t)有很大的误差,
而训练之后各笔的预测值与实际值误差都非常小了。
·212·
第 12 章 撰写单层 Perceptron 程序
12.6 一个更详细的Perceptron代码
在之前的小节里,Perceptron 类比较简单,适合展现机器学习(Machine
Learning)的基本思维,然而它只适用于比较简单的应用情境上。如老鼠一次
只能选择一个洞,而不能同时选择探索两个洞。
基于这个简单的老鼠探索情境,就能轻易理解老鼠和机器人的学习过程。
有了这些基础,就可以撰写更详细的代码,让机器人学习更高级的智能,以面
对更复杂的应用情境。下面撰写一个通用型的 Perceptron 代码。
首先,撰写一个 training.py 模块,它包含一个 Perceptron 类和一个 Training
类,程序代码如下。
#training
Import numpy as np
class Perceptron(object):
def Sigmoid(self):
self.z = float(1/(1 + np.exp(-self.s)))
return self.z
·213·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
return self.Sigmoid()
def Deriv(self):
self.deriv = self.z * (1 - self.z)
return self.deriv
def Delta(self):
self.delta = 2 * self.error * self.deriv
return self.delta
def GetW(self):
return self.w
#------------------------------------------------
class Training:
def __init__(self, mx, mt):
self.X = mx
self.T = mt
·214·
第 12 章 撰写单层 Perceptron 程序
def Start(self):
for i in range(len(self.X)):
self.P.Forward(self.X[i])
self.P.Backward(self.X[i], self.T[i])
def GetW(self):
return self.P.GetW()
#Ex12-06
import numpy as np
import training
X = np.array([ [1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1] ])
T = np.array([0,0,1,0])
aa = training.Training(X, T)
aa.SetW(W, 0.5)
·215·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
图 12-9
#Ex12-07
import numpy as np
import training
X = np.array([ [1,0,0,0],
·216·
第 12 章 撰写单层 Perceptron 程序
[0,1,0,0],
[0,0,1,0],
[0,0,0,1],
[0,1,1,0]])
T = np.array([0,0,1,0,1])
aa = training.Training(X, T)
aa.SetW(W, 0.5)
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
图 12-10
·217·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex12-08
import numpy as np
import training
aa = training.Training(X, T)
aa.SetW(W, 0.3)
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
·218·
第 12 章 撰写单层 Perceptron 程序
print("-----------------------")
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
图 12-11
表 12-2 玩具兔和玩具熊
身体重量 尾巴长度(cm) 玩具种类
1 4.2 玩具兔
1 5.6 玩具兔
2 6.0 玩具兔
2 5.2 玩具兔
3 1.3 玩具熊
3 2.1 玩具熊
4 1.4 玩具熊
5 2.0 玩具熊
#Ex12-09
import numpy as np
import training
·219·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
T = np.array([0,0,0,0,1,1,1,1])
aa = training.Training(X, T)
aa.SetW(W, 0.3)
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
px = np.array([2.8, 5.9])
print("-----------------------")
print(px)
p = aa.Predict( px )
if p <= 0.5:
print("z:", '%.3f'%p, "玩具兔")
else:
print("z:", '%.3f'%p, "玩具熊")
px = np.array([4.8, 3.3])
print("-----------------------")
print(px)
p = aa.Predict( px )
if p <= 0.5:
print("z:", '%.3f'%p, "玩具兔")
else:
print("z:", '%.3f'%p, "玩具熊")
·220·
第 12 章 撰写单层 Perceptron 程序
px = np.array([2.8, 5.9])
# ………..
p = aa.Predict( px )
if p <= 0.5:
print("z:", '%.3f'%p, "玩具兔")
else:
print("z:", '%.3f'%p, "玩具熊")
图 12-12
表 12-3 辨识颜色
R, G, B 色系
0, 0, 255 BLUE(以 0 表示)
0, 0, 192 BLUE
243, 80, 59 RED(以 1 表示)
2255, 0, 77 RED
·221·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
续表
R, G, B 色系
77, 93, 190 BLUE
255, 98, 89 RED
208, 0, 49 RED
67, 15, 210 BLUE
82, 117,174 BLUE
168, 42, 89 RED
238, 48,167 RED
#Ex12-10
import numpy as np
import training
X = np.array([[0, 0, 255],
[0, 0, 192],
[243, 80, 59],
[255, 0, 77],
[77, 93, 190],
[255, 98, 89],
[208, 0, 49],
[67, 15, 210],
[82, 117,174],
[168, 42, 89],
[238, 48,167]])
T = np.array([0,0,1,1,0,1,1,0,0,1,1])
·222·
第 12 章 撰写单层 Perceptron 程序
aa = training.Training(NX, T)
aa.SetW(W, 0.3)
print(aa.GetW())
for row in range(len(X)):
p = aa.Predict( X[row] )
print("z:", '%.3f'%p, ", t:", T[row])
px = np.array([228, 105,116])
print("-----------------------")
print(px)
p = aa.Predict( px )
if p <= 0.5:
print("z:", '%.3f'%p, "BLUE(蓝色)")
else:
print("z:", '%.3f'%p, "RED(红色)")
此程序展开 10 回合的重复训练后,其预测值(z)与实际值(t)之间的误
差就非常小了。看来机器人的学习效果不错,老鼠就拿[228, 105,116]和[128, 80,
255]请机器人辨识它是属于 BLUE 色系,还是属于 RED 色系?程序的结果如
·223·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 12-13 所示。
图 12-13
·224·
第 13 章
13
使用 TensorFlow 编程
13.1 TensorFlow 入门
13.2 安装 TensorFlow 环境
13.6 设计 Perceptron 类
13.1 TensorFlow入门
13.2 安装TensorFlow环境
·226·
第 13 章 使用 TensorFlow 编程
图 13-2
图 13-3
图 13-4
图 13-5
·227·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 13-6
图 13-7
图 13-8
·228·
第 13 章 使用 TensorFlow 编程
图 13-9
图 13-10
图 13-11
·229·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 13-12
图 13-13
图 13-14
到此,TensorFlow 的安装全部完成。
13.3 开始使用TensorFlow
在上一章里,曾使用一个简单的数学公式:
y = x1×w1 + x2×w2 + x3×w3 + x4×w4
其中,数组[x1、x2、x3、x4]代表一次探索的选择,而数组[w1, w2, w3, w4]
代表机器人在预测时的权重。机器人经过这个数学式,能快速得出预测值,即
老鼠预测从某一个洞出去时,顺利出门的可能性,如图 13-15 所示。
·230·
第 13 章 使用 TensorFlow 编程
图 13-15
#Ex13-01
import numpy as np
X = np.array([ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ], np.float32)
W = np.array([0.5, 0.5, 0.5, 0.5], np.float32)
y = np.dot(X, W)
print("y = ", y)
图 13-16
y[0] = X[0] *W
= [1,0,0,0] * [0.5, 0.5, 0.5, 0.5]
= 1 * 0.5 + 0* 0.5 + 0* 0.5 + 0* 0.5
= 0.5
y[1] = X[1] *W
= [0,1,0,0] * [0.5, 0.5, 0.5, 0.5]
= 1 * 0.5 + 0* 0.5 + 0* 0.5 + 0* 0.5
·231·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
= 0.5
#...........
#Ex13-02
import numpy as np
import tensorflow as tf
y = tf.matmul(X, W)
sess = tf.Session()
vy = sess.run(y)
print(vy)
在这程序里,X 和 W 都是一般的浮点数数组,使用如下命令对其进一步
定义。
y = tf.matmul(X, W)
sess = tf.Session()
vy = sess.run(y)
·232·
第 13 章 使用 TensorFlow 编程
print(vy)
#Ex13-03
import numpy as np
import tensorflow as tf
sess = tf.Session()
vy = sess.run(y)
print("vy:")
print(vy)
print()
sy = np.squeeze(vy)
print("sy:")
print(sy)
·233·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
[[0.5],[0.5],[0.5],[0.5]]。接下来,使用命令:
vy = sess.run(y)
sy = np.squeeze(vy)
图 13-18
上 述 的 W 和 vw 都 是 常 数 数 组 , 都 含 有 常数 值 。 可 以将 常 数 值 存 入
TensorFlow 的变量(即 TensorFlow 的对象),代码如下。
#Ex13-04
import numpy as np
import tensorflow as tf
dx = tf.Variable(X, tf.float32)
dw = tf.Variable(vw, tf.float32)
·234·
第 13 章 使用 TensorFlow 编程
y = tf.matmul(dx, dw)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
sess.run(adding)
vy = sess.run(y)
其中如下两行命令:
dx = tf.Variable(X, tf.float32)
dw = tf.Variable(vw, tf.float32)
sess.run(init)
该命令启动运行时,会运行如下命令:
init = tf.global_variables_initializer()
然后去运行如下命令:
dx = tf.Variable(X, tf.float32)
dw = tf.Variable(vw, tf.float32)
sess.run(adding)
运行如下命令:
·235·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
vy = sess.run(y)
图 13-19
#Ex13-05
import numpy as np
import tensorflow as tf
y = tf.matmul(px, dw)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
·236·
第 13 章 使用 TensorFlow 编程
该程序中有一行命令:
13.4 展开第1回合的训练:以老鼠教练为例
·237·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
x4 的值都是:0,所以,预测 y 值如下:
这是一开始老鼠在还没有任何经验,运用其现有智能预测,得出的 y 预测
值:0.5。然后把 t[0]与 y 两者相减,得到误差值为:-0.5。接着拿这项误差值
(即-0.5)修正机器人里的数学公式,如图 13-22 所示。
图 13-21 图 13-22
图 13-23
·238·
第 13 章 使用 TensorFlow 编程
#Ex13-06
import numpy as np
import tensorflow as tf
dw = np.reshape(dw, [4,1])
dt = np.reshape(dt, [4,1])
y = tf.matmul(X, W)
error = T - y
deltaW = tf.matmul(tf.transpose(X), error )
W_ = W + deltaW
step = tf.group(W.assign(W_))
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
W = np.squeeze(sess.run(W))
print("w: ", W)
print("------------------------")
·239·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 13-24
13.5 展开100回合更周全的训练
上一节里的机器学习算法比较简单,可以让用户更容易理解其背后的原
理。理解之后,就能增添其算法的高级功能。例如,添加上偏移值(Bias)和
学习率(Learning Rate)。
偏移值是一个门槛值(或称阈值),例如一位学生早上醒来,如果心情不
太好,就常常不想去上学。其考虑的因素有两个:x1 代表他心情坏的程度,x2
代表他对该课程的讨厌程度。依据数学公式:x1×w1 + x2×w2。如果:
他就打电话去学校请假(不去上学);反之就会去上学。这个数学式 相
当于:
至于学习率则决定每次(每笔资料)训练时,对权重(weights)和偏移值
·240·
第 13 章 使用 TensorFlow 编程
(bias)的调整幅度。幅度太小,可能需要更长的学习时间,而幅度太大则可能
跳过最优解。不同的算法,各有不同的策略设定其学习率。现在来看一个更精
致的算法,范例代码如下。
#Ex13-07
import numpy as np
import tensorflow as tf
vw = np.reshape(vw, [2,1])
vt = np.reshape(vt, [4,1])
W = tf.Variable(vw, tf.float32)
error = T - z
deriv = z * (1 - z)
delta = 2*error*deriv
deltaB = tf.reduce_sum(error, 0)
·241·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
B_ = B + 0.05 * deltaB
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(100):
sess.run([step], feed_dict={X: vx, T: vt})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
print(W)
predict = sess.run(z, feed_dict={X: vx, T: vt})
for i in range(4):
print("z:", '%.3f'%predict[i], " t:", '%d'%vt[i])
图 13-25
·242·
第 13 章 使用 TensorFlow 编程
13.6 设计Perceptron类
#Ex13-08
import numpy as np
import tensorflow as tf
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dw, dt, db):
self.dx = dx
self.dw = dw
self.dt = dt
self.db = db
self.lr = 0.05
·243·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
err = T - z
deriv = z * (1 - z)
delta = 2*err*deriv
deltaB = tf.reduce_sum(err, 0)
W_ = W + self.lr * deltaW
B_ = B + self.lr * deltaB
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run([step], feed_dict={X: self.dx, T: vt})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
# ---------------------------------------------
p = Perceptron(dx, dw, dt, db)
p.training(100)
print("------------------------------")
print(p.weights)
v += p.bias
z = float(1/(1 + np.exp(-v)))
·244·
第 13 章 使用 TensorFlow 编程
v = np.dot(dx[row], p.weights)
v += p.bias
z = float(1/(1 + np.exp(-v)))
图 13-26
13.7 采用TensorFlow的损失函数
TensorFlow 提供许多损失函数,包括误差平方和、均方误差等,然后设定
损失最小的优化策略。案例代码如下。
#Ex13-09
import numpy as np
import tensorflow as tf
·245·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
LEN, N = np.shape(dx)
class Perceptron(object):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
vt = np.reshape(self.dt, [4,1])
W = tf.Variable(tf.random_normal(shape=(2,1)))
B = tf.Variable(self.db, tf.float32)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run(train, feed_dict={X: self.dx, T: vt})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
·246·
第 13 章 使用 TensorFlow 编程
self.weights = W
self.bias = b
# ---------------------------------------------
print("------------------------------")
print(p.weights)
其中的命令:
定义了损失函数为误差平方和。然后使用如下命令,进行最小平方误差优
化策略的训练。
train = tf.train.AdamOptimizer(
learning_rate=self.lr).minimize(loss)
图 13-27
·247·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
13.8 撰写多层Perceptron程序
#Ex13-10
import numpy as np
import tensorflow as tf
class MLP(object):
def __init__(self, dx, dt, lr):
self.dx = dx
self.dt = dt
self.lr = lr
w1 = tf.Variable(tf.random_normal(shape=(N,HN)))
b1 = tf.Variable(tf.random_normal(shape=(1,HN)))
#-----------------------------------------------------
W = tf.Variable(tf.random_normal(shape=(HN,1)))
B = tf.Variable(tf.random_normal(shape=(1,1)))
·248·
第 13 章 使用 TensorFlow 编程
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run([train], feed_dict={X: self.dx, T: t})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
# ---------------------------------------------
p = MLP(dx, dt, 0.05)
p.training(2000)
print("------------------------------")
print(p.weights)
·249·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 13-28
图 13-29
·250·
第 14 章
14
TensorFlow 应用范例
14.2 开始训练 NN 模型
14.1 mnist手写数字识别范例
#Ex14-01
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
x_train = mnist.train.images
t_train = mnist.train.labels
x_test = mnist.test.images
t_test = mnist.test.labels
x_va = mnist.validation.images
t_va = mnist.validation.labels
·252·
第 14 章 TensorFlow 应用范例
print(x_train.shape)
print(t_train.shape)
print()
print("--- test -------------")
print(x_test.shape)
print(t_test.shape)
print()
print("--- validation -------")
print(x_va.shape)
print(x_va.shape)
此程序的命令如下:
x_train = mnist.train.images
t_train = mnist.train.labels
#...............
print(x_train.shape)
print(t_train.shape)
#.................
plt.matshow(curr_img, cmap=plt.get_cmap('gray'))
plt.show()
·253·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
图 14-1
这张图显示如下内容。
在 MNIST 手写数字图片库里,它提供了 55,000 张图片(Image)用来
训练 NN 模型,还有搭配的 55,000 个标签(Label)。
训练完毕后,再利用 10,000 张图片进行对模型的测试,也搭配 10,000
个标签。
最后,进行验证。其提供的 5000 张图片作为验证的用途,也搭配 5000
个标签。
而选出来的一个图片是“3”。也许用户会问,图片所搭配的标签是什么?
它用来告诉计算机某个图片所代表的正确数字。整个训练过程,就是通过 NN
模型来预测某图片呈现的图形是代表哪一个数字。计算机拿这预测值来与标签
所记载的实际值相比较,来计算输入误差值,再拿这误差值来调整权重。下面
再看一个程序,代码如下。
#Ex14-02
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
·254·
第 14 章 TensorFlow 应用范例
x_train = mnist.train.images
t_train = mnist.train.labels
print(curr_label)
print("number:", np.argmax(curr_label))
plt.matshow(curr_img, cmap=plt.get_cmap('gray'))
plt.show()
此程序的命令如下:
print(curr_label)
print("number:", np.argmax(curr_label))
图 14-2
·255·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
14.2 开始训练 NN 模型
准备好了训练数据(55,000 张图片)集,就能展开训练。首先建立一个简
单的 NN 模型(即单层 Perceptron 模型),以便比较容易观察训练的结果,可以
看到其不断地提升智能性,也就是表现出来的结果:对特定图片的识别预测值,
就越接近其标签所定的实际值。如下例,程序代码如下。
#Ex14-03
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
z = tf.nn.softmax(tf.matmul(X,W) + B)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
·256·
第 14 章 TensorFlow 应用范例
print("number:", np.argmax(select_t))
predict = sess.run(z, feed_dict={X: select_x, T: select_t})
for i in range(10):
print("z:", '%.3f'%predict[0, i], " t:", '%d'%select_t[0, i])
print()
print("---- after 500 iterations ------")
print("number:", np.argmax(select_t))
predict2 = sess.run(z, feed_dict={X: select_x, T: select_t})
for i in range(10):
print("z:", '%.3f'%predict2[0, i], " t:", '%d'%select_t[0, i])
print()
print("---- after 5000 iterations ------")
print("number:", np.argmax(select_t))
predict2 = sess.run(z, feed_dict={X: select_x, T: select_t})
for i in range(10):
print("z:", '%.3f'%predict2[0, i], " t:", '%d'%select_t[0, i])
·257·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
此程序首先运行命令:
这从训练图片集中随意挑出一张图片,来检验其训练过程的阶段性成果。
然后建立一个单层的 Perceptron 模型,它的输入层含有 784 个输入神经元(因
为一张图片有 784 个像素);而输出层则有 10 个神经元(因为用 10 个二进制
数来代表所写的阿拉伯数字)。代码如下:
W = tf.Variable(tf.zeros([784, 10]))
B = tf.Variable(tf.zeros([10]))
z = tf.nn.softmax(tf.matmul(X,W) + B)
·258·
第 14 章 TensorFlow 应用范例
图 14-3
·259·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
14.3 改进 NN 模型:建立两层Perceptron
#Ex14-04
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/",one_hot = True)
in_nodes = 784
hi_nodes = 300
x_train = mnist.train.images
t_train = mnist.train.labels
x_test = mnist.test.images
t_test = mnist.test.labels
select_x = mnist.test.images[0:1]
select_t = mnist.test.labels[0:1]
# hidden layer
hidden_W = tf.Variable(tf.truncated_normal([in_nodes,
hi_nodes],stddev = 0.1))
hidden_B = tf.Variable(tf.zeros([hi_nodes]))
# output layer
W = tf.Variable(tf.zeros([hi_nodes,10]))
B = tf.Variable(tf.zeros([10]))
·260·
第 14 章 TensorFlow 应用范例
X = tf.placeholder(tf.float32,[None,in_nodes])
T = tf.placeholder(tf.float32,[None,10])
sess = tf.InteractiveSession()
init = tf.global_variables_initializer()
sess.run(init)
for i in range(5000):
batch_x,batch_t = mnist.train.next_batch(100)
sess.run([train_step], feed_dict={X: batch_x, T: batch_t})
print("number:", np.argmax(select_t))
predict = sess.run(z, feed_dict={X: select_x})
for i in range(10):
print("z:", '%.3f'%predict[0, i], " t:", '%d'%select_t[0, i])
in_nodes = 784
hi_nodes = 300
·261·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
stddev = 0.1))
hidden_B = tf.Variable(tf.zeros([hi_nodes]))
这定义隐藏层的权重(Weight)数组为:hidden_W[784, 300];而其偏移值
(Bias)数组为:hidden_B[300]。命令如下:
W = tf.Variable(tf.zeros([hi_nodes,10]))
B = tf.Variable(tf.zeros([10]))
这定义输出层的权重数组为:W[300, 10];而其偏移值(Bias)数组为:
B[10]。命令如下:
计算出隐藏层各神经元的预测值。然后,继续输入命令:
z = tf.nn.softmax(tf.matmul(layer_1,W) + B)
计算出输出层各神经元的预测值。建立好模型后,命令如下:
for i in range(5000):
batch_x,batch_t = mnist.train.next_batch(100)
sess.run([train_step], feed_dict={X: batch_x, T: batch_t})
·262·
第 14 章 TensorFlow 应用范例
图 14-4
14.4 改进 NN 模型:建立三层Perceptron
#Ex14-05
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/",one_hot = True)
in_nodes = 784
h1_nodes = 256
h2_nodes = 256
x_train = mnist.train.images
t_train = mnist.train.labels
select_x, select_t = mnist.train.next_batch(1)
# hidden layer
h1_W = tf.Variable(tf.truncated_normal([in_nodes, h1_nodes],stddev
= 0.1))
h1_B = tf.Variable(tf.zeros([h1_nodes]))
h2_W = tf.Variable(tf.truncated_normal([h1_nodes, h1_nodes],stddev
= 0.1))
h2_B = tf.Variable(tf.zeros([h2_nodes]))
·263·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
# output layer
W = tf.Variable(tf.zeros([h2_nodes,10]))
B = tf.Variable(tf.zeros([10]))
X = tf.placeholder(tf.float32,[None,in_nodes])
T = tf.placeholder(tf.float32,[None,10])
sess = tf.InteractiveSession()
init = tf.global_variables_initializer()
sess.run(init)
for i in range(5000):
batch_x,batch_t = mnist.train.next_batch(100)
sess.run([train_step], feed_dict={X: batch_x, T: batch_t})
print("number:", np.argmax(select_t))
predict = sess.run(z, feed_dict={X: select_x})
for i in range(10):
print("z:", '%.3f'%predict[0, i], " t:", '%d'%select_t[0, i])
in_nodes = 784
·264·
第 14 章 TensorFlow 应用范例
h1_nodes = 256
h2_nodes = 256
该命令计算出第一隐藏层各神经元的预测值。接着,输入命令:
该命令计算出第二隐藏层各神经元的预测值。接着,输入命令:
z = tf.nn.softmax(tf.matmul(layer_2, W) + B)
该命令计算出输出层各神经元的预测值。建立好模型后,展开 5000 个回
合的训练。其中,每一回合拿 100 张(图片)来训练这个模型(即调整其 W[]
和 B[]值),持续进行 5000 个回合,而得出最新的权重和偏移值。
最后,针对所挑选的图片,依据最新的权重和偏移值,来进行预测,输出
结果如图 14-5 所示。
图 14-5
14.5 撰写一个MLP类
·265·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
#Ex14-06
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
class MLP(object):
def __init__(self, input_num, h1_num, h2_num, lr):
self.mnist = input_data.read_data_sets("MNIST_data/",
one_hot = True)
self.in_nodes = input_num
self.h1_nodes = h1_num
self.h2_nodes = h2_num
self.lr = lr
# output layer
W = tf.Variable(tf.zeros([self.h2_nodes,10]))
B = tf.Variable(tf.zeros([10]))
X = tf.placeholder(tf.float32,[None,self.in_nodes])
T = tf.placeholder(tf.float32,[None,10])
sess = tf.InteractiveSession()
init = tf.global_variables_initializer()
sess.run(init)
·266·
第 14 章 TensorFlow 应用范例
for i in range(iteration):
batch_x,batch_t = self.mnist.train.next_batch(100)
sess.run([train_step], feed_dict={X: batch_x, T:
batch_t})
print("number:", np.argmax(select_t))
predict = sess.run(z, feed_dict={X: select_x})
for i in range(10):
print("z:", '%.3f'%predict[0, i], " t:", '%d'%select_t
[0, i])
#--------------------------------------------------
p = MLP(784, 256, 256, 0.05)
p.training(5000)
图 14-6
·267·
第 15 章
15
如何导出 AI 模型
15.1 导出模型入门
15.2 机器人:像老鼠一样学习
15.3 基于 TensorFlow 建立 AI 模型
15.4 存入 Checkpoint 文件
15.5 读取 Checkpoint 文件
15.6 读取流图定义文件
15.7 导出模型:写入.pb 文件
15.8 导入模型,读取.pb 文件
第 15 章 如何导出 AI 模型
15.1 导出模型入门
图 15-1 图 15-2
以后需要时,直接从文件里加载这个模型即可,而不必再花费大量时间训
练,如图 15-3 所示。
图 15-3
也可以把训练好的模型,提供给更多人来使用,从而节省时间,产生很大
的效益。
·269·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
15.2 机器人:像老鼠一样学习
在前面的章节里,老鼠当起了教练,拿它的经验记录数据,作为训练数据
(Training Data)教导它的机器人朋友。
那时拿一个简单的数学式表示:
y = x1×w1 + x2×w2 + x3×w3 + x4×w4
其中,数组[x1、x2、x3、x4]代表一次探索的选择,而数组[w1, w2, w3, w4]
代表机器人在思考期预测值时的权重。机器人经过这个数学公式,就能快速得
出其预测值。
现在学习编写 Python/TensorFlow 程序表达上述的数学运算。
15.3 基于TensorFlow建立AI模型
#Ex15-01
import numpy as np
import tensorflow as tf
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
·270·
第 15 章 如何导出 AI 模型
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run(train, feed_dict={X: self.dx, T: vt})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
self.result = sess.run(z, feed_dict={X: self.dx})
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.training(1500)
print("------------------------------")
print(p.weights)
其中的命令:
·271·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
用来定义损失函数:误差平方和。然后输入命令:
train = tf.train.AdamOptimizer(
learning_rate=self.lr).minimize(loss)
该命令用来进行最小平方误差优化策略的训练。其设定学习率为:0.05,
共训练 1500 次,然后输出相应的结果。
15.4 存入Checkpoint文件
训练好模型后,可以把模型结构及最新的权重值(Weight)、偏移值(Bias)
等导出并记录下来,以便后续继续训练或使用。现在,来学习如何导出模型并
存入*.ckpt 文件里,如图 15-4 所示。
图 15-4
#Ex15-02
import numpy as np
import tensorflow as tf
·272·
第 15 章 如何导出 AI 模型
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
z = tf.sigmoid(tf.matmul(X, W) + B, name="z")
loss = tf.reduce_sum(tf.square(T - z))
train = tf.train.AdamOptimizer(learning_rate=self.lr).
minimize(loss)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run(train, feed_dict={X: self.dx, T: vt})
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
self.result = sess.run(z, feed_dict={X: self.dx})
#----------------------------------------
saver = tf.train.Saver()
·273·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.training(1500)
print("------------------------------")
print(p.weights)
这 里 的 *.ckpt 文 件 ,就 是 指“ Checkpoint”文件。这意味着,在训练或
应用过程中的某个时间点,将当下模型里的变量值(如 Weights 值)以二进制
(Binary)的形式存入文件里,就称为“ Checkpoint”文件(*.ckpt)。也就是说,
它存储模型里的变量(Variable)、表达式(Operatopn)的名称及内容(即 Ttensor
数组数值)。命令如下:
在模型里的变量定义里,输入一个名称,然后以这个名称存入相应的文件。
此程序的结果如图 15-5 所示。
图 15-5
同时,也将模型的当下变量值存储到*.ckpt 文件里。此时,可以在计算机
的相应文件夹里看到“ Checkpoint” 文件,如图 15-6 所示。
·274·
第 15 章 如何导出 AI 模型
图 15-6
15.5 读取Checkpoint文件
现在来看如何读取“Checkpoint”文件的内容作为继续训练的起点,或拿
来应用(如做预测),如图 15-7 所示。
图 15-7
在 上 一 个 范 例 里 , 使 用 TensorFlow 的 tf.train.saver.save() 函 数 , 存 储
“Checkpoint”文件。现在,使用 tf.train.saver.restore()函数来读取这个文件的内
容,以便继续训练,或加以应用。程序代码如下。
#Ex15-03
import numpy as np
import tensorflow as tf
·275·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
1]], np.float32)
dt = np.array([0, 0, 1, 0], np.float32)
db = np.array([1], np.float32)
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
def predict(self):
vt = np.reshape(self.dt, [4,1])
X = tf.placeholder(tf.float32, shape=[4, 4], name="X")
T = tf.placeholder(tf.float32, shape=[4, 1])
W = tf.Variable(tf.random_normal(shape=(4,1)), name="W")
B = tf.Variable(self.db, tf.float32, name="B")
z = tf.sigmoid(tf.matmul(X, W) + B, name="z")
#-----------------------------------------------
saver = tf.train.Saver()
sess = tf.Session()
saver.restore(sess, "/tmp/model.ckpt")
#-----------------------------------------------
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
self.result = sess.run(z, feed_dict={X: self.dx})
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.predict()
·276·
第 15 章 如何导出 AI 模型
print("------------------------------")
print(p.weights)
图 15-8
也就是说,是一样的模型,只是恢复模型的状态(如变量值)。
15.6 读取流图定义文件
#Ex15-04
import numpy as np
import tensorflow as tf
·277·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
db = np.array([1], np.float32)
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
def predict(self):
vt = np.reshape(self.dt, [4,1])
T = tf.placeholder(tf.float32, shape=[4, 1])
sess = tf.Session()
loader = tf.train.import_meta_graph("c:/tmp/model.
ckpt.meta")
loader.restore(sess, "c:/tmp/model.ckpt")
XP = sess.graph.get_tensor_by_name("X:0")
op = sess.graph.get_operation_by_name("z").outputs[0]
W = sess.graph.get_tensor_by_name("W:0")
w = np.squeeze(sess.run(W))
B = sess.graph.get_tensor_by_name("B:0")
b = np.squeeze(sess.run(B))
self.weights = w
self.bias = b
self.result = sess.run(op, feed_dict={XP: self.dx})
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.predict()
print("------------------------------")
print(p.weights)
·278·
第 15 章 如何导出 AI 模型
在这个程序里,不再重复定义模型,而是从“model.ckpt.meta”文件里读
取模型的定义。使用命令如下:
loader =
tf.train.import_meta_graph("c:/tmp/model.ckpt.meta")
此时就建立了模型。接着,从“model.ckpt”文件里读取变量的值,导入
到模型的变量里,其命令如下:
loader.restore(sess, "c:/tmp/model.ckpt")
XP = sess.graph.get_tensor_by_name("X:0")
从这个(刚才建立的)模型里,查询出名称为“X”的引数(Placeholder),
并指定给 XP。接下来,命令如下:
op =
sess.graph.get_operation_by_name("z").outputs[0]
从这个(刚才建立的)模型里,查询出名称为“z”的表达式(Operation),
并指定给 op。接下来,命令如下:
图 15-9
此程序读取“model.ckpt.meta”文件,以及“model.ckpt”文件,并根据其
恢复了模型,这模型与上一程序的模型相同,所以输出相同的预测值。
·279·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
15.7 导出模型:写入.pb文件
图 15-10 图 15-11
现在,来看看如何输出完整而标准的*.pb 文件,代码如下。
#Ex15-05
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import graph_util
LEN, N = np.shape(dx)
·280·
第 15 章 如何导出 AI 模型
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
z = tf.sigmoid(tf.matmul(X, W) + B, name="z")
loss = tf.reduce_sum(tf.square(T - z))
train = tf.train.AdamOptimizer(learning_rate=self.lr).
minimize(loss)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
for k in range(iteration):
sess.run(train, feed_dict={X: self.dx, T: vt})
#-------------------------------------------------
export_path = "c:/temp2/misoo01"
builder =
tf.saved_model.builder.SavedModelBuilder(export_path)
signature =
tf.saved_model.signature_def_utils.build_signature_def(inputs,
·281·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
outputs, 'misoo')
builder.add_meta_graph_and_variables(sess, ['test_saved_
model'], {'test_signature': signature})
builder.save()
#---------------------------------------
W = np.squeeze(sess.run(W))
b = np.squeeze(sess.run(B))
self.weights = W
self.bias = b
self.result = sess.run(z, feed_dict={X: self.dx})
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.training(1500)
print("------------------------------")
print(p.weights)
首先,使用如下命令设定导出*pb 文件的所在路径。
export_path = "c:/temp2/misoo01"
再叙述模型里的输入引数,以及输出表达式,代码如下:
然后定义签章,代码如下:
·282·
第 15 章 如何导出 AI 模型
signature =
tf.saved_model.signature_def_utils.build_signature_def(inputs,
outputs, 'misoo')
最后,把模型(Graph)定义和变量值,写入*.pb 文件里,代码如下:
builder.add_meta_graph_and_variables(sess, ['test_saved_model'],
{'test_signature': signature})
builder.save()
此程序一方面导出模型到*.pb 文件,然后将(存入文件的权重)变量值,
以及预测值显示在屏幕上,如图 15-12 所示。
图 15-12
图 15-13
·283·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
15.8 导入模型,读取.pb文件
#Ex15-06
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import graph_util
LEN, N = np.shape(dx)
class Perceptron(object):
def __init__(self, dx, dt, db, lr):
self.dx = dx
self.dt = dt
self.db = db
self.lr = lr
def training(self):
signature_key = 'test_signature'
input_key = 'input'
output_key = 'predict'
vt = np.reshape(self.dt, [4,1])
T = tf.placeholder(tf.float32, shape=[4, 1])
sess = tf.Session()
path = "c:/temp2/misoo01"
·284·
第 15 章 如何导出 AI 模型
signature = graph_def.signature_def
tensor_name_X =
signature[signature_key].inputs[input_key].name
tensor_name_z =
signature[signature_key].outputs[output_key].name
X = sess.graph.get_tensor_by_name(tensor_name_X)
z = sess.graph.get_tensor_by_name(tensor_name_z)
W = sess.graph.get_tensor_by_name("W:0")
w = np.squeeze(sess.run(W))
B = sess.graph.get_tensor_by_name("B:0")
b = np.squeeze(sess.run(B))
self.weights = w
self.bias = b
self.result = sess.run(z, feed_dict={X: self.dx})
# ---------------------------------------------
p = Perceptron(dx, dt, db, 0.05)
p.training()
print("------------------------------")
print(p.weights)
命令如下:
path = "c:/temp2/misoo01"
graph_def =
tf.saved_model.loader.load(sess, ['test_saved_model'], path)
·285·
从 AI 模型到智能机器人:基于 Python 与 TensorFlow
该命令导入.pb 文件的内容,包含模型(图)的定义,以及变量值。于是,
在 TensorFlow 环境里建立模型,这个模型(含 Graph 定义及内容)与上一范例
程序里的模型一样。接着,用以下命令从这个(刚才建立的)模型里,查询出
名称为“X”的引数(Placeholder),并指定给 X。
X = sess.graph.get_tensor_by_name(tensor_name_X)
接下来,命令就从这个(刚才建立的)模型里,查询出名称为“z”的表
达式(Operation),并指定给 z。
z = sess.graph.get_tensor_by_name(tensor_name_z)
图 15-14
此程序导入(读取)*.pb 模型文件,并根据其来建立模型,这模型与上一
程序的模型一样,所以输出相同的预测值。
·286·