文章目录
0、前言
为节省篇幅,本记录的代码放在另一篇博客里,链接
1、截至2月23日14:40(best score=-19.6)
1.1、基本思路
从测试的角度来看,输入数据维度:
(
m
o
n
t
h
s
,
l
a
t
s
,
l
o
n
s
,
f
e
a
t
u
r
e
s
)
(months, lats, lons, features)
(months,lats,lons,features)
其中,
m
o
n
t
h
s
=
24
,
l
a
t
s
=
24
,
l
o
n
s
=
72
,
f
e
a
t
u
r
e
s
=
4
months=24, lats=24,lons=72,features=4
months=24,lats=24,lons=72,features=4。
输出数据维度:
(
m
o
n
t
h
s
,
)
(months,)
(months,),其中,
m
o
n
t
h
s
=
24
months=24
months=24。
训练时维度与测试匹配,只是多一个
b
a
t
c
h
batch
batch维度,人为设定。
training input:
(
b
a
t
c
h
,
m
o
n
t
h
s
,
l
a
t
s
,
l
o
n
s
,
f
e
a
t
u
r
e
s
)
(batch,months, lats, lons, features)
(batch,months,lats,lons,features)
training output:
(
b
a
t
c
h
,
m
o
n
t
h
s
)
(batch,months)
(batch,months)
框架与toy model类似,CNN+时序模型(LSTM/GRU/Seq2Seq/Attention…)
把12个月先看做12通道的图片,用卷积核提取像素点特征,然后再输入时序模型,时序模型的output长度(24)大于input长度(12),所以我觉得LSTM或GRU大概不会很合适。Linear
层一般是对特征feature做变换吧,对序列长度做变换合适吗?
我个人觉得滑窗/历史平移好像不太行,因为测试的时候是不给历史的Nino 3.4 index的,除非。。。用历史特征先预测出历史的Nino 3.4 index,然后再做滑窗,但是那样会很麻烦,我估计效果不会太好。
对label做一阶/二阶差分我觉得也不太合适,做差分一般是为了消除趋势,但是这么做好像一般是用在只有label的情况下的?比方说只有客流量数据,要用前一段时间客流量预测之后的客流量。但是这个题不一样吧,他还给了4个气象因子呢,气象因子也是有趋势的吧,我们消除了label的趋势,是不是也要消除一下features的趋势?按理说Nino 3.4 index是会随着这四个特征变化而变化的。所以我觉得应该不用差分吧。二来,差分后还原是要初值的,初值哪里来?正如下面一段也涉及初值的问题。
如果是Seq2Seq这种的话,decoder的初始值是啥呢?在机器翻译中一般是<SOS>/<BOS>,但是正如上面说的测试的时候没有历史的label,所以我觉得要么就拿label的均值or中位数这种统计量充当初值,要么就用LSTM/GRU先预测一个初值,再输入。
1.2、数据预处理
见代码段1.2.1
- 其实
sklearn
里是有train_test_split
和StandardScaler
的,但是我还是写了自己的版本,减少第三方库的使用,加快docker build速度。 - 训练数据量可以说是很大很大了,把所有数据连接起来(也就是通过
CMIP_gen(13)
生成的pkl
文件)大概是1.48GB,直接把我小破笔记本卡的不要不要的。所以我产生了分批训练的想法,每400个样本作为一批,具体下小节再说。 - 从上一小节对input和output的分析来看,sample里只有36个月的前12个月才是有用的,所以我扔掉了sample的后24个月的数据;label里只有后24个月才是有用的,所以我扔掉了前12个月的数据。
为了验证不同模式的样本可以混合起来训练,我画了SODA和CMIP+SODA(Total)的分布。
画图代码见代码段1.2.2
max: 4.1381884 min: -3.5832222 mean: -0.14928016 std: 0.8475895
SODA_max:3.1885402 SODA_min: -2.0704737 mean: -0.005200089 std: 0.7847775
可以看出来两种的统计量差不多,分布的差别也不大,属于中心很接近的两个正态分布。
P.S. 分布直方图有三种颜色,淡紫色是SODA和Total重合的部分。
我原来还有个缩小数据规模的想法,就是对不同CMIP模式取均值,但是分析一通后发现,不同CMIP模式的相关性似乎不是很高。。。
我挑了CMIP6前四种模式的第一年的sst,小标题是相关系数,无论是从相关系数还是从散点分布来看,都看不出什么相关性。所以最后我还是没有取均值。
1.3、网络结构
见代码段1.3.1
- Seq2Seq和Attention还没来得及试,我总感觉最后通过一个
Linear
层得到24个月的label这么做哪里怪怪的,按我以前的学习Linear
一般是对特征做变换,但这24个月显然不是特征吧,而是时间点,应该用many to many的模型来得到24个时间点更合适 - 据说RNN用正交初始化效果不错,参考链接
1.4、训练网络
见代码段1.4.1
与score对应,损失函数也是让越往后的时间点预测权重更高,这么做也是希望拿更高的score
见代码段1.4.2
评价体系与官方保持一致,代码参考了时间序列实践 - 赛题理解及Baseline12:10处的截屏,并做了一定改动。
见代码段1.4.3
以上是超参数的设定。
昨天,我只让SODA参与训练验证。主要问题是过拟合太严重了,Dropout,BatchNorm,weight decay, flooding,early stop各种奇技淫巧差不多都用了,还是没法抑制过拟合,说明网络基本上没学到什么重要的特征。但是另一方面来说也还可以,验证集score最高能达到17,不小的正数了。但是测试后发现分数还是-20左右。
今天,我把把SODA CMIP数据一起输进去训练,CUDA溢出了,难受了。
再试试分批训练。每一批是一个大的Batch,一共是1+12个Batches(一个SODA,十二个CMIP),SODA批次有100个样本,CMIP批次前11个都是400个样本,最后一个批次是245个样本。
蓝色训练集,红色验证集。可以看出score具有很明显的周期性,每个峰值都是SODA的score,说明SODA还是比较好学的,CMIP就比较难学。
对曲线做平滑处理
纵然score一直是负的,并且强烈波动,但毕竟趋势是向好的,说明数据特征是可学习的。但是到训练后期仿佛就进入一个瓶颈了,score不上升loss也不下降了。
这种周期性大概就是深度学习模型的“灾难遗忘”,通俗来讲就是学了后面的忘了前面的。
2、截至2月23日23:30(best score=6.2)
2.1、模型结构
据说maxout激活函数能一定程度上防止灾难遗忘(参见花书第六章第三节[1])
然后我就自己写了一个maxout模块(因为PyTorch没有现成的),代替ReLU。但是效果很烂,loss很难下降,train score 和 vali score都很难上升,可能是我代码写得有问题。要么就是非线性程度太高不利于学习。(图片文字就是上一张图的下一段)
见代码段2.1.1
我也把我的代码贴出来(参考了nn.Linear
的源码),供大家讨论。我把channel数目看作神经元个数,因为maxout是激活函数,所以激活前后神经元个数不变,我在网上看的一些maxout代码,都改变了神经元个数(channels),我感觉不太符合激活函数的本意。BTW,有关maxout去年我在b站做了一个讲解视频,有兴趣的可以去看看。
然而这都不是重点!maxout根本就训练不起来,直接打入冷宫叭~
2.2、训练网络
后来我就试试把所有SODA和300个CMIP样本合起来训练(之前是400个CMIP,现在把其中100个替换成SODA)。没想到效果贼好??vali score竟然达到了26!!虽然还是有严重过拟合,因为train score可以达到90-100,但是还是很不错的了。我赶紧提交结果,发现真的可以欸,test score到6了哈哈哈!
Reference:
[1]Ian Goodfellow, Yoshua Bengio, Aaron Courville.Deep Learning[M].MIT Press:,2016:167-168.
3、截至2月24号19:10(best score=6.2)
3.1、数据预处理
还在苦苦思索如何准备一个合适的训练集。。。基本的想法是“及时复习”。每次复习200个、新学400个样本。
见代码段3.1.1
3.2、训练网络
就用这种training data set 训练了6次,在第6次第24个epoch,验证集score勉勉强强提升到了30.31。
就用这个30.31的模型来测试,提交结果后发现只有-11.52???心态又崩了啊
总结一下目前的一些待实现的ideas:BoTorch、Attention、持续学习。
4、截至2月24日23:21(best score=13.87)
4.1、网络结构
上述ideas还没来得及实现,因为我相信“复习”法肯定有效果。窃以为上次的-11.5分还是因为过拟合太严重了,所以我加入了更多的dropout抑制过拟合。
4.2、训练网络
依然采用复习大法。现在vali score提升到34.11了,这个峰值出现在第一个round第二个Batch的第4个epoch,后面就进入瓶颈期了。
另外很明显可以看到train score 和vali score差距变小了,周期性仍然存在但是波动没有那么强烈了,说明dropout真的是有效果的!可以从一定程度缓解灾难遗忘~
提交结果后发现test score提升到了13.87!啊啊啊啊啊好棒!
5、截至2月25日12:58(best score=17.89)
5.1、网络结构
今天早晨起来,再次审视了一下昨晚的结构,感觉dropout还是有不合理之处
这四个dropout不合适,因为做完第二次max pooling 每个气象因子的每个时间步只剩1×3=3个像素了,这样dropout好像最后就寥寥无几了?不合适不合适,所以我把这边的dropout去掉了。到目前为止一共是5处dropout(4个池化后的,1个cat后的)。
5.2、训练网络
增加了两个batch。
vali score最高达到了35.8,提交结果后test score是17.89
比较一下三次正test score的结果。用所给test样例可视化。
- 绿色线条是最初的,只用了前四百个样本(70SODA+330CMIP),dropout也只有一处。
- 蓝色线条是第二次的,用了6个batches(400+600×5),dropout有9处。
- 橙色线条是最新的,用了8个batches(400+600×7),dropout有5处。
可以断定,前三个月label肯定是急速上升,这大概是sample 400的短板;后几个月label应该分布在0-0.3之间。最后可以考虑做集成学习(按test score加权?)。
6、截至2月25日23:41(best score=17.89)
6.1、网络结构
成绩没有进步,但想说两点:
- 之前的
GRU
都是有问题的,__init__
的时候忘记设置batch_first=True
了,但是竟然能预测的很好。。。?改过来以后发现,反而没有之前预测的好。 - 试了attention机制,发现效果不尽如人意,代码段放上来,哎,也许是写的有问题,大家看看。
代码段6.1.1,attention的代码
Encoder很好弄,比较难的是Decoder。因为input有四种气象因子,至少有四个特征(实际上做完最后一个max pooling再拉平是12个特征),output是一维的,也就是只有一个特征,这也太少了。一个最简单的想法是用Linear
,但我不甘于此。回想NLP中通常用词嵌入的方法来给每个词编码,我就产生了一个很naive的想法,我能不能也把这些Nino 3.4 index也当成一个一个词呢?因为咱们做的是回归,数据是连续分布的,总不能有无穷多个“词”吧?所以我进而想到了规定一个精度。比方说精度为0.001,那么,0.3001和0.3002是同一个词,但是0.301和0.302是两个不同的词。
这样一来,根据我们在1.2中算的最值,可以粗略地把范围划在 [ − 4 , 5 ] [-4,5] [−4,5],精度取0.01,一共有 5 − ( − 4 ) 0.01 + 1 = 901 \frac{5-(-4)}{0.01}+1=901 0.015−(−4)+1=901个“词”。考虑到inference的时候可能会超出这个范围,必须在embedding前加一个线性整流单元把上次的预测值clip在 [ − 4 , 5 ] [-4,5] [−4,5]里面。
关于初值的问题,SODA均值-0.005,总体均值-0.149,我粗略地取了-0.05,没什么道理,就是在俩均值之间随便选了一个点。如果这么选的话,可以把这个初值作为超参,最后慢慢调参去吧~
关于这个的结果。。。可能是哪儿出bug了,我竟然预测出一条水平线🤦