深度学习相关见解资料摘录

简介

概述

什么是人工智能

人工智能的概念:机器模拟人的意识和思维

重要人物:艾伦·麦席森·图灵(Alan Mathison Turing)

人物简介:1912 年 6 月 23 日-1954 年 6 月 7 日,英国数学家、逻辑学家,被称为计算机科学之父,人工智能之父。

相关事件

  1. 1950 年在论文《机器能思考吗?》中提出了图灵测试,一种用于判定机器是否具有智能的试验方法:提问者和回答者隔开,提问者通过一些装置(如键盘)向机器随意提问。多次测试,如果有超过 30%的提问者认为回答问题的是人而不是机器,那么这台机器就通过测试,具有了人工智能。也就是工智能的概念:“用机器模拟人的意识和思维”。
  2. 图灵在论文中预测:在 2000 年,会出现通过图灵测试具备人工智能的机器。然而直到 2014 年 6 月,英国雷丁大学的聊天程序才成功冒充了 13 岁男孩,通过了图灵测试。这一事件比图灵的预测晚了 14 年。
  3. 在 2015 年 11 月 science 杂志封面新闻报道,机器人已经可以依据从未见过的文字中的一个字符,写出同样风格的字符,说明机器已经具备了迅速学习陌生文字的创造能力。

消费级人工智能产品

  • 国外
    1. 谷歌 Assistant
    2. 微软 Cortana
    3. 苹果 Siri
    4. 亚马逊 Alexa
  • 国内
    1. 阿里的天猫精灵
    2. 小米的小爱同学

人工智能先锋

  1. Geoffrey Hinton:多伦多大学的教授,谷歌大脑多伦多分布负责人,是人工智能领域的鼻祖,他发表了许多让神经网络得以应用的论文,激活了整个人工智能领域。他还培养了许多人工智能的大家。比如 LeCun 就是他的博士后。
  2. Yann LeCun:纽约大学的教授,Facebook 人工智能研究室负责人,他改进了卷积神经网路算法,使卷积神经网络具有了工程应用价值,现在卷积神经网络依旧是计算机视觉领域最有效的模型之一。
  3. Yoshua Bengio:蒙特利尔大学的教授,现任微软公司战略顾问,他推动了循环神经网路算法的发展,使循环神经网络得到工程应用,用循环神经网络解决了自然语言处理中的问题。

什么是机器学习

机器学习的概念:机器学习是一种统计学方法,计算机利用已有数据得出某种模型,再利用此模型预测结果。

特点:随经验的增加,效果会变好。

简单模型举例:决策树模型

预测班车到达时间问题描述: 每天早上七点半,班车从 A 地发往 B 地,到达 B地的时间如何准确预测?

如果你第一次乘坐班车,你的预测通常不太准。
一周之后,你大概能预测出班车 8:00 左右到达 B 地;
一个月之后,随着经验的增加,你还会知道,周一常堵车,会晚 10 分钟,下雨常堵车,会晚 20 分钟。
于是你画出了如下的一张树状图,
如果是周一,还下了雨,班车会 8:30 到达;
如果不是周一,也没有下雨,班车会 8:00 到达。

机器学习和传统计算机运算的区别:传统计算机是基于冯诺依曼结构,指令预先存储。运行时,CPU 从存储器里逐行读取指令,按部就班逐行执行预先安排好的
指令。其特点是,输出结果确定,因为先干什么,后干什么都已经提前写在指令里了。

机器学习三要素:数据、算法、算力

什么是深度学习

深度学习的概念:深层次神经网络,源于对生物脑神经元结构的研究。

人脑神经网络:随着人的成长,脑神经网络是在渐渐变粗变壮。

生物学中的神经元
下图左侧有许多支流汇总在一起,生物学中称这些支流叫做树突。树突具有接受刺激并将冲动传入细胞体的功能,是神经元的输入。这些树突汇总于细胞核又沿着一条轴突输出。
轴突的主要功能是将神经冲动由胞体传至其他神经元,是神经元的输出。人脑便是由 860 亿个这样的神经元组成,所有的思维意识,都以它为基本单元,连接成网络实现的。

计算机中的神经元模型
1943 年,心理学家 McCulloch 和数学家 Pitts 参考了生物神经元的结构,发表了抽象的神经元模型 MP。神经元模型是一个包含输入,输出与计算功能的模型。输入可以类比为神经元的树突,输出可以类比为神经元的轴突,计算可以类比为细胞核。

人工智能 Vs 机器学习 Vs 深度学习

人工智能,就是用机器模拟人的意识和思维。

机器学习,则是实现人工智能的一种方法,是人工智能的子集。

深度学习就是深层次神经网络,是机器学习的一种实现方法,是机器学习的子集。

神经网络的发展历史(三起两落)

第一次兴起:1958 年,人们把两层神经元首尾相接,组成单层神经网络,称做感知机。感知机成了首个可以学习的人工神经网络。引发了神经网络研究的第一次兴起。

第一次寒冬:1969 年,这个领域的权威学者 Minsky 用数学公式证明了只有单层神经网络的感知机无法对异或逻辑进行分类,Minsky 还指出要想解决异或可分问题,需要把单层神经网络扩展到两层或者以上。然而在那个年代计算机的运算能力,是无法支撑这种运算量的。只有一层计算单元的感知机,暴露出他的天然缺陷,使得神经网络研究进入了第一个寒冬。

第二次兴起:1986 年,Hinton 等人提出了反向传播方法,有效解决了两层神经网络的算力问题。引发了神经网络研究的第二次兴起。

第二次寒冬:1995 年,支持向量机诞生。支持向量机可以免去神经网络需要调节参数的不足,还避免了神经网络中局部最优的问题。一举击败神经网络,成为当时人工智能领域的主流算法,使得神经网络进入了他的第二个冬季。

第三次兴起:2006 年,深层次神经网络出现,2012 年,卷积神经网络在图像识别领域中的惊人表现,又引发了神经网络研究的再一次兴起。

机器学习的典型应用

应用领域

计算机视觉、语音识别、自然语言处理

主流应用

(1) 预测(对连续数据进行预测)

如,预测某小区 100 平米的房价卖多少钱。
根据以往数据(红色●),拟合出一条线,让它“穿过”所有的点,并且与各个点的距离尽可能的小。

我们可以把以前的数据,输入神经网络,让他训练出一个模型,比如这张图中红色点表示了以往的数据,虚线表示了预测出的模型 Y = ax + b ,大量历史数据也就是面积 x 和房价 y 作为输入,训练出了模型的参数 a = 3.5, b = 150,则你家 100 平米的房价应该是 3.5 * 100 + 150 = 500 万。

我们发现,模型不一定全是直线,也可以是曲线;我们还发现,随着数据的增多,模型一般会更准确。

(2) 分类(对离散数据进行分类)

如,根据肿瘤患者的年龄和肿瘤大小判断良性、恶性。
红色样本为恶性,蓝色样本为良性,绿色分为哪类?

假如让计算机判断肿瘤是良性还是恶性,先要把历史数据输入到神经网络进行建模,调节模型的参数,得到一条线把良性肿瘤和恶性肿瘤分开。比如输入患者的年龄、肿瘤的大小 还有对应的良性肿瘤还是恶性肿瘤,使用神经网络训练模型 调整参数,再输入新的患者年龄和肿瘤大小时,计算机会直接告诉你肿瘤是良性还是恶性。比如上图的绿色三角就属于良性肿瘤。

总结

  1. 机器学习,就是在任务 T 上,随经验 E 的增加,效果 P 随之增加。
  2. 机器学习的过程是通过大量数据的输入,生成一个模型,再利用这个生成的模型,实现对结果的预测。
  3. 庞大的神经网络是基于神经元结构的,是输入乘以权重,再求和,再过非线性函数的过程。




Python及Shell

Linux指令、Hello World

常用指令

  • √ 桌面点击右键 > 选择 Open Terminal打开终端
  • pwd打印当前在哪个目录
  • ls列出当前路径下的文件和目录
  • mkdir 目录名新建目录
  • cd 目录名进到指定目录
  • python运行 Python 解释器
  • print "Hello World"

代码验证:

补充:

使用 pwd 命令打印当前在哪个目录,打印的是绝对路径。

绝对路径:是以根目录(/)为起点的完整路径,以你所要到的目录为终点。

相对路径:是你当前的目录(.)为起点的路径,以你所要到的目录为终点。

使用 cd 目录名 进到指定目录,如果指定的”目录名”是

  • . 表示当前目录
  • .. 表示当前目录的上一级目录
  • - 表示上一次所在目录
  • ~ 表示当前用户的 home 目录(即刚 login 时所在的目录)

比如:

  • cd .. 返回上级目录 cd ../.. 返回上两级目录
  • cd ~ 进入用户主目录 home 目录

常用基础语法点

  • 运算符+ - * / %
  • 运算顺序:先乘除、再加减、括号最优先
    代码验证:

  • 变量,就是一个标签,由非数字开头的字母、数字、下划线组成,它的内容可以是数值、字符串、列表、元组和字典。

  • 数值,就是数字。 a = 100
  • 字符串,就是用一对儿双引号、或单引号引起来的内容,只要被引号引起来,就是字符串了。 b = "Hello World"
    100 是数值 Vs "100" 是字符串。
  • 转义字符
    • \t 表示 tab
    • \n 表示换行
    • \" 表示 "
  • %s 占位,用 % 后的变量替换
    举例:

    1
    2
    3
    a = 100
    b = " Hello World "
    print " point = %s \n \" %s \" " % (a, b)

    打印出:

    1
    2
    point = 100
    " Hello World "

列表、元组、字典

列表

  • 列表 [ ]

    1
    2
    3
    c = [1,2,3,4,5,6,7]
    d = ["张三","李四","王五"]
    e = [1,2,3,"4","5",d]
  • √ 用列表名 [ 索引号 ] 索引列表中的元素
    d[0] 表示列表 d 中的第零个元素 "张三"

  • √ 用列表名 [起 : 止] 表示切片,从列表中切出相应的元素 前闭后开
    • c[0:2] 切出 [1,2]
    • c[ : ] 切出 [1,2,3,4,5,6,7]
  • √ 用列表名 [起 : 止: 步长] 带步长的切片,步长有方向。
    • c = [1,2,3,4,5,6,7]
    • 切出 [5,4,3,2]c[4:0:-1]
    • 切出 [5,4,3,2,1]c[4::-1]
    • 切出 [6,4,2]c[-2::-2] 从倒数第二个开始一直切到头,步长 -2
  • 修改列表名[ 索引号 ] = 新值
  • 删除del 列表名[ 索引号 ]
  • 插入列表名.insert()插入位置索引号,新元素)

代码验证:

元组

  • 元组 ( ) 誓言,一旦定义不能改变 f=(1,2,3)

字典

  • 字典 { }
  • √ 字典里放着 {键:值, 键:值, 键:值} n 个键值对
    dic={1:"123","name":"zhangsan","height":180}
  • √ 用字典名 [ 键 ] 索引字典中的值
    dic["name"] 表示字典 dic 中键 "name" 对应的值 "zhangsan"
  • 修改字典名[ 键 ] = 新值
  • 删除del 字典名[ 键 ]
  • 插入字典名[ 新键 ] = 新值

代码验证:

条件语句

vim 编辑器

  • vim 文件名 打开或新建文本
  • √ 在 vim 中 点击 i 进入插入模式 可往文本里写内容
  • ESC + :q 退出 vim
  • ESC + :wq 保存更改退出 vim
  • ESC + :q! 不保存更改退出 vim

条件语句

  • √ 1、

    1
    2
    if 条件成立:
    执行任务
  • √ 2、

    1
    2
    3
    4
    if 条件 1 成立:
    执行任务 1
    else:
    执行任务 2
  • √ 3、

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if 条件 1 成立:
    执行任务 1
    elif 条件 2 成立:
    执行任务 2

    elif 条件 n 成立:
    执行任务 n
    else:
    执行任务 n+1

代码验证:


其中 #coding:utf-8 以注释的形式加入来兼容中文输入;

age=input("输入你的年龄\n") 中的input() 是一个函数,表示从屏幕接收内容括号里的字符串是向屏幕打印出的提示内容,可以增加程序和用户的交互。

python 语句代码层次

Python 代码是使用四个空格的缩进表示层次关系的,从缩进我们可以看出这段条件语句分了三个层次,第一个层次是黄色的 block1,然后是红色的 block2,最后是蓝色的 block3。

逻辑关系

  • == 等于
  • != 不等于
  • > 大于
  • >= 大于等于
  • < 小于
  • <= 小于等于
  • and
  • or

循环语句

循环语句

  • √ 1、

    1
    2
    for 变量 in range(开始值, 结束值): 
    执行某些任务

    其中的括号内的开始、结束值也为前闭后开区间 代码验证:

  • √ 2、

    1
    2
    for 变量 in 列表名: 
    执行某些任务

    代码验证:

    在第一个 for 循环中,
    先打印出 i 对应的 abcd 中的 a,
    然后执行第二个 for 循环,打印出 j 对应的 abcd;
    再回到 i,然后打印出 i 对应的 abcd 中的 b,
    再打印出第二个 for 循环 j 对应的 abcd

  • √ 3、

    1
    2
    while 条件:
    执行某些任务

    代码验证:

  • √ 4、终止循环用 break

turtle 模块

  • turtle 模块
1
2
3
4
5
6
7
8
import turtle       # 导入 turtle 模块

