1. 从赛题到实战:一次完整的数据挖掘项目复盘

去年带学生打“华为杯”数学建模竞赛,B题《基于数据挖掘技术的汽油辛烷值优化研究》给我留下了挺深的印象。这题目乍一看是化工领域的专业问题,但内核其实是一个典型的数据驱动建模与优化任务,非常适合用数据挖掘和机器学习的方法来解决。很多队伍拿到题目后,容易陷入两个极端:要么被“汽油精制”、“辛烷值”这些专业术语吓到,花大量时间补习化工知识;要么完全忽略背景,把数据当成纯数字游戏,建出来的模型缺乏物理意义,难以自圆其说。我们当时的策略是,抓住“数据挖掘技术”这个核心,把问题拆解为“特征工程”、“模型构建”和“优化求解”三个环环相扣的模块,最终不仅拿到了不错的奖项,整个解题思路也沉淀为一套可复用的方法论。

这篇文章,我就以这道赛题为蓝本,结合我们当时的论文和代码,从头到尾复盘一遍。我会重点讲清楚几个关键问题:面对一份包含原料性质、操作条件、产品指标的生产数据,我们如何从杂乱的特征中提取有效信息?如何选择并构建一个既能准确预测辛烷值、又具备一定可解释性的模型?最后,如何利用这个模型,在复杂的工艺约束下,找到使辛烷值最大化的最优操作方案?整个过程,我会穿插我们当时踩过的坑、做过的关键决策,以及事后看来可以做得更好的地方。无论你是对数学建模竞赛感兴趣,还是在实际工作中遇到了类似的过程优化问题,希望这篇近万字的复盘能给你带来一些直接的启发和可操作的代码参考。

2. 问题本质剖析:数据挖掘在流程工业优化中的角色

在深入技术细节之前,我们得先搞清楚这个题目到底在考什么。题目提供了炼油厂催化裂化装置的生产数据,包含了多种原料油的性质(如密度、馏程、烃组成)、一系列操作条件(如反应温度、剂油比、空速等),以及最终产品的关键质量指标——研究法辛烷值(RON)。目标很明确:建立辛烷值与众多输入变量之间的定量关系模型,并以此为基础,调整操作条件,在满足其他产品指标和装置安全约束的前提下,最大化辛烷值。

这本质上是一个 回归预测+约束优化 的问题。但其特殊性在于:

  1. 高维、小样本、强耦合 :通常特征变量(原料性质+操作条件)多达数十个,而历史生产数据样本可能只有几百条。变量之间还存在复杂的非线性关系和多重共线性(比如某些操作条件会相互影响)。
  2. 物理意义模糊与数据驱动并存 :虽然催化裂化反应机理极其复杂,完全从第一性原理建模非常困难,但数据中必然隐含着物理化学规律。一个优秀的数据挖掘模型,应该在保证预测精度的同时,其学到的规律(如特征重要性、部分依赖关系)不能明显违背基本的工艺常识。
  3. 约束复杂 :优化不是无限制的。除了辛烷值,我们还要保证汽油收率、液化气收率、焦炭产率等关键经济和技术指标在合格范围内,同时所有操作条件必须在装置设计的安全上下限内运行。

所以,解题的核心思路就清晰了: 首先,利用数据挖掘技术,从历史数据中学习一个高精度的辛烷值预测模型(即“软测量”模型)。然后,将这个预测模型作为目标函数,结合各种工艺约束,构建一个优化模型。最后,采用合适的优化算法,搜索出最优的操作参数组合。 数据挖掘在这里扮演了“认知”和“代理”的角色,让我们能够用数据的方式理解并模拟复杂的生产过程。

3. 数据预处理与特征工程:从原始数据到模型“食材”

我们拿到的原始数据通常是一个Excel或CSV文件,里面可能混杂着数值型、字符型数据,甚至存在缺失值、异常值和量纲不统一的问题。这一步是后续所有工作的基石,处理得好坏直接决定模型的天花板。

3.1 数据清洗:识别与处理“脏数据”

首先进行探索性数据分析(EDA)。我们用Python的 pandas seaborn 快速查看数据概览、分布和关系。

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# 加载数据
data = pd.read_csv('gasoline_production_data.csv')
print(data.info()) # 查看数据类型和缺失情况
print(data.describe()) # 查看数值统计

