快速跳轉位置
文章目錄
-
-
-
- 先來一份代碼, 支援IDE斷點調試
- SoftMax計算公式
- 交叉熵損失
- SoftMax交叉熵損失
- SoftMax交叉熵損失梯度求導
-
-
作為分類任務重最常用的激活函數, SoftMax是如何進行計算, 以及對應的交叉熵損失如何計算?
先來一份代碼, 支援IDE斷點調試
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import time
import argparse
import math
import tensorflow as tf
from tensorflow.keras.utils import Sequence
import numpy as np
"""兩種方式加載的資料集不同圖像部分資料是不同的,
official.mnist: 加載的圖像是uint8資料類型編碼, /255. 需要歸一化
tensorflow.examples.tutorials.mnist 是float類型編碼, 無需歸一化操作
"""
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Model
from tensorflow.keras.layers import MaxPooling2D, Conv2D, Input, Flatten, Dense, Dropout, Layer
# 立即執行模式
tf.enable_eager_execution()
"""
Sequence生成器的方法
__iter__()
__getitem__(index)
支援batch
"""
class DataGenerator(Sequence):
# 自定義資料集加載方式,
# 傳入資料可以是檔案清單或是其他格式,實作相應的加載和預處理方法即可
def __init__(self, x, y, batch_size=32, shuffle=True):
self.batch_size = batch_size
self.x, self.y = x, y
# 索引重排
self.indexes = np.arange(len(self.x))
self.shuffle = shuffle
def __len__(self):
# 計算每一個epoch的疊代次數
return math.ceil(len(self.x) / float(self.batch_size))
def __getitem__(self, index):
# 生成每個batch資料,這裡就根據自己對資料的讀取方式進行發揮了
# 生成batch_size個索引
batch_indexs = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]
# 根據索引擷取資料集合中的資料
x, y = self.data_generation(batch_indexs)
return x, y
def on_epoch_end(self):
# 在每一次epoch結束是否需要進行一次随機,重新随機一下index
if self.shuffle == True:
np.random.shuffle(self.indexes)
def data_generation(self, batch_indexs):
x = self.x[batch_indexs]
y = self.y[batch_indexs]
return x, y
class MySoftMaxLayer(Layer):
def __init__(self, **kwargs):
super(MySoftMaxLayer, self).__init__(**kwargs)
def build(self, input_shape):
# SoftMax層不需要定義任何參數
# 是以這裡不需要定義任何訓練參數
# 一定調用父類方法 在最後!!!
super(MySoftMaxLayer, self).build(input_shape)
def call(self, logits, **kwargs):
# 前向傳播計算
# (200,10) / (200, 1)
# tf.expand_dims 擴充次元
# tf.squeeze 壓縮次元
# return tf.exp(inputs) / tf.expand_dims(tf.reduce_sum(tf.exp(inputs), axis=-1), axis=-1)
# 等同于
return tf.exp(logits) / tf.reduce_sum(tf.exp(logits), axis=-1, keep_dims=True)
def compute_output_shape(self, input_shape):
# 在指定輸入下的輸出
# TensorShape類型
return input_shape
class LeNet(Model):
def __init__(self, input_shape=(28, 28, 1), num_classes=10):
# super(LeNet, self).__init__(name="LeNet")
self.num_classes = num_classes
''' 定義要用到的層 layers '''
# 輸入層
img_input = Input(shape=input_shape)
# Conv => ReLu => Pool
x = Conv2D(filters=20, kernel_size=5, padding="same", activation="relu", name='block1_conv1')(img_input)
x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block1_pool')(x)
# Conv => ReLu => Pool
x = Conv2D(filters=50, kernel_size=5, padding="same", activation="relu", name='block1_conv2')(x)
x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='block1_poo2')(x)
# 壓成一維
x = Flatten(name='flatten')(x)
# 全連接配接層
x = Dense(units=500, activation="relu", name="f1")(x)
# softmax分類器
# x = Dense(units=num_classes, activation="softmax", name="prediction")(x)
x = Dense(units=num_classes)(x)
x = MySoftMaxLayer(name="my_softmax_layer")(x)
# 調用Model類的Model(input, output, name="***")構造方法
super(LeNet, self).__init__(img_input, x, name="LeNet")
def my_softmax_cross_entropy_with_logits(labels, logits):
# L = -sum(Yj*ln(Pj) = - ln(Pj)
# Yj 中取值為 [0,1]
# 标準的交叉熵損失為E = -sum(Yi * ln(Pi)
return -tf.log(tf.reduce_sum(labels*logits, axis=1))
# 自定義損失函數
def loss(logits, labels):
return tf.reduce_mean(my_softmax_cross_entropy_with_logits(labels, logits))
# softmax_cross_entropy_with_logits 為每一個輸入項結果計算一個損失, 傳回值arrayList, 長度N=Batch
# reduce_mean 再求均值
# return tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=labels, logits=logits))
# 自定義評估函數
def compute_accuracy(logits, labels):
predictions = tf.argmax(input=logits, axis=1, output_type=tf.int64)
labels = tf.argmax(input=labels, axis=1, output_type=tf.int64) # tf.cast(x=labels, dtype=tf.int64)
batch_size = int(logits.shape[0])
return tf.reduce_sum(tf.cast(tf.equal(predictions, labels), dtype=tf.float32)) / batch_size
def run_mnist_eager(cfg):
# 自動選擇裝置
(device, data_format) = ('/gpu:0', 'channels_last')
if not tf.test.is_gpu_available():
(device, data_format) = ('/cpu:0', 'channels_first')
print('Using device %s, and data format %s.' % (device, data_format))
# 載入資料集
train_ds, test_ds = load_mnist() # shape = (?, 768) / (?)
# 建立 model and optimizer
model = LeNet()
optimizer = tf.train.MomentumOptimizer(cfg.lr, cfg.momentum)
model.compile(optimizer=optimizer,
loss=loss, # 'categorical_crossentropy',
metrics=[compute_accuracy]) # ['accuracy']
# Generator 使用生成器方式提供資料,支援eager模式
train_ds = DataGenerator(train_ds[0], train_ds[1], batch_size=200)
test_ds = DataGenerator(test_ds[0], test_ds[1], batch_size=200)
# Train and evaluate for a set number of epochs.
with tf.device(device): # 使用GPU必須有此一句
for _ in range(cfg.train_epochs):
start = time.time()
model.fit_generator(generator=train_ds, epochs=1)
# model.fit(train_ds[0], train_ds[1], batch_size=200, epochs=1)
# verbose=0 不顯示
# 生成器
# _loss, _acc = model.evaluate(test_ds[0], test_ds[1], batch_size=100, verbose=0)
# _loss, _acc = model.evaluate_generator(generator=test_ds, steps=10000)
#
_loss, _acc = model.evaluate_generator(generator=test_ds, verbose=1)
print("test dataset loss: %f acc: %f" % (_loss, _acc))
# train(model, optimizer, train_ds, step_counter, cfg.log_interval)
end = time.time()
print('\nTrain time for epoch #%d (%d total steps): %f' %
(_, len(train_ds), end - start))
def arg_parse():
"""參數定義"""
parser = argparse.ArgumentParser(description="Lenet-5 MNIST 模型")
parser.add_argument("--lr", dest="lr", help="學習率", default=0.01, type=float)
parser.add_argument("--momentum", dest="momentum", help="SGD momentum.", default=0.5)
parser.add_argument("--data_dir", dest="data_dir", help="資料集下載下傳/儲存目錄", default="data/mnist/input_data/")
parser.add_argument("--model_dir", dest="model_dir", help="模型儲存目錄", default="data/mnist/checkpoints/")
parser.add_argument("--batch_size", dest="batch_size", help="訓練或測試時 Batch Size", default=100, type=int)
parser.add_argument("--train_epochs", dest="train_epochs", help="訓練時epoch疊代次數", default=4, type=int)
parser.add_argument("--log_interval", dest="log_interval", help="日志列印間隔", default=10, type=int)
# 傳回轉換好的結果
return parser.parse_args()
def load_mnist():
# 加載資料,轉換編碼格式并歸一化
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.astype("float32") / 255.
x_test = x_test.astype("float32") / 255.
# 擴充1維, 等效寫法
x_train = x_train[:, :, :, None]
x_test = x_test[:, :, :, None]
print(x_train.shape, "train shape")
print(x_test.shape, "test shape")
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
train_ds = (x_train, y_train)
test_ds = (x_test, y_test)
return train_ds, test_ds
if __name__ == '__main__':
args = arg_parse()
run_mnist_eager(args)
其中: 我們關注的核心的是
my_softmax_cross_entropy_with_logits
函數和
MySoftMaxLayer
層
SoftMax計算公式
對于次元
(T,1)
的輸入, SoftMax的計算
第j類
機率(
0-1之間的一個浮點值
)公式
S j = e a j ∑ k = 1 T e a k {S_j} = \frac{{{e^{{a_j}}}}}{{\sum\limits_{k = 1}^T {{e^{{a_k}}}} }} Sj=k=1∑Teakeaj
MySoftMaxLayer
層
build
函數中是批量輸入計算
softmax
分類實作, 最核心的代碼如下
return tf.exp(logits) / tf.reduce_sum(tf.exp(logits), axis=-1, keep_dims=True)
交叉熵損失
對于标準的交叉熵損失, 可以用下面的公式去表示
L = − ∑ j = 1 T Y j ln P j L = - \sum\limits_{j = 1}^T {{Y_j}\ln {P_j}} L=−j=1∑TYjlnPj
其中
Pj
表示屬于第j類的機率,
Yj
表示第j類對應的參數值
SoftMax交叉熵損失
SoftMax的交叉熵損失标準形式如下,
L = − ∑ j = 1 T Y j ln P j L = - \sum\limits_{j = 1}^T {{Y_j}\ln{P_j}} L=−j=1∑TYjlnPj
其中
Y
是一組多個0和一個1組成的
one-hot編碼
,
P
是預測屬于每個類别的機率, 因為
Y
隻有一個位置處為1,假設為
j
, 是以,公式可以寫成如下形式
L = − ln P j L = - \ln {P_j} L=−lnPj
核心代碼如下(批輸入損失計算)
def my_softmax_cross_entropy_with_logits(labels, logits):
# L = -sum(Yj*Ln(Pj) = - Ln(Pj)
# Yj 中取值為 [0,1]
# 标準的交叉熵損失為E = -sum(Yi * ln(Pi)
return -tf.log(tf.reduce_sum(labels*logits, axis=1))
SoftMax交叉熵損失梯度求導
轉載自: https://blog.csdn.net/qian99/article/details/78046329
首先,我們要明确一下我們要求什麼,我們要求的是我們的loss對于神經元輸出(zi)的梯度,即:
∂ C ∂ z i \frac{{\partial C}}{{\partial {z_i}}} ∂zi∂C
根據複合函數求導法則:
∂ C ∂ z i = ∂ C ∂ s j ∂ s j ∂ z i \frac{{\partial C}}{{\partial {z_i}}} = \frac{{\partial C}}{{\partial {s_j}}} \frac{{\partial {s_j}}}{{\partial {z_i}}} ∂zi∂C=∂sj∂C∂zi∂sj
有個人可能有疑問了,這裡為什麼是 s j \ {{s_j}} sj而不是 s i \ {{s_i}} si(s為經過softmax運算後的結果, 即歸屬每一類的機率),這裡要看一下softmax的公式了,因為
softmax
公式的特性,它的分母包含了所有神經元的輸出,是以,對于不等于i的
其他輸出
裡面,也包含着 z i \ {{z_i}} zi,所有的s都要納入到計算範圍中,并且後面的計算可以看到需要分為
i=j
和
i≠j
兩種情況求導。
下面我們一個一個推:
∂ C ∂ s j = ∂ ( − ∑ j y j ln s j ) ∂ s j = − ∑ j y j 1 s j \frac{{\partial C}}{{\partial {s_j}}} = \frac{{\partial ( - \sum\limits_j {{y_j}\ln } {s_j})}}{{\partial {s_j}}} = - \sum\limits_j {{y_j}\frac{1}{{{s_j}}}} ∂sj∂C=∂sj∂(−j∑yjlnsj)=−j∑yjsj1
第二部分稍微複雜一點,我們先把它分為兩種情況:
①如果i=j:
∂ s i ∂ z i = ∂ ( e z i ∑ k e z k ) ∂ z i = ∑ k e z k e z i − ( e z i ) 2 ( ∑ k e z k ) 2 = ( e z i ∑ k e z k ) ( 1 − e z i ∑ k e z k ) = s i ( 1 − s i ) {{\partial \mathop {\rm{s}}\nolimits_{\rm{i}} } \over {\partial \mathop z\nolimits_i }} = {{\partial ({{\mathop e\nolimits^{\mathop z\nolimits_i } } \over {\sum\nolimits_k {\mathop e\nolimits^{\mathop z\nolimits_k } } }})} \over {\partial \mathop z\nolimits_i }} = {{\sum\nolimits_k {\mathop e\nolimits^{\mathop z\nolimits_k } } \mathop e\nolimits^{\mathop z\nolimits_i } - \mathop {(\mathop e\nolimits^{\mathop z\nolimits_i } )}\nolimits^2 } \over {\mathop {(\sum\nolimits_k {\mathop e\nolimits^{\mathop z\nolimits_k } } )}\nolimits^2 }} = ({{\mathop e\nolimits^{\mathop z\nolimits_i } } \over {\sum\nolimits_k {\mathop e\nolimits^{\mathop z\nolimits_k } } }})(1 - {{\mathop e\nolimits^{\mathop z\nolimits_i } } \over {\sum\nolimits_k {\mathop e\nolimits^{\mathop z\nolimits_k } } }}) = \mathop s\nolimits_i (1 - \mathop s\nolimits_i ) ∂zi∂si=∂zi∂(∑kezkezi)=(∑kezk)2∑kezkezi−(ezi)2=(∑kezkezi)(1−∑kezkezi)=si(1−si)
②如果i≠j:
∂ s j ∂ z i = ∂ ( e z j ∑ k e z k ) ∂ z i = − z j ( 1 ∑ k e z k ) 2 e z i = − s i s j {{\partial {{\rm{s}}_j}} \over {\partial {\rm{ }}{z_i}}} = {{\partial ({{{\rm{ }}{e^{{\rm{ }}\mathop z\nolimits_j }}} \over {\sum\nolimits_k {{\rm{ }}{e^{{\rm{ }}\mathop z\nolimits_k }}} }})} \over {\partial {\rm{ }}{z_i}}} = - {\rm{ }}{z_j}{({1 \over {\sum\nolimits_k {{\rm{ }}{e^{{\rm{ }}\mathop z\nolimits_k }}} }})^2}{\rm{ }}{e^{{z_i}}} = - {\rm{ }}{s_i}{\rm{ }}{s_j} ∂zi∂sj=∂zi∂(∑kezkezj)=−zj(∑kezk1)2ezi=−sisj
ok,接下來我們隻需要把上面的組合起來:
∂ C ∂ z i = ( − ∑ j y j 1 s j ) ∂ s i ∂ z i = − y i s i s i ( 1 − s i ) + ∑ j ≠ i y i s j s i s j = − y i + y i s i + ∑ j ≠ i y j s i = − y i + s i ∑ j y j \frac{{\partial C}}{{\partial {z_i}}} = ( - \sum\limits_j {{y_j}\frac{1}{{{s_j}}}} )\frac{{\partial {s_i}}}{{\partial {z_i}}} = - \frac{{{y_i}}}{{{s_i}}}{s_i}(1 - {s_i}) + \sum\limits_{j \ne i} {\frac{{{y_i}}}{{{s_j}}}} {s_i}{s_j} = - {y_i} + {y_i}{s_i} + \sum\limits_{j \ne i} {{y_j}{s_i} = - } {y_i} + {s_i}\sum\limits_j {{y_j}} ∂zi∂C=(−j∑yjsj1)∂zi∂si=−siyisi(1−si)+j̸=i∑sjyisisj=−yi+yisi+j̸=i∑yjsi=−yi+sij∑yj
最後的結果看起來簡單了很多,最後,針對分類問題,我們給定的結果 y i \ {{y_i}} yi最終隻會有一個類别是1,其他類别都是0,是以,對于分類問題,這個梯度等于:
∂ C ∂ z i = s i − y i \frac{{\partial C}}{{\partial {z_i}}} = {{ s_i}} - {{y_i}} ∂zi∂C=si−yi