t = turtle.Pen( ) # 用 turtle 模块中的 Pen 类,实例化出一个叫做 t 的对象
t.forward(像素点) # 让 t 向前走多少个像素点
t.backward(像素点) # 让 t 向前走多少个像素点
t.left(角度) # 让 t 左转多少角度
t.right(角度) # 让 t 右转多少角度
t.reset( ) # 让 t 复位

代码验证:

可以把刚才的重复工作用循环表示出来,for 循环一般用作循环次数已知的任务
代码验证:

while 循环复现刚才的工作,t.reset() 先让海龟复位,
为了防止程序死循环我们用 i 做个计数器,到了指定次数强制退出循环。
i 赋初值 0,做个计数器让它每运行一遍循环自加一,把 i=i+1 放到和 t.forwardt.left 一个层次,如果 i=4 要执行 break 操作,也就是停止循环。

代码验证:

函数、模块、包

函数

  • √ 1、函数(function):组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
    比如之前用过的 input() 就是一个函数,可以直接使用函数,不用每次重新定义如何从控制台接收输入,所以函数是帮助实现代码复用的。
  • √ 2、定义函数:

    1
    2
    def 函数名(参数表):  
    函数体

    括号里的参数如果不需要可以为空

  • √ 3、使用函数:函数名(参数表)
    如:input("please input your class number:")
    定义:

    1
    2
    def hi_name(yourname):  
    print "Hello %s" %yourname

    使用:hi_name("zhangsan")
    会输出:Hello zhangsan
    代码验证:

  • √ 4、函数返回值:return

    1
    2
    3
    4
    def add(a,b):
    return a+b

    c=add(a,b) # c 被赋值为 add 的返回值 11

    代码验证:

  • √ 5、内建函数:python 解释器自带的函数
    abs(-10) 返回 10

模块

  • √ 模块(module):是一个 Python 文件,以 .py 结尾,包含了 Python 函数等语句。先导入,再使用,用 模块.函数名 调用。

    1
    2
    import time 
    time.asctime()

    输出:'Tue Jan 16 21:51:06 2018'

三、包

  • √ 包:包含有多个模块
    from PIL import Image

四、变量作用域

局部变量:在函数中定义的变量,只在函数中存在,函数执行结束不可再用。

全局变量,在函数前定义的变量,一般在整个代码最前面定义,全局可用。

类、对象、面向对象的编程

类、对象和面向对象的编程

  • √1、类(class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。物以类聚人以群分,类是可实例化出对象的模具。
  • √2、实例化对象 = 类()
    t = turtle.Pen()
  • √3、对象:是类实例化出的实体,对象实实在在存在,完成具体工作。
  • √4、面向对象:程序员反复修改优化类,类实例化出对象,对象调用类里的函数执行具体的操作。

    在上图中,有动物、哺乳动物和猫。 动物是一个类,他们具有共同的功能:呼吸、移动和吃东西。哺乳动物也是一个类,他们是动物类里的子类,是在动物类的基础上多了喂奶的功能。猫是哺乳动物类的一个子类,猫类在哺乳动物的基础上多了捉老鼠的功能。

    类命名时第一个字母常大写,比如 Animals、Mammals 和 Cats 的首字母都大写了。这些类的右侧列出了每个类具有的功能:比如呼吸、移动和吃东西是动物这个类具备的功能,在计算机中用函数的形式表示。喂奶是哺乳动物的功能,是哺乳动物这个类里的函数。捉老鼠是猫类具有的功能,是猫类的函数。

    • √ 上面的类是下面类的父类;下面类是上面类的子类
    • √ 子类实例化出来的对象,可以使用自身和父类的函数与变量
  • √5、类的定义

    1
    2
    class 类名(父类名):
    pass

如果有父类,写在类名后面的括号里;如果没有父类,可以不写括号了。用关键词 pass 占个位置,之后再用具体函数把类补充完整。

举例:

1
2
3
4
5
6
7
8
class Animals:
pass

class Mammals(Animals):
pass

class Cats(Mammals):
pass

  • √6、类里定义函数时,语法规定第一个参数必须是 self
  • √7、__init__ 函数,在新对象实例化时会自动运行,用于给新对象赋初值。

    (1) 将猫类实例化出一个叫 kitty 的对象,kitty 有自己的特征属性,比如身上有 10 个斑点:

    1
    2
    kitty = Cats(10)    # 实例化时运行 init 函数,给 spots 赋值,告知 kitty 有 10 个斑点
    print "kitty.spots" # 打印出 10

    (2) kitty 可以做具体的工作,比如捉老鼠:

    1
    2
    kitty.catch_mouse() # 对象运行函数,必须用对象.函数名,调用类里的函数
    # 会运行 print "catch mouse" 故打印出 catch mouse
  • √8、对象调用类里的函数,用 对象.函数名
  • √9、对象调用类里的变量,用 对象.变量名
  • √10、类内定义函数时,如调用自身或父类的函数与变量,须用 self.引导,应写为 self.函数名self.变量名

    代码验证:

补充:

Python 中虽然没有访问控制的关键字,例如 private`、protected` 等等。
但是,在 Python 编码中,有一些约定来进行访问控制。

单下划线、双下划线、头尾双下划线说明:

  • _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
  • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
  • __foo__: 头尾双下划线定义的是特列方法,类似 __init__() 之类的。

文件操作

文件写操作

import pickle

  • 开:文件变量 = open("文件路径文件名", "wb")
  • 存:pickle.dump(待写入的变量,文件变量)
  • 关:文件变量.close()

代码验证:

文件读操作

import pickle

  • 开:文件变量 = open("文件路径文件名", "rb")
  • 取:放内容的变量 = pickle.load(文件变量)
  • 关:文件变量.close()

代码验证:

总结

  • √ 1、ubuntu 终端的简单使用
  • √ 2、vim 编辑器的基本用法
  • √ 3、python 里的数据类型:数值、字符串、列表、元组和字典
  • √ 4、python 的条件语句和循环语句
  • √ 5、代码纵向对齐表层次关系
  • √ 6、函数、对象、类、模块、包还有面向对象的编程思想
  • √ 7、文件读写操作




搭建神经网络,总结搭建八股

张量、计算图、会话

基本概念

  • 基于 Tensorflow 的 NN:用张量表示数据,用计算图搭建神经网络,用会话执行计算图,优化线上的权重(参数),得到模型。
  • 张量:张量就是多维数组(列表),用”阶”表示张量的维度。

    • 0 阶张量称作标量,表示一个单独的数;
      举例S=123
    • 1 阶张量称作向量,表示一个一维数组;
      举例V=[1,2,3]
    • 2 阶张量称作矩阵,表示一个二维数组,它可以有 ij 列个元素,每个元素可以用行号和列号共同索引到;
      举例m=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    • 判断张量是几阶的,就通过张量右边的方括号数,0 个是 0 阶,n 个是 n 阶,张量可以表示 0 阶到 n 阶数组(列表);
      举例t=[ [ [… ] ] ] 为 3 阶。
  • 数据类型:Tensorflow 的数据类型有 tf.float32tf.int32 等。
    举例
    我们实现 Tensorflow 的加法:

    1
    2
    3
    4
    5
    6
    7
    import tensorflow as tf     # 引入模块

    a = tf.constant([1.0, 2.0]) # 定义一个张量等于 [1.0,2.0]
    b = tf.constant([3.0, 4.0]) # 定义一个张量等于 [3.0,4.0]
    result = a+b # 实现 a 加 b 的加法

    print result # 打印出结果

    可以打印出这样一句话:
    Tensor("add:0", shape=(2, ), dtype=float32)
    意思为 result 是一个名称为 add:0 的张量,
    shape=(2,) 表示一维数组长度为 2,
    dtype=float32 表示数据类型为浮点型。

  • 计算图(Graph):搭建神经网络的计算过程,是承载一个或多个计算节点的一张图,只搭建网络,不运算。
    举例
    在第一讲中我们曾提到过,神经网络的基本模型是神经元,神经元的基本模型其实就是数学中的乘、加运算。我们搭建如下的计算图:

    x1x2 表示输入,w1w2 分别是 x1yx2y 的权重,"$y=x_1w_1+x_2w_2$"。 我们实现上述计算图:

    1
    2
    3
    4
    5
    6
    7
    import tensorflow as tf         # 引入模块

    x = tf.constant([[1.0, 2.0]]) # 定义一个 2 阶张量等于 [[1.0,2.0]]
    w = tf.constant([[3.0], [4.0]]) # 定义一个 2 阶张量等于 [[3.0],[4.0]]
    y = tf.matmul(x, w) # 实现 xw 矩阵乘法

    print y # 打印出结果

    可以打印出这样一句话:
    Tensor("matmul:0", shape(1,1), dtype=float32)
    从这里我们可以看出,print 的结果显示 y 是一个张量,只搭建承载计算过程的计算图,并没有运算,如果我们想得到运算结果就要用到”会话 Session()“了。

  • 会话(Session):执行计算图中的节点运算。
    我们用 with 结构实现,语法如下:

    1
    2
    with tf.Session() as sess:
    print sess.run(y)

    举例】:
    对于刚刚所述计算图,我们执行 Session() 会话可得到矩阵相乘结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import tensorflow as tf         # 引入模块

    x = tf.constant([[1.0, 2.0]]) # 定义一个 2 阶张量等于 [[1.0,2.0]]
    w = tf.constant([[3.0], [4.0]]) # 定义一个 2 阶张量等于 [[3.0],[4.0]]
    y = tf.matmul(x, w) # 实现 xw 矩阵乘法

    print y # 打印出结果
    with tf.Session() as sess:
    print sess.run(y) # 执行会话并打印出执行后的结果

    可以打印出这样的结果:
    Tensor("matmul:0", shape(1,1), dtype=float32) [[11.]]
    我们可以看到,运行 Session() 会话前只打印出 y 是个张量的提示,运行 Session() 会话后打印出了 y 的结果 1.0*3.0 + 2.0*4.0 = 11.0

注①:我们以后会常用到 vim 编辑器,为了使用方便,我们可以更改 vim 的配置
文件,使 vim 的使用更加便捷。我们在 vim ~/.vimrc
写入: set ts=4 表示使 Tab 键等效为 4 个空格

set nu 表示使 vim 显示行号 nunumber 缩写

注②:在 vim 编辑器中运行 Session() 会话时,有时会出现”提示 warning”,是因为有的电脑可以支持加速指令,但是运行代码时并没有启动这些指令。可以把这些”提示 warning”暂时屏蔽掉。屏蔽方法为进入主目录下的 bashrc 文件,在
bashrc 文件中加入这样一句 export TF_CPP_MIN_LOG_LEVEL=2,从而把”提示 warning”等级降低。

这个命令可以控制 python 程序显示提示信息的等级,在 Tensorflow 里面一般设置成是
0“(显示所有信息)或者”1“(不显示 info),
2“代表不显示 warning,
3“代表不显示 error。一般不建议设置成 3。
source 命令用于重新执行修改的初始化文件,使之立即生效,而不必注销并重新登录。

前向传播

神经网络的参数

  • 神经网络的参数:是指神经元线上的权重 w,用变量表示,一般会先随机生成 这些参数。生成参数的方法是让 w 等于tf.Variable,把生成的方式写在括号里。

神经网络中常用的生成随机数/数组的函数有:

  • tf.random_normal() 生成正态分布随机数
  • tf.truncated_normal() 生成去掉过大偏离点的正态分布随机数
  • tf.random_uniform() 生成均匀分布随机数
  • tf.zeros 表示生成全 0 数组
  • tf.ones 表示生成全 1 数组
  • tf.fill 表示生成全定值数组
  • tf.constant 表示生成直接给定值的数组

举例

  • w=tf.Variable(tf.random_normal([2,3],stddev=2, mean=0, seed=1))
    表示生成正态分布随机数,形状两行三列,标准差是 2,均值是 0,随机种子是 1。
  • w=tf.Variable(tf.Truncated_normal([2,3],stddev=2, mean=0, seed=1))
    表示去掉偏离过大的正态分布,也就是如果随机出来的数据偏离平均值超过两个 标准差,这个数据将重新生成。
  • w=random_uniform(shape=7,minval=0,maxval=1,dtype=tf.int32,seed=1)
    表示从一个均匀分布 [minval maxval) 中随机采样,
    注意定义域是左闭右开,即包含 minval,不包含 maxval
  • ④ 除了生成随机数, 还可以生成常量。
    • tf.zeros([3,2],int32) 表示生成 [[0,0],[0,0],[0,0]]
    • tf.ones([3,2],int32) 表示生成 [[1,1],[1,1],[1,1]
    • tf.fill([3,2],6) 表示生成 [[6,6],[6,6],[6,6]]
    • tf.constant([3,2,1]) 表示生成 [3,2,1]

注意

  • ① 随机种子如果去掉每次生成的随机数将不一致。
  • ② 如果没有特殊要求标准差、均值、随机种子是可以不写的。

神经网络的搭建

当我们知道张量、计算图、会话和参数后,我们可以讨论神经网络的实现过程了。

√ 神经网络的实现过程:

  1. 准备数据集,提取特征,作为输入喂给神经网络(Neural Network, NN)
  2. 搭建 NN 结构,从输入到输出(先搭建计算图,再用会话执行)
    ( NN 前向传播算法 ==>> 计算输出 )
  3. 大量特征数据喂给 NN,迭代优化 NN 参数
    ( NN 反向传播算法 ==>> 优化参数训练模型 )
  4. 使用训练好的模型预测和分类

由此可见,基于神经网络的机器学习主要分为两个过程,即训练过程和使用过程。 训练过程是第一步、第二步、第三步的循环迭代,使用过程是第四步,一旦参数 优化完成就可以固定这些参数,实现特定应用了。

很多实际应用中,我们会先使用现有的成熟网络结构,喂入新的数据,训练相应模型,判断是否能对喂入的从未见过的新数据作出正确响应,再适当更改网络结构,反复迭代,让机器自动训练参数找出最优结构和参数,以固定专用模型。

前向传播

  • 前向传播就是搭建模型的计算过程,让模型具有推理能力,可以针对一组输入给出相应的输出。

举例

假如生产一批零件,体积为 x1,重量为 x2,体积和重量就是我们选择的特征,把它们喂入神经网络,当体积和重量这组数据走过神经网络后会得到一个输出。

假如输入的特征值是:体积 0.7 重量 0.5

由搭建的神经网络可得,隐藏层节点 "$a_{11}=x_1w_{1, 1}^{(1)}+x_2w_{1, 2}^{(1)}=0.14+0.15=0.29$",同理算得节点 "$a_{12}=0.32, a_{13}=0.38$",最终计算得到输出层 Y=-0.015,这便实现了前向传播过程。

√ 推导

第一层
  • X 是输入为 1x2 矩阵
    用 x 表示输入,是一个 1 行 2 列矩阵,表示一次输入一组特征,这组特征包含了 体积和重量两个元素。

  • "$W_{i,j}^{(n)}$" 为待优化的参数

    n 为层数;i 为前节点编号;j 为后节点编号

    对于第一层的 w 前面有两个节点,后面有三个节点 w 应该是个两行三列矩阵,我们这样表示:

  • 神经网络共有几层(或当前是第几层网络) 都是指的计算层,输入不是计算层,所以 a 为第一层网络,a 是一个一行三列矩阵。
    我们这样表示:

第二层
  • 参数要满足前面三个节点,后面一个节点,所以 是三行一列矩阵。

    我们这样表示:

    我们把每层输入乘以线上的权重 w,这样用矩阵乘法可以计算出输出 y 了。

    1
    2
    a = tf.matmul(X, W1)
    y = tf.matmul(a, W2)

    由于需要计算结果,就要用 with 结构实现,所有变量初始化过程、计算过程都要放到 sess.run 函数中。对于变量初始化,我们在 sess.run 中写入 tf.global_variables_initializer 实现对所有变量初始化,也就是赋初值。对于计算图中的运算,我们直接把运算节点填入 sess.run 即可,比如要计算输出 y,直接写 sess.run(y) 即可。

    在实际应用中,我们可以一次喂入一组或多组输入,让神经网络计算输出 y,可以先用 tf.placeholder 给输入占位。如果一次喂一组数据 shape 的第一维位置写 1,第二维位置看有几个输入特征;如果一次想喂多组数据,shape 的第一维位置可以写 None 表示先空着,第二维位置写有几个输入特征。这样在 feed_dict 中可以喂入若干组体积重量了。

  • 前向传播过程的 tensorflow 描述:

  • 变量初始化、计算图节点运算都要用会话(with 结构)实现

    1
    2
    with tf.Session() as sess:
    sess.run()
  • 变量初始化:在 sess.run 函数中用 tf.global_variables_initializer() 汇总所有待优化变量。

    1
    2
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
  • 计算图节点运算:在 sess.run 函数中写入待运算的节点
    sess.run(y)

  • tf.placeholder 占位,在 sess.run 函数中用 feed_dict 喂数据
    喂一组数据:

    1
    2
    x = tf.placeholder(tf.float32, shape=(1, 2)) 
    sess.run(y, feed_dict={x: [[0.5,0.6]]})

    喂多组数据:

    1
    2
    x = tf.placeholder(tf.float32, shape=(None, 2))
    sess.run(y, feed_dict={x: [[0.1,0.2],[0.2,0.3],[0.3,0.4],[0.4,0.5]]})

举例】:
这是一个实现神经网络前向传播过程,网络可以自动推理出输出 y 的值。

  • ① 用 placeholder 实现输入定义(sess.run 中喂入一组数据)的情况

    第一组喂体积 0.7、重量 0.5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # coding:utf-8
    import tensorflow as tf

    # 定义输入和参数
    x=tf.placeholder(tf.float32,shape=(1,2))
    w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
    w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

    # 定义前向传播过程
    a=tf.matmul(x,w1)
    y=tf.matmul(a,w2)

    # 用会话计算结果
    withtf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    print "y in tf3_3.py is:\n", sess.run(y,feed_dict={x:[[0.7,0.5]]})
  • ② 用 placeholder 实现输入定义(sess.run 中喂入多组数据)的情况

    第一组喂体积 0.7、重量 0.5,第二组喂体积 0.2、重量 0.3,第三组喂体积 0.3 、重量 0.4,第四组喂体积 0.4、重量 0.5。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # coding:utf-8
    import tensorflow as tf

    # 定义输入和参数
    x=tf.placeholder(tf.float32,shape=(None,2))
    w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
    w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

    # 定义前向传播过程
    a=tf.matmul(x,w1)
    y=tf.matmul(a,w2)

    # 用会话计算结果
    withtf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    print "y in tf3_4.py is:\n", sess.run(y,feed_dict={x:[[0.7,0.5], [0.2,0.3],[0.3,0.4],[0.4,0.5]]})

反向传播

反向传播

  • 反向传播:训练模型参数,在所有参数上用梯度下降,使 NN 模型在训练数据上的损失函数最小。
  • 损失函数(loss):计算得到的预测值 y 与已知答案 y_ 的差距。
    损失函数的计算有很多方法,均方误差 MSE 是比较常用的方法之一。
  • 均方误差 MSE:求前向传播计算结果与已知答案之差的平方再求平均。
    用 tensorflow 函数表示为:
    loss_mse = tf.reduce_mean(tf.square(y_ - y))
  • 反向传播训练方法:以减小 loss 值为优化目标,有梯度下降、momentum 优化器、adam 优化器等优化方法。

    这三种优化方法用 tensorflow 的函数可以表示为:

    1
    2
    3
    train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
    train_step=tf.train.MomentumOptimizer(learning_rate, momentum).minimize(loss)
    train_step=tf.train.AdamOptimizer(learning_rate).minimize(loss)

三种优化方法区别如下:

  • tf.train.GradientDescentOptimizer() 使用随机梯度下降算法,使参数沿着梯度的反方向,即总损失减小的方向移动,实现更新参数。

    参数更新公式是

    其中,𝐽(𝜃) 为损失函数,𝜃 为参数,𝛼 为学习率。

  • tf.train.MomentumOptimizer() 在更新参数时,利用了超参数,参数更新公式是

    其中,𝛼 为学习率,超参数为 𝛽, 𝜃 为参数,"$g\left( \theta _i-1 \right)$" 为损失函数的梯度。

  • tf.train.AdamOptimizer() 是利用自适应学习率的优化算法,Adam 算法和随机梯度下降算法不同。随机梯度下降算法保持单一的学习率更新所有的参数,学习率在训练过程中并不会改变。而 Adam 算法通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。

  • 学习率:决定每次参数更新的幅度。
    优化器中都需要一个叫做学习率的参数,使用时,如果学习率选择过大会出现震荡不收敛的情况,如果学习率选择过小,会出现收敛速度慢的情况。我们可以选个比较小的值填入,比如 0.01、0.001。

进阶:反向传播参数更新推导过程

符号说明:

  • "$\text{z}^l$" 表示第 l 层隐藏层和输出层的输入值;
  • "$\alpha^l$" 表示第 l 层隐藏层和输出层的输值;
  • f(z) 表示激活函数;
  • 最后的输出层为第 L 层。

推导过程:

前向传播第 L 层输出:

损失函数:

对于输出层第 L 层:

更新最后一层 L 层的参数 "$w^L$"
"$b^L$"



则有

"$\delta^l$" 可以用数学归纳法得到:

由于 "$z^{l+1}=w^{l+1}a^l+b^l$"

所以

因此第 l 层参数 "$w^l$"
"$b^l$" 更新公式为:

搭建神经网络的八股

我们最后梳理出神经网络搭建的八股,神经网络的搭建课分四步完成:准备工作、前向传播、反向传播和循环迭代。

  1. 导入模块,生成模拟数据集
    import
    常量定义
    生成数据集
  2. 前向传播:定义输入、参数和输出

    1
    2
    3
    x=    y_=
    w1= w2=
    a= y=
  3. 反向传播:定义损失函数、反向传播方法

    1
    2
    loss= 
    train_step=
  4. 生成会话,训练 STEPS 轮

    1
    2
    3
    4
    5
    6
    7
    8
    with tf.session() as sess
    Init_op=tf. global_variables_initializer()
    sess_run(init_op)
    STEPS=3000
    for i in range(STEPS):
    start=
    end=
    sess.run(train_step, feed_dict:)

举例

随机产生 32 组生产出的零件的体积和重量,训练 3000 轮,每 500 轮输出一次损失函数。下面我们通过源代码进一步理解神经网络的实现过程:

  1. 导入模块,生成模拟数据集;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # coding:utf-8
    # 0导入模块,生成模拟数据集。
    import tensorflow as tf
    import numpy as np

    BATCH_SIZE = 8
    SEED = 23455

    #基于seed产生随机数
    rdm = np.random.RandomState(SEED)

    # 随机数返回32行2列的矩阵 表示32组 体积和重量 作为输入数据集
    X = rdm.rand(32,2)

    # 从X这个32行2列的矩阵中 取出一行
    # 判断 如果和小于1 给Y赋值1;如果和不小于1 给Y赋值0
    # 作为输入数据集的标签(正确答案)
    Y_ = [[int(x0 + x1 < 1)] for (x0, x1) in X]

    print "X:\n",X
    print "Y_:\n",Y_
  2. 定义神经网络的输入、参数和输出,定义前向传播过程;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 1定义神经网络的输入、参数和输出,定义前向传播过程。
    x = tf.placeholder(tf.float32, shape=(None, 2))
    y_= tf.placeholder(tf.float32, shape=(None, 1))

    w1= tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
    w2= tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))

    a = tf.matmul(x, w1)
    y = tf.matmul(a, w2)
  3. 定义损失函数及反向传播方法

    1
    2
    3
    4
    5
    # 2定义损失函数及反向传播方法。
    loss_mse = tf.reduce_mean(tf.square(y-y_))
    train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss_mse)
    # train_step = tf.train.MomentumOptimizer(0.001,0.9).minimize(loss_mse)
    # train_step = tf.train.AdamOptimizer(0.001).minimize(loss_mse)
  4. 生成会话,训练 STEPS 轮

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 3生成会话,训练STEPS轮
    with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    # 输出目前(未经训练)的参数取值。
    print "w1:\n", sess.run(w1)
    print "w2:\n", sess.run(w2)
    print "\n"

    # 训练模型。
    STEPS = 3000
    for i in range(STEPS):
    start = (i*BATCH_SIZE) % 32
    end = start + BATCH_SIZE
    sess.run(train_step, feed_dict={x: X[start:end], y_: Y_[start:end]})
    if i % 500 == 0:
    total_loss = sess.run(loss_mse, feed_dict={x: X, y_: Y_})
    print("After %d training step(s), loss_mse on all data is %g" % (i, total_loss))

    # 输出训练后的参数取值。
    print "\n"
    print "w1:\n", sess.run(w1)
    print "w2:\n", sess.run(w2)

