人工智能

Intelligent Agent

  • 通过传感器(sensors)察觉周围环境
  • 执行器(actuators)根据环境做出反应

Agents interact with environments through actuators and sensors.

Percept sequence

感知的序列

Agent function

根据感知的序列映射出一个动作

可以选择列表(Tabular Agent Function)或者写程序(Agent Program)。 表格可以在理论上很好的描述(Expressiveness)一个agent的行为,但是缺乏实践意义(Practicality)

Agent program 是agent function的实际实现(practical implementation)

Rational Agent

理想的agent, 一直做"正确的"事情

  • 显然的表现评估法则(performance measure)并不总有
  • 设计者需要找到一个可接受的评估法则

对于一个感知序列,一个理想的agent需要选择行动,使得它在给定的知识范围的表现期望最大化

Omniscience agent

知道它行为的最终结果(实际不可能)

Learning学习

Rational agent 可以通过它的感知学习,改进它的知识

Autonomy

一个 rational agent 是autonomous, 如果它相比已经获得的知识,更加依赖于新获得的知识

Task Environment

我们用PEAS来描述

image-20211022153701200

Fully observable/partially observable

一个环境是可全部观测的,若agent可以检测到环境完整的状态,否则就是部分可观测

Single-agent/multi-agent

一个环境是multi agent环境,若它包含多个agent

Deterministic/stochastic

有确定性若下一个状态完全取决于当前状态

Episodic/sequential

episodic若在一个时期(episode,机器人感知和做出行为的时期)的行为不会影响到下一个时期

discrete/continuous

分为状态的离散/连续和时间的离散/连续

static/dynamic

如果环境的变化只基于agent,那么他是static的

known/unknown

known:如果agent知道行为结果或者结果的概率

Agent Types

  • simple reflex agent
  • reflex agents with state
  • goal-based agents
  • utility-based agents

simple reflex agents

image-20211022154656330

agent的决定基于当前的感知

model-based reflex agents

image-20211022154820184

simple reflex agent的扩展版本,可以处理partial observability 通过追踪不能观测的环境

goal-based agents

model-based reflex agents的拓展版本。agent的目标会被explicitly considered明确的考虑

image-20211022155338018

utility-based agents

goal-based reflex agents的扩展版本。

会最大化效用(utility),比如最大化agent的幸福程度

image-20211022155621968

Learning agents

所有agent都可以扩展为学习agent

image-20211022155611372

Constraint Satisfaction Problems

A constraint satisfaction problem is a tuple \((X,D,C)\)

\(X\) 是变量集合, \(D\) 是变量的值域,\(C\) 是限制的集合

限制的种类

  • unary 单个值 \(sa \ne green\)
  • binary 两个变量 \(sa \ne wa\)
  • high-order 多个变量 \(sa\ne wa \ne nt\)

人工智能深度学习

模型评估与选择

经验误差与过拟合

错误率

如果\(m\) 个样本中有\(a\) 个错误,则错误率为 \(E=\frac{a}{m}\) ,精度为 \(1 - E\)

误差

学习器在训练集上的误差是训练误差,在新样本上的误差是泛化误差

过拟合和欠拟合

过拟合是学习能力过于强大分类标准过于严格,欠拟合是学习能力低下分类标准过于松

image-20200626134647139

评估方法

我们需要训练集来训练样本,测试集来测试样本。怎样更好地划分出这两个集合呢,有下面这几种办法。

留出法

直接把数据集 \(D\) 分成两个互斥的集合,一个作为训练集\(S\) 一个作为测试集 \(T\) 。可以直接三七分或者二八分。但是需要注意数据的平均性。

比如有连续的10年内的数据。不能把前7年训练,后三年测试。但这会破坏数据的分布。应该随机抽取7成数据训练,剩下的再测试。

交叉验证法

image-20200626141117111

把数据分为10分 \(D_{1}\)\(D_{10}\) ,分别

\(D_{10}\)\(D_{1}\) 作为测试集,其他的作为训练集,测试10次。然后取平均值。这对计算机的算力要求高。

自助法

从包含\(m\)个样本的数据集中每次都随机抽取样本(可以重复), 抽\(m\) 次,对于每个元素,被抽中的概率是 \(\frac{1}{m}\) ,没被抽中的概率是 \(1-\frac{1}{m}\) .所以始终没被选中的概率有 \[ \lim _{m \rightarrow \infty}\left(1-\frac{1}{m}\right)^{m} \mapsto \frac{1}{e} \approx 0.368 \] 也就是大概\(36.8\%\)的数据没有被选中,可以作为测试集。

调参与最终模型

大多数学习算法都有些参数(parameter)需要设定,参数配置不同,学得模型的性能往往有显著差别.因此,在进行模型评估与选择时,除了要对适用学习算法进行选择,还需调参。

每次选定一个参数,都要重新训练一次。所以我们需要一个验证集来多次验证每一次的训练结果。等调好参数后,最终再用测试集测试最终的结果

性能度量

