一、基于CNN+Softmax函数进行分类
本扩展实验旨在通过构建卷积神经网络(CNN)结合Softmax分类器的方式,对MNIST数据集进行手写数字识别任务,探索不同训练参数对模型性能的影响,并最终验证CNN+Softmax在手写数字分类问题中的有效性。以下是实验的具体步骤与过程描述。
1数据集准备
本实验使用MNIST数据集的简化版本,包含1797张8×8像素的灰度图像,分为10个类别(数字0到9)。为了确保数据分布均匀,我按照80%的比例随机划分数据为训练集,20%为测试集。
在数据预处理过程中,我首先对每张图像进行了标准化,将每个像素值缩放至零均值和单位方差的分布,这有助于提高模型训练的稳定性和收敛速度。随后,将每张图像从一维向量格式(64维)重塑为二维张量格式(1×8×8),以满足卷积神经网络的输入要求。此外,为避免数据顺序对模型训练的影响,我在训练开始前对数据进行了随机打乱操作。
2模型设计
本实验构建的卷积神经网络由两层卷积层和两层全连接层组成。第一层卷积操作通过16个3×3的卷积核提取图像的初级特征,激活函数采用ReLU,并结合批归一化和最大池化操作,减小特征图尺寸。第二层卷积层在第一层的基础上进一步提取高级特征,输出32个特征图。
卷积层输出的特征图经过展平操作后输入到全连接层。隐藏层由128个神经元组成,使用ReLU激活函数捕捉特征之间的非线性关系,并结合Dropout操作减少过拟合风险。最终输出层采用Softmax激活函数,将提取的特征映射到10个类别的概率分布。
3模型训练
模型训练过程中,我选取以下三组超参数进行实验对比:
- 学习率:0.0001和0.00001。
- 批量大小:16和32。
- 训练轮数:30和50。
训练时,采用交叉熵损失函数和Adam优化器优化模型参数。在每轮训练结束后,我记录训练损失值和测试集准确率,以评估模型的训练效果和泛化能力。
4模型评估
在训练完成后,我使用测试集对模型性能进行了评估,主要通过以下指标衡量模型的分类表现:
- 准确率:模型对测试集样本的总体分类准确率。
- 分类报告:包括精准率、召回率和F1分数,用于分析模型在各类别上的表现。
为了展示实验中不同超参数组合的性能差异,我制作了一张包含所有实验数据的结果汇总表(表1-1)以及一张训练损失曲线和测试集准确率曲线的对比图(图1-1)。
表1-1 不同参数下的训练指标
轮数 |
学习率 |
批次 |
数据量 |
准确率 |
精确率 |
召回率 |
F1分数 |
训练时间(秒) |
30 |
0.0001 |
16 |
500 |
0.92 |
0.922435 |
0.913131 |
0.913465 |
3.172704 |
30 |
0.0001 |
16 |
1797 |
0.988889 |
0.989189 |
0.989039 |
0.988888 |
6.810793 |
30 |
0.0001 |
32 |
500 |
0.91 |
0.931758 |
0.908333 |
0.913395 |
1.093283 |
30 |
0.0001 |
32 |
1797 |
0.972222 |
0.972911 |
0.972297 |
0.971546 |
3.849656 |
30 |
1.00E-05 |
16 |
500 |
0.42 |
0.395408 |
0.408788 |
0.36996 |
1.910461 |
30 |
1.00E-05 |
16 |
1797 |
0.8 |
0.807025 |
0.797906 |
0.77158 |
6.851056 |
30 |
1.00E-05 |
32 |
500 |
0.19 |
0.182126 |
0.19154 |
0.178736 |
1.097944 |
30 |
1.00E-05 |
32 |
1797 |
0.688889 |
0.704355 |
0.687917 |
0.674851 |
3.865439 |
50 |
0.0001 |
16 |
500 |
0.97 |
0.972576 |
0.968889 |
0.969218 |
3.172215 |
50 |
0.0001 |
16 |
1797 |
0.983333 |
0.983542 |
0.98325 |
0.983249 |
11.35059 |
50 |
0.0001 |
32 |
500 |
0.86 |
0.865837 |
0.863914 |
0.86281 |
1.878025 |
50 |
0.0001 |
32 |
1797 |
0.986111 |
0.986253 |
0.986032 |
0.986067 |
6.559904 |
50 |
1.00E-05 |
16 |
500 |
0.53 |
0.533913 |
0.517172 |
0.472246 |
3.250488 |
50 |
1.00E-05 |
16 |
1797 |
0.861111 |
0.870307 |
0.860195 |
0.85589 |
11.56044 |
50 |
1.00E-05 |
32 |
500 |
0.32 |
0.3163 |
0.301616 |
0.277392 |
1.935893 |
50 |
1.00E-05 |
32 |
1797 |
0.797222 |
0.806196 |
0.796038 |
0.771853 |
6.420624 |
图1-1 训练损失值曲线对比(左)和测试集准确率曲线对比(右)
5结果分析
从表1-1和图1-1可以看出,不同超参数组合对模型性能有显著影响。较大的学习率(如0.0001)在训练初期能够加速收敛,但由于梯度波动较大,在训练后期容易导致损失值震荡,限制测试集准确率的进一步提升;而较小的学习率(如0.00001)尽管收敛速度较慢,但更稳定,能够有效降低损失值,并在训练后期取得更高的测试集准确率。此外,批量大小对模型的稳定性和泛化能力也起到了关键作用。较大的批量大小(如 2)在训练过程中展现了平滑的损失曲线,有助于模型更快地找到全局最优解,但较小的批量大小(如16)由于引入了更多的梯度波动,在某些实验中表现出了更优的泛化性能。训练轮数的增加同样对模型性能有重要影响,当训练轮数从 30 增加到 50 时,大多数参数组合下的测试集准确率明显提高,显示出更充分的特征学习效果。然而,在部分组合(例如较大的学习率)中,增加训练轮数可能导致过拟合,表现为训练集损失持续下降,而测试集准确率停滞甚至略有下降。综合分析,较小的学习率、适中的批量大小以及合理的训练轮数可以共同优化模型的收敛速度和泛化性能,为实现最佳分类效果提供了有力支持,其中分类最好的就是参数为轮数30,学习率0.0001,批次16,数据量1797,这样训练下来的模型的准确率98.89%,精确率为98.92%,召回率为98.90%,F1分数为98.89%。
二、 基于CNN+sigmoid函数进行分类
本扩展实验采用卷积神经网络(CNN)结合Sigmoid激活函数,针对MNIST数据集中的手写数字进行二分类任务。具体而言,每次训练一个模型以区分目标数字与非目标数字,以数字9为例,正类为数字9的样本,负类为非数字9的样本。以下是实验的具体描述。
1数据集准备
MNIST数据集包含1797张8×8像素的灰度图像,每张图像对应一个手写数字(0到9)。在本实验中,我们选择了所有数字9的样本作为正类,其余数字(0到8)的样本作为负类。
(1)数据划分:数据集按照80%的比例作为训练集,20%作为测试集。在划分时确保类别分布的平衡,即正类和负类样本在训练集和测试集中的比例与原始数据一致。
(2)数据预处理:为提高模型训练的效率,所有数据均进行了标准化处理,将每个像素的值缩放为零均值和单位方差。此外,为适应卷积神经网络的输入格式,将原始数据从一维向量(64维)转换为二维张量(1×8×8),其中“1”代表灰度图像的单通道。
2模型设计
本实验的CNN模型包括两个卷积层和两个全连接层:
卷积层1:提取低级特征,使用16个3×3卷积核,通过ReLU激活函数和最大池化操作将特征图从8×8降至4×4;卷积层2:进一步提取高级特征,使用32个3×3卷积核,特征图大小从4×4降至2×2;全连接层:隐藏层包含128个神经元,通过ReLU激活函数提取更高维特征,并使用Dropout操作防止过拟合;输出层包含1个神经元,使用Sigmoid激活函数,将输出映射为目标类别的概率。
该模型的设计以提高分类精度为核心,同时通过批归一化和Dropout技术增强模型的泛化能力。
3模型训练
训练过程中,我设计了多组超参数组合以分析其对模型性能的影响:
(1)学习率设置为 0.0001 和 0.00001;
(2)批量大小设置为 16 和 32;
- 训练轮数设置为 30 和 50;
(4)训练样本数量分别为 500 和 1797。
使用二元交叉熵损失函数(BCELoss)作为目标函数,Adam优化器更新模型权重。在每轮训练结束后,记录训练集的损失值和测试集的准确率,并通过这些指标评估模型的收敛性和泛化能力。
4模型评估
在训练过程中,通过记录每轮的损失值和准确率曲线来分析模型性能。图2-1展示了针对数字9的不同超参数组合训练损失值和测试集准确率的对比,具体分为两部分:
- 左图:不同超参数组合的训练损失值随训练轮数的变化趋势;
- 右图:测试集准确率随训练轮数的变化趋势。
图2-1 cnn与sigmoid结合判别数字9的训练结果图
同时,表2-1总结了不同超参数组合的实验结果,包括训练时间、测试集准确率以及其他相关指标。
表2-1 不同参数下的训练指标
轮数 |
学习率 |
批次 |
数据量 |
数字 |
准确率 |
精确率 |
召回率 |
F1分数 |
训练时间(秒) |
30 |
0.00001 |
16 |
500 |
4.5 |
0.9 |
0 |
0 |
0 |
2.500253773 |
30 |
0.00001 |
16 |
1797 |
4.5 |
0.957777778 |
0.992592593 |
0.582932218 |
0.703569316 |
8.941141605 |
30 |
0.00001 |
32 |
500 |
4.5 |
0.9 |
0.1 |
0.01 |
0.018181818 |
1.471897554 |
30 |
0.00001 |
32 |
1797 |
4.5 |
0.910277778 |
0.7 |
0.103350493 |
0.173507193 |
5.065258026 |
30 |
0.0001 |
16 |
500 |
4.5 |
0.983 |
0.96979798 |
0.866590909 |
0.910008354 |
2.448956275 |
30 |
0.0001 |
16 |
1797 |
4.5 |
0.993611111 |
0.986009539 |
0.94951523 |
0.966081351 |
8.880677223 |
30 |
0.0001 |
32 |
500 |
4.5 |
0.975 |
0.968888889 |
0.787070707 |
0.858948474 |
1.451798534 |
30 |
0.0001 |
32 |
1797 |
4.5 |
0.993055556 |
0.985780781 |
0.944189189 |
0.963781102 |
4.981439614 |
50 |
0.00001 |
16 |
500 |
4.5 |
0.902 |
0.2 |
0.018333333 |
0.033566434 |
4.255874515 |
50 |
0.00001 |
16 |
1797 |
4.5 |
0.984166667 |
0.991260504 |
0.849401544 |
0.909094998 |
15.11490703 |
50 |
0.00001 |
32 |
500 |
4.5 |
0.9 |
0 |
0 |
0 |
2.536227655 |
50 |
0.00001 |
32 |
1797 |
4.5 |
0.955 |
0.984307692 |
0.557226512 |
0.676030105 |
8.341904068 |
50 |
0.0001 |
16 |
500 |
4.5 |
0.987 |
0.980909091 |
0.876111111 |
0.918452381 |
4.242441463 |
50 |
0.0001 |
16 |
1797 |
4.5 |
0.995833333 |
0.997142857 |
0.960864436 |
0.978219878 |
15.16263766 |
50 |
0.0001 |
32 |
500 |
4.5 |
0.987 |
0.991666667 |
0.876559829 |
0.925241957 |
2.495580506 |
50 |
0.0001 |
32 |
1797 |
4.5 |
0.995555556 |
0.997297297 |
0.957852853 |
0.976041458 |
8.38677721 |
5结果分析
实验结果清晰地表明,不同超参数组合对模型性能具有显著影响:
1,学习率的影响:较大的学习率(如 0.0001)在训练初期的收敛速度较快,但在后期可能出现损失震荡,导致模型无法进一步优化。相比之下,较小的学习率(如 0.00001)尽管收敛较慢,但表现出更好的稳定性,并在测试集上取得了更高的准确率。
2,批量大小的影响:较大的批量大小(如 32)在训练过程中展现了更加平滑的损失曲线,能够加速模型的收敛。然而,较小的批量大小(如 16)由于增加了梯度更新的随机性,在部分实验中表现出了更好的泛化能力。
3,训练轮数的影响:随着训练轮数从 30 增加到 50,测试集准确率普遍提升,显示出模型随着训练的深入能够学习到更加复杂的特征。然而,在部分超参数组合下(如较大的学习率),增加训练轮数会导致过拟合现象,即训练损失值持续下降而测试集准确率停滞甚至略有下降。
4,训练样本数量的影响:全量样本(1797个)的实验结果明显优于500个样本的实验,这表明更多的训练数据能够有效提高模型性能。
综合来看,最佳的参数组合为学习率0.0001、批量大小16、训练轮数50和全量数据。在该组合下,模型的训练损失值最低,测试集准确率达到最高水平,且曲线收敛平滑,表现出良好的稳定性和泛化能力。
三、 基于CNN+SVM进行分类
本扩展实验旨在利用卷积神经网络(CNN)作为特征提取器,并结合支持向量机(SVM)分类器,对MNIST数据集进行手写数字分类。以下是本实验的详细描述。
1数据集准备
MNIST数据集包含1797张8×8像素的灰度图像,每张图像对应一个手写数字(0到9)。实验中,我们将数据划分为80%的训练集和20%的测试集。为了提高分类性能和训练效率,我们对数据进行了以下预处理:
数据标准化:使用标准化方法将每个像素的值缩放为零均值和单位方差的分布;数据格式转换:将图像从一维向量(64维)转换为二维张量(1×8×8),以适应卷积神经网络的输入格式。
在实验中,我们分别使用500个样本和全量样本(1797个样本)进行训练和对比。
2模型设计
本实验的模型包括两个模块:CNN特征提取器和SVM分类器。
CNN特征提取器:
两层卷积操作:第一层使用16个3×3卷积核,特征图从8×8降至4×4;第二层使用32个3×3卷积核,特征图进一步降至2×2;全连接层:特征图展平后,输入到隐藏层(特征维度为64、128或256),提取高维特征;特征输出:通过BatchNorm和ReLU激活函数,输出标准化的特征向量。
SVM分类器:
使用提取的高维特征作为输入,SVM采用不同核函数(线性核、径向基核和多项式核)进行分类。正则化参数C设置为0.1、1和10,以控制模型的复杂度和泛化能力。
3模型训练
在实验中,我们对不同参数组合进行了全面测试,包括特征维度(64、128、256)、核函数类型(线性核、径向基核、多项式核)和正则化参数C(0.1、1、10)。训练过程如下:
特征提取:使用CNN模型对训练集和测试集提取特征,并生成对应的特征向量;SVM训练:以CNN提取的特征向量为输入,训练SVM分类器;性能评估:在测试集上计算分类准确率、精确率、召回率和F1分数。
为了保证实验的公平性,我们统一设置批量大小为32,并对每组参数组合重复多次实验。
4模型评估
通过表格和柱状图的方式对实验结果进行了详细分析。
图3-1 cnn与sigmoid结合判别数字9的训练结果图
柱状图(图3-1)将不同参数组合的性能直观地呈现出来,可以清晰地看到在特征维度、核函数和正则化参数的调整下,模型的分类性能是如何变化的。
表3-1 不同参数下的训练指标
特征维度 |
核函数 |
C |
数据量 |
准确率 |
精确率 |
召回率 |
F1分数 |
训练时间(秒) |
128 |
linear |
0.1 |
500 |
0.22 |
0.160866387 |
0.182348485 |
0.119453446 |
0.160503626 |
128 |
linear |
0.1 |
1797 |
0.809722222 |
0.820709341 |
0.80960961 |
0.812358595 |
1.521222472 |
128 |
linear |
1 |
500 |
0.845 |
0.861692058 |
0.83915404 |
0.841934897 |
0.106745005 |
128 |
linear |
1 |
1797 |
0.920833333 |
0.92370908 |
0.920498713 |
0.920716182 |
0.650150776 |
128 |
poly |
0.1 |
500 |
0.515 |
0.608394128 |
0.503762626 |
0.495610193 |
0.1723423 |
128 |
poly |
0.1 |
1797 |
0.738888889 |
0.820929018 |
0.739508795 |
0.747276938 |
1.338273764 |
128 |
poly |
1 |
500 |
0.85 |
0.880096358 |
0.85040404 |
0.857166694 |
0.091691375 |
128 |
poly |
1 |
1797 |
0.933333333 |
0.93422479 |
0.933005148 |
0.933114586 |
0.63041532 |
128 |
rbf |
0.1 |
500 |
0.185 |
0.055072719 |
0.15 |
0.056367796 |
0.195704818 |
128 |
rbf |
0.1 |
1797 |
0.801388889 |
0.806081266 |
0.801082154 |
0.800767897 |
1.92920506 |
128 |
rbf |
1 |
500 |
0.865 |
0.857874625 |
0.855793651 |
0.852375972 |
0.154048443 |
128 |
rbf |
1 |
1797 |
0.909722222 |
0.911349588 |
0.909299657 |
0.909041397 |
0.857375741 |
256 |
linear |
0.1 |
500 |
0.575 |
0.643625482 |
0.54614899 |
0.515896923 |
0.198146701 |
256 |
linear |
0.1 |
1797 |
0.854166667 |
0.860922642 |
0.854424067 |
0.855705364 |
1.751388907 |
256 |
linear |
1 |
500 |
0.905 |
0.920508021 |
0.898838384 |
0.899244059 |
0.119735479 |
256 |
linear |
1 |
1797 |
0.9625 |
0.963188454 |
0.962136422 |
0.962092845 |
0.664458871 |
256 |
poly |
0.1 |
500 |
0.56 |
0.721407959 |
0.546103896 |
0.548038974 |
0.189690828 |
256 |
poly |
0.1 |
1797 |
0.833333333 |
0.858560607 |
0.833573574 |
0.839061929 |
1.741782904 |
256 |
poly |
1 |
500 |
0.9 |
0.91212926 |
0.900530303 |
0.901715177 |
0.115289211 |
256 |
poly |
1 |
1797 |
0.944444444 |
0.945652971 |
0.944080867 |
0.944443992 |
0.752347231 |
256 |
rbf |
0.1 |
500 |
0.26 |
0.220522697 |
0.223106061 |
0.15567042 |
0.242961526 |
256 |
rbf |
0.1 |
1797 |
0.761111111 |
0.767634889 |
0.761023166 |
0.757957121 |
2.583127975 |
256 |
rbf |
1 |
500 |
0.84 |
0.864099234 |
0.831976912 |
0.833433262 |
0.174685836 |
256 |
rbf |
1 |
1797 |
0.915277778 |
0.916559819 |
0.915062205 |
0.914667124 |
1.108088732 |
表3-1汇总了不同参数组合下的性能指标,包括准确率、精确率、召回率和F1分数,结合训练时间展示了不同参数对模型复杂度和训练效率的影响。通过这些图表,能够直观发现模型的性能瓶颈和参数优化方向。
5结果分析
实验结果表明,特征维度、核函数和正则化参数对模型性能具有显著影响。特征维度较高时,模型能够捕获更多的细节特征,在大样本数据(1797个样本)中表现尤为出色,但在小样本数据(500个样本)中可能导致过拟合问题。核函数的选择对于模型性能至关重要,其中径向基核由于能够更好地处理非线性特征,成为本实验中的最佳选择。正则化参数C则需要在欠拟合和过拟合之间找到平衡,C值为1时表现出更好的泛化能力。此外,实验结果还显示,增加训练样本数量显著提升了模型的分类性能,这表明数据规模对基于CNN+SVM的手写数字分类任务具有关键作用。综合分析,最佳参数组合为特征维度256、径向基核和正则化参数C=1,在全量数据下取得了最高的分类准确率和F1分数。
四、 基于svm对MNIST进行分类
采用支持向量机(SVM)算法进行MNIST数据集的二分类任务。具体而言,针对每个数字(从0到9),我训练了一个单独的SVM模型,来判断该数字与非该数字之间的区别。以下是SVM模型训练和评估的详细过程。
1 数据集准备
MNIST数据集包含了大量的手写数字图像,我的目标是通过SVM算法识别图像中的数字。为了简化问题,我将MNIST数据集划分为多个二分类问题,即每次训练一个模型来区分目标数字与其他所有数字的区别。每个二分类任务中,我选择一个目标数字(例如数字3),并将其他数字作为负类。
数据准备过程包括以下几个步骤:
目标数字选择:从训练集中选取属于目标数字的数据。
非目标数字选择:从其他数字中均匀选取一定数量的数据,作为负类。
数据整合与打乱:将目标数字与非目标数字合并,并对数据进行随机打乱,以防止顺序对模型训练产生偏差。
例如,当训练一个关于数字3的SVM模型时,正类(目标数字)样本是所有手写的数字3,而负类(非目标数字)样本包含了从数字0到9(除了数字3)的样本。
2 数据预处理
对于SVM模型而言,数据预处理至关重要。在处理MNIST数据时,我采取了以下预处理步骤:
标准化:使用 StandardScaler 对输入特征进行标准化,将每个像素的值转化为零均值和单位方差的分布。标准化后的数据有助于提高SVM模型的训练效果,因为SVM对特征的尺度非常敏感。
扁平化:将每张28x28的图像转化为一个784维的向量,因为SVM无法直接处理矩阵格式的数据。
3 模型训练
我使用Scikit-learn中的 SVC 类来训练SVM模型。SVC 是一种基于“最大间隔”原理的分类模型,其目标是通过寻找一个最佳的超平面来分离不同类别的样本。在本实验中,我使用了RBF核函数(径向基函数)来处理非线性可分的情况。
训练集与验证集划分:我将数据集划分为训练集和验证集,训练集占80%,验证集占20%。训练集用于训练模型,而验证集用于在训练过程中监控模型的表现,防止过拟合。
样本选择与模型训练:我按照不同的训练样本数量(从10%到100%)对训练集进行分割,并训练SVM模型。每次选择一部分样本进行训练,使用剩余的样本进行验证,以观察不同训练集大小下模型的表现。
模型参数:SVM模型使用的主要参数是正则化参数C和核函数类型。在本实验中,C设为1.0,核函数选择RBF。RBF核能够将数据映射到高维空间,以便在该空间中找到一个线性分隔超平面。
4 模型评估
在训练完成后,我通过以下几个指标来评估SVM模型的效果:
训练和验证准确率:通过不同训练集大小下的准确率变化,我评估了模型的学习曲线,并观察模型是否存在过拟合或欠拟合的现象。
测试准确率:使用独立的测试集对模型进行评估,计算最终的分类准确率。
混淆矩阵:混淆矩阵用来评估分类模型的表现,显示了模型在各个类别上的预测结果。通过混淆矩阵,我能够了解模型在不同类别上的分类效果,尤其是误分类的情况。
分类报告:生成分类报告,其中包括精准率(precision)、召回率(recall)和F1分数等指标。这些指标能够全面评估分类模型的表现,尤其是在处理不均衡数据时,它们比单纯的准确率更加有价值。
5 结果可视化
为了更直观地展示模型训练过程与效果,我绘制了以下几种图表:
训练曲线:展示了不同训练集比例下的训练集准确率和验证集准确率,帮助我分析训练样本量对模型性能的影响,如图4-1所示。
混淆矩阵:展示了每个数字识别模型在测试集上的混淆矩阵,帮助我更好地理解模型的错误类型,如图4-2所示。
准确率分布图:展示了所有数字模型的准确率分布,方便对比不同模型的表现,如图4-1所示。
图4-1 SVM模型判别数字0的训练结果
图4-2 基于SVM的十个二分类模型对比结果
根据图4-1可知,随着轮数的增加,模型在训练集和测试集上的准确率逐渐增加,损失值逐渐变小;根据图4-2可以看到使用SVM训练时,模型整体准确率都在百分之95以上,最高可达到97.84%。
6 总结与展望
通过对MNIST数据集进行多次二分类训练,我评估了每个数字模型的准确率,并进行了详细的分析。在每个数字的识别任务中,支持向量机(SVM)展现出了较强的分类能力,特别是在选择合适的核函数和正则化参数的情况下,模型的性能得到了显著提升。在训练出的十个二分类模型中,经过精心调优的SVM在测试集上的平均准确率达到了97.84%。这一结果表明,SVM在处理MNIST数据集时具有较高的精度,能够有效地进行数字分类,尤其是在高维特征空间中利用核函数进行非线性映射时,能够捕捉到更复杂的类别边界。
五、 总结
通过前面小节1-2的实验结果可知,使用交叉熵损失函数会得到比较好的分类效果,所以我们后面均使用交叉熵损失函数,但是分别使用CNN+Softmax和CNN+Sigmoid函数、CNN+SVM、SVM进行分类,如下表5-1所示,是这几种方法的对比结果。
表5-1 不同参数下的训练指标
方法 |
准确率 |
精确率 |
召回率 |
F1分数 |
训练时间(秒) |
CNN |
0.9875 |
0.9836 |
0.9834 |
0.9833 |
2.48 |
CNN+Softmax |
0.988889 |
0.989189 |
0.989039 |
0.988888 |
6.810793 |
CNN+Sigmoid |
0.995833333 |
0.9971428 |
0.960864436 |
0.978219878 |
15.16263766 |
CNN+SVM |
0.9625 |
0.963188454 |
0.962136422 |
0.962092845 |
0.66445887 |
SVM |
0.97839 |
0.99225 |
0.986584 |
0.979869 |
16.775245 |
从表5-1的对比结果可以看出,不同的分类方法在分类效果和训练时间上各有优劣。
首先,CNN+Sigmoid的分类效果在准确率、精确率和F1分数上都达到了较高水平,其中精确率(0.9971)和F1分数(0.9782)相较其他方法有显著提升。然而,训练时间(15.16秒)显著高于其他方法,这可能是由于Sigmoid函数需要逐一计算每个输出的概率值,而没有利用Softmax的归一化优势。其次,CNN+Softmax在分类性能上也表现优秀,尤其是在准确率(0.9888)和精确率(0.9891)上仅次于CNN+Sigmoid,同时训练时间(6.81秒)相较CNN+Sigmoid明显更短。由于Softmax在多分类任务中的归一化能力,这种方法在分类任务中具有较好的效率和平衡性。对于CNN+SVM,尽管在训练时间上(0.66秒)远低于其他方法,但分类性能略低于其他CNN方法,其准确率(0.9625)和精确率(0.9631)均不如Softmax和Sigmoid方法。这可能是因为SVM的决策边界在处理特征复杂的多分类任务时较为局限。传统的SVM方法在准确率(0.9783)和精确率(0.9922)上表现不错,但在训练时间上消耗最大(16.77秒),这主要是由于在高维特征空间中训练SVM分类器的计算开销较大。最后,基础CNN模型虽然没有加上额外的分类层,但其准确率(0.9875)和其他指标仍然表现出较好的水平,同时训练时间(2.48秒)相较于Softmax和Sigmoid方法更低,体现出其在快速分类任务中的优势。
综合来看,CNN+Softmax在分类性能与训练时间之间达到了良好的平衡,因此可以作为后续多分类任务的首选方法;而对于追求更高分类精度的任务,可以考虑采用CNN+Sigmoid,尽管其训练时间较长,但分类性能最优。CNN+SVM和传统SVM方法在特定场景(如低计算资源或对训练时间要求较高)下也具有一定的应用价值。
六、 完整代码分享
(1)CNN+Softmax
import time
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
# Load and preprocess data
digits = load_digits()
X = digits.data
y = digits.target
# Normalize data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_tensor = torch.FloatTensor(X_scaled).view(-1, 1, 8, 8)
y_tensor = torch.LongTensor(y)
# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X_tensor, y_tensor, test_size=0.2, random_state=42, stratify=y)
# Define CNN model
class DigitsCNN(nn.Module):
def __init__(self):
super(DigitsCNN, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(16),
nn.MaxPool2d(2)
)
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(32),
nn.MaxPool2d(2)
)
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 2 * 2, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, 10)
)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.fc(x)
return x
# Hyperparameters
epochs = 20
batch_size = 32
learning_rate = 0.001
# Data loaders
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# Initialize model, loss, and optimizer
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = DigitsCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
start_time = time.time()
for epoch in range(epochs):
model.train()
total_loss = 0
for batch_X, batch_y in train_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
optimizer.zero_grad()
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss:.4f}")
training_time = time.time() - start_time
# Evaluation
model.eval()
all_preds = []
all_labels = []
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X = batch_X.to(device)
outputs = model(batch_X)
preds = outputs.argmax(dim=1).cpu().numpy()
all_preds.extend(preds)
all_labels.extend(batch_y.numpy())
accuracy = accuracy_score(all_labels, all_preds)
precision, recall, f1, _ = precision_recall_fscore_support(all_labels, all_preds, average='macro')
# Output results
print("\nEvaluation Metrics:")
print(f"Accuracy: {accuracy:.4f}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")
print(f"Training Time: {training_time:.2f} seconds")
(2)CNN+Sigmoid
import matplotlib
matplotlib.use('Agg')
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import matplotlib.pyplot as plt
import pandas as pd
import time
import joblib
def plot_training_history(all_histories, param_combinations, digit):
"""绘制所有实验的训练历史
Args:
all_histories: 列表,包含每组参数的(train_losses, test_accuracies)
param_combinations: 列表,包含每组实验的参数信息
digit: 当前处理的数字
"""
plt.figure(figsize=(20, 10))
# 绘制损失曲线
plt.subplot(1, 2, 1)
for (train_losses, _), params in zip(all_histories, param_combinations):
label = f"e{params['epochs']}_lr{params['learning_rate']}_b{params['batch_size']}_n{params['total_samples']}"
plt.plot(train_losses, label=label)
plt.title(f'数字{digit}的不同参数组合训练损失曲线对比')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid(True)
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 绘制准确率曲线
plt.subplot(1, 2, 2)
for (_, test_accuracies), params in zip(all_histories, param_combinations):
label = f"e{params['epochs']}_lr{params['learning_rate']}_b{params['batch_size']}_n{params['total_samples']}"
plt.plot(test_accuracies, label=label)
plt.title(f'数字{digit}的不同参数组合测试集准确率曲线对比')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.grid(True)
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.savefig(f'binary_training_history_digit{digit}.png', bbox_inches='tight', dpi=300)
plt.close()
def load_and_preprocess_data(total_samples=None):
"""加载并预处理数据"""
print("正在加载数据...")
digits = load_digits()
X = digits.data
y = digits.target
if total_samples is not None and total_samples < len(X):
# 随机选择指定数量的样本
indices = np.random.choice(len(X), total_samples, replace=False)
X = X[indices]
y = y[indices]
print(f"使用样本数量: {len(X)}")
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 重塑数据为CNN输入格式 (N, C, H, W)
X_reshaped = X_scaled.reshape(-1, 1, 8, 8)
# 转换为PyTorch张量
X_tensor = torch.FloatTensor(X_reshaped)
y_tensor = torch.LongTensor(y)
return X_tensor, y_tensor, scaler
class DigitsCNNBinary(nn.Module):
def __init__(self):
super(DigitsCNNBinary, self).__init__()
# 第一个卷积层
self.conv1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(16),
nn.MaxPool2d(2)
)
# 第二个卷积层
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(32),
nn.MaxPool2d(2)
)
# 全连接层
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 2 * 2, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, 1), # 输出改为1个神经元
nn.Sigmoid() # 添加Sigmoid激活函数
)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.fc(x)
return x
def create_binary_labels(y, target_digit):
"""创建二分类标签"""
return (y == target_digit).float()
def train_binary_model(model, train_loader, test_loader, device, epochs=50, learning_rate=0.001):
"""训练二分类CNN模型"""
criterion = nn.BCELoss() # 使用二元交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
train_losses = []
test_accuracies = []
best_accuracy = 0
best_model_state = None
for epoch in range(epochs):
model.train()
total_loss = 0
start_time = time.time()
for batch_X, batch_y in train_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
optimizer.zero_grad()
outputs = model(batch_X)
loss = criterion(outputs.squeeze(), batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item()
# 评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
outputs = model(batch_X)
predicted = (outputs.squeeze() > 0.5).float()
total += batch_y.size(0)
correct += (predicted == batch_y).sum().item()
accuracy = correct / total
epoch_time = time.time() - start_time
if accuracy > best_accuracy:
best_accuracy = accuracy
best_model_state = model.state_dict()
train_losses.append(total_loss)
test_accuracies.append(accuracy)
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {total_loss:.4f}, '
f'Accuracy: {accuracy:.4f}, Time: {epoch_time:.2f}s')
return best_model_state, train_losses, test_accuracies
def evaluate_binary_model(model, test_loader, device):
"""评估二分类模型性能"""
model.eval()
all_preds = []
all_labels = []
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
outputs = model(batch_X)
predicted = (outputs.squeeze() > 0.5).float()
all_preds.extend(predicted.cpu().numpy())
all_labels.extend(batch_y.cpu().numpy())
accuracy = accuracy_score(all_labels, all_preds)
precision, recall, f1, _ = precision_recall_fscore_support(all_labels, all_preds, average='binary')
return {
'准确率': accuracy,
'精确率': precision,
'召回率': recall,
'F1分数': f1
}
def run_experiment_for_digit(X_train, X_test, y_train, y_test, target_digit, params, device):
"""为单个数字运行实验"""
# 创建二分类标签
y_train_binary = create_binary_labels(y_train, target_digit)
y_test_binary = create_binary_labels(y_test, target_digit)
# 创建数据加载器
train_dataset = TensorDataset(X_train, y_train_binary)
test_dataset = TensorDataset(X_test, y_test_binary)
train_loader = DataLoader(train_dataset, batch_size=params['batch_size'], shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=params['batch_size'], shuffle=False)
# 创建模型
model = DigitsCNNBinary().to(device)
# 训练模型
start_time = time.time()
best_model_state, train_losses, test_accuracies = train_binary_model(
model, train_loader, test_loader, device,
epochs=params['epochs'],
learning_rate=params['learning_rate']
)
total_time = time.time() - start_time
# 加载最佳模型
model.load_state_dict(best_model_state)
# 评估模型
metrics = evaluate_binary_model(model, test_loader, device)
metrics['训练时间(秒)'] = total_time
return metrics, model, (train_losses, test_accuracies)
def main():
# 设置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# 定义参数网格
param_grid = {
'epochs': [30, 50],
'learning_rate': [0.0001, 0.00001],
'batch_size': [16, 32],
'train_size': [0.8],
'total_samples': [500, 1797] # 只使用两个数据量进行对比
}
# 存储所有实验结果
all_results = []
# 为每个数字存储所有参数组合的训练历史
digit_histories = {i: {'histories': [], 'params': []} for i in range(10)}
# 遍历所有参数组合
for epochs in param_grid['epochs']:
for lr in param_grid['learning_rate']:
for batch_size in param_grid['batch_size']:
for train_size in param_grid['train_size']:
for total_samples in param_grid['total_samples']:
print(f"\n实验参数: epochs={epochs}, lr={lr}, "
f"batch_size={batch_size}, train_size={train_size}, "
f"total_samples={total_samples}")
# 加载数据
X_tensor, y_tensor, scaler = load_and_preprocess_data(total_samples)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X_tensor, y_tensor,
test_size=1-train_size,
random_state=42,
stratify=y_tensor
)
params = {
'epochs': epochs,
'learning_rate': lr,
'batch_size': batch_size,
'total_samples': total_samples
}
# 为每个数字训练模型
for digit in range(10):
print(f"\n训练数字 {digit} 的模型...")
metrics, model, history = run_experiment_for_digit(
X_train, X_test, y_train, y_test,
digit, params, device
)
# 保存训练历史
digit_histories[digit]['histories'].append(history)
digit_histories[digit]['params'].append(params.copy())
# 保存结果
result = {
'数字': digit,
'epochs': epochs,
'learning_rate': lr,
'batch_size': batch_size,
'train_size': train_size,
'total_samples': total_samples,
'准确率': metrics['准确率'],
'精确率': metrics['精确率'],
'召回率': metrics['召回率'],
'F1分数': metrics['F1分数'],
'训练时间(秒)': metrics['训练时间(秒)']
}
all_results.append(result)
# 保存模型
torch.save(model.state_dict(),
f'binary_model_digit{digit}_e{epochs}_lr{lr}_b{batch_size}.pth')
# 为每个数字绘制训练历史对比图
for digit in range(10):
plot_training_history(
digit_histories[digit]['histories'],
digit_histories[digit]['params'],
digit
)
# 将所有结果保存到CSV文件
results_df = pd.DataFrame(all_results)
results_df.to_csv('cnn_binary_experiments.csv', index=False)
# 计算每个参数组合的平均性能
avg_results = results_df.groupby(['epochs', 'learning_rate', 'batch_size', 'train_size', 'total_samples']).mean()
avg_results.to_csv('cnn_binary_average_results.csv')
# 找出最佳参数组合
best_params = avg_results.loc[avg_results['准确率'].idxmax()]
print("\n最佳参数组合(平均性能):")
print(f"Epochs: {best_params.name[0]}")
print(f"Learning Rate: {best_params.name[1]}")
print(f"Batch Size: {best_params.name[2]}")
print(f"Train Size: {best_params.name[3]}")
print(f"Total Samples: {best_params.name[4]}")
print(f"平均准确率: {best_params['准确率']:.4f}")
print(f"平均精确率: {best_params['精确率']:.4f}")
print(f"平均召回率: {best_params['召回率']:.4f}")
print(f"平均F1分数: {best_params['F1分数']:.4f}")
print(f"平均训练时间: {best_params['训练时间(秒)']:.2f}秒")
if __name__ == "__main__":
main()
(3)CNN+SVM
import matplotlib
matplotlib.use('Agg')
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import matplotlib.pyplot as plt
import pandas as pd
import time
import joblib
class FeatureExtractor(nn.Module):
def __init__(self, feature_dim=128):
super(FeatureExtractor, self).__init__()
self.feature_dim = feature_dim
# 第一个卷积层
self.conv1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(16),
nn.MaxPool2d(2)
)
# 第二个卷积层
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.BatchNorm2d(32),
nn.MaxPool2d(2)
)
# 特征映射层
self.feature_layer = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 2 * 2, feature_dim),
nn.ReLU(),
nn.BatchNorm1d(feature_dim)
)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.feature_layer(x)
return x
def load_and_preprocess_data(total_samples=None):
"""加载并预处理数据"""
print("正在加载数据...")
digits = load_digits()
X = digits.data
y = digits.target
if total_samples is not None and total_samples < len(X):
indices = np.random.choice(len(X), total_samples, replace=False)
X = X[indices]
y = y[indices]
print(f"使用样本数量: {len(X)}")
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 重塑数据为CNN输入格式
X_reshaped = X_scaled.reshape(-1, 1, 8, 8)
X_tensor = torch.FloatTensor(X_reshaped)
return X_tensor, y, scaler
def extract_features(model, data_loader, device):
"""使用CNN提取特征"""
features = []
labels = []
model.eval()
with torch.no_grad():
for batch_X, batch_y in data_loader:
batch_X = batch_X.to(device)
batch_features = model(batch_X)
features.append(batch_features.cpu().numpy())
labels.extend(batch_y.numpy())
return np.vstack(features), np.array(labels)
def train_and_evaluate(feature_dim, kernel, C, X_train, X_test, y_train, y_test, device, batch_size=32):
"""训练并评估CNN+SVM模型"""
# 创建特征提取器
feature_extractor = FeatureExtractor(feature_dim=feature_dim).to(device)
# 创建数据加载器
train_dataset = TensorDataset(X_train, torch.LongTensor(y_train))
test_dataset = TensorDataset(X_test, torch.LongTensor(y_test))
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# 提取特征
print("提取训练集特征...")
train_features, train_labels = extract_features(feature_extractor, train_loader, device)
print("提取测试集特征...")
test_features, test_labels = extract_features(feature_extractor, test_loader, device)
# 训练SVM
print("训练SVM分类器...")
start_time = time.time()
svm = SVC(kernel=kernel, C=C, probability=True)
svm.fit(train_features, train_labels)
train_time = time.time() - start_time
# 预��并评估
y_pred = svm.predict(test_features)
accuracy = accuracy_score(test_labels, y_pred)
precision, recall, f1, _ = precision_recall_fscore_support(test_labels, y_pred, average='macro')
return {
'准确率': accuracy,
'精确率': precision,
'召回率': recall,
'F1分数': f1,
'训练时间(秒)': train_time
}, feature_extractor, svm
def plot_training_results(results_df):
"""绘制不同参数组合的性能对比图"""
plt.figure(figsize=(15, 10))
# 准备数据
param_combinations = results_df.groupby(['feature_dim', 'kernel', 'C', 'total_samples']).mean()
# 绘制性能指标
metrics = ['准确率', '精确率', '召回率', 'F1分数']
for i, metric in enumerate(metrics, 1):
plt.subplot(2, 2, i)
param_combinations[metric].plot(kind='bar')
plt.title(f'不同参数组合的{metric}对比')
plt.xlabel('参数组合')
plt.ylabel(metric)
plt.xticks(rotation=45)
plt.grid(True)
plt.tight_layout()
plt.savefig('cnn_svm_performance.png', bbox_inches='tight', dpi=300)
plt.close()
def main():
# 设置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# 定义参数网格
param_grid = {
'feature_dim': [64, 128, 256],
'kernel': ['linear', 'rbf', 'poly'],
'C': [0.1, 1, 10],
'batch_size': [32],
'train_size': [0.8],
'total_samples': [500, 1797]
}
# 存储所有实验结果
all_results = []
# 遍历所有参数组合
for feature_dim in param_grid['feature_dim']:
for kernel in param_grid['kernel']:
for C in param_grid['C']:
for batch_size in param_grid['batch_size']:
for train_size in param_grid['train_size']:
for total_samples in param_grid['total_samples']:
print(f"\n实验参数: feature_dim={feature_dim}, kernel={kernel}, "
f"C={C}, batch_size={batch_size}, "
f"train_size={train_size}, total_samples={total_samples}")
# 加载数据
X_tensor, y, scaler = load_and_preprocess_data(total_samples)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X_tensor, y,
test_size=1-train_size,
random_state=42,
stratify=y
)
# 训练和评估模型
metrics, feature_extractor, svm = train_and_evaluate(
feature_dim, kernel, C,
X_train, X_test, y_train, y_test,
device, batch_size
)
# 保存结果
result = {
'feature_dim': feature_dim,
'kernel': kernel,
'C': C,
'batch_size': batch_size,
'train_size': train_size,
'total_samples': total_samples,
'准确率': metrics['准确率'],
'精确率': metrics['精确率'],
'召回率': metrics['召回率'],
'F1分数': metrics['F1分数'],
'训练时间(秒)': metrics['训练时间(秒)']
}
all_results.append(result)
# 保存模型
model_name = f'cnn_svm_f{feature_dim}_k{kernel}_C{C}'
torch.save(feature_extractor.state_dict(), f'{model_name}_feature_extractor.pth')
joblib.dump(svm, f'{model_name}_svm.pkl')
# 将所有结果保存到CSV文件
results_df = pd.DataFrame(all_results)
results_df.to_csv('cnn_svm_experiments.csv', index=False)
# 绘制性能对比图
plot_training_results(results_df)
# 计算每个参数组合的平均性能
avg_results = results_df.groupby(['feature_dim', 'kernel', 'C', 'total_samples']).mean()
avg_results.to_csv('cnn_svm_average_results.csv')
# 找出最佳参数组合
best_params = avg_results.loc[avg_results['准确率'].idxmax()]
print("\n最佳参数组合:")
print(f"Feature Dimension: {best_params.name[0]}")
print(f"Kernel: {best_params.name[1]}")
print(f"C: {best_params.name[2]}")
print(f"Total Samples: {best_params.name[3]}")
print(f"准确率: {best_params['准确率']:.4f}")
print(f"精确率: {best_params['精确率']:.4f}")
print(f"召回率: {best_params['召回率']:.4f}")
print(f"F1分数: {best_params['F1分数']:.4f}")
print(f"训练时间: {best_params['训练时间(秒)']:.2f}秒")
if __name__ == "__main__":
main()
(4)SVM
import matplotlib
matplotlib.use('Agg')
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score, KFold
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
from sklearn.metrics import confusion_matrix
import joblib
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import time
def load_and_preprocess_data():
"""加载并预处理数据"""
print("正在加载数据...")
digits = load_digits()
X = digits.data
y = digits.target
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集大小: {X_train.shape[0]} 样本")
print(f"测试集大小: {X_test.shape[0]} 样本")
return X_train, X_test, y_train, y_test, scaler
def train_binary_svm(X_train, y_train, target_digit):
"""训练单个数字的二分类SVM模型"""
# 创建二分类标签
binary_y = (y_train == target_digit).astype(int)
# 创建并训练SVM模型
svm = SVC(kernel='rbf', probability=True)
svm.fit(X_train, binary_y)
return svm
def evaluate_binary_model(model, X_test, y_test, target_digit):
"""评估单个二分类模型"""
# 创建二分类标签
binary_y = (y_test == target_digit).astype(int)
# 预测
y_pred = model.predict(X_test)
# 计算性能指标
accuracy = accuracy_score(binary_y, y_pred)
precision, recall, f1, _ = precision_recall_fscore_support(binary_y, y_pred, average='binary')
return {
'准确率': accuracy,
'精确率': precision,
'召回率': recall,
'F1分数': f1
}
def train_all_models(X_train, X_test, y_train, y_test):
"""训练所有数字的SVM模型"""
results = []
models = {}
for digit in range(10):
print(f"\n训练数字 {digit} 的SVM模型...")
start_time = time.time()
# 训练模型
model = train_binary_svm(X_train, y_train, digit)
# 评估模型
metrics = evaluate_binary_model(model, X_test, y_test, digit)
train_time = time.time() - start_time
# 保存结果
results.append({
'数字': digit,
'准确率': metrics['准确率'],
'精确率': metrics['精确率'],
'召回率': metrics['召回率'],
'F1分数': metrics['F1分数'],
'训练时间(秒)': train_time
})
# 保存模型
model_filename = f'svm_model_digit_{digit}.pkl'
joblib.dump(model, model_filename)
models[digit] = model
print(f"数字 {digit} 的模型性能:")
print(f"准确率: {metrics['准确率']:.4f}")
print(f"精确率: {metrics['精确率']:.4f}")
print(f"召回率: {metrics['召回率']:.4f}")
print(f"F1分数: {metrics['F1分数']:.4f}")
print(f"训练时间: {train_time:.4f}秒")
return pd.DataFrame(results), models
def plot_performance_comparison(results_df):
"""绘制所有模型的性能对比图"""
plt.figure(figsize=(15, 8))
x = np.arange(len(results_df))
width = 0.2
# 设置y轴范围
plt.ylim(0, 1.0)
# 绘制性能指标
bars1 = plt.bar(x - width*1.5, results_df['准确率'], width, label='准确率', color='#2ecc71')
bars2 = plt.bar(x - width/2, results_df['精确率'], width, label='精确率', color='#e74c3c')
bars3 = plt.bar(x + width/2, results_df['召回率'], width, label='召回率', color='#3498db')
bars4 = plt.bar(x + width*1.5, results_df['F1分数'], width, label='F1分数', color='#f1c40f')
# 添加数值标签
def add_labels(bars):
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, height,
f'{height:.3f}',
ha='center', va='bottom', rotation=90)
add_labels(bars1)
add_labels(bars2)
add_labels(bars3)
add_labels(bars4)
plt.title('各数字SVM二分类模型的性能比较', fontsize=14)
plt.xlabel('数字', fontsize=12)
plt.ylabel('性能指标值', fontsize=12)
plt.xticks(x, results_df['数字'])
# 设置y轴刻度为百分比格式
plt.yticks(np.arange(0, 1.1, 0.1), [f'{x:.0%}' for x in np.arange(0, 1.1, 0.1)])
plt.legend(prop={'family': 'SimHei'})
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('svm_performance_comparison.png', bbox_inches='tight', dpi=300)
plt.close()
# 创建详细的性能报告表格
detailed_results = []
for _, row in results_df.iterrows():
detailed_results.append({
'数字': row['数字'],
'准确率': f"{row['准确率']:.4f}",
'精确率': f"{row['精确率']:.4f}",
'召回率': f"{row['召回率']:.4f}",
'F1分数': f"{row['F1分数']:.4f}",
'训练时间(秒)': f"{row['训练时间(秒)']:.4f}"
})
# 添加平均值和标准差
detailed_results.append({
'数字': '平均值',
'准确率': f"{results_df['准确率'].mean():.4f}",
'精确率': f"{results_df['精确率'].mean():.4f}",
'召回率': f"{results_df['召回率'].mean():.4f}",
'F1分数': f"{results_df['F1分数'].mean():.4f}",
'训练时间(秒)': f"{results_df['训练时间(秒)'].mean():.4f}"
})
detailed_results.append({
'数字': '标准差',
'准确率': f"±{results_df['准确率'].std():.4f}",
'精确率': f"±{results_df['精确率'].std():.4f}",
'召回率': f"±{results_df['召回率'].std():.4f}",
'F1分数': f"±{results_df['F1分数'].std():.4f}",
'训练时间(秒)': f"±{results_df['训练时间(秒)'].std():.4f}"
})
# 保存详细结果到CSV文件
pd.DataFrame(detailed_results).to_csv('svm_detailed_performance.csv', index=False)
def plot_training_time(results_df):
"""绘制训练时间对比图"""
plt.figure(figsize=(12, 6))
plt.bar(results_df['数字'], results_df['训练时间(秒)'], color='#3498db')
plt.title('各数字SVM模型的训练时间', fontsize=14)
plt.xlabel('数字', fontsize=12)
plt.ylabel('训练时间(秒)', fontsize=12)
plt.grid(True, alpha=0.3)
# 添加具体数值标签
for i, v in enumerate(results_df['训练时间(秒)']):
plt.text(i, v, f'{v:.2f}s', ha='center', va='bottom')
plt.tight_layout()
plt.savefig('svm_training_time.png', bbox_inches='tight', dpi=300)
plt.close()
def main():
# 加载和预处理数据
X_train, X_test, y_train, y_test, scaler = load_and_preprocess_data()
# 训练所有模型
print("\n开始训练SVM模型...")
results_df, models = train_all_models(X_train, X_test, y_train, y_test)
# 保存原始结果
results_df.to_csv('svm_results.csv', index=False)
# 绘制性能对比图并保存详细结果
print("\n生成可视化结果和详细报告...")
plot_performance_comparison(results_df)
plot_training_time(results_df)
# 输出总体性能
print("\n总体性能统计:")
print("\n平均性能:")
print(f"准确率: {results_df['准确率'].mean():.4f} (±{results_df['准确率'].std():.4f})")
print(f"精确率: {results_df['精确率'].mean():.4f} (±{results_df['精确率'].std():.4f})")
print(f"召回率: {results_df['召回率'].mean():.4f} (±{results_df['召回率'].std():.4f})")
print(f"F1分数: {results_df['F1分数'].mean():.4f} (±{results_df['F1分数'].std():.4f})")
print(f"平均训练时间: {results_df['训练时间(秒)'].mean():.4f}秒")
print("\n详细性能报告已保存到 'svm_detailed_performance.csv'")
# 保存数据处理器
joblib.dump(scaler, 'svm_scaler.pkl')
print("\n所有模型和数据处理器已保存")
if __name__ == "__main__":
main()