“华为杯”中国研究生数学建模大赛记录

2021华为杯数学建模 记录

其实很早就想着记录一下建模比赛的过程,但是大概是因为过程中似乎没有令人记忆深刻的点,加之研究生第一个学期比较忙(课多+写论文),所以给一直拖下去了。这学期论文第二稿提交后,也闲下来了一些,就趁着还有一些印象记录下来。但毕竟也过了半年左右,难免有一些记不清楚的地方。
比赛官网链接: https://cpipc.acge.org.cn/.

组队

三个人一个队,研一的新生可以和其他学校的同学(比如本科或者高中的同学)一起组队,研一以上的只能在本校组队。我们当时就是宿舍的三个人,看通知有这个比赛而且学校报销报名费就报上去了,当时没有什么雄心壮志的。目标就是成功完赛交上论文退报名费。同时也没有数学建模的基础(师兄说这个比赛很简单给了我们自信),也没有提前看相关的内容,没有分工。。。。
所以如果想冲刺好一些的名次的话,还是建议好好准备,听别人说是上海落户加分最简单的比赛了。

选题

题目的压缩包是很早就可以在网站上下载的,但是解压需要密码,到时间公布解压的密码,才可以看到里面的题目,这样减轻了网站下载的压力。
上午8点公布题,但我们宿舍睡到了11点半。。。起来看题:

  • A题,华为专项题——“相关矩阵组的低复杂度计算和存储建模”,要求是对矩阵储存算法进行优化,看了一下就pass了,属于是题目都看不懂。
  • B题,“空气质量预报二次建模”,要求是已知一个实测数据和一次预报数据(一次预报是通过模型算出的,没有考虑到实际数据 )利用实测数据对模型的一次预报数据进行建模优化,得到更为准确的二次预报数据。看完题我和队友说能做,因为我的本科毕设是做电离层预报的,两者的思路很类似,做起来比较得心应手。
  • C题,“帕金森病的脑深部电刺激治疗建模研究”,要求是对脑部刺激后,神经元的一系列反应和传递进行建模,看完题也没太考虑,因为题目理解就感觉比较累。
  • D题,“抗乳腺癌候选药物的优化建模”,主要是对化合物合成药物的成分进行选择,有一个同学选了这道题。
  • E题,“信号干扰下的超宽带(UWB)精确定位问题”,要求是对收集到的定位数据进行处理建模和应用场景的测试以及分类。这道题也比较想选,因为专业是和定位有关,相关的背景知识也很了解,而且看了一下题目给的数据,以及要求的分类模型,标签也很清楚,我觉的用SVM去做是一定可以的,甚至效果会很好。
  • F题,“航空公司机组优化排班问题”,要求是对飞行员的排班优化问题进行建模,因为题目太长加上前面有两个候选题,就pass了。
  • 看完题讨论了一会就准备在B和E题中选了,然后出去吃饭的时候商量了一下,最后因为我说B题后面两问我一个人就可以搞完,于是就决定做B题了。最后分工就是一个室友画图,一个室友做前两问,我做后两问。论文的话就各自写各自做的部分。

    问题重述

    根据提供的监测点长期空气质量预报基础数据,包括污染物浓度一次预报数据、气象一次预报数据、气象实测数据和污染物浓度实测数据进行分析建模,完成以下问题。
    空气预报建模

  • 问题1:AQI计算

    计算监测点A从2020年8月25日到8月28日每天实测的AQI和首要污染物。

  • 问题2:气象条件和AQI的相关性分析

    在污染物排放情况不变的条件下,某一地区的气象条件有利于污染物扩散或沉降时,该地区的AQI会下降,反之会上升。根据对污染物浓度的影响程度,对气象条件进行合理分类,并阐述各类气象条件的特征。

  • 问题3:单监测点的空气污染物二次预报模型

    建立一个同时适用于A、B、C三个监测点的二次预报数学模型,用来预测未来三天6种常规污染物单日浓度值,要求二次预报模型预测结果中AQI预报值的最大相对误差应尽量小,且首要污染物预测准确度尽量高。并预测监测点A、B、C在2021年7月13日至7月15日6种常规污染物的单日浓度值,计算相应的AQI和首要污染物。

  • 问题4:多监测点的区域协同二次预报模型

    相邻区域的污染物浓度往往具有一定的相关性,区域协同预报可能会提升空气质量预报的准确度。监测点A的临近区域内存在监测点A1、A2、A3,建立包含A、A1、A2、A3四个监测点的协同预报模型,要求二次模型预测结果中AQI预报值的最大相对误差应尽量小,且首要污染物预测准确度尽量高。使用该模型预测监测点A、A1、A2、A3在2021年7月13日至7月15日6种常规污染物的单日浓度值,计算相应的AQI和首要污染物。并研究与问题3的模型相比,协同预报模型能否提升针对监测点A的污染物浓度预报准确度?说明原因。

  • 第一问解题

    第一问很简单,只要按照公式写一些代码计算出来就好了,注意一下臭氧是滑动平均值。

    第二问解题

    该问主要是做相关性分析,难度门槛也不高,所以该算的东西一定要算,然后根据算出来的相关性给到对应的解释与分析,基本这一问就完成了。我觉的最重要的是把图做好看,因为如果只给一堆数字,看上去就会很乱,用图的话在论文里展示出来也会很丰富。
    同时考虑到有些时间点缺失数据,后面两问做的时候数据也需要补全,所以在做相关性分析之前,先把缺失数据的时间点给内插了出来,方法就是拉格朗日插值法。


    计算过程:
    首先使用监测点A的每日污染物浓度数据计算出A点每日的AQI。之后根据每小时的气象条件数据计算出日均的气象条件参数,以二十四个整点时刻的均值代表当天的气象条件参数水平。然后按全年十二个月将数据进行分类,计算每个月内AQI与各项气象条件参数的相关系数,由此评估不同月份中气象条件对AQI的不同影响,得到气象条件与AQI变化的季节性特征。另外,计算了每种污染物与四种气象条件参数共二十四组的相关系数,由此体现不同气象条件对不同污染物浓度的影响的差异。
    相关系数计算公式:

    每个月的相关系数结果:

    分析:

  • 湿度:在一年中的绝大部分时间,空气质量指数AQI与湿度呈负相关,相关系数为负,呈现出一定的季节性特征,在八、九月份相关系数绝对值达到最大,为-0.70,表明在这两个月份湿度的上升会显著降低AQI,在五月份和十二月份,AQI与湿度呈正相关,相关系数分别为0.10和0.20,在十一月份,AQI与湿度的相关系数为0,表明在该月内,AQI与湿度不相关。
  • 压强:AQI与其相关性呈现出一定的季节性特征,在春季两者呈正相关,三月份和五月份两者的相关系数达到最大,为0.30,在其它时间大都呈负相关,其中负相关系数绝对值最小的月份为一月和六月,相关系数为-0.20,负相关系数绝对值最大的月份为十二月,相关系数为-0.70,值得一提的是,从十月份到十二月份,AQI与压强呈负相关,且相关系数绝对值从0.30一直增长到0.70,表明进入秋冬季后,AQI与压强的相关性一直在增强,另外,在九月份,AQI与压强呈现了正相关,这可能与及其它因素如监测点A的地理位置和其它气象因素等的影响有关。
  • 风速:AQI与其相关系数在全年皆为负,在这种情况下,两者的相关性仍呈现出一定的季节性特征,在春夏季,两者的相关系数绝对值较小,绝对值最小出现在四月份和六月份,对应的相关系数为-0.20,绝对值最大也仅为0.5,出现在五月份,进入秋冬季后,AQI与风速的相关系数绝对值逐渐增大,相关性增强,最大出现在十二月份,最大绝对值为0.8。值得注意的是,根据[1]对成都市的污染物浓度变化研究,污染物的浓度均值随着风速的增加而减小,AQI应该上升,但在本例的分析中,随着风速的增加,AQI却呈下降趋势,即空气质量降低,这可能是因为监测点A的地理位置等因素导致风速增加利于污染物的形成或聚集,另外,分析时未加入风向对污染物浓度的影响,这可能也是导致AQI与风速呈负相关的原因之一。
  • 温度:在全年的大部分时间中,AQI与温度呈正相关,且呈现出一定的季节性特征。在春季,两者的相关性较低,三月份两者的相关性最低,相关系数为0.0,表明两者在三月份不相关,在春季两者的相关系数绝对值最大也仅为0.30,最大出现在五月份,相关系数为-0.30。在夏季,两者的相关性逐渐正向攀升,其相关系数从六月份的-0.10,到七月份的0.40,到八月份的0.70达到全年最高,此后一直维持在较高的水平,至一月份减小至0.20后,二月份又升高至0.50。 综合考虑四种气象条件参数对AQI的影响随月份的变化可以发现,每种气象条件参数与AQI的相关性都呈现出一定的季节性特征。
  • 在春季,AQI与压强呈正相关,与其它三种气象条件参数呈负相关,但相关性最强的参数为风速,表明在春季,风速的变化对AQI的影响更显著;
  • 在夏季,AQI与温度呈正相关,与其它三种气象条件参数呈负相关,相关性最强的参数为温度和湿度,另外两种参数相较之下对AQI的影响更小;
  • 在秋季,AQI与温度呈正相关,与其它三种气象条件参数呈负相关,而相关性最强的两种参数依次为温度和风速,表明在秋季,温度和风速的变化对AQI的影响更显著;
  • 在冬季,AQI与温度呈正相关,与其它三种气象条件参数呈负相关,相关性最强的两种参数依次为风速和温度,表明在冬季,风速和温度的变化对AQI的影响更显著。
    为了呈现出气象条件对每种污染物浓度各自的影响,本文对2019年4月16日 ~2021年7月13日二氧化硫(SO2)、二氧化氮(NO2)、粒径小于10μm的颗粒物(PM10)、粒径小于2.5μm的颗粒物(PM2.5)、臭氧(O3)、一氧化碳(CO)六种污染物浓度与四种气象条件参数温度(℃)、湿度(%)、气压(MBar)、风速(m/s)进行了线性回归分析,结果如图所示。

    另外,对于监测站A的2020年7月23日~2021年7月13日的气象一次预报数据,本文对其中所有的预报元素两两计算了相关系数,得到的相关性热图,如图所示。

    可以看出,大部分预报元素之间相关性很弱,就本文所关心的六种污染物浓度而言,除臭氧之外的五种污染物,它们的每小时平均浓度与近地2米温度,地表温度,比湿,大气压,长波辐射相较别的元素有较强的相关性,这几种因素涉及到的气象条件参数有温度,湿度,气压。另外,它们两两之间也有较强的相关性。对臭氧而言,它仅与边界层高度、感热通量、潜热通量、短波辐射和地面太阳能辐射这四种元素有较强的相关性,而与其它元素的相关性很弱甚至没有,这可能与的形成机制有关。
  • 第三问解题

    这一问我的思路就是用LSTM(长短期记忆模型)去做,因为之前对这一块有比较多的了解和准备。解题的想法就是训练一个模型,输入是历史数据序列(一次预报和实测数据),输出是二次预报数据。而题目提供了两年的数据,从数据量的角度看,完成一个模型的训练也是足够的。具体的步骤如下:

    LSTM结构:
    LSTM结构
    网络模型:

    第一个输入参数为需要预报的空气污染物的实测数据,对于每种污染物,分别输入网络处理。第二个参数为对应空气污染物的一次预报数据。第三个参数输入的则是上一节分析得到的与污染物相关性较强的实测数据,根据第二问中的分析,这里选择为风速和湿度两个气象条件。由于臭氧O3是六种污染物中唯一的二次污染物,其并非来自污染源的直接排放,而在大气中经过一系列化学及光化学反应生成的。因此在对臭氧O3进行预报时,同时输入了对应时间的NO2实测数据值。同时引入了日内时参数 (hour in day, HD),以使得网络可以学习到日周期变化的特性,同时考虑参数的连续性,在处理时将 HD 参数分为 sin,cos 两个分量 HDs,HDc 表示。

    对于输入的时间长度,由于输入矩阵需要为标准矩阵,考虑了两种设计:

    1. 由于需要预报未来三天64组数据(第一天8:00至第三天23:00,一小时一组数据),输入数据中一次预报的数据时间即为未来这三天64小时对应的数据,而与实际观测相关的数据则为预报时刻前的64组数据,这样两类数据可以组成一个二维矩阵输入网络。
    2. 将需要预报的未来64组数据分为N组,进行层层递进的预报方式。如N=4时,每次预报64/4=16小时的数据,对于第一次预报,和1中类似,为构成二维矩阵,输入数据中一次预报的数据时间即为未来16小时对应的数据,而与实际观测相关的数据则为预报时刻前的16组数据。对于第二次预报,实际观测相关的数据则替换为第一次预报的结果,以此类推,最后得到64小时的预报值。

    在测试验证过程中,我们发现两种预报策略最后的精度相差不大,同时第二种策略训练时间是第一种的数倍之多,权衡之下考虑到实际应用场景,我们最后选择了第一种预报策略进行后续的研究和结果评估。
    (关于这个点我还思考了挺久的,因为要让输入的数据是整齐的矩阵形式,而实测数据和一次预报的数据时间又不是对齐的,所以需要人为的把数据对齐,才能输入网络进行训练)
    后面训练过程也没什么好说的,这里放一个结果图:

    第四问解题

    其实和第三问一样,只是多了个需要考虑其他站的影响,对于我写的LSTM模型来说,就是多加一个输入的维度而已。但是我取了个很吊的名字——区域智能协同的空气污染物二次预报模型(哈哈哈哈哈哈哈)

    后面过程和第三问也基本一样,放一个对比结果图:

    分析结果:
    通过问题三和问题四的监测站A在不同条件下进行的预报结果可知,两种条件下的预报结果有一定的差异性和相似性。在两种条件下的预报结果中,7月13日均无首要污染物, 并且在13日当天,各污染物浓度在两种条件下相对变化率不大,因此AQI同样差异不大但在7月14日与7月15日AQI差异较大,引起AQI差异的主要原因来自臭氧的变化,SO2、 PM10 、PM2.5、CO预报浓度发生的改变不明显,NO2预报浓度差异较大,因此预报结果 的变化主要由O3与NO2引起。通过监测站A、A1、A2、A3的联测预报,A站O3与NO2在单 站预报的基础上大大减小,而通过分析A1、A2、A3的数据可以看到,该三个站的O3与NO2 均比A站的O3与NO2要小,这是区域协同模型预报结果比单监测站预报结果的根本原因。 通过加入测站周围的测站,同时提供模型以地理信息相关因素,会使得监测站的数据受到 周围测站数据的修正,说明区域协同模型比单监测站模型的预报结果更能体现该区域的未来污染物变化特性。

    最后写了一些模型的评价与推广,就是硬吹,现在看尬的不行。

    赛后总结

    其实也没啥好总结的,前面看题的时候太自信了,导致一共四天的比赛前面两天大家都在上课开组会吃饭摆烂,结果到了最后两天着急了,最后一天通宵搞到7点把论文提交了后吃了个饭回宿舍睡到了下午。也是因为没什么经验,后面非常的赶,而我又要把两问的代码实现,把结果画成图,很多评估模型的部分我也没能弄的比较详细,一些后面觉的很棒的想法也没能实现。但是也不能说后悔吧,怎样的经历都是自己的选择,自己的选择就是最好的。以后争取做的更好。
    记忆最深刻的就是通宵那个晚上,我写论文写的有点累了,于是拿了瓶rio,去楼顶吹风,当时看着万籁寂静的珞珈山,静静的站着,感觉很奇怪,想起了在林芝的晚上,看着前面的南迦巴瓦峰,仿佛手中抓住了时间的流逝,山就在那里,可以远远的看着他,但更想去触摸他。
    我们都在朝着一个具体的方向向前奔走
    12月份出结果,运气很好,这份我觉的半完成度的论文拿了二等奖。

    一些代码
    代码可能有些乱,当时是ipynb格式写的。其实很简单,用tensorflow的keras库直接搞的,搭建网络很方便,训练的代码可能还没有处理数据和画图写的代码多。

    #!/usr/bin/env python
    # coding: utf-8
    
    from tensorflow import keras
    from tensorflow.keras.models import Sequential
    from tensorflow.keras import layers
    from tensorflow.keras.optimizers import RMSprop
    
    import matplotlib.pyplot as plt
    import matplotlib
    
    import numpy as np
    import csv
    
    folder = r'C:\Users\yes\A站结果\\'
    real_csv = 'A点内插观测值.csv'
    pre_csv = 'A点一次预报值.csv'
    
    with open(folder+real_csv) as f:
        data = csv.reader(f, delimiter=",")
        real_data = []
        for line in data:
            line = list(map(float,line))
            real_data.append((line))
    real_data = np.array(real_data)
    print(real_data)
    
    with open(folder+pre_csv) as f:
        data = csv.reader(f, delimiter=",")
        pre_data = []
        for line in data:
            line = list(map(float,line))
            pre_data.append((line))
    pre_data = np.array(pre_data)
    print(pre_data)
    
    pre_all_one = np.vstack((real_data[:,4],pre_data[:,4]))
    pre_all = np.hstack((pre_all_one.transpose(),real_data[:,10:12]))
    #针对O3特殊处理
    pre_all = np.hstack((pre_all,real_data[:,1].reshape(len(pre_all),1)))
    
    data_mean = pre_all.mean(axis=0)
    data_std = pre_all.std(axis=0)
    
    deal_data = (pre_all - data_mean) / data_std
    
    def generator(data, lookback, delay, min_index, max_index, target_num=0,
                  shuffle=False, batch_size=64):
        if max_index is None:
            max_index = len(data) - delay - 1
        i = min_index + lookback
        while 1:
            if shuffle:
                rows = np.random.randint(
                    min_index + lookback, max_index, size=batch_size)
            else:
                if i + batch_size >= max_index:
                    i = min_index + lookback
                rows = np.arange(i, min(i + batch_size, max_index))
                i += len(rows)
    
            samples = np.zeros((len(rows),
                               lookback,
                               data.shape[-1]))
            targets = np.zeros((len(rows),delay))
            
            for j, row in enumerate(rows):
                    indices_sam = range(rows[j] - lookback, rows[j])
                    indices_tar = range(rows[j] + 1, rows[j] + 1 + delay)
                    samples[j] = data[indices_sam]
                    targets[j] = data[indices_tar][:,0]
            yield samples, targets
    
    lookback = 64
    delay = 64
    batch_size = 16
    
    train_gen = generator(deal_data,
                          lookback=lookback,
                          delay=delay,
                          min_index=0,
                          max_index=None,
                          shuffle=True,
                          batch_size=batch_size)
    val_gen = generator(deal_data,
                        lookback=lookback,
                        delay=delay,
                        min_index=8001,
                        max_index=None,
                        batch_size=batch_size)
    test_gen = generator(deal_data,
                         lookback=lookback,
                         delay=delay,
                         min_index=8001,
                         max_index=None,
                         batch_size=batch_size)
    
    # This is how many steps to draw from `val_gen`
    # in order to see the whole validation set:
    val_steps = (len(deal_data) - 8001 - lookback) // batch_size
    
    # This is how many steps to draw from `test_gen`
    # in order to see the whole test set:
    test_steps = (len(deal_data) - 8001 - lookback) // batch_size
    
    
    model = Sequential()
    model.add(layers.LSTM(64,
                #return_sequences = True,
                input_shape = (None,deal_data.shape[-1])))
                #input_shape = (None,1)))
    #model.add(layers.LSTM(32))
    model.add(layers.Dense(64))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit(train_gen,
                        steps_per_epoch=200,
                        epochs=30,
                        validation_data=val_gen,
                        validation_steps=val_steps,
                        verbose = 1)
    
    
    import matplotlib.pyplot as plt
    
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epochs = range(len(loss))
    
    plt.figure()
    plt.subplots(figsize=(20,10))
    plt.plot(epochs, loss, 'ro', label='Training loss')
    plt.plot(epochs, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend(fontsize=24)
    plt.show()
    
    
    test_data = deal_data[-64:,].reshape(1,64,5)
    results=model.predict(test_data, batch_size=1)
    ypx = results * data_std[0] + data_mean[0]
    
    
    #设置标签格式
    matplotlib.rcParams['xtick.labelsize'] = 18
    matplotlib.rcParams['ytick.labelsize'] = 18
    # X、Y轴刻度标签字体大小
    matplotlib.rcParams['axes.titlesize'] = 24
    matplotlib.rcParams['axes.labelsize'] = 24
    fig, ax = plt.subplots(figsize=(10,6),)
    plt.ylim(0,200)
    plt.plot(pre_all[-64:,1] , '^', color='r', label='First_predict', markersize=8)
    plt.plot(ypx.reshape(64), 'o', color='b', label='Twice_predict', markersize=8)
    plt.legend(fontsize=20)
    plt.xlabel('Epoch(1h)')
    plt.ylabel('O3(μg/m³)')
    plt.savefig('O3.jpg', dpi=300)#保存图片
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » “华为杯”中国研究生数学建模大赛记录

    发表评论