对学习器的泛化性能进行评估需要有衡量模型泛化能力的评价标准,这就是性能度量。

如果训练集是 \(D=\left\{\left( x _{1}, y_{1}\right),\left( x _{2}, y_{2}\right), \ldots,\left( x _{m}, y_{m}\right)\right\}\)

回归任务最常用的是均方误差: \[ E(f ; D)=\frac{1}{m} \sum_{i=1}^{m}\left(f\left( x _{i}\right)-y_{i}\right)^{2} \] 对于数据分布 \(\mathcal{D}\) 和概率密度函数 \(p(\cdot)\) , 它可以描述为 \[ E(f ; D )=\int_{ x \sim D }(f( x )-y)^{2} p( x ) d x \] 也就是对每个样本出现的概率加权,然后在求和。

错误率和精度

错误率

统计错误个数, 然后除以总个数 \[ E(f ; D)=\frac{1}{m} \sum_{i=1}^{m} \mathbb{I} \left(f\left( x _{i}\right) \neq y_{i}\right) \] 这个 \(\mathbb{I}\) 是 判断 \(f \left( x _{i}\right) \neq y_{i}\) 是否成立,如何是就是 \(1\) 否则就是 \(0\).

精度

\[ \operatorname{acc}(f ; D)=\frac{1}{m} \sum_{i=1}^{m} \mathbb{I} \left(f\left( x _{i}\right)=y_{i}\right) \]

查准率,查全率与F1

对于2分类问题,正例是答案正确的,反例是答案错误的。\(TP\)是有多少个正确的答案算对了, \(FN\) 是有多少个正确的答案算错了,\(FP\) 是有多少个错误答案算对了。

比如100个数据,70个标答是正确。预测这70个样本中80个是正例,其中正确的是60个,错误的是20个。那么TP就是60,FP就是20,FN就是10

image-20200626144341011

查准率\(P\) 和查全率\(R\) 定义为 \[ \begin{array}{l} P=\frac{T P}{T P+F P} \\ R=\frac{T P}{T P+F N} \end{array} \] 查准率是我预测出来的正例中的正确率是多少。查全率是所有的样本中我预测出来了多少个正例。

人工智能基础概念

预测

\(z = dot(w,x) + b\)

人工智能通上面的公式来预测。w和x是向量,dot是向量积。b是阈值。x是输入。w1是权值,表示每个输入的重要程度。

image-20200302204254840

激活函数

在实际的神经网络中,我们不能直接用逻辑回归。必须要在逻辑回归外面再套上一个函数。这个函数我们就称它为激活函数。激活函数非常非常重要,如果没有它,那么神经网络的智商永远高不起来。 \[ \sigma(z)=\frac{1}{1+e^{-z}} \] 我们可以画出它的图像。

image-20200302204352817

我们在这里先只介绍它的一个用途——把z映射到[0,1]之间。也可以理解为结果的概率

损失函数

神经网络里如何判断自己预测的结果是否准确 \[ \begin{aligned} &\hat{y}^{(i)}=\sigma\left(w^{T} x^{(i)}+b\right)\\ &\sigma\left(Z^{(i)}\right)=\frac{1}{1+e^{-Z^{(i)}}} \end{aligned} \] 在上面的公式中: \(\hat{y}\)表示预测的结果,\(\hat{y}^{(i)}\)上面的i表示针对某个训练样本。比如\(\hat{y}^{(i)}\)是针对\(x^{(i)}\)的预测结果。在实践中,我们会用到下面的公式: \[ L\left(\hat{y}^{(i)}, y^{(i)}\right)=-\left(y^{(i)} \log \left(\hat{y}^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-\hat{y}^{(i)}\right)\right) \] 但是这只是单个样本的公式,如果我们要堆所有的样本进行精度的预测,那么我们就要用到下面的公式,也就是求和再求平均值 \[ J(w, b)=\frac{1}{m} \sum_{i=1}^{m} L\left(\hat{y}^{(i)}, y^{(i)}\right)=-\frac{1}{m} \sum_{i=1}^{m}\left[\left(y^{(i)} \log \left(\hat{y}^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-\hat{y}^{(i)}\right)\right]\right. \]

梯度下降

在预测的公式里。w和b决定了预测结果是否准确。所以得到这两个参数的值的方式是梯度下降,它会一步步改变w和b的值,让预测的结果更加精确。上面的公式是一个漏斗型的函数,我们需要求出再函数底部的一组w和b。

image-20200302205325728

这是一个凸函数(向下凸的函数),我们选择J也是因为它是个凸函数

计算图

计算图是研究人工智能的重要手段。

神经网络的计算是由一个向前传播和一个反向传播构成的。向前传播计算出预测结果和损失。反向传播计算损失函数关于每个参数(w,b)的偏导数来梯度下降。由此往复达到最优的值。

例如:\(J(a, b, c)=3(a+b c)\)

image-20200304194503021 \[ \left\{\begin{array}{l} U=b c \\ v=a+U \\ J=3 V \end{array}\right. \] 我们可以求出对于的偏导数 \[ \frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{V}}=3 , \frac{\mathrm{d} \mathrm{V}}{\mathrm{d} \mathrm{U}}=1 , \frac{\mathrm{d} \mathrm{U}}{\mathrm{d} \mathrm{b}}=c=2 \\ \Rightarrow\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{U}} =\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{V}} \cdot \frac{\mathrm{d} \mathrm{V}}{\mathrm{d} \mathrm{U}}=3\cdot1=3 \Rightarrow\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{b}} =\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{U}} \cdot \frac{\mathrm{d} \mathrm{U}}{\mathrm{d} \mathrm{b}}=3\cdot2=6 \\ \Rightarrow\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{a}} =3 ,\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{b}} =6,\frac{\mathrm{d} \mathrm{J}}{\mathrm{d} \mathrm{c}} =9 \\ \Rightarrow \mathrm{d} \mathrm{a}=3,\mathrm{d} \mathrm{b}=6,\mathrm{d} \mathrm{c}=9 \]

