You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

keras.MeanSquaredError与自定义reduce_sum(square(diff))损失差异问题

Keras原生MSE与自定义MSE训练效果差异排查

问题现象

花费数小时排查以下问题:直接使用keras.MSE可让程序正常运行,但自定义的MSE实现效果偏差很大。原本认为差值平方的均值计算结果应该和keras.MSE非常接近,实际运行时两者初始值差距不大,但后续偏差越来越大,运行日志如下:

step=0 theirs= 13.1761 mine= 14.0251
step=5 theirs= 10.3337 mine= 11.8363
…
step=90 theirs=  0.0361 mine=  6.9888
step=95 theirs=  0.0332 mine=  6.9604

初步排查

翻查Keras和TensorFlow源码,发现keras/losses.py中MSE的核心实现是backend.mean(tf.math.squared_difference(y_pred, y_true), axis=-1),和自定义的tf.reduce_mean(tf.square(y_true-y_pred))逻辑几乎一致,在ipython中测试两者结果也相近,但训练时差异明显,测试代码如下:

import tensorflow as tf
import numpy as np

def small_ds():
    in_t = tf.cast(np.random.randint(5, size=(24, 2)), tf.float32)
    out_t = tf.reduce_sum(in_t, axis=-1)
    return in_t, out_t

def small_model():
    i = tf.keras.layers.Input(shape=(2,))
    d = i
    d = tf.keras.layers.Dense(32, activation="LeakyReLU")(d)
    d = tf.keras.layers.Dense(32, activation="LeakyReLU")(d)
    d = tf.keras.layers.Dense(32, activation="LeakyReLU")(d)
    o = tf.keras.layers.Dense(1, activation="LeakyReLU")(d)
    m = tf.keras.Model(inputs=i, outputs=o)
    return m

def what_is_happening_here():
    opt = tf.keras.optimizers.Adam()
    tf_mse = tf.keras.losses.MeanSquaredError()

    @tf.function
    def my_mse(y_true, y_pred):
        return tf.reduce_mean(tf.square(y_true-y_pred))

    m = small_model()

    @tf.function
    def train_step(x_input, y_true):
        with tf.GradientTape() as tape:
            y_pred = m(x_input, training=True)
            theirs = tf_mse(y_true, y_pred)
            mine   = my_mse(y_true, y_pred)
        grad = tape.gradient(theirs, m.trainable_variables)
        opt.apply_gradients(zip(grad, m.trainable_variables))
        return theirs, mine

    x_input, y_true = small_ds()
    for step in range(100):
        theirs, mine = train_step(x_input, y_true)
        if (step % 5) == 0:
            print(f'step={step} theirs={theirs:8.4f} mine={mine:8.4f}')

if __name__ == '__main__':
    what_is_happening_here()

补充测试

最初被第一个回答说服,但后续测试发现完全随机向量测试下,有无归约操作时两者结果完全一致,但放到训练循环中还是会出现异常的非线性差异。怀疑是优化器或计算图有未注意到的逻辑,由于后续需要自定义损失函数,所以需要搞清楚这个问题,也试过把自定义损失继承tf.keras.losses.Loss类,结果还是异常,测试代码如下:

In [22]: tf_mse = tf.keras.losses.MeanSquaredError(reduction=tf.keras.losses.Reduction.NONE)
    ...: my_mse = lambda x,y: tf.reduce_mean(tf.square(x-y), axis=-1)
    ...:
    ...: tf_mser = tf.keras.losses.MeanSquaredError()
    ...: my_mser = lambda x,y: tf.reduce_mean(my_mse(x,y))
    ...:
    ...: y_true = tf.cast(np.random.randint(10, size=(6,1)), tf.float32)
    ...: y_pred = tf.cast(np.random.randint(10, size=(6,1)), tf.float32)
    ...:
    ...: i = tf.keras.layers.Input(shape=(1,))
    ...: o = tf.keras.layers.Dense(32)(i)
    ...: m = tf.keras.Model(inputs=i, outputs=o)
    ...:
    ...: m_pred = m(y_pred)
    ...:
    ...: for a,b in [(tf_mse, tf_mser), (my_mse, my_mser)]:
    ...:     print(f'{a(y_true, y_pred).numpy()} -> {b(y_true, y_pred)}')
    ...:
    ...: for a,b in [(tf_mse, tf_mser), (my_mse, my_mser)]:
    ...:     print(f'{a(y_true, m_pred).numpy()} -> {b(y_true, m_pred)}')
[ 9.  4.  4.  9. 16.  4.] -> 7.666666507720947
[ 9.  4.  4.  9. 16.  4.] -> 7.666666507720947
[20.47 30.15 19.62  9.   12.8  19.62] -> 18.608726501464844
[20.47 30.15 19.62  9.   12.8  19.62] -> 18.608726501464844

根因与解决方案

最终找到问题原因,修改small_ds的实现后结果完全一致,修改后的代码如下:

def small_ds():
    in_t = tf.cast(np.random.randint(5, size=(24, 2)), tf.float32)
    out_t = tf.expand_dims(tf.reduce_sum(in_t, axis=-1), -1)
    return in_t, out_t

问题本质是原生MSE会先对最后一个通道(axis=-1)做均值,如果y_true的shape是(24,),不管归约设置是什么都会按此逻辑计算,给out_t添加expand_dims调整维度后,自定义MSE就和原生实现效果完全一致了。


内容的提问来源于stack exchange,提问作者jettero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 22:24:05