1. 赛题核心与解题总览:从“是什么”到“怎么想”

大家好,我是老张,一个在数学建模圈子里摸爬滚打了十来年的“老油条”。每年电工杯的赛题一出来,总能引起一阵热议,今年的A题(光伏预测)和B题(垃圾运输路径优化)也不例外。很多刚接触建模的同学拿到题目,第一反应往往是“题目好长,数据好多,从哪下手?”。别急,今天我就用最接地气的方式,帮你把这两道题掰开揉碎了讲清楚,不仅告诉你“是什么”,更重点分享“怎么想”和“怎么做”。

先说说A题,光伏发电功率预测。这本质上是一个时间序列预测问题,但又不是简单的“用昨天猜今天”。它像是一个层层递进的闯关游戏:第一关,让你只用历史发电数据去分析规律;第二关,还是只用历史数据,但要你预测未来一天(日前预测);第三关,难度升级,给你加入了天气预报(NWP)信息,让你结合天气来预测;第四关,更是让你研究,如果把天气预报的“地图”画得更精细(空间降尺度),预测会不会更准。这道题的核心挑战在于,如何把时间、天气、空间这三个维度的信息,巧妙地融合到一个模型里。你可能会想到LSTM、XGBoost这些“明星算法”,但比选算法更重要的是,你如何理解光伏发电的物理特性(比如中午发电多,阴天发电少),并把这些理解转化成模型能“吃”进去的特征。

再来看B题,城市垃圾运输路径优化。这活脱脱就是一个现实版的“快递员送快递”游戏,但规则复杂得多:你有不同类型的垃圾(厨余、可回收等),有不同载重和容积的车辆,垃圾点分布在全城,有的点垃圾多,有的点垃圾少,车辆从处理厂出发,最后还得回去。题目也是三阶段:从最简单的单车型送一种垃圾,到多车型送多种垃圾,最后还要考虑在哪里建中转站最划算,并且车辆要在规定的时间窗口内收垃圾。这道题是典型的组合优化问题,属于运筹学的范畴。它的难点不在于模型的数学形式有多高深,而在于约束条件多且杂(载重、容积、时间、车辆类型、中转站容量),如何设计一个高效的算法,在成千上万种可能的路径方案中,快速找到一个成本最低、又满足所有“规矩”的好方案,这才是真正的考验。

所以,在动手写代码之前,我建议你先花半天时间,带着下面这个思路框架去读题、分析数据:

  1. 目标量化:题目要求“最小化”什么?是预测误差(RMSE, MAE)?还是运输总距离/总成本?把它变成一个明确的数学公式。
  2. 约束理清:把所有“必须遵守”的条件列出来,比如车辆不能超载、预测必须基于历史数据、垃圾必须当天清运等。
  3. 数据摸底:给你的数据有哪些字段?缺失值多吗?功率数据和天气数据的时间戳对齐了吗?垃圾点的位置坐标是什么坐标系?
  4. 方法选型:根据问题特点,初步圈定几个候选模型或算法。别贪多,选一两个主攻,再准备一个备选方案。

2. A题深度剖析:光伏预测的特征工程与模型融合术

光伏预测这事儿,我做过不少实际项目,可以负责任地说,特征工程的好坏,直接决定了模型性能的上限。很多人一上来就套LSTM,结果效果平平,问题往往就出在特征没处理好。

2.1 数据预处理:给模型准备“干净食材”

原始数据就像刚从地里摘回来的菜,带着泥,你得先洗干净、切好。对于A题的数据,你需要做这几步:

首先是时间对齐与缺失值处理。光伏功率数据通常是15分钟或1小时一个点,而NWP(数值天气预报)数据可能是3小时或6小时一次。你需要用前向填充(ffill)或线性插值的方法,把NWP数据插值到和功率数据相同的时间频率上。遇到功率数据因为设备故障断崖式下跌为0的情况,要小心识别,这可能是异常值而非真实值,可以用前后时刻的平均值或基于相似日期的值进行填补。

其次是特征构造,这是重头戏。除了题目直接给的辐照度、温度、湿度等,你必须自己动手“创造”一些对模型超级有用的特征:

  • 时间周期性特征:sin(2*pi*小时/24), cos(2*pi*小时/24)。这样比直接输入“14点”更好,因为模型能理解0点和24点是相邻的。
  • 季节特征:月份、季度,或者更精细的sin(2*pi*一年中的第几天/365)。
  • 天气类型标签:根据云量、降雨概率,可以将天气分为“晴天”、“多云”、“阴天”、“雨天”等类别,进行独热编码(One-hot Encoding)。这对模型理解不同天气模式下的发电规律至关重要。
  • 滞后特征:把前1小时、前3小时、前24小时、甚至前一周同期的功率值作为特征输入,这对捕捉时间序列的自相关性非常有效。
  • NWP的衍生特征:比如“温差”(最高温-最低温)、“体感温度”(结合温度和湿度计算)等。

这里给一个我常用的特征构造代码片段,你可以在此基础上扩展:

import pandas as pd
import numpy as np

def create_features(df, power_col='power'):
    """
    df: 包含日期时间索引和功率、气象数据的DataFrame
    """
    df = df.copy()
    # 1. 基础时间特征
    df['hour_sin'] = np.sin(2 * np.pi * df.index.hour / 24)
    df['hour_cos'] = np.cos(2 * np.pi * df.index.hour / 24)
    df['day_of_year_sin'] = np.sin(2 * np.pi * df.index.dayofyear / 365.25)
    df['day_of_year_cos'] = np.cos(2 * np.pi * df.index.dayofyear / 365.25)
    df['month'] = df.index.month
    df['is_weekend'] = df.index.dayofweek >= 5

    # 2. 滞后特征 (以1小时和24小时为例)
    df['power_lag_1h'] = df[power_col].shift(4)  # 假设15分钟间隔,4个点=1小时
    df['power_lag_24h'] = df[power_col].shift(96) # 96个点=24小时

    # 3. 滑动窗口统计特征 (过去24小时的平均值、标准差)
    df['power_rolling_mean_24h'] = df[power_col].rolling(window=96, min_periods=1).mean()
    df['power_rolling_std_24h'] = df[power_col].rolling(window=96, min_periods=1).std()

    # 4. 天气类型分类 (示例,根据云量)
    df['weather_type'] = 'unknown'
    df.loc[df['cloud_cover'] < 0.3, 'weather_type'] = 'sunny'
    df.loc[(df['cloud_cover'] >= 0.3) & (df['cloud_cover'] < 0.7), 'weather_type'] = 'cloudy'
    df.loc[df['cloud_cover'] >= 0.7, 'weather_type'] = 'overcast'
    # 对分类特征进行独热编码
    df = pd.get_dummies(df, columns=['weather_type'], prefix='weather')

    # 删除因创建滞后特征产生的缺失值行
    df.dropna(inplace=True)
    return df

2.2 模型选择与融合:没有“银弹”,只有“组合拳”

对于问题2(仅用历史功率),LSTM 确实是天然的选择,因为它能记忆长期的时序依赖。但单纯堆叠LSTM层可能不够。我建议可以尝试 CNN-LSTM混合模型:先用一维CNN层来提取局部(比如几小时内)的高维特征,再用LSTM层来捕捉长期趋势,这样往往比纯LSTM更稳定。训练时,一定要注意序列数据的划分,必须按时间顺序划分训练集和验证集,绝对不能随机打乱,否则会造成数据泄露,让模型“偷看”到未来信息。

对于问题3(加入NWP),XGBoost/LightGBM 这类树模型表现通常非常强劲,因为它们能很好地处理表格型数据,自动学习特征间的复杂交互。这时,你之前精心构造的特征就派上大用场了。一个实用的技巧是场景划分建模:分别对晴天、多云、阴雨等不同天气类型的数据训练不同的模型。因为晴天时光照是主导因素,而阴雨天时,温度和湿度的影响权重可能更大。分开建模,能让每个“专家”模型更专注。

到了问题4,研究NWP空间降尺度的影响,这不仅是建模,更是一个对比实验设计问题。你需要明确:

  1. 降尺度方法:除了题目可能提到的克里金(Kriging)、反距离加权(IDW),还可以尝试用机器学习方法(如随机森林、神经网络)进行降尺度,利用周边气象站和高程、坡度等地理信息来预测目标点的精细气象数据。
  2. 对比基准:一定要设置一个对照组,即使用原始粗分辨率NWP数据的预测精度。
  3. 评价指标:除了RMSE(均方根误差),我强烈建议计算 MAE(平均绝对误差) 和 MAPE(平均绝对百分比误差),尤其是MAPE,它能直观反映误差的相对大小,便于向非专业人士解释。例如,你可以这样呈现结果:“经过空间降尺度后,模型在阴雨天的预测误差(MAPE)从15%降低到了12%”。

最后,别忘了模型融合这个“大招”。一个简单有效的策略是加权平均:用LSTM、XGBoost、甚至简单的线性回归分别训练模型,然后根据它们在验证集上的表现分配权重(如误差小的模型权重大),将它们的预测结果加权组合。这通常能稳定地提升最终预测精度,是竞赛中拉开差距的常用手段。

3. B题实战拆解:路径优化算法的迭代与创新

B题是典型的运筹优化问题,考验的是你的建模抽象能力和算法设计能力。很多同学一看到“优化”、“路径”就想到遗传算法,但上来就调遗传算法的参数,很容易陷入局部最优且效率低下。我的经验是,从简单到复杂,从精确到启发式,一步步搭建你的解决方案。

3.1 问题1:单车型基础VRP——节约算法(Clarke-Wright)的妙用

问题1是经典的带容量约束的车辆路径问题(CVRP)。所有车辆型号相同,只运输一种垃圾,从处理厂出发,服务完所有点后返回。目标是总距离最短。

节约算法是解决这类问题最直观、最经典的方法之一,它的思想非常“人性化”:一开始,假设每辆车只服务一个点,然后从处理厂出发再返回(即N条独立路线)。然后我们计算,如果把两个点i和j的路线合并(即路线变成 处理厂 -> i -> j -> 处理厂),比原来两条独立路线能“节约”多少距离。节约值 S(i,j) = d(0,i) + d(0,j) - d(i,j),其中0代表处理厂。我们总是优先合并节约值最大的两条路线,直到车辆容量不够为止。

这个算法实现简单,速度快,非常适合作为初始解生成器。但它的解质量通常不是最优的。在实际编程中,有几点需要注意:

  • 距离矩阵:务必使用真实的道路网络距离或至少是欧氏距离,如果题目给了坐标,可以用geopy库或haversine公式计算球面距离。
  • 合并判断:合并时,不仅要看容量约束,还要注意合并后路线的“形状”,避免产生交叉或非常迂回的路线。可以加入一些启发式规则,比如只合并位于同一区域的两个点。
  • 解的改进:得到节约算法的解后,一定要用 2-opt 或 3-opt 等局部搜索算子进行优化。2-opt就是尝试交换路径中两段边的连接方式,看是否能缩短总距离。这个步骤能显著提升解的质量。

下面是一个结合了节约算法和2-opt局部搜索的简化框架:

import numpy as np
from scipy.spatial.distance import cdist

def clarke_wright_savings(points, demands, vehicle_capacity, distance_matrix=None):
    """带2-opt后处理的节约算法"""
    n = len(points) - 1  # 假设points[0]是处理厂
    if distance_matrix is None:
        distance_matrix = cdist(points, points)

    # 1. 初始化:每个点单独一条路线
    routes = [[0, i, 0] for i in range(1, n+1)]
    route_demands = [demands[i-1] for i in range(1, n+1)]

    # 2. 计算所有节约值
    savings = []
    for i in range(1, n+1):
        for j in range(i+1, n+1):
            sav = distance_matrix[0, i] + distance_matrix[0, j] - distance_matrix[i, j]
            savings.append((sav, i, j))
    savings.sort(reverse=True, key=lambda x: x[0])

    # 3. 合并路径
    for sav, i, j in savings:
        # 找到包含i和j的路线
        route_i_idx, position_i = find_route_and_position(routes, i)
        route_j_idx, position_j = find_route_and_position(routes, j)
        if route_i_idx == route_j_idx:
            continue  # i和j已在同一条路线
        if route_demands[route_i_idx] + route_demands[route_j_idx] > vehicle_capacity:
            continue  # 合并后超载

        # 尝试两种合并方式:i的尾接j的头,或j的尾接i的头,选择距离更短的
        new_route1 = merge_two_routes(routes[route_i_idx], routes[route_j_idx], i, j, 'ij')
        new_route2 = merge_two_routes(routes[route_i_idx], routes[route_j_idx], i, j, 'ji')
        dist1 = calculate_route_distance(new_route1, distance_matrix)
        dist2 = calculate_route_distance(new_route2, distance_matrix)

        if dist1 <= dist2:
            new_route = new_route1
        else:
            new_route = new_route2

        # 更新路线和需求
        routes[route_i_idx] = new_route
        route_demands[route_i_idx] += route_demands[route_j_idx]
        # 删除被合并的路线
        del routes[route_j_idx]
        del route_demands[route_j_idx]

    # 4. 对每条路线进行2-opt局部优化
    optimized_routes = []
    for route in routes:
        optimized_route = two_opt(route, distance_matrix)
        optimized_routes.append(optimized_route)

    return optimized_routes

def two_opt(route, dist_matrix):
    """2-opt局部搜索优化单条路径"""
    best_route = route
    improved = True
    while improved:
        improved = False
        for i in range(1, len(route)-2):
            for j in range(i+1, len(route)-1):
                # 尝试交换边 (i-1,i) 和 (j,j+1) 为 (i-1,j) 和 (i,j+1)
                new_route = route[:i] + route[i:j+1][::-1] + route[j+1:]
                if calculate_route_distance(new_route, dist_matrix) < calculate_route_distance(best_route, dist_matrix):
                    best_route = new_route
                    improved = True
        route = best_route
    return best_route

3.2 问题2与问题3:多约束下的建模与算法升级

