1 python基础语法
1.1字面量,print,type
字面量:在代码中,被写下来的的固定的值称之为字面量。
注释:(1)单行注释: # 一个空格(2)多行注释 “““ ”””
Print语句可同时输出多份数据,逗号隔开即可。
默认会换行,使用end=“”不换行
type(数据)获取数据类型,会返回结果,可用变量存储结果。注意:变量无类型,变量存储的数据才有类型。字符串变量是指变量存储了一个字符串数据
1.2数据转换,标识符,命名规范
带有结果(返回值),可直接输出
标识符:
是用户在编程的时候所使用的一系列名字,用于给变量、类、方法等命名。
数字,字母,下划线,不能数字开头。大小写敏感。
变量命名规范:见名知意,多单词下划线,使用小写
1.3算术运算符,字符串定义,字符串嵌套
常见的算术(数学)运算符有: 加(+)、减(-)、乘()、除(/)、整除(//)、取余(%)、求平方(**) 赋值运算符有: 标准赋值: = 复合赋值:+=、-=、=、/=、//=、%=、**=
字符串的三种定义方式:单引号方式 双引号方式 三引号方式 引号的嵌套:可以使用:\来进行转义 单引号内可以写双引号或双引号内可以写单引号
1.4字符串格式化,精度控制 ,快速格式化
使用“+”号连接字符串变量或字符串字面量(少用)但无法和非字符串类型进行拼接
通过如下语法,完成字符串和变量的快速拼接。
“%s” %变量 % 表示:我要占位 s 表示:将变量变成字符串放入占位的地方 多个占位变量使用括号
“%s %s”%(变量1,变量2) 注意没有逗号
%d 将内容转换成整数,放入占位位置
%f 将内容转换成浮点数,放入占位位置
%m.n(d,f,s) m控制宽度,n控制小数点精度(四舍五入),可以无m,n %.2f
快速格式化 f指的format
f ”{变量1} {变量2}”的方式进行快速格式化
不理会类型,不做精度控制,适合对精度没有要求的时候快速使用
表达式就是一个具有明确结果的代码语句,如 1 + 1、type(“字符串”)、3 * 5等 在变量定义的时候,如 age = 11 + 11,等号右侧的就是表达式,也就是有具体的结果,将结果赋值给了等号左侧的变量 如何格式化表达式? f"{表达式}"
“%s%d%f” % (表达式1、表达式2、表达式3)
补充:\t制表符可以使字符串进行对齐
Python中字符串的格式化—format()方法_python字符串format方法-CSDN博客
format函数可以被直接调用或在print函数中以占位符实现格式化调用。
在2.6版本前,占位符仍和其他语言一样使用%(比如C中)。
但在其后的版本,format函数的占位符更改为{}(花括号)。
‘{} {}’.format(‘hello’,‘world’) #占位符不指定顺序
‘hello world’
‘{0} {1}’.format(‘hello’,‘world’) #占位符制定顺序
‘hello world’
‘{1} {0}’.format(‘hello’,‘world’) #换一下顺序试试
‘world hello’
print(‘{}获得了S8冠军’.format(‘IG’)
#结果:
#IG获得了S8冠军
1.5 键盘输入input(),if判断,嵌套判断
使用变量接受input()输入结果,括号内可直接写入提示信息
,会将输入信转换为string
注:==比较符号可以同时比较数字和字符串
if else语句的注意事项: else不需要判断条件,当if的条件不满足时,else执行 else的代码块,同样要4个空格作为缩进
if条件1:
if 条件2:
嵌套的关键点,在于:空格缩进 通过空格缩进,来决定语句之间的:层次关系
1.6 while循环,for循环 嵌套,
While 条件: #条件为bool型结果
事件
While 条件: #条件为bool型结果
事件1
While 条件: #条件为bool型结果
事件2
for 临时变量 in 待处理数据集(序列): 循环满足条件时执行的代码 同while循环不同,for循环是无法定义循环条件的。 只能从被处理的数据集中,依次取出内容进行处理。 语法中的:待处理数据集,严格来说,称之为:可迭代类型 可迭代类型指,其内容可以一个个依次取出的一种类型,包括:字符串,列表,元组等
range语句:获得数字序列,配合for完成循环次数的确认
方法1:range(num)
获取一个从0开始,到num结束的数字序列(不含num本身) 如range(5)取得的数据是:[0, 1, 2, 3, 4]
语法2:range(num1,num2)
获得一个从num1开始,到num2结束的数字序列(不含num2本身) 如,range(5, 10)取得的数据是:[5, 6, 7, 8, 9] 语法3:range(num1,num2,step)
获得一个从num1开始,到num2结束的数字序列(不含num2本身) 数字之间的步长,以step为准(step默认为1) 如,range(5, 10, 2)取得的数据是:[5, 7, 9] for x in range(1,101)
需要注意缩进,嵌套for循环同样通过缩进确定层次关系 for循环和while循环可以相互嵌套使用
1.7 函数,函数注释,参数作用域
Python中有一个特殊的字面量:None,其类型是:
无返回值的函数,实际上就是返回了:None这个字面量
函数体内进行注释,使用三引号
想要将函数内的局部变量变为全局变量,使用global关键字 global 变量
1.8 数据容器list列表,定义,常用操作
列表内的每一个数据,称之为元素以 [ ] 作为标识列表内每一个元素之间用, 逗号隔开 注意:列表可以一次存储多个数据,且可以为不同的数据类型,支持嵌套 列表正向下标与数组相同 从0开始,自左向右
反向索引,也就是从后向前:从-1开始,依次递减(-1、-2、-3…) 多层嵌套下标list[0][1],多加中括号即可
(1)查找某元素的下标 功能:查找指定元素在列表的下标,如果找不到,报错ValueError 语法:列表.index(元素)
(2)修改特定位置(索引)的元素值: 语法:列表[下标] = 值 即直接重新赋值,可以使用如上语法,直接对指定下标(正向、反向下标均可)的值进行:重新赋值(修改)
(3)插入元素: 语法:列表.insert(下标, 元素),在指定的下标位置,插入指定的元素 (4)追加元素: 语法:列表.append(元素),将指定元素,追加到列表的尾部 追加元素方式2: 语法:列表.extend(其它数据容器),将其它数据容器的内容取出,依次追加列表尾部 (5)删除元素:
语法1: del 列表[下标]
语法2:列表.pop(下标) 本质上是将元素取出,所以有返回值,可以赋值给变量
(6)删除某元素在列表中的第一个匹配项
语法:列表.remove(元素) 删除指定内容的元素,从前到后搜索,删除第一个
(7)清空 列表.clear
(8)统计某元素在列表内的数量 语法:列表.count(元素)
(9)统计列表内,一共有多少元素 语法:len(列表)
列表有如下特点:
可以容纳多个元素(上限为2**63-1、9223372036854775807个)
可以容纳不同类型的元素(混装)
数据是有序存储的(有下标序号)
允许重复数据存在
可以修改(增加或删除元素等)
1.9 数据容器元组tuple,字符串str
元组同列表一样,都是可以封装多个、不同类型的元素在内。
但最大的不同点在于:元组一旦定义完成,就不可修改
元组定义:定义元组使用小括号,且使用逗号隔开各个数据,数据可以是不同的数据类型。
注:字面量定义可直接小括号内写数据 单个元素的元组必须加逗号,否则不是元组
操作:(1)index(元素) 查找元素下标
(2)count(元素) 统计元素数量 (3)len(元组)统计元组元素数量
可以修改元组内的list的内容
字符串
字符串也可以通过下标进行访问:从前向后,下标从0开始,从后向前,下标从-1开始
字符串是一个:无法修改的数据容器。
操作:(1)查找特定字符串的下标索引值 语法:字符串.index(字符串)
(2)字符串的替换
语法:字符串.replace(字符串1,字符串2)
功能:将字符串内的全部:字符串1,替换为字符串2
注意:不是修改字符串本身,需要将修改后的字符串赋给新变量,原字符串不变
(3)字符串的分割
语法:字符串.split(分隔符字符串)
功能:按照指定的分隔符字符串,将字符串划分为多个字符串,并存入列表对象中
注意:原字符串本身不变,而是新得到了一个列表对象
(4)字符串的规整操作(去前后空格)
语法:字符串.strip()
(5)字符串的规整操作(去前后指定字符串) 本质上是按照字符串内的单个字符去移除
语法:字符串.strip(字符串)
注意:原字符串不变,是返回了一个新字符串
1.9 序列,切片。
序列是指:内容连续、有序,可使用下标索引的一类数据容器
列表、元组、字符串,均可以视为序列。序列支持切片,即:列表、元组、字符串,均支持进行切片操作。
切片:从一个序列中,取出一个子序列
语法:序列[ 起始下标:结束下标:步长 ]
从序列中,从指定位置开始,依次取出元素,到指定位置结束(不包括结束元素),得到一个新序列:
步长为负数表示,反向取(注意,起始下标和结束下标也要反向标记)
注意,此操作不会影响序列本身,而是会得到一个新的序列(列表、元组、字符串)
之前到现在的操作都可链式编程
1.10集合set,字典dict,容器通用操作
集合
集合,最主要的特点就是:不支持元素的重复(自带去重功能)、并且内容无序
原因:因为要对元素做去重处理,所以无法保证顺序和创建的时候一致
无序导致无法通过下标访问,也就只能for循环
定义方法与其他容器相似
列表使用:[ ],元组使用:() 字符串使用:引号 集合使用 { }大括号
集合set和list一样,允许修改,所以操作之后是原集合发生变化
(1)添加新元素 语法:集合.add(元素)。将指定元素,添加到集合内
(2)移除元素 语法:集合.remove(元素),将指定元素,从集合内移除
(3 ) 从集合中随机取出元素
语法:集合.pop(),从集合中随机取出一个元素 相比之下,列表.pop(下标)括号内可指定取出元素
(4)取出2个集合的差集
语法:集合1.difference(集合2),功能:取出集合1和集合2的差集(集合1有而集合2没有的)
结果:得到一个新集合,集合1和集合2不变
(5)消除2个集合的差集
语法:集合1.difference_update(集合2)
功能:对比集合1和集合2,在集合1内,删除和集合2相同的元素。
结果:集合1被修改,集合2不变
(6)2个集合合并
语法:集合1.union(集合2)
功能:将集合1和集合2组合成新集合
结果:得到新集合,集合1和集合2不变
字典
同样使用{ },不过存储的元素是一个个的:键值对。 {key: value, key: value}
字典同集合一样,不可以使用下标索引
但是字典可以通过Key值来取得对应的Value
语法:字典[key]即可取到value
字典的Key和Value可以是任意数据类型(Key不可为字典)
(1)新增元素或修改元素(取决于key是否存在,不存在则新增,存在则修改)
语法:字典[Key] = Value
(2)删除元素
语法:字典.pop(Key),结果:获得指定Key的Value,同时字典被修改,指定Key的数据被删除
(3)获取全部的key
语法:字典.keys(),结果:得到字典中的全部Key
(4)遍历字典方式1 语法:for key in 字典.keys():
遍历字典方式2 语法 for key in 字典:
数据容器总结
数据容器的通用操作
len(容器) :统计容器的元素个数 max(容器):统计容器的最大元素 min(容器):统计容器的最小元素
list(容器)将给定容器转换为列表,tuple(容器)将给定容器转换为元组,str(容器)将给定容器转换为字符串,set(容器)将给定容器转换为集合
sorted(容器,[reverse=True])将给定容器进行排序。注意,排序后都会得到列表((list)对象。
字典丢失value。reverse倒序从大到小。
补充学习列表的sort方法。
使用方式:
列表.sort(key=选择排序依据的函数, reverse=True|False)
参数key,是要求传入一个函数,可以是匿名函数,表示将列表的每一个元素都传入函数中,返回排序的依据
参数reverse,是否反转排序结果,True表示降序,False表示升序
1.11函数进阶
函数多返回值
函数要有多个返回值。
def test_return():
return 1,2,……
x ,y ,…= test_return()
按照返回值的顺序,写对应顺序的多个变量接收即可,变量之间用逗号隔开,return的类型不受限。
函数多种传参方式
(1)位置参数:调用函数时根据函数定义的参数位置来传递参数 即一一对应,保证顺序
(2)关键字参数:函数调用时通过“键=值”形式传递参数. 此时无顺序要求
(3)缺省参数:缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用).
(4)不定长参数:不定长参数也叫可变参数. 用于不确定调用的时候会传递多少个参数(不传参也可以)的场景.
不定长参数的类型:
①位置传递 def use_info(*args)
传进的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是位置传递
②关键字传递 def user_info(**kwargs) kw=keyword
user_info(name=‘小王’,age=11, gender=‘男孩’,addr=‘北京’)
传入的参数是“键=值”形式的情况下, 所有的“键=值”都会被kwargs接受, 同时会根据“键=值”组成字典. 注意:传入时是key=value的形式,不是key: value
函数作为参数传递
1.12文件编码
open函数,可以打开一个已经存在的文件,或者创建一个新文件
open(name, mode, encoding)
其中
name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)。
mode:设置打开文件的模式(访问模式):只读r、写入w、追加a等。
encoding:编码格式(推荐使用UTF-8)
示例:
其中encoding使用位置参数,是因为在open函数中encoding的位置并不是第三位
注意:此时的f
是open
函数的文件对象,对象是Python中一种特殊的数据类型,拥有属性和方法,可以使用对象.属性或对象.方法对其进行访问
文件读取操作
(1)read()方法:语法 文件对象.read(num)其中num表示读取字节数,无num表示读全部。
(2)readline()方法:一次读取一行内容
(3)readlines()方法 文件对象.readlines()可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素。
注意:多次read, readline(只要是读数据)会在上一次read结束的地方开始(应该是文件指针导致的)
(4)使用for循环进行读取 for line in 文件对象(或者open(name,mode,encoding))
(5)f.close()
最后通过close,关闭文件对象,也就是关闭对文件的占用
如果不调用close,同时程序没有停止运行,那么这个文件将一直被Python程序占用
(6)with open(“python.txt”, “r”) as f:
f.readlines()
写操作
捕获指定异常语法 except 异常类型 (as 别名) :
一般try下方只放一行尝试执行的代码。
捕获多个异常,通过元组 except (NameError, ZeroDivisionError,…):
基础语法,和Exception都能捕获全部异常
else表示的是如果没有异常要执行的代码,finally表示的是无论是否异常都要执行的代码,例如关闭文件。
异常的传递性
函数间的嵌套也会传递异常,最后一行是最初出现异常的位置,从下向上
模块的导入方式
常用的组合形式如:
(1)import 模块名
(2)from 模块名 import 类、变量、方法等
(3)from 模块名 import *
(4)import 模块名 as 别名
(5)from 模块名 import 功能名 as 别名
其中方法5,3意思是使用某模块的某个功能和所有功能,在使用时直接调用方法名即可。
其他直接import的形式需要使用 模块.功能方法
自定义模块
每个Python文件都可以作为一个模块,模块的名字就是文件的名字. 也就是说自定义模块名必须要符合标识符命名规则
不同模块的同名函数,后者覆盖前者
在实际开发中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息。如果不对这些测试进行操作,在import时,这些测试方法会直接执行。
在模块中打出main,可以变成if _ name _ == '_ main ':,将测试方法写在if下即可。
原因: 整个python文件都会内置一个变量 name ,运行时,此变量变为__main_,if中的表达式为true,执行测试方法。但在导入该模块时,再次运行,模块的name不再是main,if不成立,测试方法不执行
Python包
从物理上看,包就是一个文件夹,在该文件夹下包含了一个 init.py 文件,该文件夹可用于包含多个模块文件
从逻辑上看,包的本质依然是模块
导入包
2 JSON,python与Json转换,pyecharts模块
1.json:是一种轻量级的数据交互格式, 采用完全独立于编程语言的文本格式来存储和表示数据(就是字符串)
Python语言使用JSON有很大优势,因为:JSON无非就是一个单独的字典或一个内部元素都是字典的列表。
所以JSON可以直接和Python的字典或列表进行无缝转换
2.Python数据和Json数据的相互转化,dump有转存意,所以python->json用dumps
pyechars入门
基础折线图 注:axis的英文意即是坐标轴
pyecharts模块中有很多的配置选项, 常用到2个类别的选项:
全局配置选项 代表所有图标的通用设置,如标题,图例等
通过调用set_global_opts方法来进行配置即 对象. set_global_opts(…)
from pyecharts.options import TitleOpts,LegendOpts,ToolboxOpts,VisualMapOpts
配置图表的标题 ,配置图例, ,配置工具栏,配置视觉映射
3类与对象(不用看)
3.1类的定义和使用,成员方法的定义语法
class是关键字,表示要定义类了
类的属性,即定义在类中的变量(成员变量)
类的行为,即定义在类中的函数(成员方法)
创建对象 对象=类名()
其中self关键字是成员方法定义的时候,必须填写的,但是传参时是透明的,可以忽略。(类似于java this指针)
它用来表示类对象自身的意思
当我们使用类对象调用方法,self会自动被python传入
在方法内部,想要访问类的成员变量,必须使用self,
如果不是访问成员变量,直接外部传参即可
3.2python的构造方法
_ init _()方法 init前后都有2个下划线
在创建类对象(构造类)的时候,会自动执行。
创建类对象(构造类)的时候,将传入参数自动传递给__init__方法使用。
创建对象时,没有调用init方法,自动执行了,并且将参数传入。初始的成员变量可以省略
3.3python的内置方法(str)
init 构造方法,是Python类内置的方法之一。
这些内置的类方法,各自有各自特殊的功能,这些内置方法我们称之为:魔术方法
str 字符串方法 控制类转换为字符串的行为 (类似toString)
直接print(类)只会输出地址,定义__str__后可以控制print(类)的内容
lt 小于符号比较方法
le 小于等于比较符号方法
eq,比较运算符实现方法 (类似equals)
直接对2个对象进行比较是不可以的,但是在类中实现__lt__或者__le__方法,即可同时完成:小于符号 和 大于符号 2种比较,小于等于,大于等于
传入参数:other,另一个类对象
返回值:True 或 False
3.4 封装,继承,多态
定义私有成员的方式非常简单,只需要:
私有成员变量:变量名以__开头(2个下划线)
私有成员方法:方法名以__开头(2个下划线)
类对象无法访问私有成员
类中的其它成员可以访问私有成员
继承分为:单继承和多继承
继承表示:将从父类那里继承(复制)来成员变量和成员方法(不含私有)
多个父类中,如果有同名的成员,同名的成员方法,那么默认以继承顺序(从左到右)为优先级。
即:先继承的保留,后继承的被覆盖
pass关键字
pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思
复写 (override)
在子类中重新定义同名的属性或方法即可。
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员
如果需要使用被复写的父类的成员,需要特殊的调用方式:
方式1:
调用父类成员
使用成员变量:父类名.成员变量
使用成员方法:父类名.成员方法(self) #self不可少
方式2:
使用super()调用父类成员
使用成员变量:super().成员变量
使用成员方法:super().成员方法()
注意:只能在子类内调用父类的同名成员
多态
函数(方法)形参声明接收父类对象
实际传入父类的子类对象进行工作
以父类做定义声明
以子类做实际工作
用以获得同一行为, 不同状态
抽象类(也可以称之为接口)
抽象类:含有抽象方法的类称之为抽象类
抽象方法:方法体是空实现的(pass)称之为抽象方法
4数据处理
Anaconda (官网:https://www.anaconda.com/),中文大蟒蛇,是一个开源的Python发行版本,Anaconda包括Conda、Python以及一大堆安装好的工具包,比如:numpy、pandas等, 是数据分析,机器学习过程中常用的库.
Anaconda包含了Jupyter Notebook编辑器和IPython解释器, 我们可以在Jupyter Notebook中使用IPython解释器编写代码.
IPython 是一个基于Python的交互式解释器,提供了强大的编辑和交互功能,比默认的python shell 好用得多,支持变量自动补全,自动缩进,支持 bash shell 命令,内置了许多很有用的功能和函数。
Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言。
Jupyter Notebook 的本质是一个 Web 应用程序,便于创建和共享文学化程序文档,支持实时代码,数学方程,可视化和markdown。
shift + tab 可以查看函数参数
启动方式:搜索框搜索anaconda prompt cd进入想要应用的位置,输入jupyter notebook
命令模式(点击运行框外即可) 或者ESC
Shift + Enter : 运行本单元,选中下个单元
Ctrl + Enter : 运行本单元,Alt + Enter : 运行本单元,在其下插入新单元
Y : 单元转入代码状态,M :单元转入markdown状态
A : 在上方插入新单元,B : 在下方插入新单元
DD : 删除选中的单元
编辑模式 (点击运行框内)
Tab : 代码补全或缩进,Shift + Tab : 提示,需要将光标移至括号内
Shift-Enter : 运行本单元,选中下一单元
Ctrl-Enter : 运行本单元
Alt-Enter : 运行本单元,在下面插入一单元
IPython魔法命令
1运行外部Python文件: %run
使用下面命令运行外部python文件(默认是当前目录,其他目录可以加上绝对路径)
%run *.py
2运行计时: %time
用下面命令计算statement的运行时间:
%time statement
用下面命令计算statement的平均运行时间:
%timeit statement
%time一般用于耗时长的代码段
%timeit一般用于耗时短的代码段
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
numpy
cat = plt.imread(‘cat.jpg’)
type(cat)
numpy.ndarray
plt.imshow(cat)
一、创建ndarray
注意:
创建一个所有元素都为1的多维数组
参数说明:
shape : 形状 此参数是一个元组,注意如果只有一个参数要加逗号
dtype=None: 元素类型 默认浮点数
order : {‘C’,‘F’},可选,默认值:C 是否在内存中以行主(C-风格)或列主(Fortran-风格)顺序存储多维数据, 一般默认即可
np.zeros(shape, dtype= None, order=‘C’)
创建一个所有元素都为0的多维数组
np.full(shape, fill_value, dtype=None, order=‘C’)
创建一个所有元素都为指定元素的多维数组
参数说明: fill_value: 填充值 注意是fill_value
np.eye(N, M=None, k=0, dtype=float)
对角线为1其他的位置为0的二维数组,
k为位移位置
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)重
创建一个等差数列
np.arange(start, stop, step, dtype=None)
创建一个数值范围(star,stop)的数组
和Python中range功能类似
np.random.randint(low, high=None, size=None, dtype=‘l’)
创建一个随机整数的多维数组
low : 最小值
high=None: 最大值
high=None时,生成的数值在【0, low)区间内
如果使用high这个值,则生成的数值在【low, high)区间
size=None: 数组形状, 默认只输出一个随机值 size=6(一维的6个数字数组)size=(3,4,5),三个4*5的三维数组
dtype=None: 元素类型b
np.random.randn(d0, d1, …, dn)
创建一个服从标准正态分布的多维数组
标准正态分布又称为u分布,是以0为均数、以1为标准差的正态分布,记为N(0,1) 标准正态分布, 在0左右出现的概率最大, 越远离出现的概率越低, 如下图
创建一个所有元素都为1的多维数组
参数说明:
dn : 第n个维度的数值
9)np.random.normal(loc=0.0, scale=1.0, size=None)
创建一个服从正态分布的多维数组
参数说明:
loc=0.0: 均值, 对应着正态分布的中心
scale: 标准差, 对应分布的宽度,scale越大,正态分布的曲线越矮胖,scale越小,曲线越高瘦
size=None: 数组形状
np.random.random(size=None)
创建一个元素为0~1(左闭右开)的随机数的多维数组
参数说明:
size=None: 数组形状
np.random.rand(d0, d1, …, dn)
创建一个元素为0~1(左闭右开)的随机数的多维数组
和np.random.random功能类似, 掌握其中一个即可
二、ndarray的属性
4个重要属性:
ndim:维度
shape:形状(各维度的长度)
size:总长度
dtype:元素类型
三、ndarray的基本操作
取列:需要切片和索引同时使用,其中逗号之前代表行,逗号后代表列
切某一列:逗号之前根据 :切行,在逗号后切列
切连续列:逗号之前根据 :切行,逗号后也根据 :切列
切不连续列:逗号之前根据 :切行,逗号后根据中括号内容切列
行翻转 n[: : -1 ],列翻转n[ : , : : -1]
3. 变形
使用reshape函数,np.reshape(n,(4,5)) 其中n代表ndarray ,(4,5)代表形状,不能超过size
注 # 使用-1 :代表任意剩余长度,计算机会根据已经给出的另一个维度给出-1代表的值
4. 级联np.concatenate
参数是列表或元组
级联的数组维度必须相同 : 上下必须列同,左右级联必须行同
可通过axis参数改变级联的方向
axis=0 代表行但意思是列上进行,也就是说会上下连接
,axis=1代表列但是会左右连接
np.hstack与np.vstack
horizontal水平级联与vertical垂直级联,作用与concatenate相同
np.hsplit 水平拆分 可以理解为列拆,如np.hsplit(n, 2) 所有列每2列分一组
6. 副本/复制/拷贝¶
使用copy()函数创建副本,即是一种深拷贝,可以避免共用同一个地址导致修改共同发生
如果直接赋值就会发生问题
n1 = np.arange(10)
n2 = n1
n1[0] = 100
display(n1, n2)
n1 = np.arange(10)
n2 = n1.copy()
n1[0] = 100
display(n1, n2)
四、ndarray的聚合操作
np.min 最小值
np.max 最大值
平均值
np.average 平均值
np.median 中位数
np.percentile 百分位数 np.percentile(n, q=50) # 百分位数, q=50表示中位数
np.argmin 最小值对应的下标 #经常用到,查看如loss最低的位置
np.argmax 最大值对应的下标
np.std 标准差
np.var 方差
np.power 次方,求幂 或者n ** 3
np.argwhere 按条件查找 np.argwhere(n==np.max(n)) # 按条件找到所有最大值的下标
n = n.reshape((-1,)) 从上文reshape 可知,-1代表任意数,会根据其他值确定-1值,只有-1就会二维变一维
nan : 数值类型,not a number :不是一个正常的数值,表式空
np.nan : float类型
五、ndarray的矩阵操作
n + 1 # 加 n - 1 # 减 n * 2 # 乘 n / 2 # 除
n // 2 # 整除
n % 2 # 余数
2.线性代数中常用矩阵操作 lin 线性 alg 代数
n1 @ n2
np.round(np.linalg.det(n)) round用来四舍五入
np.linalg.matrix_rank(n)
2. 广播机制
【重要】ndarray广播机制的两条规则
规则一:为缺失的维度补维度
规则二:缺失元素用已有值填充 即会获得相同数据
3. 其他常见数学操作¶
abs、sqrt、square、exp、log、sin、cos、tan、round、ceil、floor、cumsum
n = np.array([1, 4, 8, 9, 16, 25, 64])
np.abs(n) # 绝对值
np.sqrt(n) # 平方根
np.square(n) # 平方
np.exp(n) # 指数 e=2.718281828459045
np.log(n) # 自然对数:以e为底的对数, ln3
np.log(np.e) # 1
np.log(1) # 0
np.log2(n) # 2为底的对数
np.log10(n) # 10为底的对数,常用对数
np.sin(n) # 正弦
np.cos(n) # 余弦
np.tan(n) # 正切
np.round(n, 2) # 四舍五入
np.ceil(n) # 向上取整
np.floor(n) # 向下取整
np.cumsum(n) # 累加 前n个数的和
n= [1, 4, 8, 9, 16, 25, 64]
结果= [1, 5,13, 22, 38, 63, 127]
六、ndarray的排序
np.sort()与ndarray.sort()都可以,但有区别:
np.sort()不改变输入 即原来的数组不变,获得排好序的新数组
np.sort(n1)
ndarray.sort()本地处理,不占用空间,但改变输入 ndarray是代表某个数组 n3.sort
七、ndarray文件操作
保存数组
save : 保存ndarray到一个npy文件 np.save( “file文件名”,arr)
对应的读取np.load(“file文件名”)
savez : 将多个array保存到一个npz文件中 np.savez( “file名.npz”,key1=value1,key2=value2,…)
x = np.arange(5)
y = np.arange(10, 20)
np.savez(‘arr.npz’, xarr=x, yarr=y)
np.load(‘arr.npz’)[‘xarr’] 或者np.load(“arr.npz”)[“yarr”]对应keyvalue读取数据使用key
csv、txt文件的读写操作¶
np.savetxt(‘arr.csv’, n, delimiter=‘,’)
读取csv txt np.loadtxt(“arr.csv”,delimiter=”,”,dtype=np.int16)
Pandas
Pandas 是基于NumPy的一种工具,该工具是为解决数据分析任务而创建的, Pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
Pandas与出色的 Jupyter工具包和其他库相结合,Python中用于进行数据分析的环境在性能、生产率和协作能力方面都是卓越的。
Pandas的主要数据结构是 Series(一维数据)与 DataFrame (二维数据),这两种数据结构足以处理金融、统计、社会科学、工程等领域里的大多数案例
处理数据一般分为几个阶段:数据整理与清洗、数据分析与建模、数据可视化,Pandas 是处理数据的理想工具。
1 Pandas数据结构
Series是一种类似于一维数组的对象,由下面两个部分组成:
values:一组数据(ndarray类型)
index:相关的数据索引标签
1)Series的创建
两种创建方式:
(1) 由列表或NumPy数组创建
默认索引为0到N-1的整数型索引 (索引可以不是数字,也可以不是从0开始)
由列表创建 即先创建列表 pd.Series
list1 = [11, 22, 33, 44]
s = pd.Series(list1)
由numpy数组创建,即将numpy数组放入pd.Series()即可
s.values Series值
s.index Series索引
索引可以修改 s.index = list(‘BCDE’),
根据索引获取值进行操作s.B, s.C,s.D 注意:数字索引需要使用中括号
(2) 由字典创建 直接根据字典的key value 将字典放入pd.Series()创建
d = {‘a’: 11, ‘b’: 22, ‘c’: 33,‘d’: 44}
s = pd.Series(d)
2)Series的索引
可以使用中括号取单个索引(此时返回的是元素类型),或者中括号里一个列表取多个索引(此时返回的仍然是一个Series类型)。分为显示索引和隐式索引:
(1) 显式索引:就是指直接使用索引名
方式1:使用index中的元素作为索引值
方式2:使用.loc[] (推荐)
s = Series({‘语文’: 150, “数学”: 100, “英语”: 120, “Python”: 99})
s[‘语文’] # int类型
s[[‘语文’, “Python”, “数学”]] # Series类型 使用多个索引需要中括号,结果是series类型
s.loc[‘语文’] .loc的结果都是series型
s.loc[[‘语文’, “Python”, “数学”]]
s.loc[[‘语文’]]
(2) 隐式索引:即使用数字下标0,1,2,3,…
方式1:使用整数作为索引值
s[0] # int类型 单个结果
s[[0, 2, 1]] # Series类型 使用多个索引需要中括号,结果是series类型
方式2:使用.iloc[](推荐) 括号内也是写数字下标
s.iloc[0] s.iloc[[0, 2, 1]]
3)Series的切片
s = pd.Series({ ‘语文’: 100, ‘数学’: 150,‘英语’: 110,‘Python’: 130,‘Pandas’: 150,‘NumPy’: 150})
s[1 : 4]
s.iloc[1 : 4]
s[‘数学’ : ‘Python’]
s.loc[‘数学’ : ‘Python’]
4)Series的基本属性和方法
shape:形状 size:长度 元素个数 index:索引 values 值 name:名字
head() 查看前几条数据,默认5条 s.head(x)X为想查看的数据条数
tail() 查看后几条数据,默认5 s.tail(x)
检测缺失数据
pd.isnull() 判断是否为空 括号内传入series 或者 s.isnull() 返回结果为bool类型
pd.notnull() 括号内传入series 或者 s.notnull()
使用bool值索引过滤数据 ,s[] 中括号内的值为true时会显现
法1:s[~s.isnull()] 首先s.isnull的结果,空为true,非空为false ,~波浪线为取反的意思false变为true,
法2:s[s.notnull()] s.notnull()直接得到为非空的值,为true
5)Series的运算
(1) 适用于NumPy的数组运算也适用于Series 加减乘除
s = pd.Series(np.random.randint(10, 100, size=10))
(2) Series之间的运算
在运算中自动对齐索引 即根据相同索引计算,不管位置
即s1索引为0的value和s2索引为0的加
如果索引不对应,则补NaN
Series没有广播机制
注意:要想保留所有的index,则需要使用.add()函数,里面可以加fill_value= 不用Nan
总结
Series: 可以看做是一个有序的字典结构
DataFrame
DataFrame是一个【表格型】的数据结构,可以看做是【由Series组成的字典】(共用同一个索引)。DataFrame由按一定顺序排列的多列数据组成。设计初衷是将Series的使用场景从一维拓展到多维。DataFrame既有行索引,也有列索引。
行索引:index
列索引:columns
值:values(NumPy的二维数组)
1)DataFrame的创建
最常用的方法是传递一个字典来创建。DataFrame以字典的键作为每一【列】的名称,以字典的值(一个数组)作为每一列。
此外,DataFrame会自动加上每一行的索引(和Series一样)。
同Series一样,若传入的列与字典的键不匹配,则相应的值为NaN。
DataFrame的基本属性和方法:
----values 值,即一个二维ndarray数组 df.value -> array([[‘千锋’, 11], [‘Python’, 30],[‘Pandas’, 20]], dtype=object)
----columns 列索引 Index([‘name’, ‘age’], dtype=‘object’)
----index 行索引 RangeIndex(start=0, stop=3, step=1)
----shape 形状
----head() 查看前几条数据,默认5条
----tail() 查看后几条数据,默认5
修改索引 # 设置index df.index = list(‘ABC’)
或者创建时设置 df = pd.DataFrame(d, index=list(‘ABC’))
其他创建DataFrame的方式 注意:需要给一个二维数组,行列数要跟索引匹配
df = pd.DataFrame(
data=np.random.randint(10, 100, size=(4, 6)),
index=[‘小明’, ‘小红’, ‘小黄’, ‘小绿’],
columns=[‘语文’, ‘数学’, ‘英语’, ‘化学’, ‘物理’, ‘生物’]
)
2)DataFrame的索引
(1) 对列进行索引
通过类似字典的方式
通过属性的方式
可以将DataFrame的列获取为一个Series。返回的Series拥有原DataFrame相同的索引,且name属性也已经设置好了,就是相应的列名。
理解如下:通过单一列索引,获取相应数据,类型为series,而行索引自动变为series的index,并且此列索引会变为此series的name属性。
上文的dataframe
df.语文或者df[“语文”]:注意加引号得到的结果都是 series类型
df[[‘语文’, “化学”]] 或者# df[[‘语文’]] 双中括号则会得到dataframe类型
(2) 对行进行索引
使用.loc[]加index来进行行索引 是显示使用,直接括号内加行索引
使用.iloc[]加整数来进行行索引 是隐式使用 括号内使用0,1,2,。。。
同样返回一个Series,index为原来的columns。
不可以直接取行索引 即不能像列索引一样如df[‘小明’] ,报错
DataFrame默认是先取列索引
取行索引
df.loc[‘小明’] df.iloc[0] # Series类型
同理
display(df.loc[[‘小明’, ‘小绿’]])
display(df.iloc[[0, -1]])
(3) 对元素索引的方法
使用列索引
使用行索引(iloc[3,1]相当于两个参数;iloc[[3,3]] 里面的[3,3]看做一个参数)
使用values属性(二维NumPy数组)
先取列,再取行 直接使用索引
df[‘语文’][‘小明’] df[‘语文’][0] df.语文[0] df.语文.小明
先取行,再取列 需要加loc iloc 其中iloc[3,1]==iloc[3][1] 第三行,第一列
注意:但是iloc[[3,3]]是双中括号,结果就变为dataframe ,只有一个参数,结果就为两行相同值第三行的dataframe了。
df.loc[‘小明’][‘语文’] df.loc[‘小明’][0] df.iloc[0][0] df.iloc[0][‘语文’]
3)DataFrame的切片
【注意】 直接用中括号时:
索引优先对列进行操作
切片优先对行进行操作 跟numpy相似,先行后列
df[1: 3] # 隐式 数字 左闭右开
df[‘小红’ : ‘小黄’] # 显式 索引 左闭右闭
df.iloc[ : , 1: 4]
df.loc[:, “数学”: “化学”]
总结:
要么取一行或一列 : 索引 注意 可以结合使用
要么取连续的多行或多列 : 切片
要么取不连续的多行或多列 : 中括号
4)DataFrame的运算
(1) DataFrame之间的运算
在运算中自动对齐不同索引的数据
如果索引不对应,则补NaN 同series一样 可以使用add/divide(,fill_value)
DataFrame没有广播机制
(2) Series与DataFrame之间的运算
使用Python操作符:以行为单位操作(参数必须是行),对所有行都有效。
类似于NumPy中二维数组与一维数组的运算,但可能出现NaN
使用Pandas操作函数:
axis=0:以列为单位操作(参数必须是列),对所有列都有效。
axis=1:以行为单位操作(参数必须是行),对所有行都有效。、、、
df1 = pd.DataFrame(
data=np.random.randint(10, 100, size=(3, 3)),
index=[‘小明’, ‘小红’, ‘小黄’],
columns=[‘语文’, ‘数学’, ‘英语’]
)
s = pd.Series([100, 10, 1], index=df1.columns)
df1.add(s, axis=‘columns’) # 列 也可以写axis=1
若 s = pd.Series([100, 10, 1], index=df1.index)
则 df1.add(s, axis=‘index’) # 行 也可以写axis=0
总结:当series的index为dataframe的index或者columns,对应可以直接加,对应的索引值也会加上,一个为行一个为列则不行。
2 Pandas层次化索引
2.使用元组 使用tuple
index = pd.MultiIndex.from_tuples( (
(‘1班’, ‘张三’), (‘1班’, ‘李四’), (‘1班’, ‘王五’),
(‘2班’, ‘鲁班’), (‘2班’, ‘张三丰’), (‘2班’, ‘张无忌’)
))
3使用product
index = pd.MultiIndex.from_product( [
[‘1班’, ‘2班’],
[‘张三’, ‘李四’, ‘王五’]
]) // 这种方式需要注意大小,如上会产生6行6列,若加名字则会报错
2. 多层列索引
除了行索引index,列索引columns也能用同样的方法创建多层索引
3. 多层索引对象的索引与切片操作
1)Series的操作
(1) 索引
对于Series来说,直接中括号[]与使用.loc()完全一样
s[‘1班’] == s.loc[‘1班’] ==s[[‘1班’]] s[[‘1班’, ‘2班’]]
s[‘1班’][‘张三’]==s.loc[‘1班’][‘张三’]==s.loc[‘1班’, ‘张三’]
s[0] ,s[1] ,s.iloc[1] 会直接得值不需要多层索引 s.iloc[[1, 2]]取多个值用双中括号
(2) 切片
s[‘1班’ : ‘2班’]==s.loc[‘1班’ : ‘2班’]
s.loc[‘1班’][:]
s[1 : 5] ==s.iloc[1 : 5]
2)DataFrame的操作¶
(1) 索引
df[‘期中’]
df[‘期中’][[‘数学’]] //双中括号,保持dataframe形式==df[‘期中’][‘数学’]==df.期中.数学
df.iloc[:, 2] //注 : 记住 iloc永远是先取行
df.iloc[:, [0, 2, 1]] df.loc[:, (‘期中’, ‘数学’)]
df[‘期中’][‘数学’][‘1班’][‘张三’]==df[‘期中’][‘数学’][‘1班’][0]
df.iloc[0, 1]==df.loc[(‘1班’, ‘张三’), (‘期中’, ‘数学’)]
(2) 切片 一样 先行,对列切片也要先切行
df.iloc[1 : 5] ==df.loc[(‘1班’, ‘李四’) : (‘2班’, ‘李四’)]
df.iloc[:, 1: 5]==df.loc[:, ‘期中’: ‘期末’]
df.stack() # 默认是将最里层的列索引变成行索引,变化后的也是最里层索引
df.stack(level=-1) //最里层
df.stack(level=1) //从0开始
【小技巧】使用unstack()的时候,level等于哪一个,哪一个就消失,出现在列里。
df2.unstack()
使用堆叠可能会造成Nan空值,使用fill_value填充 df2.unstack(fill_value=xxx)
5. 聚合操作
DataFrame聚合函数
求和
平均值
最大值
最小值等
df2.values.sum() 使用.values.sum()才能求所有值和
df2.sum()== df2.sum(axis=0) # 默认为0,代表行,求每一列的行和
df2.sum(axis=1) # 求每一行的列和
多层索引聚合操作
sum()无level方法 与上相同
df.sum(axis=0, level=0) #结果即保留第一层行索引,计算每一列的行和
df.sum(axis=0, level=1) # 结果即保留第二层行索引
df.sum(axis=1, level=0) # 结果即保留第一层列索引,计算每一行的列和
df.sum(axis=1, level=1) # 表示计算 列 中 的第2层(level=1)
3 Pandas数据合并
def make_df(indexs, columns):
data = [[str(j)+str(i) for j in columns] for i in indexs]
df = pd.DataFrame(data=data, index=indexs, columns=columns)
return df
定义一个生成dataframe的函数
pandas使用pd.concat函数,与np.concatenate函数类似
pd.concat([df1, df2])
pd.concat([df1, df2], axis=1)
pd.concat([df1, df2], ignore_index=True)
pd.concat([df1, df2], keys=[‘x’, ‘y’])
pd.concat([df1, df2], keys=[‘x’, ‘y’], axis=1)
2) 不匹配级联
不匹配指的是级联的维度的索引不一致。例如纵向级联时列索引不一致,横向级联时行索引不一致
df3 = make_df([1, 2, 3, 4], list(‘ABCD’))
df4 = make_df([2, 3, 4, 5], list(‘BCDE’))
pd.concat([df3, df4])
pd.concat([df3, df4])
pd.concat([df3, df4], join=‘outer’)
pd.concat([df3, df4], join=‘inner’)
3) 使用append()函数添加
由于在后面级联的使用非常普遍,因此有一个函数append专门用于在后面添加
df3.append(df4) 结果与上一致
df1.merge(df2, on=‘name’)
df1.merge(df2, left_on=‘id’, right_on=‘id2’)
当左边的列和右边的index行索引相同的时候,使用,left_index=True或者right_index=True
为true的意思就是使用两个相同的行索引作为连接字段
df1.merge(df2, left_index=True, right_index=True)
df1.merge(df2, left_index=True, right_on=‘id2’) 获得索引相同的值
内合并与外合并 (how=inner,outer,left,right)
内合并:只保留两者都有的key(默认模式) == 数据库 inner join
df1.merge(df2, how=‘inner’)
# 外连接 : 会显示2个表的所有数据
df1.merge(df2, how=‘outer’)
左合并、右合并:how=‘left’,how=‘right’,
df1.merge(df2, how=‘left’)
df1.merge(df2, how=‘right’)
列冲突的解决¶
当列冲突时,即有多个列名称相同时,需要使用on=来指定哪一个列作为key,配合suffixes指定冲突列名,即将两个相同列属性名进行自定义 suffix即后缀意
merge合并总结:
合并有三种现象: 一对一, 多对一, 多对多.
合并默认会找相同的列名进行合并, 如果有多个列名相同,用on来指定.
如果没有列名相同,但是数据又相同,可以通过left_on, right_on来分别指定要合并的列.
如果想和index合并, 使用left_index, right_index来指定.
如果多个列相同,合并之后可以通过suffixes来区分.
还可以通过how来控制合并的结果, 默认是内合并, 还有外合并outer, 左合并left, 右合并right.
4 Pandas缺失值处理
有两种丢失数据(空值):
None python中
np.nan numpy中 多为此
df.isnull().any() # 常用,尽可能找到有空的列或行
df.notnull().all() # 常用,尽量找没有空值的列或行
使用bool值索引过滤数据
cond = df.isnull().any(axis=1)
df[~cond]
cond = df.notnull().all(axis=1)
df[cond]
cond = df.isnull().any()
df.loc[:, ~cond]
cond = df.notnull().all()
df.loc[:, cond]
(2) 过滤函数dropna()
df.dropna()
df.dropna(axis=1) # 删除有空的列
过滤的方式 how = ‘all’# 必须所有数据都为nan才会删除
df.dropna(how=‘all’, axis=1)
inplace=True 修改原数据 之前的操作都是获得一个新对象,可以使用此方法直接修改
df2.dropna(inplace=True)
(3) 填充函数 Series/DataFrame
df.fillna(value=100)
df2.fillna(value=100, limit=1, inplace=True)
df.fillna(method=‘ffill’) # 向前填充 即Nan变为上一行的值
df.fillna(method=‘backfill’) # 向后填充 即Nan变为下一行的值
df.fillna(method=‘ffill’, axis=1) # 向左填充
df.fillna(method=‘backfill’, axis=1) # 向右填充
5 Pandas处理重复值和异常值
1删除重复行
使用duplicated()函数检测重复的行 不考虑列同
返回元素为布尔类型的Series对象
每个元素对应一行,如果该行不是第一次出现,则元素为True
df.duplicated() 结果为true 即为与上一行重复
df.duplicated(keep=‘first’) # 保留第一行 即默认情况,认为第一行不为重复行
df.duplicated(keep=‘last’) # 保留最后一行
df.duplicated(keep=False) # 标记所有重复行,不保留任何一行
df.duplicated(subset=[‘A’,‘B’,‘C’]) // 列表
使用drop_duplicates()函数删除重复的行
df.drop_duplicates()
df.drop_duplicates(subset=[‘A’, ‘B’, ‘C’])
df.drop_duplicates(subset=[‘A’, ‘B’, ‘C’], keep=‘last’)
df.replace ({6: 50, 1: 100}) 50代替6,100代替1
df2[‘Python’].map(lambda x : x * 10) 将得到的属性带入x(形参) ,返回x*10的结果
df2[‘Pandas’] = df2[‘Python’].map(lambda x : x * 10)
df2[‘Java是否及格’] = df2[‘Java’].map(lambda n: ‘及格’ if n>=60 else ‘不及格’)
判断UI成绩 # <60 不及格 60<=n <80 及格 >=80 优秀
def fn(n):
if n < 60:
return ‘不及格’
elif n < 80:
return ‘及格’
return ‘优秀’
df2[‘UI等级’] = df2[‘UI’].map(fn)
3) rename()函数:替换索引
df3.rename({‘张三’: ‘Mr Zhang’}) # 默认修改行索引名 不能直接修改列索引
df3.rename({‘Python’: ‘派森’}, axis=1) # 修改列索引名
或者
df3.rename(index={‘张三’: ‘Mr Zhang’}) # 修改行索引名
df3.rename(columns={‘Python’: ‘派森’}) # 修改列索引名
df3.reset_index()
df3.set_index(keys=[‘H5,…’])
4) apply()函数:既支持 Series,也支持 DataFrame
df[‘Python’].apply(lambda x: True if x>5 else False)
df.apply(lambda x : x.mean(), axis=0) # 求每一列数据的平均值
df.apply(lambda x : x.mean(), axis=1) # 求每一行数据的平均值
也可同上自定义方法
def fn2(x):
return (np.round(x.mean(), 1), x.count()) # 平均值,计数
df.apply(fn2, axis=1)
df.applymap(lambda x : x + 100)
5) transform()函数 两者都适用
df[‘Python’].transform([np.sqrt, np.exp])
def convert(x):
if x.mean() > 5:
return x * 10
return x * (-10)
df.transform(convert) # 处理每一列
df.transform(convert, axis=1) # 处理每一行
注意:.T 是转置的意思
df.drop(): 删除特定索引
df2.drop(‘A’) # 默认删除行
df2.drop(‘Python’, axis=1) # 不能同上直接删除列
df2.drop(index=‘A’) # 删除行
df2.drop(columns=‘Python’) # 删除列
df2.drop(columns=[‘NumPy’, ‘Python’])
df2.drop(index=[‘A’, ‘B’], inplace=True) inplace=true 代表直接修改
unique() : 唯一,去重
df[‘Python’].unique()
df.query : 按条件查询
df.query(‘Python == 9’) # 找到Python列中等于9的所有行
df.query(‘Python < 8’)
df.query(‘Python>6 and NumPy2’) df.query('Python>6 & NumPy2’)
df.query(‘Python3 or NumPy2’) df.query(‘Python3 | NumPy2’)
df.query(‘Python in [3, 4, 5, 6]’) # 成员运算符 可为3,4,5,6中任意
n = 7
df.query(‘Python == @n’) # @n 表式使用变量n的值
m = [3, 4, 5, 6]
df.query(‘Python in @m’) # 成员运算符
df.sort_values(): 根据值排序
df.sort_index(): 根据索引排序
df.sort_values(‘Python’)
df.sort_values(‘Python’, ascending=False) # 降序
df.sort_values(‘B’, axis=1)
df.sort_index(ascending=False)
df.sort_index(ascending=False, axis=1)
df.info(): 查看数据信息
df.info()
np.random.permutation([0, 1, 2])
df2.take(np.random.permutation([0, 1, 2])) //取出三个随机,但不会出现重复的数字顺序,再通过take排列
np.random.randint(0, 3, size=5) //结果可能出现重复值
df2.take(np.random.randint(0, 3, size=5)) //使用take 可能出现重复排列
6 Pandas数学函数
聚合函数 注意 默认都是计算每列
df.count() # 非空的数量 df.count(axis=1)
df.max() # 默认求在每一列中不同行之间的最大值 df.max(axis=1)
df.min() # 最小值
df.min(axis=1)
df.median() # 中位数
df.sum() # 求和
df.sum(axis=1)
df.mean() # 平均值 df.mean(axis=1)
方差:
当数据分布比较分散(即数据在平均数附近波动较大)时,各个数据与平均数的差的平方和较大,方差就较大;
当数据分布比较集中时,各个数据与平均数的差的平方和较小。
因此方差越大,数据的波动越大;方差越小数据的波动就越小
标准差
标准差 = 方差的算术平方根
df.var() # 方差 variance
df.std() # 标准差
其他数学函数
df[1].value_counts() # 统计某列元素出现次数 经常使用
df.cumsum() # 累加 即每一列会加上一列的值
df.cumprod() # 累乘
协方差
两组数值中每对变量的偏差乘积的平均值
协方差>0 : 表式两组变量正相关
如果两个变量的变化趋势一致,也就是说如果其中一个大于自身的期望值时另外一个也大于自身的期望值,那么两个变量之间的协方差就是正值;
协方差<0 : 表式两组变量负相关
如果两个变量的变化趋势相反,即其中一个变量大于自身的期望值时另外一个却小于自身的期望值,那么两个变量之间的协方差就是负值。
协方差=0 : 表式两组变量不相关
df.cov() # 协方差
df[0].cov(df[1]) # 第0列和第1列的协方差
相关系数r
相关系数 = X与Y的协方差 / (X的标准差 * Y的标准差)
相关系数值的范围在-1和+1之间
r>0为正相关,r<0为负相关。r=0表示不相关
r 的绝对值越大,相关程度越高
df.corr() # 所有特征相关系数
df.corrwith(df[2]) # 单一特征相关系数
7 Pandas数据分组聚合
数据聚合是数据处理的最后一步,通常是要使每一个数组生成一个单一的数值。
数据分类处理:
分组:先把数据分为几组
用函数处理:为不同组的数据应用不同的函数以转换数据
合并:把不同组得到的结果合并起来
数据分类处理的核心: groupby()函数
df.groupby(by=‘color’) //得到结果为一个对象
df.groupby(by=‘color’).groups //使用.groups属性查看各行的分组情况:注意不能直接使用
ddd.groupby(‘color’)[‘price’].sum() # 通过对象[属性]获得数据,单括号Series
ddd.groupby(‘color’)[[‘price’]].sum() # 双中括号DataFrame
ddd.groupby(‘color’)[[‘price’]].sum().loc[[‘白’]]
df1 = ddd.groupby(‘item’)[[‘weight’]].sum()
df2 = ddd.groupby(‘item’)[[‘price’]].mean()
df1.merge(df2, left_index=True, right_index=True)
8 Pandas加载数据
csv数据 逗号隔开
df.to_csv: 保存到csv
df.to_csv(‘data.csv’, sep=‘,’, header=True, index=True) 第一个为存储path
pd.read_csv: 加载csv数据
pd.read_csv(‘data.csv’, sep=‘,’, header=[0], index_col=0)
不获取列: header=None 第一行列索引不取
pd.read_table(‘data.csv’, sep=‘,’ , index_col=0)
excel数据
df1.to_excel: 保存到excel文件
df.to_excel(‘data.xlsx’, sheet_name=‘Sheet1’, header=True, index=False)
pd.read_excel: 读取excel
pd.read_excel(‘data.xlsx’, sheet_name=‘Sheet1’, header=[0, 1]) //多行作为列索引
pd.read_excel(‘data.xlsx’, sheet_name=0, header=0, names=list(‘ABCDE’))
MySQL数据
需要安装pymysql
pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple
需要安装sqlalchemy:
pip install sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
sqlalchemy是Python语言下的数据库引擎库
from sqlalchemy import create_engine
data = np.random.randint(0, 150, size=(150, 3))
df = pd.DataFrame(data=data, columns=[‘Python’, ‘Pandas’, ‘PyTorch’])
df.head()
先连接MySQL
conn = create_engine(‘mysql+pymysql://root:1234@localhost:3306/db’)
df.to_sql保存到MySQL
df.to_sql(
name=‘score’, # 数据库中表名字
con=conn, # 数据库连接对象
index=False, # 是否保存行索引
if_exists=‘append’ # 如果表存在,则追加数据
)
pd.read_sql: 从MySQL中加载数据
pd.read_sql(
sql=‘select * from score’, # sql语句
con=conn, # 数据库连接对象
)
9 Pandas分箱操作
分箱操作就是将连续型数据离散化。连续型数据: 温度,身高,年龄
分箱操作分为等距分箱和等频分箱。
等宽分箱
s = pd.cut(df.Python, bins=4) pd.Python代表某列数据,其中bins代表分组个数
s.value_counts().plot.bar() pandas的柱状图使用
也可自己分端
pd.cut(
df.Python, # 分箱数据
bins=[0, 30, 60, 80, 100], # 分箱断点 0-30 30-60 。。。。。
right=False, # 左闭右开,默认是左开右闭
labels=[‘D’, ‘C’, ‘B’, ‘A’] # 分箱后分类的标签
)
2、等频分箱
pd.qcut(
df.Python, # 分箱数据
q=4, # 4等份 只有此处不同
labels=[‘D’, ‘C’, ‘B’, ‘A’] # 分箱后分类的标签
)
10 Pandas时间序列
创建时间戳
pd.Timestamp(‘2030-2-23’) #时刻数据
pd.Period(‘2030-2-23’,freq=‘D’) #时期数据 freq=Y 年 M 月 D 日
.批量生成时刻数据
periods =4: 代表从此向后创建4个时间
freq=’D’ :按天为周期
pd.date_range(‘2030.02.13’,periods=4,freq=‘D’) #时刻数据
pd.period_range(‘2030.02.13’,periods=4,freq=‘D’) #时期数据
创建时间戳的主要目的是给series做索引
#时间戳索引
index=pd.date_range(‘2030.02.13’,periods=4,freq=‘D’) 、
pd.Series(np.random.randint(0,10,size=4),index=index)
#2.转换方法
pd.to_datetime([‘2030.03.14’,‘2030-3-14’,‘14/03/2030’,‘2030/3/14’])
#时间戳->时间
pd.to_datetime([1899678987],unit=‘s’)
#时间差 DateOffset
dt+pd.DateOffset(hours=8) #+8个小时
dt+pd.DateOffset(days=8) #+8天
dt-pd.DateOffset(hours=8) #-8个小时 或者加 -8
#3时间戳的索引和切片
index=pd.date_range(‘2030-03-14’,periods=100,freq=‘D’) 获得100个时间
ts=pd.Series(range(len(index)),index=index)
#索引
ts[‘2030-03-22’] #具体某天的索引
ts[‘2030-03’] #三月份 也可以 ts[‘2030-3’]
ts[‘2030’] #2030年
#切片
ts[ ‘2030-03-22’: ‘2030-03-25’ ]
#时间戳索引
pd.Timestamp(‘2030-03-22’)
ts[pd.Timestamp(‘2030-03-22’)]
#切片
ts[pd.Timestamp(‘2030-03-22’):pd.Timestamp(‘2030-03-28’)]
#date_range
ts[pd.date_range(‘2030-3-24’,periods=5,freq=‘D’)]
#4 属性
ts.index
ts.index.year #年
ts.index.month #月
ts.index.day #日
ts.index.dayofweek #星期几
时间序列常用方法
对时间做一些移动/滞后、频率转换、采样等相关操作
index=pd.date_range(‘2030-3-1’,periods=365,freq=‘D’) 创建一年时间索引
ts=pd.Series(np.random.randint(0,500,size=len(index)),index=index) 创建Series
#1.移动
ts.shift() #默认后移一位
ts.shift(periods=2) #后移一位
ts.shift(periods=-2) #前移两位
#2.频率转换 由天转换为其他
ts.asfreq(pd.tseries.offsets.Week()) #天 -> 星期
ts.asfreq(pd.tseries.offsets.MonthEnd()) #天 -> 月
ts.asfreq(pd.tseries.offsets.MonthBegin())
#由少变多,使用fill_value补充
ts.asfreq(pd.tseries.offsets.Hour(),fill_value=0) #天 -> 小时
resample:根据日期维度进行数据聚合
·按照分钟(T)、小时(H)、日(D)、周(W)、月(M)、年(Y)等来作为日期维度
ts.resample(‘2D’).sum() #以两天为单位进行汇总,求和
ts.resample(‘2W’).sum() #以两周为单位进行汇总,求和
ts.resample(‘3M’).sum() #以3月(一季度)为单位进行汇总,求和
ts.resample(‘3M’).sum().cumsum() #以两天为单位进行汇总,求和,累加
ts.resample(‘H’).sum().cumsum() #以1小时为单位进行汇总,求和,累加
#4.DataFrame重采样
d = {
‘price’:[10,11,2,44,33,44,55,66],
‘score’:[40,30,20,50,60,70,80,10],
‘week’:pd.date_range(‘2030-3-1’, periods=8,freq=‘W’)
}
df = pd.DataFrame(d) #创建dataframe
假设对week列进行按月汇总求和 on=。。。。
df.resample(‘M’,on=‘week’).sum()
df.resample(‘M’,on=‘week’).apply(np,sum) apply两者都能用,见上
#对week列进行按月汇总求和 price求平均,score 求和
df.resample(‘M’,on=‘week’).agg({‘price’:np.mean ,‘score’:np.sum})
#注意sum,mean不要打括号,调的是函数
时区
#tz :timezone:时区
import pytz
ts=ts.tz_localize(tz=‘UTC’) UTC 世界统一时间
#时区转换
ts.tz_convert(tz=‘Asia/Shanghai’)
11Pandas绘图
Series和DataFrame都有一个用于生成各类图表的plot方法
Pandas的绘图是基于Matplotlib, 可以快速实现基本图形的绘制,复杂的图形还是需要用Matplotlib 所以需要导入 matplotlib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
常见可视化图形:
折线图
条形图/柱形图
饼图
散点图
箱型图
面积图
直方图
折线图 默认line
Series图表
s = pd.Series([100, 250, 300, 200, 150, 100])
s.plot()
x = np.arange(0, 2*np.pi, 0.1) np.pi=π
y = np.sin(x)
s = pd.Series(data=y, index=x)
s.plot()
DataFrame图表
图例的位置可能会随着数据的不同而不同
data = np.random.randint(50, 100, size=(5, 6))
index = [‘1st’, ‘2nd’, ‘3th’, ‘4th’, ‘5th’]
columns = [‘Jeff’, ‘Jack’, ‘Rose’, ‘Lucy’, ‘Lily’, ‘Bob’]
df = pd.DataFrame(data=data, index=index, columns=columns)
df.plot()
条形图和柱状图 barh/bar
Series柱状图示例,kind = ‘bar’/‘barh’
df = pd.DataFrame(data=np.random.rand(10, 4)) 10hang4列 0到1
df.plot(kind=‘bar’) # 第一种方式
s = pd.Series(data=[100, 200, 300, 200])
s.index = [‘Lily’, ‘Lucy’, ‘Jack’, ‘Rose’]
s.plot(kind=‘barh’) # 水平:条形图
DataFrame柱状图示例
data = np.random.randint(0, 100, size=(4, 3))
index = list(“ABCD”)1
columns = [‘Python’, ‘C’, ‘Java’]
df = pd.DataFrame(data=data, index=index, columns=columns)
df.plot(kind=‘barh’)
直方图 hist
rondom生成随机数百分比直方图,调用hist方法
柱高表示数据的频数,柱宽表示各组数据的组距
参数bins可以设置直方图方柱的个数上限,越大柱宽越小,数据分组越细致
设置density参数为True,可以把频数转换为概率
s = pd.Series([1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 5, 5, 6, 6])
#直方图 出现频数
s.plot(kind=‘hist’)
s.plot(kind=‘hist’, bins=5, density=True)
kde图:核密度估计,用于弥补直方图由于参数bins设置的不合理导致的精度缺失问题
s.plot(kind=‘kde’) # 可以结合上面的直方图一起显示,效果更好
饼图 pie
df = pd.DataFrame(data=np.random.rand(4, 2), index=list(‘ABCD’),columns=[‘Python’, ‘Java’])
df.plot.pie(subplots=True, figsize=(8, 8), autopct=‘%.1f%%’) figsize:饼状图的大小
散点图 scatter
散点图是观察两个一维数据数列之间的关系的有效方法,DataFrame对象可用 即两列数据
data = np.random.normal(size=(1000, 2))
df = pd.DataFrame(data=data, columns=list(‘AB’))
df.plot(kind=‘scatter’, x=‘A’, y=‘B’) 或者 df.plot.scatter(x=‘A’, y=‘B’)
面积图 area
df = pd.DataFrame(data=np.random.rand(10, 4), columns=list(‘ABCD’))
箱型图 box
df = pd.DataFrame(data=np.random.rand(10, 4), columns=list(‘ABCD’))
df.plot.box()
箱型图看法
Matplotlib介绍
运行时配置参数
rcParams : runtime configuration Parameters
运行时配置参数
%matplotlib inline
大部分时候都要使用
plt.rcParams[‘font.sans-serif’] = ‘SimHei’
plt.rcParams[‘axes.unicode_minus’] = False
%config Inlinebackend.figure_format = ‘svg’
基本绘图
plt.plot()
x = np.linspace(-5, 5, 50) #等差数列
y = x**2
plt.plot(x, y)
样式和颜色
样式: ‘-’:实线,‘–’:虚线,‘-.’,‘:’,‘.’,‘,’,,o,^,v,<,>,s,+,x,D,d,1,2,3,4,h,H,p,| ,_
颜色: b(蓝色),g(绿色),r(红色),c(青色),m(品红),y(黄色),k(黑色),w(白色)
x = np.linspace(-5, 5, 50)
y = x**2
plt.plot(x, y, color=‘red’,ls=‘-’) # ls :LineStyle 线的样式
或者plt.plot(x, y,‘r–’)
画布配置
plt.figure()
fig = plt.figure(figsize=(6, 4), dpi=100, facecolor=‘#11aa11’)
x = np.linspace(0, 2*np.pi) ,y = np.sin(x)
plt.plot(x,y)
plt.grid()
立刻显示图片¶
show
plt.show() 会将之前的图片立刻显示,后面若有图片则会另外出现
Matplotlib多图布局
子图 使用subplot()函数
fig = plt.figure(figsize=(8, 5))
x = np.linspace(-np.pi, np.pi, 30)
y = np.sin(x)
axes2 = plt.subplot(222) # 2行2列的第2个子视图
axes2.plot(x, y)
axes2.set_title(‘子图2’)
axes3 = plt.subplot(2, 2, 3) # 2行2列的第3个子视图
axes3.plot(x, y)
axes3.set_title(‘子图3’)
fig.tight_layout()
图形嵌套
add_subplot()函数
fig = plt.figure(figsize=(8, 5))
axes1 = fig.add_subplot(1, 1, 1)
axes1.plot( [0,1] , [ 1, 3]) x轴0-1,y轴1-3
axes2 = fig.add_subplot(2, 2, 1, facecolor=‘pink’)
axes2.plot([1, 3])
使用 axes() 函数
使用 add_axes() 函数
fig = plt.figure(figsize=(8, 5))
x = np.linspace(0, 2*np.pi, 30)
y = np.sin(x)
plt.plot(x, y)
axes1 = plt.axes([0.55, 0.55, 0.3, 0.3])
axes1.plot(x, y, color=‘g’)
axes2 = fig.add_axes([0.2, 0.2, 0.25, 0.25])
axes2.plot(x, y, color=‘r’)
使用 subplots() 函数
x = np.linspace(0, 2*np.pi)
fig, ax = plt.subplots(3, 3)
ax1, ax2, ax3 = ax
ax11, ax12, ax13 = ax1
ax21, ax22, ax23 = ax2
ax31, ax32, ax33 = ax3
双轴显示 x,y轴对面的轴也能显示
plt.figure(figsize=(6,4))
x = np.linspace(0, 10, 100)
axes1 = plt.gca() # 获取当前轴域
axes1.plot(x, np.exp(x), color=‘red’)
axes1.set_xlabel(‘time’) #子图需要使用set_xlabel 不是子图直接使用
axes1.set_ylabel(‘exp’, color=‘red’)
axes1.tick_params(axis=‘y’, labelcolor=‘red’) #刻度参数
axes2 = axes1.twinx() # 和图1共享x轴
axes2.set_ylabel(‘sin’, color=‘blue’)
axes2.plot(x, np.sin(x), color=‘blue’)
axes2.tick_params(axis=‘y’, labelcolor=‘blue’)
Matplotlib绘图属性设置
绘图基本属性
图例
legend
fig = plt.figure(figsize=(8, 5))
x = np.linspace(0, 2*np.pi)
plt.plot(x, np.sin(x), label=‘sin’)
plt.plot(x, np.cos(x), label=‘cos’)
plt.legend()
如果不在plot里使用label 也可以在legend中学
plt.legend([‘Sin’, ‘Cos’], 图例
fontsize=18,
loc=‘center’, # 居中
ncol=2, # 图例显示成几列
# bbox_to_anchor = [x, y, width, height]
bbox_to_anchor=[0, 0.8, 1, 0.2] # 图例的具体位置
)
线条属性
color 颜色
linestyle 样式
linewidth 宽度
alpha 透明度
marker 标记的样式或点的样式
mfc: marker face color 标记的背景颜色
坐标轴刻度
xticks
yticks
可以自行修改X轴,y’轴刻度(不是修改初始值)
x = np.linspace(0, 10) y = np.sin(x) plt.plot(x,y)
plt.yticks(ticks=[-1, 0, 1], # 自行设置的刻度值
labels=[‘min’, ‘0’, ‘max’], # 刻度值对应的标签名(显示)
fontsize=20, # 文字大小
ha=‘right’, # 水平对齐方式
color=‘blue’ # 颜色
)
plt.xticks(ticks=np.arange(0, 11, 1), fontsize=20, color=‘red’ )
坐标轴范围
xlim
ylim
plt.xlim(-2, 8)
plt.ylim(-2, 2)
坐标轴配置
axis
plt.axis([-2, 8, -2, 2])
plt.axis(‘square’)
标题 和 网格
title
grid
plt.title(‘sin曲线’, fontsize=20, loc=‘center’)
plt.suptitle(‘父标题’, y=1.1, # 位置 fontsize=30 #文字大小)
注意:这些都是对网格线的控制
plt.grid(ls=‘–’, lw=0.5, c=‘gray’, axis=‘y’)
标签
xlabel
ylabel
plt.xlabel(‘y=sin(x)’, #x轴下的标签
fontsize=20, #文字大小
rotation=0, # 旋转角度 x轴默认0度
)
plt.ylabel(‘y=sin(x)’, #y轴下的标签
rotation=90, # 旋转角度 y轴默认为90
horizontalalignment=‘right’, # 水平对齐方式
fontsize=20 )
文本
text
图表
plt.figure(figsize=(8, 5))
x = np.linspace(0, 9, 10)
y = np.array([60, 30, 20, 90, 40, 60, 50, 80, 70, 30])
plt.plot(x, y, ls=‘–’, marker=‘o’)
#加文字一
plt.text(2,40,‘rui’) #x,y轴的位置,内容
#加文字
for a, b in zip(x, y): #zip的作用是将x,y两个数组进行一一对应组合,变为元组,用a,b取出来
plt.text(
x=a, # x坐标
y=b, # y坐标
s=b, # s应该是string 接上 文字内容
ha=‘center’, # 调整文字位置 水平居中
va=‘center’, # 垂直居中
fontsize=14, # 文字大小color=‘r’ # 文字颜色 )
注释
annotate
plt.annotate(
text=‘最高销量’, # 标注的内容
xy=(3, 90), # 标注的坐标点,也是箭头指向的位置
xytext=(1, 80), # 标注的内容的位置
# 箭头样式
arrowprops={
‘width’: 2, # 箭头线的宽度
‘headwidth’: 8, # 箭头头部的宽度
‘facecolor’: ‘blue’ # 箭头的背景颜色})
保存图片
savefig
f = plt.figure(figsize=(8, 5))
x = np.linspace(0, 2*np.pi)
plt.plot(x, np.sin(x) )
plt.plot(x, np.cos(x))
方式一 :直接 plt.savefig(‘images/4-11.png’)
方式二: 应用获得的f对象
f.savefig(
fname=‘pic_name2.png’, # 文件名:png、jpg、pdf
dpi=100, # 保存图片像素密度
facecolor=‘pink’, # 背景颜色
# edgecolor=‘lightgreen’, # 边界颜色
bbox_inches=‘tight’, # 保存图片完整
pad_inches=1 # 内边距 最外边和图片的距离
)
Matplotlib图
#必备
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams[‘axes.unicode_minus’] = False
plt.rcParams[‘font.sans-serif’] = ‘SimHei’
Matplotlib-折线图
绘制一条线
plt.figure(figsize=(8, 5))
x = [“Mon”, “Tues”, “Wed”, “Thur”, “Fri”,“Sat”,“Sun”]
y = [20, 40, 35, 55, 42, 80, 50]
plt.plot(x, y, c=“g”, marker=‘D’, markersize=5)
#绘制坐标轴标签
plt.xlabel(“星期”)
plt.ylabel(“活跃度”)
plt.title(“Python语言活跃度”)
#增加文本解释
for x1, y1 in zip(x, y):
plt.text(x1, y1, str(y1), ha=‘center’, va=‘bottom’, fontsize=16)
绘制多条线
注意:plt.plot()中直接给x值其实是y轴值,x轴值为选填,不写会自动生成
记得附带图例 legend
plt.figure(figsize=(8, 5))
x = np.random.randint(0, 10, size=15)
plt.plot(x, marker=‘*’, color=‘r’)
plt.plot(x.cumsum(), marker=‘o’)
Matplotlib-柱形图
简单柱状图 plt.bar() # 需要填写x,y轴值
fig = plt.figure(figsize=(8, 5))
x = [‘语文’, ‘数学’, ‘英语’, ‘Python’, ‘化学’]
y = [20, 10, 40, 60, 10]
plt.bar(x, y)
plt.show()
一次绘制多个柱状图 防止重叠
width=0.2
plt.bar(x-width, y1, width=width, label=‘北区’)
plt.bar(x, y2, width=width, label=‘中区’)
plt.bar(x+width, y3, width=width, label=‘南区’)
plt.bar(x, y1, label=‘北区’)
plt.bar(x, y2, label=‘中区’, bottom=y1) # 画图的时候y轴的底部起始值
plt.bar(x, y3, label=‘南区’, bottom=y1+y2)
plt.barh(x, y1)
Matplotlib-直方图
直方图(Histogram),又称质量分布图,它是柱形图的一种,由一系列高度不等的纵向线段来表示数据分布的情况。直方图的横轴表示数据类型,纵轴表示分布情况。
直方图用于概率分布,它显示了一组数值序列在给定的数值范围内出现的概率或次数。
x = np.random.randint(0, 10, 100)
pd.Series(x).value_counts()
plt.hist(x, bins=5)
plt.hist(x, bins=[0, 3, 6, 9, 10])
plt.hist(x, bins=range(40, 110, 6), facecolor=‘b’, alpha=0.4,
edgecolor=‘k’, density=True)
Matplotlib-箱型图 plt.boxplot
x = [1, 2, 3, 5, 7, 9, -10]
plt.boxplot(x)
一次画多个箱型图
x1 = np.random.randint(10, 100, 100)
x2 = np.random.randint(10, 100, 100)
x3 = np.random.randint(10, 100, 100)
plt.boxplot([x1, x2, x3])
data=np.random.normal(size=(500, 4))
lables = [‘A’,‘B’,‘C’,‘D’]
plt.boxplot(data,
notch=True, # 箱型图样式
sym=‘g *’, # 颜色+marker样式
labels=lables # x轴标签
)
Matplotlib-散点图 plt.scatter
散点图是观察两个一维数据数列之间的关系的有效方法
x = range(1, 7, 1)
y = range(10, 70, 10)
plt.scatter(x, y, marker=‘o’)
#气泡图
data = np.random.randn(100, 2)
s = np.random.randint(50, 200, size=100) 随机大小
color = np.random.randn(100) 随机颜色
plt.scatter(
data[:, 0], # x坐标 切片:行全保留,切第0列
data[:, 1], # y坐标
s=s, # 尺寸
c=color, # 颜色
alpha=0.6 # 透明度
)
plt.hexbin(x, y, gridsize=20, cmap=“rainbow”)
Matplotlib-饼图
x = [10, 20, 30, 40]
plt.pie(x, autopct=‘%.1f%%’) # autopct:显示百分比 .1f保留一位小数
要显示%必须两个%
df = pd.read_excel(‘data/plot.xlsx’, sheet_name=‘pie1’)
citys, values = df.省份, df.销量 //citys= df[“省份”],
plt.figure(figsize=(5, 5))
plt.pie(
x=values, # 值
autopct=‘%.1f%%’, # 百分比
labels=citys, # 标签
pctdistance=0.8, # 百分比数字的位置
explode=[0, 0, 0, 0.1, 0, 0.1, 0, 0, 0, 0], # 分裂效果(距离)
# 字体样式
textprops={‘fontsize’: 12, ‘color’: ‘blue’},
shadow=True #阴影
)
单个圆环:甜甜圈
# wedgeprops={‘width’: 0.4, ‘edgecolor’: ‘w’}
)
多个圆环
plt.pie(
x=values2, # 值
autopct=‘%.1f%%’, # 百分比
# labels=citys2, # 标签
pctdistance=0.8, # 百分比文字的位置
# 字体样式
textprops={‘fontsize’: 8, ‘color’: ‘k’},
# 半径
radius=0.6
)
Matplotlib-面积图
跟折线图相似,只是折线与x轴相连形成面积
面积图又称区域图,和折线图差不多,强调y轴随x轴而变化的程度,可用于引起人们对总值趋势的注意。
x = [1, 2, 3, 4, 5]
y = np.random.randint(10, 100, 5)
plt.stackplot(x, y)
Matplotlib-热力图
热力图是一种通过对色块着色来显示数据的统计图表。绘图时,需指定颜色映射的规则。
df = pd.read_excel(‘data/plot.xlsx’, sheet_name=‘imshow’) #读取数据
data = df.drop(columns=‘省份’).values #获得去除省份列的数据
y = df[‘省份’] #省份数据
x = df.drop(columns=‘省份’).columns #column数据
或者 # x = df.columns[1:]
plt.figure(figsize=(14, 10))
plt.imshow(data, cmap=‘Blues’)
plt.xticks(range(len(x)), x)
plt.yticks(range(len(y)), y)
for i in range(len(x)):
for j in range(len(y)):
plt.text(x=i, y=j, s=data[j, i], #x,y是位置,x是列,y是行,所以先j再i
ha=‘center’,
va=‘center’,
fontsize=12
)
plt.colorbar()
Matplotlib-极坐标图
极坐标系是一个二维坐标系统,该坐标系统中任意位置可由一个夹角和一段相对原点—极点的距离来表示。
N = 8 # 分成8份
x = np.linspace(0, 2*np.pi, N, endpoint=False) # 360度等分成8份
height = np.random.randint(3, 15, size=N) # 值
width = np.pi / 4 # 宽度
colors = np.random.rand(8, 3) # 随机颜色
ax = plt.subplot(111, projection=‘polar’)
ax.bar(x=x, height=height, width=width, bottom=0, color=colors)
Matplotlib-雷达图
雷达图是以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多变量数据的图形方法
fig = plt.figure(figsize=(6, 6))
x = np.linspace(0, 2*np.pi, 6, endpoint=False)
y = [83, 61, 95, 67, 76, 88]
x = np.concatenate((x, [x[0]])) #其实就是第一个值最后一个值相同
y = np.concatenate((y, [y[0]]))
axes = plt.subplot(111, polar=True) # 或者projection=‘polar’
axes.plot(x, y, ‘o-’, linewidth=2) # 连线 ==折线图
axes.fill(x, y, alpha=0.3) # 填充
axes.set_rgrids([20, 40, 60, 80], fontsize=14)
Matplotlib-等高线图
等高线图
等高线图:也称水平图,是一种在二维平面上显示 3D 图像的方法
meshgrid会根据两者的数量各自进行复制
x会向下复制y个数字,y会竖着复制x个
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y) #x是每一行从-5到5,一直向下复制的array
#y是从每一列-5到5,一直向右复制的array
Z = np.sqrt(X2 + Y2)
cp = plt.contourf(X, Y, Z)
plt.colorbar(cp)
Matplotlib-3D图
from mpl_toolkits.mplot3d.axes3d import Axes3D
fig = plt.figure(figsize=(5, 3))
x = np.linspace(0, 100, 400)
y = np.sin(x)
z = np.cos(x)
axes = Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(axes)
axes.plot(x,y,z)
x = np.random.rand(50)
y = np.random.rand(50)
z = np.random.rand(50)
axes.scatter(x, y, z, color=‘red’, s=100) #s 点的大小
x = np.arange(1, 5)
for m in x:
axes.bar(
np.arange(4), #y轴
np.random.randint(10, 100, size=4), #z轴
zs=m, # 在x轴中的第几个
zdir=‘x’, # 在哪个方向上排列
alpha=0.7, # 透明度
width=0.5 # 宽度)
Matplotlib-图像处理
读取图片¶
imread
img = plt.imread(‘data/pandas.jpeg’) #img值是个三维数据,分别代表RGB
显示图片¶
imshow
plt.imshow(img)
plt.imshow(img, origin=‘lower’)
plt.imshow(img[::-1])
plt.imshow(img[:, ::-1])
#保存图片 记住:img不是画布fig,而是数据
imsave
plt.imsave(‘pandas.png’, img[100:400, 600:)
科比投篮数据可视化项目
读取科比投篮数据
df = pd.read_csv(‘data/kobe.csv’)
1, 分别根据投篮点坐标(loc_x, loc_y) 和 投篮经纬度(lat, lon)画出科比投篮的位置
(2个散点图, 使用子图)
plt.figure(figsize=(52, 5)) #根据要求,设置两个图,宽为52,行为5
axes1 = plt.subplot(1, 2, 1) #设置1行两列的第一个子图
axes1.scatter(df[‘loc_x’], df[‘loc_y’])
axes1.set_title(‘投篮点坐标’, fontsize=12) #注意:子图不能直接titlle,需要set
画图对比科比的出手方式action_type的次数,得出科比哪种出手方式最多¶
使用条形图
画图显示前10个即可
df[‘action_type’].value_counts().head(10)[::-1].plot.barh(figsize=(8, 4))
或者
action_type=df[‘action_type’].value_counts().head(10)[::-1]
plt.figure(figsize(5,3))
plot.barh(action_type.index, action_type.values)
科比各种投篮方式命中率 可视化对比¶
combined_shot_type 投篮方式
action_type_sum = df.groupby(‘combined_shot_type’)[‘shot_made_flag’].sum()
#groupby分组,结果是一个对象,再通过对象[属性]获得数据,单括号为Series
action_type_count = df.groupby(‘combined_shot_type’)[‘shot_made_flag’].count()
action_type_rate = action_type_sum / action_type_count
目的:将原复杂字符串变为简单数组
factorize函数可以将Series中的标称型数据映射称为一组数字,相同的标称型映射为相同的数字。factorize函数的返回值是一个tuple(元组),元组中包含两个元素。第一个元素是一个array,其中的元素是标称型元素映射为的数字;第二个元素是Index类型,其中的元素是所有标称型元素,没有重复。
#1行3列
plt.figure(figsize=(4*3, 4))
N = [‘shot_zone_basic’, ‘shot_zone_range’, ‘shot_zone_area’]
for k in range(3):
#子图 从一到三 k=0,1,2,
axes = plt.subplot(1, 3, k+1)
axes.scatter(df[‘loc_x’], df[‘loc_y’], c=df[N[k]], cmap=‘rainbow’)
axes.set_title(N[k], fontsize=20)
1人工智能、机器学习与深度学习
人工智能的简洁定义如下:
努力将通常由人类完成的智力任务自动化。因此,人工智能是一个综合性的领域,不仅包括机器学习与深度学习,还包括更多不涉及学习的方法。
在相当长的时间内,许多专家相信,只要程序员精心编写足够多的明确规则来处理知识,就可以实现与人类水平相当的人工智能。这一方法被称
为符号主义人工智能(symbolic AI).
符号主义人工智能适合用来解决定义明确的逻辑问题,比如下国际象棋,但它难以给出明确的规则来解决更加复杂、模糊的问题,比如图像分类、语音识别和语言翻译。于是出现了一种新的方法来替代符号主义人工智能,这就是机器学习(machine learning).
机器学习系统是训练出来的,而不是明确地用程序编写出来的。将与某个任务相关的许多示例输入机器学习系统,它会在这些示例中找到统计结构,从而最终找到规则将任务自动化。
机器学习经常用于处理复杂的大型数据集(比如包含数百万张图像的数据集,每张图像又包含数万个像素),用经典的统计分析(比如贝叶斯分析)来处理这种数据集是不切实际的。
机器学习(尤其是深度学习) 呈现出相对较少的数学理论(可能太少了),并且是以工程为导向的。
这是一门需要上手实践的学科,想法更多地是靠实践来证明,而不是靠理论推导。
需要以下三个要素来进行机器学习。
‰ 输入数据点。例如,你的任务是语音识别,那么这些数据点可能是记录人们说话的声音文件。如果你的任务是为图像添加标签,那么这些数据点可能是图像。
‰ 预期输出的示例。对于语音识别任务来说,这些示例可能是人们根据声音文件整理生成的文本。对于图像标记任务来说,预期输出可能是“狗”“猫”之类的标签。
‰ 衡量算法效果好坏的方法。这一衡量方法是为了计算算法的当前输出与预期输出的差距。
衡量结果是一种反馈信号,用于调节算法的工作方式。这个调节步骤就是我们所说的学习。
机器学习中的学习指的是,寻找更好数据表示的自动搜索过程.
1.1.4 深度学习之“深度”
深度学习是机器学习的一个分支领域:它是从数据中学习表示的一种新方法,强调从连续的层(layer)中进行学习.
“深度学习”中的“深度”指的并不是利用这种方法所获取的更深层次的理解,而是指一系列连续的表示层。
数据模型中包含多少层,这被称为模型的深度(depth)。这一领域的其他名称包括分层表示学习(layered representations learning)和层级表示学习(hierarchical representations learning).
其他机器学习方法的重点往往是仅仅学习一两层的数据表示,因此有时也被称为浅层学习(shallow learning)。.
在深度学习中,这些分层表示几乎总是通过叫作神经网络(neural network)的模型来学习得到的。神经网络的结构是逐层堆叠。
神经网络这一术语来自于神经生物学,但深度学习模型不是大脑模型。
如图 1-6 所示,这个网络将数字图像转换成与原始图像差别越来越大的表示,而其中关于
最终结果的信息却越来越丰富。你可以将深度网络看作多级信息蒸馏操作:信息穿过连续的过
滤器,其纯度越来越高(即对任务的帮助越来越大)。
通过notand 和or的值,再进行一次and 就可以得到XOR(异或)(or中1,1应该为1,但结果不变,理解意思即可)
权重(weight)参数(parameter)损失函数(loss function)优化器(optimizer)
损失函数的输入是网络预测值与真实目标值(即你希望网络输出的结果),然后计算一个距离值,衡量该网络在这个示例上的效果好坏。
深度学习的基本技巧是利用这个距离值作为反馈信号来对权重值进行微调,以降低当前示
例对应的损失值。
这种调节由优化器(optimizer)来完成,它实现了所谓的反向传播(backpropagation)算法,这是深度学习的核心算法。
先前的机器学习技术(浅层学习)仅包含将输入数据变换到一两个连续的表示空间,通常
使用简单的变换,比如高维非线性投影(SVM)或决策树。但这些技术通常无法得到复杂问题
所需要的精确表示。因此,人们必须竭尽全力让初始输入数据更适合用这些方法处理,也必须
手动为数据设计好的表示层。这叫作特征工程。与此相反,深度学习完全将这个步骤自动化:
利用深度学习,你可以一次性学习所有特征,而无须自己手动设计。这极大地简化了机器学习
工作流程。
在实践中,如果连续应用浅层学习方法,其收益会随着层数增加迅速降低,因为三层模型中最优的第一表示层并不是单层或双层模型中最优的第一表示层。
深度学习的变革性在于,模型可以在同一时间共同学习所有表示层,而不是依次连续学习(这被称为贪婪学习)。
深度学习从数据中进行学习时有两个基本特征:第一,通过渐进的、逐层的方式形成越来
越复杂的表示;第二,对中间这些渐进的表示共同进行学习,每一层的变化都需要同时考虑上
下两层的需要
2神经网络的数学基础
2.1 初识神经网络
将手写数字的灰度图像(28 像素×28 像素)划分到 10 个类别中(0~9)。我们将使用 MNIST 数据集。
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images 和 train_labels 组成了训练集(training set),模型将从这些数据中进行
学习。然后在测试集(test set,即 test_images 和 test_labels)上对模型进行测试。
在机器学习中,分类问题中的某个类别叫作类(class)。
数据点叫作样本(sample)。某个样本对应的类叫作标签(label)。
from keras import models
from keras import layers
network = models.Sequential()
network.add(layers.Dense(512, activation=‘relu’, input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation=‘softmax’))
理解:其中activation代表激活函数,512,10是神经元,28 * 28的图片实际上会变为784*1的形状,然后每个都会分别与每个神经元连接 ,
还要加上个截距项B
,而后512个同样,连接第二层的所有神经元。所以Dense被称为全连接
网络包含 2 个 Dense 层,它们是密集连接(也叫全连接)的神经层。
第二层(也 是最后一层)是一个 10 路 softmax 层,
它将返回一个由 10 个(由之前数字决定)概率值(总和为 1)组成的数组。
每个概率值表示当前数字图像属于 10 个数字类别中某一个的概率。
要想训练网络,我们还需要选择编译(compile)步骤的三个参数。
‰ 损失函数(loss function):网络如何衡量在训练数据上的性能,即网络如何朝着正确的
方向前进。 学习权重张量的反馈信号,在训练阶段应使它最小化
‰ 优化器(optimizer):基于训练数据和损失函数来更新网络
(使用梯度下降的具体方法)的机制。
‰ 在训练和测试过程中需要监控的指标(metric):即精度的条件
network.compile(optimizer=‘rmsprop’, #还会经常使用Adam,多一个向量
loss=‘categorical_crossentropy’,
metrics=[‘accuracy’])
#之前训练图像保存在一个 uint8 类型的数组中,其形状为 (60000, 28, 28),取值区间为 [0, 255]。图片的RGB大小都为0-255
train_images = train_images.reshape((60000, 28 * 28)) #三维压成二维
train_images = train_images.astype(“float32”) / 255
#记住深度学习喜欢0附近的数字,浮点,将原本uint8先变为float,再除以255,变为【0,1】
同理,对测试数据操作
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype(“float32”) / 255
#对标签进行分类编码 to_categorical, keras自带的one-hot编码,将labels变为向量
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
network.fit(train_images, train_labels, epochs=5, batch_size=128)
在 Keras 中这一步是通过调用网络的 fit 方法来完成的——
我们在训练数据上拟合(fit)模型。 直接evaluate出结果
test_loss, test_acc = network.evaluate(test_images, test_labels)
print(‘test_acc:’, test_acc)
test_acc: 0.9785
2.2 神经网络的数据表示
前面例子使用的数据存储在多维 Numpy 数组中,也叫张量(tensor)。
张量(tensor):张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,因此它是数字的容器。
你可能对矩阵很熟悉,它是二维张量。张量是矩阵向任意维度的推广
[注意, 张量的维度(dimension)通常叫作轴(axis)]。
2.2.1 标量(0D 张量)
仅包含一个数字的张量叫作标量(scalar,也叫标量张量、零维张量、0D 张量)
你可以用 ndim 属性来查看一个 Numpy 张量的轴的个数。
标量张量有 0 个轴(ndim == 0)。张量轴的个数也叫作阶(rank)。
x = np.array(12)
2.2.2 向量(1D 张量)
数字组成的数组叫作向量(vector)或一维张量(1D 张量)。一维张量只有一个轴。
x = np.array([12, 3, 6, 14, 7])
这个向量有 5 个元素,所以被称为 5D 向量。
关键!!!:不要把 5D 向量和 5D 张量弄混!
5D 向量只有一个轴,沿着轴有 5 个元素
,
而 5D 张量有 5 个轴(沿着每个轴可能有任意个维度)。
2.2.3 矩阵(2D 张量)
向量组成的数组叫作矩阵(matrix)或二维张量(2D 张量)。矩阵有 2 个轴.行(row),列(column)
x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
2.2.4 3D 张量与更高维张量
将多个矩阵组合成一个新的数组,可以得到一个 3D 张量。
将多个 3D 张量组合成一个数组,可以创建一个 4D 张量,以此类推。深度学习处理的一般
是 0D 到 4D 的张量,但处理视频数据时可能会遇到 5D 张量。
2.2.5 关键属性
张量是由以下三个关键属性来定义的。
‰ 轴的个数(阶)。例如,3D 张量有 3 个轴,矩阵有 2 个轴。
这在 Numpy 等 Python 库中也叫张量的 ndim。
‰ 形状。这是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。例如,前面矩
阵示例的形状为 (3, 5),3D 张量示例的形状为 (3, 3, 5)。
向量的形状只包含一个元素,比如 (5,) 而标量的形状为空,即 ()。
‰ 数据类型(在 Python 库中通常叫作 dtype)。这是张量中所包含数据的类型,例如,张量的类型可以是 float32、uint8、float64 等。
2.2.6 在 Numpy 中操作张量 (可复习之前numpy切片操作)、
2.2.7 数据批量的概念
深度学习中所有数据张量的第一个轴(0 轴,因为索引从 0 开始)都是样本轴
(samples axis,有时也叫样本维度)。在 MNIST 的例子中,样本就是数字图像。
此外,深度学习模型不会同时处理整个数据集,而是将数据拆分成小批量。
具体来看,下面是 MNIST 数据集的一个批量,批量大小为 128。
batch = train_images[:128]
然后是下一个批量。
batch = train_images[128:256]
然后是第 n 个批量。
batch = train_images[128 * n:128 * (n + 1)]
对于这种批量张量,第一个轴(0 轴)叫作批量轴(batch axis)或批量维度
(batch dimension)。
2.2.8 现实世界中的数据张量 (关键)
我们用几个你未来会遇到的示例来具体介绍数据张量。你需要处理的数据几乎总是以下类
别之一。
自我理解:如向量其实是1D张量,但是向量数据是多个向量组成的,所以需要多加一个sample维度,也就是上面说的深度学习所有数据张量的第一个轴都是样本轴
‰ 向量数据:2D 张量,形状为 (samples, features)。
‰ 时间序列数据或序列数据:3D 张量,形状为 (samples, timesteps, features)。
根据惯例,时间轴始终是第 2 个轴(索引为 1 的轴)。
‰ 图像数据:4D张量,形状为 (samples, height, width, channels)
通道在后(channels-last)的约定(在 TensorFlow 中使用)
例如:灰度图片就为1,彩色图片RGB 就为3
‰ 视频:5D 张量,形状为 (samples, frames, height, width, channels)
视频可以看作一系列帧frame,即图片, 每一帧都是一张彩色图像。
每秒 4 帧采样的 60 秒 YouTube 视频片段,视频尺寸为 144×256,这个
视频共有 240 帧。
4 个这样的视频片段组成的批量将保存在形状为 (4, 240, 144, 256, 3)
2.3 神经网络的“齿轮”:张量运算
所有计算机程序最终都可以简化为二进制输入上的一些二进制运算(AND、OR、NOR 等)
深度神经网络学到的所有变换也都可以简化为数值数据张量上的张量运算(tensor operation)
在最开始的例子中,我们通过叠加 Dense 层来构建网络。Keras 层的实例如下所示。
keras.layers.Dense(512, activation=‘relu’)
这个层可以理解为一个函数,输入一个 2D 张量,返回另一个 2D 张量,即输入张量的新表示。
具体而言,这个函数如下所示(其中 W 是一个 2D 张量,b 是一个向量,二者都是该层的属性)。
output = relu(dot(W, input) + b)
我们将上式拆开来看。这里有三个张量运算:输入张量和张量 W 之间的点积运算(dot)、 得到的 2D 张量与向量 b 之间的加法运算(+)、最后的 relu 运算。
relu(x) 是 max(x, 0)。
relu 运算和加法都是逐元素(element-wise)的运算,
在实践中处理 Numpy 数组时,这些运算都是优化好的 Numpy 内置函数
因此,在 Numpy 中可以直接进行下列逐元素运算,速度非常快。
z = x + y 逐元素的相加
z = np.maximum(z, 0.) 逐元素的 relu
2.3.2 广播 (可复习上面numpy知识)
将两个形状不同的张量相加,
较小的张量会被广播(broadcast),以匹配较大张量的形状。广播包含
以下两步。
(1) 向较小的张量添加轴(叫作广播轴),使其 ndim 与较大的张量相同。
(2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
2.3.3 张量点积dot 类似线代的乘法,但有内积
点积运算,也叫张量积(tensor product,不要与逐元素的乘积弄混),
有向量和向量的内积,即:每行对应的元素相乘后,汇总 依然是一维
还有矩阵和向量的内积,也是矩阵的每一行和向量做内积,汇总 变一维
矩阵和矩阵的积就是经典算法
更一般地说,你可以对更高维的张量做点积,只要其形状匹配遵循与前面 2D 张量相同的
原则:
(a, b, c, d) . (d,) -> (a, b, c) (c,d)与(d,)相乘,会变为(c,)
(a, b, c, d) . (d, e) -> (a, b, c, e) 易理解,实际上有 ab 个 (c,d)去和 (d,e)相乘,就变成 ab 个 (c,e)
2.3.4 张量变形 即reshape
第三个重要的张量运算是张量变形(tensor reshaping)。
经常遇到的一种特殊的张量变形是转置(transposition)。对矩阵做转置是指将行和列互换,
x = np.transpose(x) #二维转置简单 ,高维转置结果倒过来即可
x=np.arange(12)
y=x.reshape(2,2,3)
z=np.transpose(y,(0,2,1)) #0,2,1代表的是之前轴位置,z变(2,3,2)
2.3.6 深度学习的几何解释
神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何 变换。因此,你可以将神经网络解释为高维空间中非常复杂的几何变换。
深度学习特别擅长这一点:它将复杂的几何变换逐步分解为一长串基本的几何变换。
2.4 神经网络的“引擎”:基于梯度的优化
output = relu(dot(W, input) + b)
W 和 b 都是张量,均为该层的属性。它们被称为该层的权重(weight)或
可训练参数(trainable parameter),分别对应 kernel (核,中心)和 bias 属性。
一开始,这些权重矩阵取较小的随机值,这一步叫作随机初始化,
W 和 b 都是随机的,relu(dot(W, input) + b) 肯定不会得到任何有用的表示。虽然
得到的表示是没有意义的,但这是一个起点。
下一步则是根据反馈信号逐渐调节这些权重。这个逐渐调节的过程叫作训练,也就是机器学习中的学习。
上述过程发生在一个训练循环(training loop)内,其具体过程如下。必要时一直重复这些步骤。
(1) 抽取训练样本 x 和对应目标 y 组成的数据批量。
(2) 在 x 上运行网络[这一步叫作前向传播(forward pass)],得到预测值 y_pred。
(3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
(4) 更新网络的所有权重,使网络在这批数据上的损失略微下降。
难点在于第四步:更新网络的权重。
也就是使用梯度下降的原因:
如果是根据损失去调节每一个系数,代价巨大
一种更好的方法是利用网络中所有运算都是可微(differentiable)的这一事实,计算损失相对于网络系数的梯度(gradient),然后向梯度的反方向改变系数,从而使损失降低。
2.4.2 张量运算的导数:梯度
对于每个可微函数 f(x)(可微的意思是“可以被求导”。例如,光滑的连续函数可以被求导), 都存在一个导数函数 f’(x),将 x 的值映射为 f 在该点的局部线性近似的斜率。
梯度(gradient)是张量运算的导数。它是导数这一概念向多元函数导数的推广。
假设有一个输入向量 x、一个矩阵 W、一个目标 y 和一个损失函数 loss。你可以用 W 来计算预测值 y_pred,然后计算损失,或者说预测值 y_pred 和目标 y 之间的距离。
y_pred = dot(W, x)
loss_value = loss(y_pred, y)
如果输入数据 x 和 y 保持不变,那么这可以看作将 W 映射到损失值的函数。
理解 输入的xy值不变,那loss_value的值就只和W有关,是不是就相当于有一个W的和loss_value的一个函数f
即loss_value = f(W)
再假设 W 的当前值为 W0。
f 在 W0 点的导数是一个张量 gradient(f)(W0),
其形状与 W 相同,
每个系数 gradient(f)(W0)[i, j] 表示改变 W0[i, j] 时 ,loss_value 变化的方向和大小。
张量 gradient(f)(W0) 是函数 f(W) = loss_value 在 W0 的导数。
单变量函数 f(x) 的导数可以看作函数 f 曲线的斜率。数学知识:导数就是斜率
同样,gradient(f) (W0) 即一个梯度,也可以看作表示 f(W) 在 W0 附近曲率(curvature)的张量。
对于一个函数 f(x),你可以通过将 x 向导数的反方向移动一小步来减小 f(x) 的值。
同样,对于张量的函数 f(W),你也可以通过将 W 向梯度的反方向移动来减小 f(W)。
W1 = W0 - step * gradient(f)(W0),其中 step 是一个很小的比例因子。
二次补充:step也就是后面的学习率。
理解:为什么是向着梯度的反方向移动,记得上面的假设loss_value = f(W)
因为f(w)是loss损失函数,我们的目的就是减少损失。
2.4.3 随机梯度下降
给定一个可微函数,理论上可以用解析法找到它的最小值:
函数的最小值是导数为 0 的点,因此你只需找到所有导数为 0 的点,然后计算函数在其中哪个点具有最小值。
将这一方法应用于神经网络,就是用解析法求出最小损失函数对应的所有权重值。
可以通过对方程 gradient(f)(W) = 0 求解 W
但对于实际的神经网络是无法求解的,因为参数的个数不会少于几千个,而且经常有上千万个。
所以实际上就是使用上节的四步方法,基于当前在随机数据批量上的损失,一点一点地对参数进行调节。
不直接求解,慢慢靠近正答
由于处理的是一个可微函数,你可以计算出它的梯度,从而有效地实现第四步。沿着梯度的反方向更新权重,损失每次都会变小一点。
(1) 抽取训练样本 x 和对应目标 y 组成的数据批量。
(2) 在 x 上运行网络,得到预测值 y_pred。
(3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
(4) 计算损失相对于网络参数的梯度[一次反向传播(backward pass)].
loss_value=f(W) W即权重矩阵,也就是参数
(5) 将参数沿着梯度的反方向移动一点,比如 W -= step * gradient,从而使这批数据上的损失减小一点。 step也叫做learning rate学习率
此方法叫作小批量随机梯度下降(mini-batch stochastic gradient descent,又称为小批量 SGD)。术语随机(stochastic)=random。
理解:就像是一个倒置的钟,你在钟的某个表面上,想要最快到达钟的顶端(也就是底部),就需要找到斜率最大的地方下去,通常梯度就是斜率最大的地方。
SGD 还有多种变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新, 而不是仅仅考虑当前梯度值。
比如带动量的 SGD、Adagrad、RMSProp(之后一般都是用它) (视频Adam)等变体。
这些变体被称为优化方法(optimization method)或优化器(optimizer)。
其中动量的概念尤其值得关注。
动量解决了 SGD 的两个问题:收敛速度和局部极小点。
理解:就是在只考虑梯度的情况下,小批量SGD可能因为达到局部极小点,无论左移还是右移都会导致loss增大,导致无法找到全局最小点。
而加上动量,动量方法的物理学实现过程是每一步都移动小球,不仅要考虑当前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)。
这在实践中的是指
更新参数W不仅要考虑当前的梯度值,还要考虑上一次的参数更新。
derivative:导数,微分
2.4.4 链式求导:反向传播算法
网络 f 包含 3 个张量运算 a、b 和 c,还有 3 个权重矩阵 W1、W2 和 W3。
f(W1, W2, W3) = a(W1, b(W2, c(W3)))
根据微积分的知识,这种函数链可以利用下面这个恒等式进行求导,
它称为链式法则(chain rule):(f(g(x)))’ = f’(g(x)) * g’(x)。
将链式法则应用于神经网络梯度值的计算,得到的算法叫作反向传播(backpropagation)
人们将使用能够进行符号微分(symbolic differentiation)的现代框架来实现神经网络,比如 TensorFlow。
小结
学习是指找到一组模型参数,使得在给定的训练数据样本和对应目标值上的损失函数最小化。
学习的过程:随机选取包含数据样本及其目标值的批量,并计算批量损失相对于网络参数的梯度。随后将网络参数沿着梯度的反方向稍稍移动(移动距离由学习率指定)。
整个学习过程之所以能够实现,是因为神经网络是一系列可微分的张量运算,因此可以利用求导的链式法则来得到梯度函数,这个函数将当前参数和当前数据批量映射为一个梯度值。
损失是在训练过程中需要最小化的量,因此,它应该能够衡量当前任务是否已成功解决。 学习权重张量的反馈信号,在训练阶段应使它最小化
优化器是使用损失梯度更新参数的具体方式,比如 RMSProp 优化器、带动量的随机梯度下降(SGD)等
3神经网络入门
3.1 神经网络剖析
前面几章介绍过,训练神经网络主要围绕以下四个方面。
‰ 层,多个层组合成网络(或模型)。
‰ 输入数据和相应的目标。
‰ 损失函数,即用于学习的反馈信号。
‰ 优化器,决定学习过程如何进行。
3.1.1 层:深度学习的基础组件
不同的张量格式与不同的数据处理类型需要用到不同的层。
例如,简单的向量数据保存在 形状为 (samples, features) 的 2D 张量中,通常用密集连接层[densely connected layer,也叫全连接层(fully connected layer)或密集层(dense layer),对应于 Keras 的 Dense 类]来处理。
关键:注意,这里是向量数据,所以说是2D张量,单纯向量就是1D张量
所以记住 全连接处理1维数据,下面学的Conv2D处理2D张量,就是矩阵
序列数据保存在形状为 (samples, timesteps, features) 的 3D 张量中,
通常用循环层(recurrent layer,比如 Keras 的 LSTM 层)来处理。
图像数据保存在 4D 张量中,通常用二维卷积层(Keras 的 Conv2D)来处理。
关键:层兼容性(layer compatibility)具体指的是每一层只接受特定形状的输入张量,并返回特定形状的输出张量。
但使用 Keras 时,你无须担心兼容性,因为向模型中添加的层都会自动匹配输入层的形状
3.1.2 模型:层构成的网络
深度学习模型是层构成的有向无环图。最常见的例子就是层的线性堆叠,将
单一输入映射为单一输出。
但随着深入学习,你会接触到更多类型的网络拓扑结构。常见的网络拓扑结构如下。
‰ 双分支(two-branch)网络
‰ 多头(multihead)网络
‰ Inception 模块
3.1.3 损失函数与优化器:配置学习过程的关键
确定了网络架构,你还需要选择以下两个参数。
‰ 损失函数(目标函数)——在训练过程中需要将其最小化。它能够衡量当前任务是否已
成功完成。
‰ 优化器——决定如何基于损失函数对网络进行更新。它执行的是随机梯度下降(SGD)
的某个变体。
具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。
但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失
函数取平均,变为一个标量值。
选择正确的目标函数对解决问题是非常重要的。
对于二分类问题,你可以使用二元交叉熵(binary crossentropy)损失函数;
对于多分类问题,可以用分类交叉熵(categorical crossentropy)损失函数;
对于回归问题,可以用均方误差(mean-squared error)损失函数;
对于序列学习问题,可以用联结主义时序分类(CTC,connectionist temporal classification)损失函数
3.2 Keras 简介
Keras 具有以下重要特性。
‰ 相同的代码可以在 CPU 或 GPU 上无缝切换运行。
‰ 具有用户友好的 API,便于快速开发深度学习模型的原型。
‰ 内置支持卷积网络(用于计算机视觉)、循环网络(用于序列处理)以及二者的任意
组合。
‰ 支持任意网络架构:多输入或多输出模型、层共享、模型共享等。这也就是说,Keras
能够构建任意深度学习模型,无论是生成式对抗网络还是神经图灵机。
keras依赖于一个专门的、高度优化的张量库来完成张量操作、求微分等低层次的运算,这个张量库就是 Keras 的后端引擎(backend engine):
即TensorFlow 后端、 Theano 后端和微软认知工具包(CNTK)
3.2.2 Keras开发概述 第一次函数式API
典型的 Keras 工作流程就和例子类似。
(1) 定义训练数据:输入张量和目标张量。
(2) 定义层组成的网络(或模型),将输入映射到目标。
(3) 配置学习过程:选择损失函数、优化器和需要监控的指标。 compile
(4) 调用模型的 fit 方法在训练数据上进行迭代。
3.1.2曾经提过:
定义模型有两种方法:一种是使用 Sequential 类(仅用于层的线性堆叠,这是目前最常见的网络架构),
另一种是函数式 API(functional API,用于层组成的有向无环图(即层与层之间有方向,但不会成环),让你可以构建任意形式的架构)。所以更加有弹性。
model = models.Sequential()
model.add(layers.Dense(32, activation=‘relu’, input_shape=(784,)))
model.add(layers.Dense(10, activation=‘softmax’))
下面是用函数式 API 定义的相同模型。 注:第七章重点学习
input_tensor = layers.Input(shape=(784,))
x = layers.Dense(32, activation=‘relu’)(input_tensor)
output_tensor = layers.Dense(10, activation=‘softmax’)(x)
model = models.Model(inputs=input_tensor, outputs=output_tensor)
3.4 电影评论IMDB分类:二分类问题 正负
3.4.1 IMDB 数据集
本节使用 IMDB 数据集,它包含来自互联网电影数据库(IMDB)的 50 000 条严重两极分化的评论。
数据集被分为用于训练的 25 000 条评论与用于测试的 25000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论。
补充:为什么要将训练集和测试集分开?
因为你不应该将训练机器学习模型的同一批数据再用于测试模型!
你的模型最终可能只是记住了训练样本和目标值之间的映射关系,但这对在前所未见的数据上进行预测毫无用处。
与MNIST 数据集一样,IMDB 数据集也内置于 Keras 库。
补充:它已经过预处理: 补充:之后需要我们自己预处理数据在6章.1节
评论(单词序列)已经被转换为整数序列,其中每个整数代表字典中的某个单词。
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
参数 num_words=10000 的意思是仅保留训练数据中前 10000 个最常出现的单词。0-9999 没有数字10000
低频单词将被舍弃。
train_data是25000条评论,将每条评论每个字都变为了整数,形成了一个array
train_data[0]
[1, 14, 22, 16, … 178, 32]
train_labels 则负责每条评论的正面或者负面
train_labels[0]
1
使用 ?imdb.load_data 查看函数参数
其中 start_char=1 代表数据中每篇文章的开始start of sequence
oov_char=2 因为只保留10000字,若有不在这10000字中的字,以2表示,其实0也不用
train_labels 和 test_labels 都是 0 和 1 组成的列表,其中 0 代表负面(negative),1 代表正面(positive)。
由于限定为前 10 000 个最常见的单词,单词索引都不会超过 10 000。
max([max(sequence) for sequence in train_data])
#先从for看,sequence就相当于一个i,从0开始,从train_data中取值,通过max获得一个最大值,在循环结束后,从所有最大值中再次找最大值。
将某条评论迅速解码为英文单词
word_index = imdb.get_word_index()
#word_index 是一个将单词映射为整数索引的字典,key是单词,value是整数
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
#通过for循环,其中 .item()能够同时获得字典的key,和value,再反向输入进reverse
decoded_review = ’ '.join([reverse_word_index.get(i - 3, ‘?’) for i in train_data[0]])
#将评论解码。
注意,索引减去了 3,因为 0、1、2 是为“padding”(填充),start of sequence序列开始)、(“unknown未知词)分别保留的索引 即上面的黄字部分
验证方法:在word__value中查看‘this’代表的value是11,但在train_data中是14
i-3后的“?”填补进未知单词的位置。因为还有超过10000常用单词的存在
3.4.2 整数变张量 embedding,one-hot
关键:不能将整数序列直接输入神经网络。
你需要将列表转换为张量(永远记住神经网络是张量运算)。
转换方法有以下两种。
1.填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices) 的整数张量,
然后网络第一层使用能处理这种整数张量的层(即 Embedding 层)。
(indice意思类似index)
理解:就是将25000条评论的列表变成某个相同长度,超过的部分就删除,不足的在前补
2.对列表进行 one-hot 编码,将其转换为由 0 和 1 组成的多维向量。
举个例子,在imdb中,序列 [3, 5] 将会 被转换为 10000 维向量,
只有索引为 3 和 5 的元素是 1,其余元素都是 0。
然后网络第一层可以用 Dense 层,它能够处理浮点数向量数据。
我们这里就是使用第二种
import numpy as np
def vectorize_sequences(sequences, dimension=10000): #定义函数
results = np.zeros((len(sequences), dimension)) # sequences值即为即将输入的train_data,创造一个25000*10000的0 array
for i, sequence in enumerate(sequences): #enumerate创造索引,组合数据
results[i, sequence] = 1.
return results
x_train = vectorize_sequences(train_data) # 将训练数据向量化
x_test = vectorize_sequences(test_data) # 将测试数据向量化
#将标签向量化,标签之前也是list,要变为numpy的张量,dense能处理浮点向量
y_train = np.asarray(train_labels).astype(‘float32’) #asarray就是将list,tuple变为ndarray
y_test = np.asarray(test_labels).astype(‘float32’)
现在可以将数据输入到神经网络中。
enumerate学习:
seq = [‘one’, ‘two’, ‘three’]
#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
for i, element in enumerate(seq):
… print i, element
0 one
1 two
2 three
3.4.3 构建网络 relu sigmoid激活函数(非线性)
from keras import models
from keras import layers
#补充:写代码是要注意包的使用
model.add(layers.Dense(16, activation=‘relu’, input_shape=(10000,)))
model.add(layers.Dense(16, activation=‘relu’))
model.add(layers.Dense(1, activation=‘sigmoid’))
传入 Dense 层的参数16是该层隐藏单元的个数。
一个隐藏单元(hidden unit)是该层表示空间的一个维度。
我们在第 2 章讲过,每个带有 relu 激活的 Dense 层都实现了下列张量运算:
output = relu(dot(W, input) + b) #理解为每个神经元都需要经过这种运算
16 个隐藏单元对应的权重矩阵 W 的形状为 (input_dimension, 16),与 W 做点积相当于 将输入数据投影到 16 维表示空间中
(然后再加上偏置向量 b(bias截距项)并应用 relu 运算)。
理解:这里应该就是解释为什么计算参数时是输入维度先加1,再去乘以神经元数了
你可以将表示空间的维度直观地理解为“网络学习内部表示时所拥有的自由度”。
隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的计算代价也变得更大,而且可能会导致学到不好的模式,即可能直接背下了所有的测试数据。
对于这种 Dense 层的堆叠,你需要确定以下两个关键架构:
‰ 网络有多少层;
‰ 每层有多少个隐藏单元。
第 4 章中的原则将会指导你对上述问题做出选择。
现在你只需要相信我选择的下列架构:
两个中间层,每层都有 16 个隐藏单元;
第三层输出一个标量0D,即0,1,预测当前评论的情感。
中间层使用 relu 作为激活函数,最后一层使用 sigmoid 激活以输出一个 0~1 范围内的概率值(表示样本的目标值等于 1 的可能性,即评论为正面的可能性)。
其中激活函数relu(rectified linear unit,整流线性单元)函数将所有负值归零(见图 3-4),
补充:rectify:整流,纠正 relu 读作 riliu,或者reliu?
而 sigmoid 函数则将任意值“压缩”到 [0, 1] 区间内(见图 3-5),
其输出值可以看作概率值。
什么是激活函数?为什么要使用激活函数?
如果没有 relu 等激活函数(也叫非线性),
Dense 层将只包含两个线性运算——点积和加法: output = dot(W, input) + b
这样 Dense 层就只能学习输入数据的线性变换,多个线性层堆叠实现的仍是线性运算,添加层数并不会扩展假设空间。
为了得到更丰富的假设空间,从而充分利用多层表示的优势,你需要添加非线性或激活函数。
relu 是深度学习中最常用的激活函数,还有许多其他函数可选,它们都有类似
的奇怪名称比如 prelu、elu 等
最后,你需要选择损失函数和优化器。
面对的是一个二分类问题,网络输出是一个概率值,网络最后一层使用 sigmoid 激活函数,仅包含一个单元,
补充:二分类问题都是这样处理,最后一层必须是1个单元使用 sigmoid
那么最好使用 binary_ crossentropy(二元交叉熵)损失函数。
对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。
交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。
代码清单 3-4 编译模型 补充metrics指标
model.compile( optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘accuracy’])
上述代码将优化器、损失函数和指标作为字符串传入,这是因为 rmsprop、binary_
crossentropy 和 accuracy 都是 Keras 内置的一部分。
如果想配置自定义优化器的参数,或者传入自定义的损失函数或指标函数。
前者可通过向 optimizer 参数传入一个优化器类实例来实现,
补充:lr 应该不行了,要变成learning-rate
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=‘binary_crossentropy’,
metrics=[‘accuracy’])
后者可通过向 loss 和 metrics 参数传入函数对象来实现,
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=losses.binary_crossentropy,
metrics=[metrics.binary_accuracy])
3.4.4 割训练,先验证,再测试
从训练资料中割出 验证资料,绝对不能让神经网络先接触到测试资料
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
需要监控在留出的 10000 个样本上的损失和精度。
可以通过将验证数据传入 validation_data 参数来完成.
现在使用 512 个样本组成的小批量,将模型训练 20 个轮次
每轮结束时会有短暂的停顿,因为模型要计算在验证集的10 000 个样本上的损失和精度。
注意,调用 model.fit() 返回了一个 history 对象。
这个对象有一个成员 history,它是一个字典,包含训练过程中的所有数据。
我们来看一下。
关键:dict_keys([‘val_acc’, ‘acc’, ‘val_loss’, ‘loss’]) 记住这几个key,后面狂用
history_dict = history.history
history_dict.keys()
dict_keys([‘val_acc’, ‘acc’, ‘val_loss’, ‘loss’])
补充:在jupyternotebook可以分段进行,所以容易忘记之前的代码和导入的包,平时需要注意
代码清单 3-11 从头开始重新训练一个模型
import keras
#keras.version
from keras import models
from keras import layers
from keras.datasets import imdb
import numpy as np
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
#向量化
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # set specific indices of results[i] to 1s
return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
y_train = np.asarray(train_labels).astype(‘float32’)
y_test = np.asarray(test_labels).astype(‘float32’)
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
model = models.Sequential()
model.add(layers.Dense(16, activation=‘relu’, input_shape=(10000,)))
model.add(layers.Dense(16, activation=‘relu’))
model.add(layers.Dense(1, activation=‘sigmoid’))
model.compile(optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘accuracy’])
model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)
补充:不需要画图,直接得结果,可以不使用history,直接用evaluate(test…)
结果:
782/782 [==============================] - 4s 4ms/step - loss: 0.2973 - accuracy: 0.8832
训练好网络之后,你希望将其用于实践。你可以用 predict 方法来得到评论为正面的可能性大小。
直接predict是获得最后神经元的值
model.predict(x_test)
===array([[0.23766142], …,
[0.16107902],
[0.10667685],
[0.73618495]], dtype=float32)
3.4.7 小结
下面是你应该从这个例子中学到的要点。
1.通常需要对原始数据进行大量预处理,以便将其转换为张量输入到神经网络中。
单词序列可以编码为二进制向量,但也有其他编码方式。
2.带有 relu 激活的 Dense 层堆叠,可以解决很多种问题(包括情感分类),你可能会经常用到这种模型。
3.对于二分类问题(两个输出类别),网络的最后一层应该是只有一个单元并使用 sigmoid 激活的 Dense 层,网络输出应该是 0~1 范围内的标量,表示概率值。
4.对于二分类问题的 sigmoid 标量输出,你应该使用 binary_crossentropy 损失函数。 无论你的问题是什么,rmsprop 优化器通常都是足够好的选择。这一点你无须担心。
5.随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果。一定要一直监控模型在训练集之外的数据上的性能。
3.5 新闻分类:多分类问题 路透社
本节你会构建一个网络,将路透社新闻划分为 46 个互斥的主题。因为有多个类别,所以这是多分类(multiclass classification)问题的一个例子。
因为每个数据点只能划分到一个类别, 所以更具体地说,这是单标签、多分类(single-label, multiclass classification)问题的一个例子。
如果每个数据点可以划分到多个类别(主题),那它就是一个多标签、多分类(multilabel, multiclass classification)问题
单标签、多分类:待预测的label标签只有一个,但是label标签的取值可能有多种情况。直白来讲就是每个实例的可能类别有K种(t1,t2,…,tk,k≥3)常见算法:Softmax、KNN等。
补充:分不清看这里
多分类及多标签分类算法 - 码迷-wjz - 博客园 (cnblogs.com)
3.5.1 路透社数据集
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)
#相似的,IMDB数据集使用的是 imdb.load_data,作用类似看3.4.1
与 IMDB 评论一样,每个样本都是一个整数列表
(表示单词索引,就是一个整数代表一个单词)。说明已经过了预处理。
参数 num_words=10000 将数据限定为前 10 000 个最常出现的单词。
len(train_data)
8982
len(test_data)
2246
train_data[10]
[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,
3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]
关键:理解46 个互斥的主题,也就是
样本对应的标签的值是0~45 范围内的整数,即话题索引编号。
整个标签array包含了8982条新闻的类别记录
train_labels
array([ 3, 4, 3, …, 25, 3, 25], dtype=int64)
train_labels[8981]
25
3.5.2 准备数据
可以使用与上一个例子相同的代码将数据向量化。
补充:注意这里是数据使用了上文的代码
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
将标签向量化有两种方法:
你可以将标签列表转换为整数张量
(下文是使用ndarray直接转化然后再配合sparse交叉熵==等价于使用onehot编码,使用二元交叉熵,并不是数据向量化的embedding),
或者使用 one-hot 编码 注意上面方法就是one-hot。
理解:预处理过的整数列表数据,标签,进行向量化其实就是相同的
one-hot 编码是分类数据广泛使用的一种格式,也叫分类编码(categorical encoding)。
理解:其实就是和数据向量化相似,就是对train_label做同样的事,先根据大小创建一个全是0的array,再根据lable对应的索引,赋值为1
len(train_labels)
8982
def to_one_hot(labels, dimension=46):
results = np.zeros((len(labels), dimension))
for i, label in enumerate(labels):
results[i, label] = 1
#通过枚举出的i,和labels中的 与i对应的label,将0变为1
return results
one_hot_train_labels = to_one_hot(train_labels)
one_hot_test_labels = to_one_hot(test_labels)
#下图即为 (i,label)的前两个值,和result的前两个值
关键:注意,因为多分类都需要这样的操作,Keras 内置方法可以实现这个操作,你在 MNIST 例子中已经见过这种方法。
to_categorical 工具 - Keras 中文文档
将类向量(整数)转换为二进制类矩阵。
例如,用于 categorical_crossentropy。
参数
y: 需要转换成矩阵的类矢量 (从 0 到 num_classes 的整数)。
num_classes: 总类别数。
dtype: 字符串,输入所期望的数据类型 (float32, float64, int32…)
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
3.5.3 构建网络
主题分类问题与前面的电影评论分类问题类似,但输出类别的数量从 2 个变为 46 个。输出空间的维度要大得多。
一直用的是 Dense 层的堆叠,每层只能访问上一层输出的信息。如果某一层丢失了与分类问题相关的一些信息,那么这些信息无法被后面的层找回,出于这个原因,下面将使用维度更大的层,包含 64 个单元。
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(64, activation=‘relu’, input_shape=(10000,)))
model.add(layers.Dense(64, activation=‘relu’))
model.add(layers.Dense(46, activation=‘softmax’))
1.网络的最后一层是大小为 46 的 Dense 层。这意味着,对于每个输入样本,网络都会输出一个 46 维向量。这个向量的每个元素(即每个维度)代表不同的输出类别。
2.最后一层使用了 softmax 激活。在 MNIST 例子中见过这种用法。
网络将输出在 46 个不同输出类别上的概率分布——对于每一个输入样本,网络都会输出一个 46 维向量,其中 output[i] 是样本属于第 i 个类别的概率。46 个概率的总和为 1。
最好的损失函数是 categorical_crossentropy(分类交叉熵)
(上一次使用的是binary_ crossentropy(二元交叉熵))
它用于衡量两个概率分布之间的距离,这里两个概率分布分别是网络输出的概率分布和标签的真实分布。通过将这两个分布的距离最小化,训练网络可使输出结果尽可能接近真实标签。
理解:也就是将预期分布,与真实分步之间的距离即损失变的越来越小,说明结果也越来越接近真实。
代码清单 3-16 编译模型
model.compile(optimizer=‘rmsprop’,
loss=‘categorical_crossentropy’,
metrics=[‘accuracy’])
3.5.4 验证你的方法
在训练数据中留出 1000 个样本作为验证集。
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]
#到这里,每次切割验证集,都是从前开始切 注意,这里的one_hot_train_labels是之前已经进行过编码的,不是再编码一次。
根据讲解,在切割之前,就是需要先进行one_hot编码,
如果先割再编码,验证集和训练集有可能不会满足46类的要求,导致编码后,大小不同
history = model.fit( partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
根据结果,绘制出相应的训练损失,验证损失
import matplotlib.pyplot as plt
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘r’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.xlabel(‘Epochs’)
plt.ylabel(‘Loss’)
plt.legend()
plt.show()
绘制出相应的训练精度,验证精度
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’] #其他同上
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, ‘b’, label=‘Validation acc’)
根据结果,或者图片可以发现在第9轮左右开始过拟合,
重新训练模型(正常是跑所有的训练数据),调整epoch值为9,输入测试数据。
results = model.evaluate(x_test, one_hot_test_labels)
Epoch 9/9
16/16 [==============================] - 0s 12ms/step - loss: 0.2983 - accuracy: 0.9344 - val_loss: 0.8927 - val_accuracy: 0.8180
这种方法可以得到约 80% 的精度。
完全随机可能是19%,二分类完全随机自然就是50%左右
import copy
test_labels_copy = copy.copy(test_labels)
np.random.shuffle(test_labels_copy)
float(np.sum(np.array(test_labels) == np.array(test_labels_copy))) / len(test_labels)
测试方法,
补充:混淆矩阵Confusion Matrix
机器学习算法结果评估指标_f1-score公式_lili要努力的博客-CSDN博客
p:positive N:negative T ture F false
(F1-score)是分类问题的一个衡量指标。一些多分类问题的机器学习竞赛,常常将
F1-score作为最终测评的方法。它是精确率和召回率的调和平均数
即下图的 f1-score求法
TP :预测Positive, 真值Positive FN :预测Negative,真值Positive
FP :预测Positive, 真值Negative TN :预测Negative,真值Negative
Precision 精确率
定义:精确率是针对我们预测结果而言的,它表示的是预测为正的样本中有多少是真正的正样本。那么预测为正就有两种可能了,一种就是把正类预测为正类(TP),另一种就是把负类预测为正类(FP)。
公式:Precision = TP / (TP + FP)
Recall 召回率
定义:召回率是针对我们原来的样本而言的,它表示的是样本中的正例有多少被预测正确了。那也有两种可能,一种是把原来的正类预测成正类(TP),另一种就是把原来的正类预测为负类(FN)。
公式:Recall = TP / (TP + FN)
F1-Score
定义:F1-score基于召回率和精确率计算的;
公式:F1-score = 2 * Precision * Recall / ( Precision + Recall )
与model.predict不同的是 :model.predict_classes是获得输入值最终会被分为某一类的值
通过函数 pd.crosstab 先写真实的test_labels
坐标轴0,写标准答案,也就是test,axis=1写预测
3.5.5 在新数据上生成预测结果
predictions = model.predict(x_test)
predictions[0].shape
(46,)
这个向量的所有元素总和为 1。
np.sum(predictions[0])
1.0
最大的元素就是预测类别,即概率最大的类别。
np.argmax(predictions[0])
4
3.5.6 处理标签和损失的另一种方法
前面提到了另一种编码标签的方法,就是将其转换为整数张量,如下所示。
y_train = np.array(train_labels)
y_test = np.array(test_labels)
对于这种编码方法,唯一需要改变的是损失函数的选择。之前使用的损失函数
categorical_crossentropy,标签应该遵循分类编码。
对于整数标签,你应该使用 sparse_categorical_crossentropy。
在7.1.1中,可以使用,意思就是这个整数标签并没有经过one-hot编码
这个新的损失函数在数学上与 categorical_crossentropy 完全相同,二者只是接口不同
3.5.7 中间层维度足够大的重要性
最终输出是 46 维的,因此中间层的隐藏单元个数不应该比 46 小太多
如果中间层的维度远远小于 46(比如 4 维),就会导致结果精度下降
主要原因:
将大量信息(这些信息足够恢复 46 个类别的分割超平面)压缩到维度很小的中间空间。网络能够将大部分必要信息塞入这个四维表示中,但并不是全部信息。
补充:这种情况也可以用来专门降维,就是将中间维度变的很小,去芜存菁,AE(见第七章),PCA
3.5.8 进一步的实验
‰ :尝试使用更多或更少的隐藏单元,比如 32 个、128 个等。
32 71/71 [] - 0s 4ms/step - loss: 1.0538 - accuracy: 0.7703
128 71/71 [] - 0s 4ms/step - loss: 1.0363 - accuracy: 0.7961
‰ 前面使用了两个隐藏层,现在尝试使用一个或三个隐藏层。
三层71/71 [] - 0s 4ms/step - loss: 1.1637 - accuracy: 0.7658
一层71/71 [] - 0s 4ms/step - loss: 0.9119 - accuracy: 0.7907
根据实验,这样调节并不会造成很大的影响,
3.5.9 小结 (wrapping up)
下面是你应该从这个例子中学到的要点。
如果要对 N 个类别的数据点进行分类,网络的最后一层应该是大小为 N 的 Dense 层。
对于单标签、多分类问题,网络的最后一层应该使用 softmax 激活,这样可以输出在 N 个输出类别上的概率分布。
单标签、多分类这种问题的损失函数几乎总是应该使用分类交叉熵。它将网络输出的概率分布与目标的 真实分布之间的距离最小化。
处理多分类问题的标签有两种方法。 见上3.5.6
通过分类编码(也叫 one-hot 编码)对标签进行编码,然后使用 categorical_crossentropy 作为损失函数。
将标签编码为整数,然后使用 sparse_categorical_crossentropy 损失函数。
见上3.5.7 如果你需要将数据划分到许多类别中,应该避免使用太小的中间层,以免在网络中造成信息瓶颈。
3.6 预测房价:回归问题 (k-fold validation)
前面两个例子都是分类问题,其目标是预测输入数据点所对应的单一离散的标签。
另一种常见的机器学习问题是回归问题,
它预测一个连续值而不是离散的标签,例如,根据气象数据预测明天的气温,或者根据软件说明书预测完成软件项目所需要的时间。
1
注意 不要将回归问题与 logistic 回归算法混为一谈。令人困惑的是,
logistic 回归(逻辑回归)不是回归算法, 而是分类算法
3.6.1 波士顿房价数据集
本节将要预测 20 世纪 70 年代中期波士顿郊区房屋价格的中位数,已知当时郊区的一些数据点,比如犯罪率、当地房产税率等。
本节用到的数据集与前面两个例子有一个有趣的区别。
它包含的数据点相对较少,只有 506 个,分为 404 个训练样本和 102 个测试样本。
输入数据的每个特征(比如犯罪率)都有不同的取值范围。注:13个特征
例如,有些特性是比例,取值范围为 0~1;有的取值范围为 1~12;还有的取值范围为 0~100,等等。
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
#返回的是Returns 由自己命名
Tuple of Numpy arrays: `(x_train, y_train), (x_test, y_test)
print(train_data[4])
[ 3.69311 0. 18.1 0. 0.713 6.376 88.4
2.5671 24. 666. 20.2 391.43 14.65 ]
print(train_data.shape)
(404, 13)
test_data.shape
(102, 13)
有 404 个训练样本和 102 个测试样本,每个样本都有 13 个数值特征,比如人均犯罪率,每个住宅的平均房间数、高速公路可达性等,都是影响房价的。
目标即targets就是这404个房屋价格的中位数,单位是千美元。
print(train_targets)
array([ 15.2, 42.3, 50. … 19.4, 19.4, 29.1])
print(train_targets.shape)
(404,)
3.6.2 对于范围差距大的数据,做标准化
将取值范围差异很大的数据输入到神经网络中,这是有问题的。
网络可能会自动适应这种取值范围不同的数据,但学习肯定变得更加困难。
在不同的问题中,标准化的意义不同:
(1)在回归预测中,标准化是为了让特征值有均等的权重;
(2)在训练神经网络的过程中,通过将数据标准化,能够加速权重参数的收敛;
关键知识点:对于这种数据,普遍采用的最佳实践是对每个特征做标准化,
即对于输入数据的每个特征(也就是输入数据矩阵中的每一列),
减去特征平均值,再除以标准差,
这是数据标准化最常用的方法,即Z-score标准化,也叫标准差标准化法。
理解:根据之前学到的,神经网络喜欢0-1的数据,所以这里就是
每个值减去对应列特征值的平均数,这样每列的平均数就变为0,
再除以标准差.
新数据=(原数据- 均值)/ 标准差
均值和标准差都是在样本集上定义的,而不是在单个样本上定义的。标准化是针对某个属性的,需要用到所有样本在该属性上的值
这样得到的特征平均值为 0,标准差为 1。用 Numpy 可以很容易实现标准化。
mean = train_data.mean(axis=0) #求mean 注意这里axis=0是代表所有列
train_data -= mean #减去特征平均值
std = train_data.std(axis=0) #求标准差
train_data /= std #除标准差
test_data -= mean #注意 只能使用训练的数据
test_data /= std
注意,用于测试数据标准化的均值和标准差都是在训练数据上计算得到的。4
关键:在在工作流程中,你不能使用在测试数据上计算得到的任何结果,
即使是像数据标准化这么简单的事情也不行。
在6.3.1中也有提及,也就是说,测试数据是不能去标准化的,只有训练数据
3.6.3 构建网络
由于样本数量很少,我们将使用一个非常小的网络,其中包含两个隐藏层,每层有 64 个单元。
一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。
大网络就会将数据背下,导致过拟合
from keras import models
from keras import layers
def build_model(): # 因为需要将同一个模型多次实例化,
所以用一个函数来构建模型
model = models.Sequential()
model.add(layers.Dense(64, activation=‘relu’,
input_shape=(train_data.shape[1],)))
# train_data.shape 是(404,13),[1]就变为(13,)
(13, )就是一个 一维array[]
model.add(layers.Dense(64, activation='relu'))
#知识点:最后一层没有激活函数,是回归问题的典型设置
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model
网络的最后一层只有一个单元,没有激活函数,是一个线性层。
网络可以学会预测任意范围的值
这是标量回归(标量回归是预测单一连续值的回归)的典型设置。
添加激活函数将会限制输出范围。例如,如果向最后一层
添加 sigmoid 激活函数,网络只能学会预测 0~1 范围内的值
知识点:编译网络用的是 mse 损失函数,即均方误差MSE,mean squared error),预测值与目标值之差的平方。
这是回归问题常用的损失函数。
在训练过程中还监控一个新指标:平均绝对误差MAE,mean absolute error。
它是预测值与目标值之差的绝对值。
比如,如果这个问题的 MAE 等于 0.5,就表示你预测的房价与实际价
格平均相差 500 美元(单位:千美元)。
3.6.4 适用数据量少-K 折验证K-fold validation
为了在调节网络参数(比如训练的轮数epoch)的同时对网络进行评估,你可以将数据划分为训练集和验证集,但由于数据点很少,验证集会非常小(比如大约100 个样本)。
因此,验证分数可能会有很大波动,造成验证分数上有很大的方差,这取决于你所选择的验证集和训练集,这样就无法对模型进行可靠的评估。
在这种情况下,最佳做法是使用 K 折交叉验证(见图 3-11)。
这种方法将可用数据划分为 K 个分区(K 通常取 4 或 5) ,
实例化 K 个相同的模型(这就是之前将模型实例化写成函数的原因),
将每个模型在 K-1 个分区上训练,并在剩下的一个分区上进行test。
模型的验证分数等于 K 个验证分数的平均值。
补充:划分数据一般是横切,而不是竖切,具体见下
不要被这里图像误导,一般都是横切,比如这里40413,切分为多个10013
所以后面级联也是使用默认的axis=0,上下级联
import numpy as np
k = 4
num_val_samples = len(train_data) // k #整除切分,得一块长度
num_epochs = 100
all_scores = [] #负责保存结果
for i in range(k):
print(‘processing fold #’, i) #提示是第几次
val_data = train_data[i * num_val_samples : (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
#由于可能训练数据被验证数据隔开,使用级联连起来,横切,上下级联axis=0
#注意空格:假设i=0,那么就是0-101作验证,训练就从101-404
partial_train_data = np.concatenate( [train_data[ :i * num_val_samples],
train_data[(i + 1) * num_val_samples: ] ],
axis=0)
partial_train_targets = np.concatenate([train_targets[ :i * num_val_samples],
train_targets[(i + 1) * num_val_samples: ]],
axis=0)
model = build_model()
#训练模型(静默模式, verbose=0, =2显示进度条 意思:啰唆的)
model.fit(partial_train_data, partial_train_targets,
epochs=num_epochs, batch_size=1, verbose=0)
#这里直接fit时没有使用validation_data(可选)而是在下面evaluate
loss和metrics也变了,是mse和mae,所以也不再是acc,val_acc,loss,val_loss
#在验证数据上评估模型,也就是将验证数据当作test,所以获得一个评估值
放入all_score,注意这是一次循环,append一个值
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=2)
#记得之前mae的例子吗,0.5就是500美元,它代表预与实际差值的绝对值,
所以计算4次验证的平均值使用它
all_scores.append(val_mae)
#最后再求平均 这是四次循环后得到四个值再进行,
np.mean(all_scores)
每次运行模型得到的验证分数有很大差异,所以将epoch变为500,做500次,其他不变
注意:与上次的差异,为了记录模型在每轮的表现,使用了history
from keras import backend as K
K.clear_session()
num_epochs = 500
all_mae_histories = []
for i in range(k):
print(‘processing fold #’, i) #提示是第几次
val_data = train_data[i * num_val_samples : (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
partial_train_data = np.concatenate( [train_data[ :i * num_val_samples],
train_data[(i + 1) * num_val_samples: ] ],
axis=0)
partial_train_targets = np.concatenate([train_targets[ :i * num_val_samples],
train_targets[(i + 1) * num_val_samples: ]],
axis=0)
model = build_model()
#通过history来画图
history = model.fit(partial_train_data, partial_train_targets,
validation_data=(val_data, val_targets),
epochs=num_epochs, batch_size=1, verbose=0)
#与之前的差异点:这里不是evaluate直接评估值,
最关键点:因为epochs=500,一次fit一共500轮,所以就会产生500个val_mae,类似之前的val_acc
所以mae_history并不是一个值,而是一个(500,)的list,
因为history是一个dict,之前例子里的key是acc,loss等,
它包含训练过程中的所有数据
#再次补充:2023年此key已经更新,再次记住
#print(history.history.keys())
#dict_keys([‘loss’, ‘mae’, ‘val_loss’, ‘val_mae’])
#原:mae_history = history.history[‘val_mean_absolute_error’]
mae_history = history.history[‘val_mae’]
#经过循环,all_mae_histories则是一个有四个list的list
all_mae_histories.append(mae_history)
#最后再对all_mae 里的四个list的相对应的值进行求平均
#x[i]会获得四个list中每个list的中的第i个值,通过mean求平均,
i的值从0到500,来遍历mas_history里的500个数据,求平均
average_mae_history = [
np.mean( [x[i] for x in all_mae_histories] ) for i in range(num_epochs)
]
#最后可以通过np.argmin()查看mae最小值在哪个位置
通过matplotlib可查看最终图像
import matplotlib.pyplot as plt
#注意看x轴,1-500,
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel(‘Epochs’)
plt.ylabel(‘Validation MAE’)
plt.show()
因为纵轴的范围较大,且数据方差相对较大,所以难以看清这张图的规律。
我们来重新绘制一张图。
1删除前 10 个数据点,因为它们的取值范围与曲线上的其他点不同。
2将每个数据点替换为前面数据点的指数移动平均值,以得到光滑的曲线。
由视频讲解:
此方法叫做EMA(exponential Moving Average)-指数移动平均 比如有 a,b,c ,现在对它们重新赋值,
第一个a不变,b=0.9a+0.1b(转换前),c=0.9*b(转换后)+0.1c (转换前)
def smooth_curve(points, factor=0.9): #factor即要乘0.9的数
smoothed_points = [] #定义空容器
for point in points: #points里输入的是ave_mae_his,point是list中的值
if smoothed_points: #容器如果非空
previous = smoothed_points[-1] #将容器最后一个值定义为 前值
#新加入的值就是上面的公式,前值0.9+原值01,写入容器
smoothed_points.append(previous * factor + point * (1 - factor))
else: #容器为空时,就是把第一个值直接放入
smoothed_points.append(point)
return smoothed_points
smooth_mae_history = smooth_curve(average_mae_history[10: ]) #切去前10个
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel(‘Epochs’)
plt.ylabel(‘Validation MAE’)
plt.show()
通过np.argmin得到最低位置,记得加10,因为就上面减了10,就是epoch的最佳值.
再进行最后的train,也就是对test。
知识点:最终预测test数据时用所有的train_data,而不是k折
model = build_model()
model.fit(train_data, train_targets,
epochs=80, batch_size=16, verbose=2) #通过之前的值调整epoch
关键理解:此时的模型算是已经设置好了,将所有的训练数据输入,拟合
然后再去使用test数据,放入模型,获得结果。
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
4/4 [==============================] - 0s 2ms/step - loss: 18.7225 - mae: 2.7271
补充知识:【机器学习】 - TensorFlow.Keras 建立模型 model.evaluate 和 model.predict 的区别_keras model.evaluate_xuanweiace的博客-CSDN博客
model.evaluate输入数据和标签,输出损失值和选定的指标值(如精确度accuracy)
model.predict输入测试数据,输出预测结果(通常用在需要得到预测结果的时候,比如需要拿到结果来画图)
Keras中用验证集进行模型验证的方法_(validation_data,validation_split,model.evaluate)_日拱一两卒的博客-CSDN博客
3.6.5小结
下面是你应该从这个例子中学到的要点。
1.回归问题使用的损失函数与分类问题不同。
回归常用的损失函数是均方误差(MSE )。
同样,回归问题使用的评估指标也与分类问题不同。显而易见,精度的概念不适用于回归问题。常见的回归指标是平均绝对误差(MAE )。
2.如果输人数据的特征具有不同的取值范围,应该先进行预处理,对每个特征单独进行缩放。
就是标准化,对于输入数据的每个特征(也就是输入数据矩阵中的每一列),减去特征平均值,再除以标准差
3.如果可用的数据很少,使用K折验证可以可靠地评估模型。
4.如果可用的训练数据很少,最好使用隐藏层较少(通常只有一到两个)的小型网络,以避免严重的过拟合。
第4章
4机器学习基础
4.1 机器学习的四个分支
在前面的例子中,你已经熟悉了三种类型的机器学习问题:
二分类问题、多分类问题和标量回归问题。这三者都是监督学习
(supervised learning)的例子,
其目标是学习训练输入与训练目标之间的关系。 (也就是已知输入,和输出,获得它们之间的映射关系)
监督学习只是冰山一角——机器学习是非常宽泛的领域,其子领域的划分非常复杂。
机器学习算法大致可分为四大类,我们将在接下来的四小节中依次介绍
4.1.1监督学习 supervised learning
监督学习是目前最常见的机器学习类型。
给定一组样本(通常由人工标注),它可以学会将输入数据映射到已知目标「也叫标注( annotation )]。 也就是有数据,也有答案
本书前面的四个例子都属于监督学习。一般来说,近年来广受关注的深度学习应用几乎都属于监督学习,比如光学字符识别、语音识别、图像分类和语言翻译。
虽然监督学习主要包括分类和回归,但还有更多的奇特变体,主要包括如下几种。
1.序列生成( sequence generation )。给定一张图像,预测描述图像的文字。序列生成有时可以被重新表示为一系列分类问题,比如反复预测序列中的单词或标记。
2.语法树预测( syntax tree prediction )。给定一个句子,预测其分解生成的语法树
3目标检测( object detection )。
给定一张图像,在图中特定目标的周围画一个边界框。
这个问题也可以表示为分类问题(给定多个候选边界框,对每个框内的目标进行分类)或分类与回归联合问题(用向量回归来预测边界框的坐标)。
4图像分割(image segmentation)。给定一张图像,在特定物体上画一个像素级的掩模( mask )。
4.1.2 无监督学习 Unsupervised learning
无监督学习是指在没有目标的情况下寻找输入数据的有趣变换,
其目的在于数据可视化、 数据压缩、数据去噪或更好地理解数据中的相关性。
无监督学习是数据分析的必备技能,在解决监督学习问题之前,为了更好地了解数据集,它通常是一个必要步骤。
降维(dimensionality reduction)和聚类(clustering)都是众所周知的无监督学习方法。
4.1.3 自监督学习
自监督学习是监督学习的一个特例,它与众不同,值得单独归为一类。
自监督学习是没有人工标注的标签的监督学习,你可以将它看作没有人类参与的监督学习。
标签仍然存在(因为总要有什么东西来监督学习过程),但它们是从输入数据中生成的,通常是使用启发式算法生成的。
自编码器(autoencoder)是有名的自监督学习的例子。
同样,给定视频中过去的帧来预测下一帧,或者给定文本中前面的词来预测下一个词, 都是自监督学习的例子[这两个例子也属于时序监督学习(temporally supervised learning),即用未来的输入数据作为监督]。
注意,监督学习、自监督学习和无监督学习之间的区别有时很模糊,
这三个类别更像是没有明确界限的连续体。自监督学习可以被重新解释为监督学习或无监督学习,这取决于你关注的是学习机制还是应用场景。
4.1.4 强化学习
在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行。
分类和回归术语表
分类和回归都包含很多专业术语。前面你已经见过一些术语,在后续章节会遇到更多。
这些术语在机器学习领域都有确切的定义,你应该了解这些定义。
‰ 样本(sample)或输入(input):进入模型的数据点。
‰ 预测(prediction)或输出(output):从模型出来的结果。
‰ 目标(target):真实值。对于外部数据源,理想情况下,模型应该能够预测出目标。
‰ 预测误差(prediction error)或损失值(loss value):模型预测与目标之间的距离。
‰ 类别(class):分类问题中供选择的一组标签。例如,对猫狗图像进行分类时,“狗”
和“猫”就是两个类别。
‰ 标签(label):分类问题中类别标注的具体例子。比如,如果 1234 号图像被标注为
包含类别“狗”,那么“狗”就是 1234 号图像的标签。
‰ 真值(ground-truth)或标注(annotation):数据集的所有目标,通常由人工收集。
‰ 二分类(binary classification):一种分类任务,每个输入样本都应被划分到两个互
斥的类别中。
‰ 多分类(multiclass classification):一种分类任务,每个输入样本都应被划分到两个
以上的类别中,比如手写数字分类。
‰ 多标签分类(multilabel classification):一种分类任务,每个输入样本都可以分配多
个标签。举个例子,如果一幅图像里可能既有猫又有狗,那么应该同时标注“猫”
标签和“狗”标签。每幅图像的标签个数通常是可变的。
‰ 标量回归(scalar regression):目标是连续标量值(标量维度为0,只有一个值,连续标量就是连续的单一值组成的结果)的任务。预测房价就是一个很好的例子,不同的目标价格形成一个连续的空间。
‰ 向量回归(vector regression):目标是一组连续值(比如一个连续向量(一维,但是可以包含多个数据))的任务。如果对多个值(比如图像边界框的坐标)进行回归,那就是向量回归。
‰ 小批量(mini-batch)或批量(batch):模型同时处理的一小部分样本(样本数通常
为 8~128)。样本数通常取 2 的幂,这样便于 GPU 上的内存分配。训练时,小批量
用来为模型权重计算一次梯度下降更新。
4.2 评估机器学习模型
机器学习的目的是得到可以泛化(generalize)的模型,即在前所未见的数据上表现很好的模型,而过拟合则是核心难点。
你只能控制可以观察的事情,所以能够可靠地衡量模型的泛化能力非常重要。后面几节将介绍降低过拟合以及将泛化能力最大化的方法。
4.2.1 训练集、验证集和测试集
评估模型的重点是将数据划分为三个集合:训练集、验证集和测试集。而不是两个。
原因在于开发模型时总是需要调节模型配置,
比如选择层数或每层大小[这叫作模型的超参数(hyperparameter),(可以简单理解为机器无法自己学习的)
以便与模型参数(即权重,在过程中要经过线性或者非线性的变化成为某层的输入或输出)区分开]。
这个调节过程需要使用模型在验证数据上的性能作为反馈信号。
这个调节过程本质上就是一种学习。
如果基于模型在验证集上的性能来调节模型配置,会很快导致模型在验证集上过拟合,即使你并没有在验证集上直接训练模型也会如此。
造成这一现象的关键在于信息泄露(information leak)
每次基于模型在验证集上的性能来调节模型超参数,都会有一些关于验证数据的信息泄露到模型中。
最后,你得到的模型在验证集上的性能非常好(人为造成的),因为这正是你优化的目的。
你关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此你需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。
之前看视频已经了解到:
你的模型一定不能读取与测试集有关的任何信息,既使间接读取也不行。如果基于测试集性能来调节模型,那么对泛化能力的衡量是不准确的。
如果可用数据很少,还有几种高级方法可以派上用场。
我们先来介绍三种经典的评估方法:
简单的留出验证、(包括自己划分,自动划分)
K 折验证,
以及带有打乱数据的重复 K 折验证
num_validation_samples = 10000
np.random.shuffle(data) #打乱数据
validation_data = data[:num_validation_samples] #留出验证数据
data = data[num_validation_samples:]
training_data = data[:] #定义训练数据
model = get_model()
model.train(training_data) #在训练数据上训练模型,根据验证数据反映结果评估模型
validation_score = model.evaluate(validation_data)
model = get_model() #调整好后,使用训练加上验证
数据训练最终模型,评估test
model.train(np.concatenate([training_data, validation_data]))
test_score = model.evaluate(test_data)
这是最简单的评估方法,但有一个缺点:
如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。
K 折验证与重复的 K 折验证,它们是解决这一问题的两种方法
2. K 折验证 具体可看上面
K 折验证(K-fold validation)将数据划分为大小相同的 K 个分区。
对于每个分区 i,在剩余的 K-1 个分区(训练集)上训练模型,然后在
分区 i(验证集) 上评估模型。最终分数等于 K 个分数的平均值。
对于不同的训练集-- 测试集划分,如果模型性能的变化很大,那么这种方法很有用。
与留出验证一样,这种方法也需要独立的验证集进行模型校正。
k = 4
num_validation_samples = len(data) // k #通过分多少个区整除获得一个区验证
np.random.shuffle(data) #打乱数据
validation_scores = []
for fold in range(k):
validation_data = data[num_validation_samples * fold:
num_validation_samples * (fold + 1)] #根据fold值获得 不同的验证分区
training_data = data[ :num_validation_samples * fold] +
data[num_validation_samples * (fold + 1): ]
#这里使用的是+号作 为list合并,不是求和
model = get_model() #创建全新模型,未训练
model.train(training_data)
validation_score = model.evaluate(validation_data)
validation_scores.append(validation_score)
validation_score = np.average(validation_scores) #最终求所有分数的平均
model = get_model()
model.train(data)
test_score = model.evaluate(test_data) #最后在test上训练
3. 带有打乱数据的重复 K 折验证
如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复 K 折验证(iterated K-fold validation with shuffling)。
具体做法是多次使用 K 折验证,在每次将数据划分为 K 个分区之前都先将数据打乱。 (也就是每次k折前都打乱一次数据)
最终分数是每次 K 折验证分数的平均值。
注意,这种方法一共要训练和评估 P×K 个模型(P 是重复次数),计算代价很大。
4.2.2 评估模型注意事项
1划分数据前随机打乱
2 涉及时间 不要打乱,测试一定晚于训练
3数据容易重复,同时出现在训练和测试
选择模型评估方法时,需要注意以下几点。
数据代表性(data representativeness)。你希望训练集和测试集都能够代表当前数据。例如,你想要对数字图像进行分类,而图像样本是按类别排序的,如果你将前 80% 作为训练集,剩余 20% 作为测试集,那么会导致训练集中只包含类别 0~7,而测试集中只包含类别 8~9。这个错误看起来很可笑,却很常见。因此,在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
时间箭头(the arrow of time)。如果想要根据过去预测未来(比如明天的天气、股票走势等),那么在划分数据前你不应该随机打乱数据,因为这么做会造成
时间泄露(temporal leak):你的模型将在未来数据上得到有效训练。在这种情况下,你应该始终确保测试集中所有数据的时间都晚于训练集数据。
数据冗余(redundancy in your data)。如果数据中的某些数据点出现了两次
(这在现实中的数据里十分常见),那么打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余。
从效果上来看,你是在部分训练数据上评估模型,这是极其糟糕的!
一定要确保训练集和验证集之间没有交集。 注:难以完成
4.3 数据预处理、特征工程和特征学习
除模型评估之外,在深入研究模型开发之前,我们还必须解决另一个重要问题:
将数据输入神经网络之前,如何准备输入数据和目标?
许多数据预处理方法和特征工程技术都是和特定领域相关的
(比如只和文本数据或图像数据相关),我们将在后续章节的实例中介绍。
现在我们要介绍所有数据领域通用的基本方法。
4.3.1 神经网络的数据预处理
数据预处理的目的是使原始数据更适于用神经网络处理,
包括向量化、标准化、处理缺失值和特征提取。
无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化(data vectorization)。
例如,在前面两个文本分类的例子中,开始时文本都表示为整数列表(代表单词序列),然后我们用 one-hot 编码将其转换为 float32 格式的张量。
在手写数字分类和预测房价的例子中,数据已经是向量形式,所以可以跳过这一步。
输入数据应该具有以下特征。 牢记!!!
取值较小:大部分值都应该在 0~1 范围内。
同质性(homogenous):所有特征的取值都应该在大致相同的范围内。
下面这种更严格的标准化方法也很常见而且很有用,虽然不一定总是必需的(例如, 对于数字分类问题就不需要这么做)。
将每个特征分别标准化,使其平均值为 0。 即所有值减去mean在除以标准差
将每个特征分别标准化,使其标准差为 1。
x - = x.mean(axis=0) #假设 x 是一个形状为 (samples,
features) 的二维矩阵
x /= x.std(axis=0)
3. 处理缺失值:设置为0
你的数据中有时可能会有缺失值。
例如在房价的例子中,第一个特征(数据中索引编号为0 的列)是人均犯罪率。如果不是所有样本都具有这个特征的话,怎么办?
那样你的训练数据或测试数据将会有缺失值。
一般来说,对于神经网络,将缺失值设置为 0 是安全的,只要 0 不是一个有意义的值。网络能够从数据中学到 0 意味着缺失数据,并且会忽略这个值。
注意,如果测试数据中可能有缺失值,而网络是在没有缺失值的数据上训练的,那么网络不可能学会忽略缺失值。
在这种情况下,你应该人为生成一些有缺失项的训练样本:多次复制一些训练样本,然后删除测试数据中可能缺失的某些特征。
4.3.2 特征工程
特征工程(feature engineering)是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换
(不是模型学到的),以改善模型的效果。
多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。
经典例子:怎么让机器读取时间?
法一:根据100万张钟表图像,使用卷积神经网络来解决这个问题,而且还需要花费大量的计算资源来训练网络
法二:编写 5 行 Python 脚本,找到时钟指针对应的黑色像素并输出每个指针尖的 (x, y) 坐标 一个简单的机器学习算法就可以学会这些坐标与时间的对应关系。
法三:进行坐标变换,将 (x, y) 坐标转换为相对于图像中心的极坐标。这样
输入就变成了每个时钟指针的角度 theta。 现在的特征使问题变得非常简单,根本不需要机器学习
这就是特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。
深度学习出现之前,特征工程曾经非常重要,因为经典的浅层算法没有足够大的假设空间来自己学习有用的表示。
对于现代深度学习,大部分特征工程都是不需要的,因为神经网络能够从原始数据中自动提取有用的特征。但不意味着就不需要考虑特征工程
------良好的特征仍然可以让你用更少的资源更优雅地解决问题
------良好的特征可以让你用更少的数据解决问题
4.4 过拟合与欠拟合 降低过拟合:正则化(3个)
过拟合存在于所有机器学习问题中。学会如何处理过拟合对掌握机器学习至关重要。
机器学习的根本问题是优化和泛化之间的对立。
优化(optimization)是指调节模型以在训练数据上得到最佳性能
(即机器学习中的学习)
而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。
机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。
(理解:这就是像一个矛盾问题,你要把机器未见过的数据拿来测试才叫泛化,但是训练后的结果你无法控制,若想要得到更好的结果,就需要更改weight,再次训练,可这又不叫泛化了,因为机器已经见过这些数据了,只能又去测试新数据才叫泛化)
训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。
这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模 式建模。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差, 即模型开始过拟合。
为了防止模型从训练数据中学到错误或无关紧要的模式,
最优解决方法是获取更多的训练数据。
如果无法获取更多数据,次优解决方法是
调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。
这种降低过拟合的方法叫作正则化(regularization)。
我们先介绍几种最常见的正则化方法,
然后将其应用于实践中,以改进 3.4 节的电影分类模型。
4.4.1正则化法一:减小网络(模型)大小
防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。
在深度学习中,模型中可学习参数的个数通常被称为模型的容量(capacity)。
直观上来看,参数更多的模型拥有更大的记忆容量(memorization capacity),
因此能够在训练样本和目标之间轻松地学会完美的字典式映射,
这种映射没有任何泛化能力。就是相当于神经网络背下来了。
同时请记住,你使用的模型应该具有足够多的参数,以防欠拟合。
所以,应该在在容量过大与容量不足之间要找到一个折中。
评估一系列不同的网络架构(当然是在验证集上评估,而不是在测试集上),以便为数据找到最佳的模型大小。
记住:一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
例子:上面的电影评论
original_model = models.Sequential()
original_model.add(layers.Dense(16, activation=‘relu’, input_shape=(10000,)))
original_model.add(layers.Dense(16, activation=‘relu’))
original_model.add(layers.Dense(1, activation=‘sigmoid’))
smaller_model = models.Sequential()
smaller_model.add(layers.Dense(4, activation=‘relu’, input_shape=(10000,)))
smaller_model.add(layers.Dense(4, activation=‘relu’))
smaller_model.add(layers.Dense(1, activation=‘sigmoid’))
更小的网络开始过拟合的时间要晚于参考网络
4.4.2 法二:添加权重正则化L1 L2(权重只取小值)
你可能知道奥卡姆剃刀(Occam’s razor)原理:如果一件事情有两种解释,那么最可能正确的解释就是最简单的那个,即假设更少的那个。
这个原理也适用于神经网络学到的模型:给定一些训练数据和一种网络架构,很多组
权重值(即很多模型)都可以解释这些数据。
简单模型比复杂模型更不容易过拟合.
这里的简单模型(simple model)是指参数值分布的熵(entropy)更小(或参数更少的模型,比如上一节的例子)。
因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,使得权重值的分布更加规则(regular)。
这种方法叫作权重正则化 (weight regularization)。
实现方法是向网络损失函数中添加与较大权重值相关的成本(cost)。
这个成本有两种形式。
L1 正则化(L1 regularization):
添加的成本与权重系数的绝对值[权重的 L1 范数(norm)]成正比。
L2 正则化(L2 regularization):
添加的成本与权重系数的平方(权重的 L2 范数)成正比。
扩展:
什么是范数(norm)?以及L1,L2范数的简单介绍_小白的进阶之路的博客-CSDN博客
L1范数可以使权值稀疏,方便特征提取。 L2范数可以防止过拟合,提升模型的泛化能力
成正比其实就是要乘以一个λ,设置λ的值
神经网络的 L2 正则化也叫权重衰减(weight decay)。
不要被不同的名称搞混,权重衰减与 L2 正则化在数学上是完全相同的。
在 Keras 中,添加权重正则化的方法是
向层传递权重正则化项实例(weight regularizer instance)作为关键字参数。
下列代码将向电影评论分类网络中添加 L2 权重正则化。
from keras import regularizers
l2_model = models.Sequential()
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation=‘relu’, input_shape=(10000,)))
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation=‘relu’))
l2_model.add(layers.Dense(1, activation=‘sigmoid’))
l2(0.001) 的意思是该层权重矩阵的每个系数都会使网络总损失增加
0.001* weight_ coefficient_value(权重系数值)
注意,由于这个惩罚项只在训练时添加,所以这个网络的训练损失会比测试损失大很多。
图显示了 L2 正则化惩罚的影响。如你所见,即使两个模型的参数个数相同,具有 L2
正则化的模型(圆点)比参考模型(十字)更不容易过拟合。
注意:可同时做 L1 和 L2 正则化 ,用 Keras 中以下这些权重正则化项来代替 L2 正则化
from keras import regularizers
regularizers.l1(0.001)
regularizers.l1_l2(l1=0.001, l2=0.001)
4.4.3 法三:添加 dropout 正则化(最常用)
dropout 是神经网络最有效也最常用的正则化方法之一,
假设在训练过程中,某一层对给定输入样本的返回值应该是向量 [0.2, 0.5,
1.3, 0.8, 1.1]。使用 dropout 后,这个向量会有几个随机的元素变成 0,比如 [0, 0.5, 1.3, 0, 1.1]。
对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。
dropout 比率(dropout rate)是 被设为 0 的特征所占的比例,通常在 0.2~0.5
范围内。
关键:测试时没有单元被舍弃,而该层的输出值需要按 dropout 比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。
具体过程:假设有一个包含某层输出的 Numpy 矩阵 layer_output,其形状为 (batch_size, features)。
注意:这里是训练时,我们随机将矩阵中一部分值设为 0。
扩展:numpy的加减乘除运算 - 知乎 (zhihu.com)
关键一句:numpy.array 是 numpy 中最常见的数据结构,用于表示多维数组,在数学上就是一个张量。
注意:这里是测试时,我们将输出按 dropout 比率缩小。这里我们乘以 0.5(因为前面训练时舍弃了一半的单元)
理解:最后测试阶段,每一层算出来的结果必须也乘0.5,因为测试时使用的是所有的神经元,当初估计参数时每个参数都是正常情况的两倍,测试时乘的参数变大,所以结果需要变小
layer_output *= 0.5
注意,为了实现这一过程,实际上是让两个运算都在训练时进行,
而测试时输出保持不变。
#训练时先减少再扩大
layer_output *= np.randint(0, high=2, size=layer_output.shape)
layer_output /= 0.5 #注意这里是在训练时完成,除0.5来扩大
这一方法可能看起来有些奇怪和随意。它为什么能够降低过拟合?
其核心思想是在层的输出值中引入噪声, 打破不显著的偶然模式(Hinton 称之为阴谋)。
如果没有噪声的话,网络将会记住这些偶然模式。 #也就是防止网络背下数据
#IMDB例子电影评论
model = models.Sequential()
model.add(layers.Dense(16, activation=‘relu’, input_shape=(10000,)))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(16, activation=‘relu’))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation=‘sigmoid’))
dpt_model_hist = dpt_model.fit(x_train, y_train,
epochs=20,
batch_size=256, #原512
validation_data=(x_test, y_test))
这种方法的性能相比参考网络有明显提高
注意:这里由于设置的batch_size=512,非常耗内存RAM,不管是电脑还是colab都无法运行,所以将其改为256
总结一下,防止神经网络过拟合的常用方法包括:
1获取更多的训练数据
2减小网络(模型)容量
3添加权重正则化
4添加 dropout
4.5 机器学习的通用工作流程
本节将介绍一种可用于解决任何机器学习问题的通用模板。这一模板将你在本章学到的这些概念串在一起:问题定义、评估、特征工程和解决过拟合。
4.5.1 定义问题,收集数据集
首先,你必须定义所面对的问题。
你的输入数据是什么?你要预测什么?
你面对的是什么类型的问题?是二分类问题、多分类问题、标量回归问题、向量回归问题(向量回归和标量的差距就在于 标量是获得一个连续的值,向量是得到一个向量,很多个值),多分类、多标签或单标签
注意你在这一阶段所做的假设。
假设输出是可以根据输入进行预测的。
假设可用数据包含足够多的信息,足以学习输入和输出之间的关系。
事实上,现实生活中很多东西未来的规律与过去数据并没有什么很强的关联
4.5.2 选择衡量成功的指标
要控制一件事物,就需要能够观察它。要取得成功,就必须给出成功的定义:精度?准确率(precision)和召回率(recall)?
对于平衡分类问题(每个类别的可能性相同),精度和
接收者操作特征曲线下面积(area under the receiver operating characteristic curve,ROC AUC)是常用的指标。
对于类别不平衡的问题,你可以使用准确率和召回率。可以据此获得f1-score
对于排序问题或多标签分类,你可以使用平均准确率均值(mean average precision)
4.5.3 确定评估方法
一旦明确了目标,你必须确定如何衡量当前的进展。前面介绍了三种常见的评估方法。
留出验证集。数据量很大时可以采用这种方法。
K 折交叉验证。如果留出验证的样本量太少,无法保证可靠性,那么应该选择这种方法。
重复的 K 折验证。如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法。
只需选择三者之一。大多数情况下,第一种方法足以满足要求
4.5.4 准备数据(预处理)
如前所述,应该将数据格式化为(浮点)张量。
这些张量的取值通常应该缩放为较小的值,比如在 [-1, 1] 区间或 [0, 1] 区间。 原因:在梯度下降时的初始值就很小,不需要迭代很多次就能得出一个比较好的结果。
如果不同的特征(一般都是数据的column列)具有不同的取值范围(异质数据),那么应该做数据标准化。
你可能需要做特征工程,尤其是对于小数据问题。
4.5.5 开发比基准更好模型(比随机猜更好)
这一阶段的目标是获得统计功效(statistical power),即开发一个小型模型,它能够打败纯随机的基准(dumb baseline)。
在 MNIST 数字分类的例子中,任何精度大于 0.1 的模型都可以说具有统计功效;
在 IMDB 的例子中,任何精度大于 0.5 的模型都可以说具有统计功效。
注意,不一定总是能获得统计功效。如果你尝试了多种合理架构之后仍然无法打败随机基准,说明之前的两个假设很可能是错误的,这样的话你需要从头重新开始。
还需要选择三个关键参数来构建第一个工作模型。
最后一层的激活函数。它对网络输出进行有效的限制。例如,IMDB 分类的例子在最后一层使用了 sigmoid,回归的例子在最后一层没有使用激活,等等。
损失函数。它应该匹配你要解决的问题的类型。例如,IMDB 的例子使用 binary_ crossentropy、回归的例子使用 mse,等等。
优化配置。使用优化器,大多数情况下,使用 rmsprop 及其默认的学习率是稳的。
关于损失函数的选择 注意:直接优化衡量问题成功的指标不一定总是可行的
损失函数需要在只有小批量数据时即可计算 ,而且还必须是可微的(才能进行梯度下降),广泛使用的分类指标 ROC AUC 就不能被直接优化
(准确率其实也是不能直接优化)。
所以为什么不直接对准确率做微分,操作上是行不通的,只能寻找替代品
补充:relu的0点其实也不能微分,我们的数据一般都是在0左右,真正落在0上概率很小
因此在分类任务中,常见的做法是优化 ROC AUC 的替代指标,比如交叉熵。
一般来说,你可以认为交叉熵越小,ROC AUC 越大。
表 4-1 重要 常见问题类型的最后一层激活和损失函数
sigmoid :全压到0到1,数字多少就是概率
softmax:一个概率分布,所有值加起来=1
4.5.6 扩大模型规模:开发过拟合的模型
理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。
为了找到这条界线,你必须穿过它。
要搞清楚你需要多大的模型,就必须开发一个过拟合的模型。方法简单
(1) 添加更多的层。
(2) 让每一层变得更大。
(3) 训练更多的轮次。
如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。
下一阶段将开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。
4.5.7 模型正则化与调节超参数(层数和层数大小)
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、
再次调节模型,然后重复这一过程,直到模型达到最佳性能。你应该尝试以下几项。
1添加 dropout。
2尝试不同的架构:增加或减少层数。
3添加 L1 和 / 或 L2 正则化。
4.尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置。
5(可选)反复做特征工程:添加新特征或删除没有信息量的特征。
请注意:由于每次使用验证数据的反馈来调节模型,都会将有关验证过程的信息泄露到模型中。
所以如果开发出满意的模型,就可以在所有可用数据(训练数据 + 验证数据)上训练最终的生产模型,然后在测试集上最后评估一次。
如果测试结果很差,就说明需要更换更可靠的评估方法
本章小结 记住6步
定义问题与要训练的数据。收集这些数据,有需要的话用标签来标注数据。
选择衡量问题成功的指标。你要在验证数据上监控哪些指标?
确定评估方法:留出验证? K 折验证?你应该将哪一部分数据用于验证?
开发第一个比基准更好的模型,即一个具有统计功效的模型。
开发过拟合的模型。
基于模型在验证数据上的性能来进行模型正则化与调节超参数。许多机器学习研究往往只关注这一步,但你一定要牢记整个工作流程。
5深度学习用于计算机视觉CNN
convolution 卷积+net
本章将介绍卷积神经网络,也叫 convnet,它是计算机视觉应用几乎都在使用的一种深度学习模型。
将学到将卷积神经网络应用于图像分类问题,特别是那些训练数据集较小的问题。
5.1 卷积神经网络简介
即使用卷积神经网络对 MNIST 数字进行分类,
这个任务我们在第 2 章用密集连接网络做过(当时的测试精度为 97.8%)
下列代码将会展示一个简单的卷积神经网络。它是 Conv2D 层和 MaxPooling2D 层的堆叠。
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’, input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
其中 图像数据:4D 张量,形状为 (samples, height/row, width/column, channels)
通道在后(channels-last)的约定(在 TensorFlow 中使用)
例如:灰度图片就为1,彩色图片RGB 就为3
关键!!!:重要的是,卷积神经网络接收形状为
(image_height, image_width, image_channels) 的输入张量(不包括批量维度第0轴即sample)。
输出都是一个形状为 (height, width, channels) 的 3D 张量。
本例中设置卷积神经网络处理大小为 (28, 28, 1) 的输入张量,这正是 MNIST 图像的格式。
注意:其实mnist图片没有后面的channel,原来是(60000,28,28)是通过了reshape变为(60000,28,28,1),这是为了满足keras的要求做的操作,
实质上没有改变,因为keras的conv 的input需要四维
可以网上查询keras conv2D,了解各个参数代表意思。
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’, input_shape=(28, 28, 1)))
对于 Keras 的 Conv2D 层,这些参数都是向层传入的前几个参数:Conv2D(output_depth, (window_height, window_width),……)。
1–第一个32代表有32种filter过滤器,按视频叫做滤镜,就是截取图片一部分后显示的一种情况,现在假设有32种可能。
通道数量由传入 Conv2D 层的第一个参数所控制(32 或 64)。
2–(3,3)代表filter的大小,一个33的九宫格,每个格子根据输入图像有自己的参数。
它会和原始图片同位置经过处理的值(0到1)进行点对点的相乘,之后再加上bias(等同于以前的全连接),最后得到一个0到1的数字,2828的图片每行每列都经过这样的计算就会出现一个26*26的结果,再加上有32种filter,最后结果形状就是(26,26,32)
3–有一个padding参数,选择是否将其填充为原来的形状。其实是对原图在输入的左/右或上/下均匀地填充零,“valid” 表示不使用填充(只使用有效的窗口位置);“same” 表示“填充后输出的宽度和高度与输入相同”。
padding 参数的默认值为 “valid”
4不写strides参数默认一次filter移动一格,从左到右一共需要移动25次
查看下目前卷积神经网络的架构
model.summary()
#参数个数1 =(331+1)32=320 filter是33,只有一层,再加bias,乘以当前层的神经元
#参数个数2 = (3332+1)64=18469 ,channel变为32,乘以64
#参数个数3 = (33*64+1)*64=36928 , channel变为64
其中maxpooling2d的作用:去芜存菁,上述代码里为(2,2)也是一个
22的方块,会对2626的结果从左到右,从上到下,不重叠的遍历,
选出22方块中最大的数字(抓出具有最明显的特征),得到1313矩阵,最后结果就是上图的情况。
第二次conv2d是对131332 用33的filter过滤,就是这个33要与288(932)做点对点(与卷积核做张量积)的乘积,加上bias,经过线性转换,转换成形状为 (output_depth, ) 的 1D 向量
注意:还是获得一个值,而不是因为有32层就获得了32个值,所以最后
形状是变为1111,再有64个filter,变为(11,11,64)
第二次maxpooling因为11不能整除2,就舍弃一部分,最后一次conv2d同理。
下一步是将最后的输出张量[大小为 (3, 3, 64)]输入到一个密集连接分类器网络中,即 Dense 层的堆叠,这些分类器可以处理 1D 向量.
而当前的输出是 3D 张量。
首先,我们需要将 3D 输出展平为 1D,然后在上面添加几个 Dense 层
#使用flatten,将 3D 输出展平为 1D
model.add(layers.Flatten())
model.add(layers.Dense(64, activation=‘relu’))
model.add(layers.Dense(10, activation=‘softmax’))
from keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
#from keras.utils import to_categorical
#这里应该是from tensorflow.keras.utils 版本问题,TensorFlow现在容纳了keras
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images =train_images.reshape((60000, 28, 28, 1)) #这里就是前面所说reshape 为四维,keras的规定
train_images = train_images.astype(‘float32’) / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype(‘float32’) / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
#我们在测试数据上对模型进行评估。
test_loss, test_acc = model.evaluate(test_images, test_labels)
test_acc
第 2 章密集连接网络的测试精度为 97.8%,但这个简单卷积神经网络的测试精度达到了99.3%,我们将错误率降低了 68%(相对比例)。相当不错!
与密集连接模型相比,为什么这个简单卷积神经网络的效果这么好?
要回答这个问题,我们来深入了解 Conv2D 层和 MaxPooling2D 层的作用。(即上文提到的一些东西)
5.1.1 卷积运算
密集连接层和卷积层的根本区别在于,
Dense 层(处理1D张量即向量)从输入特征空间中学到的是全局模式(直接将图片变为(784,)一下子全部输入,可能就会忽视图片之间的一些关联),
而卷积层学到的是局部模式(3*3,可能就会把这种关联显示出来),
对于图像来说,学到的就是在输入图像的二维小窗口中发现的模式(即那个3*3的filter)
局部学习使卷积神经网络具有以下两个有趣的性质。
1.卷积神经网络学到的模式具有平移不变性(translation invariant)。
卷积神经网络在图像右下角学到某个模式之后,它可以在任何地方识别这个模式,比如左上角。
对于密集连接网络来说,如果模式出现在新的位置,它只能重新学习这个模式。
这使得卷积神经网络在处理图像时可以高效利用数据
(因为视觉世界从根本上具有平移不变性),它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
2.卷积神经网络可以学到模式的空间层次结构(spatial hierarchies of patterns),
第一个卷积层将学习较小的局部模式(比如边缘),
第二个卷积层将学习由第一层特征组成的更大的模式,以此类推。
这使得卷积神经网络可以有效地学习越来越复杂、越来越抽象的视觉概念
(因为视觉世界从根本上具有空间层次结构)。
对于包含两个空间轴(高度height和宽度weight)和一个深度轴(也叫通道轴)的 3D 张量,其卷积也叫特征图(feature map)。RGB 图像,深度轴等于 3。对于黑白图像,深度等于 1(表示灰度等级)。
卷积运算从输入特征图中提取图块,并对所有这些图块应用相同的变换,生成输出特征图(output feature map)。
该输出特征图仍是一个 3D 张量,具有宽度和高度,其深度可以任意取值,因为输出深度是层的参数,深度轴的不同通道不再像 RGB 输入那样代表特定颜色,而是代表过滤器(filter)的个数(就像上文的64,32个filter)。
结合上文,下文,根据视频讲解,再次理解。
在MNIST 示例中,第一个卷积层接收一个大小为 (28, 28, 1) 的特征图,并输出一个大小为 (26, 26, 32) 的特征图,即它在输入上计算 32 个过滤器。
对于这 32 个输出通道,每个通道都包含一个 26×26 的数值网格,它是过滤器对输入的响应图(response map),表示这个过滤器模式在输入中不同位置的响应。
也就说明了特征图的含义,深度轴的每个维度都是一个特征(或过滤器),而 2D 张量 output[:, :, n] 是这个过滤器在输入上的响应 的二维空间图(map)
卷积由以下两个关键参数所定义。
从输入中提取的图块尺寸:这些图块的大小通常是 3×3 或 5×5。本例中为 3×3,这是很常见的选择。
输出特征图的深度:卷积所计算的过滤器的数量。本例第一层的深度为 32,最后一层的深度是 64。
卷积的工作原理:基本同上,再次理解:
在 3D 输入特征图上滑动(slide)这些 3×3 或 5×5 的窗口,
在每个可能的位置停止并提取周围特征的 3D 图块,形状为 (window_height, window_width, input_ depth)]。
然后每个 3D 图块与学到的同一个权重矩阵[叫作卷积核(convolution kernel)]做张量积,转换成形状为 (output_depth,) 的 1D 向量。
然后对所有这些向量进行空间重组,使其转换为形状为(height, width, output_depth) 的 3D 输出特征图。
输出特征图中的每个空间位置都对应于输入特征图中的相同位置(比如输出的右下角包含了输入右下角的信息)
注意,输出的宽度和高度可能与输入的宽度和高度不同。不同的原因可能有两点。
1边界效应,可以通过对输入特征图进行填充来抵消。即上文的padding
假设有一个 5×5 的特征图(共 25 个方块)。其中只有 9 个方块可以作为中心放入一个3×3 的窗口,这 9 个方块形成一个 3×3 的网格(见图 5-5)。因此,输出特征图的尺寸是 3×3。
2使用了步幅(stride)。意思就是说假设stride的大小不是默认的1,
也可 以使用步进卷积(strided convolution),即步幅大于 1 的卷积。
步幅为 2 意味着特征图的宽度和高度都被做了 2 倍下采样,在实践中很少使用。熟悉这个概念是有好处的。
为了对特征图进行下采样,我们不用步幅,而是通常使用最大池化(max-pooling)运算
5.1.2 最大池化运算
关键:最大池化的作用:对特征图进行下采样,与步进卷积类似
最大池化是从输入特征图中提取窗口,并输出每个通道的最大值。
它的概念与卷积类似,但是最大池化使用硬编码的 max 张量运算对局部图块进行变换,而不是使用学到的线性变换(卷积核)。
最大池化与卷积的最大不同之处在于,最大池化通常使用 2×2 的窗口和步幅 2(所以不会重叠),其目的是将特征图下采样 2 倍。
与此相对的是,卷积通常使用 3×3 窗口和步幅 1。
为什么要maxpooling,而不去直接conv2D?
这里是三层conv2D。
1、从28到26,到24,到22,三层卷积其实就等于一次用77的filter会出现的结果,而通过maxpooling,再去卷积,33的filter能获得更多更大的特征
2、最后一层的特征图对每个样本共有 22×22×64=30 976 个元素。这太多了。如果你将其展平并在上面添加一个大小为 512 的Dense 层,那一层将会有 1580 万个参数。这对于这样一个小模型来说太多了,会导致严重的过拟合。
总结:多次连续conv2D,跟直接一次大的conv2D是一样的,而且会导致参数过多,严重拟合。
所以,简而言之,使用下采样的原因,
一是减少需要处理的特征图的元素个数,
二是通过让连续卷积层的观察窗口越来越大(即窗口覆盖原始输入的比例越来越大)(3*3的窗口覆盖的输入由于输入特征图的变小而变大),从而引入空间过滤器的层级结构。
注意,最大池化不是实现这种下采样的唯一方法。你已经知道,还可以在前一个卷积层中使用步幅来实现(但实际上不使用)。
此外,你还可以使用平均池化来代替最大池化,其方法是将每个局部输入图块
变换为取该图块各通道的平均值,而不是最大值。但最大池化的效果往往比这些替代方法更好。
简而言之,原因在于特征中往往编码了某种模式或概念在特征图的不同位置是否存在(因此得名特征图),而观察不同特征的最大值而不是平均值能够给出更多的信息。
因此,最合理的子采样策略是首先生成密集的特征图(通过无步进的卷积),
然后观察特征每个小图块上的最大激活,
而不是查看输入的稀疏窗口(通过步进卷积)或对输入图块取平均,因为后两种方法可能导致错过或淡化特征是否存在的信息。
自我理解:先细看找出所有特征,再获得最明显的特征
5.2 在小型数据集上从头开始训练一个卷积神经网络
使用很少的数据来训练一个图像分类模型,这是很常见的情况
本节将介绍解决这一问题的基本策略,即使用已有的少量数据从头开始训练一个新模型。
我们将重点讨论猫狗图像分类,数据集中包含 4000 张猫和狗的图像
大小并不相同
(2000 张猫的图像,2000 张狗的图像)。
我们将 2000 张图像用于训练,1000 张用于验证,1000 张用于测试。
猫(1000训练,500验证,500测试 )
狗(1000训练,500验证,500测试 )
首先,在 2000 个训练样本上训练一个简单的小型卷积神经网络,不做任何正则化,为模型目标 设定一个基准。这会得到 71% 的分类精度。此时主要的问题在于过拟合。
然后,我们会介绍数据增强(data augmentation)
(根据视频讲解,就是将图片进行一些旋转,角度移动,防止网络背下),
它在计算机视觉领域是一种非常强大的降低过拟合的技术。
5.3 节会介绍将深度学习应用于小型数据集的另外两个重要技巧:
用预训练的网络做特征提取(得到的精度范围在 90%~96%),
对预训练的网络进行微调(最终精度为 97%)。
5.2.1 深度学习与小数据问题的相关性
仅在有大量数据可用时,深度学习才有效。这种说法部分正确
由于卷积神经网络学到的是局部的、平移不变的特征,它对于感知问题可以高效地利用数据。虽然数据相对较少,但在非常小的图像数据集上从头开始训练一个卷积神经网络,仍然可以得到不错的结果,而且无须任何自定义的特征工程。
深度学习模型本质上具有高度的可复用性,比如,已有一个在大规模数据集上训练的图像分类模型或语音转文本模型,你只需做很小的修改就能将其复用于完全不同的问题。
特别是在计算机视觉领域预训练的模型(通常都是在 ImageNet 数据集上训练得到的)现在都可以公开下载。
5.2.2 下载数据 colab(相关方法见csdn)
本节用到的猫狗分类数据集不包含在 Keras 中。
可以从 https://www.kaggle.com/c/dogs-vs-cats/data 下载原始数据集
这个数据集包含 25000 张猫狗图像(每个类别都有 12500 张),大小为 543MB(压缩后)。
下载数据并解压之后,你需要创建一个新数据集,其中包含三个子集:
每个类别各 1000 个样本的训练集、每个类别各 500 个样本的验证集和每个类别各 500 个样本的测试集。
代码清单 5-4 将图像复制到训练、验证和测试的目录
补充:1.Python——os.mkdir()在指定路径下创建文件夹 + 路径的连接理解_py中的os.mkdir的路径问题_威尔莫爵士的博客-CSDN博客
2路径前面加一个‘r’,是为了保持路径在读取时不被漏读,错读。即保持字符原始值的意思。否则就要使用转义字符\ ,或者用/,容易出错
import os, shutil
#原始数据集解压目录的路径 原书漏写 /train 去看数据集就明白 只有test 和train
original_dataset_dir =r’D:/softwareprocess\Downloads\kaggle_original_data\dogs-vs-cats\train’
#保存较小数据集的目录,因为我们只需要2000张猫,2000张狗
base_dir = r’D:\softwareprocess\Downloads\cats_and_dogs_small’
os.mkdir(base_dir)
#分别对应划分后的训练、 验证和测试的目录
使用os.path.join就是在这个文件夹之下建立为此名字的文件夹,
原理,就是连接文件路径
train_dir = os.path.join(base_dir, ‘train’)
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, ‘validation’)
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, ‘test’)
os.mkdir(test_dir)
#还需要将训练、 验证和测试的目录划分为猫和狗的不同目录
#猫的训练图像目录
train_cats_dir = os.path.join(train_dir, ‘cats’)
os.mkdir(train_cats_dir)
#狗的训练图像目录
train_dogs_dir = os.path.join(train_dir, ‘dogs’)
os.mkdir(train_dogs_dir)
#猫的验证图像目录
validation_cats_dir = os.path.join(validation_dir, ‘cats’)
os.mkdir(validation_cats_dir)
#狗的验证图像目录
validation_dogs_dir = os.path.join(validation_dir, ‘dogs’)
os.mkdir(validation_dogs_dir)
#猫的测试图像目录
test_cats_dir = os.path.join(test_dir, ‘cats’)
os.mkdir(test_cats_dir)
#狗的测试图像目录
test_dogs_dir = os.path.join(test_dir, ‘dogs’)
os.mkdir(test_dogs_dir)
目录建好之后,就是向里面装图片数据
由于我们只需要2000张数据,所以接下来是对数据切片
补充:python使用shutil copyfile 复制文件_python shutil.copyfile_Mr.小蔡的博客-CSDN博客
shutil.copyfile(src, dst):将名为src的文件的内容复制到名为dst的文件中。dst必须是完整的目标文件名。
note:所谓完整目标文件名包含了两层含义
1、dst必须含有你复制过去的文件路径,创建路径时需要检查文件夹是否存在。
2、dst中必须包含你想要创建的文件名。简单来说,它其实是将源数据复制到在目标文件夹的文件。所以,下文dst中也有fname
#其中 format可看上文,即一个占位符,它的值会传入花括号所以fname的值就是’cat.i.jpg’ , 注意原图片数据全部已经命好名 为cat.i.jpg或dog.i.jpg
fnames = [‘cat.{}.jpg’. format (i) for i in range(1000)] #装了1000个名字的list
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
#其中src是来源,通过路径连接到original_dataset_dir ,用fname的名字获得相应图片
将图片复制到train_cats_dir
fnames = [‘cat.{}.jpg’.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = [‘cat.{}.jpg’.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = [‘dog.{}.jpg’.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = [‘dog.{}.jpg’.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = [‘dog.{}.jpg’.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
print(‘total training cat images:’, len(os.listdir(train_cats_dir))) #查看数据是否完成
每个分组中两个 类别的样本数相同,这是一个平衡的二分类问题,分类精度可作为衡量成功的指标。
补充:python中os.listdir用法
一 获取指定文件夹下的所有文件
二、 获取指定文件夹下的所有图片
5.2.3 构建网络
我们将复用相同的总体结构,即卷积神经网络由 Conv2D 层(使用 relu 激活)和 MaxPooling2D 层交替堆叠构成。
但由于这里要处理的是更大的图像和更复杂的问题,你需要相应地增大网络,即再增加一个 Conv2D+MaxPooling2D 的组合。
这既可以增大网络容量,也可以进一步减小特征图的尺寸,使其在连接 Flatten 层时尺寸不会太大。
注意 网络中特征图的深度在逐渐增大(从 32 增大到 128),而特征图的尺寸在逐渐减小(从150×150 减小到 7×7)。这几乎是所有卷积神经网络的模式。
本例中初始输入的尺寸为 150×150(有些随意的选择),所以最后在 Flatten 层之前的特征图大小为 7×7。
面对的是一个二分类问题,所以网络最后一层是使用 sigmoid 激活的单一单元
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’, input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2))) # shape(74, 74,32)
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’)) # s(72, 72,64)(3332+1)64=18496
model.add(layers.MaxPooling2D((2, 2))) # sha(36, 36,64)
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’)) # sha(34, 34,128) (3364+1)128
model.add(layers.MaxPooling2D((2, 2))) # sha(17, 17,128)
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’)) #sha(15,15,128)(33128+1)128
model.add(layers.MaxPooling2D((2, 2))) #shape(7,7,128)
model.add(layers.Flatten()) #( ,77*128=6272)
model.add(layers.Dense(512, activation=‘relu’)) # (6272+1)*512
model.add(layers.Dense(1, activation=‘sigmoid’))
#tensorflow2.6之后应该这样写,lr应该变为learning_rate,keras前加tensorflow
from tensorflow.keras import optimizers
model.compile(loss=‘binary_crossentropy’,
optimizer=optimizers.RMSprop(learning_rate=1e-4),
metrics=[‘acc’])
5.2.4 数据预处理
你现在已经知道,将数据输入神经网络之前,应该将数据格式化为经过预处理的浮点数张量。
现在,数据以 JPEG 文件的形式保存在硬盘中,所以数据预处理步骤大致如下。
(1) 读取图像文件。
(2) 将 JPEG 文件解码为 RGB 像素网格。
(3) 将这些像素网格转换为浮点数张量。
(4) 将像素值(0~255 范围内)缩放到 [0, 1] 区间(正如你所知,神经网络喜欢处理较小的输入值)。
这些步骤可能看起来有点吓人,
但幸运的是,Keras 拥有自动完成这些步骤的工具。
关键!!!:Keras 有一个图像处理辅助工具的模块,
位于 keras.preprocessing.image。
它包含 ImageDataGenerator 类. 可以快速创建 Python 生成器,能够
将硬盘上的图像文件自动转换为预处理好的张量批量。
Python 生成器(Python generator)是一个类似于迭代器的对象,一个可以和 for … in 运算符一起使用的对象。
ImageDataGenerator生成器的flow,flow_from_directory用法_冯爽朗的博客-CSDN博客
Keras ImageDataGenerator参数_AI视觉网奇的博客-CSDN博客
from keras.preprocessing.image import ImageDataGenerator
#rescale的作用是对图片的每个像素值均乘上这个放缩因子,这个操作在所有其它变换操作之前执行,train_datagen就是一个python生成器
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
#flow_from_directory(directory): 以文件夹路径为参数,生成经过数据提升/归一化后的数据,在一个无限循环中无限产生batch数据
train_generator = train_datagen.flow_from_directory( #根据文件夹判断分类
#目标文件夹路径,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG、BNP、PPM的图片都会被生成器使用
train_dir,
# 将所有图像的大小调整为 150×150
target_size=(150, 150),
batch_size=20,
# 因为使用了 binary_crossentropy ,所以需要用二进制标签
class_mode=‘binary’)
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode=‘binary’)
classes: 可选参数,为子文件夹的列表,如[‘dogs’,‘cats’]默认为None. 若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。
看一下其中一个生成器的输出:它生成了 150×150 的 RGB 图像
[形状为 (20, 150, 150, 3)]与二进制标签[形状为 (20,)]组成的批量。
每个批量中包含 20 个样本(批量大小),这是因为batch_size=20
注意,生成器会不停地生成这些批量,所以查看需要break
for data_batch, labels_batch in train_generator:
print(‘data batch shape:’, data_batch.shape)
print(‘labels batch shape:’, labels_batch.shape)
break
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)
将使用 fit_generator 方法来拟合,它在数据生成器上的效果和 fit 相同。
第一个参数:是一个 Python 生成器这里就是train_generator
,可以不停地生成输入和目标组成的批量.
因为数据是不断生成的,所以 Keras模型要知道每一轮需要从生成器中抽取多少个样本.
第二个参数: steps_per_epoch
作用:从生成器中抽取 steps_per_epoch 个批量后
(即运行了 steps_per_epoch 次梯度下降),拟合过程将进入下一个轮次。
steps_per_epoch =总数量除以批量大小 ------2000/20=100
使用fit_generator时,可以传入一个validation_data参数,作用和在 fit 方法中类似。
值得注意的是,这个参数可以是一个数据生成器,
但也可以是 Numpy 数组组成的元组。
如果向 validation_data 传入一个生成器如validation_generator,
那么这个生成器能够不停地生成验证数据批量,因此你还需要指定 validation_steps 参数:同step_per_epoch 1000/20=50
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
始终在训练完成后保存模型,这是一种良好实践(习惯)。
model.save(‘cats_and_dogs_small_1.h5’)
补充:model.fit_generator方法即将在未来版本中被移除,要使用model.fit方法来替代。解决Model.fit_generator (from tensorflow.python.keras.engine.training) is deprecated and will be remov_吉野原的博客-CSDN博客
#生成图
import matplotlib.pyplot as plt
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(len(acc)) #从0开始 书上从(1,len(acc)+1)
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, 'r label=‘Validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, 'r, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
可以明显看出训练准确率和训练loss越来越好,但是验证数据在几轮过后开始过拟合
现在我们将使用一种针对于计算机视觉领域的新方法,在用深度学习模型处理图像时几乎都会用到这种方法,它就是数据增强(data augmentation)。
5.2.5数据增强data argument(解决过拟合)
word小技巧(键盘的insert会造成覆盖)
过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。
数据增强是从现有的训练样本中生成更多的训练数据,
其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本。
其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察
到数据的更多内容,从而具有更好的泛化能力。
在 Keras 中,这可以通过对 ImageDataGenerator 实例读取的图像执行多次随机变换来实现。
ImageDataGenerator( #除了之前的rescale 还有ratation
rotation_range: 旋转角度 (在 0~180 范围内)
width_shift_range:水平平移的范围(相对于总宽度或总高度的比例)。
height_shift_range 垂直平移范围
brightness_range: 给定一个含两个float值的list,亮度值取自上下限值间
shear_range: Float. 沿着某个轴将其拉长,缩短 | 随机错切变换的角度
zoom_range: Float or [lower, upper]. 随机放大缩小图片
fill_mode: One of {“constant”, “nearest(默认)”, “reflect” or “wrap”}.
在图片进行变换后,对图片进行填补 这些新像素可能来自于旋转或宽度/高度平移
horizontal_flip: 随机将一半图像水平翻转
vertical_flip:
)
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode=‘nearest’)
注:以上与实现代码无关,只是学习数据增强
这种方法可能不足以完全消除过拟合。为了进一步降低过拟合,
你还需要向模型中添加一个 Dropout 层,添加到密集连接分类器之前。
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=‘relu’,input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation=‘relu’))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5)) #具体运作见上,随机乘0,视为丢弃
model.add(layers.Dense(512, activation=‘relu’))
model.add(layers.Dense(1, activation=‘sigmoid’))
train_datagen = ImageDataGenerator( #变形,对训练数据增强
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
#目标目录
train_dir,
target_size=(150, 150),
batch_size=32,
class_mode=‘binary’)
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode=‘binary’)
history = model.fit_generator(
train_generator,
steps_per_epoch=100, #63 #?这里batch_size是32,为什么是100
#补充:第一版此处有错 根据数据以及csdn博客,应该为2000/32≈63,同理 ,下面的验证step也应该为1000/32≈32
epochs=100,
validation_data=validation_generator,
validation_steps=50 #32)
#把模型保存下来,5.4节将用到它 save下来的model会保存在当前文件下
model.save(‘cats_and_dogs_small_2.h5’)
#再次通过上面的matplotlib方法生成图片
使用了数据增强和 dropout 之后,模型不再过拟合:
训练曲线紧紧跟随着验证曲线。现在的精度为 82%,比未正则化的模型提高了 15%(相对比例)
通过进一步使用正则化方法以及调节网络参数(比如每个卷积层的过滤器个数或网络中的层数),你可以得到更高的精度,可以达到86%或87%。
但只靠从头开始训练自己的卷积神经网络,
再想提高精度就十分困难,因为可用的数据太少。
想要在这个问题上进一步提高精度,
下一步需要使用预训练的模型,这是接下来两节的重点。
5.3 使用预训练的卷积神经网络
预训练网络(pretrained network)是一个保存好的网络,之前已在大型数据集
(通常是大规模图像分类任务)上训练好。
如果这个原始数据集足够大且足够通用,那么预训练网络学到的特征的空间层次结构可以有效地作为视觉世界的通用模型,因此这些特征可用于各种不同的计算机视觉问题,即使这些新问题涉及的类别和原始任务完全不同。
假设有一个在 ImageNet 数据集(140 万张标记图像,1000 个不同的类别)上训练好的大型卷积神经网络。ImageNet 中包含许多动物类别,其中包括不同种类的猫和狗,因此可 以认为它在猫狗分类问题上也能有良好的表现。
我们将使用 VGG16 架构,它由 Karen Simonyan 和 Andrew Zisserman 在 2014 年开发。
你第一次遇到这种奇怪的模型名称——VGG、ResNet、Inception、Inception-ResNet、Xception 等。
使用预训练网络有两种方法:
特征提取(feature extraction)和微调模型(fine-tuning)。
5.3.1 特征提取
特征提取是使用之前网络学到的表示来从新样本中提取出有趣的特征。然后将这些特征输入一个新的分类器,从头开始训练。
第一部分叫作模型的卷积基(convolutional base)。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器。
理解:卷积基里面就是包含之前学的那些conv2D,maxpooling层,现在我们需要把我们的新数据输入到已经训练好的网络的卷积基(所以不能对其进行修改,是frozen冻结),再输出上使用新的分类器(其实还是full connect)
为什么仅重复使用卷积基?我们能否也重复使用密集连接分类器?
一般来说,应该避免这样做。
两个原因:
1分类器学到的表示必然是针对于模型训练的类别,其中仅包含某个类别出现在整张图像中的概率信息。
密集连接层的表示不再包含物体在输入图像中的位置信息。密集连接层舍弃了空间的概念,而物体位置信息仍然由卷积特征图所描述。
如果物体位置对于问题很重要,那么密集连接层的特征在很大程度上是无用的。
2某个卷积层提取的表示的通用性(以及可复用性)取决于该层在模型中的深度。
模型中更靠近底部(就是最先加入的层)的层提取的是局部的、高度通用的特征图(比如视觉边缘、颜色和纹理),而更靠近顶部的层(后加入的层)提取的是更加抽象的概念(比如“猫耳朵”或“狗眼睛”)。回想那张图
因此,如果你的新数据集与原始模型训练的数据集有很大差异,那么最好只使用模型的前几层来做特征提取,而不是使用整个卷积基。
这里其实imagenet类别包含多种猫狗,对辨识猫狗可能很有帮助,重复使用原始模型密集连接层中所包含的信息可能很有用,
但我们选择不这么做,以便涵盖新问题的类别与原始模型的类别不一致的更一般情况。
VGG16 等模型内置于 Keras 中。你可以从 keras.applications 模块中导入。
应用 Applications - Keras 中文文档
#from keras.applications import VGG16
#错误标注:Keras 内置在最新的 TF 框架中,因此
from tensorflow.keras.applications import VGG16
conv_base = VGG16(weights=‘imagenet’,
include_top=False,
input_shape=(150, 150, 3))
这里向构造函数中传入了三个参数。
‰ weights 指定模型初始化的权重检查点。 也就是保留imagenet训练网络参数
‰ include_top 指定模型最后是否包含密集连接分类器。
默认情况下,这个密集连接分类器对应于 ImageNet 的 1000 个类别。
因为我们打算使用自己的密集连接分类器(只有两个类别:cat 和 dog),
所以不需要包含它。
‰ input_shape 是输入到网络中的图像张量的形状。这个参数完全是可选的,如果不传入这个参数,那么网络能够处理任意形状的输入。
from tensorflow.keras.utils import plot_model
plot_model(conv_base,show_shapes=True, to_file=‘VGG16.png’)
通过图片更好理解模型
最后的特征图形状为 (4, 4, 512)。我们将在这个特征上添加一个密集连接分类器。
接下来,下一步有两种方法可供选择。
‰一:在你的数据集上运行卷积基,将输出保存成硬盘中的 Numpy 数组,然后用这个数据作为输入,输入到独立的密集连接分类器中(与本书第一部分介绍的分类器类似)。这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但出于同样的原因,这种方法不允许你使用数据增强。
补充:深度学习笔记:为什么(预)特征提取不能与数据增强结合使用_笨牛慢耕的博客-CSDN博客
理解:将自己的数据输入别人训练好的模型,获得包含空间特征的数据,再接上自己的分类器,
‰ 二:在顶部添加 Dense 层来扩展已有模型(即 conv_base),
并在输入数据上端到端地运行整个模型。
这样你可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基。
但出于同样的原因,这种方法的计算代价比第一种要高很多。
理解:就是将自己的dense层接上别人的模型,将其看做一个模型,直接从头到尾跑一遍
博客:
方法一:用预训练模型的卷积基对数据集进行处理生成原数据集的特征,我称之为预特征提取。然后,基于预提取的特征训练最后的密集连接层分类器。
方法二:在卷积基的基础上进行扩展,追加最终的密集连接分类器。然后在冻结卷积基的系数的条件下基于数据增强技术对整个模型进行训练。由于卷积基被冻结,其系数没有更新,所以卷积基的作用也仅仅是用于特征提取,但是它是在线(on-the-fly)进行的,所以我称之为在线特征提取,以区别于上面的预特征提取。
首先来看第一种方法的代码:保存你的数据在 conv_base 中的输出,然后将这些输出作为输入用于新模型。
ImageDataGenerator生成器的flow,flow_from_directory用法_冯爽朗的博客-CSDN博客
base_dir = r’D:\softwareprocess\Downloads\cats_and_dogs_small’
train_dir = os.path.join(base_dir, ‘train’)
validation_dir = os.path.join(base_dir, ‘validation’)
test_dir = os.path.join(base_dir, ‘test’)
datagen = ImageDataGenerator(rescale=1./255) #不做数据增强,只变形
batch_size = 20
def extract_features(directory, sample_count): #direct 表示路径位置,sam_count表示个数
features = np.zeros(shape=(sample_count, 4, 4, 512)) #开一个相应大小的全0四维张量
labels = np.zeros(shape=(sample_count)) #label为0,1一维
generator = datagen.flow_from_directory( #flow_from会根据有多少子文件夹判
断有少分类 默认的classes参数的作用
directory, #获取目标目录中图片
target_size=(150, 150), #所以图片调为150*150
batch_size=batch_size, #每批产生个数
class_mode=‘binary’ )
#生成器无限生成data_batch和label_batch
每次生成20个
i = 0
for inputs_batch, labels_batch in generator: #分批,每次20张通过预测获得特征,输入feature 张量
features_batch = conv_base.predict(inputs_batch) #.predict利用输入数据直接预测也就是将conv_base预测结果赋给feature
features[i * batch_size : (i + 1) * batch_size] = features_batch #通过循环,输入1000张
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i += 1
if i * batch_size >= sample_count: #读取完所有图片 i超过99, break
break
return features, labels
#总的理解,定义两个合适大小的容器,feature和labels,首先通过生成器将图片转成需要的浮点型张量,本来是无限生成,这里在遍历完2000张后break。
再把这些生成的inputs_batch, labels_batch通过VGG16也就是conv_base的predict预测出结果,放入feature
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)
目前,提取的特征形状为 (samples, 4, 4, 512)。我们要将其输入到密集连接分类器中,
所以首先必须将其形状展平为 (samples, 8192)。
现在你可以定义你的密集连接分类器(注意要使用 dropout 正则化),并在刚刚
保存的数据和标签上训练这个分类器。
from keras import models
from keras import layers
#from keras import optimizers
#此处应该改为
from tensorflow import optimizers
#放进自己的分类器 注意要使用 dropout 正则化
model = models.Sequential()
model.add(layers.Dense(256, activation=‘relu’, input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation=‘sigmoid’)) #只有两种结果,设1
model.compile(optimizer=optimizers.RMSprop(learning_rate=2e-5), #预设了值,所以要 import optimizers
loss=‘binary_crossentropy’,
metrics=[‘acc’])
history = model.fit(train_features, train_labels,
epochs=30,
batch_size=20,
validation_data=(validation_features, validation_labels))
画图代码同上
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(1, len(acc) + 1)
接下来看最终结果,准确率已经达到90%,
没有数据增强,所以后面容易过拟合
from keras import models
from keras import layers
model = models.Sequential()
model.add(conv_base) #第一层就可以将VGG16(经过处理)加入,记得输出是(4,4,512)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation=‘relu’))
model.add(layers.Dense(1, activation=‘sigmoid’))
现在模型的架构如下所示
model.summary()
Model: “sequential_1”
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0
VGG16 的卷积基有14714 688 个参数,非常多。在其上添加的分类器有 200 万个参数。
在编译和训练模型之前,一定要“冻结”卷积基。
冻结(freeze)一个或多个层是指在训练过程中保持其权重不变。
如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。
因为其上添加的 Dense 层是随机初始化的,所以非常大的权重更新将会在网络中传播。
(深度学习中的参数是通过计算梯度,再反向传播进行更新的,固定其中的某些层的参数不参与反向传播,权重就不会更改了)
在 Keras 中,冻结网络的方法是将其 trainable 属性设为 False。
print('This is the number of trainable weights ’
‘before freezing the conv base:’, len(model.trainable_weights))
This is the number of trainable weights before freezing the conv base: 30
#print('This is the number of trainable weights ’
‘after freezing the conv base:’, len(model.trainable_weights))
#会有4个,conv_base 已经冻结,只有自己的两层dense,每层两个会训练
如此设置之后,只有添加的两个 Dense 层的权重才会被训练。
总共有 4 个权重张量,每层 2 个(主权重矩阵w和偏置向量b)。
之后的操作与之前从头训练网络类似
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode=‘nearest’)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir, #目标文件夹路径,对于每一个类,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG的图片都会被生成器使用
# All images will be resized to 150x150
target_size=(150, 150),
batch_size=20,
# Since we use binary_crossentropy loss, we need binary labels
class_mode=‘binary’)
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode=‘binary’)
model.compile(loss=‘binary_crossentropy’,
optimizer=optimizers.RMSprop(lr=2e-5),
metrics=[‘acc’])
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50,
verbose=2)
自我描述下整体过程:
首先是构建想要的模型,在这就是使用VGG16加上自己的Dense,模型建好后,这里有独特的一不,冻结VGG16的卷积基。
然后,获取数据,由于数据不足,还使用了数据增强,使用生成器,其会自动的,批量的将图片转变成浮点型张量,数据有了,编译模型,写好loss和optimizer,metrics。
最后,在编译好的模型上,运行训练数据(由训练生成器生成),使用验证数据验证(由验证生成器生成)
深度学习笔记:使用预训练模型之在线特征提取+数据增强_deep learning 特征提取_笨牛慢耕的博客-CSDN博客
观看最终结果,作者说正确率会达到96%,但我和视频结果都只是90%.
第二次复习:终于找到了相关博客(重点关注此作者相关博客,有很多有用信息),90%并没有问题,是作者的乌龙
但是由于数据增强,过拟合明显下降了
5.3.2 微调模型(与特征提取互为补充)
另一种广泛使用的模型复用方法是模型微调(fine-tuning),与特征提取互为补充。
对于用于特征提取的冻结的模型基,微调是指将其顶部的几层“解冻”,并将这解冻的几层和新增加的部分(本例中是全连接分类器)联合训练。
关键理解!!!:新增加的部分也需要微调。
也就是说,我们自己加的Dense,第一次训练在卷积基全冻结时训练一次,在卷积基最顶部层解冻后,Dense层又要训练一次。
解冻原因理解:根据5.3.1中某个卷积层提取的表示的通用性(以及可复用性)取决于该层在模型中的深度,越底层越通用,说明最顶部的几层其实是不具备很强的通用性,将其设置成更适合我们的训练层,以便得到更好的结果。
之所以叫作微调,是因为它只是略微调整了所复用模型中更加抽象的表示,以便让这些表示与手头的问题更加相关。
冻结 VGG16 的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,
只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。
如果分类器没有训练好,那么训练期间通过网络传播的误差信号会特别大,
微调的几层之前学到的表示都会被破坏。
因此,微调网络的步骤如下。
(1) 在已经训练好的基网络(base network)上添加自定义网络。
(2) 冻结基网络。
(3) 训练所添加的部分。
---------------------------------前三步我们在使用数据增强的特征提取中,已经完成了
(4) 解冻基网络的一些层。
(5) 联合训练解冻的这些层和添加的部分
我们现在就需要完成4,5步
关键理解:必须要先将我们自己的全连接分类器先训练好,再去解冻最顶部层,否则提前解冻,在训练过程中由于反向传播,顶层预先设好的参数就不会是微调,而是大幅变动,使得结果不理想。
我们将微调最后三个卷积层,也就是说,直到 block4_pool 的所有层都应该被冻结,而 block5_conv1、block5_conv2 和 block5_conv3 三层应该是可训练的。
为什么不微调更多层?为什么不微调整个卷积基?你当然可以这么做,但需要考虑以下几点。
1—卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用途。微调更靠底部的层,得到的回报会更少。
2—训练的参数越多,过拟合的风险越大。卷积基有 1500 万个参数,所以在你的小型数据集上训练这么多参数是有风险的。
#keras是某一层解冻,之后的层都会解冻
from tensorflow import optimizers
conv_base.trainable = True #先全部解冻
set_trainable = False #定义一个参数,先为false
for layer in conv_base.layers:
if layer.name == ‘block5_conv1’: #只要层名为block5,设它可以训练
再由于keras的规则,一层解冻,之后所有层都解冻
set_trainable = True
if set_trainable: #如果该参数为true,该层解冻
layer.trainable = True
else:
layer.trainable = False
len(model.trainable_weights)
10 , block5有3层conv2D,加上两层Dense 一共10个w和b
model.compile(loss=‘binary_crossentropy’,
optimizer=optimizers.RMSprop(lr=1e-5), #默认为1e-3,学习率很小,是希望对微调的三层表示,其变化范围不要太大。
metrics=[‘acc’])
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100, #第二次复习发现,书里是100epochs,图是30epochs
validation_data=validation_generator,
validation_steps=50)
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(len(acc))
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, ‘r’, label=‘Validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘r’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
为了让图像更具可读性,你可以将每个损失和精度都替换为指数移动平均值(EMA),从而让曲线变得平滑。
注意:在波士顿房价预测已使用过
此方法叫做EMA(exponential Moving Average)-指数移动平均
比如有 a,b,c ,现在对它们重新赋值,
第一个a不变,b=0.9a+0.1b(转换前),c=0.9*b(转换后)+0.1c (转换前)
def smooth_curve(points, factor=0.8):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
plt.plot(epochs, smooth_curve(acc), ‘bo’, label=‘Smoothed training acc’)
plt.plot(epochs,smooth_curve(val_acc), ‘r’, label=‘Smoothed validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs,
smooth_curve(loss), ‘bo’, label=‘Smoothed training loss’)
plt.plot(epochs,
smooth_curve(val_loss), ‘r’, label=‘Smoothed validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
注意,从损失曲线上看不出与之前相比有任何真正的提高(实际上还在变差)。你可能感到奇怪,如果损失没有降低,那么精度怎么能保持稳定或提高呢?
答案很简单:图中展示的是逐点(pointwise)损失值的平均值,
但影响精度的是损失值的分布,而不是平均值,因为精度是模型预测的类别概率的二进制阈值。即使从平均损失中无法看出,但模型也仍然可能在改进。
在测试数据上最终评估这个模型。----------书上有97%
test_generator = test_datagen.flow_from_directory(
test_dir,
target_size=(150, 150),
batch_size=20,
class_mode=‘binary’)
#直接使用evaluate获得结果,因为test也是生成的,所以也有_generator
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print(‘test acc:’, test_acc)
model.fit( )函数:训练模型_方如一的博客-CSDN博客
回调函数 Callbacks - Keras 中文文档
callback 在每个training/epoch/batch结束时,可以通过回调函数Callbacks查看一些内部信息。
常用的callback有EarlyStopping,当监视的变量停止改善时,
停止训练,防止模型过拟合,其默认参数如下:
callback=callbacks.EarlyStopping(monitor=‘loss’,min_delta=0.002,patience=0,mode=‘auto’,restore_best_weights=False)
——————————————
#加载所有库
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import models
from tensorflow.keras import utils
import numpy as np
import matplotlib.pyplot as plt
import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications.imagenet_utils import preprocess_input
from PIL import Image
from tensorflow.keras import optimizers
print(tf.version)
5.3.3 小结
下面是你应该从以上两节的练习中学到的要点。
‰ 卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上也可以从头开始训练一个卷积神经网络,而且得到的结果还不错。
‰ 在小型数据集上的主要问题是过拟合。在处理图像数据时,数据增强是一种降低过拟合的强大方法。
‰ 利用特征提取,可以很容易将现有的卷积神经网络复用于新的数据集。对于小型图像数据集,这是一种很有价值的方法。
‰ 作为特征提取的补充,你还可以使用微调,将现有模型之前学到的一些数据表示应用于新问题。这种方法可以进一步提高模型性能。
现在你已经拥有一套可靠的工具来处理图像分类问题,特别是对于小型数据集。
5.4 卷积神经网络的可视化(完全不懂,以后再来)
《Python深度学习》第五章-4(可视化中间激活层)读书笔记_激活层可视化-CSDN博客
人们常说,深度学习模型是“黑盒”,即模型学到的表示很难用人类可以理解的方式来提取和呈现。部分正确。
但卷积神经网络学到的表示非常适合可视化,很大程度上是因为它们是视觉概念
的表示。
介绍三种最容易理解也最有用的方法。
‰ 可视化卷积神经网络的中间输出(中间激活):有助于理解卷积神经网络连续的层如何对输入进行变换,也有助于初步了解卷积神经网络每个过滤器的含义。
理解:就是将模型中一层层的图片输入输出结果拿出来看,看看到底变成了什么样子
‰ 可视化卷积神经网络的过滤器:有助于精确理解卷积神经网络中每个过滤器容易接受的视觉模式或视觉概念。
‰ 可视化图像中类激活的热力图:有助于理解图像的哪个部分被识别为属于某个类别,从而可以定位图像中的物体。
理解:就是图片中哪部分使得电脑认为是属于这种动物
对于第一种方法(即激活的可视化),我们将使用 5.2 节在
猫狗分类问题上从头开始训练的小型卷积神经网络。
对于另外两种可视化方法,我们将使用 5.3 节介绍的 VGG16 模型。
5.4.1 可视化中间激活
可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。
希望在三个维度对特征图进行可视化:宽度、高度和深度(通道)。
每个通道都对应相对独立的特征,所以
将这些特征图可视化的正确方法是将每个通道的内容分别绘制成二维图像。
补充“”keras保存和载入模型继续训练_model = load_model-CSDN博客
关于compile和load_model()的使用顺序
这一段落主要是为了解决我们fit、evaluate、predict之前还是之后使用compile。想要弄明白,首先我们要清楚compile在程序中是做什么的?都做了什么?
compile做什么?
compile定义了loss function损失函数、optimizer优化器和metrics度量。它与权重无关.也就是说compile并不会影响权重,不会影响之前训练的问题。
如果我们要训练模型或者评估模型evaluate,则需要compile,因为训练要使用损失函数和优化器,评估要使用度量方法;
如果我们要预测,则没有必要compile模型。
#加载已经存在的模型
from keras.models import load_model
conv2d_12 (Conv2D) (None, 148, 148, 32) 896
max_pooling2d_12 (MaxPoolin (None, 74, 74, 32) 0
g2D)
conv2d_13 (Conv2D) (None, 72, 72, 64) 18496
max_pooling2d_13 (MaxPoolin (None, 36, 36, 64) 0
g2D)
conv2d_14 (Conv2D) (None, 34, 34, 128) 73856
max_pooling2d_14 (MaxPoolin (None, 17, 17, 128) 0
g2D)
conv2d_15 (Conv2D) (None, 15, 15, 128) 147584
max_pooling2d_15 (MaxPoolin (None, 7, 7, 128) 0
g2D)
flatten_3 (Flatten) (None, 6272) 0
dropout_2 (Dropout) (None, 6272) 0
dense_6 (Dense) (None, 512) 3211776
dense_7 (Dense) (None, 1) 513
#接下来,我们需要一张输入图像,即一张猫的图像,它不属于网络的训练图像。
img_path=r’D:\softwareprocess\Downloads\cats_and_dogs_small\test\cats\cat.1700.jpg’
from keras.preprocessing import image
import numpy as np
img = image.load_img(img_path, target_size=(150, 150)) #读取图片
img_tensor = image.img_to_array(img) #将图片变为3D张量
img_tensor = np.expand_dims(img_tensor, axis=0) #输入网络必须要4D,加维度
img_tensor /= 255. # 缩到0-1 训练模型的输入数据都用这种方法预处理
(之前是用生成器生成,原理相同)
print(img_tensor.shape)
#我们来显示这张图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0]) #只有一张
plt.show()
为了提取想要查看的特征图,我们需要创建一个 Keras 模型,以图像批量作为输入,并输出所有卷积层和池化层的激活。
为此,我们需要使用 Keras 的 Model (M大写)类。
Model模型有多个输出(此处为8,因为有8层outer_shape,从summary中可以看到),这一点与 Sequential 模型不同。
Sequential 模型将特定输入映射为特定输出。
from keras import models
layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
输入一张图像,这个模型将返回原始模型(即从头开始训练模型的)
前 8 层的激活值。
这是你在本书中第一次遇到的多输出模型,之前的模型都是只有一个输入和一个输出。这个模型有一个输入和 8 个输出,即每层激活对应一个输出。
activations = activation_model.predict(img_tensor)
#activations中现在有8层激活对应的8个numpy数组,查看第一个,补充第二个
first_layer_activation = activations[0]
#tow_layer_activation = activations[1]
print(first_layer_activation.shape,tow_layer_activation)
(1, 148, 148, 32) (1, 74, 74, 32)
它是大小为 148×148 的特征图,有 32 个通道。
我们来绘制原始模型第一层激活的第 4 个通道
补充:这里有问题,channel经过实验也是从0-31,第四个应该是3,
import matplotlib.pyplot as plt
#维度0第一个,::代表行列全部包含,4代表第四个channel,cmap颜色
plt.matshow(first_layer_activation[0, :, :, 3], cmap=‘viridis’)
plt.show()
注意,你的通道可能与此不同,因为卷积层学到的过滤器并不是确定的。
下面我们来绘制网络中所有激活的完整可视化(见图 5-27)。
我们需要在 8 个特征图中的每一个中提取并绘制每一个通道,然后将结果叠加在一个大的图像张量中,按通道并排。
layer_names = []
for layer in model.layers[:8]:
layer_names.append(layer.name)
images_per_row = 16 #y每行16张图
for layer_name, layer_activation in zip(layer_names, activations):
#包含最后一个维度即滤镜32,32… 64,64 128,128
n_features = layer_activation.shape[-1]
# 特征图的形状为 (1, size, size, n_features) 148 74
size = layer_activation.shape[1]
#滤镜数整除每行16张图数 获得行数
n_cols = n_features // images_per_row
display_grid = np.zeros((size * n_cols, images_per_row * size))
# n_cols 值为:0,1 … images 即row =0-15
for col in range(n_cols):
for row in range(images_per_row):
#填入所有图片
channel_image = layer_activation[0, : , : , col * images_per_row + row]
# 对特征进行处理,更美观,标准化 减平均,除以标准差
#再乘64 ,+128 使其范围在0-255也就是RGB范围内
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
#clip作用:低于0视作0,高于255试作255
channel_image = np.clip(channel_image, 0, 255).astype('uint8')
#美化后放入全0矩阵
display_grid[col * size : (col + 1) * size,
row * size : (row + 1) * size] = channel_image
# plt.figure 中figsize: 画布大小,宽*高 不是通常的0轴为y轴
scale = 1. / size
plt.figure(figsize=(scale * display_grid.shape[1],
scale * display_grid.shape[0]))
plt.title(layer_name)
plt.grid(False) #不要横格线
plt.imshow(display_grid, aspect='auto', cmap='viridis')
plt.show()
#下面为程序分解
第一层
这里需要注意以下几点。
‰ 第一层是各种边缘探测器的集合。在这一阶段,激活几乎保留了原始图像中的所有信息。
‰ 随着层数的加深,激活变得越来越抽象,并且越来越难以直观地理解。
它们开始表示更高层次的概念,比如“猫耳朵”和“猫眼睛”。
层数越深,其表示中关于图像视觉内容的信息就越少,而关于类别的信息就越多。
‰ 激活的稀疏度(sparsity)随着层数的加深而增大。在第一层里,所有过滤器都被输入图像激活,但在后面的层里,越来越多的过滤器是空白的。也就是说,输入图像中找不到
这些过滤器所编码的模式。
我们刚刚揭示了深度神经网络学到的表示的一个重要普遍特征:
随着层数的加深,层所提取的特征变得越来越抽象。更高的层激活包含关于特定输入的信息越来越少,而关于目标的 信息越来越多(本例中即图像的类别:猫或狗)。
这与人类和动物感知世界的方式类似:人类观察一个场景几秒钟后,可以记住其中有哪些
抽象物体(比如自行车、树),但记不住这些物体的具体外观。
5.4.2 可视化卷积神经网络的过滤器
补充:《Python深度学习》第五章-5(可视化过滤器)读书笔记_python alexnet 可视化过滤器_Paul-Huang的博客-CSDN博客
想要观察卷积神经网络学到的过滤器,另一种简单的方法是显示每个过滤器所响应的视觉模式。
这可以通过在输入空间中进行梯度上升来实现:从空白输入图像开始,将梯度下降应用于卷积神经网络输入图像的值,其目的是让某个过滤器的响应最大化。
我们需要构建一个损失函数,其目的是让某个卷积层的某个过滤器的值最大化;然后,我们要使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。
例如, 对于在ImageNet上预训练的VGG16网络,其block3_conv1层第0个过滤器激活的损失如下所示。
from tensorflow.keras.applications import VGG16
from keras import backend as K
model = VGG16(weights=‘imagenet’,
include_top=False)
layer_name = ‘block3_conv1’
filter_index = 0 #看第0个滤镜
layer_output = model.get_layer(layer_name).output #得到output
loss = K.mean(layer_output[:, :, :, filter_index]) #前三个维度全取,再取第0个filter
将这个滤镜(值是二维张量)取mean
为了实现梯度下降,我们需要得到损失相对于模型输入的梯度。为此,我们需要使用 Keras的 backend 模块内置的 gradients 函数。
注:相当于作微分,前对后作微分,只不过这次input是图片
#以下两行必须加上,否则在下面梯度下降时报错
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
grads = K.gradients(loss, model.input)[0]
#结果可以看出就是一个张量 虽然是多维,但就只有一个,所以【0】
为了让梯度下降过程顺利进行,一个非显而易见的技巧是
将梯度张量除以其L2范数(张量中所有值的平方的平均值的平方根)来标准化。
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
现在你需要一种方法:给定输入图像,它能够计算损失张量和梯度张量的值。
你可以定义一个Keras 后端函数来实现此方法:
iterate 是一个函数,它将一个 Numpy 张量(表示为长度为1的张量列表)转换为两个 Numpy 张量组成的列表,这两个张量分别是损失值和梯度值。
iterate = K.function([model.input], [loss, grads])
import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.
step = 1. #梯度更新的步长
for i in range(40):
# 计算损失和梯度
loss_value, grads_value = iterate([input_img_data])
# 最大化loss ,也就是梯度提升
input_img_data += grads_value * step
得到的图像张量是形状为 (1, 150, 150, 3) 的浮点数张量,其取值可能不是 [0, 255] 区间内的整数。因此,你需要对这个张量进行后处理,将其转换为可显示的图像。
def deprocess_image(x):
# 使其均值为0,标准差为0.1
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
#利用 clip,剪裁到0-1
x += 0.5
x = np.clip(x, 0, 1)
# 乘以255,变为rgb范围
x *= 255
x = np.clip(x, 0, 255).astype('uint8')
return x
将上述代码片段放到一个Python函数中,输入一个层的名称和一个过滤器索引,
它将返回一个有效的图像张量,表示能够将特定过滤器的激活最大化的模式。
def generate_pattern(layer_name, filter_index, size=150):
# 构建一个损失函数,将该层第 n 个过滤器的激活最大化
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
# 计算loss0相对于输入图片的微分
grads = K.gradients(loss, model.input)[0]
# 梯度标准化
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
# 此函数返回输入图像的loss和梯度
iterate = K.function([model.input], [loss, grads])
# 设初始值,即有噪声的灰度图像
input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
# 梯度上升40次
step = 1.
for i in range(40):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
img = input_img_data[0]
return deprocess_image(img)
查看结果
plt.imshow(generate_pattern(‘block3_conv1’, 0))
plt.show() #1
卷积神经网络中的过滤器变得越来越复杂,越来越精细。
‰ 模型第一层(block1_conv1)的过滤器对应简单的方向边缘和颜色(还有一些是彩色边缘)。
‰ block2_conv1 层的过滤器对应边缘和颜色组合而成的简单纹理。
‰ 更高层的过滤器类似于自然图像中的纹理:羽毛、眼睛、树叶等。
5.4.3 可视化类激活的热力图
《Python深度学习》第五章-6(可视化类激活图)读书笔记_activation map_Paul-Huang的博客-CSDN博客
有助于了解一张图像的哪一部分让卷积神经网络做出了最终的分类决策。
有助于对卷积神经网络的决策过程进行调试。
这种通用的技术叫作类激活图(CAM,class activation map)可视化,它是指对输入图像生成类激活的热力图。
类激活热力图是与特定输出类别相关的二维分数网格,对任何输入图像的
每个位置都要进行计算,它表示每个位置对该类别的重要程度。
(出来的图像中的红色部分就表示它的重要性最重,让网络据此做出了判断)
方法非常简单:给定一张输入图像,对于一个卷积层的输出特征图,
用类别相对于通道的梯度对这个特征图中的每个通道进行加权。
5.4.3.4 类激活热力图的代码
步骤:
指 定 入 − − > 定 损 数 − − > 求 梯 度 − − > 乘 梯 度 − − > 染 画 布 − − > 可 视 化
1指 定 入
from keras.applications.vgg16 import VGG16
#注意,网络中包括了密集连接分类器。在前面
所有的例子中,我们都舍弃了这个分类器include_top = False
K.clear_session()
model = VGG16(weights=‘imagenet’)
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input, decode_predictions
import numpy as np
img_path = r’D:\softwareprocess\Downloads\creative_commons_elephant.jpg’
img = image.load_img(img_path, target_size=(224, 224))
#一转
x = image.img_to_array(img)
#二扩,增加维度变为 (1, 224, 224, 3)
x = np.expand_dims(x, axis=0)
#对批量进行预处理(按通道进行颜色标准化)
#将RGB变为BGR,再减去一个BGR的mean
x = preprocess_input(x)
#现在你可以在图像上运行预训练的 VGG16 网络,并将其预测向量解码为人类可读的格式。
preds = model.predict(x)
print(‘Predicted:’, decode_predictions(preds, top=3)[0])
Predicted: [(‘n02504458’, ‘African_elephant’, 0.84456015), (‘n01871265’, ‘tusker’, 0.14553268), (‘n02504013’, ‘Indian_elephant’, 0.009609564)]
#预测向量中被最大激活的元素是对应“非洲象”类别的元素,索引编号为 386。
np.argmax(preds[0])
2求 梯 度
————————————————
#预测向量中的“非洲象”元素
african_elephant_output = model.output[:, 386]
#block5_conv3 层的输出特征图,它是 VGG16 的最后一个卷积层
last_conv_layer = model.get_layer(‘block5_conv3’)
#“非洲象”类别相对于 block5_conv3 输出特征图的梯度,也就是做微分
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
#对每个轴都做平均 结果是形状为 (512,) 的向量,每个元素是特定特征图通道的梯度平均大小
pooled_grads = K.mean(grads, axis=(0, 1, 2))
#建立模型输出、最后一个卷积层激活输出、梯度均值三者之间的函数关系
#访问刚刚定义的量:对于给定的样本图像, pooled_grads 和 block5_conv3 层的输出特征图
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([x])
3乘 梯 度
————————————————
#将特征图数组的每个通道乘以“这个通道 对‘大象’类别的重要程度”
for i in range(512):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
#得到的特征图的逐通道平均值即为类激活的热力图
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
4染 画 布 、 可 视 化
最后,我们可以用 OpenCV (需要下包)来生成一张图像
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap)
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img
cv2.imwrite(r’ D:\softwareprocess\Downloads\elephant_cam.jpg’, superimposed_img)
这种可视化方法回答了两个重要问题:
‰ 网络为什么会认为这张图像中包含一头非洲象?
‰ 非洲象在图像中的什么位置?
通过梯度类激活热力图可以看到“非洲象”分类决策依据主要来自于图片中象的鼻部、嘴部、眼部、耳部等面部区域。
6深度学习用于文本和序列RNN
本章将介绍使用深度学习模型处理文本(可以将其理解为单词序列或字符序列)、时间序列和一般的序列数据。
用于处理序列的两种基本的深度学习算法分别是
循环神经网络(recurrent neural network)和一维卷积神经网络(1D convnet)
重点讨论两个小任务:一个是 IMDB 数据集的情感分析,这个任务前面介绍过;
另一个是温度预测。
6.1 处理文本数据
文本是最常用的序列数据之一,可以理解为字符序列或单词序列,
但最常见的是单词级处理。
后面几节介绍的深度学习序列处理模型都可以根据文本生成基本形式的自然语言理解,并可用于文档分类、情感分析.
深度学习用于自然语言处理是将模式识别应用于单词、句子和段落,
这与计算机视觉是将模式识别应用于像素大致相同。
同样的,深度学习模型不会接收原始文本作为输入,它只能处理数值张量。
文本向量化(vectorize)是指将文本转换为数值张量的过程。它有多种实现方法。
‰ 将文本分割为单词,并将每个单词转换为一个向量。
‰ 将文本分割为字符,并将每个字符转换为一个向量。
‰ 提取单词或字符的 n-gram,并将每个 n-gram 转换为一个向量。
将文本分解而成的单元(单词、字符或 n-gram)叫作标记(token),
将文本分解成标记的过程叫作分词(tokenization)。
所有文本向量化过程都是应用某种分词方案,然后将数值向量与生成的标记相关联。这些向量组合成序列张量,被输入到深度神经网络中(见图 6-1)。
将向量与标记相关联的方法有很多种。
本节将介绍两种主要方法:
对标记做 one-hot 编码(one-hot encoding)与
标记嵌入[token embedding,通常只用于单词,叫作词嵌入(word embedding)]
理解 n-gram 和词袋
n-gram 是从一个句子中提取的 N 个(或更少)连续单词(或字符)的集合。
下面来看一个简单的例子。考虑句子“The cat sat on the mat.”(“猫坐在垫子上”)。
它 可以被分解为以下二元语法(2-grams)的集合。
{“The”, “The cat”, “cat”, “cat sat”, “sat”,
“sat on”, “on”, “on the”, “the”, “the mat”, “mat”}
这个句子也可以被分解为以下三元语法(3-grams)的集合。
{“The”, “The cat”, “cat”, “cat sat”, “The cat sat”,
“sat”, “sat on”, “on”, “cat sat on”, “on the”, “the”,
“sat on the”, “the mat”, “mat”, “on the mat”}
这样的集合分别叫作二元语法袋(bag-of-2-grams)及三元语法袋(bag-of-3-grams)。
注意:并不会考虑他们的顺序,也就是不是list,或序列,而是集合
n-gram 是多个连续单词或字符的集合(n-gram 之间可重叠)。
注:基本上是一个特征工程,所以现在比较少用,本书深度学习中其实很少提到
n-gram 是一种特征工程,深度学习不需要这种死板而又不稳定的方法,但一定要记住,在使用轻量级的浅层文本处理模型时(比如 logistic 回归和随机森林),n-gram 是一种功能强大、不可或缺的特征工程工具。
6.1.1 单词和字符的 one-hot 编码
one-hot 编码是将标记转换为向量的最常用、最基本的方法。
它将每个单词与一个唯一的整数索引相关联, 然后将这个整数索引 i 转换为长度为 N 的二进制向量(N 是词表大小),
这个向量只有第 i 个元素是 1,其余元素都为 0。
理解:就是将一些文本变成一整个array,其中一个单词或者字符就是一个list,只有某个元素是1,其余全是0
单词级的 one-hot 编码(简单示例)
import numpy as np
samples = [‘The cat sat on the mat.’, ‘The dog ate my homework.’]
token_index = {}
#从list中获得每个元素,准备对其附上索引
for sample in samples:
#再将每个元素的中的每个单词取出,split()括号中是分割的符号,没有就按空格分
for word in sample.split():
#如果这个单词不在token_index里
if word not in token_index:
#就将此word编上唯一的索引 注意没有0,从1开始
token_index[word] = len(token_index) + 1
max_length = 10
results = np.zeros((len(samples), max_length, max(token_index.values()) + 1))
#用enumerate给每个sample编码 i=0,1,。。。
for i, sample in enumerate(samples):
#先以空格分割成单词,再给每个单词编码j=0,1,2,先转成list再切片前10个
for j, word in list(enumerate(sample.split()))[:max_length]:
#通过word得index
index = token_index.get(word)
results[i, j, index] = 1
字符级的 one-hot 编码(简单示例)
import string
samples = [‘The cat sat on the mat.’, ‘The dog ate my homework.’]
characters = string.printable # characters就包含所有的ASCII字符 长度=100
token_index = dict(zip(characters, range(1, len(characters) + 1)))
#每句只看前50个字符
max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.values()) + 1))
for i, sample in enumerate(samples):
for j, character in enumerate(sample[:max_length]):
index = token_index.get(character)
results[i, j, index] = 1.
文本预处理 - Keras 中文文档tokenizer
序列预处理 - Keras 中文文档pad_sequence
Keras 的内置函数
可以对原始文本数据进行单词级或字符级的 one-hot 编码。
你应该使用这些函数,因为它们实现了许多重要的特性,比如从字符串中去除特殊字符、只考虑数据集中前 N 个最常见的单词
(这是一种常用的限制,以避免处理非常大的输入向量空间)。
from keras.preprocessing.text import Tokenizer ##tokenizer 即分词
samples = [‘The cat sat on the mat.’, ‘The dog ate my homework.’]
tokenizer = Tokenizer(num_words=1000)
#给单词编码,建立索引根据text创建一个词汇表。其顺序依照词汇在文本中出现的频率。
tokenizer.fit_on_texts(samples)
##fit_on_texts后有两个有用的输出:word_counts:词频统计结果
##word_index:词和index的对应关系
print(tokenizer.word_counts)
print(tokenizer.word_index)
OrderedDict([(‘the’, 3), (‘cat’, 1), (‘sat’, 1), (‘on’, 1), (‘mat’, 1), (‘dog’, 1), (‘ate’, 1), (‘my’, 1), (‘homework’, 1)])
{‘the’: 1, ‘cat’: 2, ‘sat’: 3, ‘on’: 4, ‘mat’: 5, ‘dog’: 6, ‘ate’: 7, ‘my’: 8, ‘homework’: 9}
sequences = tokenizer.texts_to_sequences(samples)
one_hot_results = tokenizer.texts_to_matrix(samples, mode=‘binary’)
word_index = tokenizer.word_index
print(‘Found %s unique tokens.’ % len(word_index)) //占位
one-hot 编码的一种变体是所谓的 one-hot 散列技巧(one-hot hashing trick)
这种方法没有为每个单词显式分配一个索引并将这些索引保存在一个字典中,
而是将单词散列编码为固定长度的向量,
主要优点在于,它避免了维护一个显式的单词索引,从而节省内存并允许数据的在线编码
有一个缺点,就是可能会出现散列冲突
理解:原理可能就像是散列表一样,不同单词可能会出现相同的散列值
samples = [‘The cat sat on the mat.’, ‘The dog ate my homework.’]
#将单词保存为长度为 1000 的向量。如果单词数量接近 1000 个(或更多)
#那么会遇到很多散列冲突,这会降低这种编码方法的准确性
dimensionality = 1000
max_length = 10
results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
# 将单词散列为 0~1000 范围内的一个随机整数索引
#hash(word)会产生一个很大的数,%1000后就变成0-1000的数
index = abs(hash(word)) % dimensionality
results[i, j, index] = 1.
6.1.2 使用词嵌入(word embedding)。
将单词与向量相关联还有另一种常用的强大方法,就是使用密集的词向量(word vector), 也叫词嵌入(word embedding)。
one-hot 编码得到的向量是二进制的(只有0,1)
稀疏的(绝大部分元素都是 0)、
高维的(维度大小等于词表中的单词个数),
而词嵌入是低维的浮点数向量(即密集向量,与稀疏向量相对),词嵌入是从数据中学习得到的。
常见的词向量维度是 256、512 或 1024(处理非常大的词表时)。
与此相对,one-hot 编码的词向量维度通常为 20 000 或更高(对应包含 20 000 个标记的词表)。因此,词向量可以将更多的信息塞入更低的维度中。
获取词嵌入有两种方法。 ()
‰ 在完成主任务(比如文档分类或情感预测)的同时学习词嵌入。
在这种情况下,一开始是随机的词向量,然后对这些词向量进行学习,其学习方式与学习神经网络的权重相同。
‰ 在不同于待解决问题的机器学习任务上预计算好词嵌入,然后将其加载到模型中。这些词嵌入叫作预训练词嵌入(pretrained word embedding)。
理解:与之前的CNN 预训练的卷积神经网络相似,但产生的结果不一定会很好,因为预设的模型对不同的文档的分析具有不同的侧重点
有许多预计算的词嵌入数据库,你都可以下载并在 Keras 的 Embedding 层中使用。 word2vec 就是其中之一。另一个常用的是 GloVe(global vectors for word representation,词表示全局向量),由斯坦福大学的研究人员于 2014 年开发。
具体见下
要将一个词与一个密集向量相关联,最简单的方法就是随机选择向量。
这种方法的问题在于,得到的嵌入空间没有任何结构。
例如,accurate 和 exact 两个词的嵌入可能完全不同,尽管它们在大多数句子里都是可以互换的 。深度神经网络很难对这种杂乱的、非结构化的嵌入空间进行
学习。
词嵌入的作用应该是将人类的语言映射到几何空间中。例如,在一个合理的嵌入空间中,同义词应该被嵌入到相似的词向量中
在真实的词嵌入空间中,常见的有意义的几何变换的例子包括“性别”向量和“复数”向量。 例如,将 king(国王)向量加上 female(女性)向量,得到的是 queen(女王)向量。将 king(国王) 向量加上 plural(复数)向量,得到的是 kings 向量。
词嵌入空间通常具有几千个这种可解释的、 并且可能很有用的向量。
从更实际的角度来说,一个好的词嵌入空间在很大程度上取决于你的任务。英语电影评论情感分析模型的完美词嵌入空间,可能不同于英语法律文档分类模型的完美词嵌入空间,因为某些语义关系的重要性因任务而异。
因此,合理的做法是对每个新任务都学习一个新的嵌入空间。
幸运的是,反向传播让这种学习变得很简单,而 Keras 使其变得更简单。
我们要做的就是学习一个层的权重,这个层就是 Embedding 层
嵌入层 Embedding - Keras 中文文档
from keras.layers import Embedding
词汇表大小 (这里是 1000,即最大整数+1,0不分配)和
词向量的维度embedding_ dimensionality(这里是 64)
embedding_layer = Embedding(1000, 64)
将 Embedding 层理解为一个字典,将整数索引(表示特定单词)映射为密集向量。
Embedding 层的输入是一个二维整数张量,其形状为 (samples, sequence_length)
可以输入形状为 (32, 10)(32 个长度为 10 的序列组成的批量)
或 (64, 15)(64 个长度为 15 的序列组成的批量)的批量。
pad_sequence序列预处理 - Keras 中文文档
不过一批数据中的所有序列必须具有相同的长度(因为需要将它们打包成一个张量),所以较短的序列应该用 0 填充,较长的序列应该被截断。
(这里就是靠pad_sequence)
Embedding 层返回一个形状为
(samples, sequence_length, embedding_ dimensionality) 的三维浮点数张量
比如上面embedding参数(1000,64),输入数据(32,10)output就是(32,10,64)
然后可以用 RNN 层或一维卷积层来处理这个三维张量(二者都会在后面介绍)。
这里首先是铺平在用dense层处理。
将这个想法应用于你熟悉的 IMDB 电影评论情感预测任务。首先,我们需要快速准备数据。
将电影评论限制为前 10000 个最常见的单词(第一次处理这个数据集时就是这么做的), 然后将评论长度限制为只有 20 个单词。
对于这 10000 个单词,网络将对每个词都学习一个 8 维嵌入,
将输入的整数序列(二维整数张量)转换为嵌入序列(三维浮点数张量),
然后将这个张量展平为二维,最后在上面训练一个 Dense 层用于分类。
###加载 IMDB 数据,准备用于 Embedding 层
from keras.datasets import imdb
from keras import preprocessing
max_features = 10000
maxlen = 20
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
#从原始资料开始的话,这里其实是两步
#1–preprocessing.sequence原始文本数据变为整数列表,
#2–pad_seq扩充成二维张量
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
##在 IMDB 数据上使用 Embedding 层和分类器
from keras.models import Sequential
from keras.layers import Flatten, Dense
model = Sequential()
#input_length: 输入序列的长度。 如果你需要连接 Flatten 和 Dense 层,则这个参数是必须的 #(没有它,dense 层的输出尺寸就无法计算)
#Embedding 层激活的形状为 (samples, maxlen, 8)
model.add(Embedding(10000, 8, input_length=maxlen))
#这里文字直接铺平也会造成文字之间的关系损失,下一节就会使用RNN
#将三维的嵌入张量展平成形状为 (samples, maxlen * 8) 的二维张量,Dense才能处理
model.add(Flatten())
model.add(Dense(1, activation=‘sigmoid’))
model.compile(optimizer=‘rmsprop’, loss=‘binary_crossentropy’, metrics=[‘acc’])
model.summary()
embedding_1 (Embedding) (None, 20, 8) 80000
flatten (Flatten) (None, 160) 0
dense (Dense) (None, 1) 161
625/625 [==============================] - 3s 5ms/step - loss: 0.2998 - acc: 0.8765 - val_loss: 0.5305 - val_acc: 0.7466
得到的验证精度约为 76%,考虑到仅查看每条评论的前 20 个单词,这个结果还是相当不错的。
但请注意,仅仅将嵌入序列展开并在上面训练一个 Dense 层,会导致模型对输入序列中的每个单词单独处理,而没有考虑单词之间的关系和句子结构。
更好的做法是在嵌入序列上添加循环层RNN或一维卷积层,将每个序列作为整体来学习特征。
6.1.3 整合在一起:从原始文本到词嵌入
imdb_dir = r’D:\softwareprocess\Downloads\aclImdb’
train_dir = os.path.join(imdb_dir, ‘train’) #连接路径,获得训练资料路径
labels = []
texts = []
for label_type in [‘neg’, ‘pos’]:
dir_name = os.path.join(train_dir, label_type) #连接路径,获得训练资料的 neg,和pos
for fname in os.listdir(dir_name): #获取文件名
if fname[-4:] == ‘.txt’: #后四位是 .txt
f = open(os.path.join(dir_name, fname) ,encoding=‘UTF-8’) #此处
需要加UTF-8
texts.append(f.read()) #将读取到的数据放入texts
f.close()
if label_type == ‘neg’:
labels.append(0) #对每条数据加上对应标签
else:
labels.append(1)
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
maxlen = 100 #一条评论最多接受100单词
training_samples = 200 # 在200个样本上验证
validation_samples = 10000
max_words = 10000 # 只考虑最常用的10000个字
#这里分词过程理解可看6.1.1的文本预处理那里
tokenizer = Tokenizer(num_words=max_words)
#texts是上文里所有评论,
通过fit_on_texts 获得两个有用的输出:word_counts:词频统计结果word_index:词和index的对应关系
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print(‘Found %s unique tokens.’ % len(word_index))
#变成二维矩阵 25000*100
data = pad_sequences(sequences, maxlen=maxlen)
labels = np.asarray(labels)
print(‘Shape of data tensor:’, data.shape)
print(‘Shape of label tensor:’, labels.shape)
#将数据划分为训练集和验证集,但首先要打乱数据,因为一开始数据中的样本
是排好序的(所有负面评论都在前面, 然后是所有正面评论)
indices = np.arange(data.shape[0]) # data.shape[0]=25000
#对于一个多维的输入,只是在第一维上进行了随机排序。例如对这个25000*10的矩阵来说,只是对行进行随机排序。
【Numpy】中np.random.shuffle()与np.random.permutation()的用法和区别_Amelie_xiao的博客-CSDN博客
np.random.shuffle(indices)
data = data[indices] #二维张量只写一维默认是row
labels = labels[indices]
x_train = data[:training_samples] #0-199
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples] #200-10199
y_val = labels[training_samples: training_samples + validation_samples]
记住:这个文件的数据格式 一共400000行,每行第一个就是一个单词
后面接100维的数据,并且是用空格分割
embeddings_index = {} #创建字典
f = open(os.path.join(glove_dir, ‘glove.6B.100d.txt’),encoding=‘UTF-8’)
for line in f: #获得glove文件里的每行数据
values = line.split() #根据空格分割
word = values[0] #获得每行第一个数据,也就是单词或符号
coefs = np.asarray(values[1:], dtype=‘float32’) #把所有的维度数据变ndarray
embeddings_index[word] = coefs #key是word,value是array
f.close()
还需要构建一个可以加载到 Embedding 层中的嵌入矩阵
(即embedding层的输入)。
它必须是一个形状为 (max_words, embedding_dim) 的矩阵,见6.1.2利用embed层,其中Embedding 层至少需要两个参数: 词汇表大小(这里是10000,即最大单词索引 +1,0不分配)和嵌入的维度
注意:这里包含很多上文的变量
代码:准备glove词嵌入矩阵
embedding_dim = 100 #
#建立一个满足embedding层的矩阵 10000*100
embedding_matrix = np.zeros((max_words, embedding_dim))
#word_index即tokenizer中获得的一个结果,评论中单词会对应一个数字
for word, i in word_index.items():
#现在将评论的词放进去就能得到该词的向量
embedding_vector = embeddings_index.get(word)
#如果评论的字对应的数<10000,就将向量值放入嵌入矩阵,没有的地方就是0
if i < max_words:
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
其实可以发现,第二行就是word_index中第一个单词the在glove中的向量值。
由于word_index中0是不对应某个单词的,只用来做占位符,所以embedding_matrix第一行全0
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation=‘relu’))
model.add(Dense(1, activation=‘sigmoid’))
model.summary()
注意:第一层embedding是我们用别人的嵌入空间,所以10000*100的参数不能修改,要冻结,在下文有显示。
复习:embedding的输出是(samples, sequence_length, embedding_ dimensionality) 的三维浮点数张量 就是这里的(none,100,100)
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False
此外,需要冻结 Embedding 层(即将其 trainable 属性设为 False),
原理和预训练的卷积神经网络特征相同。
如果一个模型的一部分是经过预训练的(如 Embedding 层),而另一部分是随机初始化的(如分类器),那么在训练期间不应该更新预训练的部分
随机初始化的层会引起较大的梯度更新,会破坏已经学到的特征。
#这是保存的第5个模型
model.save_weights(‘pre_trained_glove_model.h5’)
画图
import matplotlib.pyplot as plt
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, ‘r’, label=‘Validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘r’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
验证精度也就53%左右,由于数据样本就200个,从第二次就开始过拟合
在不加载预训练词嵌入、也不冻结嵌入层的情况下训练相同的模型。
也就是不用glove,没有set_weight这一步
在这种情况下, 你将会学到针对任务的输入标记的嵌入。
就是自学习词嵌入方法。
如果有大量的可用数据,这种方法通常比预训练词嵌入更加强大,但本例只有 200 个训练样本。
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation=‘relu’))
model.add(Dense(1, activation=‘sigmoid’))
model.summary()
##模型建好之后直接编译,没有使用预训练的词嵌入,仔细观察两者的区别
model.compile(optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘acc’])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=32,
validation_data=(x_val, y_val))
acc = history.history[‘acc’]
val_acc = history.history[‘val_acc’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, ‘bo’, label=‘Training acc’)
plt.plot(epochs, val_acc, ‘b’, label=‘Validation acc’)
plt.title(‘Training and validation accuracy’)
plt.legend()
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘b’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
验证准确率在52%左右,差距不多(作者认为在极少样本的情况下56%已经是很高的准确率了)
最后,我们在测试数据上评估模型。首先需要对测试数据(也是原始文本数据)进行分词
test_dir = os.path.join(imdb_dir, ‘test’)
labels = []
texts = []
for label_type in [‘neg’, ‘pos’]:
dir_name = os.path.join(test_dir, label_type)
for fname in sorted(os.listdir(dir_name)):
if fname[-4:] == ‘.txt’:
f = open(os.path.join(dir_name, fname) ,encoding=‘utf8’)
texts.append(f.read())
f.close()
if label_type == ‘neg’:
labels.append(0)
else:
labels.append(1)
sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences, maxlen=maxlen)
y_test = np.asarray(labels) #从原本的list变为ndarray,才能输入网络
model.load_weights(‘pre_trained_glove_model.h5’)
model.evaluate(x_test, y_test)
作者的结果是56%,为此震惊,说只用了很少的训练样本,得到这样的结果很不容易。
6.1.4 小结
现在你已经学会了下列内容。
‰ 将原始文本转换为神经网络能够处理的格式。
‰ 使用 Keras 模型的 Embedding 层来学习针对特定任务的标记嵌入。
‰ 使用预训练词嵌入在小型自然语言处理问题上获得额外的性能提升。
6.2 理解循环神经网络RNN
目前你见过的所有神经网络(比如密集连接网络和卷积神经网络)都有一个主要特点,那就是它们都没有记忆。
它们单独处理每个输入,在输入与输入之间没有保存任何状态。
对于这样的网络,要想处理数据点的序列或时间序列,你需要向网络同时展示整个序列,即将序列转换成单个数据点。
例如,你在 IMDB 示例中就是这么做的:将全部电影评论转换为一个大向量,
然后一次性处理。这种网络叫作前馈网络(feedforward network)。
与此相反,当你在阅读这个句子时,你是一个词一个词地阅读(或者说,眼睛一次扫视一次扫视地阅读),同时会记住之前的内容。这让你能够动态理解这个句子所传达的含义。
循环神经网络(RNN,recurrent neural network)采用同样的原理,不过是一个极其简化的版本:它处理序列的方式是,遍历所有序列元素,并保存一个状态(state),其中包含与已查看内容相关的信息。
注意:右边展开是显示原理,便于理解,不是有这么多网络,实际上就左边一个
注意:由于RNN是具有时间轴的,所以神经网络画法是竖着画,而不是横着画
6.2.1 Keras 中的循环层
import keras
##keras.version
from keras.models import Sequential
from keras.layers import Embedding, SimpleRNN
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32)) // 这里无return_sequences,输出结果为二维
model.summary()
其中320000就是embedding层的max_wordsdimension维度
simpleRNN的参数计算除了 (32+1)32外,由于RNN的记忆特性,每次要加上两层之间的交流,即3232。
了解simpleRNN的参数计算很关键
因为simpleRNN的参数4LSTM(长短期记忆)的参数
simpleRNN的参数*3GRU的参数
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences=True))
model.summary()
SimpleRNN 可以在两种不同的模式下运行:
一种是返回每个时间步连续输出的完整序列,
即形状为 (batch_size, timesteps, output_features) 的三维张量;
另一种是只返回每个输入序列的最终输出,
即形状为 (batch_size, output_ features) 的二维张量。
这两种模式由 return_sequences 这个构造函数参数来控制。
增加return_sequences=True,输出结果增加一个维度。
注:所有的循环层都有此功能!!!
如果只是想要输出结果是个32*1的向量(后接CNN或DNN),那没有必要加。
如果还想要做一层RNN,那就需要把数据变成合适的维度
为了提高网络的表示能力,将多个循环层逐个堆叠有时也是很有用的。在这种情况下,你需要让所有中间层都返回完整的输出序列。
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32)) # 最后一层返回最终输出
model.summary()
应用于 IMDB 电影评论分类问题。首先,对数据进行预处理。
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 10000 # 单词个数
maxlen = 500 # 在500单词后截断文本
batch_size = 32
print(‘Loading data…’)
(input_train, y_train), (input_test, y_test)=imdb.load_data(num_words=max_features)
print(len(input_train), ‘train sequences’)
print(len(input_test), ‘test sequences’)
#keras中的IMDB里评论数据预先就被处理成了sequence,也就是数字序列,所以直接pad成二维矩阵
print(‘Pad sequences (samples x time)’)
input_train = sequence.pad_sequences(input_train, maxlen=maxlen)
input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
print(‘input_train shape:’, input_train.shape)
print(‘input_test shape:’, input_test.shape)
用一个 Embedding 层和一个 SimpleRNN 层来训练一个简单的循环网络。
from keras.layers import Dense
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation=‘sigmoid’))
#使用RNN时优化器很少使用带动量的Adam之类的,梯度下降时可能直接掉落至底
model.compile(optimizer=‘rmsprop’, loss=‘binary_crossentropy’, metrics=[‘acc’])
history = model.fit(input_train, y_train,
epochs=10,
batch_size=128, #可改为512
validation_split=0.2)
注意:这里使用GPU跑巨慢无比,一次epoch要100s左右,搜索找到的结果可能有用
下次增加batch_size 试一下,
二次补充:见下
为什么在训练 LSTM/RNN 模型时我的 GPU 比 CPU 慢?答案 - 爱码网 (likecs.com)
画图代码同上
提醒一下,在第 3 章,处理这个数据集的第一个简单方法得到的测试精度是 88%。不幸的是, 与这个基准相比,这个小型循环网络的表现并不好(验证精度只有 85%我自己测试只有80%左右)。
问题的部分原因在于, 输入只考虑了前 500 个单词,而不是整个序列,
因此,RNN 获得的信息比前面的基准模型更少。
另一部分原因在于,SimpleRNN 不擅长处理长序列,比如文本。
上面修改batch_size成功!
batch_size改为512后,每一次epoch 25000条数据就需要跑40批,速度提升至30s左右,但是又出现一个问题?(算是问题,准确率迅速提升至80%左右)从损失看在第四次的时候出现过拟合,也就是说80%是正常的。
深度学习中学习率和batchsize对模型准确率的影响_batchsize对准确率的影响_初识-CV的博客-CSDN博客
6.2.2 理解 LSTM 层和 GRU 层
Keras 中可用的循环层,还有另外两个:LSTM 和 GRU。在实践中
总会用到其中之一(主要是LSTM),因为 SimpleRNN 通常过于简化,没有实用价值。
SimpleRNN 的最大问题是,
在时刻 t,理论上来说,它应该能够记住许多时间步之前见过的信息,但实际上它是不可能学到这种长期依赖的。
其原因在于梯度消失问题(vanishing gradient problem),这一效应类似于在层数较多的非循环网络(即前馈网络)中观察到的效应:随着层数的增加,网络最终变得无法训练。
简易理解:比如有个值是1.0001经过几千次的循环,也会变得很大,某个值小于1一点点,多次循环后也会变成0,消失!
(LSTM,long short-term memory)算法由 Hochreiter 和 Schmidhuber 在 1997 年开发。
LSTM 层是 SimpleRNN 层的一种变体,它增加了一种携带信息跨越多个时间步的方法。
从 SimpleRNN 单元开始讲起,书上图
output_t = activation( Wo•input_t + Uo•state_t + bo)
从上面两个图以及讲解,可以得知,我们需要的就是估计W和U两个参数
参数估计 将使用simpleRNN的出的总参数乘以四倍
视频:不管是视频还是书上,在LSTM中,每行等式都需要估计一个W和U
所以一共需要估计4个W,4个U,
因此上文simpleRNN估计的参数*4==LSTM需要估计的参数
ft 是forget gate遗忘门,决定上一次的记忆可以保留多少 (0-1),打开保留记忆,关闭都忘记. it是输入门,Ot是输出门
Ct就是下面的Ct ,携带着记忆的memory 其中 C 表示携带(carry)。
ht就在这个神经元经过这一系列计算的一次结果
tanh是双曲正切函数(hyperbolic tangent function)
LSTM unit就是一层循环层中的一个神经元,流程解释:就是上面的公式、
首先是Xt和ht-1 经过和W,U的线性组合,加上一个b,经过sigmoid(值会乘以0-1)或者tanh得到结果Ft It Ot 经过tanh的未命名结果。
从遗忘门Ft开始,会和Ct-1 做一个点对点相乘,即求Ct 前半部分。
然后输入门It 会和经过tanh未命名的数(tanh值范围(-1,1))做点对点相乘,就可以看成一个0-1的正数会乘以一个-1到1的数,其结果对记忆造成正或负贡献。这样就得到了新的记忆Ct 传到下一个循环去。
Ct经过一次tanh(乘以范围(-1,1)的数), 然后乘以输出门Ot,得到这一次的输出也就是ht ,神经元被更新的数字,也传到下一次循环
作者的话:总之,你不需要理解关于 LSTM 单元具体架构的任何内容。作为人类,理解它不应该是你要做的。
你只需要记住 LSTM 单元的作用:允许过去的信息稍后重新进入,从而解决梯度消失问题。
6.2.3 Keras 中一个 LSTM 的具体例子
使用 LSTM 层来创建一个模型,然后在 IMDB 数据上
训练模型(见图 6-16 和图 6-17)。这个网络与前面介绍的 SimpleRNN 网络类似。
你只需指定 LSTM 层的输出维度,其他所有参数(有很多)都使用 Keras 默认值。Keras 具有很好的默认值,无须手动调参,模型通常也能正常运行。
#补充 如果要使用 GRU 就将所有的LSTM改成GRU即可
from keras.layers import LSTM
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(LSTM(32))
model.add(Dense(1, activation=‘sigmoid’))
model.compile(optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘acc’])
history = model.fit(input_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)
验证精度达到了88%左右,肯定比 SimpleRNN 网络好多了,这主要是因为
LSTM 受梯度消失问题的影响要小得多
这个结果也比第 3 章的全连接网络略好,虽然使用的数据量比第 3 章要少。此处在 500 个时间步之后将序列截断,而在第 3 章是读取整个序列。
为什么 LSTM 不能表现得更好?一个原因是你没有花力气来调节超参数,比如嵌入维度或 LSTM 输出维度 (32维变成64,128,或者继续加循环层)。
另一个原因可能是缺少正则化。
但说实话,主要原因在于,适用于评论分析全局的长期性结构(这正是 LSTM 所擅长的),对情感分析问题帮助不大。对于这样的基本问题,观察每条评论中出现了哪些词及其出现频率就可以很好地解决。这也正是第一个全连接方法的做法。但还有更加困难的自然语言处理问题,特别是问答和机器翻译,这时 LSTM 的优势就明显了。
补充:原书并没有对GRU进行讲解,根据视频理解如下
门控循环单元(GRU,gated recurrent unit)
首先是Xt 和ht-1 经过两次sigmoid函数变成了Rt和Zt ,Rt 又会和ht-1 相乘,变成一个新的output,再去和Xt 做一次tanh,最后贡献给下面求ht 。
注意求ht公式刚好和图片相反,图中是Zt 和ht-1 相乘,再加上 1-Zt 和做完tanh的结果相乘得到ht .公式将Zt权重反过来了,没有影响
6.3 循环神经网络的高级用法
本节将介绍提高循环神经网络的性能和泛化能力的三种高级技巧。
将在温度预测问题中介绍这三个概念。
在这个问题中,
数据点时间序列来自建筑物屋顶安装的传感器,包括温度、气压、湿度等,你将要利用这些数
据来预测最后一个数据点 24 小时之后的温度。
我们将会介绍以下三种技巧。
‰ 循环 dropout(recurrent dropout)。这是一种特殊的内置方法,在循环层中使用 dropout 来降低过拟合。
理解:就是跟全连接层一样,使用dropout层,随机丢弃
‰ 堆叠循环层(stacking recurrent layers)。这会提高网络的表示能力(代价是更高的计算负荷)。
理解:就是跟全连接层一样,不断加层
‰ 双向循环层(bidirectional recurrent layer)。将相同的信息以不同的方式呈现给循环网络,可以提高精度并缓解遗忘问题。
理解:比如说,对文字可以从头到尾,在从尾到头,合并起来
6.3.1 温度预测问题
在本节的所有例子中,我们将使用一个天气时间序列数据集,在这个数据集中,每 10 分钟记录 14 个不同的量(比如气温、气压、湿度、风向等)
这个数据集非常适合用来学习处理数值型时间序列。
开始处理数据
import keras
#keras.version
import os
data_dir = r’D:\softwareprocess\Downloads\jena_climate’
fname = os.path.join(data_dir, ‘jena_climate_2009_2016.csv’)
f = open(fname)
data = f.read()
f.close()
lines = data.split(‘\n’) #每次换行时分割数据
header = lines[0].split(‘,’) #:csv文件每列数据之间是 逗号分隔的
获得所有第一行的数据,即shijian温度,气压等
lines = lines[1:] #这里分割,是行分割 第二行到最后行才是
我们关心的数据
print(header)
print(len(lines))
这本书从头到尾都没有使用pandas,如果使用代码如下
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd #在anaconda navigator下载
df = pd.read_csv( r’D:\softwareprocess\Downloads\jena_climate’\ jena_climate_2009_2016.csv)
df.tail()#
熟悉的dataframe格式
接下来,将 420 451 行数据转换成一个 Numpy 数组。注 我相比书上数据少了一百行
import numpy as np
float_data = np.zeros((len(lines), len(header) - 1)) #开空间,只需要后14列
时间轴那列不需要了,在下面函数分割掉
for i, line in enumerate(lines): #enumerate自动编号
values = [float(x) for x in line.split(‘,’)[1:]] #注意:这注意这里的line是一行,所以从1开始割是割时间轴,
不要看成是lines,成了切去第一行
float_data[i, :] = values #获得所有行和列,赋值进0矩阵
比较一下,就是删去了时间轴
绘制温度时间序列 注意温度是在第二列
from matplotlib import pyplot as plt
temp = float_data[:, 1] #温度(单位:摄氏度) 所有行的第二列
plt.plot(range(len(temp)), temp) #输入x,y轴数据
plt.show()
下图给出了前 10 天温度数据的图像。因为每 10 分钟记录一个数据,所以每天有 144 个数据点。
plt.plot(range(1440), temp[:1440])
plt.show()
如果你想根据过去几个月的数据来预测下个月的平均温度,那么问题很简单,因为数据具有可靠的年度周期性。
但从几天的数据来看,温度看起来更混乱一些。以天作为观察尺度,这
个时间序列是可以预测的吗?我们来寻找这个问题的答案。
6.3.2 准备数据
这个问题的确切表述如下:
一个时间步是 10 分钟,6个一小时,144个1天,5天就是720个时间步,、
每 steps (这里是6个即1小时)个时间步采样一次数据,
给定过去 lookback 个时间步之内的数据,能否预测 delay 个时间步之后的温度?
用到的参数值如下。
‰ lookback = 720:给定过去 5 天内的观测数据。
‰ steps = 6:观测数据的采样频率是每小时一个数据点。
‰ delay = 144:目标是未来 24 小时之后的数据。
综合一下,给定5天的数据,每1小时采样一次数据,能否根据这些数据去推测1天后的各类数据
开始之前,你需要完成以下两件事。
一----将数据预处理为神经网络可以处理的格式。数据已经是数值型的,所以不需要做向量化。
但数据中的每个时间序列位于不同的范围(比如温度通道位于 -20 到 +30 之间,但气压大约在 1000 毫巴上下)。你需要对每个时间序列分别做标准化,让它们在相似的范围内都取较小的值。
二-----编写一个 Python 生成器,以当前的浮点数数组作为输入,并从最近的数据中生成数据批量,同时生成未来的目标温度。
因为数据集中的样本是高度冗余的(对于第 N 个样本和第 N+1 个样本,大部分时间步都是相同的),所以显式地保存每个样本是一种浪费。相反, 我们将使用原始数据即时生成样本?暂时没懂
理解:
就是说4万多条数据有很多重复性的数据,所有下面就写了一个生成器,每一个小时采样一次,作者在原数据的基础上提取了有代表性的数据,作为sample,再将1天后的温度数据作为target,也就是之前的label
将使用前200 000 个时间步作为训练数据,所以只对这部分数据计算平均值和标准差。减平均,除标准差也就是标准化。
然后从200001- 300000作为验证数据 (就是漏去了第20000条数据)
test数据 从300001-最后。
注意:对验证数据,和test数据,需要用训练数据的平均值,和标准差做标准化,绝对不能都去算各自的平均值和标准差,再标准化
代码清单 6-32
mean = float_data[:200000].mean(axis=0) #算出每一column的mean
float_data -= mean #这里mean是一个nparray包含14个平均值,利用了广播机制将所有数据进行了标准化
std = float_data[:200000].std(axis=0)
float_data /= std
将要用到的生成器generator,它生成了一个元组 (samples, targets),
其中 samples 是输入数据的一个批量,targets 是对应的目标温度数组。生成器的参数如下。
‰ data:浮点数数据组成的原始数组,在代码清单 6-32 中将其标准化。
‰ lookback:输入数据应该包括过去多少个时间步。
‰ delay:目标应该在未来多少个时间步之后。
‰ min_index 和 max_index:data 数组中的索引,用于界定需要抽取哪些时间步。这有助于保存一部分数据用于验证、另一部分用于测试。
0-200000 ,200001- 300000,300001 – ……
‰ shuffle:是打乱样本,还是按顺序抽取样本。
‰ batch_size:每个批量的样本数。
‰ step:数据采样的周期(单位:时间步)。我们将其设为 6,为的是每小时抽取一个数据点
生成时间序列样本及其目标的生成器
注意和下面的数据一起看
lookback = 1440 //应该是720
step = 6
delay = 144
batch_size = 128
def generator(data, lookback, delay, min_index, max_index,
shuffle=False, batch_size=128, step=6):
if max_index is None: #测试数据最大索引会设置为None
max_index = len(data) - delay – 1 #最大索引不能超,所以减去delay的一天,再减去预测答案的一行
i = min_index + lookback #i=0+720,200001+720,300001+720
while 1:
if shuffle: #只有训练会打乱,准备抽样,数据集设置shuffle=ture,默认就false
rows = np.random.randint(
min_index + lookback, max_index, size=batch_size)
#rows中有输出范围0+720--200000的128个乱数
else: #验证和测试资料不打乱,
if i + batch_size >= max_index: #对i不断加128,大于300000后,退回最初值
i = min_index + lookback
rows = np.arange(i, min(i + batch_size, max_index)) #实际上一步已经保证i + batch_size更小,rows就是128个数字
i += len(rows)
#这里只举了训练的一种可能,验证和test同理
samples = np.zeros((len(rows), #一维数组有128个值,
lookback//step, #720//6=120 每6个抽一个
data.shape[-1])) # data.shape (…,14)14个column
#samples形状就是(128,120,14)
targets = np.zeros((len(rows),)) #标准答案,一维张量包含128个
for j, row in enumerate(rows): #进行编码
indices = range(rows[j] - lookback, rows[j], step)
#这里可以假设第一次训练数据rows取到的乱数是最小720,那么indices为range(0,720.6) ,从0取到720,步长为6。即[0,6,12,18,……,714] 120个数据
#注意这里验证和test是一样的操作,只不过indices的范围不同 比如
range(200001,200721,6)
samples[j] = data[indices] # samples[0]就是data[0,6,12…],也就是
将经过挑选的数据赋值给samples
targets[j] = data[rows[j] + delay][1] #720+144+1行(即训练的5天,推
迟一天,从0开始)的第二列就是需要的与预测对比的温度数据
yield samples, targets
lookback = 1440
step = 6
delay = 144
batch_size = 128
train_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=0,
max_index=200000,
shuffle=True,
step=step,
batch_size=batch_size) //返回训练samples,target
val_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=200001,
max_index=300000,
step=step,
batch_size=batch_size)
test_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=300001,
max_index=None,
step=step,
batch_size=batch_size)
val_steps = (300000 - 200001 - lookback) // batch_size
#为了查看整个测试集,需要从test_gen 中抽取多少次
test_steps = (len(float_data) - 300001 - lookback) // batch_size
数据少了100条,这里也不同
6.3.3 一种基于常识的、非机器学习的基准方法
开始使用黑盒深度学习模型解决温度预测问题之前,我们先尝试一种基于常识的简单方法。
它可以作为合理性检查,还可以建立一个基准,更高级的机器学习模型需要打败这个基准才能表现出其有效性。
理解:这个新建立的模型至少要比我们通过数学常识猜对的概率高,这样才算是一个有效果的模型
面对一个尚没有已知解决方案的新问题时,这种基于常识的基准方法很有用。
一个经典的例子就是不平衡的分类任务,其中某些类别比其他类别更常见。如果数据集中包含90% 的类别 A 实例和 10% 的类别 B 实例,那么分类任务的一种基于常识的方法就是对新样本始终预测类别“A”。这种分类器的总体精度为 90%,因此任何基于学习的方法在精度高于 90% 时才能证明其有效性。
对于我们需要解决的问题
,温度时间序列是连续的(明天的温度很可能接近今天的温度),并且具有每天的周期性变化。
因此,一种基于常识的方法就是始终预测 24 小时后的温度等于现在的温度。
我们使用平均绝对误差(MAE)指标(作为metrics)来评估这种方法。
np.mean(np.abs(preds - targets))
使用的是验证集的数据,方法就是从200001开始,到200721 认为200865的温度就是200721的温度,
代码清单计算符合常识的基准方法的 MAE
next() 返回迭代器的下一个项目。语法 :next(iterable[, default])
def evaluate_naive_method():
batch_maes = []
for step in range(val_steps): #即上文的val_steps=769
samples, targets = next(val_gen) #通过next获得验证生成器的结果
sample(128,120,14)
preds = samples[:, -1, 1] #取128个的最后一行,第二列
mae = np.mean(np.abs(preds - targets))
batch_maes.append(mae)
print(np.mean(batch_maes)) #总共做val_steps次,再做平均
evaluate_naive_method()
celsius_mae = 0.29 * std[1] #std是6.3.2标准化中array,14列中第二列是温度
得到的 MAE 为 0.29。因为温度数据被标准化成均值为 0、标准差为 1,所以无法直接对这个值进行解释。它转化成温度的平均绝对误差为 0.29×temperature_std 摄氏度,即 2.57℃。
6.3.4 一种基本的机器学习方法
在尝试机器学习方法之前,建立一个基于常识的基准方法是很有用的;同样,在开始研究复杂且计算代价很高的模型(比如 RNN)之前,尝试使用简单且计算代价低的机器学习模型也是很有用的,比如小型的密集连接网络。
这可以保证进一步增加问题的复杂度是合理的
意思就是需要找到适合问题的模型,不是就直接上复杂的的模型,就像是对时间判断问题使用CNN去解决一样,浪费资源时间
开始建立模型,首先将数据展平,然后通过两个 Dense 层并运行。
注意,最后一个 Dense 层没有使用激活函数,这对于回归问题是很常见的。
我们使用 MAE 作为损失(一般是做指标)。具体可见3.6.3
from keras.models import Sequential
from keras import layers
from tensorflow.keras.optimizers import RMSprop #注意加tensorflow
model = Sequential()
#摊平成 120*14
model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
model.add(layers.Dense(32, activation=‘relu’))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500, #没有做所有的数据
epochs=20,
validation_data=val_gen,
validation_steps=val_steps)
import matplotlib.pyplot as plt
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(len(loss))
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘r’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
loss 在0.33左右
部分验证损失接近不包含学习的基准方法,但这个结果并不可靠。这也展示了首先建立这个基准方法的优点,事实证明,超越这个基准并不容易。
原因:即使在我们这个双层网络的假设空间中,是存在一种简单的,又有一定精确度的模型,但我们训练出的模型不一定就是会向这个模型去发展,改进,因为它并不是我们的目标。
如果你在一个复杂模型的空间中寻找解决方案,那么可能无法学到简单且性能良好的基准方法, 虽然技术上来说它属于假设空间的一部分。
6.3.5 第一个循环网络基准 使用GRU
第一个全连接方法的效果并不好,但这并不意味着机器学习不适用于这个问题。
前一个方法首先将时间序列展平,这从输入数据中删除了时间的概念。
原数据是一个序列,其中因果关系和顺序都很重要。我们将尝试一种循环序列处理模型,它应该特别适合这种序列数据,因为它利用了数据点的时间顺序,这与第一个方法不同。
我们将使用 Chung 等人在 2014 年开发的 GRU 层 ,而不是上一节介绍的 LSTM 层。
门控循环单元(GRU,gated recurrent unit)层的工作原理与 LSTM 相同。
GRU具体信息看6.2.2.
它做了一些简化,因此运行的计算代价更低(虽然表示能力可能不如 LSTM)。
机器学习中到处可以见到这种计算代价与表示能力之间的折中。
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop
model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=20,
validation_data=val_gen,
validation_steps=val_steps)
#画图
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs = range(len(loss))
plt.figure()
plt.plot(epochs, loss, ‘bo’, label=‘Training loss’)
plt.plot(epochs, val_loss, ‘r’, label=‘Validation loss’)
plt.title(‘Training and validation loss’)
plt.legend()
plt.show()
效果好多了!远优于?基于常识的基准方法。loss达到了0.26
这证明了机器学习的价值,也证明了循环网络与序列展平的密集网络相比在这种任务上的优势。
从第5轮左右开始过拟合,所以接下来就是降低过拟合
6.3.6 使用循环 dropout 来降低过拟合
我们已经学过降低过拟合的一种经典技术——dropout,即将某一层的输入单
元随机设为 0,其目的是打破该层训练数据中的偶然相关性。
但在循环网络中如何正确地使用dropout,这并不是一个简单的问题。人们早就知道,在循环层前面应用 dropout,这种正则化会妨碍学习过程,而不是有所帮助。
Yarin Gal 确定了在循环网络中使用 dropout 的正确方法:
对每个时间步应该使用相同的 dropout 掩码(dropout mask,相同模式的舍弃单元),而不是让 dropout 掩码随着时间步的增加而随机变化。
而且,为了对 GRU、LSTM 等循环层得到的表示做正则化,
应该将不随时间变化的 dropout 掩码应用于层的内部循环激活
(叫作循环 dropout 掩码)。
理解:想要在循环网络中使用 dropout,你应该使用一个不随时间变化的 dropout 掩码与循环 dropout 掩码。
对每个时间步使用相同的 dropout 掩码,可以让网络沿着时间正确地传播其学习误差,而随时间随机变化的 dropout 掩码则会破坏这个误差信号,并且不利于学习过程。
举例:上文的(128,120,14)中128个sample里每个都有120时间步,这120时间步一概都用同一个dropout码,
对于循环层,每层也要使用相同的循环dropout码
回想关于LSTM的图像,其中ht-1 就是代表上图中的横向时间步,
Xt 就是每一次的输入。所以 recurrent_dropout就是在决定哪一部分ht-1 会被舍弃,
dropout就是针对普通的Xt ,舍弃变0
Yarin Gal 使用 Keras 开展这项研究,并帮助将这种机制直接内置到 Keras 循环层中
Keras 的每个循环层都有两个与 dropout 相关的参数:
一个是 dropout,它是一个浮点数,指定该层输入单元的 dropout 比率;
另一个是 recurrent_dropout,指定循环单元的 dropout 比率。
因为使用 dropout正则化的网络总是需要更长的时间才能完全收敛,所以网络训练轮次增加为原来的 2 倍。
from keras.models import Sequential
from keras import layers
from tensorflow.keras.optimizers import RMSprop
model = Sequential()
model.add(layers.GRU(32,
dropout=0.2, #对输入Xt 有20%变成0
recurrent_dropout=0.2, #对Ht-1
input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=40,
validation_data=val_gen,
validation_steps=val_steps)
出现警告,并且每次epoch速度巨慢
WARNING:tensorflow:Layer gru will not use cuDNN kernels since it doesn’t meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.
WARNING:tensorflow:Layer lstm will not use cuDNN kernel since it doesn‘t meet the cuDNN kernel_辰辰无敌的博客-CSDN博客
循环层 Recurrent - Keras 中文文档
解决方法:需要满足一定条件才可以在cuDNN上加速。但是由于recurrent_dropout == 0 才能使用加速,所以没有办法. 其他博客中也表示需要大量时间!
The requirements to use the cuDNN implementation are:
使用 cuDNN 实现的要求是:
activation == tanh
recurrent_activation == sigmoid
recurrent_dropout == 0
unroll is False
use_bias is True
reset_after is True
注意:很多在keras中都是默认的,回忆关于LSTM,和GRU的公式,其中σ下面是g,也就是代表的是recurrent_activation=sigmoid,下面是h,就是代表activation = tanh
【解决方案2】:
这是 keras 库的创建者、tensorflow 框架的主要贡献者 Francois Chollet 在他的书 Deep Learning with Python 2nd edition 中谈到 RNN 运行时性能时所说的
参数很少的循环模型,比如本章中的模型,在多核 CPU 上往往比在 GPU 上快得多,因为它们只涉及小的矩阵乘法,并且乘法链不能很好地并行化,因为存在 for 循环。但较大的 RNN 可以从 GPU 运行时中受益匪浅。
当在 GPU 上使用带有默认关键字参数的 Keras LSTM 或 GRU 层时,您的层将利用 cuDNN 内核,这是一种高度优化的低级 NVIDIA 提供的底层算法实现。
像往常一样,cuDNN 内核是喜忧参半:它们速度快,但不灵活——如果你尝试做任何默认内核不支持的事情,你将遭受戏剧性的减速。例如,LSTM 和 GRU cuDNN 内核不支持经常性 dropout,因此将其添加到您的层会强制运行时回退到常规 TensorFlow 实现,这通常在 GPU 上慢 2 到 5 倍(即使它的计算费用是一样的)。
作为一种在您无法使用 cuDNN 时加速 RNN 层的方法,您可以尝试展开它。展开 for 循环包括删除循环并简单地将其内容内联 N 次。对于 RNN 的 for 循环,展开可以帮助 TensorFlow 优化底层计算图。但是,它也会大大增加 RNN 的内存消耗——因此,它只适用于相对较小的序列(大约 100 步或更少)。另外,请注意,只有在模型预先知道数据中的时间步数时才能执行此操作(也就是说,如果您将没有任何 None 条目的形状传递给初始 Input())。它的工作原理是这样的:
inputs = keras.Input(shape=(sequence_length, num_features))
x = layers.LSTM(32, recurrent_dropout=0.2, unroll=True)(inputs)
这里到底怎么解决,准备在看第二版时进行回答!
前 30 个轮次不再过拟合。不过,虽然评估分数更加稳定,但最佳分数并没有比之前低很多。
6.3.7 循环层堆叠(stacking recurrent layer)
就是将RNN变成DNN的概念,加层。
模型不再过拟合,但似乎遇到了性能瓶颈(),所以我们应该考虑增加网络容量。
增加网络容量的通常做法是增加每层单元数(unit)或增加层数。
在 Keras 中逐个堆叠循环层,所有中间层都应该返回完整的输出序列(一个 3D 张量),而 不是只返回最后一个时间步的输出。这可以通过指定 return_sequences=True 来实现。
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop
model = Sequential()
model.add(layers.GRU(32,
dropout=0.1,
recurrent_dropout=0.5,
return_sequences=True, #具体见6.2.1 增加返回3维数
据,给下一层使用
input_shape=(None, float_data.shape[-1])))
model.add(layers.GRU(64, activation=‘relu’,
dropout=0.1,
recurrent_dropout=0.5))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=40,
validation_data=val_gen,
validation_steps=val_steps)
注:没有加速,博主跑了三小时《Python 深度学习》6.3 循环神经网络的高级用法 (代码)_使用循环神经网络进行预测的代码_布拉格沃兹基硕德的博客-CSDN博客
可以看到,添加一层的确对结果有所改进,但并不显著。我们可以得出两个结论。
‰ 因为过拟合仍然不是很严重,所以可以放心地增大每层的大小,以进一步改进验证损失。但这么做的计算成本很高。
‰ 添加一层后模型并没有显著改进,所以你可能发现,提高网络能力的回报在逐渐减小。
所以下面来讲到底怎么获得更好的结果
6.3.8 使用双向 RNN(bidirectional RNN)
本节介绍的最后一种方法叫作双向 RNN(bidirectional RNN)。
双向 RNN 是一种常见的 RNN 变体,它在某些任务上的性能比普通 RNN 更好。
双向 RNN(bidirectional RNN)它常用于自然语言处理,可谓深度学习对自然语言处理的瑞士军刀。
RNN 特别依赖于顺序或时间,RNN 按顺序处理输入序列的时间步, 而打乱时间步或反转时间步会完全改变 RNN 从序列中提取的表示
如果顺序对问题很重要(比如温度预测问题),RNN 的表现会很好
双向 RNN 利用了 RNN 的顺序敏感性:
它包含两个普通 RNN,比如你已经学过的 GRU 层和 LSTM 层,
每个 RNN分别沿一个方向对输入序列进行处理 (时间正序和时间逆序),
然后将它们的表示合并在一起。
通过沿这两个方向处理序列,双向RNN 能够捕捉到可能被单向 RNN 忽略的模式。
之前我们一直都是顺序RNN,如果 RNN 按时间逆序处理输入序列(更晚的时间步在前),能否表现得足够好呢?我们在实践中尝试一下这种方法,看一下会发
生什么。只需要将之前的输入序列沿着时间维度反转
(即将6.3.2处理数据generator函数,sample[128,120,14)]最后一行代码替换为 yield samples[:, ::-1, :], targets)即只对时间步维度反转
逆序 GRU 的效果甚至比基于常识的基准方法还要差很多,
GRU 层通常更善于记住最近的数据,而不是久远的数据。
因此,按时间正序的模型必然会优于时间逆序的模型。
注:基于常识来说,也肯定是最近的温度数据,预测后来的温度更加准确,而不是靠前的温度会预测的更准确
但是重要的是, 对许多其他问题(包括自然语言)而言,情况并不是这样:
直觉上来看,一个单词对理解句子的重要性通常并不取决于它在句子中的位置。
我们使用IMDB的影评数据来进行测试
from keras.datasets import imdb
from keras.preprocessing import sequence
from keras import layers
from keras.models import Sequential
max_features = 10000
#在这么多单词之后截断文本
maxlen = 500
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = [x[::-1] for x in x_train]
x_test = [x[::-1] for x in x_test]
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)
model = Sequential()
model.add(layers.Embedding(max_features, 128))
model.add(layers.LSTM(32))
model.add(layers.Dense(1, activation=‘sigmoid’))
model.compile(optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘acc’])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)
模型性能与正序 LSTM 几乎相同。这证实了一个假设:
虽然单词顺序对理解语言很重要,但使用哪种顺序并不重要。
重要的是,在逆序序列上训练的 RNN 学到的表示不同于在原始序列上学到的表示。
在机器学习中,如果一种数据表示不同但有用,那么总是值得加以利用。
这是集成(ensembling)方法背后的直觉,我们将在第 7 章介绍集成的概念。
层封装器 wrappers - Keras 中文文档
在 Keras 中将一个双向 RNN 实例化,我们需要使用 Bidirectional (即双向的意思)层,
它的第一个参数是一个循环层实例。
Bidirectional 对这个循环层创建了第二个单独实例,然后使用一个实例按正序处理输入序列,另一个实例按逆序处理输入序列。
先清除上一步影响释放全局状态,这有助于避免旧模型和层造成混乱
from keras import backend as K
K.clear_session()
model = Sequential()
model.add(layers.Embedding(max_features, 32))
model.add(layers.Bidirectional(layers.LSTM(32))) #在LSTM或者GRU层外使用
model.add(layers.Dense(1, activation=‘sigmoid’))
model.compile(optimizer=‘rmsprop’, loss=‘binary_crossentropy’, metrics=[‘acc’])
history = model.fit(x_train, y_train, epochs=10, batch_size=128, validation_split=0.2)
这个模型的表现比上一节的普通 LSTM 略好, 验证精度超过 89%(88左右) 这个模型也很快就开始过拟合,这并不令人惊讶,因为双向层的参数个数是正序 LSTM 的 2 倍。如下,即在算比directional层是最后还要乘以2
(LSTM层神经元数32,那么参数计算=(32+1)32+3232=2080,这是simpleRNN的参数个数,对于lstm还需要乘4,即8320,最后乘2,即16640
)
再对温度预测使用双向,由于上文已经发现(肯定是更新的温度数据预测的越准),逆向RNN对温度预测并不产生很大的影响,结果也正是如此
from keras.models import Sequential
from keras import layers
from tensorflow.keras.optimizers import RMSprop
model = Sequential()
model.add(layers.Bidirectional(
layers.GRU(32), input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=40,
validation_data=val_gen,
validation_steps=val_steps)
这个模型的表现与普通 GRU 层差不多一样好,在0.26附近。其原因很容易理解:所有的预测能力肯定都来自于正序的那一半网络,因为我们已经知道,逆序的那一半在这个任务上的表现非常糟糕(本例同样是因为,最近的数据比久远的数据更加重要)。
为了提高温度预测问题的性能,你还可以尝试下面这些方法。
‰ 在堆叠循环层中调节每层的单元个数。当前取值在很大程度上是任意选择的,因此可能不是最优的。
‰ 调节 RMSprop 优化器的学习率。
‰ 尝试使用 LSTM 层代替 GRU 层。
‰ 在循环层上面尝试使用更大的密集连接回归器,即更大的 Dense 层或 Dense 层的堆叠。
‰不要忘记最后在测试集上运行性能最佳的模型(即验证 MAE 最小的模型)。否则,你开发的网络架构将会对验证集过拟合。
6.3.10 小结
‰ 我们在第 4 章学过,遇到新问题时,最好首先为你选择的指标建立一个基于常识的基准。如果没有需要打败的基准,那么就无法分辨是否取得了真正的进步。
‰ 在尝试计算代价较高的模型之前,先尝试一些简单的模型,以此证明增加计算代价是有 意义的。有时简单模型就是你的最佳选择。
‰ 如果时间顺序对数据很重要,那么循环网络是一种很适合的方法,与那些先将时间数据展平的模型相比,其性能要更好。
‰ 想要在循环网络中使用 dropout,你应该使用一个不随时间变化的 dropout 掩码与循环 dropout 掩码。这二者都内置于 Keras 的循环层中,所以你只需要使用循环层的 dropout 和 recurrent_dropout 参数即可。
‰ 与单个 RNN 层相比,堆叠 RNN 的表示能力更加强大。但它的计算代价也更高,因此不一定总是需要。
‰ 双向 RNN 从两个方向查看一个序列,它对自然语言处理问题非常有用。
但如果在序列数据中最近的数据比序列开头包含更多的信息,那么这种方法的效果就不明显。
6.4 用卷积神经网络处理序列 (1Dconv)
我们学习了卷积神经网络(convnet),并知道它在计算机视觉问题上表现出色,原因在于它能够进行卷积运算,从局部输入图块中提取特征,并能够将表示模块化,同时可以高效地利用数据。
同样也让它对序列处理特别有效。时间可以被看作一个空间维度,就像二维图像的高度或宽度。
对于某些序列处理问题,这种一维卷积神经网络的效果可以媲美 RNN,而且计算代价通常要小很多。
对于文本分类和时间序列预测等简单任务,小型的一维卷积神经网络可以替代 RNN,而且速度更快。
6.4.1 理解序列数据的一维卷积
前面介绍的卷积层都是二维卷积,从图像张量中提取二维图块并对每个图块应用相同的变换。
按照同样的方法,你也可以使用一维卷积,从序列中提取局部一维序列段。
就像是二维卷积一样,通过一定数量的滤镜如32个,从左到右,从上到下,与输入如(28,28,3)做点积,输出数据如(26,26,32),
记住,是滤镜数量做最后一个维度。然后maxpooling
池化层 Pooling - Keras 中文文档
在一维中,滤镜会沿着时间步,也会和输入做点积,从上到下,获得结果,第一个滤镜做完,结果就放在第一column,之后第二个做完放第二column,
所以滤镜的数量X就是最后一个维度(,X),
然后通过普通maxpooling1D(比如每三个中找一个最大值)沿着时间轴,使维度大幅下降。
还有global maxpooling(每一column找一个最大值),时间轴就变成了1
输入尺寸是 (batch_size, steps, features) 的 3D 张量。
输出尺寸是 (batch_size, features) 的 2D 张量。
这种一维卷积层可以识别序列中的局部模式。
因为对每个序列段执行相同的输入变换,所以在句子中某个位置学到的模式稍后可以在其他位置被识别,这使得一维卷积神经网络具有平移不变性(对于时间平移而言)。
6.4.2 序列数据的一维池化
你已经学过二维池化运算,比如二维平均池化和二维最大池化,在卷积神经网络中用于对图像张量进行空间下采样。
一维也可以做相同的池化运算:从输入中提取一维序列段(即子序列),
然后输出其最大值(最大池化)或平均值(平均池化)。
与二维卷积神经网络一样,该运算也是用于降低一维输入的长度(子采样)。
6.4.3 实现一维卷积神经网络
Keras 中的一维卷积神经网络是 Conv1D 层,其接口类似于 Conv2D。
它接收的输入是形状为 (samples, time, features) 的三维张量,
并返回类似形状的三维张量。
import keras
keras.version
-----------------------------准备 IMDB 数据
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 10000 # 要视为特征的字数
max_len = 500 # 剪切此字数之后的文本(在最常见的单词中)
print(‘Loading data…’)
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), ‘train sequences’)
print(len(x_test), ‘test sequences’)
print(‘Pad sequences (samples x time)’)
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
print(‘x_train shape:’, x_train.shape)
print(‘x_test shape:’, x_test.shape)
Loading data…
25000 train sequences
25000 test sequences
Pad sequences (samples x time)
x_train shape: (25000, 500)
x_test shape: (25000, 500)
一维卷积神经网络的架构与第 5 章的二维卷积神经网络相同,
它是 Conv1D 层和 MaxPooling1D 层的堆叠,
最后是一个全局池化层或 Flatten 层,将三维输出转换为二维输出,(这里就是通过globalMaxpooling将输入的尺寸是 (batch_size, steps, features) 的 3D 张量变为尺寸是 (batch_size, features) 的 2D 张量。)
让你可以向模型中添加一个或多个 Dense 层,用于分类或回归。
一维卷积神经网络可以使用更大的卷积窗口。对于二维卷积层,
3×3 的卷积窗口包含 3×3=9 个特征向量;
但对于一位卷积层,大小为 3 的卷积窗口只包含 3 个卷积向量。
因此,你可以轻松使用大小等于 7 或 9 的一维卷积窗口。
from keras.models import Sequential
from keras import layers
from tensorflow.keras.optimizers import RMSprop
model = Sequential()
#将(25000,500)填充为(25000,500,128)三维向量,输入
#Embedding 层至少需要两个参数:标记的个数(词汇表大小)和嵌入的维度
model.add(layers.Embedding(max_features, 128, input_length=max_len))
#比较一下2Dmodel.add(layers.Conv2D(64, (3, 3), activation=‘relu’))
#32个filter,每一个包含7个向量
model.add(layers.Conv1D(32, 7, activation=‘relu’))
#每5个不重复选最大值 比较一下2Dmodel.add(layers.MaxPooling2D((2, 2)))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation=‘relu’))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(lr=1e-4),
loss=‘binary_crossentropy’,
metrics=[‘acc’])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)
embedding层 10000128 输出(25000,500,128)
第一个conv1d (7128+1)32 filter=32
maxpooling 494//5=98 输出(,98,32)
第二份conv1d (732+1)*32
global 对每个滤镜做出的结果,即所有的column做取最大值,转为2D,所以为(,32)
6.2.3中使用LSTM 精度为88%
验证精度略低于 LSTM,但在 CPU 和 GPU 上的运行速度都要更快
在单词级的情感分类任务上,一维卷积神经网络可以替代循环网络
现在,你可以 使用正确的轮数(4 轮)重新训练这个模型,然后在测试集上运行。
results = model.evaluate(x_test, y_test)
782/782 [==============================] - 4s 6ms/step - loss: 0.4111 - acc: 0.8435
6.4.4 结合 CNN 和 RNN 来处理长序列
一维卷积神经网络分别处理每个输入序列段,所以它对时间步的顺序不敏感, (这里所说顺序的范围要大于局部尺度,即大于卷积窗口的大小),这一点与 RNN 不同.
当然,为了识别更长期的模式,你可以将许多卷积层和池化层堆叠在一起,这样上面的层能够观察到原始输入中更长的序列段,
但这仍然不是一种引入顺序敏感性的好方法。
所以如果拿一维卷积神经网络来进行温度预测,结果肯定会不尽人意。
下面就是做一个实际演示。
注意温度预测数据还是上文的获取和处理方式,下面是仅使用CNN来处理的结果
以下示例复用了前面定义的这些变量:float_data(42451,14)、train_gen(包含samples和target)、val_gen 和 val_steps。
from keras.models import Sequential
from keras import layers
from tensorflow.keras.optimizers import RMSprop
model = Sequential()
model.add(layers.Conv1D(32, 5, activation=‘relu’,
input_shape=(None, float_data.shape[-1])))
model.add(layers.MaxPooling1D(3))
model.add(layers.Conv1D(32, 5, activation=‘relu’))
model.add(layers.MaxPooling1D(3))
model.add(layers.Conv1D(32, 5, activation=‘relu’))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=20,
validation_data=val_gen,
validation_steps=val_steps)
回忆一下 基于常识的方法的结果mae是0.29左右,而这里只有1D CNN的mae达到了0.45左右,还不如我们直接自己猜
这是因为卷积神经网络在输入时间序列的所有位置寻找模式,它并不知道所看到某个模式的时间位置(距开始多长时间,距结束多长时间等)。
对于这个具体的预测问题,对最新数据点的解释与对较早数据点的解释应该并不相同,所以卷积神经网络无法得到有意义的结果。
这种限制对于 IMDB 数据来说并不是问题,因为对于与正面情绪或负面情绪相关联的关键词模式,无论出现在输入句子中的什么位置,它所包含的信息量是一样的。
(联想:这是不是就和IMDB能使用双向RNN会获得优化,而温度预测使用则没有效果相似呢)
所以我们该怎么做?
要想结合卷积神经网络的速度和轻量与 RNN 的顺序敏感性,
一种方法是在 RNN 前面使用一维卷积神经网络作为预处理步骤。
理解:通过CNN卷积神经网络的卷积层,池化层下采样的作用,提取出长序列中的明显特征,再将这些具有明显特征的序列作为输入,输入进具有顺序敏感的RNN网络,这样就同时具备了两个优点
对于那些非常长,以至于 RNN 无法处理的序列(比如包含上千个时间步的序列),这种方法尤其有用。
就比如说在之前的温度预测问题,如果我们不是每6次时间步采一次样,而是每10分钟(即每一时间步采一次)采样,RNN也无法处理
卷积神经网络可以将长的输入序列转换为高级特征组成的更短序列(即下采样的作用)。然后,提取的特征组成的这些序列成为网络中 RNN 的输入。
这种方法在研究论文和实际应用中并不多见,可能是因为很多人并不知道。这种方法非常有效,应该被更多人使用。
我们尝试将其应用于温度预测数据集。因为这种方法允许操作更长的序列,
所以我们可以查看更早的数据(通过增大数据生成器的 lookback 参数)或查看分辨率更高的时间序列(通过减小生成器的 step 参数)。这里我们任意地将 step 减半,得到时间序列的长度变为之前的两倍,温度数据的采样频率变为每 30 分钟一个数据点。
#这里就如上文所说,每30分钟采样一次
step = 3
lookback = 720 # Unchanged
delay = 144 # Unchanged
train_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=0,
max_index=200000,
shuffle=True,
step=step)
val_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=200001,
max_index=300000,
step=step)
test_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=300001,
max_index=None,
step=step)
val_steps = (300000 - 200001 - lookback) // 128
test_steps = (len(float_data) - 300001 - lookback) // 128
###下面是模型,开始是两个 Conv1D 层,然后是一个 GRU 层。
model = Sequential()
model.add(layers.Conv1D(32, 5, activation=‘relu’,
input_shape=(None, float_data.shape[-1])))
model.add(layers.MaxPooling1D(3))
model.add(layers.Conv1D(32, 5, activation=‘relu’))
model.add(layers.GRU(32, dropout=0.1, recurrent_dropout=0.5))
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(), loss=‘mae’)
history = model.fit_generator(train_gen,
steps_per_epoch=500,
epochs=20,
validation_data=val_gen,
validation_steps=val_steps)
由于跟之前一样,对dropout,recurrent_dropout进行了修改,无法使用CUDNN加速,只能使用书本结果了。
二次复习:《Python 深度学习》6.4 用卷积神经网络处理序列_lines[0].split(‘,’)_布拉格沃兹基硕德的博客-CSDN博客
找到相关博客:其结果
利用colab:每次epoch需要90秒左右,快很多,最后验证loss可以达到0.28左右,10轮之后开始过拟合
结论
从验证损失来看,这种架构的效果不如只用正则化 GRU,但速度要快很多。
它查看了两倍的数据量,在本例中可能不是非常有用,但对于其他数据集可能非常重要。
6.4.5 小结
下面是你应该从本节中学到的要点。
‰ 二维卷积神经网络在二维空间中处理视觉模式时表现很好,与此相同,
一维卷积神经网络在处理时间模式时表现也很好。对于某些问题,特别是自然语言处理任务,它可以替代RNN,并且速度更快。
‰ 通常情况下,一维卷积神经网络的架构与计算机视觉领域的二维卷积神经网络很相似, 它将 Conv1D 层和 MaxPooling1D 层堆叠在一起,最后是一个全局池化运算或展平操作。
‰ 因为 RNN 在处理非常长的序列时计算代价很大,但一维卷积神经网络的计算代价很小,所以在 RNN 之前使用一维卷积神经网络作为预处理步骤是一个好主意,这样可以使序列变短,并提取出有用的表示交给 RNN 来处理
本章总结
‰ 你在本章学到了以下技术,它们广泛应用于序列数据(从文本到时间序列)组成的数据集。
ƒ 如何对文本分词。
ƒ 什么是词嵌入,如何使用词嵌入。
ƒ 什么是循环网络,如何使用循环网络。
ƒ 如何堆叠 RNN 层和使用双向 RNN,以构建更加强大的序列处理模型。
ƒ 如何使用一维卷积神经网络来处理序列。
ƒ 如何结合一维卷积神经网络和 RNN 来处理长序列。
‰ 你可以用 RNN 进行时间序列回归(“预测未来”)、时间序列分类、时间序列异常检测和序列标记(比如找出句子中的人名或日期)。
‰ 同样,你可以将一维卷积神经网络用于机器翻译(序列到序列的卷积模型,比如SliceNet)、文档分类和拼写校正。
‰ 如果序列数据的整体顺序很重要,那么最好使用循环网络来处理。时间序列通常都是这样,最近的数据可能比久远的数据包含更多的信息量。
‰ 如果整体顺序没有意义,那么一维卷积神经网络可以实现同样好的效果,而且计算代价更小。文本数据通常都是这样,在句首发现关键词和在句尾发现关键词
7高级的深度学习最佳实践
利用 Keras 函数式 API,你可以构建类图(graph-like)模型(意思是类似图的模型,不要错误理解为class那个类)、在不同的输入之间共享某一层,
并且还可以像使用 Python 函数一样使用 Keras 模型。
Keras 回调函数(callback)和 TensorBoard 基于浏览器的可视化工具,让你可以在训练过程中监控模型。
我们还会讨论其他几种最佳实践,包括批标准化、残差连接、超参数优化和模型集成。
7.1 不用 Sequential 模型的解决方案:Keras 函数式 API
到目前为止,本书介绍的所有神经网络都是用 Sequential 模型实现的。Sequential 模型假设,网络只有一个输入和一个输出,而且网络是层的线性堆叠
这是一个经过普遍验证的假设。但有些情况下这种假设过于死板。
有些网络需要多个独立的输入,有些网络则需要多个输出,而有些网络在层与层之间具有内部分支,这使得网络看起来像是层构成的图(graph)(即类图),而不是层的线性堆叠。
例如,有些任务需要多模态(multimodal)输入
假设有一个深度学习模型,试图利用下列输入来预测一件二手衣服最可能的市场价格:
用户提供的元数据(比如商品品牌、已使用年限等)、
用户提供的文本描述与商品照片。
如果你只有元数据,那么可以使用 one-hot 编码,然后用密集连接网络来预测价格。
如果你只有文本描述,那么可以使用循环神经网络或一维卷积神经网络。
如果你只有图像,那么可以使用二维卷积神经网络。
但怎么才能同时使用这三种数据呢?一种朴素的方法是训练三个独立的模型,然后对三者的预测做加权平均。但这种方法可能不是最优的,
因为模型提取的信息可能存在冗余。
更好的方法是使用一个可以同时查看所有可用的输入模态的模型,
从而联合学习一个更加精确的数据模型——这个模型具有三个输入分支
同样,有些任务需要预测输入数据的多个目标属性。
给定一部小说的文本,你可能希望将它按类别自动分类
(比如爱情小说或惊悚小说),同时还希望预测其大致的写作日期。
当然,你可以训练两个独立的模型:一个用于划分类别,一个用于预测日期。但由于这些属性并不是统计无关的,你可以构建一个更好的模型,用这个模型来学习同时预测类别和日期。这种联合模型将有两个输出,或者说两个头(head,见图 7-3)。
因为类别和日期之间具有相关性,所以知道小说的写作日期有助于模型在小说类别的空间中学到丰富而又准确的表示,反之亦然。
此外,许多最新开发的神经架构要求非线性的网络拓扑结构,即网络结构为有向无环图。
比如,Inception 系列网络(由 Google 的 Szegedy 等人开发)
依赖于 Inception 模块,
其输入被多个并行的卷积分支所处理,然后将这些分支的输出合并为单个张量
最近还有一种趋势是向模型中添加残差连接(residual connection),它最早出现于 ResNet 系列网络(由微软的何恺明等人开发)。
残差连接是将前面的输出张量与后面的输出张量相加,
从而将前面的表示重新注入下游数据流中(见图 7-5),这有助于防止信息处理流程中的信息损失。这种类图网络还有许多其他示例。
这三个重要的使用案例(多输入模型、多输出模型和类图模型),只用 Keras 中的 Sequential 模型类是无法实现的。但是还有另一种更加通用、更加灵活的使用 Keras 的方式,就是函数式API(functional API)。
7.1.1 函数式 API 简介
使用函数式 API,你可以直接操作张量,也可以把层当作函数来使用,接收张量并返回张量(因此得名函数式 API)。
from keras.models import Sequential, Model ##注意Model M大写
from keras import layers
from keras import Input ##注意Input作用
seq_model = Sequential()
seq_model.add(layers.Dense(32, activation=‘relu’, input_shape=(64,)))
seq_model.add(layers.Dense(32, activation=‘relu’))
seq_model.add(layers.Dense(10, activation=‘softmax’))
input_2 (InputLayer) [(None, 64)] 0
dense_6 (Dense) (None, 32) 2080 (64+1 *32 =)
dense_7 (Dense) (None, 32) 1056
dense_8 (Dense) (None, 10) 330
这里只有一点可能看起来有点神奇,就是将 Model 对象实例化只用了一个输入张量和一个输出张量。
Keras 会在后台检索从 input_tensor 到 output_tensor 所包含的每一层,
并将这些层组合成一个类图的数据结构,即一个 Model。
当然,这种方法有效的原因在于, output_tensor 是通过对 input_tensor 进行多次变换得到的。
Input
keras.engine.input_layer.Input()
Input() 用于实例化 Keras 张量。
参数:
shape: 一个尺寸元组(整数),不包含批量大小。 例如,shape=(32,) 表明期望的输入是按批次的 32 维向量。
batch_shape: 一个尺寸元组(整数),包含批量大小。 例如,batch_shape=(10, 32) 表明期望的输入是 10 个 32 维向量。 batch_shape=(None, 32) 表明任意批次大小的 32 维向量。
name: 一个可选的层的名称的字符串。 在一个模型中应该是唯一的(不可以重用一个名字两次)。 如未提供,将自动生成。
dtype: 输入所期望的数据类型,字符串表示 (float32, float64, int32…)
sparse: 一个布尔值,指明需要创建的占位符是否是稀疏的。
tensor: 可选的可封装到 Input 层的现有张量。 如果设定了,那么这个层将不会创建占位符张量。
返回:
一个张量。
7.1.2 多输入模型
函数式 API 可用于构建具有多个输入的模型。
通常情况下,这种模型会在某一时刻用一个可以组合多个张量的层将不同的输入分支合并,张量组合方式可能是相加、连接等。
这通常利用 Keras 的合并运算来实现,比如 keras.layers.add、keras.layers.concatenate 等。
我们来看一个非常简单的多输入模型示例——一个问答模型。
典型的问答模型有两个输入:
一个自然语言描述的问题和一个文本片段(比如新闻文章),
后者提供用于回答问题的信息。
然后模型要生成一个回答,在最简单的情况下,这个回答只包含一个词,可以通过对某个预定义的词表做 softmax 得到(见图 7-6)。
我们设置了两个独立分支,首先将文本输入和问题输入分别编码为表示向量,然后连接这些向量,最后,在连接好的表示上添加一个 softmax 分类器
from keras.models import Model
from keras import layers
from keras import Input
text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500
##补充,这是先把文本变为整数,再通过embedding变成词向量
text_input = Input(shape=(None,), dtype=‘int32’, name=‘text’)
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)
question_input = Input(shape=(None,),dtype=‘int32’,name=‘question’)
embedded_question = layers.Embedding(question_vocabulary_size,32)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)
text (InputLayer) [(None, None)] 0 []
question (InputLayer) [(None, None)] 0 []
embedding (Embedding) (None, None, 64) 640000 [‘text[0][0]’]
embedding_1 (Embedding) (None, None, 32) 320000 [‘question[0][0]’]
lstm (LSTM) (None, 32) 12416=(64+1 32+3232)*4 [‘embedding[0][0]’]
lstm_1 (LSTM) (None, 16) 3136=(32+1 16+1616)*4 [‘embedding_1[0][0]’]
concatenate (Concatenate) (None, 48) 0 [‘lstm[0][0]’,
‘lstm_1[0][0]’]
dense (Dense) (None, 500) 24500 [‘concatenate[0][0]’]
cannot import name ‘plot_model‘ from ‘keras.utils‘解决方法_importerror: cannot import name 'plot_model_hopedu的博客-CSDN博客
#生成模型图像,并且解决了报错问题
from keras.utils.vis_utils import plot_model
plot_model(model,show_shapes=True,to_file= ‘Multi-input.png’)
from IPython.display import Image
Image(filename= ‘Multi-input.png’ )
import numpy as np
num_samples = 1000
max_length = 100
##生成1-10000范围 大小为1000*100的伪数据
text = np.random.randint(1, text_vocabulary_size, size=(num_samples, max_length))
question =
np.random.randint(1, question_vocabulary_size, size=(num_samples, max_length))
#原作者由于compile时使用的是二元交叉熵,没有用sparse那个,所以answer还进行了one-hot编码
answers = np.random.randint(answer_vocabulary_size, size=(num_samples))
answers = keras.utils.to_categorical(answers, answer_vocabulary_size)
model.fit([text, question], answers, epochs=10, batch_size=128)
##使用输入组成的字典来拟合 (只有对输入进行命名之后才能用这种方法)
model.fit({‘text’: text, ‘question’: question}, answers, epochs=10, batch_size=128)
7.1.3 多输出模型
利用相同的方法,我们还可以使用函数式 API 来构建
具有多个输出(或多头)的模型。
一个简单的例子就是一个网络试图同时预测数据的不同性质,比如一个网络,输入某个匿名人士的一系列社交媒体发帖,然后尝试预测那个人的属性,
比如年龄、性别和收入水平
(其中年龄为回归问题,性别为二分类问题,收入水平为多分类问题)
from keras import layers
from keras import Input
from keras.models import Model
vocabulary_size = 50000
num_income_groups = 10
posts_input = Input(shape=(None,), dtype=‘int32’, name=‘posts’)
embedded_posts = layers.Embedding(256, vocabulary_size)(posts_input)
x = layers.Conv1D(128, 5, activation=‘relu’)(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation=‘relu’)(x)
x = layers.Conv1D(256, 5, activation=‘relu’)(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation=‘relu’)(x)
x = layers.Conv1D(256, 5, activation=‘relu’)(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128, activation=‘relu’)(x)
##注意输出层具有名称,在后面compile时即可指名,回归问题没有激活函数
age_prediction = layers.Dense(1, name=‘age’)(x)
income_prediction = layers.Dense(num_income_groups,
activation=‘softmax’,
name=‘income’)(x)
gender_prediction = layers.Dense(1, activation=‘sigmoid’, name=‘gender’)(x)
model = Model(posts_input,[age_prediction, income_prediction, gender_prediction])
重要的是,训练这种模型需要能够对网络的各个头指定不同的损失函数,例如,年龄预测是标量回归任务,而性别预测是二分类任务,二者需要不同的训练过程。
但是,梯度下降要求将一个标量最小化,所以为了能够训练模型,我们必须将这些损失合并为单个标量。
合并不同损失最简单的方法就是对所有损失求和。(这会造成问题)
在 Keras 中,你可以在编译时使用损失组成的列表或字典来为不同输出指定不同损失,然后将得到的损失值相加得到一个全局损失,并在训练过程中将这个损失最小化。
注意,严重不平衡的损失贡献会导致模型表示针对单个损失值最大的任务优先进行优化, 而不考虑其他任务的优化。
理解:----根据视频讲解,在本例中,由于有三个loss函数,会获得三个值,梯度下降对一个值求微分,所以将三个值其求和,但是由于年龄估计的误差,相比二元交叉熵(为0,1的误差)和sparse交叉要大的多,所以模型会向年龄误差loss更小的方向去拟合,其他任务则无法获得优化。
为了解决这个问题,我们可以为每个损失值对最终损失的贡献分配不同大小的重要性。
如果不同的损失值具有不同的取值范围,那么这一方法尤其有用。
比如,用于年龄回归任务的均方误差(MSE)损失值通常在 3~5 左右,
而用于性别分类任务的交叉熵损失值可能低至 0.1。
注意:这里是loss值,是根据损失值去设置权重
在这种情况下,为了平衡不同损失的贡献,
我们可以让交叉熵损失的权重10,而 MSE损失的权重取 0.5。
##未命名时,注意前后顺序,模型中是什么这里就按什么顺序
model.compile(optimizer=‘rmsprop’,
loss=[‘mse’, ‘categorical_crossentropy’, ‘binary_crossentropy’],
loss_weights=[0.25, 1. , 10. ])
##-或者在上面命名了输出值的情况下指定
model.compile(optimizer=‘rmsprop’,
loss={ ‘age’: ‘mse’,
‘income’: ‘categorical_crossentropy’,
‘gender’: ‘binary_crossentropy’},
loss_weights={‘age’: 0.25, ‘income’: 1. , ‘gender’: 10. })
##与多输入模型相同,多输出模型的训练输入数据可以是 Numpy 数组组成的列表或字典。
##未命名时
model.fit(posts, [age_targets, income_targets, gender_targets],
epochs=10, batch_size=64)
model.fit(posts, {‘age’: age_targets,
‘income’: income_targets,
‘gender’: gender_targets},
epochs=10, batch_size=64)
由于原书并未具体设计训练数据,所以采用视频中生成数据方法。
import numpy as np
vocabulary_size = 50000
num_income_groups = 10
num_samples = 1000
max_length = 300
posts = np.random.randint(0,vocabulary_size,size=(num_samples,max_length))
##生成0-90年龄
age_targets= np.random.random(num_samples)*80 + 10
income_targets = np.random.randint(0,num_income_groups,num_samples)
gender_targets = np.random.randint(0,2,num_samples)
##差别:回归loss使用mae,mse误差平方值可能很大,多分类loss使用sparse,##其targets就不需要one-hot
model.compile(optimizer=‘rmsprop’,
loss=[‘mae’, ‘sparse_categorical_crossentropy’, ‘binary_crossentropy’],
loss_weights=[0.004, 1. , 10. ])
遇错:OOM when allocating tensor with shape[64,300,50000] and type float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc
原因:一下子放进测试数据太多,爆显存了,colab的15g显存勉强完成了
Epoch 1/10
16/16 [==============================] - 51s 2s/step –
loss: 9.4427 - age_loss: 50.3379 - income_loss: 2.3029 - gender_loss: 0.6938
即使占比权重改为0.004 还是可以看出age_loss占比很多
7.1.4 层组成的有向无环图
利用函数式 API,我们不仅可以构建多输入和多输出的模型,而且还可以实现具有复杂的内部拓扑结构的网络。
Keras 中神经网络可以是层组成的任意有向无环图(directed acyclic graph)。
无环(acyclic)这个限定词很重要,即这些图不能有循环。张量 x 不能成为生成 x 的某一层的输入。唯一允许的处理循环(即循环连接)是循环层的内部循环。
一些常见的神经网络组件都以图的形式实现。两个著名的组件是 Inception 模块和残差连接
1×1 卷积的作用
我们已经知道,卷积能够在输入张量的每一个方块周围提取空间图块,并对所有图块应用相同的变换。
极端情况是提取的图块只包含一个方块。这时卷积运算等价于让每个方块
向量经过一个 Dense 层:它计算得到的特征能够将输入张量通道中的信息混合在一起,但不会将跨空间的信息混合在一起(因为它一次只查看一个方块)。
这种 1×1 卷积[也叫作逐点卷积(pointwise convolution)]是 Inception 模块的特色,它有助于区分开通道特征学习和空间特征学习。
理解:之前对图片卷积,一般都是使用33的filter,一次可以查看9个方块信息,也就是会混合跨空间的信息,同时,由于Conv2D层的filter数32,64就是后面的channel,其实还会混合进通道channel的信息。而11的滤镜只查看1个方块,空间平面即周围方块的信息完全不获得,就只专注于获得filter即之后的channel信息。所以说它有助于区分开通道特征学习和空间特征学习。
最后需要concat连接在一起,所以使用了padding,以及相同的stride
每个分支都有相同的步幅值(2),这对于保持所有分支输出具有相同的尺寸是很有必要的,这样你才能将它们连接在一起
补充:书上没有padding,导致报错
from keras import layers
from keras import Input
from keras.models import Model
#输入图片 正常是输入4维,还有一个samples
x=Input(shape=(32,32,3))
##其中1,3都是代表滤镜大小
branch_a = layers.Conv2D(128, 1,activation=‘relu’, strides=2,padding=‘same’)(x)
branch_b = layers.Conv2D(128, 1, activation=‘relu’,padding=‘same’)(x)
branch_b = layers.Conv2D(128, 3, activation=‘relu’, strides=2,padding=‘same’)(branch_b)
branch_c = layers.AveragePooling2D(3, strides=2,padding=‘same’)(x)
branch_c = layers.Conv2D(128, 3, activation=‘relu’,padding=‘same’)(branch_c)
branch_d = layers.Conv2D(128, 1, activation=‘relu’,padding=‘same’)(x)
branch_d = layers.Conv2D(128, 3, activation=‘relu’,padding=‘same’)(branch_d)
branch_d = layers.Conv2D(128, 3, activation=‘relu’, strides=2,padding=‘same’)(branch_d)
output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)
##补充 假设使用Mnist
output=layer.Flatten()(output)
output=layer.Dense(512, activation=‘relu’)) (output)
output=layer.(Dense(10, activation=‘sigmoid’)) (output)
input_1 (InputLayer) [(None, 32, 32, 3)] 0 []
conv2d_4 (Conv2D) (None, 32, 32, 128) 512=1*3+1 128 [‘input_1[0][0]’]
conv2d_1 (Conv2D) (None, 32, 32, 128) 512 [‘input_1[0][0]’]
average_pooling2d (AveragePool (None, 16, 16, 3) 0 [‘input_1[0][0]’]
ing2D) 9128+1 * 128
conv2d_5 (Conv2D) (None, 32, 32, 128) 147584 [‘conv2d_4[0][0]’]
conv2d (Conv2D) (None, 16, 16, 128) 512 [‘input_1[0][0]’]
conv2d_2 (Conv2D) (None, 16, 16, 128) 147584 [‘conv2d_1[0][0]’]
conv2d_3 (Conv2D) (None, 16, 16, 128) 3584 ['average_pooling2d[0][0]
conv2d_6 (Conv2D) (None, 16, 16, 128) 147584 [‘conv2d_5[0][0]’]
concatenate (Concatenate) (None, 16, 16, 512) 0 [‘conv2d[0][0]’,
‘conv2d_2[0][0]’, ‘conv2d_3[0][0]’, ‘conv2d_6[0][0]’]
补充层参数:
书上到此已经结束,根据讲解继续,按照之前CNN套路,flatten后添加Dense,增加乱数,compile,评估
model.compile(optimizer=‘rmsprop’ , loss=‘sparse_categorical_crossentropy’, metrics=[ ’ acc’])
import numpy as np
x_train = np.random.random( ( 1000,32,32,3))
y_train = np.random.randint(0,10,100)
mode1.fit(x_train, y_train,epochs=10, batch_size=128)
score = model.evaluate(x_train, y_train)
print( score)#
注意,完整的Inception V3架构内置于Keras中,位置keras.applications.inception_v3. InceptionV3,其中包括在 ImageNet 数据集上预训练得到的权重。与其密切相关的另一个模型是 Xception,它也是 Keras 的 applications 模块的一部分。Xception 代表极端 Inception
(extreme inception),它是一种卷积神经网络架构,
Xception 将分别进行通道特征学习与空间特征学习的想法推向逻辑上的极端,并将 Inception 模块替换为深度可分离卷积,其中包括一个逐深度卷积(即一个空间卷积,分别对每个输入通道进行处理)和后面的一个逐点卷积(即一个 1×1 卷积)。
理解:拿图片进行理解。就是分别只对3层中的每一层分开做conv,然后和使用1*1只对Channel做卷积,这个模型就是作者创造的,在arxiv上
2. 残差连接ResNet
ResNet应用 Applications - Keras 中文文档
残差连接(residual connection)是一种常见的类图网络组件。
残差连接解决了困扰所有大规模深度学习模型的两个共性问题:
梯度消失和表示瓶颈。
通常来说,向任何多于 10 层的模型中添加残差连接,都可能会有所帮助。
深度学习中的表示瓶颈
在 Sequential 模型中,每个连续的表示层都构建于前一层之上,这意味着它只能访问前一层激活中包含的信息。
如果某一层太小(比如特征维度太低),那么模型将会受限于该层激活中能够塞入多少信息。
深度学习中的梯度消失
反向传播是用于训练深度神经网络的主要算法,其工作原理是将来自输出损失的反馈信号向下传播到更底部的层。如果这个反馈信号的传播需要经过很多层,那么信号可能会变得非常
微弱,甚至完全丢失,导致网络无法训练。这个问题被称为梯度消失(vanishing gradient)。
之前的RNN处理长序列文本也有这个问题,
LSTM等层:它引入了一个携带轨道(Carry track),可以在与主处理轨道平行的轨道上传播信息。
残差连接是让前面某层的输出作为后面某层的输入,从而在序列网络中有效地创造了一条捷径。
前面层的输出没有与后面层的激活连接在一起,而是与后面层的激活相加(这里假设两个激活的形状相同)。(即不是使用之前的Concatenate,而是add)
如果它们的形状不同,我们可以用一个线性变换将前面层的激活改变成目标形状
例如,这个线性变换可以是不带激活的 Dense 层
对于卷积特征图,可以是不带激活 1×1 卷积,
比如说一个是3232128,另一个是6464128,可以做一个1*1的卷积,使得stride=2,变成相同的形状 。
如果特征图的尺寸相同,在 Keras 中实现残差连接的方法如下,
用的是恒等残差连接(identity residual connection)。使用add
from keras import layers
x= Input(shape=(32,32,3))
y = layers.Conv2D(128, 3, activation=‘relu’, padding=‘same’)(x)
y = layers.Conv2D(128, 3, activation=‘relu’, padding=‘same’)(y)
y = layers.Conv2D(128, 3, activation=‘relu’, padding=‘same’)(y)
y = layers.add([y, x])
model = Model(x,y)
from keras import layers
x= Input(shape=(32,32,3))
y = layers.Conv2D(128, 3, activation=‘relu’, padding=‘same’)(x)
y = layers.Conv2D(128, 3, activation=‘relu’, padding=‘same’)(y)
y = layers.MaxPooling2D(2, strides=2)(y) ##maxpooling stride默认就是2
##y经过maxpooling,形状改变,因此residual也需要改变
residual = layers.Conv2D(128, 1, strides=2, padding=‘same’)(x)
y = layers.add([y, residual])
model = Model(x, y)
7.1.5 共享层权重
函数式 API 还有一个重要特性,那就是能够多次重复使用一个层实例。
如果你对一个层实例调用两次,而不是每次调用都实例化一个新层,那么每次调用可以重复使用相同的权重。
你需要用一个 LSTM 层来处理两个句子。这个 LSTM 层的表示(即它的权重)是同时基于两个输入来学习的。
我们将其称为连体 LSTM(Siamese LSTM)或共享LSTM(shared LSTM)模型。
使用 Keras 函数式 API 中的层共享(层重复使用)可以实现
from keras import layers
from keras import Input
from keras.models import Model
lstm = layers.LSTM(32)
left_input = Input(shape=(None, 128))
left_output = lstm(left_input)
right_input = Input(shape=(None, 128))
right_output = lstm(right_input)
merged = layers.concatenate([left_output, right_output], axis=-1)
predictions = layers.Dense(1, activation=‘sigmoid’)(merged)
model = Model([left_input, right_input], predictions)
##model.fit([left_data, right_data], targets)
7.1.6 将模型作为层
之前我们就学到了,模型也可以像层一样使用,在函数式 API 中,也可以像使用层一样使用模型。实际上,你可以将模型看作“更大的层”。
这意味着你可以在一个输入张量上调用模型,并得到一个输出张量。
y = model(x)
如果模型具有多个输入张量和多个输出张量,那么应该用张量列表来调用模型。
y1, y2 = model([x1, x2])
在调用模型实例时,就是在重复使用模型的权重,正如在调用层实例时,就是在重复使用层的权重。
通过重复使用模型实例可以构建一个简单的例子,就是一个使用双摄像头作为输入的视觉模型:两个平行的摄像头,相距几厘米(一英寸)。
你不需要两个单独的模型从左右两个摄像头中分别提取视觉特征,然后再将二者合并。
这样的底层处理可以在两个输入之间共享,即通过共享层(使用相同的权重,从而共享相同的表示)来实现。就像是我们人类的双眼一样。
from keras import layers
from keras import applications
from keras import Input
#不需要原模型的密集分类层,只拿卷积基,原因同样
xception_base = applications.Xception(weights=None, include_top=False)
left_input = Input(shape=(250, 250, 3))
right_input = Input(shape=(250, 250, 3))
left_features = xception_base(left_input)
right_input = xception_base(right_input)
merged_features = layers.concatenate( [left_features, right_input], axis=-1)
7.2 使用 Keras 回调函数和 TensorBoard 来检查并监控深度学习模型
本节作用就是:在训练过程中如何更好地访问并控制模型内部过程的方法。
根据之前的学习,我们在fit之后便完全无法控制模型的训练,所以我们现在想要能够控制模型的训练,所以就产生了此方法。
7.2.1 训练过程使用回调函数CallBack
回调函数 Callbacks - Keras 中文文档
训练模型时,很多事情一开始都无法预测。尤其是你不知道需要多少轮才能得到最佳验证损失。
前面所有例子都采用这样一种策略:训练足够多的轮次,这时模型已经开始过拟合,根据这第一次运行来确定训练所需要的正确轮数,然后使用这个最佳轮数从头开始再启动一次新的训练。当然,这种方法很浪费。
处理这个问题的更好方法是,当观测到验证损失不再改善时就停止训练。
这可以使用 Keras回调函数来实现。
回调函数(callback)是在调用 fit 时传入模型的一个对象(即实现特定方法
的类实例),它在训练过程中的不同时间点都会被模型调用。
它可以访问关于模型状态与性能的所有可用数据,还可以采取行动:中断训练、保存模型、加载一组不同的权重或改变模型的状态。
keras.callbacks 模块包含许多内置的回调函数,下面列出了其中一些,但还有很多没有列出来。
注:进度条就是一个回调函数!!
ModelCheckpoint
keras.callbacks.ModelCheckpoint(filepath, monitor=‘val_loss’, verbose=0, save_best_only=False, save_weights_only=False, mode=‘auto’, period=1)
参数
filepath: 字符串,保存模型的路径。
monitor: 被监测的数据。
verbose: 详细信息模式,0 或者 1 。
save_best_only: 如果 save_best_only=True,被监测数据的最佳模型就不会被覆盖。
mode: {auto, min, max} 的其中之一。 如果 save_best_only=True,那么是否覆盖保存文件的决定就取决于被监测数据的最大或者最小值。 对于 val_acc,模式就会是 max,而对于 val_loss,模式就需要是 min,等等。 在 auto 模式中,方向会自动从被监测的数据的名字中判断出来。
save_weights_only: 如果 True,那么只有模型的权重会被保存 (model.save_weights(filepath)), 否则的话,整个模型会被保存 (model.save(filepath))。
period: 每个检查点之间的间隔(训练轮数)。
EarlyStopping
keras.callbacks.EarlyStopping(monitor=‘val_loss’, min_delta=0, patience=0, verbose=0, mode=‘auto’, baseline=None, restore_best_weights=False)
当被监测的数量不再提升,则停止训练。
参数
monitor: 被监测的数据。
min_delta: 在被监测的数据中被认为是提升的最小变化,例如,小于 min_delta 的绝对变化会被认为没有提升。
patience: 没有进步的训练轮数,在这之后训练就会被停止。
verbose: 详细信息模式。
mode: {auto, min, max} 其中之一。 在 min 模式中, 当被监测的数据停止下降,训练就会停止;在 max 模式中,当被监测的数据停止上升,训练就会停止;在 auto 模式中,方向会自动从被监测的数据的名字中判断出来。
baseline: 要监控的数量的基准值。如果模型没有显示基准的改善,训练将停止。
restore_best_weights: 是否从具有监测数量的最佳值的时期恢复模型权重.
如果为 False,则使用在训练的最后一步获得的模型权重。
import keras
#当需要使用很多callback时,可以写个list,写好参数,直接调用
callbacks_list = [
keras.callbacks.EarlyStopping(
monitor=‘val_loss’, ##这里原文是监控acc,即训练资料的准确率
但是随着训练,acc就是会一直上升,改为val_loss
patience=3, ), #连续三轮都没有降低
keras.callbacks.ModelCheckpoint(
filepath='my_model.h5',
monitor='val_loss',
save_best_only=True,)
]
model.compile(optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘acc’])
model.fit( x, y,epochs=10, batch_size=32,
callbacks=callbacks_list,
validation_data=(x_val, y_val)
)
一般这两个回调函数会一起用,即使earlyStopping及时停止了过拟合的模型,但还是已经产生了恶化,所以利用modelCheckPoint保存最好参数的模型!!
下面的减少LR也会经常一起用。
ReduceLROnPlateau
keras.callbacks.ReduceLROnPlateau(monitor=‘val_loss’, factor=0.1, patience=10, verbose=0, mode=‘auto’, min_delta=0.0001, cooldown=0, min_lr=0)
当标准评估停止提升时,降低学习速率。
关键!!!:当学习停止时,模型总是会受益于降低 2-10 倍的学习速率。
这个回调函数监测一个数据并且当这个数据在一定「有耐心」的训练轮之后还没有进步, 那么学习速率就会被降低。
参数:
monitor: 被监测的数据。
factor: 学习速率被降低的因数。新的学习速率=学习速率*因数
patience: 没有进步的训练轮数,在这之后训练速率会被降低。
verbose: 整数。0:安静,1:更新信息。
mode: {auto, min, max} 其中之一。如果是 min 模式,学习速率会被降低如果被监测的数据已经停止下降; 在 max 模式,学习塑料会被降低如果被监测的数据已经停止上升; 在 auto 模式,方向会被从被监测的数据中自动推断出来。
min_delta: 对于测量新的最优化的阀值,只关注巨大的改变。
cooldown: 在学习速率被降低之后,重新恢复正常操作之前等待的训练轮数量。
min_lr: 学习速率的下边界。
示例
callbacks_list = [
keras.callbacks.ReduceLROnPlateau(
monitor=‘val_loss’
factor=0.5,
patience=2, ##两轮之后loss没有比上次更低,就会减半 一直到epoch结束
min_lr=0.001
)
]
model.fit(x, y, epochs=10, batch_size=32,
callbacks=callbacks_list,
validation_data=(x_val, y_val))
TensorBoard :TensorFlow 的可视化框架
本节将介绍 TensorBoard,一个内置于 TensorFlow 中的基于浏览器的可视化工具。注意,只有当 Keras 使用 TensorFlow 后端时,这一方法才能用于 Keras 模型。
TensorBoard 的主要用途是,在训练过程中帮助你以可视化的方法监控模型内部发生的一切。
如果你监控了除模型最终损失之外的更多信息,那么可以更清楚地了解模型做了什么、没做什么,并且能够更快地取得进展。
‰ 在训练过程中以可视化的方式监控指标
‰ 将模型架构可视化
‰ 将激活和梯度的直方图可视化
‰ 以三维的形式研究嵌入
我们用一个简单的例子来演示这些功能:在 IMDB 情感分析任务上训练一个一维卷积神经网络。
这个模型类似于 6.4 节的模型。我们将只考虑 IMDB 词表中的前 2000 个单词,这样更易于将词嵌入可视化。
import keras
from keras import layers
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 2000
max_len = 500
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
model = keras.models.Sequential()
model.add(layers.Embedding(max_features, 128,input_length=max_len,
name=‘embed’))
model.add(layers.Conv1D(32, 7, activation=‘relu’))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation=‘relu’))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile( optimizer=‘rmsprop’,
loss=‘binary_crossentropy’,
metrics=[‘acc’])
在开始使用 TensorBoard 之前,我们需要创建一个目录,用于保存它生成的日志文件
mkdir my_tensorboard_log_dir
callbacks = [ keras.callbacks.TensorBoard(
log_dir=‘my_log_dir’,
histogram_freq=1,
embeddings_freq=1,
)]
history = model.fit( x_train, y_train,
epochs=20,
batch_size=128,
validation_split=0.2,
callbacks=callbacks
)
✔✔✔ TensorBoard 的正确打开方法(含错误解决方法,超详细) ✔✔✔_打开tensorboard_FeverTwice的博客-CSDN博客
笔者原来的书里面使用 tensorboard --logdir = my_log_dir2打开文件的,但是现在改版了就不适用了,我们要使用tensorboard --logdir "abc"打开文件,abc是文件夹名(只需打开你输出的那个日志就行了,不需要具体到那个v2文件)
___________________________________________________________________–
成功!
tensorboard --logdir “D:\softwareprocess\jupyter TensorFlow workspace\deep-learning-with-python-notebooks-master\deep-learning-with-python-notebooks-master\first_edition\my_log_dir”
用浏览器打开 http://localhost:6006,并查看模型的训练过程
注意!!!在使用Anaconda prompt打开tensorboard时,居然会导致模型拟合时的loss,acc完全不改变
TensorBoard:指标监控
TensorBoard:激活直方图
GRAPHS(图)标签页显示的是 Keras 模型背后的底层 TensorFlow 运算图的交互式可视化。
相当复杂,所以在 Keras 中定义模型时可能看起来很简单,只是几个基本层的堆叠;但在底层,你需要构建相当复杂的图
结构来使其生效。其中许多内容都与梯度下降过程有关
Keras 还提供了另一种更简洁的方法——keras.utils.plot_model 函数,它可以
将模型绘制为层组成的图,而不是 TensorFlow 运算组成的图。使用这个函数需要安装 Python 的
pydot 库和 pydot-ng 库,还需要安装 graphviz 库。 即上文出现的graphviz
from keras.utils.vis_utils import plot_model
plot_model(model,show_shapes=True,to_file= ‘Multi-input.png’)
from IPython.display import Image
Image(filename= ‘Multi-input.png’ )
7.3 让模型性能发挥到极致
提供一套用于构建最先进深度学习模型的必备技术的快速指南,从而让模型由“具有不错的性能”上升到“性能卓越且能够赢得机器学习竞赛”。
7.3.1 高级架构模式
7.1.4 节详细介绍过一种重要的设计模式——残差连接。还有另外两种设计模式你也应该知道:
标准化和深度可分离卷积。这些模式在构建高性能深度卷积神经网络时特别重要.
最常见的数据标准化形式就是在本书中多次见到的那种形式:
将数据减去其平均值使其中心为 0,然后将数据除以其标准差使其标准差为 1。这种做法假设数据服从正态分布(也叫高斯分布),并确保让该分布的中心为 0,同时缩放到方差为 1。
normalized_data = (data - np.mean(data, axis=…)) / np.std(data, axis=…)
前面的示例都是在将数据输入模型之前对数据做标准化。
但在网络的每一次变换之后都应该考虑数据标准化。
即使输入 Dense 或 Conv2D 网络的数据均值为 0、方差为 1,也没有理由假定网络输出的数据也是这样。
理解:即使我们控制输入值是标准化的,在经过层层的线性和非线性转换,输入可能就会变得很大或者很小,假设在sigmoid中,此时大致或者小值的斜率即梯度都容易变成0,即梯度消失。
批标准化(batch normalization)是 Ioffe 和 Szegedy 在 2015 年提出的一种层的类型 (在Keras 中是 BatchNormalization),
即使在训练过程中均值和方差随时间发生变化,它也可以适应性地将数据标准化。
批标准化的工作原理是,训练过程中在内部保存已读取每批数据均值和方差的指数移动平均值即(EMA) ( 光滑曲线方法) 。
理解:我们每次都是分批次输入数据,所以是无法通过计算整体数据的均值和方差的,所以实际上是通过EMA(0.9*a+0.1b)去近似算出来的。
也因为它不只是做普通的normalization,还会估计最适期望和标准差,所以一次批标准化需要估计4倍参数
批标准化的主要效果是,它有助于梯度传播(这一点和残差连接很像),因此允许更深的网络。
BatchNormalization 层通常在卷积层或密集连接层之后使用。
conv_model.add(layers.Conv2D(32, 3, activation=‘relu’))
conv_model.add(layers.BatchNormalization())
dense_model.add(layers.Dense(32, activation=‘relu’))
dense_model.add(layers.BatchNormalization())
BatchNormalization 层接收一个 axis 参数,它指定应该对哪个特征轴做标准化。这个参数的默认值是 -1,即输入张量的最后一个轴,即channel。
对于 Dense 层、Conv1D 层、RNN 层和将 data_format 设为 “channels_last”(通道在后)的 Conv2D 层,这个默认值都是正确的。
补充:在tensorflow中都是如此,但在其他框架中可能就需要修改!
Dense中最好使用relu,如果使用多层dense,sigmoid容易梯度消失
2. 深度可分离卷积
———————————————————————————————————————
其实就是之前学习Xception中的极端卷积,即将空间特征学习和通道特殊学习分开。
假设输入是一个282832,下一层滤镜为64,对于最初的Conv2D来说,使用33的滤镜去卷积,其结果会同时包含空间特征和通道特征,那么参数估计就为(3332)64,没有加bias。
而对于SeparableConv2D,在空间特征上,使用33的filter,参数=3332,
即heightwidthinput_depth
在通道上使用11的filter,参数=(1(bias)+11)3264,即Input_depthout_dep
参数之和远小于Conv2D
——————————————————————————————————
深度可分离卷积(depthwise separable convolution)层(SeparableConv2D)的
作用。
这个层对输入的每个通道分别执行空间卷积,然后通过逐点卷积(1×1 卷积)将输出通道混合,如图所示。这相当于将空间特征学习和通道特征学习分开。
假设输入中的空间位置高度相关,但不同的通道之间相对独立,那么这么做是很有意义的。它需要的参数要少很多,计算量也更小,因此可以得到更小、更快的模型。
如果只用有限的数据从头开始训练小型模型,这些优点就变得尤为重要。
from keras.models import Sequential, Model
from keras import layers
height = 64
width = 64
channels = 3
num_classes = 10
model = Sequential()
model.add(layers.SeparableConv2D(32, 3, activation=‘relu’,
input_shape=(height, width, channels,)))
model.add(layers.SeparableConv2D(64, 3, activation=‘relu’))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation=‘relu’))
model.add(layers.SeparableConv2D(128, 3, activation=‘relu’))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation=‘relu’))
model.add(layers.SeparableConv2D(128, 3, activation=‘relu’))
model.add(layers.GlobalAveragePooling2D())
##假如真要对比应该使用model.add(layers.MaxPooling2D())
model.add(layers.Dense(32, activation=‘relu’))
model.add(layers.Dense(num_classes, activation=‘softmax’))
model.compile(optimizer=‘rmsprop’, loss=‘categorical_crossentropy’)
7.3.2 超参数优化(hyperparameter optimiser)
构建深度学习模型时,你必须做出许多看似随意的决定:
应该堆叠多少层?每层应该包含多少个单元或过滤器?
激活应该使用 relu 还是其他函数?
在某一层之后是否应该使用BatchNormalization ?
应该使用多大的 dropout 比率?还有很多。
这些在架构层面的参数叫作超参数(hyperparameter),以便将其与模型参数区分开来,后者通过反向传播进行训练。
补充:李宏毅learn2learn
通常情况下,随机搜索(随机选择需要评估的超参数,并重复这一过程)就是最好的解决方案,虽然这也是最简单的解决方案。
作者补充:但我发现有一种工具确实比随机搜索更好,它就是Hyperopt。它是一个用于超参数优化的 Python 库,其内部使用 Parzen 估计器的树来预测哪组超
参数可能会得到好的结果。另一个叫作 Hyperas 的库将 Hyperopt 与 Keras 模型集成在一起,一定要试试。
7.3.3 模型集成(model ensembling)
想要在一项任务上获得最佳结果,另一种强大的技术是模型集成(model ensembling)。
集成是指将一系列不同模型的预测结果汇集到一起,从而得到更好的预测结果。
集成依赖于这样的假设,即对于独立训练的不同良好模型,它们表现良好可能是因为不同的原因:每个模型都从略有不同的角度观察数据来做出预测,得到了“真相”的一部分,但不是全部真相。就像盲人摸象的古代寓言
我们以分类问题为例。想要将一组分类器的预测结果汇集在一起[即分类器集成(ensemble the classifiers)],最简单的方法就是将它们的预测结果取平均值作为预测结果。
preds_a = model_a.predict(x_val) preds_b = model_b.predict(x_val)
preds_c = model_c.predict(x_val) preds_d = model_d.predict(x_val)
final_preds = 0.25 * (preds_a + preds_b + preds_c + preds_d)
只有这组分类器中每一个的性能差不多一样好时,这种方法才奏效。如果其中一个分类器
性能比其他的差很多,那么最终预测结果可能不如这一组中的最佳分类器那么好。
将分类器集成有一个更聪明的做法,即加权平均,其权重在验证数据上学习得到。
通常来说,更好的分类器被赋予更大的权重,而较差的分类器则被赋予较小的权重。
为了找到一组好的集成权重,你可以使用随机搜索或简单的优化算法(比如 Nelder-Mead 方法)。
final_preds = 0.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d
想要保证集成方法有效,关键在于这组分类器的多样性(diversity)
用机器学习的术语来说,如果所有模型的偏差都在同一个方向上,那么集成也会保留同样的偏差。如果各个模型的偏差在不同方向上,那么这些偏差会彼此抵消,集成结果会更加稳定、更加准确。
因此,集成的模型应该尽可能好,同时尽可能不同。
这通常意味着使用非常不同的架构, 甚至使用不同类型的机器学习方法。
有一件事情基本上是不值得做的,就是对相同的网络,使用不同的随机初始化多次独立训练,然后集成。
作者说:近年来,一种在实践中非常成功的基本集成方法是宽且深(wide and deep)的模型类型
注:第一次接触推荐系统之Wide&Deep
8 生成式深度学习
本章包括以下内容:
‰ 使用 LSTM 生成文本 第二版中是使用RNN和transform,就是做出一个gpt!!!
‰ 实现 DeepDream
‰ 实现神经风格迁移
‰ 变分自编码器
‰ 了解生成式对抗网络