计算梯度下降的偏导数

例如两个变量的模型,先算出z再算出预测值,然后计算损失函数 \[ \begin{aligned} &z=w^{T} x+b\\ &\hat{y}=a=\sigma(z)\\ &\mathcal{L}(a, y)=-(y \log (a)+(1-y) \log (1-a)) \end{aligned} \] image-20200304203450916

目的是计算出 \(d\mathcal{L}/dw_{1},d\mathcal{L}/dw_{2},d\mathcal{L}/db\), 然后更新 \(w_{1},w_{2},b\) 来小损失函数。首先需要计算dL/da和da/dz。dL/da可以直接求导, \[ \frac{d L}{d a}=-\frac{y}{a}+\frac{1-y}{1-a} \] da/dz需要用到换元法 \[ a=\sigma\left(z\right)=\frac{1}{1+e^{-z}} = (1+e^{-z})^{-1} , g = (1+e^{-z}) \Rightarrow a=g^{-1} \\ \frac{da}{dg}=-g^{-2}, \frac{dg}{dz} = -e^{-z} \Rightarrow \frac{da}{dz}=g^{-2} \cdot e^{-z} \\ \frac{da}{dz}= (1+e^{-z})^{-2} \cdot e^{-z}= \frac{e^{-z}}{(1+e^{-z})^{2}}, 1-a = \frac{1+e^{-z}}{1+e^{-z}}-\frac{1}{1+e^{-z}}=\frac{e^{-z}}{1+e^{-z}} \\ \Rightarrow \frac{da}{dz}= a(1-a) \] 综上可以算出dL/dz \[ \frac{d L}{d a}=-\frac{y}{a}+\frac{1-y}{1-a} ,\quad \frac{d a}{d z}=a(1-a) \\ \begin{aligned} \frac{d L}{d z}=\frac{d L}{d a} \cdot \frac{d a}{d z} &=-y(1-a)+(1-y) a \\ &=-y+ya+a-ya \\ &=a-y \end{aligned} \] 于是有 \[ dw_{1}=\frac{d L}{d w_{1}}=\frac{d L}{d z} \cdot \frac{d z}{d w_{1}}=(a-y) \cdot x_{1} \\ dw_{2}=\frac{d L}{d w_{2}}=\frac{d L}{d z} \cdot \frac{d z}{d w_{2}}=(a-y) \cdot x_{2} \\ db=\frac{d L}{d b}=\frac{d L}{d z} \cdot \frac{d z}{db}=(a-y) \cdot 1 \\ \] 得到这三个值之后,就可以利用它们梯度下降 \[ \begin{array}{l} w_{1}:=w_{1}-\alpha d w_{1} \\ w_{2}:=w_{2}-\alpha d w_{2} \\ b:=b-\alpha d b \end{array} \]

向量化

如果用上面的计算很大的数据,效率是很慢的。我们需要向量化来提速。例如 \[ \sum_{i=1}^{3} A_{i} \cdot B_{i} = A_{1} \cdot B_{1} +A_{2} \cdot B_{2} +A_{3} \cdot B_{3} \] 它等价于 \[ \begin{aligned} &A=[1,2,3]\\ &\mathrm{B}=[1,2,3] \end{aligned} \\ sum=A \cdot B \]

Python环境

Anaconda集成了Jupiter Notebook,是很好的Python开发环境。

链接:https://pan.baidu.com/s/1cTTOBWvLzps1F4XfP875Ig 提取码:050r

下载好后安装完成。打开Anaconda Navigator,点jupyter的launch。

image-20200305173816371

然后点击右上角的new中的Python3

image-20200305173953898

写程序的前置知识

HD5文件

HD5文件。Hierarchical Data Format(HDF)是一种针对大量数据进行组织和存储的。文件格式,大数据行业和人工智能行业都用它来保存数据。

Python读取HD5文件:

1
2
3
import h5py # 加载库

