Softmax逻辑回归:scikit-learn与TensorFlow性能差异排查
你的问题很典型——scikit-learn的LogisticRegression做了很多默认的鲁棒优化,而TensorFlow需要手动把控训练细节,这才导致了性能差距。下面是几个核心问题和对应的修复方案:
1. 特征未标准化,梯度下降收敛效率极低
scikit-learn的newton-cg求解器对特征尺度的敏感度较低,但TensorFlow用的梯度下降(GradientDescent)是一阶优化算法,对特征缩放非常敏感。如果你的f1和f2数值范围差异较大,梯度更新会偏向尺度大的特征,导致模型收敛慢甚至无法达到最优解。
修复方案:对特征做标准化处理,比如用scikit-learn的StandardScaler:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() features_scaled = scaler.fit_transform(features)
之后在TensorFlow的feed_dict里传入features_scaled而非原始特征。
2. 全零权重初始化导致训练停滞
你初始化权重W为全零矩阵:
W = tf.Variable(tf.zeros([2, 5]))
这在多分类Softmax模型里是致命问题——所有类别的初始输出完全一致,反向传播时每个权重的梯度也完全相同,模型根本学不到不同类别的区分特征。
修复方案:用小范围随机值初始化权重,比如正态分布或均匀分布:
# 正态分布初始化,均值0,标准差0.01 W = tf.Variable(tf.random.normal([2, 5], mean=0.0, stddev=0.01)) # 或者用均匀分布 W = tf.Variable(tf.random.uniform([2, 5], -0.1, 0.1))
3. 手动计算交叉熵存在数值不稳定问题
你手动实现的交叉熵:
cost = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1))
当pred值接近0时,tf.log(pred)会趋向负无穷,导致数值爆炸,严重干扰训练过程。
修复方案:使用TensorFlow内置的稳定交叉熵函数,它会在计算Softmax之前处理数值问题:
# 先计算logits,不要提前做softmax logits = tf.matmul(x, W) + b pred = tf.nn.softmax(logits) # 用内置函数计算损失 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=logits))
4. 优化器选择和训练轮数不足
梯度下降本身收敛速度很慢,你设置的学习率0.01也未必是最优值,1000轮训练可能不足以让模型收敛到最优解。而scikit-learn的newton-cg是二阶优化算法,收敛速度比一阶梯度下降快得多。
修复方案:
- 改用Adam优化器,它会自适应调整学习率,收敛更稳定:
optimizer = tf.train.AdamOptimizer(0.001).minimize(cost)
- 增加训练轮数,同时监控损失变化,判断是否收敛:
for epoch in range(5000): # 提升训练轮数 _, c = sess.run([optimizer, cost], feed_dict={x: features_scaled, y: labels_one_hot}) # 每500轮打印一次损失,观察收敛情况 if epoch % 500 == 0: print(f"Epoch {epoch}, Training Cost: {c:.4f}")
5. 正则化缺失
你的scikit-learn代码设置了C=1e5(相当于极弱的L2正则化),而TensorFlow模型完全没有正则化,这也会导致模型鲁棒性差异(虽然你是在训练集上评估,但正则化能帮模型更快找到更优的权重分布)。
修复方案:给损失函数加上L2正则化项:
l2_reg = tf.nn.l2_loss(W) # 对权重做L2正则 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=logits) + 0.001 * l2_reg)
这里的0.001是正则化强度,可以根据实际训练情况调整。
整合修改后的TensorFlow代码片段
把上面的修改整合后,你的TensorFlow部分代码大概是这样:
from sklearn.preprocessing import StandardScaler # 标准化特征 scaler = StandardScaler() features_scaled = scaler.fit_transform(features) # tf Graph Input x = tf.placeholder(tf.float32, [None, 2]) # 2 input features y = tf.placeholder(tf.float32, [None, 5]) # 5 output classes # 随机初始化权重 W = tf.Variable(tf.random.normal([2, 5], mean=0.0, stddev=0.01)) b = tf.Variable(tf.zeros([5])) # Construct model logits = tf.matmul(x, W) + b pred = tf.nn.softmax(logits) # Softmax clas = tf.argmax(pred, axis=1) # 稳定的交叉熵损失 + L2正则 l2_reg = tf.nn.l2_loss(W) cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=logits) + 0.001 * l2_reg) # Adam优化器 optimizer = tf.train.AdamOptimizer(0.001).minimize(cost) # Initialize the variables init = tf.global_variables_initializer() # Start training with tf.Session() as sess: sess.run(init) # 增加训练轮数,监控损失 for epoch in range(5000): _, c = sess.run([optimizer, cost], feed_dict={x: features_scaled, y: labels_one_hot}) if epoch % 500 == 0: print(f"Epoch {epoch}, Training Cost: {c:.4f}") # Test model correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) class_out = clas.eval({x: features_scaled}) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) print("\tAccuracy:", accuracy.eval({x: features_scaled, y: labels_one_hot})) print('\tPrecision:', precision_score(labels, class_out, average='macro')) print('\tRecall:', recall_score(labels, class_out, average='macro')) print('\tF1:', f1_score(labels, class_out, average='macro'))
按照这些修改后,你的TensorFlow模型性能应该会接近scikit-learn的结果。
内容的提问来源于stack exchange,提问作者Ben