由神经网络的实现结果,我们可以看出,总共训练 3000 轮,每轮从 X 的数据集 和 Y 的标签中抽取相对应的从 start 开始到 end 结束个特征值和标签,喂入神经网络,用 sess.run 求出 loss,每 500 轮打印一次 loss 值。经过 3000 轮后,我们打印出最终训练好的参数w1w2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
After 0 training step(s), loss_mse on all data is 5.13118
After 500 training step(s), loss_mse on all data is 0.429111
After 1000 training step(s), loss_mse on all data is 0.409789
After 1500 training step(s), loss_mse on all data is 0.399923
After 2000 training step(s), loss_mse on all data is 0.394146
After 2500 training step(s), loss_mse on all data is 0.390597


w1:
[[-0.7000663 0.9136318 0.08953571]
[-2.3402493 -0.14641267 0.58823055]]
w2:
[[-0.06024267]
[ 0.91956186]
[-0.0682071 ]]

这样四步就可以实现神经网络的搭建了。

神经网络优化

损失函数

  • 神经元模型:用数学公式表示为:"$f\left( \sum_i{x_iw_i}+b \right)$"f 为激活函数。神经网络是以神经元为基本单元构成的。
  • 激活函数:引入非线性激活因素,提高模型的表达力。
    常用的激活函数有 relusigmoidtanh 等。

    • 激活函数 relu: 在 Tensorflow 中,用 tf.nn.relu() 表示
      relu() 数学表达式

      "relu() 数学表达式"
      "ReLU 函数图形"

    • 激活函数 sigmoid:在 Tensorflow 中,用 tf.nn.sigmoid() 表示
      sigmoid() 数学表达式

      "sigmoid() 数学表达式"
      "sigmod 函数图形"

    • 激活函数 tanh:在 Tensorflow 中,用 tf.nn.tanh() 表示
      tanh() 数学表达式

      "tanh() 数学表达式"
      "tanh 函数图形"

  • 神经网络的复杂度:可用神经网络的层数和神经网络中待优化参数个数表示

  • 神经网路的层数:一般不计入输入层,层数 = n 个隐藏层 + 1 个输出层
  • 神经网路待优化的参数:神经网络中所有参数 w 的个数 + 所有参数 b 的个数
    【例如】

    在该神经网络中,包含 1 个输入层、1 个隐藏层和 1 个输出层,该神经网络的层数为 2 层。

    在该神经网络中,参数的个数是所有参数 w 的个数加上所有参数 b 的总数,第一层参数用三行四列的二阶张量表示(即 12 个线上的权重 w)再加上 4 个偏置 b;第二层参数是四行两列的二阶张量(即 8 个线上的权重 w)再加上 2 个偏置 b。总参数 = 3*4+4+4*2+2 = 26

  • 损失函数(loss):用来表示预测值 y 与已知答案 y_ 的差距。在训练神经网络时,通过不断改变神经网络中所有参数,使损失函数不断减小,从而训练出更高准确率的神经网络模型。

  • 常用的损失函数有均方误差、自定义和交叉熵等。
  • 均方误差 MSE:n 个样本的预测值 y 与已知答案 y_ 之差的平方和,再求平均值。

    在 Tensorflow 中用 loss_mse = tf.reduce_mean(tf.square(y_ - y))

