十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数学建模竞赛实战:从数据预处理到模型构建的完整代码实现

数学建模竞赛实战:从数据预处理到模型构建的完整代码实现 1. 项目概述从“思路”到“实现”的实战跨越看到“2022高教社杯数学建模国赛C题思路代码实现”这个标题很多参加过数模竞赛或者正在备赛的同学应该会心一笑甚至有点“血压升高”。这标题精准地戳中了数模竞赛中最核心、也最让参赛者头疼的两个环节解题思路的构建以及最终将思路落地为可运行、可验证的代码。2022年的国赛C题当年让无数队伍在三天三夜里绞尽脑汁其核心在于对古代玻璃制品的成分分析与鉴别涉及化学、统计学、模式识别等多个学科的交叉。光有漂亮的思路模型纸上谈兵不行没有代码实现支撑论文就成了无根之木反之只埋头写代码而没有清晰的建模思路引领很容易陷入局部细节做出一堆漂亮但无用的图表。这个项目标题的价值就在于它试图打通从“思维”到“实践”的最后一公里为后来者提供一个完整的、可复现的参考范例。我参加过也指导过多次数学建模竞赛深知一篇优秀的获奖论文背后是思路、模型、求解、写作的完美结合而代码是实现这一切的基石。对于C题这类数据分析与建模题目思路决定了你能走多高而代码实现决定了你能走多稳。本文将围绕2022年国赛C题深度拆解其解题全流程从题目理解、数据预处理、模型选择与建立到最终的算法实现与结果分析。我会分享当时我们团队的实际操作路径、关键决策点的思考以及那些在官方优秀论文里不会写的“踩坑”经验和调试技巧。无论你是正在备战新一轮国赛还是希望系统学习如何将一个复杂的实际问题转化为数学模型并求解这篇文章都将提供一份详尽的“作战地图”。2. 赛题核心剖析与解题总览2.1 题目回顾与问题本质提炼2022年高教社杯全国大学生数学建模竞赛C题的题目是《古代玻璃制品的成分分析与鉴别》。题目提供了考古发掘出土的一批古代玻璃文物的化学成分检测数据要求参赛者通过数据分析解决诸如玻璃类型鉴别、风化规律分析、化学成分关联、文物分类与判别等一系列问题。抛开具体的考古背景这道题的本质是一个多源数据分析与统计建模问题。数据中包含了高钾玻璃、铅钡玻璃两种类型以及风化与未风化的样本。我们需要处理的是成分数据其特点是高维小样本每个样本有十几种化学成分如二氧化硅、氧化铅、氧化钾等的百分比含量但样本总数有限。成分数据所有化学成分含量之和为100%或接近100%即数据存在于一个“单纯形”空间中各变量之间存在天然的共线性和约束。数据缺失与异常部分成分数据有缺失且由于古代工艺和风化作用数据可能存在异常值。标签部分已知部分样本已知其类型高钾/铅钡和风化状态部分未知这引导我们采用有监督、半监督或无监督学习的方法。因此解题的总体思路非常清晰以数据驱动为核心综合利用描述性统计、可视化、统计检验、机器学习与化学计量学方法从数据中挖掘规律建立鉴别与预测模型。整个工作流可以概括为数据清洗 - 探索性数据分析 - 特征工程 - 模型构建与验证 - 结果解释与论文撰写。2.2 解题技术栈与工具选型工欲善其事必先利其器。在72小时的极限竞赛中工具的选择直接影响效率。核心编程语言Python。这是不二之选。其强大的科学生态NumPy, Pandas, Scikit-learn, Matplotlib, Seaborn, Statsmodels足以覆盖本题99%的需求。相比MATLABPython在数据处理、机器学习库的丰富性和代码的灵活性上更具优势且开源免费。辅助工具Jupyter Notebook / VS Code。Jupyter非常适合做探索性数据分析可以即时看到图表和中间结果但大型项目代码管理稍弱。VS Code配合Python插件提供了更好的代码编辑、调试和版本管理体验。我们团队当时采用VS Code进行主体开发用Jupyter做快速原型验证。关键Python库Pandas NumPy数据读入、清洗、转换、计算的基石。Matplotlib Seaborn数据可视化绘制散点图、箱线图、热力图、分布图等直观展示数据规律。Scikit-learn机器学习模型库提供分类如逻辑回归、SVM、随机森林、聚类如K-Means、降维如PCA、预处理等全套工具。SciPy用于科学计算包含各种统计检验如t检验、方差分析、卡方检验和优化算法。Statsmodels用于更专业的统计分析如线性回归、逻辑回归的详细统计输出。注意不建议在竞赛中尝试使用过于新颖或冷门的库除非有十足把握稳定性和社区支持是关键。所有用到的库务必在赛前熟悉其基本API。3. 数据预处理奠定模型可靠性的基石原始数据通常很“脏”直接建模无异于沙上筑塔。预处理阶段花费的时间往往能换来模型性能成倍的提升。3.1 数据读取与初步审查首先使用Pandas读取题目提供的Excel或CSV数据文件。import pandas as pd import numpy as np # 读取数据 data_df pd.read_excel(附件.xlsx, sheet_name表单1) # 查看数据概览 print(data_df.info()) print(data_df.head()) print(data_df.describe())这一步要立刻关注数据类型各列是数值型还是对象型编号、类型等列可能需要特殊处理。缺失值用data_df.isnull().sum()快速查看各列缺失情况。异常值通过describe()查看最大值、最小值初步判断是否存在明显不合理的数据如成分含量为负或大于100。3.2 缺失值处理与成分数据归一化本题数据的缺失可能有两种含义一是未检测到含量极低二是数据丢失。需要根据化学常识和题目背景谨慎处理。常见策略删除若某个样本缺失值过多如超过一半特征可考虑删除该样本。但本题样本珍贵一般不轻易删除。填充填充为0如果认为“缺失”即代表“未检出”或含量为零可以填充0。但需格外小心因为成分数据和为100%填充0会改变数据的“组成”结构。均值/中位数填充对同一类型如高钾玻璃的样本进行均值或中位数填充相对合理。回归/模型预测填充更高级的方法但竞赛时间有限需权衡性价比。针对本题成分数据的特殊性一个更严谨的做法是进行“闭合处理”或“归一化”即使没有缺失值由于测量误差各成分之和也可能不是严格的100%。因此在填充缺失值后或对无缺失数据通常需要对每个样本的化学成分数据进行归一化使其和为100%。# 假设我们已经处理了缺失值现在对成分列进行归一化 # 定义成分列名列表 composition_cols [SiO2, Na2O, K2O, ...] # 根据实际数据列名填写 def normalize_composition(row): total row[composition_cols].sum() if total 0: return row[composition_cols] / total * 100 else: return row[composition_cols] # 防止除零错误 data_df[composition_cols] data_df.apply(normalize_composition, axis1)3.3 特征工程从原始数据中挖掘信息原始成分比例是直接特征但我们可以构造更有意义的衍生特征帮助模型学习。比值特征某些元素的比值可能具有鉴别意义如K2O/PbO钾铅比可能对区分高钾和铅钡玻璃有效。统计特征对于多个样本可以计算其某些成分的波动性标准差作为特征。风化相关特征可以计算“风化前后成分变化量”如果有配对数据或构造“抗风化指数”等。降维特征使用主成分分析PCA将高维成分数据降至2-3维既能可视化其主成分得分也可作为新特征输入模型。# 示例创建钾铅比特征 data_df[K2O_to_PbO_ratio] data_df[K2O] / (data_df[PbO] 1e-6) # 加一个小数防止除零 # 示例PCA降维并获取主成分特征 from sklearn.decomposition import PCA pca PCA(n_components3) # 保留3个主成分 pca_features pca.fit_transform(data_df[composition_cols]) data_df[PC1] pca_features[:, 0] data_df[PC2] pca_features[:, 1] data_df[PC3] pca_features[:, 2] print(f前三个主成分的方差解释比例: {pca.explained_variance_ratio_})4. 探索性数据分析与可视化在建模前必须用眼睛“看”数据。这是发现规律、形成假设的关键步骤。4.1 单变量与双变量分析分布观察绘制高钾玻璃和铅钡玻璃主要成分如SiO2, PbO, K2O的分布直方图或核密度估计图直观感受其分布差异。箱线图按玻璃类型和风化状态分组绘制各成分的箱线图。可以清晰看出成分的中位数、四分位数、异常值以及组间差异。import matplotlib.pyplot as plt import seaborn as sns # 设置绘图风格 sns.set_style(whitegrid) # 绘制SiO2含量按类型分组的箱线图 plt.figure(figsize(10, 6)) sns.boxplot(x类型, ySiO2, datadata_df, hue风化状态) # 假设列名为‘类型’和‘风化状态’ plt.title(不同玻璃类型及风化状态的SiO2含量分布) plt.show()4.2 多变量关系与相关性分析散点图矩阵选择几个关键成分绘制其两两之间的散点图并按类型着色观察是否存在线性或聚类关系。热力图计算所有化学成分之间的相关系数矩阵并用热力图显示。这有助于发现高度相关的成分为后续特征选择或处理多重共线性提供依据。# 计算相关系数矩阵 corr_matrix data_df[composition_cols].corr() # 绘制热力图 plt.figure(figsize(12, 10)) sns.heatmap(corr_matrix, annotTrue, fmt.2f, cmapcoolwarm, center0, squareTrue) plt.title(化学成分相关性热力图) plt.tight_layout() plt.show()通过EDA我们可能得到一些初步结论例如“铅钡玻璃的PbO含量显著高于高钾玻璃而高钾玻璃的K2O含量更高”“风化导致某些碱性氧化物如Na2O, K2O含量降低而某些稳定氧化物相对升高”。这些直观认识将直接指导后续的模型选择。5. 核心模型构建与代码实现这是从思路到代码的核心环节。针对C题的不同子问题需要选用不同的模型。5.1 问题一玻璃类型鉴别分类问题这是一个典型的有监督分类问题。已知部分样本的类型标签目标是对未知样本进行分类。模型选型与对比逻辑回归基础线性分类器可解释性强能给出概率输出。适合作为基线模型。支持向量机在高维小样本数据上往往表现优异特别是使用RBF核函数处理非线性边界时。随机森林集成方法能自动评估特征重要性对异常值和缺失值不敏感且不易过拟合。XGBoost/LightGBM强大的梯度提升树模型竞赛常客精度通常很高但需要调参。实操步骤与代码from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix, accuracy_score # 1. 准备数据假设‘类型_编码’是目标变量0-高钾1-铅钡且已处理缺失值 X data_df[composition_cols [K2O_to_PbO_ratio, PC1, PC2]] # 使用原始成分衍生特征主成分特征 y data_df[类型_编码] # 划分训练集和测试集用已知标签的数据 known_data data_df[data_df[类型_编码].notnull()] X_known known_data[X.columns] y_known known_data[类型_编码] X_train, X_test, y_train, y_test train_test_split(X_known, y_known, test_size0.2, random_state42, stratifyy_known) # 2. 特征标准化对SVM和逻辑回归很重要 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 3. 训练与评估多个模型 models { Logistic Regression: LogisticRegression(max_iter1000, random_state42), SVM (RBF): SVC(kernelrbf, probabilityTrue, random_state42), Random Forest: RandomForestClassifier(n_estimators100, random_state42) } for name, model in models.items(): if name SVM (RBF) or name Logistic Regression: model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) else: model.fit(X_train, y_train) y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) print(f{name} 准确率: {acc:.4f}) print(classification_report(y_test, y_pred)) # 可以绘制混淆矩阵 # cm confusion_matrix(y_test, y_pred) # sns.heatmap(cm, annotTrue, fmtd) # 4. 使用最佳模型预测未知样本 best_model RandomForestClassifier(n_estimators150, max_depth10, random_state42) # 假设RF最好并进行了调参 best_model.fit(X_known[X.columns], y_known) unknown_data data_df[data_df[类型_编码].isnull()] predictions best_model.predict(unknown_data[X.columns]) unknown_data[预测类型] predictions实操心得不要只追求最高的测试集准确率要关注模型的稳定性和可解释性。逻辑回归的系数可以解释为“成分每增加一个单位成为铅钡玻璃的对数几率变化”这对论文写作非常有利。随机森林的特征重要性排名可以直接告诉我们哪些化学成分是区分类型的关键这本身就是一项重要的分析结果。5.2 问题二风化规律分析回归/差异分析分析风化前后化学成分的变化规律。如果有配对的风化/未风化样本可视为配对样本t检验问题如果只是比较风化组和未风化组的整体差异则是两独立样本t检验或曼-惠特尼U检验非参数。更进一步可以建立多元线性回归模型以风化状态或风化程度为因变量成分为自变量分析各成分对风化的贡献。from scipy import stats # 示例比较风化与未风化玻璃的SiO2含量差异两独立样本 weathered_sio2 data_df[data_df[风化状态]风化][SiO2] unweathered_sio2 data_df[data_df[风化状态]无风化][SiO2] # 先进行方差齐性检验 levene_stat, levene_p stats.levene(weathered_sio2, unweathered_sio2) if levene_p 0.05: # 方差齐使用独立样本t检验 t_stat, t_p stats.ttest_ind(weathered_sio2, unweathered_sio2, equal_varTrue) test_used 独立样本t检验(方差齐) else: # 方差不齐使用Welchs t检验 t_stat, t_p stats.ttest_ind(weathered_sio2, unweathered_sio2, equal_varFalse) test_used Welchs t检验(方差不齐) print(f使用 {test_used}) print(ft统计量: {t_stat:.4f}, p值: {t_p:.4e}) if t_p 0.05: print(在0.05显著性水平下风化与未风化玻璃的SiO2含量存在显著差异。) else: print(在0.05显著性水平下未发现显著差异。) # 可视化 plt.figure(figsize(8,5)) sns.boxplot(x风化状态, ySiO2, datadata_df) plt.title(风化与未风化玻璃SiO2含量对比) plt.show()5.3 问题三化学成分关联与亚类划分聚类分析分析化学成分之间的关联关系除了之前的相关性热力图还可以使用聚类分析来探索样本是否自然形成不同的亚类。K-Means聚类和层次聚类是常用方法。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 使用标准化后的成分数据进行聚类 X_for_cluster scaler.fit_transform(data_df[composition_cols]) # 寻找最佳K值肘部法则或轮廓系数 inertia [] sil_scores [] K_range range(2, 10) for k in K_range: kmeans KMeans(n_clustersk, random_state42, n_initauto) kmeans.fit(X_for_cluster) inertia.append(kmeans.inertia_) sil_scores.append(silhouette_score(X_for_cluster, kmeans.labels_)) # 绘制肘部法则图和轮廓系数图 fig, axes plt.subplots(1, 2, figsize(14,5)) axes[0].plot(K_range, inertia, bo-) axes[0].set_xlabel(K) axes[0].set_ylabel(Inertia) axes[0].set_title(Elbow Method) axes[1].plot(K_range, sil_scores, ro-) axes[1].set_xlabel(K) axes[1].set_ylabel(Silhouette Score) axes[1].set_title(Silhouette Score) plt.show() # 根据图表选择K值例如K3 best_k 3 final_kmeans KMeans(n_clustersbest_k, random_state42, n_initauto) cluster_labels final_kmeans.fit_predict(X_for_cluster) data_df[聚类标签] cluster_labels # 将聚类结果与已知类型对比观察一致性 if 类型 in data_df.columns: cross_tab pd.crosstab(data_df[类型], data_df[聚类标签]) print(聚类结果与已知类型的交叉表) print(cross_tab) # 可视化聚类结果使用前两个主成分 pca_for_viz PCA(n_components2) X_pca pca_for_viz.fit_transform(X_for_cluster) plt.figure(figsize(10,8)) scatter plt.scatter(X_pca[:,0], X_pca[:,1], ccluster_labels, cmapviridis, alpha0.7) if 类型 in data_df.columns: # 用形状区分已知类型 for idx, row in data_df.iterrows(): if pd.notnull(row[类型]): marker o if row[类型]高钾 else s plt.scatter(X_pca[idx,0], X_pca[idx,1], cred, markermarker, s100, edgecolorsblack) plt.colorbar(scatter, labelCluster Label) plt.xlabel(Principal Component 1) plt.ylabel(Principal Component 2) plt.title(K-Means Clustering Result (with known type overlay)) plt.legend(handles[...]) # 可添加图例 plt.show()6. 模型优化、验证与结果整合6.1 模型调参与交叉验证在初步建模后需要对表现好的模型进行调优。以随机森林为例from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid { n_estimators: [50, 100, 200], max_depth: [5, 10, 15, None], min_samples_split: [2, 5, 10], min_samples_leaf: [1, 2, 4] } rf RandomForestClassifier(random_state42) grid_search GridSearchCV(estimatorrf, param_gridparam_grid, cv5, scoringaccuracy, n_jobs-1, verbose1) grid_search.fit(X_train, y_train) print(f最佳参数: {grid_search.best_params_}) print(f最佳交叉验证分数: {grid_search.best_score_:.4f}) best_rf grid_search.best_estimator_ # 在测试集上最终评估 y_pred_final best_rf.predict(X_test) print(f调优后测试集准确率: {accuracy_score(y_test, y_pred_final):.4f})交叉验证是评估模型泛化能力、防止过拟合的关键。务必使用cross_val_score对最终模型进行稳健性评估。6.2 结果分析与论文图表生成模型输出的不仅仅是预测标签更重要的是分析结果。特征重要性从随机森林或XGBoost模型中提取特征重要性绘制条形图直观展示哪些化学成分对分类贡献最大。决策边界可视化对于二维或三维特征如两个主成分可以绘制分类器的决策边界增强论文的可读性。概率输出对于逻辑回归等模型可以输出属于各类别的概率为“不确定”的样本提供置信度参考。# 绘制随机森林特征重要性 importances best_rf.feature_importances_ feature_names X.columns indices np.argsort(importances)[::-1] plt.figure(figsize(12,6)) plt.title(Feature Importances (Random Forest)) plt.bar(range(len(indices)), importances[indices], aligncenter) plt.xticks(range(len(indices)), [feature_names[i] for i in indices], rotation90) plt.tight_layout() plt.show()6.3 代码模块化与工程化建议竞赛时间紧张但良好的代码结构能极大提升效率和减少错误。模块化将数据加载、预处理、特征工程、模型训练、评估等步骤写成独立的函数或类。配置文件将文件路径、模型参数等写入配置文件如config.yaml或config.py便于统一修改。版本控制即使一个人作战也建议用Git进行简单的版本管理关键时刻可以回退。结果保存将关键的中间数据如处理后的干净数据和最终结果预测标签、模型对象保存为文件如.csv,.pkl避免重复计算。# 示例保存模型和预测结果 import joblib # 保存最佳模型 joblib.dump(best_rf, best_random_forest_model.pkl) # 保存未知样本的预测结果 unknown_data[[文物编号, 预测类型]].to_csv(unknown_samples_predictions.csv, indexFalse)7. 常见问题与实战避坑指南7.1 数据预处理中的陷阱陷阱一忽视成分数据的“定和约束”。直接对成分数据进行标准化如Z-score或填充缺失值可能会破坏其和为100%的结构导致后续分析出现偏差。务必先处理缺失值再进行归一化或使用专门针对成分数据的处理方法如对数比变换。陷阱二异常值盲目删除。古代玻璃成分本身可能波动很大一个看似异常的值可能是某种特殊工艺的体现。应先结合化学知识和题目背景判断或使用箱线图、3σ原则等进行甄别对于确认为测量错误或录入错误的再考虑处理。陷阱三训练数据泄露。在填充缺失值或进行特征缩放时如果使用了全数据集包括测试集的统计量如均值、标准差会导致信息泄露模型评估结果过于乐观。必须严格区分训练集和测试集所有预处理步骤的拟合如scaler.fit()只应在训练集上进行然后应用到测试集。7.2 模型选择与评估的误区误区一唯准确率论。在类别不平衡的数据集上如高钾和铅钡样本数量悬殊准确率可能具有欺骗性。必须同时查看精确率、召回率、F1-score和混淆矩阵。对于C题两类玻璃的误判成本可能不同需要根据题目要求权衡。误区二不进行交叉验证。仅用一次训练测试分割评估模型结果具有偶然性。一定要使用K折交叉验证来获得模型性能的稳健估计。误区三过度调参与过拟合。在小型数据集上进行过于复杂的网格搜索很容易找到在训练集上表现极好但在未知数据上泛化很差的参数组合。调参范围要合理并始终用验证集或交叉验证来监控模型是否过拟合。7.3 时间管理与团队协作第一天Day 1应完成题目精读、数据初步探索、预处理方案确定和基础可视化。形成初步的解题思路报告。第二天Day 2集中火力进行核心建模、代码实现和初步结果分析。尝试多种模型并进行快速评估。第三天Day 3模型优化、结果整合、论文写作与图表美化。务必留出至少6-8小时进行论文撰写和排版。代码和论文要同步更新。团队协作明确分工一人主攻建模代码一人主攻论文写作一人负责数据处理和可视化辅助。但核心思路必须共同讨论确定。使用Git和云协作工具如Overleaf for LaTeX可以有效管理代码和文档版本。7.4 论文写作与结果呈现图表即语言论文中的图表要精美、自明。每个图表都应有清晰的标题、坐标轴标签和图例。多用组合图如分组箱线图、散点图矩阵来高效传递信息。模型描述要清晰不仅要说用了什么模型还要说为什么用这个模型以及关键参数的选择依据如为什么选择RBF核的SVM为什么随机森林的树数量设为100。结果分析要深入不要只罗列“准确率95%”要分析为什么模型能达到这个效果例如特征重要性显示PbO和K2O是关键区分因素这与化学知识吻合。也要分析错在了哪里哪些样本被分错了可能是什么原因。代码与论文的衔接在论文中提及的关键结果、图表其生成代码必须清晰、可复现。可以将核心代码片段作为附录。数学建模竞赛是思维、实践与表达的全面比拼。“思路代码实现”这个标题恰恰点明了其精髓清晰的思路是灵魂可靠的代码是骨骼而严谨的论文则是血肉。希望这份基于2022年国赛C题的深度解析能为你打开一扇窗让你看到从问题到代码从数据到结论的完整路径。真正的提升还需要你在下一个赛题中亲自走一遍这条路去经历那些选择、调试和顿悟的时刻。
返回列表