train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") # 加载训练数据

numpy语法

shape
1
2
a = np.array([[2,3,3],[2,2,5]]) # 定义np数组
print(a.shape) # (2, 3) 输出数组的规格
reshape

改变数组的规格

1
2
arr=np.arange(16).reshape(2,8) # 生产一个数组
print(arr)
image-20200306230719097
1
2
arr = arr.reshape(4,-1) # 改成4行
print(arr)
image-20200306230804870
T: 转置
1
2
3
arr=np.arange(16).reshape(2,8) # 上面的2*8数组
arr = arr.reshape(8,-1).T # 改成8行并且转置
print(arr)
image-20200306231055669
取出列
1
2
3
print(arr[:, 1]) # 取出第1列的数据
b = a[:2, :2] #取出2*2的数据
print(b)
image-20200307103625043
交换行列

用切片操作

1
2
# 交互第1行和第3行
a[[1,3],:] = a[[3,1],:]
初始化

.zero 函数

1
m = np.zeros((3,2)) # 创建一个3行2列的矩阵并初始化成0
指数函数exp

\[ e^{x} \]

1
2
arr = [0,1,2,4,5,6]
arr = np.exp(arr) # 对每个值都进行指数运算
求和sum

\[ \sum_{i=1}^{m} arr_{i} \cdot a_{i} \]

1
2
3
arr = np.array([0,1,2,4,5,6])
a = np.array([1,2,4,5,1,2])
arr = np.sum(arr*a)

matplotlib

显示图片

1
2
index =2
plt.imshow(train_set_x_orig[index])

或者

1
2
3
# Load the image first
image = Image.open('images/sample_image.jpg')
plt.imshow(image)

Python第一个机器学习的例子

加载库和数据

加载库文件

1
2
3
4
5
import numpy as np
import matplotlib.pyplot as plt
import h5py
import skimage.transform as tf
%matplotlib inline #这是jupyter notebook里的命令, 意思是将那些用matplotlib绘制的图显示在页面里而不是弹出一个窗口

加载训练和测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def load_dataset():
train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") # 加载训练数据
train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # 从训练数据中提取出图片的特征数据
train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # 从训练数据中提取出图片的标签数据

test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r") # 加载测试数据
test_set_x_orig = np.array(test_dataset["test_set_x"][:])
test_set_y_orig = np.array(test_dataset["test_set_y"][:])

classes = np.array(test_dataset["list_classes"][:]) # 加载标签类别数据,这里的类别只有两种,1代表有猫,0代表无猫

train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0])) # 把数组的维度从(209,)变成(1, 209),这样好方便后面进行计算
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0])) # 从(50,)变成(1, 50)

return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes

# 调用上面定义的函数将数据加载到各个变量中
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()

预处理

数据扁平化和转置

1
2
train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T

全部除以255,使得每个像素点都在\([0,1]\) 里,方便以后处理

1
2
train_set_x = train_set_x_flatten/255.
test_set_x = test_set_x_flatten/255. # 255. === 255.0

我们的神经网络图如下

猫

编写工具函数

sigmoid

\[ \sigma(z)=\frac{1}{1+e^{-z}} \]

1
2
def sigmoid(z):
return 1 / (1 + np.exp(-z))
初始化

我们要初始化权重数组w和阈值b, dim是w的大小,在本例中是12288

1
2
3
4
def initialize_with_zeros(dim):
w = np.zeros((dim,1))
b = 0
return w, b
向前传播和反向传播

向前传播: \[ \begin{aligned} &A=\sigma\left(w^{T} X+b\right)=\left(a^{(1)}, a^{(2)}, \ldots, a^{(m-1)}, a^{(m)}\right)\\ &J=-\frac{1}{m} \sum_{i=1}^{m} y^{(i)} \log \left(a^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-a^{(i)}\right) \end{aligned} \] 反向传播: \[ \begin{aligned} &\frac{\partial J}{\partial w}=\frac{1}{m} X(A-Y)^{T}\\ &\frac{\partial J}{\partial b}=\frac{1}{m} \sum_{i=1}^{m}\left(a^{(i)}-y^{(i)}\right) \end{aligned} \]

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
def propagate(w, b, X, Y):
"""
参数:
w -- 权重数组,维度是(12288, 1)
b -- 偏置bias
X -- 图片的特征数据,维度是 (12288, 209)
Y -- 图片对应的标签,0或1,0是无猫,1是有猫,维度是(1,209)

返回值:
cost -- 成本
dw -- w的梯度
db -- b的梯度
"""

m = X.shape[1]

# 前向传播
A = sigmoid(np.dot(w.T, X) + b)
cost = -np.sum(Y*np.log(A) + (1-Y)*np.log(1-A)) / m

# 反向传播
dZ = A - Y
dw = np.dot(X,dZ.T) / m
db = np.sum(dZ) / m

# 将dw和db保存到字典里面
grads = {"dw": dw,
"db": db}

return grads, cost
更新w和b
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
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
"""
参数:
w -- 权重数组,维度是 (12288, 1)
b -- 偏置bias
X -- 图片的特征数据,维度是 (12288, 209)
Y -- 图片对应的标签,0或1,0是无猫,1是有猫,维度是(1,209)
num_iterations -- 指定要优化多少次
learning_rate -- 学习步进,是我们用来控制优化步进的参数
print_cost -- 为True时,每优化100次就把成本cost打印出来,以便我们观察成本的变化

返回值:
params -- 优化后的w和b
costs -- 每优化100次,将成本记录下来,成本越小,表示参数越优化
"""

costs = []

for i in range(num_iterations):
grads, cost = propagate(w, b, X, Y) # 计算得出梯度和成本

# 从字典中取出梯度
dw = grads["dw"]
db = grads["db"]

# 进行梯度下降,更新参数,使其越来越优化,使成本越来越小
w = w - learning_rate * dw
b = b - learning_rate * db

# 将成本记录下来
if i % 100 == 0:
costs.append(cost)
if print_cost:
print ("优化%i次后成本是: %f" %(i, cost))

params = {"w": w,
"b": b}
return params, costs
预测函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def predict(w, b, X):
'''
参数:
w -- 权重数组,维度是 (12288, 1)
b -- 偏置bias
X -- 图片的特征数据,维度是 (12288, 图片张数)

返回值:
Y_prediction -- 对每张图片的预测结果
'''
m = X.shape[1]
Y_prediction = np.zeros((1,m))

A = sigmoid(np.dot(w.T, X) + b) # 通过这行代码来对图片进行预测

# 上面得出的预测结果是小数的形式,为了方便后面显示,我们将其转换成0和1的形式(大于等于0.5就是1/有猫,小于0.5就是0/无猫)
for i in range(A.shape[1]):
if A[0,i] >= 0.5:
Y_prediction[0,i] = 1

return Y_prediction

将函数组合

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
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
"""
参数:
X_train -- 训练图片,维度是(12288, 209)
Y_train -- 训练图片对应的标签,维度是 (1, 209)
X_test -- 测试图片,维度是(12288, 50)
Y_test -- 测试图片对应的标签,维度是 (1, 50)
num_iterations -- 需要训练/优化多少次
learning_rate -- 学习步进,是我们用来控制优化步进的参数
print_cost -- 为True时,每优化100次就把成本cost打印出来,以便我们观察成本的变化

返回值:
d -- 返回一些信息
"""

# 初始化待训练的参数
w, b = initialize_with_zeros(X_train.shape[0])

# 使用训练数据来训练/优化参数
parameters, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)

# 从字典中分别取出训练好的w和b
w = parameters["w"]
b = parameters["b"]

# 使用训练好的w和b来分别对训练图片和测试图片进行预测
Y_prediction_train = predict(w, b, X_train)
Y_prediction_test = predict(w, b, X_test)

# 打印出预测的准确率
print("对训练图片的预测准确率为: {}%".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
print("对测试图片的预测准确率为: {}%".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))


d = {"costs": costs,
"Y_prediction_test": Y_prediction_test,
"Y_prediction_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations": num_iterations}

return d

浅层神经网络

之前我们学习的是单神经元的神经网络,其实还有多神经元的,比如下面这个

image-20200307183507940

其中的每一个都是单个的神经元

计算向前传播

我们可以先算出第一层神经元的预测值a,上角标表示层数