【例如】

预测酸奶日销量 yx1x2 是影响日销量的两个因素。

应提前采集的数据有:一段时间内,每日的 x1 因素、x2 因素和销量 y_。采集的数据尽量多。在本例中用销量预测产量,最优的产量应该等于销量。由于目前没有数据集,所以拟造了一套数据集。利用 Tensorflow 中函数随机生成 x1x2,制造标准答案 y_ = x1 + x2,为了更真实,求和后还加了正负 0.05 的随机噪声。

我们把这套自制的数据集喂入神经网络,构建一个一层的神经网络,拟合预测酸奶日销量的函数。

代码 opt4_1.py 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# coding:utf-8
# 预测多或预测少的影响一样
# 0 导入模块,生成数据集
import tensorflow as tf
import numpy as np
BATCH_SIZE = 8
SEED = 23455

rdm = np.random.RandomState(SEED)
X = rdm.rand(32,2)
Y_ = [[x1+x2+(rdm.rand()/10.0-0.05)] for (x1, x2) in X]

# 1 定义神经网络的输入、参数和输出,定义前向传播过程。
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
w1= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)

# 2 定义损失函数及反向传播方法。
# 定义损失函数为 MSE,反向传播方法为梯度下降。
loss_mse = tf.reduce_mean(tf.square(y_ - y))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss_mse)

# 3 生成会话,训练 STEPS 轮
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 20000
for i in range(STEPS):
start = (i*BATCH_SIZE) % 32
end = (i*BATCH_SIZE) % 32 + BATCH_SIZE
sess.run(
train_step,
feed_dict={x: X[start:end], y_: Y_[start:end]}
)
if i % 500 == 0:
print "After %d training steps, w1 is: " % (i)
print sess.run(w1), "\n"
print "Final w1 is: \n", sess.run(w1)

运行结果如下:

1
2
3
4
5
6
7
8
9
...

After 19500 training steps, w1 is:
[[0.9777026]
[1.0181949]]

Final w1 is:
[[0.98019385]
[1.0159807 ]]

由上述代码可知,本例中神经网络预测模型为 y = w1*x1 + w2*x2,损失函数采用均方误差。通过使损失函数值(loss)不断降低,神经网络模型得到最终参数 w1=0.98w2=1.02,销量预测结果为 y = 0.98*x1 + 1.02*x2。由于在生成数据集时,标准答案为 y = x1 + x2,因此,销量预测结果和标准答案已非常接近,说明该神经网络预测酸奶日销量正确。

  • √ 自定义损失函数:根据问题的实际情况,定制合理的损失函数。

【例如】

对于预测酸奶日销量问题,如果预测销量大于实际销量则会损失成本;如果预测销量小于实际销量则会损失利润。在实际生活中,往往制造一盒酸奶的成本和销售一盒酸奶的利润是不等价的。因此,需要使用符合该问题的自定义损失函数。

自定义损失函数为:"$\text{loss}=\sum_n{f\left( y_{\_},y \right)}$"
其中,损失定义成分段函数:

损失函数表示,若预测结果 y 小于标准答案 y_,损失函数为利润乘以预测结果 y 与标准答案 y_ 之差;若预测结果 y 大于标准答案 y_,损失函数为成本乘以预测结果 y 与标准答案 y_ 之差。

用 Tensorflow 函数表示为:

loss = tf.reduce_sum(tf.where(tf.greater(y,y_),COST(y-y_),PROFIT(y_-y)))

  • ① 若酸奶成本为 1 元,酸奶销售利润为 9 元,则制造成本小于酸奶利润,因此希望预测的结果 y 多一些。采用上述的自定义损失函数,训练神经网络模型。

    代码 opt4_2.py 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    # coding:utf-8
    # 酸奶成本 1元, 酸奶利润 9元
    # 预测少了损失大,故不要预测少,故生成的模型会多预测一些
    # 0 导入模块,生成数据集
    import tensorflow as tf
    import numpy as np
    BATCH_SIZE = 8
    SEED = 23455
    COST = 1
    PROFIT = 9

    rdm = np.random.RandomState(SEED)
    X = rdm.rand(32,2)
    Y = [[x1+x2+(rdm.rand()/10.0-0.05)] for (x1, x2) in X]

    # 1 定义神经网络的输入、参数和输出,定义前向传播过程。
    x = tf.placeholder(tf.float32, shape=(None, 2))
    y_ = tf.placeholder(tf.float32, shape=(None, 1))
    w1= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
    y = tf.matmul(x, w1)

    # 2 定义损失函数及反向传播方法。
    # 定义损失函数使得预测少了的损失大,于是模型应该偏向多的方向预测。
    loss = tf.reduce_sum(
    tf.where(
    tf.greater(y, y_),
    (y - y_)*COST,
    (y_ - y)*PROFIT
    )
    )
    train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)

    # 3 生成会话,训练STEPS轮。
    # ....

    运行结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ...

    After 2000 training steps, w1 is:
    [[1.0179386]
    [1.0412899]]

    After 2500 training steps, w1 is:
    [[1.0205938]
    [1.0390677]]

    Final w1 is:
    [[1.0296593]
    [1.0484432]]

    由代码执行结果可知,神经网络最终参数为 w1=1.03w2=1.05,销量预测结果为 y = 1.03*x1+1.05*x2。由此可见,采用自定义损失函数预测的结果大于采用均方误差预测的结果,更符合实际需求。

  • ② 若酸奶成本为 9 元,酸奶销售利润为 1 元,则制造成本大于酸奶利润,因此希望预测结果 y 小一些。采用上述的自定义损失函数,训练神经网络模型。

    代码 opt4_3.py 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    # coding:utf-8
    # 酸奶成本9元, 酸奶利润1元
    # 预测多了损失大,故不要预测多,故生成的模型会少预测一些
    # 0 导入模块,生成数据集
    import tensorflow as tf
    import numpy as np
    BATCH_SIZE = 8
    SEED = 23455
    COST = 9
    PROFIT = 1

    rdm = np.random.RandomState(SEED)
    X = rdm.rand(32,2)
    Y = [[x1+x2+(rdm.rand()/10.0-0.05)] for (x1, x2) in X]

    # 1 定义神经网络的输入、参数和输出,定义前向传播过程。
    x = tf.placeholder(tf.float32, shape=(None, 2))
    y_ = tf.placeholder(tf.float32, shape=(None, 1))
    w1= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
    y = tf.matmul(x, w1)

    # 2 定义损失函数及反向传播方法。
    # 重新定义损失函数,使得预测多了的损失大,于是模型应该偏向少的方向预测。
    loss = tf.reduce_sum(
    tf.where(
    tf.greater(y, y_),
    (y - y_)*COST,
    (y_ - y)*PROFIT
    )
    )
    train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)

    # 3 生成会话,训练STEPS轮。
    # ....

    运行结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ...

    After 2000 training steps, w1 is:
    [[0.9602475]
    [0.9742084]]

    After 2500 training steps, w1 is:
    [[0.96100295]
    [0.96993417]]

    Final w1 is:
    [[0.9600407 ]
    [0.97334176]]

    由执行结果可知,神经网络最终参数为 w1=0.96w2=0.97,销量预测结果为 y = 0.96*x1 + 0.97*x2

因此,采用自定义损失函数预测的结果小于采用均方误差预测的结果,更符合实际需求。

  • 交叉熵(Cross Entropy):表示两个概率分布之间的距离。
    交叉熵越大,两个概率分布距离越远,两个概率分布越相异;
    交叉熵越小,两个概率分布距离越近,两个概率分布越相似。
    交叉熵计算公式"$H\left( y_{\_}, y \right) =-\sum{y_{\_}\cdot \log y}$"
    用 Tensorflow 函数表示为
    ce= -tf.reduce_mean(y_* tf.log(tf.clip_by_value(y, 1e-12, 1.0)))

    【例如】

    两个神经网络模型解决二分类问题中,已知标准答案为 y_=(1, 0),第一个神经网络模型预测结果为 y1=(0.6, 0.4),第二个神经网络模型预测结果为 y2=(0.8, 0.2),判断哪个神经网络模型预测的结果更接近标准答案。

    根据交叉熵的计算公式得:

    1
    2
    H1((1,0),(0.6,0.4)) = -(1*log0.6 + 0*log0.4) ≈ -(-0.222 + 0) = 0.222
    H2((1,0),(0.8,0.2)) = -(1*log0.8 + 0*log0.2) ≈ -(-0.097 + 0) = 0.097

    由于 0.222 > 0.097,所以预测结果 y2 与标准答案 y_ 更接近,y2 预测更准确。

  • softmax 函数:将 n 分类的 n 个输出 (y1, y2, ..., yn) 变为满足以下概率分布要求的函数。

    softmax 函数表示为"$\mathbf{softmax}\left( y_i \right) =\frac{e^{yi}}{\sum_{j=1}^n{e^{yj}}}$"

    softmax 函数应用:在 n 分类中,模型会有 n 个输出,即 y1, y2, ..., yn,其中 yi 表示第 i 种情况出现的可能性大小。将 n 个输出经过 softmax 函数,可得到符合概率分布的分类结果。

  • √ 在 Tensorflow 中,一般让模型的输出经过 sofemax 函数,以获得输出分类的概率分布,再与标准答案对比,求出交叉熵,得到损失函数,用如下函数实现:

    1
    2
    3
    4
    5
    ce = tf.nn.sparse_softmax_cross_entropy_with_logits(
    logits=y,
    labels=tf.argmax(y_, 1)
    )
    cem = tf.reduce_mean(ce)

学习率

  • √ 学习率 learning_rate:表示了每次参数更新的幅度大小。
    学习率过大,会导致待优化的参数在最小值附近波动,不收敛;
    学习率过小,会导致待优化的参数收敛缓慢。在训练过程中,参数的更新向着损失函数梯度下降的方向。

参数的更新公式为

假设损失函数为 "$loss=\left( w+1 \right) ^2$"。梯度是损失函数 loss 的导数为 ∇=2w+2。如参数初值为 5,学习率为 0.2,则参数和损失函数更新如下:

1
2
3
4
1 次参数 w: 5          5 - 0.2 * (2 * 5 + 2) = 2.6
2 次参数 w: 2.6 2.6 - 0.2 * (2 * 2.6 + 2) = 1.16
3 次参数 w: 1.16 1.16 - 0.2 * (2 * 1.16 + 2) = 0.296
4 次参数 w: 0.296

损失函数 "$loss=\left( w+1 \right) ^2$" 的图像为:

由图可知,损失函数 loss 的最小值会在 (-1, 0) 处得到,此时损失函数的导数为 0,得到最终参数 w = -1

代码 opt4_4.py 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# coding:utf-8
# 设损失函数 loss=(w+1)^2, 令 w 初值是常数 5。
# 反向传播就是求最优 w,即求最小 loss 对应的 w 值
import tensorflow as tf

# 定义待优化参数 w 初值赋 5
w = tf.Variable(tf.constant(5, dtype=tf.float32))

# 定义损失函数 loss
loss = tf.square(w+1)

# 定义反向传播方法
train_step = tf.train.GradientDescentOptimizer(0.2).minimize(loss)

# 生成会话,训练 40 轮
with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
for i in range(40):
sess.run(train_step)
w_val = sess.run(w)
loss_val = sess.run(loss)
print "After %s steps: w is %f, loss is %f." % (i, w_val,loss_val)

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
...

After 30 steps: w is -0.999999, loss is 0.000000.
After 31 steps: w is -1.000000, loss is 0.000000.
After 32 steps: w is -1.000000, loss is 0.000000.
After 33 steps: w is -1.000000, loss is 0.000000.
After 34 steps: w is -1.000000, loss is 0.000000.
After 35 steps: w is -1.000000, loss is 0.000000.
After 36 steps: w is -1.000000, loss is 0.000000.
After 37 steps: w is -1.000000, loss is 0.000000.
After 38 steps: w is -1.000000, loss is 0.000000.
After 39 steps: w is -1.000000, loss is 0.000000.