# 绘制特征分布直方图
data.hist(bins=30, figsize=(20, 15))
plt.tight_layout()
plt.show()

# 绘制箱线图,识别异常值
plt.figure(figsize=(15, 10))
sns.boxplot(data=data)
plt.xticks(rotation=90)
plt.show()

关键操作与踩坑点:

  1. 缺失值处理 :对于少量缺失(如<5%),我们采用了特征列的均值或中位数填充。但对于某些关键操作参数连续缺失的情况,我们怀疑是传感器故障或生产工况记录不完整,这类样本我们选择了直接删除,因为盲目填充可能引入更大噪声。一个教训是,如果时间允许,应该尝试用更高级的方法如KNN或随机森林回归来预测填充缺失值,效果可能会更好。
  2. 异常值处理 :箱线图能快速找出那些远离主分布的“离群点”。但异常值不等于错误值。我们需要区分:
    • 记录错误 :例如反应温度超过了装置的理论极限值,这很可能是录入错误,予以删除或修正。
    • 特殊工况 :某些点可能是装置开停工、催化剂更换初期的数据,虽然数值异常,但代表了真实的生产状态。我们当时的做法是,结合工艺知识进行判断。对于无法明确判断的,我们采用了“稳健缩放”而非直接删除,以减少其对模型的影响。
  3. 数据标准化/归一化 :由于特征量纲差异巨大(温度几百度,某个组分含量可能只有零点几),必须进行尺度统一。我们对比了 StandardScaler (标准化,均值为0,方差为1)和 MinMaxScaler (归一化,缩放到[0,1])。对于后续使用支持向量机(SVR)或神经网络等对尺度敏感的模型,标准化是必须的。即使使用树模型(如随机森林),标准化也能加速训练。我们最终选择了 StandardScaler

3.2 特征工程:创造更有价值的输入

原始特征直接喂给模型往往效果不佳。特征工程的目的是降维、去噪、并构建更能体现问题本质的新特征。

  1. 相关性分析与特征初筛 :计算所有特征与目标变量(辛烷值)的皮尔逊相关系数,并绘制热力图。对于与目标相关性极低(如|r|<0.05)且从工艺角度也无法解释的特征,可以考虑初步剔除,以降低模型复杂度和过拟合风险。

    corr_matrix = data.corr()
    plt.figure(figsize=(12, 10))
    sns.heatmap(corr_matrix, annot=False, cmap='coolwarm', center=0)
    plt.title('Feature Correlation Heatmap')
    plt.show()
    # 查看与目标变量的相关性排序
    target_corr = corr_matrix['RON'].abs().sort_values(ascending=False)
    print(target_corr)
    
  2. 处理多重共线性 :特征之间高度相关(如多个温度测点)会导致模型不稳定、系数难以解释。我们使用了方差膨胀因子(VIF)来诊断。通常VIF>10被认为存在严重共线性。对于共线性高的特征组,我们采用了两种策略:

    • 保留最具代表性的一个 :例如,一组高度相关的温度变量,只保留工艺上最重要的那个。
    • 构建综合指标 :使用主成分分析(PCA)将一组相关特征转换为一组不相关的主成分,用主成分作为新特征。但要注意,PCA后的特征失去了物理意义,不利于后续的工艺解释。
  3. 构造衍生特征 :这是提升模型性能的关键。我们基于有限的工艺知识,构造了一些可能影响辛烷值的组合特征。例如:

    • 强度因子 :将反应温度与空速相结合,构造“反应强度”特征(如温度/空速)。
    • 转化深度指标 :结合原料性质和操作条件,构造一个经验性的“转化率”估算特征。
    • 交互项 :对于线性模型,可以手动添加重要特征的乘积项,以捕捉非线性效应。对于树模型,这一步不是必须的,因为树模型本身能发现交互作用。

注意 :特征工程需要反复迭代。我们当时的流程是:初步清洗 -> 构建基线模型 -> 分析模型误差/特征重要性 -> 返回调整特征 -> 重新训练。这是一个闭环过程。

4. 预测模型构建:寻找辛烷值的“数据映射器”

特征准备好后,接下来就是选择并训练预测模型。我们的目标是找到一个在测试集上 预测精度高、泛化能力强、且具有一定可解释性 的模型。

