返回列表

1st place solution

605. Enefit - Predict Energy Behavior of Prosumers | predict-energy-behavior-of-prosumers

开始: 2023-11-02 结束: 2024-04-30 新能源发电 数据算法赛
第一名解决方案

作者:hyd

比赛:Predict Energy Behavior of Prosumers

排名:第1名

概述

感谢Enefit和Kaggle举办这次精彩的竞赛。也感谢各位优秀的notebook和讨论,让我学到了很多。我很高兴能够获得第三次单人冠军!😃😀😀

我的最终解决方案由4个XGBoost模型(针对两个目标变量和两种is_consumption)以及2个GRU模型(针对两个目标变量)组成。这些模型共享相同的600个特征

模型名称 验证集 测试集(无在线训练) 测试集(更新一次) 测试集(更新三次)
XGBoost 41.5912(2个目标:42.2531/42.6607) 55.6231 54.1343 54.1213
GRU 43.0815(2个目标:43.7182/45.1687) 56.6651 54.9948 54.6960
XGBoost+GRU 40.6790(2个目标:40.9613/41.5294) 53.6627 52.4813 52.3090

验证策略

我的验证策略非常简单:使用前500天进行训练,剩余天数作为保留验证集,这与排行榜得分有很好的对应关系。我只检查最终的总分,没有打印单个模型的得分。

特征

我认为我的特征与其他顶尖团队相比并无特别之处。我只是把所有表(除gas_df外)与train_df合并,并生成大量滞后特征。由于本比赛内存压力不大,我没有花太多时间削减特征规模。通过精心设计,我认为最终特征数可以减少到100-200个。也许根据不同条件生成不同的特征和目标会取得更好的模型性能,但在阅读其他Kaggler的解决方案后,这对我来说是十分耗时的。

fw_new_feature 来自于 此讨论,帮助提升了约0.1~0.2,感谢 @mohammadpakdaman0

# client_df
client_df = client_df.drop(["date"]).sort(["data_block_id"], descending=False)
df = df.join(client_df.select(["county", "is_business", "product_type", "data_block_id","eic_count","installed_capacity"]), how="left", on=["county", "is_business", "product_type", "data_block_id"])
# electricity
electricity_df = electricity_df.drop(["origin_date"]).rename({"forecast_date":"datetime"}).with_columns([(pl.col("datetime").str.to_datetime()+pl.duration(days=1)).alias("datetime"), (pl.col("euros_per_mwh").abs()+0.1).alias("euros_per_mwh"),])
df = df.join(electricity_df[["data_block_id","datetime","euros_per_mwh"]], how="left", on=["data_block_id","datetime"])

# fw_df 
fw_df = fw_df.with_columns(
                (pl.col("origin_datetime").str.to_datetime()+pl.duration(hours="hours_ahead")).alias("datetime"),
                pl.col("latitude").cast(pl.datatypes.Float32).round(1),
                pl.col("longitude").cast(pl.datatypes.Float32).round(1),
                pl.col("data_block_id").cast(pl.datatypes.Int64),
            ).join(weather_station_to_county_mapping.drop(["county_name"]).with_columns(
                pl.col("latitude").cast(pl.datatypes.Float32).round(1),
                pl.col("longitude").cast(pl.datatypes.Float32).round(1),
            ), how="left",on=["longitude","latitude"]).drop(["longitude","latitude","origin_datetime"])
fw_df = fw_df.group_by(["county","datetime","data_block_id"]).agg([pl.mean(col).alias("fw_{}".format(col)) for col in forecast_weather_cols]).with_columns([pl.col("county").cast(pl.datatypes.Int64),    
                                                                    pl.col("data_block_id").cast(pl.datatypes.Int64),])
df = df.join(fw_df, how="left", on=["county","datetime","data_block_id"]).with_columns([(pl.col("installed_capacity")*pl.col("fw_surface_solar_radiation_downwards") / (pl.col("fw_temperature") + 273.15)).alias("fw_new_feature"),])

# hw_df 
hw_df = hw_df.with_columns(
            (pl.col("datetime").str.to_datetime()+pl.duration(days=1)).alias("datetime"),
                pl.col("latitude").cast(pl.datatypes.Float32).round(1),
                pl.col("longitude").cast(pl.datatypes.Float32).round(1),
                pl.col("data_block_id").cast(pl.datatypes.Int64),
            ).join(weather_station_to_county_mapping.drop(["county_name"]).with_columns(
                pl.col("latitude").cast(pl.datatypes.Float32).round(1),
                pl.col("longitude").cast(pl.datatypes.Float32).round(1),
            ), how="left",on=["longitude","latitude"]).drop(["longitude","latitude"])
hw_df = hw_df.group_by(["county","datetime","data_block_id"]).agg([pl.mean(col).alias("hw_{}".format(col)) for col in historical_weather_cols]).with_columns([                                                            
                                                            pl.when(pl.col("datetime").dt.hour()>10).then(pl.col("datetime")+pl.duration(days=1)).otherwise(pl.col("datetime")).alias("datetime"),
                                                                pl.col("county").cast(pl.datatypes.Int64),    
                                                                pl.col("data_block_id").cast(pl.datatypes.Int64),])
df = df.join(hw_df, how="left", on=["county","datetime","data_block_id"])

目标变量

  1. (target - target_shift2) / installed_capacity
  2. target / installed_capacity
  3. target - target_shift2
  4. 原始 target

我只使用 1 和 2,因为 3 和 4 对我的最终总分没有提升。

模型

我在 XGBoost 模型上没有花太多时间调参,只是简单地训练和预测。而且我惊讶地发现还没有团队使用神经网络模型。

我的 GRU 模型也非常简单。输入张量的形状为 (batch_size, 24 hours, 600 dense_feature_dim + 6*16 cat_feature_dim),随后是两层 GRU,输出张量的形状为 (batch_size, 24 hours)。分类特征包括 ['county', 'product_type', 'hour', 'month', 'weekday', 'day']。神经网络模型为我的最终分数提升了 0.9 分。

在线学习策略

虽然在线学习在公开排行榜上帮助不大,但我认为这在私有排行榜上至关重要,因为我们将有额外的 8 个月数据。因此,我不再追逐更高的公开排行榜分数,而是决定每个月重新训练较少的模型。

哪些方法对我无效

  • 此 notebook 中的太阳能特征
  • 1D CNN 模型和 Transformer 模型
  • 在 GRU 模型中使用多天输入而非单天输入
  • 依据 is_business = 0/1 进一步细分模型

感谢大家!如有需要,我会继续补充。

同比赛其他方案