我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

从二分类到多分类:路透社数据集实战与避坑指南

从二分类到多分类:路透社数据集实战与避坑指南 1. 从二分类到多分类为什么路透社数据集值得单独拎出来讲做深度学习入门的人几乎都绕不开一个经典的进阶路径先拿二分类练手再啃多分类。二分类问题里输出层一个神经元配sigmoid就够了标签非0即1损失函数用二元交叉熵整个流程非常直观。但一旦类别数超过两个事情就开始变得有意思了——输出层要换成softmax神经元数量等于类别数损失函数要换成分类交叉熵标签还得做独热编码。这一连串变化恰恰是很多人从“能跑通”到“真理解”的分水岭。路透社数据集就是在这个节点上最合适的练手材料。它来自经典的文本分类语料库包含大量新闻短讯每条新闻被标注了多个主题类别中的某一个。和IMDB影评数据集那种“正面/负面”的二选一不同路透社数据集有46个不同的类别虽然类别分布极不均衡但正好模拟了真实世界中多分类任务的复杂性。用它来练手你能完整体验从数据加载、标签处理、模型搭建到评估调优的全流程而且数据量适中在普通笔记本上几分钟就能跑完一轮训练反馈极快。这篇文章面向的是已经跑过二分类Demo、想进一步搞懂多分类问题核心细节的读者。我会把重点放在“为什么这么做”上而不是简单贴一段能跑的代码。比如为什么路透社数据集要用to_categorical做独热编码为什么中间层用ReLU而输出层用softmax为什么评估指标不能只看准确率这些问题的答案才是你真正能从这篇内容里带走的东西。下面我会按照实际操作的顺序把每个环节拆开揉碎配上我踩过的坑和实测有效的技巧。2. 数据加载与标签处理多分类问题的第一道坎2.1 路透社数据集的结构与加载方式Keras内置了这个数据集的加载接口一行代码就能拿到训练集和测试集。加载出来的数据格式是整数序列——每个整数代表词典中的一个词索引整条新闻就是一个整数列表。这一点和IMDB影评数据集完全一致所以如果你之前跑过IMDB的二分类Demo数据加载这块会非常熟悉。from tensorflow.keras.datasets import reuters (train_data, train_labels), (test_data, test_labels) reuters.load_data(num_words10000)这里的num_words10000表示只保留出现频率最高的10000个词低频词会被丢弃并替换为特殊索引。为什么要限制词表大小因为路透社数据集的完整词表大约有30979个词如果全部保留输入向量的维度会非常大模型参数爆炸训练慢且容易过拟合。限制到10000是在信息保留和计算效率之间取的一个平衡点这也是文本分类任务中非常常见的做法。加载完成后你可以快速看一下数据规模print(len(train_data)) # 8982 print(len(test_data)) # 2246 print(train_labels[0]) # 某个0到45之间的整数训练集8982条测试集2246条类别总数46。这个数据量对于深度学习来说偏小所以模型不能设计得太复杂否则过拟合会非常严重。这也是为什么后面我们会用一个相对浅层的网络结构。2.2 为什么必须做独热编码这是多分类问题里最容易让人困惑的一步。在二分类中标签是0或1直接丢给模型就行。但在多分类中标签是0到45的整数如果你直接把这些整数作为目标值模型会误以为类别之间存在大小关系——比如它会认为类别45比类别0“更大”这显然是无意义的。独热编码的作用就是把一个整数标签变成一个长度为46的向量只有对应类别的位置是1其余全是0。比如标签3会变成[0,0,0,1,0,...,0]。这样一来类别之间就没有任何顺序关系了每个类别都是平等的。from tensorflow.keras.utils import to_categorical one_hot_train_labels to_categorical(train_labels) one_hot_test_labels to_categorical(test_labels)注意to_categorical默认会推断类别总数。如果你在训练集上调用它它会根据训练集中出现的最大标签值来确定向量长度。但测试集如果出现了训练集中没有的类别就会出问题。稳妥的做法是显式传入num_classes46确保训练集和测试集的编码维度一致。我实测下来这一步如果忘了做或者做错了模型训练时损失函数会直接报错或者loss不下降排查起来很浪费时间。所以建议在数据预处理阶段就加一行打印确认编码后的形状是(样本数, 46)。2.3 输入数据的向量化处理原始数据是整数列表长度不一不能直接输入神经网络。我们需要把它转换成固定长度的向量。最常见的方法是做一个长度为10000的零向量然后把新闻中出现的词索引对应位置置为1。这就是所谓的“multi-hot”编码。import numpy as np def vectorize_sequences(sequences, dimension10000): results np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): results[i, sequence] 1. return results x_train vectorize_sequences(train_data) x_test vectorize_sequences(test_data)这个操作的本质是把每条新闻表示成一个10000维的稀疏向量向量中为1的位置代表该词在新闻中出现过。虽然丢失了词序信息但对于主题分类这种任务来说词袋模型已经足够有效了。如果你想让模型效果更好可以尝试TF-IDF加权或者保留词频信息但在入门阶段multi-hot是最直观的选择。3. 模型架构设计为什么这样搭才合理3.1 网络层数与隐藏单元数的选择逻辑对于路透社数据集这种小规模文本分类任务网络不能太深。我试过用三四层全连接堆叠结果训练集准确率很快冲到接近100%但验证集准确率卡在70%左右上不去典型的过拟合。后来把结构简化到两层隐藏层效果反而更稳。from tensorflow.keras import models from tensorflow.keras import layers model models.Sequential() model.add(layers.Dense(64, activationrelu, input_shape(10000,))) model.add(layers.Dense(64, activationrelu)) model.add(layers.Dense(46, activationsoftmax))第一层64个单元第二层64个单元输出层46个单元对应46个类别。为什么选64这是一个经验值。隐藏单元太少模型容量不够欠拟合太多参数暴涨过拟合风险高。64在路透社数据集上是一个经过大量实践验证的平衡点。你也可以试试32或128但64通常是最省心的起点。中间层用ReLU激活函数这是深度学习的标配。ReLU计算简单梯度不会饱和能有效缓解梯度消失问题。输出层用softmax它的作用是把46个神经元的输出转换成概率分布所有输出值在0到1之间且总和为1。这样模型输出的就是一个完整的概率分布哪个类别的概率最大就预测为哪个类别。3.2 损失函数与优化器的搭配多分类问题的标准损失函数是分类交叉熵categorical crossentropy。它衡量的是模型输出的概率分布与真实独热分布之间的差异。如果模型对正确类别的预测概率越高损失就越小。model.compile(optimizerrmsprop, losscategorical_crossentropy, metrics[accuracy])优化器选RMSprop这是Keras作者推荐用于循环神经网络和文本任务的默认优化器对学习率不敏感收敛稳定。当然你也可以用Adam效果通常差不多但RMSprop在这个数据集上是我用过最稳的。实操心得如果你把标签做成了整数形式而不是独热编码损失函数要换成sparse_categorical_crossentropy这样就不用做独热编码了省内存。两种方式效果一样看个人习惯。我一般推荐用sparse版本代码更简洁也不容易在编码维度上出错。3.3 评估指标的选择与局限metrics[accuracy]是最直观的评估指标但在类别不均衡的多分类任务中准确率可能会误导你。路透社数据集中某些类别样本很多某些类别样本极少。如果模型把所有样本都预测为最常见的那几个类别准确率可能看起来还不错但实际上对稀有类别的识别能力为零。更全面的评估方式是看混淆矩阵或者每个类别的F1分数。不过在入门阶段先用准确率把流程跑通然后再深入分析每个类别的表现。我建议在模型训练完成后至少打印一下测试集上的准确率并手动检查几条预测结果看看模型到底学到了什么。4. 训练过程与验证集划分别跳过这一步4.1 为什么要留出验证集很多人跑Demo时习惯把所有训练数据都用来训练然后直接看测试集准确率。这样做的问题是你无法判断模型是否过拟合也无法在训练过程中做任何调整。正确的做法是从训练集中切出一部分作为验证集用来监控训练过程中的泛化表现。x_val x_train[:1000] partial_x_train x_train[1000:] y_val one_hot_train_labels[:1000] partial_y_train one_hot_train_labels[1000:]这里切了1000条作为验证集剩下的7982条用于训练。验证集的比例没有硬性规定小数据集通常切10%到20%。切的时候要注意随机打乱否则如果数据本身有顺序验证集可能会有偏差。路透社数据集本身已经做过打乱处理所以直接取前1000条问题不大但养成手动打乱的习惯总是好的。4.2 训练轮数与过拟合监控history model.fit(partial_x_train, partial_y_train, epochs20, batch_size512, validation_data(x_val, y_val))训练20轮每批512条。batch_size选512是因为数据集不大太大的批次会导致更新次数太少收敛慢太小的批次训练不稳定。512是一个适中的选择。训练完成后把训练损失和验证损失画出来你会看到典型的过拟合曲线训练损失持续下降验证损失先下降后上升。验证损失开始上升的那个点就是模型开始过拟合的时刻。在路透社数据集上这个点通常出现在第9到第12轮之间。import matplotlib.pyplot as plt loss history.history[loss] val_loss history.history[val_loss] epochs range(1, len(loss) 1) plt.plot(epochs, loss, bo, labelTraining loss) plt.plot(epochs, val_loss, b, labelValidation loss) plt.title(Training and validation loss) plt.legend() plt.show()看到过拟合点之后你可以把训练轮数设成那个值重新训练一个最终模型。比如验证损失在第9轮开始上升那就把epochs设为9。这样得到的模型在测试集上的表现通常比训练20轮的更好。4.3 最终模型训练与测试集评估model models.Sequential() model.add(layers.Dense(64, activationrelu, input_shape(10000,))) model.add(layers.Dense(64, activationrelu)) model.add(layers.Dense(46, activationsoftmax)) model.compile(optimizerrmsprop, losscategorical_crossentropy, metrics[accuracy]) model.fit(x_train, one_hot_train_labels, epochs9, batch_size512) results model.evaluate(x_test, one_hot_test_labels) print(results)在路透社数据集上这个简单模型的测试准确率通常能到78%到80%左右。别小看这个数字考虑到有46个类别随机猜测的准确率只有2%左右80%已经相当不错了。如果你用更大的词表或者预训练词向量还能再提升几个百分点但那就超出入门范围了。5. 常见问题与排查技巧实录5.1 损失函数报错与标签形状不匹配这是新手最容易遇到的问题。报错信息通常是“形状不匹配”或者“无法计算交叉熵”。根本原因几乎都是标签没有做独热编码或者编码后的维度与模型输出层不一致。排查方法很简单打印one_hot_train_labels.shape确认第二维是46再打印model.output_shape确认最后一维也是46。两者一致才能正常训练。5.2 准确率卡在随机水平不上升如果训练几轮后准确率还在2%左右徘徊说明模型根本没学到东西。常见原因有三个一是学习率太大导致梯度爆炸可以换用Adam优化器试试二是输入数据没有正确归一化multi-hot编码本身已经在0到1之间一般不需要额外处理三是标签编码错误比如把独热编码和sparse标签混用了。逐个排查通常能快速定位。5.3 验证损失波动大不稳定小数据集上验证损失波动大是正常现象但如果波动幅度超过0.5就值得注意了。可以尝试减小batch_size比如从512降到128让梯度更新更频繁更平滑。或者增加验证集的比例让验证评估更稳定。另外确保每次训练前都重新初始化模型不要在同一个模型上反复fit否则历史状态会干扰结果。5.4 预测结果全是同一类别如果模型对任何输入都预测为同一个类别通常是因为类别不均衡太严重模型学会了“偷懒”——只要全猜最常见的那几个类别损失就能降到比较低的水平。解决办法是在训练时对稀有类别加权或者用focal loss替代标准交叉熵。不过在入门阶段先接受这个现象等把基础流程跑熟之后再回来优化。问题现象可能原因排查方法解决思路损失函数报错标签未独热编码或维度不匹配打印标签和输出形状用to_categorical并指定num_classes准确率不上升学习率过大或标签编码错误检查优化器和标签格式换Adam确认标签类型验证损失波动大batch_size过大或验证集太小调整batch_size和验证集比例减小batch_size增加验证样本预测全为同一类类别不均衡严重统计各类别样本数类别加权或换损失函数独家避坑技巧在训练之前先跑一个极简的“冒烟测试”——用10条训练数据训练1轮看损失是否能下降。如果能下降说明整个流程是通的再上全量数据。这个习惯能帮你节省大量排查时间尤其是在你改了数据预处理代码之后。6. 从路透社数据集延伸出去多分类问题的通用套路路透社数据集虽然小但它覆盖了多分类问题的完整流程。把这里的每一步搞懂之后你换到任何多分类任务上——不管是图像分类、情感分析还是意图识别——套路都是一样的数据加载与清洗、标签编码、输入向量化、模型搭建、损失函数选择、训练监控、过拟合处理、评估调优。区别只在于数据形态和网络结构核心逻辑完全相通。我个人的体会是多分类问题最容易被忽视的环节是标签处理。很多人把大量精力花在调网络结构上结果发现效果不好回头一查是标签编码出了问题。所以我的建议是在数据预处理阶段多花十分钟把标签的形状、分布、编码方式都打印出来确认一遍比后面调半天参数管用得多。另外路透社数据集的46个类别中有些类别只有几十条样本这种极端不均衡在真实业务中非常常见。如果你以后要做实际项目建议尽早接触类别加权、重采样、focal loss这些处理不均衡的手段。路透社数据集正好提供了一个安全的实验环境让你在跑通基础流程之后可以继续深入探索这些进阶话题。
返回列表