由结果可知,随着损失函数值的减小,w 无限趋近于 -1,模型计算推测出最优参数 w = -1

  • √ 学习率的设置
    学习率过大,会导致待优化的参数在最小值附近波动,不收敛;
    学习率过小,会导致待优化的参数收敛缓慢。

    【例如】

    • ① 对于上例的损失函数 "$loss=\left( w+1 \right) ^2$"。则将上述代码中学习率修改为 1,其余内容不变。(opt4_4-1.py)
      实验结果如下:

      1
      2
      3
      4
      5
      6
      7
      8
      ...
      After 11 steps: w is 5.000000, loss is 36.000000.
      After 12 steps: w is -7.000000, loss is 36.000000.
      After 13 steps: w is 5.000000, loss is 36.000000.
      After 14 steps: w is -7.000000, loss is 36.000000.
      After 15 steps: w is 5.000000, loss is 36.000000.
      After 16 steps: w is -7.000000, loss is 36.000000.
      ...

      由运行结果可知,损失函数 loss 值并没有收敛,而是在 5-7 之间波动。

    • ② 对于上例的损失函数 "$loss=\left( w+1 \right) ^2$"。则将上述代码中学习率修改为 0.0001,其余内容不变。(opt4_4-2.py)
      实验结果如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ...
      After 31 steps: w is 4.961716, loss is 35.542053.
      After 32 steps: w is 4.960523, loss is 35.527836.
      After 33 steps: w is 4.959331, loss is 35.513626.
      After 34 steps: w is 4.958139, loss is 35.499420.
      After 35 steps: w is 4.956947, loss is 35.485222.
      After 36 steps: w is 4.955756, loss is 35.471027.
      After 37 steps: w is 4.954565, loss is 35.456841.
      After 38 steps: w is 4.953373, loss is 35.442654.
      After 39 steps: w is 4.952183, loss is 35.428478.

      由运行结果可知,损失函数 loss 值缓慢下降,w 值也在小幅度变化,收敛缓慢。

  • √ 指数衰减学习率:学习率随着训练轮数变化而动态更新学习率计算公式如下

用 Tensorflow 的函数表示为:

1
2
3
4
5
6
7
global_step = tf.Variable(0, trainable=False) 
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
LEARNING_RATE_STEP, LEARNING_RATE_DECAY,
staircase=True/False
)

其中,
LEARNING_RATE_BASE 为学习率初始值,LEARNING_RATE_DECAY 为学习率衰减率,global_step 记录了当前训练轮数,为不可训练型参数。

学习率 learning_rate 更新频率为输入数据集总样本数除以每次喂入样本数。

staircase 设置为 True 时,表示 global_step/learning rate step 取整数,学习率阶梯型衰减;
staircase 设置为 false 时,学习率会是一条平滑下降的曲线。

【例如】

在本例中,模型训练过程不设定固定的学习率,使用指数衰减学习率进行训练。其中,学习率初值设置为 0.1,学习率衰减率设置为 0.99,BATCH_SIZE 设置为 1。

代码 opt4_5.py 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# coding:utf-8
# 设损失函数 loss=(w+1)^2, 令 w 初值是常数 10。反向传播就是求最优 w,即求最小 loss 对应的 w 值
# 使用指数衰减的学习率,在迭代初期得到较高的下降速度,可以在较小的训练轮数下取得更有收敛度。
import tensorflow as tf

LEARNING_RATE_BASE = 0.1 # 最初学习率
LEARNING_RATE_DECAY = 0.99 # 学习率衰减率
LEARNING_RATE_STEP = 1 # 喂入多少轮 BATCH_SIZE 后,更新一次学习率,一般设为:总样本数/BATCH_SIZE

# 运行了几轮 BATCH_SIZE 的计数器,初值给 0, 设为不被训练
global_step = tf.Variable(0, trainable=False)
# 定义指数下降学习率
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, LEARNING_RATE_STEP, LEARNING_RATE_DECAY, staircase=True)
# 定义待优化参数,初值给 10
w = tf.Variable(tf.constant(5, dtype=tf.float32))
# 定义损失函数 loss
loss = tf.square(w+1)
# 定义反向传播方法
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

# 生成会话,训练 40 轮
with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
for i in range(40):
sess.run(train_step)
learning_rate_val = sess.run(learning_rate)
global_step_val = sess.run(global_step)
w_val = sess.run(w)
loss_val = sess.run(loss)
print "After %s steps: global_step is %f, w is %f, learning rate is %f, loss is %f" % (i, global_step_val, w_val, learning_rate_val, loss_val)

运行结果如下:

1
2
3
4
5
6
...
After 35 steps: global_step is 36.000000, w is -0.992297, learning rate is 0.069641, loss is 0.000059
After 36 steps: global_step is 37.000000, w is -0.993369, learning rate is 0.068945, loss is 0.000044
After 37 steps: global_step is 38.000000, w is -0.994284, learning rate is 0.068255, loss is 0.000033
After 38 steps: global_step is 39.000000, w is -0.995064, learning rate is 0.067573, loss is 0.000024
After 39 steps: global_step is 40.000000, w is -0.995731, learning rate is 0.066897, loss is 0.000018

由结果可以看出,随着训练轮数增加学习率在不断减小。

滑动平均

  • √ 滑动平均:记录了一段时间内模型中所有参数 wb 各自的平均值。利用滑动平均值可以增强模型的泛化能力。

  • √ 滑动平均值(影子)计算公式
    影子 = 衰减率*影子 + (1-衰减率)*参数

    其中,

  • √ 用 Tesnsorflow 函数表示为

  • ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)
    其中,MOVING_AVERAGE_DECAY 表示滑动平均衰减率,一般会赋接近 1 的值,global_step 表示当前训练了多少轮。

  • ema_op = ema.apply(tf.trainable_variables())
    其中,ema.apply() 函数实现对括号内参数求滑动平均, tf.trainable_variables() 函数实现把所有待训练参数汇总为列表。

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
        with tf.control_dependencies([train_step, ema_op]):
    train_op = tf.no_op(name='train')
    ```
    其中,该函数实现将滑动平均和训练过程同步运行。

    查看模型中参数的平均值,可以用 `ema.average()` 函数。

    **【例如】**:

    在神经网络模型中,将 `MOVING_AVERAGE_DECAY` 设置为 0.99,参数 `w1` 设置为 0,`w1` 的滑动平均值设置为 0

    - ① 开始时,轮数 `global_step` 设置为 0,参数 `w1` 更新为 1,则 `w1` 的滑动平均值为:
    `w1 滑动平均值= min(0.99,1/10)*0+(1–min(0.99,1/10)*1= 0.9`
    - ② 当轮数 `global_step` 设置为 100 时,参数 `w1` 更新为 10,以下代码 `global_step` 保持为 100,每次执行滑动平均操作影子值更新,则滑动平均值变为:
    `w1 滑动平均值= min(0.99,101/110)*0.9+(1–min(0.99,101/110)*10= 0.826+0.818= 1.644`
    - ③ 再次运行,参数 `w1` 更新为 1.644,则滑动平均值变为:
    `w1 滑动平均值= min(0.99,101/110)*1.644+(1–min(0.99,101/110)*10= 2.328`
    - ④ 再次运行,参数 `w1` 更新为 2.328,则滑动平均值: `w1 滑动平均值=2.956`

    代码 [`opt4_6.py`](./opt4_6.py) 如下:

    ```python
    # coding:utf-8
    import tensorflow as tf

    # 1. 定义变量及滑动平均类
    # 定义一个 32 位浮点变量,初始值为 0.0 这个代码就是不断更新 w1 参数,优化 w1 参数,滑动平均做了个 w1 的影子
    w1 = tf.Variable(0, dtype=tf.float32)

    # 定义 num_updates(NN的迭代轮数),初始值为 0,不可被优化(训练),这个参数不训练
    global_step = tf.Variable(0, trainable=False)

    # 实例化滑动平均类,给衰减率为0.99,当前轮数global_step
    MOVING_AVERAGE_DECAY = 0.99
    ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

    # ema.apply 后的括号里是更新列表,每次运行 sess.run(ema_op) 时,对更新列表中的元素求滑动平均值。
    # 在实际应用中会使用 tf.trainable_variables() 自动将所有待训练的参数汇总为列表
    # ema_op = ema.apply([w1])
    ema_op = ema.apply(tf.trainable_variables())

    # 2. 查看不同迭代中变量取值的变化。
    with tf.Session() as sess:
    # 初始化
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    # 用 ema.average(w1) 获取 w1 滑动平均值 (要运行多个节点,作为列表中的元素列出,写在 sess.run 中)
    # 打印出当前参数 w1 和 w1 滑动平均值
    print "current global_step:", sess.run(global_step)
    print "current w1", sess.run([w1, ema.average(w1)])

    # 参数 w1 的值赋为 1
    sess.run(tf.assign(w1, 1))
    sess.run(ema_op)
    print "current global_step:", sess.run(global_step)
    print "current w1", sess.run([w1, ema.average(w1)])

    # 更新 global_step 和 w1 的值,模拟出轮数为 100时,参数 w1 变为 10, 以下代码 global_step 保持为 100,每次执行滑动平均操作,影子值会更新
    sess.run(tf.assign(global_step, 100))
    sess.run(tf.assign(w1, 10))
    sess.run(ema_op)
    print "current global_step:", sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    # 每次 sess.run 会更新一次 w1 的滑动平均值
    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

运行程序,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
current global_step: 0
current w1 [0.0, 0.0]
current global_step: 0
current w1 [1.0, 0.9]
current global_step: 100
current w1: [10.0, 1.6445453]
current global_step: 100
current w1: [10.0, 2.3281732]
current global_step: 100
current w1: [10.0, 2.955868]
current global_step: 100
current w1: [10.0, 3.532206]
current global_step: 100
current w1: [10.0, 4.061389]
current global_step: 100
current w1: [10.0, 4.547275]
current global_step: 100
current w1: [10.0, 4.9934072]

从运行结果可知,最初参数 w1 和滑动平均值都是 0;参数 w1 设定为 1 后,滑动平均值变为 0.9;当迭代轮数更新为 100 轮时,参数 w1 更新为 10 后,滑动平均值变为 1.644。随后每执行一次,参数

w1 的滑动平均值都向参数 w1 靠近。可见,滑动平均追随参数的变化而变化。

正则化

  • 过拟合:神经网络模型在训练数据集上的准确率较高,在新的数据进行预测或分类时准确率较低,说明模型的泛化能力差。
  • 正则化:在损失函数中给每个参数 w 加上权重,引入模型复杂度指标,从而抑制模型噪声,减小过拟合。

使用正则化后,损失函数 loss 变为两项之和:

loss = loss(y 与 y_) + REGULARIZER*loss(w)

其中,第一项是预测结果与标准答案之间的差距,如之前讲过的交叉熵、均方误差等;第二项是正则化计算结果。

  • 正则化计算方法:
    • ① L1 正则化:"$loss_{L1}=\sum_i{|w_i|}$"
      用 Tesnsorflow 函数表示:
      loss(w) = tf.contrib.layers.l1_regularizer(REGULARIZER)(w)
    • ② L2 正则化:"$loss_{L2}=\sum_i{|w_i|^2}$"
      用 Tesnsorflow 函数表示:
      loss(w) = tf.contrib.layers.l2_regularizer(REGULARIZER)(w)
  • √ 用 Tesnsorflow 函数实现正则化:

    1
    2
    3
    4
    5
    tf.add_to_collection(
    'losses',
    tf.contrib.layers.l2_regularizer(regularizer)(w)
    )
    loss = cem + tf.add_n(tf.get_collection('losses'))

    cem 的计算已在 4.1 节中给出。

【例如】
用 300 个符合正态分布的点 "$X\left[ x_0, x_1 \right]$" 作为数据集,根据点 "$X\left[ x_0, x_1 \right]$" 计算生成标注 Y_,将数据集标注为红色点和蓝色点。

标注规则为:当 "$x_{0}^{2}+x_{1}^{2}<2$" 时,y_=1,标注为红色;当 "$x_{0}^{2}+x_{1}^{2}\ge 2$" 时,y_=0,标注为蓝色。 我们分别用无正则化和有正则化两种方法,拟合曲线,把红色点和蓝色点分开。在实际分类时,

如果前向传播输出的预测值 y 接近 1 则为红色点概率越大,接近 0 则为蓝色点概率越大,输出的预测值 y 为 0.5 是红蓝点概率分界线。

在本例子中,我们使用了之前未用过的模块与函数:

  • matplotlib 模块:Python 中的可视化工具模块,实现函数可视化
    终端安装指令
    sudo pip install matplotlib
  • 函数 plt.scatter():利用指定颜色实现点 (x,y) 的可视化

    1
    2
    plt.scatter(x 坐标, y 坐标, c="颜色")
    plt.show()
  • 收集规定区域内所有的网格坐标点

    1
    2
    xx, yy = np.mgrid[起:止:步长, 起:止:步长]   # 找到规定区域以步长为分辨率的行列网格坐标点 
    grid = np.c_[xx.ravel(), yy.ravel()] # 收集规定区域内所有的网格坐标点
  • plt.contour() 函数:告知 x、y 坐标和各点高度,用 levels 指定高度的点描上颜色

    1
    2
    plt.contour(x 轴坐标值, y 轴坐标值, 该点的高度, levels=[等高线的高度]) 
    plt.show()

本例 opt4_7.py 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# coding:utf-8
# 0 导入模块 ,生成模拟数据集
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

BATCH_SIZE = 30
seed = 2

# 基于 seed 产生随机数
rdm = np.random.RandomState(seed)

# 随机数返回 300 行 2 列的矩阵,
# 表示 300 组坐标点 (x0, x1) 作为输入数据集
X = rdm.randn(300,2)

# 从 X 这个 300 行 2 列的矩阵中取出一行,
# 判断如果两个坐标的平方和小于 2,给 Y 赋值 1,其余赋值 0
# 作为输入数据集的标签(正确答案)
Y_ = [int(x0*x0 + x1*x1 <2) for (x0,x1) in X]

# 遍历 Y 中的每个元素,1 赋值 'red' 其余赋值 'blue',
# 这样可视化显示时人可以直观区分
Y_c = [['red' if y else 'blue'] for y in Y_]

# 对数据集 X 和标签 Y 进行 shape 整理,
# 第一个元素为 -1 表示,随第二个参数计算得到,第二个元素表示多少列,
# 把 X 整理为 n 行 2 列,把 Y 整理为 n 行 1 列
X = np.vstack(X).reshape(-1,2)
Y_ = np.vstack(Y_).reshape(-1,1)
print X
print Y_
print Y_c

# 用 plt.scatter 画出数据集 X 各行中第 0 列元素和第 1 列元素的点即各行的 (x0, x1),
# 用各行 Y_c 对应的值表示颜色(c 是 color 的缩写)
plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
plt.show()


# 定义神经网络的输入、参数和输出,定义前向传播过程
def get_weight(shape, regularizer):
w = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
return w

def get_bias(shape):
b = tf.Variable(tf.constant(0.01, shape=shape))
return b

x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))

w1 = get_weight([2,11], 0.01)
b1 = get_bias([11])
y1 = tf.nn.relu(tf.matmul(x, w1)+b1)

w2 = get_weight([11,1], 0.01)
b2 = get_bias([1])
y = tf.matmul(y1, w2)+b2


# 定义损失函数
loss_mse = tf.reduce_mean(tf.square(y-y_))
loss_total = loss_mse + tf.add_n(tf.get_collection('losses'))


# 定义反向传播方法:不含正则化
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss_mse)

