使用神经网络解决二分类问题:从回归到分类

使用神经网络解决二分类问题:从回归到分类

使用神经网络解决二分类问题:从回归到分类

在前面的神经网络学习中,我们通常用模型解决回归问题,例如预测房价、销量、温度等连续数值。现在要进入另一个非常常见的机器学习任务:分类问题

分类任务与回归任务在网络结构上有很多相似之处,例如仍然可以使用全连接层、激活函数、优化器和早停机制。但二者最大的区别在于:

  • 输出层希望得到的结果不同
  • 损失函数不同
  • 评估指标不同

本文以二分类 Binary Classification为例,介绍神经网络如何完成分类任务。


1. 什么是二分类问题?

二分类指的是将样本划分到两个类别之一。

常见例子包括:

  • 判断客户是否会购买商品
  • 判断信用卡交易是否为欺诈
  • 判断雷达信号是否探测到目标
  • 判断医学检测结果是否显示患病
  • 判断酒店订单是否会被取消

这些问题的共同点是:结果只有两种可能。

例如:

购买 / 不购买 欺诈 / 正常 有病 / 无病 取消 / 不取消 猫 / 狗

在原始数据中,类别可能是字符串,例如:

Yes / No Dog / Cat good / bad

但神经网络不能直接处理这些文本标签,因此需要将它们转换成数字标签。

例如:

df['Class']=df['Class'].map({'good':0,'bad':1})

也就是:

good -> 0 bad -> 1

这样模型才能学习输入特征与类别标签之间的关系。


2. 分类问题中的准确率 Accuracy

在分类任务中,最直观的评估指标是准确率 Accuracy

准确率定义为:

accuracy = 预测正确的样本数 / 总样本数

如果模型全部预测正确,则准确率为:

accuracy = 1.0

例如有 100 个样本,模型预测对了 88 个,那么准确率就是:

accuracy = 88 / 100 = 0.88

也就是 88%。

准确率适合用在类别比较均衡的数据集中。例如正样本和负样本数量差不多时,准确率通常是一个比较合理的指标。

但是,如果类别极度不均衡,准确率可能会产生误导。

例如,某疾病检测数据中:

99% 的人没有病 1% 的人有病

如果模型永远预测“没有病”,它也能达到 99% 的准确率,但这个模型显然没有实际价值。

所以在实际项目中,除了准确率,还经常关注:

  • Precision 精确率
  • Recall 召回率
  • F1-score
  • ROC-AUC
  • PR-AUC
  • 混淆矩阵 Confusion Matrix

3. 为什么不能直接用准确率作为损失函数?

训练神经网络时,需要一个损失函数 Loss Function来告诉模型当前预测有多差。

在回归问题中,我们常使用:

  • MAE:平均绝对误差
  • MSE:均方误差

这些损失函数是连续、平滑变化的,适合梯度下降算法优化。

但准确率不适合作为损失函数。

原因是准确率是一个“计数比例”,它的变化是不连续的。

例如模型输出概率从:

0.51 -> 0.99

如果真实标签是 1,这两个预测最终都被判定为类别 1,因此准确率没有变化。

但显然,0.99 比 0.51 更有信心,也更接近理想预测。

反过来,如果模型输出从:

0.49 -> 0.51

只变化了一点点,但分类结果却从 0 变成了 1,准确率发生突变。

这种“不平滑”的特性不适合梯度下降算法。因此,分类任务通常使用另一种损失函数:交叉熵 Cross-Entropy


4. 交叉熵 Cross-Entropy

对于分类任务,我们希望模型输出的是某个类别的概率。

例如对于二分类问题,模型可以输出:

0.8

表示模型认为该样本属于类别 1 的概率是 80%。

如果真实标签是 1,那么这个预测比较好。

如果真实标签是 0,那么这个预测就很差。

交叉熵的核心思想是:

当模型给正确类别分配的概率越高,损失越小;
当模型给正确类别分配的概率越低,损失越大。

对于二分类问题,常用损失函数是:

binary_crossentropy

其数学形式可以写成:

Loss=−[y⋅log⁡(p)+(1−y)⋅log⁡(1−p)] \text{Loss} = -\big[ y \cdot \log(p) + (1 - y) \cdot \log(1 - p) \big]Loss=[ylog(p)+(1y)log(1p)]

其中:

  • yyy是真实标签,取值为 0 或 1
  • ppp是模型预测为类别 1 的概率
  • log⁡\loglog是对数函数(通常指自然对数)

如果真实标签是 1,希望ppp越接近 1 越好。

如果真实标签是 0,希望ppp越接近 0 越好。

举个例子,真实标签为 1:

预测概率 p = 0.99 -> 损失很小 预测概率 p = 0.60 -> 损失较大 预测概率 p = 0.01 -> 损失非常大

