为何我的神经网络始终输出相同预测结果?技术求助
我来帮你梳理下可能的问题点和解决办法,你遇到的这种“模型只输出平均情况”的问题在多标签分类任务里挺常见的,咱们一步步排查改进:
1. 先修正网络结构里的激活函数错误
你代码里犯了一个小但关键的错误:在Dense层指定了activation='relu'之后,又叠加了LeakyReLU层。这相当于让数据先经过ReLU把所有负数变成0,再经过LeakyReLU——而LeakyReLU对0的处理还是0,等于完全浪费了LeakyReLU解决“神经元死亡”的作用,反而让模型更容易陷入梯度消失。
修正后的网络结构应该是去掉Dense层里的activation参数,只保留LeakyReLU:
model = Sequential() model.add(Dense(150, input_dim=9600)) # 去掉activation='relu' model.add(LeakyReLU(alpha=.01)) model.add(Dense(50)) # 同样去掉activation='relu' model.add(LeakyReLU(alpha=.01)) model.add(Dense(12, activation='sigmoid'))
2. 更换适合多标签分类的损失函数和评估指标
你现在用的mean_squared_error是回归任务的损失函数,而你的任务是12个非互斥的二分类任务(每个输出对应一个“是否满足阈值”的判断),MSE会让模型趋向于输出训练数据的均值(也就是你看到的固定值),因为平方损失会惩罚偏离均值的预测,而不平衡的数据会让均值偏向多数类。
正确的选择是binary_crossentropy,这是专门针对多标签二分类的损失函数。同时评估指标别用普通的accuracy,改用binary_accuracy(每个标签单独计算准确率,更适合多标签场景):
from tensorflow.keras.optimizers import Adam metrics_to_output = ['binary_accuracy'] model.compile(loss='binary_crossentropy', optimizer=Adam(), metrics=metrics_to_output)
3. 解决数据极度不平衡的问题
从你描述的输出结果(固定0 1 1 0 0 0 0 0 0 0 0 0)来看,训练数据里第2、3位的正样本(值为1)占比极高,其他标签的正样本极少。模型为了最小化损失,自然会一直预测最常见的组合。
解决办法可以用类别权重,给少数类的错误预测更高的惩罚:
import numpy as np # 计算每个标签的类别权重:负样本数/正样本数 class_weights = {} for label_idx in range(12): positive_count = np.sum(output[:, label_idx] == 1) negative_count = len(output) - positive_count # 避免除以0的情况 class_weights[label_idx] = negative_count / positive_count if positive_count != 0 else 1.0
然后在训练时传入这个权重:
model.fit(x=input, y=output, batch_size=32, epochs=20, class_weight=class_weights)
4. 必须对输入数据做归一化
你的输入是9600维的高维数据,如果没有做归一化,不同维度的数值范围差异会极大干扰模型的梯度更新,导致模型无法学习到有效特征,只能输出平均情况。
用标准化把输入数据调整到均值为0、方差为1的分布:
from sklearn.preprocessing import StandardScaler # 注意:只在训练数据上拟合scaler,再用它转换训练和测试数据 scaler = StandardScaler() input_normalized = scaler.fit_transform(input) # 训练时用归一化后的输入 model.fit(x=input_normalized, y=output, batch_size=32, epochs=20, class_weight=class_weights)
5. 调整训练策略
- 增加训练轮数:你现在只训练了1个epoch,模型根本没机会学习到有效特征,至少训练20-50个epoch,同时观察训练损失和验证损失的变化,避免过拟合。
- 加入验证集:用
validation_split=0.1留出10%的数据做验证,看模型在 unseen 数据上的表现,判断是否过拟合或欠拟合。 - 调整学习率:如果训练时损失下降很慢,可以试试把Adam的学习率调小(比如
optimizer=Adam(learning_rate=0.0001)),避免梯度震荡。
关于你提到的softsign替换sigmoid
其实sigmoid本身是多标签分类的标准激活函数,问题不在激活函数上,而是前面的损失函数、数据处理和网络结构问题。如果你想尝试softsign也可以,但记得同步调整损失函数和目标值的范围(softsign输出是[-1,1],所以目标值也要对应调整),不过优先解决上面的几个核心问题会更有效。
内容的提问来源于stack exchange,提问作者Mack