image-20200307183927759 \[ \mathrm{z}_{1}^{[1]}=\mathrm{w}_{1}^{[1]\mathrm{T}}\mathrm{x}+\mathrm{b}_{1}^{[1]},\quad \mathbf{a}_{1}^{[1]}=\sigma(z_{1}^{[1]}) \\ \mathrm{z}_{2}^{[1]}=\mathrm{w}_{2}^{[1]\mathrm{T}}\mathrm{x}+\mathrm{b}_{2}^{[1]},\quad \mathbf{a}_{2}^{[1]}=\sigma(z_{2}^{[1]}) \\ \mathrm{z}_{3}^{[1]}=\mathrm{w}_{3}^{[1]\mathrm{T}}\mathrm{x}+\mathrm{b}_{3}^{[1]},\quad \mathbf{a}_{3}^{[1]}=\sigma(z_{3}^{[1]}) \\ \mathrm{z}_{4}^{[1]}=\mathrm{w}_{4}^{[1]\mathrm{T}}\mathrm{x}+\mathrm{b}_{4}^{[1]},\quad \mathbf{a}_{4}^{[1]}=\sigma(z_{4}^{[1]}) \\ \] 如果有很多神经元,一个个算效率很低,所以我们可以向量化。我们把w值组成一个矩阵 \[ \left[\begin{array}{l} --\mathrm{w}_{1}^{[1] \mathrm{T}}-- \\ --\mathrm{w}_{2}^{[1] \mathrm{T}}-- \\ --\mathrm{w}_{3}^{[1] \mathrm{T}}-- \\ --\mathrm{w}_{4}^{[1] \mathrm{T}}-- \end{array}\right]\left[\begin{array}{l} \times 1 \\ \times 2 \\ \times 3 \end{array}\right]+\left[\begin{array}{l} \mathrm{b}_{1}^{[1]} \\ \mathrm{b}_{2}^{[1]} \\ \mathrm{b}_{3}^{[1]} \\ \mathrm{b}_{4}^{[1]} \end{array}\right] \] 于是我们就可以将上面4个式子简化成1个: \[ \mathrm{z}^{[1]}=\mathrm{W}^{[1]\top}\mathrm{x}+\mathrm{b}^{[1]},\quad \mathbf{a}^{[1]}=\sigma(z^{[1]}) \\ \mathrm{z}^{[2]}=\mathrm{W}^{[2]\top}\mathrm{a}^{[1]}+\mathrm{b}^{[2]},\quad \mathbf{a}^{[2]}=\sigma(z^{[2]}) \\ \] 上面的式子只使用于计算单个样本。而我们通常是由多个样本的,我们可以把每组数据的特征值组合成矩阵 \[ X=\left[\begin{array}{cccc} | & | & & | \\ x^{(1)} & x^{(2)} & ... & x^{(m)} \\ | & | & & | \end{array}\right] \] 于是计算所有样本又可以这样简化: \[ \mathrm{Z}^{[1]}=\mathrm{W}^{[1]\mathrm{T}}\mathrm{X}+\mathrm{b}^{[1]},\quad \mathbf{A}^{[1]}=\sigma(Z^{[1]}) \\ \mathrm{Z}^{[2]}=\mathrm{W}^{[2]\mathrm{T}}\mathrm{A}^{[1]}+\mathrm{b}^{[2]},\quad \mathbf{a}^{[2]}=\sigma(Z^{[2]}) \\ \]

计算反向传播

我们先计算出第二层预测值的偏导数

image-20200309195811339

之前我们计算量单神经元的公式: \[ dz=a-y\\ dw_{1}=dz \cdot x_{1} \\ dw_{2}=dz \cdot x_{2} \\ db=dz \\ \] 这个模型和上个单神经元的模型一样,所以我们可以直接套用公式得出 \[ \begin{aligned} &\mathrm{d} \mathrm{z}^{[2]}=\mathrm{a}^{[2]}-\mathrm{y}\\ &\mathrm{d} \mathrm{W}^{[2]}=\mathrm{d} \mathrm{z}^{[2]} \mathrm{A}^{[1] \top}\\ &\mathrm{d} \mathrm{b}^{[2]}=\mathrm{d} \mathrm{z}^{[2]} \end{aligned} \] image-20200309200115862

