搭建第一个神经网络训练模型
当前人工智能主流方向-连接主义
人工智能三学派:
-
行为主义:基于控制论,构建感知-动作控制系统
-
符号主义:基于算术逻辑表达式,求解问题时先把问题描述为表达式,再求解表达式。
-
连接主义:仿生学,模仿神经元连接关系。(即神经网络)
一个典型的生物神经元结构
一个最简单的前向传播神经网络
用计算机仿真出神经网络连接关系,让计算机具备感性思维
- 准备数据:采集大量“特征-标签”(类似于键值对)数据
es:<猫的大量特征图片,cat> - 搭建网络:搭建神经网络结构
- 优化参数:训练网络获取最佳参数(反传)
- 应用网络:将网络保存为模型,输入新数据,输出分类或预测结果(前传)
神经网络设计过程的例子:鸢尾花分类
搭建网络
以上面的例子为例,我们在搭建网络之前,首先需要明确这个神经网络的作用是什么?暂时不考虑是回归还是分类问题,只考虑目标的话?
很明显,我们希望这个神经网络可以根据一朵鸢尾花的特征来识别这个鸢尾花的品种对吧.打一个不太恰当的比喻,将其抽象到数学上就是我Input一堆特征,比如这个碗它又大又圆?然后需要这个模型output一个结果,比如这个碗是吴先生的,由于一个物理的特征肯定有很多个,所以借助矩阵这一数学工具来进行具体的运算.
回到这个例子本身,我们明确了要输入鸢尾花的特征[花蕊长,花蕊宽,花瓣长,花瓣宽],也明确了要输入的结果为[狗尾草的概率,杂色的概率,弗吉尼亚的概率],当然结果中概率最大的那个品种就是我们的最终结果,这很简单,一个max函数就搞定了.所以我们先定义
X
=
[
a
1
,
a
2
,
b
1
,
b
2
]
X=[a1,a2,b1,b2]
X=[a1,a2,b1,b2]
Y = [ p 1 , p 2 , p 3 ] Y=[p1,p2,p3] Y=[p1,p2,p3]
要实现矩阵X到Y的变化,我们需要借助矩阵的知识啦,即:
A
m
×
r
B
r
×
n
=
C
m
×
n
A_{m\times r}B_{r\times n}=C_{m\times n}
Am×rBr×n=Cm×n
所以,我们根据X是(1,4)的矩阵,Y是(1,3)的矩阵.我们可以定义权重矩阵W为一个(4,3)的矩阵,因为没有经验,所以直接用随机值来填充W即可.
W
4
×
3
=
[
w
11
w
12
w
1
n
w
21
w
22
w
2
n
⋮
⋮
⋮
w
41
w
42
w
4
n
]
W_{4\times 3}=\left[ \begin{matrix} w_{11}& w_{12}& w_{1n}\\ w_{21}& w_{22}& w_{2n}\\ \vdots& \vdots& \vdots\\ w_{41}& w_{42}& w_{4n}\\ \end{matrix} \right]
W4×3=⎣⎢⎢⎢⎡w11w21⋮w41w12w22⋮w42w1nw2n⋮w4n⎦⎥⎥⎥⎤
最后,我们还需要定义一个小小的偏置向量b,在tensorflow或者是numpy中都可以利用广播的性质,所以这个偏置向量可以为一维的合理的偏置向量.
综上,我们可以得到我们的神经网络框架了:
Y
=
X
∗
W
+
b
Y=X*W+b
Y=X∗W+b
前向传播
在搭建好神经网络模型后,我们需要把真实的数据input给这个模型,然后看看这个模型的输出,从输入到输出的过程就是前向传播,在神经网络的训练过程中,每一次迭代都会发生一次前向传播,一次后向传播。
损失函数
我们考虑一个问题,如果我们只有这个模型和一大堆数据,但是没有一个标准来定义模型预测结果的正确率,那么这个模型其实是不会有任何改变的(后向传播梯度下降也依赖于损失函数),也就不存在机器“学习”一说了。
在定义损失函数的时候,我们一般是将其定义为预测值y
与标准答案y_
的差距。在概率论中,有一章专门讨论这一问题,叫做误差函数。
损失函数可以定量判断W
,b
的优劣,当损失函数输出最小时,参数W
,b
会出现最优值。—(导数的物理意义)
常用的损失函数如下:
(1)0-1损失函数(0-1 loss function)
L
(
y
,
f
(
x
)
)
=
{
1
,
y
≠
f
(
x
)
0
,
y
=
f
(
x
)
L\left( y,f\left( x \right) \right) =\begin{cases} 1,& y≠f\left( x \right)\\ 0,& y=f\left( x \right)\\ \end{cases}
L(y,f(x))={1,0,y=f(x)y=f(x)
当预测错误时,损失函数为1,当预测正确时,损失函数值为0。该损失函数不考虑预测值和真实值的误差程度。只要错误,就是1。
(2)平方损失函数(quadratic loss function)
L
(
y
,
f
(
x
)
)
=
(
y
−
f
(
x
)
)
2
L(y,f(x))=(y−f(x))^2
L(y,f(x))=(y−f(x))2
是指预测值与实际值差的平方。
(3)绝对值损失函数(absolute loss function)
L
(
y
,
f
(
x
)
)
=
∣
y
−
f
(
x
)
∣
L(y,f(x))=|y−f(x)|
L(y,f(x))=∣y−f(x)∣
该损失函数的意义和上面差不多,只不过是取了绝对值而不是求平方,差距不会被平方放大。
(4)对数损失函数(logarithmic loss function)
L
(
y
,
p
(
y
∣
x
)
)
=
−
l
o
g
p
(
y
∣
x
)
L(y,p(y|x))=−logp(y|x)
L(y,p(y∣x))=−logp(y∣x)
这个损失函数就比较难理解了。事实上,该损失函数用到了极大似然估计的思想。P(Y|X)通俗的解释就是:在当前模型的基础上,对于样本X,其预测值为Y,也就是预测正确的概率。为了将其概率的乘除转化为加减法,我们将其取对数。最后由于是损失函数,所以预测正确的概率越高,其损失值应该是越小,因此再加个负号取个反。
(5)Hinge loss
Hinge loss专用于二分类问题,尤其是在SVM(支持向量机)中,其定义为:
L
(
w
,
b
)
=
m
a
x
{
0
,
1
−
y
f
(
x
)
}
L(w,b)=max\{0,1−yf(x)\}
L(w,b)=max{0,1−yf(x)}
其中
y
=
+
1
y=+1
y=+1或
y
=
−
1
y=−1
y=−1 ,
f
(
x
)
=
w
x
+
b
f(x)=wx+b
f(x)=wx+b ,HInge loss其实是一个分段函数:
- 当 f ( x ) f(x) f(x)大于等于+1或者小于等于-1时,都是分类器确定的分类结果,此时的损失函数loss为0;
- 而当预测值 f ( x ) ∈ ( − 1 , 1 ) f(x) \in (-1,1) f(x)∈(−1,1)时,分类器对分类结果不确定,loss不为0。
- 最后,当 f ( x ) = 0 f(x)=0 f(x)=0时,loss达到最大值。
由损失函数推出代价函数
- 损失函数(Loss Function):是定义在单个样本上的,是指一个样本的误差。
- 代价函数(Cost Function):是定义在整个训练集上的,是所有样本误差的平均,也就是所有损失函数值的平均。
- 目标函数(Object Function):是指最终需要优化的函数,一般来说是经验风险+结构风险,也就是(代价函数+正则化项)。
一句话概括:代价函数是建立在损失函数基础上的均值估计
常用代价函数
(1) 均方误差MSE(Mean Squared Error)
M
S
E
=
1
N
∑
i
=
1
N
(
y
(
i
)
−
f
(
x
(
i
)
)
2
)
MSE=\frac{1}{N}{\sum_{i=1}^{N}{\left( y^{\left( i \right)}-f\left( x^{\left( i \right)} \right) ^2 \right)}}
MSE=N1i=1∑N(y(i)−f(x(i))2)
均方误差是指参数估计值与参数真值之差平方的期望值; MSE可以评价数据的变化程度,MSE的值越小,说明预测模型描述实验数据具有更好的精确度。( i 表示第 i 个样本,N表示样本总数)
通常用来做回归问题的代价函数。
(2)均方根误差RMSE
R
M
S
E
=
M
S
E
2
=
R
M
S
E
=
1
N
∑
i
=
1
N
(
y
(
i
)
−
f
(
x
(
i
)
)
2
)
2
RMSE=\sqrt[2]{MSE}=RMSE=\sqrt[2]{\frac{1}{N}{\sum_{i=1}^{N}{\left( y^{\left( i \right)}-f\left( x^{\left( i \right)} \right) ^2 \right)}}}
RMSE=2MSE=RMSE=2N1i=1∑N(y(i)−f(x(i))2)
均方根误差是均方误差的算术平方根,能够直观观测预测值与实际值的离散程度。
通常用来作为回归算法的性能指标。
(3)平均绝对误差MAE(Mean Absolute Error)
M
A
E
=
1
N
∑
i
=
1
N
∣
y
(
i
)
−
f
(
x
)
(
i
)
∣
MAE=\frac{1}{N}\sum_{i=1}^{N}|y^{(i)}-f(x)^{(i)}|
MAE=N1i=1∑N∣y(i)−f(x)(i)∣
平均绝对误差是绝对误差的平均值 ,平均绝对误差能更好地反映预测值误差的实际情况。
通常用来作为回归算法的性能指标。
(4)交叉熵代价函数CE(Cross Entry)
H
(
p
,
q
)
=
−
∑
i
=
1
N
p
(
x
(
i
)
)
l
o
g
q
(
x
(
−
i
)
)
H(p,q)=-\sum_{i=1}^{N}p(x^{(i)})log_q(x^{(-i)})
H(p,q)=−i=1∑Np(x(i))logq(x(−i))
交叉熵是用来评估当前训练得到的概率分布与真实分布的差异情况,减少交叉熵损失就是在提高模型的预测准确率。其中 p(x)是指真实分布的概率, q(x) 是模型通过数据计算出来的概率估计。
比如对于二分类模型的交叉熵代价函数:
L
(
w
,
b
)
=
−
1
N
∑
i
=
1
N
(
y
(
i
)
l
o
g
f
(
x
(
i
)
)
)
+
(
1
−
y
(
i
)
)
l
o
g
(
1
−
f
(
x
(
i
)
)
)
L(w,b)= -\frac{1}{N}\sum_{i=1}^{N}(y^{(i)}logf(x^{(i)}))+(1-y^{(i)})log(1-f(x^{(i)}))
L(w,b)=−N1i=1∑N(y(i)logf(x(i)))+(1−y(i))log(1−f(x(i)))
其中 f(x)可以是sigmoid函数。或深度学习中的其它激活函数。而 y(i)∈0,1。
通常用做分类问题的代价函数。
学习率
参考的博客https://blog.csdn.net/qq_41204464/article/details/83660728
学习率(Learning rate) 作为监督学习以及深度学习中重要的超参,其决定着目标函数能否收敛到局部最小值以及何时收敛到最小值。合适的学习率能够使目标函数在合适的时间内收敛到局部最小值。
学习率大小与学习效果的关系
学习率 大 | 学习率 小 | |
---|---|---|
学习速度 | 快 | 慢 |
使用时间点 | 刚开始训练时 | 一定轮数过后 |
副作用 | 1.易损失值爆炸;2.易振荡。 | 1.易过拟合;2.收敛速度慢。 |
反向传播更新参数
其实就是利用了高数中导数(或微分)的物理意义,学过微积分的同学都知道,对某个函数积分可以得到它的(局部或全局)最大值/最小值,利用这一性质我们可以实现上面定义的损失函数的下降过程。
具体公式如下:
w
t
+
1
=
w
t
−
l
r
∗
∂
l
o
s
s
∂
w
t
w_{t+1}=w_t-lr*\frac{\partial loss}{\partial w_t}
wt+1=wt−lr∗∂wt∂loss
代码的实现
import tensorflow as tf
w = tf.Variable(tf.constant(5,dtype=tf.float32)) # 设置初始权重为5
lr = 0.2 # 超参数学习率,可以自行调整,这个真的只能看经验了
epochs = 40 # 迭代次数
for epoch in range(epochs):
with tf.GradientTape() as tape: # 定义损失函数(w+1)^2,并且用with结构包裹其梯度的计算过程
loss = tf.square(w+1)
grads = tape.gradient(loss,w) # .gradient函数告知谁对谁求导
w.assign_sub(lr*grads) # .assign_sub 对变量做自减,即:w -= lr*grads 这便是是梯度下降的具体公式
print(f"After{epoch},w is {w.numpy()},loss is {loss}")
Tensorflow的快速入门
1. 张量(Tensor)
张量可以表示0~n阶数组(列表),具体细节如下表
维数 | 阶 | 名字 | 例子 |
---|---|---|---|
0-D | 0 | 标量 scalar | s=1 2 3 |
1-D | 1 | 向量 vector | v = [1,2,3] |
2-D | 2 | 矩阵 matrix | m=[[1,2,3],[4,5,6],[7,8,9]] |
n-D | n | 张量 tensor | t=[[[…]]] n个方括号嵌套 |
2. 数据类型
- 整形,浮点型
- tf.int32,tf.float32,tf.float64…
- 布尔型
- tf.constant([True,False])
- 字符串型
- tf.constant(“hello,world!”)
3. 张量的创建
-
tf.constant(张量内容,[dtype=数据类型])
import tensorflow as tf a = tf.constant([1,5],dtype=tf.int64) print(a) print(a.dtype) print(a.shape)
运行结果: tf.Tensor([1 5], shape=(2,), dtype=int64) <dtype: 'int64'> (2,)
-
tf.convert_to_tensor(数据名, [dtype=数据类型])
很多时候数据是有numpy格式给出的,可以使用这个函数将numpy类型的数据转化为张量import numpy as np a = np.arange(0,5) b = tf.convert_to_tensor(a,dtype=tf.int64) print(a) print(b)
运行结果: [0 1 2 3 4] tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
-
tf.zeros(维度)
创建全为0的张量 -
tf.ones(维度)
创建全为1的张量 -
tf.fill(维度,指定值)
创建全为指定值的张量
这里的维度:- 一维 直接写个数
- 二维 用[行,列]的形式给出
- 多维 用[n,m,k,j…] 的形式给出
a = tf.zeros([2,3]) b = tf.ones(4) c = tf.fill([2,3],9) print(a) print(b) print(c)
运行结果: tf.Tensor( [[0. 0. 0.] [0. 0. 0.]], shape=(2, 3), dtype=float32) tf.Tensor([1. 1. 1. 1.], shape=(4,), dtype=float32) tf.Tensor( [[9 9 9] [9 9 9]], shape=(2, 3), dtype=int32)
-
tf.random.normal(维度,mean=均值,stddev=方差或标准差)
生成正态分布的随机数,默认生成标准正态(均值为0,方差为1) -
tf.random.truncated_normal(维度,mean=均值,stddev=方差)
生成截断式正态分布的随机数在tf.truncated_normal中如果随机生成数据的取值在 ( μ − 2 σ , μ + 2 σ ) \text{(}\mu -2\sigma \text{,}\mu +2\sigma \text{)} (μ−2σ,μ+2σ)之外,则重新进行生成,保证生成值在均值附近。
标准差计算公式
σ = ∑ i = 1 n ( x i − μ ) 2 n \sigma =\sqrt{\frac{\sum_{i=1}^n{\text{(}x_i-\mu \text{)}^2}}{n}} σ=n∑i=1n(xi−μ)2d = tf.random.normal([2,2],mean=0.5,stddev=1) e = tf.random.truncated_normal([2,2],mean=0.5,stddev=1) print("normal:",d) print("truncated normal:",e)
结果: normal: tf.Tensor( [[ 0.683962 -0.11715412] [-0.05526519 -0.01987666]], shape=(2, 2), dtype=float32) truncated normal: tf.Tensor( [[1.6128237 0.23255497] [0.42114788 2.0300918 ]], shape=(2, 2), dtype=float32)
-
tf.random.uniform(维度,minval=最小值,maxval=最大值)
生成均匀分布随机数u = tf.random.uniform([2,2],minval=0,maxval=1) print(u)
结果: tf.Tensor( [[0.40941513 0.9080398 ] [0.37251842 0.5373926 ]], shape=(2, 2), dtype=float32)
4. 常用函数
理解axis
在一个二维张量或数组中,可以通过设置axis为0或1控制执行维度。
- axis=0 代表跨行(经度,down)
- axis=1 代表跨列(纬度,across)
- 如果不指定axis,则所有元素参与计算
函数 | 描述 |
---|---|
tf.cast(张量名,dtype=数据类型) | 强制tensor转换为该数据类型 |
tf.reduce_min(张量名,axis=操作轴) | 计算张量维度上元素的最小值 |
tf.reduce_max(张量名,axis=操作轴) | 计算张量维度上元素的最大值 |
tf.Variable(初始值) | 将变量标记为“可训练”,被标记的变量会在反向传播中记录梯度信息。神经网络训练中,常用该函数标记待训练参数w |
数学运算
-
对应元素的四则运算:
td.add,tf.substract,tf.multiply,tf.divide
-
平方,次方与开方:
tf.square,tf.pow,tf.sqrt
-
矩阵乘:
tf.matmul
函数 | 描述 |
---|---|
tf.add(tensor1,tensor2) | 实现两个张量的对应元素相加 |
tf.substract(tensor1,tensor2) | 实现两个张量的对应元素相减 |
tf.multiply(tensor1,tensor2) | 实现两个张量的对应元素相乘 |
tf.divide(tensor1,tensor2) | 实现两个张量的对应元素相除 |
tf.square(tensor) | 计算某个张量的平方 |
tf.pow(tensor,n次方数) | 计算某个张量的n次方 |
tf.sqrt(tensor) | 计算某个张量的开方 |
tf.matmul(matrix1,matrix2) | 实现两个矩阵的相乘 |
四则运算示例:
注意:只有维度相同的张量才可以做四则运算
a = tf.ones([1,3])
b = tf.fill([1,3],3.)
print(a)
print(b)
print(tf.add(a,b))
print(tf.subtract(a,b))
print(tf.multiply(a,b))
print(tf.divide(b,a))
结果:
tf.Tensor([[1. 1. 1.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
tf.Tensor([[4. 4. 4.]], shape=(1, 3), dtype=float32)
tf.Tensor([[-2. -2. -2.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
数字运算示例:
a= tf.fill([1,2],3.)
print(a)
print(tf.pow(a,3))
print(tf.square(a))
print(tf.sqrt(a))
result:
tf.Tensor([[3. 3.]], shape=(1, 2), dtype=float32)
tf.Tensor([[27. 27.]], shape=(1, 2), dtype=float32)
tf.Tensor([[9. 9.]], shape=(1, 2), dtype=float32)
tf.Tensor([[1.7320508 1.7320508]], shape=(1, 2), dtype=float32)
矩阵运算示例:
a = tf.ones([3,2])
b = tf.fill([2,3],3.)
print(tf.matmul(a,b))
result:
tf.Tensor(
[[6. 6. 6.]
[6. 6. 6.]
[6. 6. 6.]], shape=(3, 3), dtype=float32)
from_tensor_slices
神经网络训练时,是把输入特征和标签配对后喂入网络的,Tensorflow给出了把特征和标签配对的函数
-
tf.data.Dataset.from_tensor_slices
切片传入张量的第一维度,生成输入特征/标签对,构建数据集
data=tf.data.Dataset.from_tensor_slices((输入特征,标签))
Numpy 和 Tensor 格式都可以使用该语句读入数据
例子:
features = tf.constant([12,23,10,17])
labels = tf.constant([0,1,1,0])
dataset = tf.data.Dataset.from_tensor_slices((features,labels))
print(dataset)
for element in dataset:
print(element)
结果:
<TensorSliceDataset shapes: ((), ()), types: (tf.int32, tf.int32)> #(特征,标签配对)
(<tf.Tensor: id=9, shape=(), dtype=int32, numpy=12>, <tf.Tensor: id=10, shape=(), dtype=int32, numpy=0>)
(<tf.Tensor: id=11, shape=(), dtype=int32, numpy=23>, <tf.Tensor: id=12, shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: id=13, shape=(), dtype=int32, numpy=10>, <tf.Tensor: id=14, shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: id=15, shape=(), dtype=int32, numpy=17>, <tf.Tensor: id=16, shape=(), dtype=int32, numpy=0>)
with+GradientTape
我们可以在with结构中使用GradientTape实现对指定参数的求导运算
配合刚刚的Variable函数,可以实现损失函数Loss对参数w的求导数运算
-
with结构记录计算过程,gradient求出张量的梯度
with tf.GradientTape() as tape: 若干个计算过程 grad = tape.gradient(函数,求导对象)
例子:
with tf.GradientTape() as tape: w = tf.Variable(tf.constant(3.0)) loss = tf.pow(w,2) # loss = w^2 grad = tape.gradient(loss,w) # dloss/dw = 2w=6.0 print(grad)
结果: tf.Tensor(6.0, shape=(), dtype=float32)
enumerate
python的内置函数enumerate可以将列表,元组,字符串等对象转化为键值对<索引,元素>的可迭代对象,常在for 循环中使用。
-
enumerate(列表名)
seq = ['one','two','three'] for i,element in enumerate(seq): print(i,element) >>(0, 'one') >>(1, 'two') >>(2, 'three')
tf.one_hot獨熱編碼
在实现分类问题中,常用独热编码(one-hot encoding)做标签,标记类别:1表示是,0表示否
-
tf.one_hot(待转换数据,depth=几分类)
可以实现将待转换数据经过独热编码后以one-hot形式输出
例子:classes = 3 labels = tf.constant([1,0,2]) # 输入的元素值最小为0,最大为2 output = tf.one_hot(labels,depth=classes) print(output)
结果: tf.Tensor( [[0. 1. 0.] [1. 0. 0.] [0. 0. 1.]], shape=(3, 3), dtype=float32)
softmax激活函數
在之前鸢尾花的例子里输出的是3个分类的对应得分,而只有将这些得分转化为概率分布之后,才能与独热码的标签做比较
在分类问题中,为了使得输出结果为0~1区间的概率分布,在机器学习中通常在输出层使用激励函数来进行处理。
其中最常用的是softmax
函数,其定义为:
s
o
f
t
m
a
x
(
Y
)
=
e
y
i
∑
j
=
0
n
e
y
i
softmax(Y)=\frac{e^{y_i}}{\sum_{j=0}^n{e^{y_i}}}
softmax(Y)=∑j=0neyieyi
输出层:
应用在上面的例子中:
y
=
{
1.01
2.01
−
0.66
→
e
y
i
∑
j
=
0
n
e
y
i
→
{
e
y
0
e
y
o
+
e
y
1
+
e
y
2
=
0.256
e
y
1
e
y
o
+
e
y
1
+
e
y
2
=
0.695
e
y
2
e
y
o
+
e
y
1
+
e
y
2
=
0.048
y=\left\{ \begin{array}{l} 1.01\\ 2.01\\ -0.66\\ \end{array}\rightarrow \frac{e^{y_i}}{\sum_{j=0}^n{e^{y_i}}}\rightarrow \left\{ \begin{array}{l} \frac{e^{y_0}}{e^{y_o}+e^{y_1}+e^{y_2}}=0.256\\ \frac{e^{y_1}}{e^{y_o}+e^{y_1}+e^{y_2}}=0.695\\ \frac{e^{y_2}}{e^{y_o}+e^{y_1}+e^{y_2}}=0.048\\ \end{array} \right. \right.
y=⎩⎨⎧1.012.01−0.66→∑j=0neyieyi→⎩⎨⎧eyo+ey1+ey2ey0=0.256eyo+ey1+ey2ey1=0.695eyo+ey1+ey2ey2=0.048
-
tf.nn.softmax(y)
可以使得输出y符合概率分布
当n分类的n个输出 ( y 0 , y 1 , . . . , y n − 1 ) (y_0,y_1,...,y_n-1) (y0,y1,...,yn−1)通过softmax()函数,便会生成其对应的概率分布 ( p 0 , p 1 , . . . , p n ) (p_0,p_1,...,p_n) (p0,p1,...,pn)
例子:y = tf.constant([1.01,2.01,-0.66]) y_pro = tf.nn.softmax(y) print("After softmax,y_pro is :",y_pro)
结果: After softmax,y_pro is : tf.Tensor([0.25598174 0.69583046 0.0481878 ], shape=(3,), dtype=float32)
参数的自更新-assign_sub
asssign_sub函数常用于参数的自更新,等待自更新的参数w要先被指定为可更新可训练,也就是Variable类型 ,才可以实现自我更新。
-
w.assign_sub(1)
等效为w-=1
两个注意的点:- assign_sub是赋值操作,更新参数的值并返回,非原地操作
- 调用assign_sub前,要用
tf.Variable
定义变量w为可训练(可自更新)
w = tf.Variable(4) for i in range(4): w.assign_sub(1) print(w)
结果: <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=3> <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=2> <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=1> <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=0>
最大值索引–tf.argmax
-
tf.argmax(tensor,axis=操作轴)
返回张量沿指定维度最大值的索引import numpy as np test = np.array([[1,2,3],[2,3,4],[5,4,3],[8,7,2]]) print(test) print(tf.argmax(test,axis=0)) # 返回每一列(跨行)最大值的索引 print(tf.argmax(test,axis=1)) # 返回每一行(跨列)最大值的索引
结果: [[1 2 3] [2 3 4] [5 4 3] [8 7 2]] tf.Tensor([3 3 1], shape=(3,), dtype=int64) tf.Tensor([2 2 0 0], shape=(4,), dtype=int64)
鸢尾花分类模型实现
准备数据
1. 数据集读取
-
数据集介绍
共有数据150组,每组包括花萼长,花萼宽,花瓣长、花瓣宽4个输入特征。同时给出了这一组特征对应的鸢尾花类别。类别包括Setosa Iris(狗尾草鸢尾花),Versicolour Iris(杂色鸢尾),Virginica Iris(弗吉尼亚鸢尾) 三类,分别用数字0,1,2表示。 -
通过sklearn 模块导入鸢尾花数据集
from sklearn import datasets from pandas import DataFrame import pandas as pd x_data = datasets.load_iris().data # .data返回iris数据集所有输入特征 y_data = datasets.load_iris().target # .target 返回iris数据集所有标签 print("x_data from datasets:\n",x_data) print("y_data from datasets:\n",y_data) x_data = DataFrame(x_data,columns=['花萼长','花萼宽','花瓣长','花瓣宽']) # 将x_data转换为padas的DataFrame类,增加可读性 pd.set_option('display.unicode.east_asian_width',True) # 设置列名对齐 print("x_data add column name:\n",x_data) x_data['类别']=y_data # 新加一列,列标签为‘类名’,数据为y_data print("x_data add a column:\n",x_data)
结果 x_data from datasets: [[5.1 3.5 1.4 0.2] [6.4 2.7 5.3 1.9] [6.8 3. 5.5 2.1] [5.7 2.5 5. 2. ] [5.8 2.8 5.1 2.4] [6.4 3.2 5.3 2.3] [6.5 3. 5.5 1.8] [7.7 3.8 6.7 2.2] ... [6.5 3. 5.2 2. ] [6.2 3.4 5.4 2.3] [5.9 3. 5.1 1.8]] y_data from datasets: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2] x_data add column name: 花萼长 花萼宽 花瓣长 花瓣宽 0 5.1 3.5 1.4 0.2 1 4.9 3.0 1.4 0.2 2 4.7 3.2 1.3 0.2 3 4.6 3.1 1.5 0.2 4 5.0 3.6 1.4 0.2 .. ... ... ... ... 145 6.7 3.0 5.2 2.3 146 6.3 2.5 5.0 1.9 147 6.5 3.0 5.2 2.0 148 6.2 3.4 5.4 2.3 149 5.9 3.0 5.1 1.8 [150 rows x 4 columns] x_data add a column: 花萼长 花萼宽 花瓣长 花瓣宽 类别 0 5.1 3.5 1.4 0.2 0 1 4.9 3.0 1.4 0.2 0 2 4.7 3.2 1.3 0.2 0 3 4.6 3.1 1.5 0.2 0 4 5.0 3.6 1.4 0.2 0 .. ... ... ... ... ... 145 6.7 3.0 5.2 2.3 2 146 6.3 2.5 5.0 1.9 2 147 6.5 3.0 5.2 2.0 2 148 6.2 3.4 5.4 2.3 2 149 5.9 3.0 5.1 1.8 2 [150 rows x 5 columns]
2. 数据集乱序
人类在认识世界的时候,信息是没有规律的,杂乱无章地涌入大脑,所以喂入神经网络的数据集也需要被打乱顺序。
np.random.seed(116) # 随机数种子,使用相同的seed,使得输入特征/标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)
3. 生成训练集和测试集(即x_train/y_train,x_test,y_test)
为了公正判断神经网络的效果,所以需要将原始数据分为没有任何交集的训练集和测试集
x_train = x_data[:-30] # 取前120行数据作为训练集
y_train = y_data[:-30]
x_test = x_data[-30:] # 取最后30行数据作为测试集
y_test = y_data[-30:] # 取最后30行数据作为测试集
4.配成(输入特征,标签)对,每次读取一个小批量/一小撮(batch)
使用from_tensor_slices
把输入特征和标签配对打包,每32组输入特征标签对打包为一个batch,一会儿喂入神经网络时,会以batch为单位喂入
train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test)).batch(32)
搭建网络
定义神经网络中所有可训练参数,有4个输入,3个输出,所以W应该是4x3的矩阵,偏置变量是1x3的向量
w1 = tf.Variable(tf.random.truncated_normal([4,3],stddev=0.1,seed=1)) # 同样需要指定随机数种子,保证参数一一对应
b1 = tf.Variable(tf.random.truncated_normal([3],stddev=0.1,seed=1))
参数优化
嵌套循环迭代,with结构更新参数,显示当前loss
for epoch in range(epoch): # 数据集级别迭代
for step,(x_train,y_train) in enumerate(train_db): # batch级别迭代
with tf.GradientTape() as tape: # 记录梯度信息
# 前向传播计算y,计算总loss
pass
grads = tape.gradient(loss,[w1,b1])
w1.assign_sub(lr*grads[0]) # 参数自更新
b1.assign_sub(lr*grads[1])
print("Epoch{},loss:{}".format(epoch,loss_all/4))
测试结果
计算当前参数前向传播后的准确率,显示当前acc,我们希望实时监控神经网络的训练情况以及其训练效果
for x_test, y_test in test_db:
# 使用更新后的参数进行预测
y = tf.matmul(x_test, w1) + b1 # y为预测结果
y = tf.nn.softmax(y) # 使得预测结果y符合概率分布
pred = tf.argmax(y, axis=1) # 返回y中最大值的索引即为预测的分类
pred = tf.cast(pred, dtype=y_test.dtype) # 调整数据类型与标签一致
# 若分类正确,则correct=1,否则为0,将bool类型的结果转换为int型
correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
correct = tf.reduce_sum(correct) # 将每个batch的correct数加起来
total_correct += int(correct) # 将所有batch的correct数加起来
total_number += x_test.shape[0]
# 准确率acc
acc = total_correct / total_number
test_acc.append(acc)
print("test_acc:", acc)
print("-" * 50)
acc/loss 可视化
通过matplotlib将准确率和损失函数值描绘成线型图,方便开发人员辨识模型效果
plt.title('Acc Curve') # 图片标题
plt.xlabel('Epoch') # x轴名称
plt.ylabel('Acc') # y轴名称
plt.plot(test_acc, label="$Accuracy$") # 逐点画出test_acc值并连线
plt.legend()
plt.show()
## 同样的方法可以画出Loss曲线
在搭建了模型,喂入了數據后,我們通過可視化工具可以看到我們的神經網絡的學習曲綫(即準確率和loss的變化)
附录-code
import tensorflow as tf
from sklearn import datasets
import numpy as np
from matplotlib import pyplot as plt
# 读入数据集
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target
# 数据集乱序
np.random.seed(116) # 随机数种子,使用相同的seed,使得输入特征/标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)
# 数据集分出永不相见的训练集和测试集
x_train = x_data[:-30] # 取前120行数据作为训练集
y_train = y_data[:-30]
x_test = x_data[-30:] # 取最后30行数据作为测试集
y_test = y_data[-30:] # 取最后30行数据作为测试集
# 对x的测试集和训练集数据进行数据类型转换,否则后面矩阵相乘时会因为数据类型不一致而报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)
# 配对[输入特征,标签]对。每次喂入神经网络一小撮(batch)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
# 定义神经网络中所有可训练参数--有4个输入,3个输出,所以W应该是4x3的矩阵,偏置变量是1x3的向量
## 使用tf.Variable()标记参数为可训练
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))
## 设置超参数学习率,以及其他的一些变量
lr = 0.1
train_loss_results = [] # 将每轮的loss记录在这个列表中,方便后面的数据可视化
test_acc = [] # 将每轮的acc记录在此列表中。为后续画acc曲线提供数据
epochs = 500 # 循环500轮
loss_all = 0 # 每轮分为4个step(120/30),loss_all记录4个step生成的4个loss的和
# 嵌套循环迭代,with结构更新参数,显示当前loss
for epoch in range(epochs): # 数据集级别迭代
for step, (x_train, y_train) in enumerate(train_db): # batch级别迭代
with tf.GradientTape() as tape: # 记录梯度信息
# 前向传播计算y=xw+b
y = tf.matmul(x_train, w1) + b1
y = tf.nn.softmax(y) # 使得输出y符合概率分布
y_ = tf.one_hot(y_train, depth=3) # 将标签值转化为独热码格式
# 计算总loss
loss = tf.reduce_mean(tf.square(y- y_)) # 采用均方误差函数mse
loss_all += loss.numpy() # 将每个step计算出的loss累加,方便后续求解loss平均值
# 计算loss对各个参数的梯度(偏导数)
grads = tape.gradient(loss, [w1, b1])
# 实现梯度更新
w1.assign_sub(lr * grads[0]) # 参数自更新
b1.assign_sub(lr * grads[1])
# 每个epoch,打印loss信息
print("Epoch{},loss:{}".format(epoch, loss_all / 4))
train_loss_results.append(loss_all / 4) # 将每次epoch的平均损失记录到列表中
loss_all = 0 # 归零,为记录下一次epoch的loss做准备
# 测试部分
# 计算当前参数前向传播后的准确率,显示当前acc
## total_correct为预测对的样本个数,total_number为测试的总样本数,将这两个变量都初始化为0
total_correct, total_number = 0, 0
for x_test, y_test in test_db:
# 使用更新后的参数进行预测
y = tf.matmul(x_test, w1) + b1 # y为预测结果
y = tf.nn.softmax(y) # 使得预测结果y符合概率分布
pred = tf.argmax(y, axis=1) # 返回y中最大值的索引即为预测的分类
pred = tf.cast(pred, dtype=y_test.dtype) # 调整数据类型与标签一致
# 若分类正确,则correct=1,否则为0,将bool类型的结果转换为int型
correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
correct = tf.reduce_sum(correct) # 将每个batch的correct数加起来
total_correct += int(correct) # 将所有batch的correct数加起来
total_number += x_test.shape[0]
# 准确率acc
acc = total_correct / total_number
test_acc.append(acc)
print("test_acc:", acc)
print("-" * 50)
# acc/loss可视化
## acc准确率可视化
plt.title('Acc Curve') # 图片标题
plt.xlabel('Epoch') # x轴名称
plt.ylabel('Acc') # y轴名称
plt.plot(test_acc, label="$Accuracy$") # 逐点画出test_acc值并连线
plt.legend()
plt.show()
## 同样的方法可以画出Loss曲线
plt.title('Loss Function Curve') # 图片标题
plt.xlabel('Epoch') # x轴变量名称
plt.ylabel('Loss') # y轴变量名称
plt.plot(train_loss_results, label="$Loss$") # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend() # 画出曲线图标
plt.show() # 画出图像