4.1 模型选型与对比

我们试验了多种经典的数据挖掘和机器学习模型,并进行系统对比:

  1. 多元线性回归(MLR) :作为基准模型。它简单、可解释性强,但难以捕捉复杂非线性关系,在本题中表现通常不佳,R²往往较低。
  2. 支持向量回归(SVR) :对于中小规模、非线性数据有优势。我们使用了径向基(RBF)核函数。其性能高度依赖于超参数(惩罚系数C、核系数gamma)。我们通过网格搜索进行调优。SVR的优点是泛化能力可能较好,缺点是训练速度慢,且模型完全是一个“黑箱”,可解释性差。
  3. 随机森林回归(RFR) :这是我们最终的主力模型之一。集成学习算法,通过构建大量决策树并取平均来预测。它天然能处理非线性、高维数据,对异常值不敏感,且不需要复杂的特征缩放。 最大的优点是提供了特征重要性排序 ,这为工艺分析提供了宝贵依据。
  4. 梯度提升树(如XGBoost, LightGBM) :另一种强大的集成树模型。相比随机森林,它采用串行、迭代的方式构建树,旨在不断减少残差,通常能达到更高的预测精度。LightGBM训练速度更快,适合大数据集。我们最终也使用了LightGBM作为对比和补充。

4.2 模型训练与评估实战

我们采用Python的 scikit-learn lightgbm 库来实现。

from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import lightgbm as lgb