with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 40000
for i in range(STEPS):
start = (i*BATCH_SIZE) % 300
end = start + BATCH_SIZE
sess.run(train_step, feed_dict={x:X[start:end], y_:Y_[start:end]})
if i % 2000 == 0:
loss_mse_v = sess.run(loss_mse, feed_dict={x:X, y_:Y_})
print("After %d steps, loss is: %f" %(i, loss_mse_v))

# xx 在 -3 到 3 之间以步长为 0.01,yy 在 -3 到 3 之间以步长 0.01,生成二维网格坐标点
xx, yy = np.mgrid[-3:3:.01, -3:3:.01]

# 将 xx, yy 拉直,并合并成一个 2 列的矩阵,得到一个网格坐标点的集合
grid = np.c_[xx.ravel(), yy.ravel()]

# 将网格坐标点喂入神经网络 ,probs 为输出
probs = sess.run(y, feed_dict={x:grid})

# probs 的 shape 调整成 xx 的样子
probs = probs.reshape(xx.shape)
print "w1:\n",sess.run(w1)
print "b1:\n",sess.run(b1)
print "w2:\n",sess.run(w2)
print "b2:\n",sess.run(b2)

plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
plt.contour(xx, yy, probs, levels=[.5])
plt.show()



# 定义反向传播方法:包含正则化
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss_total)

with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 40000
for i in range(STEPS):
start = (i*BATCH_SIZE) % 300
end = start + BATCH_SIZE
sess.run(train_step, feed_dict={x: X[start:end], y_:Y_[start:end]})
if i % 2000 == 0:
loss_v = sess.run(loss_total, feed_dict={x:X,y_:Y_})
print("After %d steps, loss is: %f" %(i, loss_v))

xx, yy = np.mgrid[-3:3:.01, -3:3:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)
print "w1:\n",sess.run(w1)
print "b1:\n",sess.run(b1)
print "w2:\n",sess.run(w2)
print "b2:\n",sess.run(b2)

plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
plt.contour(xx, yy, probs, levels=[.5])
plt.show()

执行代码,效果如下:

首先,数据集实现可视化,"$x_{0}^{2}+x_{1}^{2}<2$" 的点显示红色, "$x_{0}^{2}+x_{1}^{2}\ge 2$" 的点显示蓝色,如图所示:

接着,执行无正则化的训练过程,把红色的点和蓝色的点分开,生成曲线如下图所示:

最后,执行有正则化的训练过程,把红色的点和蓝色的点分开,生成曲线如下图所示:

对比无正则化与有正则化模型的训练结果,可看出有正则化模型的拟合曲线平滑,模型具有更好的泛化能力。

搭建模块化神经网络八股

  • 前向传播:由输入到输出,搭建完整的网络结构描述前向传播的过程需要定义三个函数

    • forward()

      1
      2
      3
      4
      5
      def forward(x, regularizer): 
      w=
      b=
      y=
      return y

      第一个函数 forward() 完成网络结构的设计,从输入到输出搭建完整的网络结构,实现前向传播过程。该函数中,参数 x 为输入,regularizer 为正则化权重,返回值为预测或分类结果 y。

    • get_weight()

      1
      2
      3
      4
      5
      6
      7
      def get_weight(shape, regularizer):
      w = tf.Variable( )
      tf.add_to_collection(
      'losses',
      tf.contrib.layers.l2_regularizer(regularizer)(w)
      )
      return w

      第二个函数 get_weight() 对参数 w 设定。该函数中,参数 shape 表示参数 w 的形状,regularizer 表示正则化权重,返回值为参数 w。
      其中,tf.variable() 给 w 赋初值,tf.add_to_collection() 表示将参数 w 正则化损失加到总损失 losses 中。

    • get_bias()

      1
      2
      3
      def get_bias(shape):
      b = tf.Variable( )
      return b

      第三个函数 get_bias() 对参数 b 进行设定。该函数中,参数 shape 表示参数 b 的形状,返回值为参数b。其中,tf.variable()表示给 b 赋初值。

  • 反向传播:训练网络,优化网络参数,提高模型准确性。

    • backward()

      1
      2
      3
      4
      5
      6
      def backward( ):
      x = tf.placeholder( )
      y_ = tf.placeholder( )
      y = forward.forward(x, REGULARIZER)
      global_step = tf.Variable(0, trainable=False)
      loss =

      函数 backward() 中,placeholder() 实现对数据集 x 和标准答案 y_ 占位,forward.forward() 实现前向传播的网络结构,参数 global_step 表示训练轮数,设置为不可训练型参数。

在训练网络模型时,常将正则化、指数衰减学习率和滑动平均这三个方法作为模型优化方法。

  • √ 在 Tensorflow 中,正则化 表示为:

    首先,计算预测结果与标准答案的损失值

    • ① MSE:y 与 y_ 的差距(loss_mse) = tf.reduce_mean(tf.square(y-y_))
    • ② 交叉熵:

      1
      2
      ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
      y 与 y_的差距(cem) = tf.reduce_mean(ce)
    • ③ 自定义:yy_ 的差距

      其次,总损失值为预测结果与标准答案的损失值加上正则化项
      loss = y 与 y_的差距 + tf.add_n(tf.get_collection('losses'))

  • √ 在 Tensorflow 中,指数衰减学习率 表示为:

    1
    2
    3
    4
    5
    6
    7
    8
    learning_rate = tf.train.exponential_decay(
    LEARNING_RATE_BASE,
    global_step,
    数据集总样本数 / BATCH_SIZE,
    LEARNING_RATE_DECAY,
    staircase=True
    )
    train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
  • √ 在 Tensorflow 中,滑动平均 表示为:

    1
    2
    3
    4
    5
    6
    7
    ema = tf.train.ExponentialMovingAverage(
    MOVING_AVERAGE_DECAY,
    global_step
    )
    ema_op = ema.apply(tf.trainable_variables())
    with tf.control_dependencies([train_step, ema_op]):
    train_op = tf.no_op(name='train')

    其中,滑动平均和指数衰减学习率中的 global_step 为同一个参数。

  • with 结构初始化所有参数

    1
    2
    3
    4
    5
    6
    7
    8
    with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    for i in range(STEPS):
    sess.run(train_step, feed_dict={x: , y_: })
    if i % 轮数 == 0:
    print

    其中,with 结构用于初始化所有参数信息以及实现调用训练过程,并打印出 loss 值。

  • 判断 python 运行文件是否为主文件
    if name ==' main__': backward()
    该部分用来判断 python 运行的文件是否为主文件。若是主文件,则执行 backword() 函数。

【例如】

用 300 个符合正态分布的点 "$X\left[ x_0, x_1 \right]$" 作为数据集,根据点 "$X\left[ x_0, x_1 \right]$" 计算生成标注 Y_,将数据集标注为红色点和蓝色点。

标注规则为:当 "$x_{0}^{2}+x_{1}^{2}<2$" 时,y_=1,点 X 标注为红色;当 "$x_{0}^{2}+x_{1}^{2}\ge 2$" 时,y_=0,点 X 标注为蓝色。

我们加入指数衰减学习率优化效率,加入正则化提高泛化性,并使用模块化设计方法,把红色点和蓝色点分开。

代码总共分为三个模块:
生成数据集(generateds.py)、
前向传播(forward.py)、
反向传播(backward.py)。

  • ① 生成数据集的模块 (generateds.py)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # coding:utf-8
    # 0 导入模块 ,生成模拟数据集
    import numpy as np
    import matplotlib.pyplot as plt

    seed = 2

    def generateds():
    # 基于seed产生随机数
    rdm = np.random.RandomState(seed)

    # 随机数返回 300 行 2 列的矩阵
    # 表示 300 组坐标点 (x0,x1)作为输入数据集
    X = rdm.randn(300,2)

    # 从 X 这个 300 行 2 列的矩阵中取出一行
    # 判断如果两个坐标的平方和小于 2,给 Y 赋值 1,其余赋值 0
    # 作为输入数据集的标签(正确答案)
    Y_ = [int(x0*x0 + x1*x1 <2) for (x0,x1) in X]

    # 遍历Y中的每个元素,1 赋值 'red' 其余赋值 'blue'
    # 这样可视化显示时人可以直观区分
    Y_c = [['red' if y else 'blue'] for y in Y_]

    # 对数据集 X 和标签 Y 进行形状整理
    # 第一个元素为 -1 表示跟随第二列计算
    # 第二个元素表示多少列,可见 X 为两列,Y 为 1 列
    X = np.vstack(X).reshape(-1,2)
    Y_ = np.vstack(Y_).reshape(-1,1)

    return X, Y_, Y_c

    # print X
    # print Y_
    # print Y_c

    # # 用 plt.scatter 画出数据集 X 各行中
    # # 第 0 列元素和第 1 列元素的点即各行的(x0,x1),
    # # 用各行 Y_c对应的值表示颜色(c 是 color 的缩写)
    # plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
    # plt.show()
  • ② 前向传播模块 (forward.py)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # coding:utf-8
    # 0 导入模块 ,生成模拟数据集
    import tensorflow as tf

    # 定义神经网络的输入、参数和输出,定义前向传播过程
    def get_weight(shape, regularizer):
    w = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
    tf.add_to_collection(
    'losses',
    tf.contrib.layers.l2_regularizer(regularizer)(w)
    )
    return w

    def get_bias(shape):
    b = tf.Variable(tf.constant(0.01, shape=shape))
    return b

    def forward(x, regularizer):

    w1 = get_weight([2,11], regularizer)
    b1 = get_bias([11])
    y1 = tf.nn.relu(tf.matmul(x, w1) + b1)

    w2 = get_weight([11,1], regularizer)
    b2 = get_bias([1])
    y = tf.matmul(y1, w2) + b2

    return y
  • ③ 反向传播模块 (backward.py)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    # coding:utf-8
    # 0 导入模块 ,生成模拟数据集
    import tensorflow as tf
    import numpy as np
    import matplotlib.pyplot as plt
    import opt4_8_generateds
    import opt4_8_forward

    STEPS = 40000
    BATCH_SIZE = 30
    LEARNING_RATE_BASE = 0.001
    LEARNING_RATE_DECAY = 0.999
    REGULARIZER = 0.01

    def backward():
    x = tf.placeholder(tf.float32, shape=(None, 2))
    y_ = tf.placeholder(tf.float32, shape=(None, 1))

    X, Y_, Y_c = opt4_8_generateds.generateds()

    y = opt4_8_forward.forward(x, REGULARIZER)

    global_step = tf.Variable(0,trainable=False)

    learning_rate = tf.train.exponential_decay(
    LEARNING_RATE_BASE,
    global_step,
    300/BATCH_SIZE,
    LEARNING_RATE_DECAY,
    staircase=True)


    # 定义损失函数
    loss_mse = tf.reduce_mean(tf.square(y-y_))
    loss_total = loss_mse + tf.add_n(tf.get_collection('losses'))

    # 定义反向传播方法:包含正则化
    train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss_total)

    with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    for i in range(STEPS):
    start = (i*BATCH_SIZE) % 300
    end = start + BATCH_SIZE
    sess.run(
    train_step,
    feed_dict={x: X[start:end], y_:Y_[start:end]}
    )
    if i % 2000 == 0:
    loss_v = sess.run(loss_total, feed_dict={x:X,y_:Y_})
    print("After %d steps, loss is: %f" %(i, loss_v))

    xx, yy = np.mgrid[-3:3:.01, -3:3:.01]
    grid = np.c_[xx.ravel(), yy.ravel()]
    probs = sess.run(y, feed_dict={x:grid})
    probs = probs.reshape(xx.shape)

    plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
    plt.contour(xx, yy, probs, levels=[.5])
    plt.show()

    if __name__=='__main__':
    backward()

运行代码,结果如下:

由运行结果可见,程序使用模块化设计方法,加入指数衰减学习率,使用正则化后,红色点和蓝色点的分割曲线相对平滑,效果变好。




搭建神经网络(mnist 数据集上训练模型,输出手写数字识别准确率)

