前言
一直都想参加一次阿里天池比赛,看到有公众号提了这次比赛的baseline,果断报名。0基础,开始了第一次数据科学比赛的尝试~
这次比赛遇到了各种各样的问题,几度想放弃,但是不忍心排200多名。来都来了,冲完B榜吧,也算是表明自己第一次参加天池大赛的态度,不能让母校的名字排在最后。
题目
https://tianchi.aliyun.com/competition/entrance/531871/introduction
发生在热带太平洋上的厄尔尼诺-南方涛动(ENSO)现象是地球上最强、最显著的年际气候信号。通过大气或海洋遥相关过程,经常会引发洪涝、干旱、高温、雪灾等极端事件,对全球的天气、气候以及粮食产量具有重要的影响。准确预测ENSO,是提高东亚和全球气候预测水平和防灾减灾的关键。
本次赛题是一个时间序列预测问题。基于历史气候观测和模式模拟数据,利用T时刻过去12个月(包含T时刻)的时空序列(气象因子),构建预测ENSO的深度学习模型,预测未来1-24个月的Nino3.4指数,如下图所示:
这题是一个典型的时间序列预测问题,看起来没那么难,但是认真一想还是有很多难点的,比如数据质量,内存限制,模型,都是可以改进的,下面是我的baseline。
数据分析
拿到数据,我都是一股脑往模型里扔,然后开始炼丹,炼着炼着,分数从-6变为-10、-13、-15、-22…最后都到NaN了,平时老师也总是说,不要一上来就上模型,先把数据分析清楚了,于是打算再花几个小时进行数据分析。
CMIP数据分析
CMIP是模拟数据,网友UCDuan说:
CMIP是气候模型的数据
你可以理解为不同情况下(不同的初始场和不同的气候模型)“地球”的真实情况,只是符合大致的‘物理规律’,但不是地球真实的历史情况。所以用CMIP就是想挖掘这个‘物理规律’。
这里我打算拿CMIP数据训练,SODA数据验证。
数据统计
以sst数据为例
趋势图
可以看出数据是经过处理的,每隔12个月,连续三组数据值是一样的
再加上下个特征,发现他们也是相似的
另外有些特征数据缺失,需要进行平均填充
方法如下:
tr_features2 = np.concatenate([sst_CMIP_shaped,t300_CMIP_shaped,va_CMIP_shaped,ua_CMIP_shaped],axis=-1)
tr_features2[np.isnan(tr_features2)] = np.mean(tr_features2)#对NaN值进行均值填充
SODA数据分析
代码
baseline来自这篇文章:https://mp.weixin.qq.com/s/63LPCHNo4zOA_UGDAc2xUQ
这篇文章完成了数据组织、处理、MLP模型构建、本地测试、上传代码,但是有很多问题,我进行了初步修正:
- 由于内存限制,CMIP数据只加载1000年==》全部
- 前面数据转换为csv文件,后面没有用到==》后面数据加载来源于csv文件
- 模型使用MLP==》模型改为3维CNN
代码链接:https://github.com/guhang987/-AI-Earth-
代码解释:
- ./ENSO/data文件夹
存放原始数据与转换后的数据
- data_prepare.py
将比赛提供的nc文件转换为csv文件 - train.ipynb
训练模型 - main.py
模型打包提交
训练
conda环境:
- python 3.8
- tensorflow 2.20
首先进行数据格式转换,提取数据前24个月,以及标签后12个月的数据。
soda_label = pd.read_csv('./ENSO/data/df_SODA_label.csv')['label']
sst_SODA = pd.read_csv('./ENSO/data/df_sst_SODA.csv')
t300_SODA = pd.read_csv('./ENSO/data/df_t300_SODA.csv')
ua_SODA = pd.read_csv('./ENSO/data/df_ua_SODA.csv')
va_SODA = pd.read_csv('./ENSO/data/df_va_SODA.csv')
sst_SODA = np.array(sst_SODA).reshape(100,36,24,72,1)[:,:12,:,:,:]
t300_SODA = np.array(t300_SODA).reshape(100,36,24,72,1)[:,:12,:,:,:]
va_SODA = np.array(va_SODA).reshape(100,36,24,72,1)[:,:12,:,:,:]
ua_SODA = np.array(ua_SODA).reshape(100,36,24,72,1)[:,:12,:,:,:]
然后利用concat方法拼接这4个维度:
tr_features = np.concatenate([sst_SODA,t300_SODA,va_SODA,ua_SODA],axis=-1)
tr_features.shape
### 训练标签,取后24个
tr_labels = np.array(soda_label).reshape(-1,36)[:,12:]
tr_labels.shape
得到(100, 12, 24, 72, 4)
的数据以及(100, 24)
的标签。按8:2分割训练集与验证集。
下一步构建CNN模型:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 12, 24, 72, 4) 0
_________________________________________________________________
conv3d (Conv3D) (None, 9, 9, 32, 64) 671808
_________________________________________________________________
max_pooling3d (MaxPooling3D) (None, 4, 4, 16, 64) 0
_________________________________________________________________
conv3d_1 (Conv3D) (None, 1, 1, 1, 128) 2097280
_________________________________________________________________
flatten (Flatten) (None, 128) 0
_________________________________________________________________
dense (Dense) (None, 64) 8256
_________________________________________________________________
dropout (Dropout) (None, 64) 0
_________________________________________________________________
dense_1 (Dense) (None, 32) 2080
_________________________________________________________________
dropout_1 (Dropout) (None, 32) 0
_________________________________________________________________
dense_2 (Dense) (None, 24) 792
=================================================================
Total params: 2,780,216
Trainable params: 2,780,216
Non-trainable params: 0
相关训练参数:
history = model_cnn.fit(tr_fea,tr_label,
validation_data=(val_fea, val_label),
batch_size=4096, epochs=2000,
callbacks=[plateau, checkpoint, early_stopping],
verbose=1)
之前是拼接SODA和CMIP数据(2000年),后来拼接SODA和CMIP数据(4368年)发现内存爆了,怎么改都不行。于是尝试多次model.fit方法分别输入SODA CMIP数据,中间用del 和gc.collect
来释放内存。
#增量学习
for round_ in range(4):
step=1000
leng = step*36
inde = list(range(step*round_*36+1))
if(round_==4):
leng = 600*36
gc.collect()
sst_CMIP = pd.read_csv('./ENSO/data/df_sst_CMIP.csv',header=None,skiprows=inde,nrows = leng)
t300_CMIP = pd.read_csv('./ENSO/data/df_t300_CMIP.csv',header=None,skiprows=inde,nrows = leng)
ua_CMIP = pd.read_csv('./ENSO/data/df_ua_CMIP.csv',header=None,skiprows=inde,nrows = leng)
va_CMIP = pd.read_csv('./ENSO/data/df_va_CMIP.csv',header=None,skiprows=inde,nrows = leng)
cmip_label = pd.read_csv('./ENSO/data/df_CMIP_label.csv',header=None,skiprows=inde,nrows = leng)[1]
sst_CMIP.drop(sst_CMIP.columns[0],axis=1,inplace=True)
t300_CMIP.drop(t300_CMIP.columns[0],axis=1,inplace=True)
va_CMIP.drop(va_CMIP.columns[0],axis=1,inplace=True)
ua_CMIP.drop(ua_CMIP.columns[0],axis=1,inplace=True)
sst_CMIP_shaped = np.array(sst_CMIP).reshape(-1,36,24,72,1)[:,:12,:,:,:]
t300_CMIP_shaped = np.array(t300_CMIP).reshape(-1,36,24,72,1)[:,:12,:,:,:]
va_CMIP_shaped = np.array(va_CMIP).reshape(-1,36,24,72,1)[:,:12,:,:,:]
ua_CMIP_shaped = np.array(ua_CMIP).reshape(-1,36,24,72,1)[:,:12,:,:,:]
del va_CMIP
del t300_CMIP
del ua_CMIP
del sst_CMIP
gc.collect()
tr_features2 = np.concatenate([sst_CMIP_shaped,t300_CMIP_shaped,va_CMIP_shaped,ua_CMIP_shaped],axis=-1)
tr_features2[np.isnan(tr_features2)] = np.mean(tr_features2)#对NaN值进行均值填充
del va_CMIP_shaped
del t300_CMIP_shaped
del ua_CMIP_shaped
del sst_CMIP_shaped
gc.collect()
tr_labels2 = np.array(cmip_label).reshape(-1,36)[:,12:]
model_cnn = build_model()
if round_ != 0:
#加载上一轮模型
model_cnn.load_weights('./model/model-%d.h5'%(round_-1))
#存储本轮模型
model_weights = "./model/model-%d.h5"%round_
#保存最佳模型
checkpoint = ModelCheckpoint(model_weights, monitor='val_loss', verbose=0, save_best_only=True, mode='min',
save_weights_only=True)
plateau = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, verbose=1, min_delta=1e-4, mode='min')
early_stopping = EarlyStopping(monitor="val_loss", patience=20)
model_cnn.fit(tr_features2,tr_labels2,
validation_data=(val_f,val_l),
batch_size=32, epochs=20000,
callbacks=[plateau, checkpoint, early_stopping],
verbose=2)
prediction = model_cnn.predict(tr_features)
#from sklearn.metrics import mean_squared_error
sc = score(y_true = tr_labels, y_preds = prediction)
print("%d轮完成,分数%f"%(round_,sc))
#3轮完成,分数-17.867135
总的来说,数据处理目前有三种思路:
- 只拿100年的SODA数据训练
- 拿4645年的CMIP数据训练,SODA数据作为验证集
- CMIP与SODA数据融合训练
模型的话,也可以用RNN来做,只不过提供的数据已经组织好了,用CNN就直接可以输入。于是我就拿了一个玩具CNN来做,可惜对CNN没啥理解,参数都是自己调的,不知道哪些超参最好,希望以后能搞明白。
我始终没搞清楚什么是气象中的模拟数据、真实历史数据,以上三种方法都尝试过,可惜各种奇怪的bug和每天几次的提交机会,让我无法验证🤯,下次比赛一定要在A轮做好系统的总结。
另外,在服务器训练过程中,经常用到的命令:
nvidia-smi
:查看显卡信息os.environ["CUDA_VISIBLE_DEVICES"] = "2"
:设置显卡os.getpid()
查看进程idtop -d 2 -p 44429 -o ruser=WIDE-RUSER-COLUMN
查看某进程free --giga
查看剩余内存echo 1 > /proc/sys/vm/overcommit_memory
设置内存覆盖模式firewall-cmd --zone=public --list-ports
查看端口firewall-cmd --zone=public --add-port=8894/tcp --permanent
开端口firewall-cmd --reload
重启防火墙
-gpasswd -a $USER docker
将当前用户添加到docker组
BUG
各种各样的bug,主要是数据集太大,内存不够的原因
OOM
:减少batchsizeMemoryError: Unable to allocate 8.61 GiB for an array with shape (4645, 36, 24, 72, 4) and data type float64
读文件时加限制,不全部读取:sst_CMIP = pd.read_csv('./ENSO/data/df_sst_CMIP.csv',nrows = leng )
ValueError: Data cardinality is ambiguous: x sizes: 20 y sizes: 500 Please provide data which shares the same first dimension.
检查数据输入格式tensorflow.python.framework.errors_impl.UnknownError: Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above. [[node model/conv3d/Conv3D (defined at train_soda.py:134) ]] [Op:__inference_train_function_902] Function call stack: train_function
之前可以,突然不行,怀疑是内存不够了
提交
第一次用docker,在Windows、linux、macOS上都踩了一遍坑,希望以后会好一点,这里详细记录下如果用docker提交模型。
- 去阿里云官网免费申请镜像容器
2. 设置镜像密码
3. 在自己的环境中,准备好如下文件夹或代码:
model
文件夹存储自己训练完的模型,
result
存储打包提交的结果,
main.py
包含了模型加载、测试数据读取、模型预测、预测结果打包等一系列流程。
run.sh
是一行脚本,比如python main.py
4. docker login
5. docker build -t gogogo:3.4 .
这里gogogo是你的镜像名字,后面跟的是版本号。第一次运行时,会自动加载Dockerfile里面的镜像,从服务器拉取
6. docker images
查看目前的镜像id
7. docker tag [imageID] [remote]
8. docker push
9. 在比赛提交页面,配置路径,提交
10. 查看排名
羞耻排名,只要提交结果,有分数就能进B轮 🎉 🎉 🎉
成绩
A轮成绩:晋级赛A榜:213 /-15.04
B轮成绩:
太羞耻了!!!😓
感悟
我 太 菜 了, 折腾几天,分数越来越多,bug越来越多。但是还是想坚持下去
杀不死你的bug让你更强大🙂