简介:面向数学建模竞赛参赛者、数据分析初学者与算法爱好者的一份实用算法代码合集,围绕30个数学建模高频模型提供Python实现,覆盖ARIMA时间序列预测、逻辑回归、二次规划、神经网络分类、线性规划、模拟退火、K-means聚类、随机森林、马尔科夫预测、动态规划、BP神经网络、整数规划、模糊综合评价、卷积神经网络、层次分析法、决策树、0-1背包、最短路径、插值拟合、TOPSIS评价、支持向量机、粒子群、多目标模糊评价、灰色预测、遗传算法、主成分分析、蒙特卡洛、非线性规划、判别分析等方向,每个模型均附有可直接参考或改写的Python代码及配套说明文档。压缩包共39个文件,以txt算法代码/说明、docx详细笔记、py脚本为主体,辅以dat数据文件、rar子压缩包、spec配置与exe可执行程序,整体大小约129.22MB,目录按算法分类清晰,便于按需检索。该资源已有1653人浏览学习,适合赛前集中刷模型、对照代码理解原理,也可作为课程设计与论文实验的参考工具。
1. 数学建模30个常用算法Python代码:一份能把备赛周期压缩一半的算法库
参加过全国大学生数学建模竞赛或者华为杯研究生数学建模的同学应该都有同感:拿到赛题后最耗时间的不是建模思路,而是算法代码的调试。想用ARIMA做时间序列预测,光差分阶数就能调一晚上;想用粒子群解优化问题,初始温度和迭代次数设不对,结果就跑飞。这套「数学建模30个常用算法Python代码」把预测、分类、优化、评价四大类模型里最高频的30种算法一次性收齐,每种都给了可直接运行的Python实现,从ARIMA、灰色预测到遗传算法、TOPSIS评价全覆盖。适合正在备战国赛、华为杯,或者毕业论文里需要快速验证算法效果的读者,拿到手后替换成自己的数据、按下面说的调参思路改几个参数就能跑出结果。
2. 预测类算法先选型:ARIMA、灰色预测、马尔科夫的适用边界与代码骨架
2.1 预测类模型的选型逻辑:先看数据形态再挑算法
建模比赛里的预测题几乎年年都有,但很多人上来就套ARIMA,这是最常见的翻车起点。预测类算法不是越复杂越好,而是越匹配数据形态越好。拿到一份数据,我一般会先回答三个问题:数据是不是时间序列,样本量有多少,数据本身有没有明显的状态转移特征。
时间序列连续数值、样本量在30以上,优先考虑ARIMA,它把自回归和滑动平均组合在一起,对平稳序列的拟合能力很强。样本量小到只有几个到十几个点,而且序列呈近似指数增长或衰减,灰色预测GM(1,1)是更稳妥的选择,它不需要大量历史数据,用累加生成把随机性抹平再建模。数据是离散状态之间的转移,比如天气晴雨、用户购买行为、设备运行状态,马尔科夫模型更合适,它关注的是状态之间的转移概率而不是数值本身。
| 算法 | 适用数据形态 | 前提条件 | 输出形态 |
|---|---|---|---|
| ARIMA | 连续时间序列,样本量≥30 | 差分后平稳 | 连续数值预测 |
| 灰色预测GM(1,1) | 小样本指数趋势序列 | 级比检验通过 | 连续数值预测 |
| 马尔科夫/HMM | 离散状态序列或观测序列 | 状态转移平稳 | 状态概率分布 |
2.2 ARIMA参数d、p、q的确定:差分检验与ACF/PACF判读
ARIMA三个参数里面,d是最容易定的,做一阶或二阶差分直到序列通过ADF检验就行。麻烦的是p和q,很多人直接靠猜,其实标准做法是先画ACF和PACF图,看它们的截尾和拖尾特征。ACF在q阶后截尾,PACF拖尾,判定为MA(q);PACF在p阶后截尾,ACF拖尾,判定为AR(p);两者都拖尾就考虑ARMA。
import pandas as pd from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from statsmodels.tsa.arima.model import ARIMA # 读取时间序列,date列解析成索引 series = pd.read_csv("sales.csv", parse_dates=["date"], index_col="date") # 先做ADF平稳性检验,p值>0.05说明不平稳,需要差分 result = adfuller(series["value"]) print("ADF p值:", result[1]) # 一阶差分后再做一次检验 diff1 = series["value"].diff().dropna() print("一阶差分后ADF p值:", adfuller(diff1)[1]) # 看差分后序列的ACF和PACF图,确定p和q plot_acf(diff1) plot_pacf(diff1) # 拟合ARIMA(1,1,1),p、q取图上判读的值 model = ARIMA(series["value"], order=(1, 1, 1)) result = model.fit() print(result.summary()) # 预测未来7期 forecast = result.forecast(steps=7) print(forecast)order里的三个数字对应p、d、q,p是自回归阶数,d是差分阶数,q是滑动平均阶数。p、q的取值不需要精确到极致,比赛里更看重模型能不能解释数据、预测方向对不对,追求过高的拟合精度反而会过拟合历史噪声。statsmodels拟合后记得把summary打出来看,重点关注AIC和BIC,它们越小说明模型在拟合度和复杂度之间平衡得越好。
2.3 灰色预测与马尔科夫的代码骨架:小样本和状态转移的兜底方案
灰色预测GM(1,1)是建模比赛里的小样本神器,核心思想是把原始数据做一次累加生成,让随机波动被弱化,再用一阶微分方程去拟合这条生成序列。代码实现不复杂,但级比检验这一步不能省,否则预测结果可能完全不能用。
import numpy as np def gm11(data, predict_len=5): n = len(data) # 级比检验:lambda落在可容覆盖区间才算通过 lambda_k = data[:-1] / data[1:] valid_range = (np.exp(-2 / (n + 1)), np.exp(2 / (n + 1))) if not (valid_range[0] <= np.min(lambda_k) and np.max(lambda_k) <= valid_range[1]): print("级比检验未通过,需对序列做平移或开方变换") # 累加生成序列 x1 = np.cumsum(data) # 紧邻均值生成序列 z1 z1 = (x1[:-1] + x1[1:]) / 2 # 构造数据矩阵 B 和观测向量 Y B = np.vstack([-z1, np.ones(n - 1)]).T Y = data[1:] # 最小二乘估计参数:a为发展系数,b为灰作用量 a, b = np.linalg.inv(B.T @ B) @ B.T @ Y # 时间响应函数,还原预测值 x1_hat = (data[0] - b / a) * np.exp(-a * np.arange(1, n + predict_len)) + b / a # 累减还原成原始量纲的预测值 x0_hat = np.diff(x1_hat, prepend=data[0]) return x0_hat data = np.array([15.3, 16.8, 18.2, 19.9, 22.1]) forecast = gm11(data, predict_len=3) print("原始数据后3期预测:", forecast)B矩阵第一列是紧邻均值序列的负值,第二列是全1,这是GM(1,1)的标准构造方式。a的绝对值越大,序列的增长或衰减趋势越陡;b的符号决定预测方向。代码里用prepend参数保证累减还原后第一个值和原始数据对齐,很多人自己手写累减时容易在这个位置多算或少算一个点。马尔科夫模型在资源包里给到的是HMM的Python实现,适合做隐状态的序列标注,比如根据观测到的用户行为推断后续状态,它的核心是前向算法和后向算法,观测序列的长度直接影响转移概率矩阵的估计精度,样本太少时转移概率会失真。
3. 分类与聚类模型实战:逻辑回归、SVM、K-means的参数取舍
3.1 分类模型怎么选:逻辑回归、SVM、随机森林的边界划分
分类问题是建模比赛里的另一大类,但很多人一上来就上神经网络,这其实是把简单问题复杂化。逻辑回归适合二分类任务,输出的是概率而不是硬标签,而且系数可以解释成特征对结果的贡献方向,答辩时特别好讲。SVM适合样本量中等、特征维度较高的分类任务,通过核函数把低维不可分的数据映射到高维空间,但核函数选错了效果还不如逻辑回归。随机森林对特征多、有缺失值、非线性关系明显的数据更稳,它天生能处理混合类型特征,不容易过拟合。
需要说明的是,sklearn里的MLPClassifier和资源包里的神经网络分类代码都属于浅层神经网络,做数字识别这类图像任务时可以跑通,但遇到复杂图片分类还是得用卷积神经网络。比赛里如果数据是表格型的,优先试逻辑回归和随机森林,最后再用神经网络做对比,而不是反过来一上来就把模型复杂度拉满。
| 模型 | 适用场景 | 主要局限 | 关键参数 |
|---|---|---|---|
| 逻辑回归 | 二分类、需要概率解释 | 线性边界 | C、solver |
| SVM | 中小样本高维特征 | 核函数敏感 | C、kernel |
| 随机森林 | 特征多、非线性明显 | 预测慢 | n_estimators、max_depth |
| MLP | 中等复杂度模式识别 | 需要特征缩放 | hidden_layer_sizes |
3.2 逻辑回归与SVM代码落地:标准化这一步千万别省
逻辑回归和SVM在sklearn里都是几行代码的事,但有一个隐藏前置步骤,就是特征标准化。逻辑回归的损失函数里包含了惩罚项,SVM依赖距离度量,如果特征量纲差异大,量纲大的特征会主导模型训练,系数解释性也全乱了。我自己的习惯是无论用什么分类器,先StandardScaler一遍再进模型。
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.metrics import classification_report # X是特征矩阵,y是标签,先划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 标准化:fit在训练集上做,transform在测试集上做 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 逻辑回归:C越小正则越强,lbfgs适合中小数据集 clf_lr = LogisticRegression(C=1.0, solver="lbfgs", max_iter=1000) clf_lr.fit(X_train_scaled, y_train) print("逻辑回归结果:") print(classification_report(y_test, clf_lr.predict(X_test_scaled))) # SVM:rbf核适合非线性边界,C控制误分类惩罚 clf_svm = SVC(C=1.0, kernel="rbf", gamma="scale") clf_svm.fit(X_train_scaled, y_train) print("SVM结果:") print(classification_report(y_test, clf_svm.predict(X_test_scaled)))C是正则化强度的倒数,C越小对误分类的惩罚越轻,模型越简单;C越大模型越倾向于拟合每一个训练样本,容易过拟合。gamma在SVM里控制的是径向基核的作用半径,默认用scale会自动根据特征数量调整,一般不用手动改。测试集上如果逻辑回归和SVM的F1分数差不多,比赛里优先选逻辑回归,因为解释性更强,写论文时能给出每个特征的系数和方向。
3.3 K-means聚类与神经网络:K值确定和特征缩放
K-means聚类看起来简单,实际用起来最容易出问题的是K值怎么定。不要凭感觉拍脑袋,先跑肘部法则,画出不同K值下SSE(簇内误差平方和)的变化曲线,找到拐点位置。另外聚类前不做StandardScaler的话,距离计算会被数值范围大的特征完全绑架,聚类结果几乎没有意义。
import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt # 假设features是已经读取的数值型特征矩阵 scaler = StandardScaler() features_scaled = scaler.fit_transform(features) # 肘部法则:计算K从1到10的SSE sse = [] k_range = range(1, 11) for k in k_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(features_scaled) sse.append(kmeans.inertia_) # 画出SSE曲线,观察拐点 plt.plot(k_range, sse, marker="o") plt.xlabel("K") plt.ylabel("SSE") plt.show()n_init表示k-means用不同初始质心跑的次数,默认10次取最优,random_state固定后才能复现结果。inertia就是SSE,K增大时SSE会持续下降,真正的拐点在哪,图上肉眼判断即可。这个资源包里还包含了CNN的Python代码,处理图像分类时用,但CNN前必须把图像像素值归一化到0到1区间,并且需要足够的样本量,否则很容易在训练集上表现好、测试集上一塌糊涂。
4. 规划与智能优化算法:线性规划到遗传算法的框架与调参
4.1 规划模型三分支:线性、整数、非线性的场景划分
优化类问题在建模比赛里占的比重很大,资源包里的线性规划、整数规划、非线性规划、二次规划、动态规划、0-1背包、最短路径、模拟退火、粒子群、遗传算法,这些都是优化问题的不同解法。选算法之前先搞清楚目标函数和约束条件的性质:目标函数和约束都是线性的,用线性规划;要求决策变量是整数,用整数规划;目标函数或约束里带非线性项,用非线性规划。
scipy的linprog只处理最小化问题,求最大值要把目标函数系数取负,这是新手最容易踩的地方。整数规划推荐用scipy的milp或者pulp库。非线性规划用minimize加method="SLSQP",它支持等式和不等式约束。
from scipy.optimize import linprog # 目标:max 3x + 2y,转换为min -3x - 2y c = [-3, -2] # 约束:2x + y <= 10, x + y <= 8,x >= 0, y >= 0 A_ub = [[2, 1], [1, 1]] b_ub = [10, 8] bounds = [(0, None), (0, None)] res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs") print("最优解:", res.x) print("最优值:", -res.fun)linprog里的A_ub是小于等于约束的系数矩阵,b_ub是对应的右侧常数。bounds里None表示该变量没有上界或下界。method参数建议用highs,它是高精度内点法实现,比默认方法更快更稳。注意linprog的res.fun返回的是取负后的目标值,所以最大值要用负号还原,这一步做错的话整个优化结果都会对不上论文里写的目标函数。
4.2 智能优化算法的共性框架:模拟退火、粒子群、遗传的调参逻辑
智能优化算法都属于元启发式方法,核心思想是在解空间里做启发式搜索,不保证全局最优但能在可接受时间内给出一个工程可用的解。模拟退火从高温开始,允许以一定概率接受更差解来跳出局部最优,温度逐渐降低,接受差解的概率越来越小。粒子群通过个体最优和群体最优来引导粒子飞行,遗传算法则靠选择、交叉、变异三个算子迭代进化。
import numpy as np def objective(x): # 多峰测试函数:求最小值 return x[0]**2 + x[1]**2 - np.cos(12 * x[0]) - np.cos(12 * x[1]) def simulated_annealing(func, bounds, T0=1000, alpha=0.95, iter_max=1000): # 随机生成初始解 x_cur = np.array([np.random.uniform(b[0], b[1]) for b in bounds]) f_cur = func(x_cur) best_x, best_f = x_cur.copy(), f_cur T = T0 while T > 1e-3: for _ in range(iter_max): # 邻域扰动生成新解 x_new = x_cur + np.random.uniform(-0.1, 0.1, size=len(bounds)) f_new = func(x_new) # Metropolis准则:更优则接受,更差则按概率接受 if f_new < f_cur or np.random.random() < np.exp(-(f_new - f_cur) / T): x_cur, f_cur = x_new, f_new if f_cur < best_f: best_x, best_f = x_cur.copy(), f_cur T *= alpha return best_x, best_f best_x, best_f = simulated_annealing(objective, [(-5, 5), (-5, 5)]) print("最优解:", best_x, "目标值:", best_f)初始温度T0决定算法前期接受差解的概率,设太低算法一开始就陷入局部最优;降温系数alpha控制降温速度,alpha越接近1,搜索越充分但耗时越长;迭代次数iter_max是每个温度下的搜索步数。这些参数在比赛里属于玄学,不同问题的最优参数差异很大。我从实践里总结的调参套路是:先固定alpha=0.95,T0从大到小试几组,记录目标值的变化,找到拐点后再小幅调iter_max,不要同时动三个参数,否则出了问题根本定位不了是哪个参数导致的。
| 算法 | 核心参数 | 参数敏感度 | 常见误区 |
|---|---|---|---|
| 模拟退火 | T0、alpha、iter_max | 高 | 初温设太低,接受差解概率趋近于0 |
| 粒子群 | 惯性权重、c1、c2 | 中 | 惯性权重固定不衰减,后期收敛差 |
| 遗传算法 | 交叉概率、变异概率 | 中 | 变异率过低,种群早熟收敛 |
4.3 动态规划与0-1背包:递推式和状态转移的写码套路
动态规划解决的是多阶段决策问题,核心是写出状态转移方程。0-1背包问题里,状态是前i个物品在容量c下能装的最大价值,转移方程就是放或不放当前物品两种选择的较大值。资源包里单独给了基础0-1背包的动态规划代码,这类代码写熟之后,排队调度、资源分配问题都能转换成类似框架。
def knapsack(weights, values, capacity): n = len(weights) # dp[i][c]表示前i个物品在容量c下的最大价值 dp = [[0] * (capacity + 1) for _ in range(n + 1)] for i in range(1, n + 1): w, v = weights[i - 1], values[i - 1] for c in range(1, capacity + 1): if w <= c: # 放得下就取max(不放, 放) dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - w] + v) else: # 放不下只能继承上一行的结果 dp[i][c] = dp[i - 1][c] return dp[n][capacity] weights = [2, 3, 4, 5] values = [3, 4, 5, 6] print("最大价值:", knapsack(weights, values, 8))dp数组的行索引是物品编号,列索引是背包容量,dp[i-1][c-w]+v表示把当前物品放进去,腾出w的容量后装前i-1个物品的最大价值加上当前物品价值。这里有个容易绕晕的点,dp下标从1开始,而weights和values从0开始,所以循环里访问weights[i-1]才能对齐。资源包里的动态规划模型代码用的是同样套路,换成最短路径问题时把容量换成节点编号,转移方程换成距离更新即可。
5. 数学建模算法实战避坑指南:从数据预处理到结果复现的五个教训
5.1 一致性检验没做就出权重:层次分析法最大的隐性坑
现象:判断矩阵算出来的权重看起来有模有样,但代入评价模型后排名结果和直觉完全相反,复盘时发现一致性比率CR高达0.25,远超0.1的合格线。
原因:构造判断矩阵时凭感觉填比例值,忽略了判断的传递性。比如A比B重要3倍,B比C重要3倍,按理A比C应该接近9倍,但手填的时候只填了2倍,矩阵的一致性就被破坏了。
解决:权重计算前必须先求最大特征值λmax,按CR=CI/RI做一致性检验,其中CI=(λmax-n)/(n-1),RI查表获取。CR大于0.1时不能直接使用该矩阵的权重,要回头调整判断矩阵里偏差最大的元素,或者改用熵权法这种完全客观的赋权方式。
5.2 指标正向化方向搞反:TOPSIS排名错乱的头号原因
现象:TOPSIS算出来的贴近度排名波动剧烈,而且最优方案明显不符合业务直觉,检查数据时发现成本型指标的处理方式根本不对。
原因:TOPSIS要求所有指标先统一成正向指标,即数值越大越好。成本型指标比如费用、耗时,直接用原始值参与距离计算的话,数值越大反而离理想解越近,排名逻辑整个颠倒。
解决:极小型指标做正向化,常见做法是取倒数或取负。取倒数会让距离拉大,对后续距离计算的影响更平滑。做完正向化后要再扫一遍数据,确认所有指标的方向一致,再进TOPSIS流程。
5.3 GM(1,1)跳过级比检验:小样本预测翻车的前兆
现象:用灰色预测模型跑出来的预测值整体偏离历史趋势,有的点甚至出现负值,后验差比值C完全不达标。
原因:原始序列没通过级比检验就直接建模。GM(1,1)要求原始序列的级比落在可容覆盖区间内,区间宽度由样本量n决定,n越小区间越窄,越容易不满足条件。
解决:建模前先算级比λ(k)=x(k-1)/x(k),确认每个级比值都落在(exp(-2/(n+1)), exp(2/(n+1)))区间内。通不过就做平移变换,给序列整体加一个常数C再做预测,预测结果出来后减回C即可。这一步是灰色预测的黑匣子,资源包里的灰色预测代码注释里标了检验逻辑,跑之前一定留意。
5.4 智能优化算法不固定随机种子:复现直接崩
现象:同一个脚本同一个数据集,上午跑和下午跑结果不一样,粒子群的最优解截然不同,论文里根本没法写「实验参数固定」。
原因:智能优化算法用随机数初始化种群和做扰动,不设置随机种子的话,每次运行的搜索路径完全不同。
解决:在调用算法前强制加上np.random.seed(42),sklearn里训练模型时设置random_state参数。比赛论文里写明随机种子和运行环境,这是评委最容易质疑的点。从那以后我每次跑智能优化算法都会在文件头部固定种子,免得答辩时说不清结果怎么来的。
5.5 K-means不归一化:距离全被量大特征主导
现象:聚类结果里某个特征几乎完全决定了簇的划分,其他特征对结果毫无贡献,聚出来的簇连业务上都能一眼看出不合理。
原因:K-means用欧氏距离衡量样本相似度,量纲大的特征在距离计算里占据绝对主导地位。比如一个特征取值范围是0到10000,另一个是0到1,后者在距离公式里几乎可以忽略。
解决:聚类前对连续型特征做StandardScaler标准化,让每个特征都落在相近的数值范围。对有序分类特征可以用LabelEncoder编码后再标准化,但对无序分类特征直接编码会引入伪距离,这种情况下需要改用One-Hot编码,或者换用支持混合类型距离的聚类算法。
6. 把30个算法串成一条流水线:数据体检、参数存档与结果验证的完整套路
前面把预测、分类、优化、评价四类算法的选型和代码都拆了一遍,最后说一个我自己的实操习惯:拿到一份新赛题数据,不急着选算法,先跑一遍数据体检。看缺失值比例、异常值分布、特征量级差异、时间序列的平稳性,这些决定了后面所有算法的可用性。
体检完再按问题类型进分支:预测题先做平稳性检验再决定ARIMA还是灰色预测;分类题先跑逻辑回归做基线,再对比随机森林和SVM;优化题先判断线性还是非线性,线性用scipy规划族,非线性上智能优化算法;评价题先做一致性检验再上TOPSIS或模糊综合评价。
一个很实用的技巧是把30个算法脚本的入口统一封装成train(X, y, params)格式,让不同模型可以无缝切换对比。
def run_model(model_name, X_train, y_train, X_test, params): if model_name == "logistic": model = LogisticRegression(**params) elif model_name == "svm": model = SVC(**params) elif model_name == "rf": model = RandomForestClassifier(**params) model.fit(X_train, y_train) return model.predict(X_test), model每个模型跑完记录三个东西:参数配置、随机种子、测试集上的核心指标。备赛期间我建了一个参数记录表,每换一组参数就记一行,回头写论文时直接抄表上的内容,不用翻代码回忆。最终结果用交叉验证而不是单次划分来确认,避免随机划分带来的偶然误差。从那以后我每次建模都强制走一遍「数据体检—选型—参数存档—交叉验证」这四步流程,比赛里省下的时间都花在结果分析上,不在调参和返工里消耗。希望帮到你。
本文还有配套的精品资源,点击获取