MNIST数据集

  • √ mnist 数据集
    包含 7 万张黑底白字手写数字图片,其中 55000 张为训练集, 5000 张为验证集,10000 张为测试集。
    每张图片大小为 28*28 像素,图片中纯黑色像素值为 0,纯白色像素值为 1
    数据集的标签是长度为 10 的一维数组,数组中每个元素索引号表示对应数字出现的概率。

    在将 mnist 数据集作为输入喂入神经网络时,需先将数据集中每张图片变为长度 784 一维数组,将该数组作为神经网络输入特征喂入神经网络。

    【例如】
    一张数字手写体图片变成长度为 784 的一维数组 [0. 0. 0. 0. 0.231 0.235 0.459 ... 0.219 0. 0. 0. 0.] 输入神经网络。
    该图片对应的标签为 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0],标签中索引号为 6 的元素为 1,表示是数字 6 出现的概率为 100%,则该图片对应的识别结果是 6。

  • √ 使用 input_data 模块中的 read_data_sets() 函数加载 mnist 数据集

    1
    2
    from tensorflow.examples.tutorials.mnist import input_data 
    mnist = input_data.read_data_sets('./data/',one_hot=True)

    read_data_sets() 函数中有两个参数,第一个参数表示数据集存放路径,第二个参数表示数据集的存取形式。
    当第二个参数为 Ture 时,表示以独热码形式存取数据集。read_data_sets() 函数运行时,会检查指定路径内是否已经有数据集,
    若指定路径中没有数据集,则自动下载,并将 mnist 数据集分为训练集 train、验证集 validation 和测试集 test 存放。在终端显示如下内容:

    1
    2
    3
    4
    Extracting ./data/train-images-idx3-ubyte.gz 
    Extracting ./data/train-labels-idx1-ubyte.gz
    Extracting ./data/tl0k-images-idx3-ubyte.gz
    Extracting ./data/ tl0k-labels-idx1-ubyte.gz
  • √ 返回 mnist 数据集中训练集 train、验证集 validation 和测试集 test 样本数
    在 Tensorflow 中用以下函数返回子集样本数:

    • ①返回训练集 train 样本数
      print "train data size:", mnist.train.mun_examples
      输出结果:train data size:55000

    • ② 返回验证集 validation 样本数
      print "validation data size:", mnist.validation.mun_examples
      输出结果:validation data size:5000

    • ③ 返回测试集 test 样本数
      print "test data size:", mnist.test.mun_examples
      输出结果:test data size:10000

  • √ 使用 train.labels 函数返回 mnist 数据集标签
    【例如】
    在 mnist 数据集中,若想要查看训练集中第 0 张图片的标签,则使用如下函数 mnist.train.labels[0]
    输出结果:array([0.,0.,0.,0.,0.,0.,1.,0.,0.,0])

  • √ 使用 train.images 函数返回 mnist 数据集图片像素值
    【例如】
    在 mnist 数据集中,若想要查看训练集中第 0 张图片像素值,则使用如下函数 mnist.train.images[0]
    输出结果:

    1
    2
    3
    4
    array([0. ,0. ,0. ,
    1. ,0. ,0. ,
    2. ,0. ,0. ,
    … … …])
  • √ 使用 mnist.train.next_batch()函数将数据输入神经网络
    【例如】

    1
    2
    3
    4
    BATCH_SIZE = 200
    xs,ys = mnist.train.next_batch(BATCH_SIZE)
    print "xs shape:",xs.shape
    print "ys shape:",ys.shape

    输出结果:xs.shape(200,784)
    输出结果:ys.shape(200,10)

    其中,mnist.train.next_batch() 函数包含一个参数 BATCH_SIZE,表示随机从训练集中抽取 BATCH_SIZE 个样本输入神经网络,并将样本的像素值和标签分别赋给 xsys

    在本例中,BATCH_SIZE 设置为 200,表示一次将 200 个样本的像素值和标签分别赋值给 xsys,故 xs 的形状为(200,784),对应的 ys 的形状为 (200,10)。

  • √ 实现”Mnist 数据集手写数字识别”的常用函数

    • tf.get_collection(" ") 函数表示从 collection 集合中取出全部变量生成一个列表

    • tf.add( ) 函数表示将参数列表中对应元素相加
      【例如】

      1
      2
      3
      4
      x=tf.constant([[1,2],[1,2]])
      y=tf.constant([[1,1],[1,2]])
      z=tf.add(x,y)
      print z

      输出结果:
      [[2,3],[2,4]]

    • tf.cast(x,dtype) 函数表示将参数 x 转换为指定数据类型
      【例如】

      1
      2
      A = tf.convert_to_tensor(np.array([[1,1,2,4], [3,4,8,5]])) print A.dtype
      b = tf.cast(A, tf.float32) print b.dtype

      结果输出:

      1
      2
      <dtype: 'int64'>
      <dtype: 'float32'>

      从输出结果看出,将矩阵 A 由整数型变为 32 位浮点型。

    • tf.equal( ) 函数表示对比两个矩阵或者向量的元素
      若对应元素相等,则返回 True;若对应元素不相等,则返回 False
      【例如】

      1
      2
      3
      4
      5
      A = [[1,3,4,5,6]]
      B = [[1,3,4,3,2]]

      with tf.Session( ) as sess:
      print(sess.run(tf.equal(A, B)))

      输出结果:[[ True True True False False]]

      在矩阵 AB 中,第 1、2、3 个元素相等,第 4、5 个元素不等,故输出结果中,第 1、2、3 个元素取值为 True,第 4、5 个元素取值为 False

    • tf.reduce_mean(x,axis) 函数表示求取矩阵或张量指定维度的平均值
      若不指定第二个参数,则在所有元素中取平均值;
      若指定第二个参数为 0,则在第一维元素上取平均值,即每一列求平均值;
      若指定第二个参数为 1,则在第二维元素上取平均值,即每一行求平均值。

      【例如】

      1
      2
      3
      4
      x = [[1., 1.]
      [2., 2.]]

      print(tf.reduce_mean(x))

      输出结果:1.5

      print(tf.reduce_mean(x, 0))
      输出结果:[1.5, 1.5]

      print(tf.reduce_mean(x, 1))
      输出结果:[1., 1.]

    • tf.argmax(x,axis) 函数表示返回指定维度 axis 下,参数 x 中最大值索引号
      【例如】
      tf.argmax([1,0,0],1) 函数中,axis 为 1,参数 x 为 [1,0,0],表示在参数 x的第一个维度取最大值对应的索引号,故返回 0。

    • os.path.join() 函数表示把参数字符串按照路径命名规则拼接
      【例如】

      1
      2
      import os
      os.path.join('/hello/','good/boy/','doiido')

      输出结果:'/hello/good/boy/doiido'

    • 字符串.split( ) 函数表示按照指定”拆分符”对字符串拆分,返回拆分列表
      【例如】
      './model/mnist_model-1001'.split('/')[-1].split('-')[-1]

      在该例子中,共进行两次拆分。
      第一个拆分符为 /,返回拆分列表,并提取 列表中索引为 -1 的元素即倒数第一个元素;
      第二个拆分符为 -,返回拆分列表,并提取列表中索引为 -1 的元素即倒数第一个元素,故函数返回值为 1001。

    • tf.Graph( ).as_default( ) 函数表示将当前图设置成为默认图,并返回一个上下文管理器。该函数一般与 with 关键字搭配使用,应用于将已经定义好的神经网络在计算图中复现
      【例如】
      with tf.Graph().as_default() as g,表示将在 Graph() 内定义的节点加入到计算图 g 中。

  • √ 神经网络模型的保存
    在反向传播过程中,一般会间隔一定轮数保存一次神经网络模型,并产生三个文件
    (保存当前图结构的 .meta 文件、保存当前参数名的 .index 文件、保存当前参数的 .data 文件)
    在 Tensorflow 中如下表示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    saver = tf.train.Saver() 
    with tf.Session() as sess:
    for i in range(STEPS):
    if i % 轮 数 == 0:
    saver.save(
    sess,
    os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
    global_step=global_step
    )

    其中,tf.train.Saver() 用来实例化 saver 对象。上述代码表示,神经网络每循环规定的轮数,将神经网络模型中所有的参数等信息保存到指定的路径中,并在存放网络模型的文件夹名称中注明保存模型时的训练轮数。

  • √ 神经网络模型的加载
    在测试网络效果时,需要将训练好的神经网络模型加载
    在 Tensorflow 中这样表示:

    1
    2
    3
    4
    with tf.Session() as sess:
    ckpt = tf.train.get_checkpoint_state(存储路径)
    if ckpt and ckpt.model_checkpoint_path:
    saver.restore(sess, ckpt.model_checkpoint_path)

    with 结构中进行加载保存的神经网络模型,若 ckpt 和保存的模型在指定路径中存在,则将保存的神经网络模型加载到当前会话中。

  • √ 加载模型中参数的滑动平均值
    在保存模型时,若模型中采用滑动平均,则参数的滑动平均值会保存在相应文件中。通过实例化 saver 对象,实现参数滑动平均值的加载
    在 Tensorflow 中如下表示:

    1
    2
    3
    ema = tf.train.ExponentialMovingAverage(滑动平均基数) 
    ema_restore = ema.variables_to_restore()
    saver = tf.train.Saver(ema_restore)
  • √ 神经网络模型准确率评估方法
    在网络评估时,一般通过计算在一组数据上的识别准确率,评估神经网络的效果。
    在 Tensorflow 中这样表示:

    1
    2
    correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    在上述中,y 表示在一组数据(即 batch_size 个数据)上神经网络模型的预测结果,y 的形状为 [batch_size,10],每一行表示一张图片的识别结果。

    通过 tf.argmax() 函数取出每张图片对应向量中最大值元素对应的索引值,组成长度为输入数据 batch_size 个的一维数组。
    通过 tf.equal() 函数判断预测结果张量和实际标签张量的每个维度是否相等,若相等则返回 True,不相等则返回 False
    通过 tf.cast() 函数将得到的布尔型数值转化为实数型,
    再通过tf.reduce_mean() 函数求平均值,最终得到神经网络模型在本组数据上的准确率。

模块化搭建神经网络

神经网络八股包括前向传播过程、反向传播过程、反向传播过程中用到的正则化、指数衰减学习率、滑动平均方法的设置、以及测试模块。

  • √ 前向传播过程(forward.py)

    前向传播过程完成神经网络的搭建,结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def forward(x, regularizer):
    w=
    b=
    y=
    return y

    def get_weight(shape, regularizer):

    def get_bias(shape):

    前向传播过程中,需要定义神经网络中的参数 w 和偏置 b,定义由输入到输出的网络结构。
    通过定义函数 get_weight() 实现对参数 w 的设置,包括参数 w 的形状和是否正则化的标志。同样,通过定义函数 get_bias() 实现对偏置 b 的设置。

  • √ 反向传播过程(backword.py)

    反向传播过程完成网络参数的训练,结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def backward( mnist ):
    x = tf.placeholder(dtype, shape )
    y_ = tf.placeholder(dtype, shape )

    # 定义前向传播函数
    y = forward( )
    global_step =
    loss =

    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    # 实例化 saver 对象
    saver = tf.train.Saver()
    with tf.Session() as sess:
    # 初始化所有模型参数
    tf.initialize_all_variables().run()

    # 训练模型
    for i in range(STEPS):
    sess.run(train_step, feed_dict={x: , y_: })
    if i % 轮数 == 0:
    print
    saver.save( )

    反向传播过程中,
    tf.placeholder(dtype, shape) 函数实现训练样本 x 和样本标签 y_占位,
    函数参数 dtype 表示数据的类型,shape 表示数据的形状;
    y 表示定义的前向传播函数 forward
    loss 表示定义的损失函数,一般为预测值与样本标签的交叉熵(或均方误差)与正则化损失之和;
    train_step 表示利用优化算法对模型参数进行优化
    常用优化算法 GradientDescentOptimizerAdamOptimizerMomentumOptimizer 算法,
    在上述代码中使用的 GradientDescentOptimizer 优化算法。

    接着实例化 saver 对象,
    其中利用 tf.initialize_all_variables().run() 函数实例化所有参数模型,
    利用 sess.run( ) 函数实现模型的训练优化过程,并每间隔一定轮数保存一次模型。

  • √ 正则化、指数衰减学习率、滑动平均方法的设置

  • ① 正则化项 regularization

当在前向传播过程中即 forward.py 文件中,设置正则化参数 regularization 为 1 时,则表明在反向传播过程中优化模型参数时,需要在损失函数中加入正则化项。

结构如下:

首先,需要在前向传播过程即 forward.py 文件中加入

1
2
if regularizer != None: 
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))

其次,需要在反向传播过程即 backword.py 文件中加入

1
2
3
ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
cem = tf.reduce_mean(ce)
loss = cem + tf.add_n(tf.get_collection('losses'))

其中,tf.nn.sparse_softmax_cross_entropy_with_logits() 表示 softmax() 函数与交叉熵一起使用。

  • ② 指数衰减学习率
    在训练模型时,使用指数衰减学习率可以使模型在训练的前期快速收敛接近较优解,又可以保证模型在训练后期不会有太大波动。

运用指数衰减学习率,需要在反向传播过程即 backword.py 文件中加入:

