Python keras.metrics源代碼分析

前言

metrics用於判斷模型性能。度量函數類似於損失函數,隻是度量的結果不用於訓練模型。可以使用任何損失函數作為度量(如logloss等)。在訓練期間監控metrics的最佳方式是通過Tensorboard。

官方提供的metrics最重要的概念就是有狀態(stateful)變量,通過更新狀態變量,可以不斷累積統計數據,並可以隨時輸出狀態變量的計算結果。這是區別於losses的重要特性,losses是無狀態的(stateless)。

本文部分內容參考瞭:

Keras-Metrics官方文檔

代碼運行環境為:tf.__version__==2.6.2 。

metrics原理解析(以metrics.Mean為例)

metrics是有狀態的(stateful),即Metric 實例會存儲、記錄和返回已經累積的結果,有助於未來事務的信息。下面以tf.keras.metrics.Mean()為例進行解釋:

創建tf.keras.metrics.Mean的實例:

m = tf.keras.metrics.Mean()

通過help(m) 可以看到MRO為:

Mean
Reduce
Metric
keras.engine.base_layer.Layer

可見Metric和Mean是 keras.layers.Layer 的子類。相比於類Layer,其子類Mean多出瞭幾個方法:

  • result: 計算並返回標量度量值(tensor形式)或標量字典,即狀態變量簡單地計算度量值。例如,m.result(),就是計算均值並返回。
  • total: 狀態變量m目前累積的數字總和
  • count: 狀態變量m目前累積的數字個數(m.total/m.count就是m.result()的返回值)
  • update_state: 累積統計數字用於計算指標。每次調用m.update_state都會更新m.totalm.count
  • reset_state: 將狀態變量重置到初始化狀態;
  • reset_states: 等價於reset_state,參見keras源代碼metrics.py L355
  • reduction: 目前來看,沒什麼用。

這也決定瞭Mean的特殊性質。其使用參見如下代碼:

# 創建狀態變量m,由於m未剛初始化,
# 所以total,count和result()均為0
m = tf.keras.metrics.Mean()
print("m.total:",m.total)
print("m.count:",m.count)
print("m.result():",m.result())

"""
# 輸出:
m.total: <tf.Variable 'total:0' shape=() dtype=float32, numpy=0.0>
m.count: <tf.Variable 'count:0' shape=() dtype=float32, numpy=0.0>
m.result(): tf.Tensor(0.0, shape=(), dtype=float32)
"""

# 更新狀態變量,可以看到total累加瞭總和,
# count累積瞭個數,result()返回total/count
m.update_state([1,2,3])
print("m.total:",m.total)
print("m.count:",m.count)
print("m.result():",m.result())

"""
# 輸出:
m.total: <tf.Variable 'total:0' shape=() dtype=float32, numpy=6.0>
m.count: <tf.Variable 'count:0' shape=() dtype=float32, numpy=3.0>
m.result(): tf.Tensor(2.0, shape=(), dtype=float32)
"""

# 重置狀態變量, 重置到初始化狀態
m.reset_state()
print("m.total:",m.total)
print("m.count:",m.count)
print("m.result():",m.result())

"""
# 輸出:
m.total: <tf.Variable 'total:0' shape=() dtype=float32, numpy=0.0>
m.count: <tf.Variable 'count:0' shape=() dtype=float32, numpy=0.0>
m.result(): tf.Tensor(0.0, shape=(), dtype=float32)
"""

創建自定義metrics

創建無狀態 metrics

與損失函數類似,任何帶有類似於metric_fn(y_true, y_pred)、返回損失數組(如輸入一個batch的數據,會返回一個batch的損失標量)的函數,都可以作為metric傳遞給compile()

import tensorflow as tf
import numpy as np
inputs = tf.keras.Input(shape=(3,))
x = tf.keras.layers.Dense(4, activation=tf.nn.relu)(inputs)
outputs = tf.keras.layers.Dense(1, activation=tf.nn.softmax)(x)
model1 = tf.keras.Model(inputs=inputs, outputs=outputs)
def my_metric_fn(y_true, y_pred):
    squared_difference = tf.square(y_true - y_pred)
    return tf.reduce_mean(squared_difference, axis=-1) # shape=(None,)
