各位同学好,今天和大家介绍一下TensorFlow2.0中的梯度下降、激活函数及其梯度、损失函数及其梯度。

(1) 梯度计算:GradientTape(),tape.watch(),tape.gradient()

(2) 损失函数及其梯度:MSE:tf.reduce_mean(tf.losses.MSE()),交叉熵:tf.losses.categorical_crossentropy()


1. 梯度下降

1.1 自动求导函数:

GradientTape(persistent=False, watch_accessed_variables=True)

persistent: 布尔值,用来指定新创建的gradient tape是否是可持续性的。默认是False,意味着只能够调用一次GradientTape()函数。

watch_accessed_variables: 布尔值,表明GradientTape()函数是否会自动追踪任何能被训练的变量。默认是True。要是为False的话,意味着你需要手动去指定你想追踪的那些变量。

1.2 监视非Variable变量

tape.watch(tensor)

tape.watch()用于跟踪指定的tensor变量。由于GradientTape()默认只对tf.Variable类型的变量进行监控。如果需要监控的变量是tensor类型,则需要tape.watch()来监控,若是没有watch函数则梯度计算结果会是None。  如果指定跟踪的是tf.Variable类型,这一句就不用写了。

1.3 梯度计算

tape.gradient(target, sources, unconnected_gradients)

根据指定监视的变量来计算梯度

target: 求导的因变量

sources: 求导的自变量

unconnected_gradients: 无法求导时,返回的值,有两个可选值[none, zero],默认none。


import tensorflow as tf
w = tf.constant(1.) # 创建全连接层
x = tf.constant(2.) # 创建输入特征
# 自动求导
with tf.GradientTape() as tape:
    tape.watch([w]) # 监视w
    y = x*w 
# 计算梯度,因变量y,自变量w
grad1 = tape.gradient(y,[w])
# 结果为: [<tf.Tensor: shape=(), dtype=float32, numpy=2.0>]

创建的权重w是tensor类型,需要tape.watch()函数,如果改成w = tf.Variable(tf.constant(1.)),将w变成Variable类型,就不需要tape.watch()函数来监视w。y=x*w,y对w求导结果为x,即为2.0。


3. 损失函数及其梯度

3.1 均方误差MSE

计算公式: 

梯度损失函数深度学习 梯度下降和损失函数_python

y代表训练集的真实值,pred代表训练输出的预测结果,N通常指的是batch_size,也有时候是指特征属性个数。

MSE函数表示:

(1) tf.reduce_mean(tf.square(y - pred))

(2) tf.reduce_mean(tf.losses.MSE(y, pred))

一般而言,均方误差损失函数比较适用于回归问题中,对于分类问题,特别是目标输出为One-hot向量的分类任务中,交叉熵损失函数要合适的多。


import tensorflow as tf
# 设有一组训练集的目标值
y = tf.constant([1,2,3,0,2]) 
y = tf.one_hot(y, depth=4) # 目标为四种分类
y = tf.cast(y,dtype=tf.float32)
# 设有一组模型输出的预测结果数据
out = tf.random.normal([5,4])
# 三种方法计算,预测结果out和真实值y之间的损失MSE
loss1 = tf.reduce_mean(tf.square(y-out))  # 1.2804947
loss2 = tf.square(tf.norm(y-out))/(5*4) # 1.2804947  #二范数方法
loss3 = tf.reduce_mean(tf.losses.MSE(y,out)) # 1.2804947
# 返回shape为[b]的tensor

3.2 MSE的梯度

使用tf.reduce_mean(tf.losses.MSE())计算真实值和预测结果的均方差,需要对真实值y进行one-hot编码tf.one_hot(),对应索引位置的值为1,分成三个类别depth=3。prob输出的也是图片属于三种分类的概率。使用tape.gradient()函数对跟踪的变量求梯度,grads[0]因变量为loss,自变量为w。

import tensorflow as tf
#(1)均方差MSE
x = tf.random.normal([2,4]) # 创建输入层,2张图片,各有4个特征
w = tf.random.normal([4,3]) #一层全连接层,输出每张图片属于3个分类的结果
b = tf.zeros([3]) # 三个偏置
y = tf.constant([2,0]) # 真实值,第一个样本属于2类别,第2个样本属于0类别
#梯度计算
with tf.GradientTape() as tape:
    tape.watch([w,b]) # 指定观测w和b,如果w和b时variable类型,就不需要watch了
    prob = tf.nn.softmax(x@w+b, axis=1) #将实数转为概率,得到属于3个节点的概率
    # 计算两个样本损失函数的均方差
    loss = tf.reduce_mean(tf.losses.MSE(tf.one_hot(y,depth=3),prob))
# 求梯度
grads = tape.gradient(loss,[w,b])
grads[0] # loss对w的梯度
grads[1] # loss对b的梯度

3.3 交叉熵

交叉熵(Cross Entropy)主要用于度量两个概率分布间的差异性信息,交叉熵越小,两者之间差异越小,当交叉熵等于0时达到最佳状态,也即是预测值与真实值完全吻合。

公式为: 

梯度损失函数深度学习 梯度下降和损失函数_机器学习_02

式中,p(x)是真实分布的概率,q(x)是模型输出的预测概率。log的底数为2。

(1) 多分类问题交叉熵

tf.losses.categorical_crossentropy(y_true, y_pred, from_logits=False)

y_true: 真实值,需要one-hot编码

y_pred: 预测值,模型输出结果

from_logits: 为True时,会使用softmax函数将y_pred从实数转化为概率值,通常情况下用True结果更稳定

# 多分类
# 真实值和预测概率,预测结果均匀,交叉熵1.38很大
tf.losses.categorical_crossentropy([0,1,0,0],[0.25,0.25,0.25,0.25])
# 预测错了,交叉熵2.3
tf.losses.categorical_crossentropy([0,1,0,0],[0.1,0.1,0.7,0.1])
# 预测对了,交叉熵为0.35
tf.losses.categorical_crossentropy([0,1,0,0],[0.1,0.7,0.1,0.1])

(2) 二分类问题交叉熵

tf.losses.binary_crossentropy()

# 二分类
# 预测对了,0.1
tf.losses.binary_crossentropy([1,0],[0.9,0.1])
# 预测错了,2.3
tf.losses.categorical_crossentropy([1,0],[0.1,0.9])

(3) logits层直接计算交叉熵

模型的输出结果可能不是概率形式,通过softmax函数转换为概率形式,然后计算交叉熵,但有时候会出现数据不稳定的情况,即输出结果是NAN或者inf。

这种情况下可以通过logits层直接计算交叉熵,不过要给categorical_crossentropy()方法传递一个from_logits=True参数。

# 计算网络损失时,一定要加数值稳定处理参数from_logits=True,防止softmax和crossentropy之间的数值不稳定
import tensorflow as tf
x = tf.random.normal([1,784]) # 1张图片784个特征
w = tf.random.normal([784,2]) # 全连接层,分为2类
b = tf.zeros([2]) # 2个偏置

logits = x@w + b # 计算logits层

# 不推荐使用下面这种,会出现数值不稳定
# 输出结果映射到0-1,且概率和为1,(这一步由from_logits=True代替)
# prob = tf.math.softmax(logits,axis=1)
# tf.losses.categorical_crossentropy([[0,1]],prob)

# 计算交叉熵,一定要对y_true进行onehot编码
tf.losses.categorical_crossentropy([[0,1]],logits,from_logits=True)

3.4 交叉熵的梯度

损失函数为计算交叉熵的平均值,一定要对训练集真实值y_true进行one-hot编码,分三类,tf.one_hot(y,depth=3),跟踪权重w和偏置b,grads[0]为以loss为因变量,w为自变量计算梯度。grads[1]为以loss为因变量,b为自变量计算梯度

#(2)交叉熵cross entropy
# softmax,使logits数值最大的所在的所有作为预测结果的label
x = tf.random.normal([2,4]) # 输入层,2张图片,4个特征
w = tf.random.normal([4,3]) # 一层全连接层,输出三个分类
b = tf.zeros([3]) # 三个偏置
y = tf.constant([2,0]) # 第一个样本属于2类别,第2个样本属于0类别
# 梯度计算
with tf.GradientTape() as tape:
    tape.watch([w,b]) # 跟踪w和b的梯度
    logits = x @ w + b # 计算logits层
    # 计算损失,不要先softmax再使用交叉熵,会导致数据不稳定,categorical_crossentropy会自动执行softmax操作再计算损失
    loss = tf.reduce_mean(tf.losses.categorical_crossentropy(tf.one_hot(y,depth=3),logits,from_logits=True))
# 计算损失函数梯度
grads = tape.gradient(loss,[w,b])
grads[0] #shape为[4,3]
grads[1] #shape为[3]


# 结果为grads[0]:
array([[ 0.05101332,  0.08121025, -0.13222364],
       [ 0.1871956 , -0.02524163, -0.16195397],
       [-1.6817021 ,  0.17055441,  1.5111479 ],
       [-0.08085182,  0.06344394,  0.01740783]], dtype=float32)>
# grads[1]:
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.12239906,  0.08427953,  0.03811949], dtype=float32)>