其实很早就想着记录一下建模比赛的过程,但是大概是因为过程中似乎没有令人记忆深刻的点,加之研究生第一个学期比较忙(课多+写论文),所以给一直拖下去了。这学期论文第二稿提交后,也闲下来了一些,就趁着还有一些印象记录下来。但毕竟也过了半年左右,难免有一些记不清楚的地方。
比赛官网链接: https://cpipc.acge.org.cn/.
三个人一个队,研一的新生可以和其他学校的同学(比如本科或者高中的同学)一起组队,研一以上的只能在本校组队。我们当时就是宿舍的三个人,看通知有这个比赛而且学校报销报名费就报上去了,当时没有什么雄心壮志的。目标就是成功完赛交上论文退报名费。同时也没有数学建模的基础(师兄说这个比赛很简单给了我们自信),也没有提前看相关的内容,没有分工。。。。
所以如果想冲刺好一些的名次的话,还是建议好好准备,听别人说是上海落户加分最简单的比赛了。
题目的压缩包是很早就可以在网站上下载的,但是解压需要密码,到时间公布解压的密码,才可以看到里面的题目,这样减轻了网站下载的压力。
上午8点公布题,但我们宿舍睡到了11点半。。。起来看题:
看完题讨论了一会就准备在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变化的季节性特征。另外,计算了每种污染物与四种气象条件参数共二十四组的相关系数,由此体现不同气象条件对不同污染物浓度的影响的差异。
相关系数计算公式:
每个月的相关系数结果:
分析:
这一问我的思路就是用LSTM(长短期记忆模型)去做,因为之前对这一块有比较多的了解和准备。解题的想法就是训练一个模型,输入是历史数据序列(一次预报和实测数据),输出是二次预报数据。而题目提供了两年的数据,从数据量的角度看,完成一个模型的训练也是足够的。具体的步骤如下:
LSTM结构:
网络模型:
第一个输入参数为需要预报的空气污染物的实测数据,对于每种污染物,分别输入网络处理。第二个参数为对应空气污染物的一次预报数据。第三个参数输入的则是上一节分析得到的与污染物相关性较强的实测数据,根据第二问中的分析,这里选择为风速和湿度两个气象条件。由于臭氧O3是六种污染物中唯一的二次污染物,其并非来自污染源的直接排放,而在大气中经过一系列化学及光化学反应生成的。因此在对臭氧O3进行预报时,同时输入了对应时间的NO2实测数据值。同时引入了日内时参数 (hour in day, HD),以使得网络可以学习到日周期变化的特性,同时考虑参数的连续性,在处理时将 HD 参数分为 sin,cos 两个分量 HDs,HDc 表示。
对于输入的时间长度,由于输入矩阵需要为标准矩阵,考虑了两种设计:
在测试验证过程中,我们发现两种预报策略最后的精度相差不大,同时第二种策略训练时间是第一种的数倍之多,权衡之下考虑到实际应用场景,我们最后选择了第一种预报策略进行后续的研究和结果评估。
(关于这个点我还思考了挺久的,因为要让输入的数据是整齐的矩阵形式,而实测数据和一次预报的数据时间又不是对齐的,所以需要人为的把数据对齐,才能输入网络进行训练)
后面训练过程也没什么好说的,这里放一个结果图:
其实和第三问一样,只是多了个需要考虑其他站的影响,对于我写的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)#保存图片