06_2_神经网络与全连接层_全连接层&输出方式&误差计算
全连接层
deep learning最基本的层:全连接层,也就是线性层
Outline
- Matmul 矩阵形式
- ->迭代到 Neural Network 神经网络结构
- ->引入 Deep Learning 概念
- ->最后实现一个 Multi-Layer
Recap
out = f(X@W+b)
f为非线性因子,一般指relu函数或者SIGMOD函数
X@W+b
h 0 0 = x 0 0 ∗ W 0 0 + x 1 0 ∗ W 0 1 + x 2 0 ∗ W 0 2 h_0^0 = x_0^0*W_0^0 + x_1^0*W_0^1 + x_2^0*W_0^2 h00=x00∗W00+x10∗W01+x20∗W02。相当于下图的连线,还有一个 b 0 b_0 b0没有画出来。
σ \sigma σ符号统指激活函数,左边是输入,右边是输出。
Black Magic!
上标表示层的概念。
Layers
输入层、隐藏层、输出层。
一层包括了权值W、偏置b、激活函数 σ \sigma σ等概念。
Here comes Deep Learning!
过去叫Neural Network,一般是3~5层。
现在大概是1200层,是过去3~5层不停地堆叠。
大概2000年代,更多的是工程paper,很难解释其中的原理机制。目前还是和neural network本质上没有什么区分,更多的是深度的增加。
Why?
在过去80年代,计算能力弱。
Heroes
- BigDATA 大量的图片、音频、视频数据
- ReLU函数,比较好的解决了梯度离散的现象
- Dropout
- BatchNorm
- ResNet 10~20层->100++的可行性
- Xavier Initialization 初始化方案
- Caffe/TensorFlow/PyTorch 深度学习加速库使一般人更方便
- …
Fully connected layer
tf.keras是TensorFlow内部的keras,它是TensorFlow对keras的实现,keras本质上是一种api的协议,TensorFlow对keras有具体的实现,位于tf.keras下。
tf.keras.layers.Dense(512) #512:输出的维度
输入的维度会自动识别,在例子中,W是[784,512],b是[512]
w和b是通过build创建的,并且可以多次创建。
input_shape=(None,4)。None为batch,4为输入的维度
刚刚上图没有调用build函数,我们在调用net(x)实例的时候,它会自动判断如果没有w和b,会自动调用build函数。
input的shape和创建的shape不一致的例子:
build的时候输入的维度是20维,喂的是12维,这样会产生error。需要给正确的shape。
现在讲解的是单层的Dense层,也就是一个全连接层,每个输出节点都和每个输入节点相连接,也就是Dense,稠密。
Multi-Layers
如果要创建多层,我们只需要创建容器:
keras.Sequential([layer1,layer2,layer3])
具体的实现:
输入是3,变成2维,再变成2维,再变成2维。
这里input_shape是3
model.summary() 其实是print的意思,返回模型的信息。
model.trainable_variables 会返回 w1,b1,w2,b2,w3,b3
实战:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow import keras
x = tf.random.normal([2,3])
#网络结构
model = keras.Sequential([
keras.layers.Dense(2,activation='relu'),
keras.layers.Dense(2,activation='relu'),
keras.layers.Dense(2)
])
model.build(input_shape=[None,3])
model.summary()
#第一层的8是因为输入为3,输出为2,一共3*2个w,再加上2个b;后面两层类似
# Model: "sequential"
# _________________________________________________________________
# Layer (type) Output Shape Param #
# =================================================================
# dense (Dense) multiple 8
# _________________________________________________________________
# dense_1 (Dense) multiple 6
# _________________________________________________________________
# dense_2 (Dense) multiple 6
# =================================================================
# Total params: 20
# Trainable params: 20
# Non-trainable params: 0
# _________________________________________________________________
for p in model.trainable_variables:
print(p.name,p.shape)
# dense/kernel:0 (3, 2)
# dense/bias:0 (2,)
# dense_1/kernel:0 (2, 2)
# dense_1/bias:0 (2,)
# dense_2/kernel:0 (2, 2)
# dense_2/bias:0 (2,)
输出方式
Outline
原因:因为神经网络是面向不同的应用的,不同的应用有不同的输出的范围的要求
有时候是实数集
有时候是概率的输出
有些概率还要求输出和是1
输出在-1~1
- y ∈ R d y \in R^d y∈Rd
- y i ∈ [ 0 , 1 ] y_i \in [0,1] yi∈[0,1]
- y i ∈ [ 0 , 1 ] , ∑ i = 0 y y i = 1 y_i \in [0,1], \sum_{i=0}^y y_i = 1 yi∈[0,1],∑i=0yyi=1
- y i ∈ [ − 1 , 1 ] y_i \in [-1,1] yi∈[−1,1]
1. y ∈ R d y \in R^d y∈Rd
linear regression x@w+b,如果要求正数,可以简单地加relu激活函数。一般来说最后一层是不加激活函数的,不加激活函数的输出值叫logits
分类问题,一般是用0~1的输出范围,但是MSE误差的话也可以不加这样范围的约束,直接用它去逼近。
实数集做输出的话,一般来讲不需要做额外的处理。
2. y i ∈ [ 0 , 1 ] y_i \in [0,1] yi∈[0,1]
- 二分类,一般有2种设定,第一种是输出节点只有1个,大于0.5判定为1;小于0.5判定为0。另外一种设定是把二分类也理解为多分类的问题,两个输出节点,P(y=0|x)和P(y=1|x),和其他多分类问题没有什么区别。
- 图片的生成,rgb是0~255范围压缩到0~1的范围,利于神经网络的优化,所以生成以后也是0~1的输出,表达是灰度或rgb的信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zTNX8I44-1615782848391)(06_2_神经网络与全连接层_全连接层&输出方式&误差计算.assets/image-20210307210845874.png)]
sigmoid函数,它的x范围是整体实数空间,y是从0~1。
通过out' = sigmoid(out)
,原来out是整个实数范围,变成0~1的范围。它不仅仅是激活函数的功能,更多的是输出值范围压缩的功能。
sigmoid
具体形式:
f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1
tf.sigmoid
把很宽的实数空间重映射到0~1的空间去,对于图片,希望能把它压缩到RGB的表示范围,也就是0~1
3. y i ∈ [ 0 , 1 ] , ∑ i = 0 y y i = 1 y_i \in [0,1], \sum_{i=0}^y y_i = 1 yi∈[0,1],∑i=0yyi=1
对MNIST,10分类属于0~9,给出数据集中的任何一张图片,它肯定是0~9之间的1类,同时要求输出之和的 ∑ i ∈ [ 0 , 9 ] P ( y = i ∣ x ) = 1 \sum_{i \in [0,9]} P(y=i|x) = 1 ∑i∈[0,9]P(y=i∣x)=1 。
sigmoid函数并没有实现这个功能。
所有概率之和为1的功能,另外1个函数叫做softmax。
softmax
一般,没有加激活函数的输出叫Logits。
softmax,把强的变得更强,把弱的变得更弱。在分类中使用的非常广泛,交叉熵也使用了softmax。
Classification
有个logits,有1个实例,10个分类,值在-2~2之间。
prob = tf.nn.softnax(logits,axis=1)
每个点代表P(y=i|x),因此名字为prob
4. y i ∈ [ − 1 , 1 ] y_i \in [-1,1] yi∈[−1,1]
tanh:Sigmoid函数通过平移,在y的轴上进行放大,0~2,然后再下移,变成-1~1的空间。
例子:
误差计算(损失函数)
Outline
- MSE 均方差
- Cross Entropy Loss 专门应对于分类问题的误差计算
- Hinge Loss 用于SVM支持向量机中Maximum margin多一些
∑ i m a x ( 0 , 1 − y i ∗ h θ ( x i ) \sum_i max(0,1-y_i * h_\theta(x_i) ∑imax(0,1−yi∗hθ(xi)
MSE
平方差的和。输出值和真实值的差-的平方-累加,再average( 1 N \frac{1}{N} N1)(可选,一般加)。-》一般来说,加learning rate的目的就是要衰减更新步长,因为我们计算的梯度往往是比较大的数值,所以才需要衰减更新的步长,如果loss计算比较大,梯度往往也比较大,所以有average操作。
N一般指batch,实际上它们往往还会在向量维度上进行average,loss不仅仅除以N,还会除以10(10类、vector length)。也就是说它是求一个sample的输出node的平均的average loss。
batch和10除不除都不是强制的。
2范数(2-norm)和MSE有些相似,在某种程度上是可以相互转换的。
具体的实例:
三种方式都可以求出MSE
loss1 = tf.reduce_mean(tf.square(y-out)) #按上图的第一个公式
loss2 = tf.square(tf.norm(y-out)) / (5*4) #按第二个公式,利用范数的平方除以(N*类别)
loss3 = tf.reduce_mean(tf.losses.MSE(y-out)) #利用tf.losses.MSE(a,b)求出shape为[b]的Tensor,再利用reduce_mean求出loss
Entropy(熵)
不确定性,事件发生的概率的确定性和不确定性是比较抽象的概念,同时需要数据来量化不确定度。
E n t r o p y = − ∑ P ( i ) log P ( i ) Entropy = - \sum P(i)\log P(i) Entropy=−∑P(i)logP(i)
因此,有以下公式,通过发生的概率P(i)乘以logP(i)求和,加上符号。这个数值用来衡量某个分布的不确定度(惊喜度)。当熵越小的时候,意味着确定度越大,事件发生的确定性更高。
因为概率的数值范围为0~1,logP(i)为负数,P(i)为正数,前面有负号,因此整个的值>=0,再求和。
当猫和狗的概率为0和1,因为熵越低,表示越确定,因此得到的熵接近于0
当猫和狗的概率都为0.5,说明非常不确定,不确定性很高,熵远远大于0
越有把握,熵越接近于0
Lottery(彩票) - 例子
都是0.25,熵很高
0.1,0.1,0.1,0.7,熵为1.35
0.97,熵为0.24
下面为手写公式计算熵
Cross Entropy交叉熵
是指两个分布之间信息衡量的标准, H ( p , q ) = − ∑ p ( x ) log q ( x ) H(p,q) = -\sum p(x) \log q(x) H(p,q)=−∑p(x)logq(x)
也可以推导成 H ( p , q ) = H ( p ) + D K L ( p ∣ q ) H(p,q) = H(p) + D_{KL} (p|q) H(p,q)=H(p)+DKL(p∣q),其中 D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q),中文名叫散度,这个是衡量p和q之间的距离。
当p=q, D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q)为0,H(p,q) = H§。
如果p是one-hot encoding,我们知道y一般是one-hot encoding过的,所以y的分布可能是[0,1,0…],它表示label为1的概率是100%,label是其他的概率是0。那么如果把y的分布理解为p,那么对于熵 h ( p : [ 0 , 1 , 0 ] ) = − 1 l o g 1 = 0 h(p:[0,1,0]) = -1log1 = 0 h(p:[0,1,0])=−1log1=0。也就是确定性非常大,对其他的概率是不可能的。
那么再看交叉熵,如果把p当做one-hot encoding放在前面的话,q是我们的output,也就是prob。这样利用公式2, H ( [ 0 , 1 , 0 ] , [ p 0 , p 1 , p 2 ] ) = 0 + D K L ( p ∣ q ) = − 1 l o g q 1 H([0,1,0],[p0,p1,p2]) = 0+D_{KL}(p|q) = -1logq_1 H([0,1,0],[p0,p1,p2])=0+DKL(p∣q)=−1logq1
因此对于one-hot encoding来说,p和q之间的交叉熵其实退化为一个 D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q), D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q)的最小值就是p=q的时候,这也说明了我们为什么使用h(p,q)作为loss的时候是合理的,当针对分类问题来说,我们希望预测的分布q: prob(预测的分布),p: [0,1,0,…](真实的分布),我们希望q能逼近于真实的分布p,那么什么时候能逼近?也就是交叉熵的最小值 D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q)等于0的时候,也就是p和q完全相等的时候,那就意味着如果预测q的分布能够真实的p的分布一样的话,那么这个时候的交叉熵就退化为 D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q),也就退化为0,也是loss取的最小的时候,输出q和真实p是完全一样的,这时候的w参数就是最好的参数。这样我们证明了采用交叉熵作为loss得到的最优化的值刚好是使得 D K L ( p ∣ q ) D_{KL}(p|q) DKL(p∣q)等于0的时候,也就是p=q的时候。
具体分类问题的交叉熵的求解方法:
Binary Classification二分类
有两种设计格式:
第1个是把二分类当做多分类求解,比如第1个节点表示P(y=0|x),第2个节点表示P(y=1|x),这个时候和二、三…分类完全一样。
第2个只有一个输出节点,这个节点P(y=1|x),那么P(y=0|x) = 1 - P(y=1|x)。这种方式减少了参数量(没太大关系)。
Single output(1个节点)
D
K
L
(
p
∣
q
)
=
−
1
l
o
g
q
1
D_{KL}(p|q) = -1logq_1
DKL(p∣q)=−1logq1的原因:因为
D
K
L
(
p
∣
q
)
=
−
∑
p
i
log
q
i
D_{KL}(p|q) = -\sum p_i\log q_i
DKL(p∣q)=−∑pilogqi对
p
i
p_i
pi等于0,那么
p
i
log
q
i
p_i\log q_i
pilogqi就等于0,
p
i
p_i
pi不等于0的时候,就等于1,10个数字(举例)间只有1个这样的项,因此以上。(p=1时候的q)
l
o
s
s
=
H
(
p
,
q
)
p
:
o
n
e
−
h
o
t
=
0
+
D
K
L
(
p
∣
q
)
=
−
1
log
q
i
loss = H(p,q) \quad p:one-hot\\ = 0 + D_{KL}(p|q)\\ = -1\log q_i
loss=H(p,q)p:one−hot=0+DKL(p∣q)=−1logqi
要最小化loss,就要最大化
q
i
q_i
qi,q表示当前节点的label是i的话,在i号节点输出的概率q,比如说
q
(
y
=
i
∣
x
)
q(y=i|x)
q(y=i∣x)越大越好,最大的可能值是1,这个时候就和p完全一样了,因为p的真实节点[0,1,0,…]也在这个下标,若q的预测值是[0,1,0,…],这个时候p和q完全相等,loss等于0。
以上我们推导了对于一个**多分类(上图类型1)**问题的loss的计算方式。
对于一个**单号节点(上图类型2)**输出的二分类问题的求解:
展开,首先因为对于单分类问题来说,它不是one-hot encoding,因为单一的输出是没有one-hot encoding的,所以,
H
(
P
,
Q
)
=
−
P
(
c
a
t
)
log
Q
(
c
a
t
)
−
P
(
!
c
a
t
)
l
o
g
(
Q
(
!
c
a
t
)
)
因
为
二
分
类
P
(
!
c
a
t
)
=
1
−
P
(
c
a
t
)
,
q
同
理
=
−
P
(
c
a
t
)
log
Q
(
c
a
t
)
−
(
1
−
P
(
c
a
t
)
)
log
(
1
−
Q
(
c
a
t
)
)
=
−
(
y
l
o
g
(
p
)
+
(
1
−
y
)
l
o
g
(
1
−
p
)
)
H(P,Q) = -P(cat)\log Q(cat) - P(!cat)log(Q(!cat))\\ 因为二分类 \quad P(!cat)=1-P(cat), q同理\\ =-P(cat)\log Q(cat) - (1-P(cat))\log(1-Q(cat))\\ =-(ylog(p) + (1-y)log(1-p))
H(P,Q)=−P(cat)logQ(cat)−P(!cat)log(Q(!cat))因为二分类P(!cat)=1−P(cat),q同理=−P(cat)logQ(cat)−(1−P(cat))log(1−Q(cat))=−(ylog(p)+(1−y)log(1−p))
p是神经网络输出节点的概率值,因此对于一个二分类的问题来说,y已知,p已知,我们可以简单的把loss归化为交叉熵H(P,Q)。当y(cat)=1,前项存在,后项不存在,为
−
y
l
o
g
(
p
)
-ylog(p)
−ylog(p);当y(cat)=0,则相反,为
−
(
1
−
y
)
l
o
g
(
1
−
p
)
-(1-y)log(1-p)
−(1−y)log(1−p)。
然后我们看更加通用的多分类,也就是多号输出节点(0,1,2,…9):
Classification
根据二分类中的多分类推导情况,可知:
H
(
[
0
,
1
,
0
]
,
[
p
0
,
p
1
,
p
2
]
)
=
0
+
D
K
L
(
p
∣
q
)
=
−
1
log
q
1
H([0,1,0],[p_0,p_1,p_2]) = 0 + D_{KL}(p|q)= -1\log q_1
H([0,1,0],[p0,p1,p2])=0+DKL(p∣q)=−1logq1
也就是当前x=1的话,其实概率就为-logP(y=1|x),这个节点的概率希望你越大越好,比如q为 q 1 q_1 q1的时候 log q 1 \log q_1 logq1等于0,这时候loss取最小值。
具体情况:
这里是五分类,真实是一只小狗。图片x=0。
第一种输出
Q
1
=
[
0.4
,
0.3
,
0.05
,
0.05
,
0.2
]
Q_1 = [0.4,0.3,0.05,0.05,0.2]
Q1=[0.4,0.3,0.05,0.05,0.2],可以判定预测为狗的概率最大,输出为狗,只是没有很大的置信度。这样网络不算太好,但是预测对了。
这样情况的loss(也是交叉熵),为0.916,loss(交叉熵)较大。
如果输出为[0.98…],这个时候的loss(交叉熵)是0.02,较小。
通过这样的情况,就可以更新参数,直到满足loss足够小。
更多的计算实例:
Categorical Cross Entropy(分类问题的交叉熵)
tf.losses.categorical_crossentropy([0,1,0,0],[0.25,0.25,0.25,0.25])
第一个参数是y_true(one-hot),第二个是预测值prob(predict)。
四个例子表示不同预测情况与肯定度的交叉熵(loss)值各不相同(正确且肯定的交叉熵小,不确定且肯定的交叉熵大……),也说明了Cross Entropy是合理的。
例子:
交叉熵有两种形式:
tf.losses.categorical_crossentropy([0,1,0,0],[0.25,0.25,0.25,0.25])
是函数形式。- 还有一种类的形式,
criteon = tf.keras.losses.CategoricalCrossentropy()
,首先声明类,然后对对象进行一个调用(对类实例criteon传入参数[y],[predict])。
第三部分是二分类但是只有1个输入节点的。例子中真实值为1,输出概率为0.1,交叉熵较大。
第四部分是函数的形式,和第三部分类似。
Why not MSE?
当sigmoid和MSE结合,会出现gradient vanish的现象,因为值大的时候梯度为0,小的时候梯度为0,这样很容易会出现前期更新非常慢,甚至出现loss很大,但是一直没更新的情况。
实践证明cross entropy收敛更加地快,在图中,预测值(概率)比较错误的情况下的梯度,当预测错误时候的概率,在刚开始的时候概率非常大,有助于前期收敛的非常快,这是它作为loss非常好的地方。
(我理解图中的梯度为斜率,这个函数是loss函数,loss函数可以是MSE,也可以是cross entropy,但是cross entropy在初期识别能力弱的时候,也就是predicted prob小的时候,它的梯度-斜率大,在前期能比较快的更新w-参数值,通过函数 w = w − l r ∗ δ l o s s δ w w = w - lr * \frac{\delta loss}{\delta w} w=w−lr∗δwδloss)
不过在meta-learning等一些学科,发现MSE更加稳定合理,交叉熵则不稳定,根据实际情况来说。
分类问题的pipeline:
logits->CrossEntropy
首先是从输入经过一系列网络得到输出(LOGIT)(一般指最后一层没有加激活函数),如果是分类问题,经过softmax,再经过cross entropy得到loss。
如果这么做会发现,有数值不稳定的过程,会出现处理错误error(not a number或infinity),为了处理这个问题,通常利用隔断,将黑色的区域统一在一起作为1个函数,函数内部已经做好了优化,防止出现数据不稳定的过程。
Numerical Stability(为数据稳定加参数from_logits)
所以做分类问题,不需要先softmax再cross entropy。直接交给TensorFlow稳定处理的函数,这个函数名字为原来的函数,只是加了参数from_logits=True
,一般情况都加这个参数。
总结注意两点:
- 第四部分的第一个参数必须要one-hot encoding
- 设定from_logits
当然,在第三部分经过softmax,第五部分from_logits设置为False,理论上讲和第四部分是一样的,但是不推荐,会出现数值不稳定的问题。