然后我们可以求出\(\frac{dL}{da^{[1]}}\)
\[ \frac{dL}{da^{[1]}}=\frac{dL}{dz^{[2]}} \cdot \frac{dz^{[2]}}{da^{[1]}} = (\mathrm{a}^{[2]}-\mathrm{y}) \cdot W^{[2]\top} = W^{[2]\top}dz^{[2]} \] 由于激活函数不知有sigmoid,之后我们会具体介绍,我们先用 \({g^{[1]}}'(z^{[1]})\) 来表示激活函数 \(a^{[1]}\) 关于 \(z^{[1]}\) 的偏导数 \[ dz^{[1]}=\frac{dL}{dz^{[1]}}=\frac{dL}{da^{[1]}} \cdot \frac{da^{[1]}}{dz^{[1]}} =W^{[2]\top}dz^{[2]} \cdot {g^{[1]}}'(z^{[1]}) \] 得出 \(dz^{[1]}\) 后,\(dW^{[1]}\)\(db^{[1]}\) 就可以通过 \(dz^{[1]}\) 算出来: \[ \begin{aligned} &d W ^{[1]}= d z ^{[1]} x ^{\top}\\ &db ^{[1]}= d z ^{[1]} \end{aligned} \] 以上的出来的公式是单样本的公式,多样本的公式如下 \[ \mathrm{d} \mathrm{Z}^{[2]}=\mathrm{A}^{[2]}-\mathrm{Y}\\ \mathrm{d} \mathrm{W}^{[2]}=\mathrm{d} \mathrm{Z}^{[2]} \mathrm{A}^{[1] \top}/m \\ \mathrm{d} \mathrm{b}^{[2]}=np.sum(\mathrm{d} \mathrm{z}^{[2]})/m\\ dZ^{[1]}=W^{[2]\top}dZ^{[2]} \cdot {g^{[1]}}'(Z^{[1]}) \\ d W ^{[1]}= d Z ^{[1]} X ^{\top}/m\\ db ^{[1]}= np.sum(d z ^{[1]},axis=1,keepdims=True)/m \] 最后一行中 axis=1 的作用是让sum只把每一行的加起来,keepdims是保持维度,以免出现 \((n^{[1]},)\) 的形式

激活函数

为什么要激活函数?因为我们每一层计算的都是线性的,无法表示复杂情况。就算有多层神经网络也没有用(因为代入之后依然是线性函数)。只有加入了激活函数,才可以表达复杂情况。下面是几种常见的激活函数。

sigmoid

尤其使用于二元分类的问题中,因为0到1的输出值可以表示概率

image-20200309220559907

它的偏导数 \[ g'(z)= a(1-a) \]

tanh

它是sigmoid的升级版,各方面都比sigmoid优秀一点

image-20200309220623741

它的偏导数 \[ g'(z)= 1-a^{2} \]

relu

一般来说,relu用到的最多

image-20200309220752575

偏导数: \[ g'(z)=\left\{\begin{array}{ll} 0 & , z<0 \\ 1 & , z>=0 \end{array}\right. \]

leaky relu
image-20200309221413860

偏导数: \[ g'(z)=\left\{\begin{array}{ll} 0.01 & , z<0 \\ 1 & , z>=0 \end{array}\right. \]

随机初始化参数

对于多层神经网络来说,初始化参数不能都设置成0。否则它就变成了单个神经元。因为输入参数一样,计算结果一样。

一般使用numpy.random.randn来进行随机初始化。

比如 \(w^{[1]} = numpy.random.randn((2,2))*0.01\) 。我们在后面乘0.01的目的是把w变得更加小一点,这样带入sigmod函数里计算出的斜率会大,这样梯度下降的速度就快,神经网络的学习速度就快。

实战编写浅层神经网络

给定一个坐标,判断是蓝色还是红色

image-20200310095545501

Python知识

ravel扁平化

可以把多维数组压缩成1维数组

1
2
print(Y.shape, Y.ravel().shape)
# 输出 (1, 400) (400,)
matplotlib.pyplot.scatter绘制散点
1
2
plt.scatter(X[0, :], X[1, :], c=Y.ravel(), s=40, cmap=plt.cm.Spectral)
# X坐标数组 Y坐标数组 颜色 大小 python自动给不同颜色标签的分配颜色

例如画一个tanh函数

1
2
3
4
n = 100
x = np.random.randn(n)
y = np.tanh(x)
plt.scatter(x, y, s=40)
image-20200310102318210
sklearn单神经元学习
1
2
3
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X.T, Y.T.ravel()) # 训练样本
LR_predictions = clf.predict(X.T)

库文件

这次多了sklearn。它是数据挖掘,数据分析和机器学习的库,里面内置了很多人工智能的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import sklearn.linear_model

# 这两个是自己写的库文件
from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets
from testCases import *

%matplotlib inline

np.random.seed(1) # 设置随机种子

函数

初始化参数
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
def initialize_parameters(n_x, n_h, n_y):
"""
参数:
n_x -- 输入层的神经元个数
n_h -- 隐藏层的神经元个数
n_y -- 输出层的神经元个数
"""

np.random.seed(2)

# 随机初始化第一层(隐藏层)相关的参数w.
# 每一个隐藏层神经元都与输入层的每一个神经元相连。每一个相连都会有一个对应的参数w。
# 所以W1的维度是(n_h, n_x),表示(隐藏层的神经元个数,输入层神经元个数)
W1 = np.random.randn(n_h, n_x) * 0.01

# 将第一层的参数b赋值为0,因为w已经非0了,所以b可以为0
# 因为每一个神经元只有一个对应的b,所以b1的维度是(n_h, 1),表示(隐藏层神经元个数,1)
b1 = np.zeros(shape=(n_h, 1))

# 同理,初始化第二层的w和b
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros(shape=(n_y, 1))

# 将初始化好的参数放入一个字典变量中
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}

return parameters
向前传播
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
def forward_propagation(X, parameters):
"""
参数:
X -- 输入特征,维度是 (横纵坐标, 样本数)
parameters -- 参数w和b

Returns:
A2 -- The sigmoid output of the second activation
cache -- a dictionary containing "Z1", "A1", "Z2" and "A2"
"""
# 从字典中取出参数
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']

# 实现前向传播算法
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1) # 第一层的激活函数我们使用tanh。numpy库里面已经帮我们实现了tanh工具函数
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2) # 第二层我们使用sigmoid,因为我们要解决的这个问题属于二分问题。这个函数是我们自己在planar_utils里面实现的。

# 将这些前向传播时得出的值保存起来,因为在后面进行反向传播计算时会用到他们。
cache = {"Z1": Z1,
"A1": A1,
"Z2": Z2,
"A2": A2}

return A2, cache
计算成本函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 这个函数被用来计算成本
def compute_cost(A2, Y, parameters):
"""
参数:
A2 -- 神经网络最后一层的输出结果
Y -- 数据的颜色标签
"""

m = Y.shape[1]

logprobs = np.multiply(np.log(A2), Y) + np.multiply((1 - Y), np.log(1 - A2))
cost = - np.sum(logprobs) / m

return cost
反向传播
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
def backward_propagation(parameters, cache, X, Y):
"""
参数:
parameters -- 参数w和b
cache -- 前向传播时保存起来的一些数据
X -- 输入特征
Y -- 标签
"""
m = X.shape[1] # 获取样本数

W1 = parameters['W1']
W2 = parameters['W2']

A1 = cache['A1']
A2 = cache['A2']

# 根据我们文章中介绍的算法公式来计算梯度(偏导数)
dZ2= A2 - Y
dW2 = (1 / m) * np.dot(dZ2, A1.T)
db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1 - np.power(A1, 2))
dW1 = (1 / m) * np.dot(dZ1, X.T)
db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)