这就很好地惩罚了“自信但错误”的预测。


5. Sigmoid 函数:把输出变成概率

普通神经网络的 Dense 层输出可以是任意实数,例如:

-10, -2.5, 0, 3.7, 20

但二分类任务需要的是概率,也就是范围在:

[0, 1]

之间的数。

因此,在二分类模型的最后一层,通常使用sigmoid 激活函数

Sigmoid 函数可以将任意实数映射到 0 到 1 之间:

sigmoid(x)=11+e−x \text{sigmoid}(x) = \frac{1}{1 + e^{-x}}sigmoid(x)=1+ex1

它的特点是:

  • 输入xxx很大时,输出接近 1
  • 输入xxx很小时,输出接近 0
  • 输入xxx为 0 时,输出为 0.5

因此它非常适合用来表示二分类概率。

例如:

layers.Dense(1,activation='sigmoid')

这里的含义是:

  • 输出层只有 1 个神经元
  • 该神经元输出类别 1 的概率
  • 使用 sigmoid 将输出限制在 0 到 1 之间

6. 如何从概率得到最终类别?

模型输出的是概率,而不是直接输出类别。

例如:

0.82

表示模型认为样本属于类别 1 的概率是 82%。

通常会设置一个阈值 threshold,例如:

0.5

判断规则为:

预测概率 < 0.5 -> 类别 0 预测概率 >= 0.5 -> 类别 1

例如:

0.12 -> 0 0.48 -> 0 0.51 -> 1 0.93 -> 1

Keras 中的binary_accuracy默认也使用 0.5 作为分类阈值。

不过在实际业务中,阈值不一定非要是 0.5。

例如在医学检测、欺诈检测等场景中,漏判代价很高,可能会降低阈值来提高召回率。


7. 示例:Ionosphere 数据集二分类

Ionosphere 数据集包含来自雷达信号的特征,任务是判断信号是否显示存在某种物体,还是只是空信号。

原始数据中的类别是:

good bad

需要先映射为数字标签:

df['Class']=df['Class'].map({'good':0,'bad':1})

8. 数据划分与归一化

示例中将数据划分为训练集和验证集:

df_train=df.sample(frac=0.7,random_state=0)df_valid=df.drop(df_train.index)

含义是:

  • 70% 数据作为训练集
  • 剩余 30% 数据作为验证集
  • random_state=0保证结果可复现

然后进行归一化:

max_=df_train.max(axis=0)min_=df_train.min(axis=0)df_train=(df_train-min_)/(max_-min_)df_valid=(df_valid-min_)/(max_-min_)

归一化后,特征值被缩放到大致 0 到 1 之间。

这样做的好处是:

  • 加快模型收敛
  • 避免某些数值范围过大的特征主导训练
  • 提高梯度下降的稳定性

需要注意的是,验证集归一化时使用的是训练集的min_max_,而不是验证集自己的统计值。

这是为了避免数据泄漏。


9. 构建二分类神经网络

模型结构如下:

fromtensorflowimportkerasfromtensorflow.kerasimportlayers model=keras.Sequential([layers.Dense(4,activation='relu',input_shape=[33]),layers.Dense(4,activation='relu'),layers.Dense(1,activation='sigmoid'),])

这个模型包含:

  • 输入层:33 个特征
  • 隐藏层 1:4 个神经元,ReLU 激活
  • 隐藏层 2:4 个神经元,ReLU 激活
  • 输出层:1 个神经元,Sigmoid 激活

这里输出层使用:

layers.Dense(1,activation='sigmoid')

是二分类模型的关键。

如果是回归任务,最后一层通常不使用 sigmoid。

如果是多分类任务,最后一层通常会使用 softmax。


10. 编译模型

模型编译代码如下:

model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['binary_accuracy'],)

各参数含义如下:

optimizer=‘adam’

使用 Adam 优化器。

Adam 是深度学习中非常常用的优化算法,通常比普通 SGD 更容易训练,收敛速度也更稳定。

loss=‘binary_crossentropy’

使用二分类交叉熵作为损失函数。

这是二分类问题中最常见的选择。

metrics=[‘binary_accuracy’]

训练过程中同时记录二分类准确率。

注意:

  • loss用于模型优化
  • metrics用于观察模型表现

模型真正优化的是binary_crossentropy,不是binary_accuracy


11. 使用 Early Stopping 防止过拟合

训练代码中使用了早停机制:

early_stopping=keras.callbacks.EarlyStopping(patience=10,min_delta=0.001,restore_best_weights=True,)

参数含义如下:

patience=10

如果验证集指标连续 10 个 epoch 没有明显改善,就停止训练。

min_delta=0.001

只有改善幅度大于 0.001,才认为是真的改善。

restore_best_weights=True