1
2
3
4
5
6
7
learning_rate = tf.train.exponential_decay( 
LEARNING_RATE_BASE,
global_step,
LEARNING_RATE_STEP,
LEARNING_RATE_DECAY,
staircase=True
)
  • ③ 滑动平均
    在模型训练时引入滑动平均可以使模型在测试数据上表现的更加健壮。
    需要在反向传播过程即 backword.py 文件中加入
    1
    2
    3
    4
    5
    6
    7
    8
    ema = tf.train.ExponentialMovingAverage(
    MOVING_AVERAGE_DECAY,
    global_step
    )
    ema_op = ema.apply(tf.trainable_variables())

    with tf.control_dependencies([train_step, ema_op]):
    train_op = tf.no_op(name='train')
  • √ 测试过程(test.py)

    当神经网络模型训练完成后,便可用于测试数据集,验证神经网络的性能。结构如下:

    首先,制定模型测试函数 test()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    def test( mnist ):
    with tf.Graph( ).as_default( ) as g:
    # 给 x y_占位
    x = tf.placeholder(dtype,shape)
    y_ = tf.placeholder(dtype,shape)

    #前向传播得到预测结果 y
    y = mnist_forward.forward(x, None) #前向传播得到 y

    #实例化可还原滑动平均的 saver
    ema = tf.train.ExponentialMovingAverage(滑动衰减率)
    ema_restore = ema.variables_to_restore()
    saver = tf.train.Saver(ema_restore)

    #计算正确率
    correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    while True:
    with tf.Session() as sess:
    #加载训练好的模型
    ckpt = tf.train.get_checkpoint_state(存储路径)

    #如果已有 ckpt 模型则恢复
    if ckpt and ckpt.model_checkpoint_path:
    #恢复会话
    saver.restore(sess, ckpt.model_checkpoint_path)

    #恢复轮数
    global_ste = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]

    #计算准确率
    accuracy_score = sess.run(accuracy, feed_dict={x:测试数据, y_:测试数据标签 })

    # 打印提示
    print("After %s training step(s), test accuracy=%g" % (global_step, accuracy_score))
    #如果没有模型
    else:
    print('No checkpoint file found') #模型不存在提示
    return

    其次,制定 main()函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def main():
    #加载测试数据集
    mnist = input_data.read_data_sets("./data/", one_hot=True)

    #调用定义好的测试函数 test()
    test(mnist)

    if __name__ == '__main__':
    main()

    通过对测试数据的预测得到准确率,从而判断出训练出的神经网络模型的性能好坏。当准确率低时,可能原因有模型需要改进,或者是训练数据量太少导致过拟合。

手写数字识别准确率输出

实现手写体 mnist 数据集的识别任务,共分为三个模块文件,分别是描述网络结构的前向传播过程文件(mnist_forward.py)、描述网络参数优化方法的反向传播过程文件(mnist_backward.py)、验证模型准确率的测试过程文件(mnist_test.py)。

  • √ 前向传播过程文件(mnist_forward.py)
    在前向传播过程中,需要定义网络模型输入层个数、隐藏层节点数、输出层个数,定义网络参数 w、偏置 b,定义由输入到输出的神经网络架构。

    实现手写体 mnist 数据集的识别任务前向传播过程如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import tensorflow as tf

    INPUT_NODE = 784
    OUTPUT_NODE = 10
    LAYER1_NODE = 500

    def get_weight(shape, regularizer):
    w = tf.Variable(tf.truncated_normal(shape,stddev=0.1))
    if regularizer != None:
    tf.add_to_collection(
    'losses',
    tf.contrib.layers.l2_regularizer(regularizer)(w)
    )
    return w


    def get_bias(shape):
    b = tf.Variable(tf.zeros(shape))
    return b

    def forward(x, regularizer):
    w1 = get_weight([INPUT_NODE, LAYER1_NODE], regularizer)
    b1 = get_bias([LAYER1_NODE])
    y1 = tf.nn.relu(tf.matmul(x, w1) + b1)

    w2 = get_weight([LAYER1_NODE, OUTPUT_NODE], regularizer)
    b2 = get_bias([OUTPUT_NODE])
    y = tf.matmul(y1, w2) + b2
    return y

    由上述代码可知,在前向传播过程中,规定网络输入结点为 784 个(代表每张输入图片的像素个数),隐藏层节点 500 个,输出节点 10 个(表示输出为数字 0-9 的十分类)。

    由输入层到隐藏层的参数 w1 形状为 [784,500],由隐藏层到输出层的参数 w2 形状为 [500,10],参数满足截断正态分布,并使用正则化,将每个参数的正则化损失加到总损失中。

    由输入层到隐藏层的偏置 b1 形状为长度为 500 的一维数组,由隐藏层到输出层的偏置 b2 形状为长度为 10 的一维数组,初始化值为全 0。

    前向传播结构第一层为输入 x 与参数 w1 矩阵相乘加上偏置 b1,再经过 relu 函数,得到隐藏层输出 y1。前向传播结构第二层为隐藏层输出 y1 与参数 w2 矩阵相乘加上偏置 b2,得到输出 y。
    由于输出 y 要经过 softmax 函数,使其符合概率分布,故输出 y 不经过 relu 函数。

  • √ 反向传播过程文件(mnist_backward.py)
    反向传播过程实现利用训练数据集对神经网络模型训练,通过降低损失函数值,实现网络模型参数的优化,从而得到准确率高且泛化能力强的神经网络模型。 实现手写体 mnist 数据集的识别任务反向传播过程如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    import tensorflow as tf
    from tensorflow.examples.tutorials.mnist import input_data
    import mnist_forward
    import os

    BATCH_SIZE = 200
    LEARNING_RATE_BASE = 0.1
    LEARNING_RATE_DECAY = 0.99
    REGULARIZER = 0.0001
    STEPS = 50000
    MOVING_AVERAGE_DECAY = 0.99
    MODEL_SAVE_PATH="./model/"
    MODEL_NAME="mnist_model"


    def backward(mnist):

    x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
    y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
    y = mnist_forward.forward(x, REGULARIZER)
    global_step = tf.Variable(0, trainable=False)

    ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cem = tf.reduce_mean(ce)
    loss = cem + tf.add_n(tf.get_collection('losses'))

    learning_rate = tf.train.exponential_decay(
    LEARNING_RATE_BASE,
    global_step,
    mnist.train.num_examples / BATCH_SIZE,
    LEARNING_RATE_DECAY,
    staircase=True)

    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    ema_op = ema.apply(tf.trainable_variables())
    with tf.control_dependencies([train_step, ema_op]):
    train_op = tf.no_op(name='train')

    saver = tf.train.Saver()

    with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    for i in range(STEPS):
    xs, ys = mnist.train.next_batch(BATCH_SIZE)
    _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
    if i % 1000 == 0:
    print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
    saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)


    def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True)
    backward(mnist)

    if __name__ == '__main__':
    main()

    由上述代码可知,在反向传播过程中,首先引入 tensorflowinput_data、前向传播 mnist_forwardos 模块,定义每轮喂入神经网络的图片数、初始学习率、学习率衰减率、正则化系数、训练轮数、模型保存路径以及模型保存名称等相关信息。

    在反向传播函数 backword 中,首先读入 mnist,用 placeholder 给训练数据 x 和标签 y_ 占位,调用 mnist_forward 文件中的前向传播过程 forword() 函数,并设置正则化,计算训练数据集上的预测结果 y,并给当前计算轮数计数器赋值,设定为不可训练类型。

    接着,调用包含所有参数正则化损失的损失函数 loss,并设定指数衰减学习率 learning_rate
    然后,使用梯度衰减算法对模型优化,降低损失函数,并定义参数的滑动平均。
    最后,在 with 结构中,实现所有参数初始化,每次喂入 batch_size 组(即 200 组)训练数据和对应标签,循环迭代 steps 轮,并每隔 1000 轮打印出一次损失函数值信息,并将当前会话加载到指定路径。
    最后,通过主函数 main(),加载指定路径下的训练数据集,并调用规定的 backward() 函数训练模型。

  • √ 测试过程文件(mnist_test.py)
    当训练完模型后,给神经网络模型输入测试集验证网络的准确性和泛化性。注意,所用的测试集和训练集是相互独立的。

    实现手写体 mnist 数据集的识别任务测试传播过程如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # coding:utf-8
    import time
    import tensorflow as tf
    from tensorflow.examples.tutorials.mnist import input_data
    import mnist_forward
    import mnist_backward

    TEST_INTERVAL_SECS = 5

    def test(mnist):
    with tf.Graph().as_default() as g:
    x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
    y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
    y = mnist_forward.forward(x, None)

    ema = tf.train.ExponentialMovingAverage(mnist_backward.MOVING_AVERAGE_DECAY)
    ema_restore = ema.variables_to_restore()
    saver = tf.train.Saver(ema_restore)

    correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    while True:
    with tf.Session() as sess:
    ckpt = tf.train.get_checkpoint_state(mnist_backward.MODEL_SAVE_PATH)
    if ckpt and ckpt.model_checkpoint_path:
    saver.restore(sess, ckpt.model_checkpoint_path)
    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
    accuracy_score = sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
    print("After %s training step(s), test accuracy = %g" % (global_step, accuracy_score))
    else:
    print('No checkpoint file found')
    return
    time.sleep(TEST_INTERVAL_SECS)

    def main():
    mnist = input_data.read_data_sets("./data/", one_hot=True)
    test(mnist)

    if __name__ == '__main__':
    main()

    在上述代码中,首先需要引入 time 模块、tensorflowinput_data、前向传播 mnist_forward、反向传播 mnist_backward 模块和 os 模块,并规定程序 5 秒的循环间隔时间。

    接着,定义测试函数 test(),读入 mnist 数据集,利用 tf.Graph() 复现之前定义的计算图,利用 placeholder 给训练数据 x 和标签 y_ 占位,调用 mnist_forward 文件中的前向传播过程 forword() 函数,计算训练数据集上的预测结果 y

    接着,实例化具有滑动平均的 saver 对象,从而在会话被加载时模型中的所有参数被赋值为各自的滑动平均值,增强模型的稳定性,然后计算模型在测试集上的准确率。

    with 结构中,加载指定路径下的 ckpt,若模型存在,则加载出模型到当前对话,在测试数据集上进行准确率验证,并打印出当前轮数下的准确率,若模型不存在,则打印出模型不存在的提示,从而 test() 函数完成。

    通过主函数 main(),加载指定路径下的测试数据集,并调用规定的 test 函数,进行模型在测试集上的准确率验证。

运行以上三个文件,可得到手写体 mnist 数据集的识别任务的运行结果:

mnist_backward.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Extracting ./data/train-images-idx3-ubyte.gz
Extracting ./data/train-labels-idx1-ubyte.gz
Extracting ./data/t10k-images-idx3-ubyte.gz
Extracting ./data/t10k-labels-idx1-ubyte.gz
After 1 training step(s), loss on training batch is 3.35724.
After 1001 training step(s), loss on training batch is 0.300589.
After 2001 training step(s), loss on training batch is 0.265261.
已杀死
ailab@ailab-virtual-machine:~/tf-notes/lec5$
ailab@ailab-virtual-machine:~/tf-notes/lec5$
ailab@ailab-virtual-machine:~/tf-notes/lec5$ python mnist_backward.py
Extracting ./data/train-images-idx3-ubyte.gz
Extracting ./data/train-labels-idx1-ubyte.gz
Extracting ./data/t10k-images-idx3-ubyte.gz
Extracting ./data/t10k-labels-idx1-ubyte.gz
After 1 training step(s), loss on training batch is 2.82503.
After 1001 training step(s), loss on training batch is 0.302797.
After 2001 training step(s), loss on training batch is 0.282333.
After 3001 training step(s), loss on training batch is 0.347805.
After 4001 training step(s), loss on training batch is 0.206898.
After 5001 training step(s), loss on training batch is 0.198906.
After 6001 training step(s), loss on training batch is 0.19496.
After 7001 training step(s), loss on training batch is 0.21611.
After 8001 training step(s), loss on training batch is 0.190037.
After 9001 training step(s), loss on training batch is 0.180762.
After 10001 training step(s), loss on training batch is 0.186933.
After 11001 training step(s), loss on training batch is 0.194215.
...

mnist_test.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Extracting ./data/train-images-idx3-ubyte.gz
Extracting ./data/train-labels-idx1-ubyte.gz
Extracting ./data/t10k-images-idx3-ubyte.gz
Extracting ./data/t10k-labels-idx1-ubyte.gz
After 1 training step(s), test accuracy = 0.0793
After 1001 training step(s), test accuracy = 0.9465
After 1001 training step(s), test accuracy = 0.9465
After 2001 training step(s), test accuracy = 0.9608
After 2001 training step(s), test accuracy = 0.9608
After 3001 training step(s), test accuracy = 0.9674
After 3001 training step(s), test accuracy = 0.9674
After 4001 training step(s), test accuracy = 0.9708
After 4001 training step(s), test accuracy = 0.9708
After 5001 training step(s), test accuracy = 0.9727
After 5001 training step(s), test accuracy = 0.9727
After 6001 training step(s), test accuracy = 0.9737
After 6001 training step(s), test accuracy = 0.9737
After 7001 training step(s), test accuracy = 0.975
After 7001 training step(s), test accuracy = 0.975
After 8001 training step(s), test accuracy = 0.9763
After 8001 training step(s), test accuracy = 0.9763
After 9001 training step(s), test accuracy = 0.9767
After 9001 training step(s), test accuracy = 0.9767
After 10001 training step(s), test accuracy = 0.9769
After 10001 training step(s), test accuracy = 0.9769
After 11001 training step(s), test accuracy = 0.9772
After 11001 training step(s), test accuracy = 0.9772
...

从终端显示的运行结果可以看出,随着训练轮数的增加,网络模型的损失函数值在不断降低,并且在测试集上的准确率在不断提升,有较好的泛化能力。