print("W1 dim:" + str(W1.shape))
print("W2 dim:" + str(W2.shape))
print("------------------------------")

print("dZ2 dim:" + str(dZ2.shape))
print("dZ1 dim:" + str(dZ1.shape))
print("------------------------------")

grads = {"dW1": dW1,
"db1": db1,
"dW2": dW2,
"db2": db2}

return grads # 返回计算得到的梯度
梯度下降
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
# 用上面得到的梯度来进行梯度下降(更新参数w和b,使其更优化)
def update_parameters(parameters, grads, learning_rate=1.2):
"""
参数:
parameters -- 参数w和b
grads -- 梯度
"""

W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']

dW1 = grads['dW1']
db1 = grads['db1']
dW2 = grads['dW2']
db2 = grads['db2']

# 根据梯度和学习率来更新参数,使其更优
W1 = W1 - learning_rate * dW1
b1 = b1 - learning_rate * db1
W2 = W2 - learning_rate * dW2
b2 = b2 - learning_rate * db2

parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}

return parameters

把函数组合在一起

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
# 上面已经将各个所需的功能函数都编写好了。现在我们将它们组合在一个大函数中来构建出一个训练模型。
def nn_model(X, Y, n_h, num_iterations=10000, print_cost=False):
"""
Arguments:
X -- 输入特征
Y -- 标签
n_h -- 隐藏层的神经元个数
num_iterations -- 训练多少次
print_cost -- 是否打印出成本
"""

np.random.seed(3)
n_x = X.shape[0] # 根据输入特征的维度得出输入层的神经元个数
n_y = Y.shape[0] # 根据标签的维度得出输出层的神经元个数


# 初始化参数
parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']

# 在这个循环里进行训练,一次一次地对参数进行优化
for i in range(0, num_iterations):
# 进行前向传播
A2, cache = forward_propagation(X, parameters)

# 计算出本次的成本
cost = compute_cost(A2, Y, parameters)

# 进行反向传播。根据前向传播得到的一些值算出梯度。
grads = backward_propagation(parameters, cache, X, Y)

# 根据梯度对参数进行一次优化(下降)
parameters = update_parameters(parameters, grads)

# 将本次训练的成本打印出来
if print_cost and i % 1000 == 0:
print ("在训练%i次后,成本是: %f" % (i, cost))

return parameters

预测新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 我们已经可以通过上面的函数来进行参数训练。
# 这个函数可以利用上面学习到的参数来对新数据进行预测
def predict(parameters, X):
"""
参数:
parameters -- 训练得出的参数(学习到的参数)
X -- 预测数据
"""

# 预测其实就是简单地执行一次前向传播
A2, cache = forward_propagation(X, parameters)
predictions = np.round(A2) # 对结果进行四舍五入,小于0.5就是0,否则就是1

return predictions

评估

1
2
3
4
5
6
7
8
9
10
11
12
# 好,所有函数都实现完毕了,我们已经构建好了一个浅层神经网络了。
# 现在用这个浅层神经网络来执行与文章开头的单神经元网络同样的任务——将花形图案中的红蓝点区分开

# 首先是根据训练数据来进行参数学习(训练数据是与单神经元网络一样一样的)
parameters = nn_model(X, Y, n_h = 4, num_iterations=10000, print_cost=True)

# 然后用训练得出的参数进行预测。
predictions = predict(parameters, X)
print ('预测准确率是: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')

# 将预测结果画出来。
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y.ravel())

深层神经网络

浅层神经网络只有两层。而深层神经网络可以由任意层。简单来说,就是把上一层的结果作为下一层的输入。

image-20200329180212175

核对矩阵的维度

编写程序的时候,我们很容易由于维度的错误而出现错误。比如下面这张图。

image-20200329180639884

输入层有2个元素,所以\(n^{[0]}=2\) \[ W^{[l]} \rightarrow (n^{[l]},n^{[l-1]}) \\ b^{[l]} \rightarrow (n^{[l]},1) \\ z^{[l]} \rightarrow (n^{[l]},1) \\ a^{[l]} \rightarrow (n^{[l]},1) \\ da^{[l]} \rightarrow (n^{[l]},1) \\ dz^{[l]} \rightarrow (n^{[l]},1) \\ db^{[l]} \rightarrow (n^{[l]},1) \\ dW^{[l]} \rightarrow (n^{[l]},n^{[l-1]}) \\ \]