训练结束后,恢复到验证集表现最好的那一轮模型参数。

这个参数非常重要。

因为模型最后一轮的权重不一定是最好的,可能已经开始过拟合。启用该参数后,可以自动保留验证集表现最佳的模型。


12. 训练模型

训练代码如下:

history=model.fit(X_train,y_train,validation_data=(X_valid,y_valid),batch_size=512,epochs=1000,callbacks=[early_stopping],verbose=0,)

这里设置了最多训练 1000 个 epoch,但由于使用了 Early Stopping,模型通常不会真的训练满 1000 轮。

参数解释:

  • X_train, y_train:训练数据
  • validation_data:验证数据
  • batch_size=512:每次用 512 个样本更新模型
  • epochs=1000:最多训练 1000 轮
  • callbacks=[early_stopping]:启用早停
  • verbose=0:不显示训练过程日志

13. 查看训练曲线

训练完成后,可以将history.history转换为 DataFrame:

history_df=pd.DataFrame(history.history)

然后绘制损失曲线:

history_df.loc[5:,['loss','val_loss']].plot()

以及准确率曲线:

history_df.loc[5:,['binary_accuracy','val_binary_accuracy']].plot()

这里从第 5 个 epoch 开始画图,是为了避开训练初期波动较大的阶段,让趋势更清晰。

输出示例:

Best Validation Loss: 0.3534 Best Validation Accuracy: 0.8857

表示验证集上最好的结果为:

  • 最佳验证损失:0.3534
  • 最佳验证准确率:0.8857

也就是模型在验证集上的准确率约为 88.57%。


14. 二分类模型的标准配置

对于神经网络二分类任务,常见配置可以总结如下:

model=keras.Sequential([layers.Dense(若干神经元,activation='relu',input_shape=[特征数量]),layers.Dense(若干神经元,activation='relu'),layers.Dense(1,activation='sigmoid'),])model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['binary_accuracy'],)

核心要点是:

任务类型输出层激活函数损失函数
回归1 个或多个神经元通常无激活MAE / MSE
二分类1 个神经元sigmoidbinary_crossentropy
多分类类别数量个神经元softmaxcategorical_crossentropy / sparse_categorical_crossentropy

15. Sigmoid 与 Softmax 的区别

二分类通常使用:

Dense(1,activation='sigmoid')

多分类通常使用:

Dense(num_classes,activation='softmax')

二者区别如下:

Sigmoid

适合二分类或多标签分类。

输出每个类别独立成立的概率。

例如多标签任务:

一张图片可以同时包含:猫、狗、车

每个标签可以独立为真。

Softmax

适合单标签多分类。

输出所有类别的概率分布,且概率和为 1。

例如:

一张图片只能是:猫 / 狗 / 鸟 中的一类

16. 实践中的注意事项

1. 类别标签必须是数字

神经网络不能直接使用字符串类别,需要先编码:

good->0bad->1

2. 特征需要归一化

对于神经网络,数值归一化通常很重要,可以提升训练稳定性。

3. 验证集不能参与训练统计

归一化参数应该从训练集计算,再应用到验证集和测试集。

4. 准确率不是万能指标

类别不平衡时,准确率可能误导判断。此时应该结合 Precision、Recall、F1、AUC 等指标。

5. 阈值可以根据业务调整

默认阈值是 0.5,但实际项目中可以根据业务需求调整。

例如:

  • 想减少漏检:降低阈值
  • 想减少误报:提高阈值

17. 总结

本文介绍了如何使用神经网络解决二分类问题。

核心知识点如下:

  • 二分类问题的目标是将样本分为两个类别之一
  • 原始类别标签需要转换为 0 和 1
  • 准确率可以用于评估模型,但不适合作为损失函数
  • 二分类常用损失函数是binary_crossentropy
  • 输出层通常使用sigmoid激活函数
  • Sigmoid 可以将模型输出转换为 0 到 1 之间的概率
  • 默认情况下,概率大于等于 0.5 判为类别 1,否则判为类别 0
  • Adam 优化器同样适用于分类任务
  • Early Stopping 可以减少过拟合并节省训练时间

一句话概括:

二分类神经网络的典型组合是:sigmoid输出概率,binary_crossentropy作为损失函数,binary_accuracy作为评估指标。

完整流程可以概括为:

准备数据 -> 标签编码 -> 划分训练集和验证集 -> 特征归一化 -> 构建神经网络 -> 输出层使用 sigmoid -> 使用 binary_crossentropy 编译 -> 训练模型 -> 查看 loss 和 accuracy 曲线 -> 根据验证集表现评估模型

掌握了这一流程之后,就可以将类似方法应用到更多实际二分类任务中,例如客户流失预测、订单取消预测、欺诈检测和医学辅助诊断等场景。