model1.compile(optimizer='adam', loss='mse', metrics=[my_metric_fn])
x = np.random.random((100, 3))
y = np.random.random((100, 1))
model1.fit(x, y, epochs=3)

輸出:

Epoch 1/3
4/4 [==============================] – 0s 667us/step – loss: 0.0971 – my_metric_fn: 0.0971
Epoch 2/3
4/4 [==============================] – 0s 667us/step – loss: 0.0958 – my_metric_fn: 0.0958
Epoch 3/3
4/4 [==============================] – 0s 1ms/step – loss: 0.0946 – my_metric_fn: 0.0946

註意,因為本例創建的是無狀態的度量,所以上面跟蹤的度量值(my_metric_fn後面的值)是每個batch的平均度量值,並不是一個epoch(完整數據集)的累積值。(這一點需要理解,這也是為什麼要使用有狀態度量的原因!)

值得一提的是,如果上述代碼使用

model1.compile(optimizer='adam', loss='mse', metrics=["mse"])

進行compile,則輸出的結果是累積的,在每個epoch結束時的結果就是整個數據集的結果,因為metrics=["mse"]是直接調用瞭標準庫的有狀態度量。

通過繼承Metric創建有狀態metrics

如果想查看整個數據集的指標,就需要傳入有狀態的metrics,這樣就會在一個epoch內累加,並在epoch結束時輸出整個數據集的度量值。

創建有狀態度量指標,需要創建Metric的子類,它可以跨batch維護狀態,步驟如下:

  • __init__中創建狀態變量(state variables)
  • 更新update_state()y_truey_pred的變量
  • result()中返回標量度量結果
  • reset_states()中清除狀態
class BinaryTruePositives(tf.keras.metrics.Metric):
    def __init__(self, name='binary_true_positives', **kwargs):
        super(BinaryTruePositives, self).__init__(name=name, **kwargs)
        self.true_positives = self.add_weight(name='tp', initializer='zeros')
    def update_state(self, y_true, y_pred, sample_weight=None):
        y_true = tf.cast(y_true, tf.bool)
        y_pred = tf.cast(y_pred, tf.bool)
        values = tf.logical_and(tf.equal(y_true, True), tf.equal(y_pred, True))
        values = tf.cast(values, self.dtype)
        if sample_weight is not None:
            sample_weight = tf.cast(sample_weight, self.dtype)
            values = tf.multiply(values, sample_weight)
        self.true_positives.assign_add(tf.reduce_sum(values))
    def result(self):
        return self.true_positives
    def reset_states(self):
        self.true_positives.assign(0)
m = BinaryTruePositives()
m.update_state([0, 1, 1, 1], [0, 1, 0, 0])
print('Intermediate result:', float(m.result()))
m.update_state([1, 1, 1, 1], [0, 1, 1, 0])
print('Final result:', float(m.result()))

add_metric()方法

add_metric 方法是 tf.keras.layers.Layer類添加的方法,Layer的父類tf.Module並沒有這個方法,因此在編寫Layer子類如包括自定義層、官方提供的層(Dense)或模型(tf.keras.Model也是Layer的子類)時,可以使用add_metric()來與層相關的統計量。比如,將類似Dense的自定義層的激活平均值記錄為metric。可以執行以下操作:

class DenseLike(Layer):
    """y = w.x + b"""
    ...
    def call(self, inputs):
        output = tf.matmul(inputs, self.w) + self.b
        self.add_metric(tf.reduce_mean(output), aggregation='mean', name='activation_mean')
        return output

將在名稱為activation_mean的度量下跟蹤output,跟蹤的值為每個批次度量值的平均值。

更詳細的信息,參閱官方文檔The base Layer class – add_metric method。

參考

Keras-Metrics官方文檔

到此這篇關於Python keras.metrics源代碼分析的文章就介紹到這瞭,更多相關Python keras.metrics內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: