news 2026/10/13 1:13:25

2020华为杯D题第一问复现:从rar解压到熵权法指标构建全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2020华为杯D题第一问复现:从rar解压到熵权法指标构建全流程

简介:这份资源是2020年华为杯第十七届研究生数学建模竞赛D题第一问的完整论文与程序包,面向备战研究生数模竞赛、研究无人机集群协同对抗与人工速度势场(APF)方法的参赛者与科研人员。内容围绕集群突防路径规划展开,涵盖目标吸引势与障碍物排斥势的建模思路、集群通信与角色分配逻辑,以及可成功突防区域边界的可视化分析。压缩包共4个文件,以3个m程序文件和1个pdf论文为主,程序分别对应集群突防主流程、动力学仿真与突防区域散点图绘制,论文则完整呈现模型构建、算法设计与结果验证,包体约909KB,轻量便于快速查阅。目前已有3530人学习下载,适合希望对照优秀赛题方案理解APF建模细节、复用程序框架或复盘第一问求解思路的读者参考。

1. 从一份 2020 华为杯 D 题压缩包说起:第一问到底在考什么

如果你手里正躺着一个叫「2020华为杯研究生数学建模国赛D题第一问论文+程序.rar」的文件,大概率是三种人之一:正在补做往年国赛真题的备赛选手、被导师要求复现优秀论文的研究生、或者想拿这道题练手数据分析 pipeline 的工程师。华为杯 D 题这类赛题的特点是数据量大、指标口径绕、第一问往往不是「算个结果」而是「把问题定义清楚」——它决定了后面几问的建模方向。第一问通常要求你对给定数据集做描述性统计、相关性分析、关键指标提取,并给出可解释的结论。很多人卡住不是因为不会写代码,而是没搞清题目要的「指标」和「数据字段」之间的映射关系。这篇笔记就按「解压看结构 → 读题拆指标 → 写程序跑通 → 避坑 → 进阶验证」的顺序,把第一问从压缩包到可复现结果的全过程讲透,适合能看懂 Python 但没做过建模题、或者做过但总在第一问翻车的读者。

2. 解压与数据摸底:rar 包里的目录结构和字段含义

2.1 拿到 rar 包先别急着跑程序

「rar解压」这件事本身就有坑。Windows 自带解压对老版本 rar 支持不全,遇到「rar密码移除」类工具生成的加密包更是直接报错。我一般用 7-Zip 或 Bandizip,7-Zip 对 rar5 格式兼容性最好,Bandizip 的预览功能方便你先看目录再决定解到哪。解压后典型结构是这样的:

D题第一问/ ├── 论文/ │ └── 第一问论文.pdf 或 .docx ├── 程序/ │ ├── main.py 或 question1.m │ ├── data/ │ │ ├── raw_data.xlsx │ │ └── processed.csv │ └── utils.py └── 结果/ └── figures/

注意:不同队伍打包习惯差异很大,有的把数据嵌在代码里,有的用相对路径引用外部文件。先确认data/目录是否存在,如果代码里写的是绝对路径(比如D:/建模/data.xlsx),你直接跑必报FileNotFoundError。

2.2 用 pandas 做第一轮字段体检

不管论文写得多漂亮,第一步永远是让数据自己说话。下面这段代码是我每次拿到新数据集必跑的「体检脚本」:

import pandas as pd import numpy as np # 读取时先不指定 dtype,让 pandas 自己推断,方便发现类型异常 df = pd.read_excel('data/raw_data.xlsx', sheet_name=0) # 1. 看形状和字段名 print("shape:", df.shape) print("columns:", df.columns.tolist()) # 2. 看每列的类型、缺失率、唯一值数量 summary = pd.DataFrame({ 'dtype': df.dtypes, 'missing_rate': df.isnull().mean().round(4), 'n_unique': df.nunique(), 'sample': [df[c].dropna().iloc[0] if df[c].notna().any() else None for c in df.columns] }) print(summary) # 3. 数值列的描述性统计 print(df.describe(include=[np.number]).T)

逻辑说明:第一步shape和columns让你知道有多少样本、多少特征;第二步的missing_rate是关键——建模题数据缺失超过 30% 的字段通常要么剔除要么插补,n_unique能帮你识别哪些是类别型字段(唯一值少)、哪些是连续型(唯一值接近样本数);第三步describe给出均值、标准差、四分位数,快速判断有没有量纲差异极大的字段(比如一个字段是 0-1 概率,另一个是几万的金额),这直接影响后面要不要标准化。

参数说明:read_excel的sheet_name要按实际 sheet 名改,有的题目数据分多个 sheet(训练集/测试集/说明表),漏读一个后面全错。如果数据是.csv且中文乱码,加encoding='gbk'或encoding='utf-8-sig'。

2.3 从论文里反推指标定义

程序跑出来的数字是冷的,论文里的指标定义才是热的。第一问论文一般会在「问题分析」或「符号说明」部分给出关键指标的计算口径。你要做的是把论文里的公式和代码里的变量对应起来。常见对应关系:

论文中的指标名代码中可能的变量名计算方式
综合得分score / total_score加权求和或熵权法
增长率growth_rate(本期-上期)/上期
归一化值norm_x / x_scaled(x-min)/(max-min)
相关系数corr_matrixdf.corr()

如果论文里写了「采用熵权法确定权重」但代码里只有df.mean(),说明这份程序可能不是最终版,或者论文和程序是两个人写的。这种情况以论文为准,自己补上权重计算。

3. 第一问的建模套路:描述统计、相关性、指标构建三步走

3.1 描述性统计不是调个 describe 就完事

很多新手以为第一问的描述性统计就是df.describe()贴进论文,结果被评委批「分析浮于表面」。真正的描述性统计要回答三个问题:数据分布是否偏态、有没有异常值、各字段量纲是否一致。下面是我常用的增强版统计脚本:

import matplotlib.pyplot as plt import seaborn as sns # 偏度和峰度:判断是否近似正态 skew_kurt = pd.DataFrame({ 'skewness': df.skew(numeric_only=True), 'kurtosis': df.kurtosis(numeric_only=True) }) print(skew_kurt) # 箱线图找异常值(IQR 法) Q1 = df.quantile(0.25, numeric_only=True) Q3 = df.quantile(0.75, numeric_only=True) IQR = Q3 - Q1 outlier_mask = (df[numeric_cols] < Q1 - 1.5*IQR) | (df[numeric_cols] > Q3 + 1.5*IQR) print("各列异常值数量:") print(outlier_mask.sum()) # 分布可视化 fig, axes = plt.subplots(2, 3, figsize=(15, 8)) for ax, col in zip(axes.ravel(), numeric_cols[:6]): sns.histplot(df[col].dropna(), kde=True, ax=ax) ax.set_title(col) plt.tight_layout() plt.savefig('results/figures/distribution.png', dpi=150)

逻辑说明:偏度绝对值大于 1 说明分布明显偏斜,后续建模可能需要对数变换;峰度大于 3 说明尾部厚,极端值多。IQR 法标记的异常值不要急着删——建模题里的「异常」往往是真实业务中的特殊情况,删之前先看论文有没有说明处理方式。分布图保存下来直接可以放进论文,比纯文字描述有说服力。

参数说明:1.5*IQR是经典阈值,如果数据本身波动大可以放宽到3*IQR。dpi=150保证论文插图清晰度,投稿级别可以设 300。

3.2 相关性分析:皮尔逊还是斯皮尔曼,别选错

第一问经常要求分析各指标之间的相关性。这里有个高频翻车点:数据不服从正态分布时用皮尔逊相关系数会严重失真。判断方法很简单——看上面算的偏度和峰度,如果多数字段偏度绝对值大于 1,果断换斯皮尔曼。

# 先做正态性检验(Shapiro-Wilk,样本量<5000时适用) from scipy import stats normality = {} for col in numeric_cols: sample = df[col].dropna() if len(sample) > 5000: sample = sample.sample(5000, random_state=42) stat, p = stats.shapiro(sample) normality[col] = p # p > 0.05 才认为近似正态 normal_cols = [c for c, p in normality.items() if p > 0.05] print("近似正态的字段:", normal_cols) # 根据正态性选择相关系数方法 if len(normal_cols) >= len(numeric_cols) * 0.7: corr = df[numeric_cols].corr(method='pearson') method = 'pearson' else: corr = df[numeric_cols].corr(method='spearman') method = 'spearman' print(f"使用 {method} 相关系数") sns.heatmap(corr, annot=True, fmt='.2f', cmap='RdBu_r', center=0) plt.savefig('results/figures/corr_heatmap.png', dpi=150)

逻辑说明:Shapiro-Wilk 检验的零假设是「数据来自正态分布」,p 值小于 0.05 就拒绝正态假设。如果超过 30% 的字段不满足正态性,整体改用斯皮尔曼秩相关。热力图的center=0让正负相关颜色对称,RdBu_r色带红正蓝负,一眼能看出强相关对。

参数说明:random_state=42保证抽样可复现。样本量超过 5000 时 Shapiro 检验会过于敏感(微小偏离也判为非正态),所以先降采样。

3.3 指标构建:熵权法还是层次分析法

如果第一问要求「构建综合评价指标」,常见做法是熵权法(客观赋权)或 AHP(主观赋权)。建模题里熵权法更常用,因为它不需要专家打分,完全由数据驱动。核心步骤:

def entropy_weight(data): """熵权法计算权重,data 为 DataFrame,每列一个指标""" # 1. 归一化(min-max),注意区分正向/负向指标 norm = (data - data.min()) / (data.max() - data.min()) # 2. 计算比重矩阵 p = norm / norm.sum(axis=0) # 3. 计算熵值,加 1e-12 防止 log(0) n = len(data) e = -p.apply(lambda col: (col * np.log(col + 1e-12)).sum()) / np.log(n) # 4. 计算权重 w = (1 - e) / (1 - e).sum() return w # 假设选了 5 个指标构建综合得分 selected = ['指标A', '指标B', '指标C', '指标D', '指标E'] weights = entropy_weight(df[selected]) print("各指标权重:") print(weights) # 计算综合得分 df['综合得分'] = (df[selected] * weights).sum(axis=1)

逻辑说明:熵权法的直觉是——某个指标在各样本间差异越大(熵越小),说明它携带的信息越多,权重应该越高。第 1 步归一化消除量纲影响,第 2 步把每个样本变成「占比分布」,第 3 步算信息熵,第 4 步用1-熵得到差异系数再归一化。如果某个指标是负向的(越小越好),归一化时要改成(max-x)/(max-min)。

参数说明:1e-12是防止log(0)的经典做法,也可以用np.log(col.replace(0, 1e-12))。权重之和恒为 1,可以直接乘原始归一化值得到综合得分。

4. 程序复现避坑:从报错到跑通的 5 个血泪经验

4.1 编码问题:中文列名读进来变乱码

现象:pd.read_csv后列名显示为\xe6\x8c\x87\xe6\xa0\x87A这类字节串。 原因:文件是 GBK 编码但 pandas 默认用 UTF-8 读。 解决:先试encoding='gbk',不行再试encoding='utf-8-sig'(带 BOM 的 UTF-8)。如果列名已经乱了,用df.columns = df.columns.str.encode('latin1').str.decode('gbk')抢救。

4.2 路径问题:代码里的绝对路径在你机器上不存在

现象:FileNotFoundError: [Errno 2] No such file or directory: 'D:/建模/data.xlsx'。 原因:原作者的盘符和目录结构和你不同。 解决:把所有路径改成相对路径,以脚本所在目录为基准。推荐用pathlib:

from pathlib import Path BASE = Path(__file__).parent df = pd.read_excel(BASE / 'data' / 'raw_data.xlsx')

4.3 版本问题:sklearn 的 API 在新版本里改了

现象:ImportError: cannot import name 'X' from 'sklearn.preprocessing'或TypeError: __init__() got an unexpected keyword argument。 原因:2020 年的代码用的 sklearn 0.22 左右,现在 1.x 版本很多参数改名或废弃。 解决:先pip show scikit-learn看版本,对照官方 changelog 改。常见改动:normalize=True改成Normalizer()管道、impute.SimpleImputer的axis参数移除。实在改不动就建虚拟环境装老版本:pip install scikit-learn==0.22.2。

4.4 数据缺失:dropna 之后样本量不够了

现象:df.dropna()后从 5000 行变成 800 行,后续统计全部失真。 原因:多个字段同时有缺失,交集后样本骤减。 解决:先看缺失模式——是随机缺失还是某个字段整列缺失。随机缺失用中位数/众数插补,整列缺失直接剔除该字段。插补代码:

# 数值列中位数插补,类别列众数插补 for col in df.columns: if df[col].dtype in ['float64', 'int64']: df[col].fillna(df[col].median(), inplace=True) else: df[col].fillna(df[col].mode()[0], inplace=True)

4.5 结果对不上:论文里的数字和你跑出来的差一点

现象:论文写「综合得分最高为 0.87」,你跑出来是 0.85。 原因:归一化范围不同(有的用 min-max,有的用 z-score)、权重精度截断、或者论文用了四舍五入后的中间结果继续算。 解决:先确认归一化方法是否一致,再检查论文有没有说明「保留几位小数」。如果差异在 5% 以内且趋势一致,通常不影响结论,可以在论文里注明「因计算精度差异,结果略有浮动」。

5. 进阶验证:怎么判断你的第一问结果真的靠谱

5.1 用交叉验证的思路检验指标稳定性

第一问的结论要经得起「换一批数据还成立吗」的追问。一个实用技巧是把样本随机分成两半,分别算相关系数和权重,看两次结果差异大不大:

from sklearn.model_selection import KFold kf = KFold(n_splits=5, shuffle=True, random_state=42) weight_records = [] for train_idx, _ in kf.split(df): w = entropy_weight(df.iloc[train_idx][selected]) weight_records.append(w) weight_df = pd.DataFrame(weight_records) print("各指标权重的均值和标准差:") print(pd.DataFrame({ 'mean': weight_df.mean(), 'std': weight_df.std() }))

如果某个指标权重的标准差超过均值的 20%,说明这个指标不稳定,论文里要谨慎解读。这个方法比单纯跑一次全量数据更有说服力,答辩时被问到「权重怎么来的」也能答得上来。

5.2 敏感性分析:改一个参数看结果变多少

熵权法对归一化方法敏感,你可以对比 min-max 和 z-score 两种归一化下的权重排序是否一致。如果排序完全不变,说明结论稳健;如果排序翻转,说明数据本身区分度不够,需要在论文里讨论局限性。

from sklearn.preprocessing import StandardScaler # z-score 归一化后重新算权重 z_norm = pd.DataFrame(StandardScaler().fit_transform(df[selected]), columns=selected) z_norm = (z_norm - z_norm.min()) / (z_norm.max() - z_norm.min()) # 再 min-max 到 [0,1] w_z = entropy_weight(z_norm) comparison = pd.DataFrame({ 'minmax_weight': weights, 'zscore_weight': w_z, 'rank_minmax': weights.rank(ascending=False), 'rank_zscore': w_z.rank(ascending=False) }) print(comparison)

5.3 论文与程序的一致性检查清单

最后交稿前,拿这张表过一遍,能省掉大部分「程序跑通但论文被挑刺」的情况:

检查项具体动作
指标口径论文公式和代码变量逐一对应
样本量论文写的 N 和代码len(df)一致
权重方法论文说熵权法,代码不能是等权平均
图表编号代码输出的图文件名和论文插图编号对应
小数位数论文保留 2 位,代码输出也 round(2)
异常值处理论文说剔除,代码里要有对应过滤逻辑

我自己的习惯是:每跑完一个脚本,立刻把关键输出(shape、权重、top5 得分)复制到一个log.md里,写论文时直接对照,避免「程序跑完就忘」。建模题的第一问从来不是最难的一问,但它是地基——地基歪了,后面几问的模型再花哨也白搭。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 1:13:23

串口数据解析实战:RS232/RS485与Modbus RTU帧拆解全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:13:19

TinyML硬件选型困境与自研开发板设计全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:12:56

低压变频器EMC整改实战:从骚扰时频特性到滤波屏蔽接地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:12:54

Apache Mesos两级调度与细粒度共享:把集群利用率从一半拉到80%

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:12:24

C# SQLite3增删改查实战:工业级本地数据库开发避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:12:22

深入解析JdkDynamicAopProxy:JDK动态代理与AOP调用链源码实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华