问题2引入了多车型(MDVRP)和多垃圾类型。这里的核心思路是分解与协同。一种直接的方法是按垃圾类型分解:为每种垃圾类型单独求解一个VRP问题,但车辆类型和成本不同。然而,这样做忽略了不同垃圾类型收集点可能重合,导致车辆空跑。更好的方法是统一建模,将车辆类型和垃圾类型作为决策变量的一部分,构建一个更大的混合整数规划模型。但这样求解难度激增。

在实际竞赛中,我推荐一种两阶段启发式方法:

  1. 聚类分配阶段:根据所有收集点的地理位置和垃圾量,先用聚类算法(如K-means)将收集点分成若干区域,每个区域的垃圾总量不超过某型车的容量。聚类的目标是最小化区域内点之间的距离。
  2. 路径优化阶段:对每个区域,根据其包含的垃圾类型组合,分配合适的车型(可能需要多辆车),然后在这个较小的区域内运行VRP算法(如改进的节约算法或遗传算法)。

问题3更是加入了中转站选址和时间窗口,变成了一个选址-路径问题(LRP)。这是最难的一步。标准的解法是先选址,后路径,但这样可能得不到全局最优。更高级的做法是设计一个交互迭代的算法:

  • 阶段一(选址初筛):利用聚类中心、或者基于垃圾产生量的加权中心,初步选出几个潜在的中转站位置。
  • 阶段二(路径优化):在给定候选站点的前提下,求解带时间窗的VRP(VRPTW),得到运输成本。
  • 阶段三(选址调整):根据路径优化的结果,评估每个候选站点的使用效率和成本,调整站点位置(比如向服务点更密集的区域移动),然后回到阶段二。
  • 如此迭代几次,直到选址和路径方案趋于稳定。

对于时间窗口约束,在算法中需要增加时间计算和可行性检查。每访问一个点,都要更新当前时间,如果到达时间早于时间窗口开始时间,则需要等待;如果晚于时间窗口结束时间,则该路径不可行。在遗传算法或禁忌搜索中,这通常通过设计惩罚函数来处理,将违反时间窗口的程度作为一个惩罚项加到总成本(距离)中,引导搜索向可行解靠近。

4. 竞赛策略与论文写作点睛之笔

搞定了模型和算法,只成功了70%,剩下的30%在于如何把它们清晰地呈现给评委,也就是论文写作和结果展示。这部分往往是区分一等奖和二等奖的关键。

首先,摘要一定要下狠功夫。摘要是一篇论文的“脸面”,评委可能只用几分钟看摘要。你的摘要必须用精炼的语言,讲清楚:“针对什么问题,用了什么方法(模型),得到了什么结果(量化指标),有什么创新或亮点”。例如:“针对光伏功率预测问题,本文提出了一个结合CNN特征提取和LSTM时序建模的混合模型,并创新性地引入了基于天气场景划分的XGBoost集成策略。在公开数据集上,该模型的RMSE较基准LSTM模型降低了15%。针对垃圾运输路径优化,设计了一种融合K-means聚类与改进节约算法的两阶段启发式算法,并引入了时间窗惩罚机制,最终求解方案使总运输成本降低了22%。”

其次,模型部分要图文并茂。不要只贴大段公式和代码。画图!画流程图说明你的算法步骤,画结构图说明你的模型架构,画曲线图展示你的预测效果对比,画路径图展示你的车辆调度方案。一图胜千言。比如,展示光伏预测结果时,一定要有一张“预测值 vs 真实值”的时序对比图,并在图上标出关键误差指标。展示路径优化结果时,用不同颜色画出每辆车的行驶路线,并在地图上清晰标出中转站位置。

再次,结果分析要深入,不能只罗列数字。比如预测误差,不仅要写RMSE=100kW,还要分析误差主要分布在哪些时段(是日出日落时误差大,还是正午?),是什么原因造成的(可能是天气突变模型没捕捉到)。对于路径优化结果,要分析成本构成:运输距离成本占多少?车辆固定使用成本占多少?时间窗惩罚成本有多少?通过敏感性分析,告诉评委如果某个参数(如车辆容量)变化,结果会如何变化,这能体现你对模型的理解深度。

最后,稳定性与可扩展性讨论是加分项。谈谈你的模型对于数据缺失的鲁棒性如何?如果数据量增大十倍,你的算法还能在合理时间内求解吗?对于光伏预测,可以讨论模型在不同季节、不同气候区的泛化能力。对于路径优化,可以讨论如果城市规模扩大,你的算法框架如何调整以适应。这些思考能让你的论文立意更高。

我在带队参赛时,最后一天总会留出至少6个小时专门用来写论文和做图。再好的模型,如果表达不清,也是徒劳。记住,数学建模竞赛,既是“数学”和“建模”的竞赛,也是“沟通”和“展示”的竞赛。把你的解题思路,像讲故事一样,逻辑清晰、证据确凿地讲出来,你就已经赢了一大半。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