# 假设X是特征DataFrame,y是目标变量RON
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 标准化 (对SVR和线性回归很重要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # 注意:使用训练集的scaler来转换测试集

# 1. 线性回归(基准)
lr = LinearRegression()
lr.fit(X_train_scaled, y_train)
y_pred_lr = lr.predict(X_test_scaled)

# 2. 支持向量回归(需要调参)
svr = SVR(kernel='rbf')
param_grid_svr = {'C': [0.1, 1, 10, 100], 'gamma': [0.01, 0.1, 1, 'scale']}
grid_svr = GridSearchCV(svr, param_grid_svr, cv=5, scoring='r2', n_jobs=-1)
grid_svr.fit(X_train_scaled, y_train)
best_svr = grid_svr.best_estimator_
y_pred_svr = best_svr.predict(X_test_scaled)

# 3. 随机森林回归(同样需要调参)
rf = RandomForestRegressor(random_state=42, n_jobs=-1)
param_grid_rf = {
    'n_estimators': [100, 200, 300],
    'max_depth': [10, 20, 30, None],
    'min_samples_split': [2, 5, 10]
}
grid_rf = GridSearchCV(rf, param_grid_rf, cv=5, scoring='r2', n_jobs=-1)
grid_rf.fit(X_train, y_train) # 树模型一般不需要标准化
best_rf = grid_rf.best_estimator_
y_pred_rf = best_rf.predict(X_test)

# 4. LightGBM
lgb_model = lgb.LGBMRegressor(random_state=42)
param_grid_lgb = {
    'num_leaves': [31, 50, 100],
    'learning_rate': [0.01, 0.05, 0.1],
    'n_estimators': [100, 200]
}
grid_lgb = GridSearchCV(lgb_model, param_grid_lgb, cv=5, scoring='r2', n_jobs=-1)
grid_lgb.fit(X_train, y_train)
best_lgb = grid_lgb.best_estimator_
y_pred_lgb = best_lgb.predict(X_test)

# 模型评估函数
def evaluate_model(y_true, y_pred, model_name):
    mse = mean_squared_error(y_true, y_pred)
    mae = mean_absolute_error(y_true, y_pred)
    r2 = r2_score(y_true, y_pred)
    print(f"{model_name} - MSE: {mse:.4f}, MAE: {mae:.4f}, R²: {r2:.4f}")
    return mse, mae, r2

print("模型在测试集上的表现:")
evaluate_model(y_test, y_pred_lr, "Linear Regression")
evaluate_model(y_test, y_pred_svr, "SVR")
evaluate_model(y_test, y_pred_rf, "Random Forest")
evaluate_model(y_test, y_pred_lgb, "LightGBM")

关键决策与经验:

  • 评估指标 :我们主要看 R²(决定系数) MAE(平均绝对误差) 。R²接近1说明模型解释力强,MAE则直观反映了预测值与真实值的平均偏差(单位与RON相同)。MSE(均方误差)因为平方项会对大误差更敏感。
  • 交叉验证 :我们使用5折或10折交叉验证来评估模型的泛化能力,避免因单次数据划分的偶然性导致评估结果过优。 GridSearchCV 本身就内置了交叉验证。
  • 为什么最终选择随机森林? 在我们当时的实验中,LightGBM的R²略高于随机森林(例如0.92 vs 0.90),但优势并不显著。而随机森林提供的 特征重要性 对于我们后续的工艺分析和优化至关重要。此外,随机森林的随机性可以通过设置 random_state 固定,结果更稳定。SVR虽然调参后效果尚可,但训练和调参时间远长于树模型,且缺乏解释性。因此,我们选择随机森林作为核心预测模型,并用LightGBM的结果作为验证和补充。
  • 特征重要性分析 :这是数据挖掘连接工艺知识的桥梁。
    # 获取随机森林特征重要性
    importances = best_rf.feature_importances_
    feature_names = X.columns
    indices = np.argsort(importances)[::-1]
    
    plt.figure(figsize=(10, 6))
    plt.title("Random Forest Feature Importances")
    plt.bar(range(X.shape[1]), importances[indices], align="center")
    plt.xticks(range(X.shape[1]), [feature_names[i] for i in indices], rotation=90)
    plt.xlim([-1, X.shape[1]])
    plt.tight_layout()
    plt.show()
    
    通过这个图,我们可以清晰地告诉工艺工程师:“看,模型认为反应温度、原料中的烯烃含量、催化剂活性这几个因素对辛烷值的影响最大。” 这极大地增强了模型的说服力。

5. 基于预测模型的辛烷值优化

有了一个可靠的辛烷值预测模型 RON = f(原料性质, 操作条件) ,我们的问题就转化为一个数学优化问题: 在原料性质相对固定(或给定范围)的情况下,调整可操作的条件变量(如反应温度、压力、空速等),使得预测的辛烷值 f(x) 最大化,同时满足一系列约束条件。

5.1 优化问题建模

我们将优化问题形式化如下:

  • 决策变量 (x) :一组可调节的操作条件,例如 x = [反应温度, 剂油比, 空速, ...]
  • 目标函数 (Maximize) RON_pred = f(x, 原料固定参数) 。这里的 f 就是我们训练好的随机森林模型。
  • 约束条件
    1. 变量边界约束 :每个操作条件都有安全操作范围。 x_lower <= x <= x_upper
    2. 其他产品质量约束 :模型可能需要同时预测其他指标(如汽油收率 Yield ),并要求其大于某个下限。 Yield_pred = g(x) >= Yield_min 。这里 g 可以是另一个预测模型,或者如果相关性不强,有时题目会直接给出经验公式或固定范围。
    3. 装置负荷等约束 :可能还有其他线性或非线性约束。

5.2 优化算法选择与实现

由于目标函数 f(x) 是一个由随机森林构成的复杂、非线性、不可导的“黑箱”函数,传统的基于梯度的优化算法(如梯度下降)无法直接应用。我们通常采用 启发式优化算法

  1. 粒子群优化算法(PSO) :这是我们当时采用的主要方法。PSO模拟鸟群觅食行为,通过群体中个体的信息共享来寻找最优解。它适用于连续变量优化,对目标函数形式无要求,全局搜索能力强。
  2. 遗传算法(GA) :另一种常用的启发式算法,通过模拟自然选择、交叉和变异来进化种群。GA同样不要求目标函数可导,且能处理离散和连续变量混合的问题。
  3. 差分进化算法(DE) :结构简单,参数少,性能鲁棒,也是处理此类问题的好选择。

我们以PSO为例,展示如何调用Python的 pyswarm 库(也可用 scipy.optimize 或自己实现)进行优化。

import numpy as np
from pyswarm import pso
# 假设我们已经有了训练好的随机森林模型 `best_rf` 和标准化器 `scaler`
# 以及固定原料性质组成的数组 `raw_material_fixed`

def objective_function(x):
    """
    目标函数:最大化辛烷值,所以返回其负值(因为PSO默认最小化)。
    x: 决策变量数组,仅包含待优化的操作条件。
    """
    # 1. 将决策变量与固定原料特征拼接成完整特征向量
    # 假设 raw_material_fixed 是原料特征,x 是操作条件
    full_feature = np.concatenate([raw_material_fixed, x])
    # 2. 将完整特征向量转换为模型输入的格式(二维数组)
    X_input = full_feature.reshape(1, -1)
    # 3. 注意:如果模型训练时使用了标准化,这里也需要用同样的scaler转换
    # 假设我们只对操作条件部分进行优化,且训练时是对所有特征一起标准化的。
    # 更稳妥的做法是,在优化循环外准备好一个包含固定值的完整特征模板,每次只替换x部分。
    # 这里为了演示简化处理。
    X_input_scaled = scaler.transform(X_input) # 使用全局scaler
    # 4. 使用模型预测
    ron_pred = best_rf.predict(X_input_scaled)
    # 5. 返回负值,因为PSO求解最小值问题
    return -ron_pred[0]

def constraint_function(x):
    """
    约束函数。返回一个数组,要求所有元素 >= 0。
    例如:约束1:汽油收率 >= 45 -> g1(x) = yield_pred(x) - 45 >= 0
         约束2:焦炭产率 <= 6 -> g2(x) = 6 - coke_pred(x) >= 0
    """
    # 同样,构建完整特征向量
    full_feature = np.concatenate([raw_material_fixed, x])
    X_input = full_feature.reshape(1, -1)
    X_input_scaled = scaler.transform(X_input)

    # 假设我们还有预测收率和焦炭的模型 `yield_model`, `coke_model`
    yield_pred = yield_model.predict(X_input_scaled)[0]
    coke_pred = coke_model.predict(X_input_scaled)[0]

    # 计算约束值
    cons = []
    cons.append(yield_pred - 45)   # 收率下限约束
    cons.append(6 - coke_pred)     # 焦炭上限约束
    # 可以添加更多约束...
    return np.array(cons)

# 定义决策变量的上下限 (根据工艺安全范围)
lb = [480, 5.0, 18]   # 例如:[温度下限, 剂油比下限, 空速下限]
ub = [520, 8.0, 25]   # 例如:[温度上限, 剂油比上限, 空速上限]

# 调用PSO求解器
# 注意:pyswarm的约束处理方式,这里使用不等式约束(ieqcons)
x_opt, f_opt = pso(objective_function, lb, ub, ieqcons=[constraint_function],
                   swarmsize=50, maxiter=200, debug=True)

print("找到的最优操作条件:", x_opt)
print("对应的最大辛烷值预测值:", -f_opt) # 注意取负号转回来

优化过程中的核心难点与对策:

  1. “黑箱”函数调用成本 :每次迭代,PSO都需要调用目标函数和约束函数,而这背后是机器学习模型的预测。随机森林预测一次很快,但如果种群规模大、迭代次数多,总计算量也不小。我们通过 设置合理的种群大小和迭代次数 来平衡搜索能力和计算时间。
  2. 约束处理 :PSO本身不直接支持约束。 pyswarm 库通过罚函数法或将约束违反度作为优化目标的一部分来处理。我们也可以采用更专业的约束处理PSO变种,或者使用支持约束优化的差分进化算法(如 scipy.optimize.differential_evolution )。
  3. 局部最优与重复性 :启发式算法可能陷入局部最优。我们通过 多次运行算法(不同随机种子) ,取最好的结果作为最终解,以增加找到全局最优的概率。
  4. 结果验证与工艺合理性 :优化给出的“数学最优解”必须经过工艺常识的检验。例如,模型可能建议将反应温度推到上限,但工程师会知道,过高的温度会导致过度裂化,催化剂失活加快,长期来看不经济。因此, 优化结果需要与领域专家讨论 ,必要时对变量边界或约束条件进行调整,得到一个“技术上可行、经济上合理”的满意解。

6. 项目总结与可复现的代码框架

回顾整个项目,从数据清洗、特征工程、模型选择与调优,到最后的优化求解,形成了一个完整的数据挖掘应用闭环。对于想要复现或学习类似问题的朋友,我建议遵循以下步骤,并重点关注我们踩过的坑:

第一步:深入理解业务(题目)背景。 不要一头扎进数据里。花点时间搞清楚“辛烷值”是什么,催化裂化大致过程是怎样的,哪些操作条件通常是可调的。这能帮你更好地判断特征的相关性和异常值,设计出更有意义的衍生特征。

第二步:稳健的数据预处理。 数据质量决定上限。异常值和缺失值的处理要谨慎,最好能结合业务逻辑。可视化(分布图、箱线图、相关热力图)是你的好朋友,能帮你快速发现数据问题。

第三步:模型选择不必追求最复杂。 在这个问题上,树模型(随机森林、LightGBM)在精度和解释性上取得了很好的平衡。特征重要性分析是连接数据和业务的桥梁,务必重视。SVR等模型可以作为对比,但其“黑箱”属性和调参难度在竞赛有限时间内可能不是最优选。

第四步:优化是工程的延伸。 将数据模型嵌入优化框架时,务必仔细检查特征拼接、标准化转换的流程,确保优化循环中给模型的数据格式和训练时完全一致。约束条件要定义清晰、完整。

第五步:结果解读要落地。 给出最优操作参数时,同时要给出模型预测的辛烷值提升幅度,并分析主要贡献变量(通过特征重要性或做敏感性分析)。这能让你的解决方案更有说服力。

为了方便大家实践,我将核心流程整理成了一个简化的、可复现的代码框架结构。你只需要替换数据路径、调整特征名和模型参数,就能跑通整个流程:

gasoline_optimization_project/
│
├── data/
│   └── raw_data.csv          # 原始数据
│
├── notebooks/
│   └── 01_eda_and_preprocessing.ipynb  # 探索性分析与预处理
│   └── 02_feature_engineering.ipynb    # 特征工程
│   └── 03_model_training_evaluation.ipynb # 模型训练与评估
│   └── 04_optimization.ipynb           # 优化求解
│
├── src/
│   ├── __init__.py
│   ├── data_preprocessing.py  # 数据清洗、标准化函数
│   ├── feature_engineering.py # 特征构造、选择函数
│   ├── model_building.py      # 模型定义、训练、评估函数
│   └── optimization.py        # PSO/GA优化问题定义函数
│
├── models/                    # 保存训练好的模型和scaler
│   └── best_rf_model.pkl
│   └── standard_scaler.pkl
│
├── config.py                  # 配置文件(路径、参数)
└── main.py                    # 主运行脚本

main.py 中,你可以这样组织流水线:

# main.py 示例
import pandas as pd
from src.data_preprocessing import load_and_clean_data
from src.feature_engineering import create_features, select_features
from src.model_building import train_random_forest, evaluate_model
from src.optimization import run_pso_optimization
import joblib

def main():
    # 1. 加载与清洗数据
    df = load_and_clean_data('data/raw_data.csv')
    
    # 2. 特征工程
    df_engineered = create_features(df)
    X, y = select_features(df_engineered, target_col='RON')
    
    # 3. 划分训练测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
    # 4. 训练模型
    best_model, scaler = train_random_forest(X_train, y_train)
    
    # 5. 评估模型
    evaluate_model(best_model, scaler, X_test, y_test)
    
    # 6. 保存模型
    joblib.dump(best_model, 'models/best_rf_model.pkl')
    joblib.dump(scaler, 'models/standard_scaler.pkl')
    
    # 7. 定义优化问题并求解 (假设固定原料特征)
    fixed_material_features = [...] # 从数据中获取或设定
    lb = [...] # 操作变量下限
    ub = [...] # 操作变量上限
    
    optimal_settings, max_ron = run_pso_optimization(best_model, scaler, fixed_material_features, lb, ub)
    
    print(f"推荐的最优操作条件: {optimal_settings}")
    print(f"预测辛烷值可达: {max_ron:.2f}")

if __name__ == '__main__':
    main()

最后想说的是,数学建模竞赛和实际工业问题非常接近,都是一个从定义问题、处理数据、构建模型到解释结果的完整过程。通过这个“华为杯”B题的详细拆解,我希望展示的不仅仅是一套解法,更是一种用数据科学解决复杂工程问题的思维框架。在实际工作中,你可能需要面对更脏的数据、更复杂的约束、以及严格的在线部署要求,但底层的方法论是相通的。多练习这样的完整项目,你对每个环节的把握才会越来越扎实。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