之前的内容中我们对时间序列做了详细的EDA,探索了赛题电力数据背后的模式,并给出了一系列的上分点(节假日、外部数据等)
在本文中,我们将探讨电力现货市场价格预测的前沿方法,并补充相关的知识,帮助大家更好地找到自己迭代上分的方向。
本文探讨了三个主要的上分方向:
- 时间序列挖掘
- ABM报价策略优化
- 强化学习
时间序列挖掘
在之前的内容中,我们对时间序列做了详细的EDA,这里将提供一个简单的时间序列模型作为参考,并提供详细的流程解释。
安装依赖
!pip install numpy pandas scikit-learn matplotlib seaborn lightgbm
导入相关包
import numpy as np
import pandas as pd
import seaborn as sns
import os
from tqdm import tqdm
import matplotlib.pylab as plt
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')
plt.style.use('ggplot')
plt.rcParams['font.sans-serif'] = ["WenQuanYi Micro Hei",'SimHei']
plt.rcParams['axes.unicode_minus'] = False
base_path=Path("data")
train_length=55392
读取数据
electricity_price=pd.read_csv(base_path/"electricity_price_parsed.csv",parse_dates=["timestamp"],index_col=0)
electricity_price.columns=["demand","price"]
sample_submit=pd.read_csv(base_path/"sample_submit.csv")
train_data=electricity_price.copy(deep=True)
electricity_price.head()
特征工程
特征工程是数据预处理过程的一部分,涉及从原始数据中提取和创建新特征,以提高机器学习模型的性能。它包括清理数据、处理缺失值、转换变量类型、标准化或归一化数据,以及创建新的衍生特征。特征工程的目标是通过选择和构建合适的特征,使模型能够更好地理解数据和预测目标变量。
详细的时间序列特征工程构造方法可以参考Datawhale电力预测夏令营的内容。
# 将电价数据的时间索引信息添加到训练数据中
train_data["hour"] = electricity_price.index.hour
train_data["day"] = electricity_price.index.day
train_data["month"] = electricity_price.index.month
train_data["year"] = electricity_price.index.year
train_data["weekday"] = electricity_price.index.weekday
train_data["is_windy_season"] = electricity_price.index.month.isin([1, 2, 3, 4, 5, 9, 10, 11, 12])
train_data["is_valley"] = electricity_price.index.hour.isin([10, 11, 12, 13, 14, 15])
train_data["quarter"] = electricity_price.index.quarter
# 对时间特征进行独热编码(One-Hot Encoding),删除第一列以避免多重共线性
train_data = pd.get_dummies(data=train_data, columns=["hour", "day", "month", "year", "weekday"], drop_first=True)
节假日特征
基于之前的EDA,我们发现节假日中电力价格更容易为负值,因此我们构造典型的春节和劳动节的节假日特征。当然你也可以尝试构造其他的节假日特征(端午节、清明节、国庆节等)。
def generate_holiday_dates(start_dates, duration):
holidays = []
for start_date in start_dates:
holidays.extend(pd.date_range(start=start_date, periods=duration).tolist())
return holidays
spring_festival_start_dates = ["2022-01-31", "2023-01-21", "2024-02-10"]
labor_start_dates = ["2022-04-30", "2023-04-29"]
spring_festivals = generate_holiday_dates(spring_festival_start_dates, 7)
labor = generate_holiday_dates(labor_start_dates, 5)
train_data["is_spring_festival"] = train_data.index.isin(spring_festivals)
train_data["is_labor"] = train_data.index.isin(labor)
构造基于demand的窗口特征
可以发现一段时间内,总负荷如果有下降趋势,则下一个出清价格也可能下降。因此我们联想到构造基于总需求的窗口特征。
def cal_range(x):
return x.max() - x.min()
def increase_num(x):
return (x.diff() > 0).sum()
def decrease_num(x):
return (x.diff() < 0).sum()
def increase_mean(x):
diff = x.diff()
return diff[diff > 0].mean()
def decrease_mean(x):
diff = x.diff()
return diff[diff < 0].abs().mean()
def increase_std(x):
diff = x.diff()
return diff[diff > 0].std()
def decrease_std(x):
diff = x.diff()
return diff[diff < 0].std()
from tqdm import tqdm
window_sizes = [4, 12, 24]
with tqdm(window_sizes) as pbar:
for window_size in pbar:
functions = ["mean", "std", "min", "max", cal_range, increase_num, decrease_num, increase_mean, decrease_mean, increase_std, decrease_std]
for func in functions:
func_name = func if type(func) == str else func.__name__
column_name = f"demand_rolling_{window_size}_{func_name}"
train_data[column_name] = train_data["demand"].rolling(window=window_size, min_periods=window_size//2, closed="left").agg(func)
pbar.set_postfix({"window_size": window_size, "func": func_name})
其他时序特征
我们还可以加入一些其他的时序特征,例如demand的滞后特征,差分特征,百分比特征等。
train_data["demand_shift_1"] = train_data["demand"].shift(1)
train_data["demand_diff_1"] = train_data["demand"].diff(1)
train_data["demand_pct_1"] = train_data["demand"].pct_change(1)
模型训练
一般数据科学比赛中,线性模型和树模型是比较常用的两种回归模型,在这里我们将线性回归和LightGBM作为参考模型,并不进行特别的超参数调优。
from sklearn.linear_model import LinearRegression
from lightgbm import LGBMRegressor
X_train = train_data.iloc[:train_length].drop(columns=["price"]).bfill().ffill()
X_test = train_data.iloc[train_length:].drop(columns=["price"]).bfill().ffill()
y_train = train_data.iloc[:train_length][["price"]]
lgb_model = LGBMRegressor(num_leaves=2**5-1, n_estimators=300, verbose=-1)
linear_model = LinearRegression()
lgb_model.fit(X_train, y_train)
linear_model.fit(X_train[["demand"]], y_train)
lgb_pred = lgb_model.predict(X_test)
linear_pred = linear_model.predict(X_test[["demand"]]).flatten()
模型融合
考虑到demand对价格的线性程度高,但直接线性回归无法捕捉到时序中的一些非线性特征,因此我们融合树模型和线性模型的结果。
y_pred = (lgb_pred + linear_pred) / 2
y_pred *= 0.95 # 进行少量修正
提交结果
sample_submit["clearing price (CNY/MWh)"] = y_pred
sample_submit.to_csv("submit.csv", index=False)
ABM报价策略优化
在之前的内容中,我们提到了智能体可以采用“边际成本”来作为自己的报价,但在现实中,火电机组会有更加复杂的报价策略。在这里,我们详细讨论如何优化智能体的报价策略。
报价机制
Baseline中我们假定了智能体的报价只有一个值,即其最大出力(容量),但现实中机组会进行分段报价。思考这样一个场景,假如某机组最大出力是600MW,都报350元,而市场以345元出清了,机组因为5元之差没能中标,损失了全部电力。
现在这个机组学聪明了,为了降低风险,他选择前300MW出力报300元,后300W出力报400元,这样即使最终价格在300-400之间,他保底能卖出300MW的出力。这就是现实中的机组报价方式:分段报价。
报价策略
在Baseline中,机组总是以边际成本(即边际煤耗)报价,即根据单位标煤价和边际煤耗量计算出来的价格。然而,实际中机组报价会随负荷变动。
例如:
- 当负荷较低(对应数据的10-15时的低谷区)和光伏发电占优的时候,机组会降低报价,避免电力无法中标。
- 当负荷较高(对应数据的早晚高峰)和光伏发电较低(阴雨天)时,机组会提高报价,争取获得更多的利益。
因此,最终优化后的报价策略应考虑机组在不同时间的策略报价系数,这个系数受自身参数、市场供需、其他机组策略、总负荷等因素的影响。
其他策略优化点:
- 预测竞争机组的策略,决定自身的报价方式:例如根据其他机组的成本,得到整体的报价曲线,并以此决定自己的报价。但在实际中,机组无法获知全部的其他机组信息。
- 基于博弈论模型,例如古诺模型(Cournot),寡头市场中的斯塔克伯格模型(Stackelberg)。
代码示例
import pandas as pd
import numpy as np
# 假设我们有机组的边际成本和总需求数据
unit = pd.read_csv("unit.csv")
electricity_price = pd.read_csv("electricity_price.csv")
# 计算报价策略
def calculate_price(demand, unit, strategy_factor):
# 按照边际成本排序
unit = unit.sort_values(by="coal consumption (g coal/KWh)")
unit['cumulative_capacity'] = unit['Capacity(MW)'].cumsum()
prices = []
for d in demand:
# 找到能够满足需求的机组,并应用策略因子调整报价
price = unit[unit['cumulative_capacity'] >= d]["coal consumption (g coal/KWh)"].iloc[0]
adjusted_price = price * strategy_factor
prices.append(adjusted_price)
return prices
# 示例:假设策略因子为1.1(表示在边际成本基础上提高10%)
strategy_factor = 1.1
demand = electricity_price["demand"].values
adjusted_prices = calculate_price(demand, unit, strategy_factor)
# 将调整后的价格添加到电力市场数据中
electricity_price["adjusted_price"] = adjusted_prices
print(electricity_price.head())
强化学习
多机组报价可以被建模为一个多智能体强化学习模型,考虑到本赛题中给定的机组参数较少,强化学习未必能稳定收敛,因此可以作为尝试的方向之一。
基本概念
强化学习的几个要素包括状态、动作、奖励和策略。对于这个题目而言,状态空间连续,动作主要是出价的方式。这里关键在于设计好的奖励函数和策略,使得每一次训练的出价策略能够有恰到好处的收益。
构建强化学习模型
我们可以使用OpenAI的gym库和PyTorch来构建一个简单的强化学习模型。
代码示例
import gym
import torch
import torch.nn as nn
import torch.optim as optim
# 定义强化学习环境
class ElectricityMarketEnv(gym.Env):
def __init__(self, unit_data, price_data):
super(ElectricityMarketEnv, self).__init__()
self.unit_data = unit_data
self.price_data = price_data
self.current_step = 0
self.action_space = gym.spaces.Discrete(10) # 简化的动作空间
self.observation_space = gym.spaces.Box(low=0, high=1, shape=(len(unit_data.columns),), dtype=np.float32)
def reset(self):
self.current_step = 0
return self.unit_data.iloc[self.current_step].values
def step(self, action):
self.current_step += 1
done = self.current_step >= len(self.unit_data)
reward = -abs(self.price_data[self.current_step] - action * 10) # 简化的奖励函数
obs = self.unit_data.iloc[self.current_step].values if not done else np.zeros(self.unit_data.shape[1])
return obs, reward, done, {}
# 定义策略网络
class PolicyNetwork(nn.Module):
def __init__(self, input_dim, output_dim):
super(PolicyNetwork, self).__init__()
self.fc1 = nn.Linear(input_dim, 32)
self.fc2 = nn.Linear(32, output_dim)
def forward(self, state):
x = torch.relu(self.fc1(state))
action_probs = torch.softmax(self.fc2(x), dim=-1)
return action_probs
# 训练强化学习模型
def train(env, model, optimizer, episodes=1000):
for episode in range(episodes):
state = env.reset()
done = False
while not done:
state_tensor = torch.FloatTensor(state).unsqueeze(0)
action_probs = model(state_tensor)
action = torch.argmax(action_probs).item()
next_state, reward, done, _ = env.step(action)
loss = -torch.log(action_probs[0, action]) * reward
optimizer.zero_grad()
loss.backward()
optimizer.step()
state = next_state
# 初始化环境和模型
unit_data = pd.read_csv("unit.csv")
price_data = pd.read_csv("electricity_price.csv")["price"].values
env = ElectricityMarketEnv(unit_data, price_data)
model = PolicyNetwork(input_dim=env.observation_space.shape[0], output_dim=env.action_space.n)
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 训练模型
train(env, model, optimizer)
# 评估策略
def evaluate_policy(model, env, episodes=15):
total_rewards = 0
for _ in range(episodes):
state = env.reset()
done = False
episode_reward = 0
while not done:
state_tensor = torch.FloatTensor(state).unsqueeze(0)
with torch.no_grad():
action_probs = model(state_tensor)
action = torch.argmax(action_probs).item()
next_state, reward, done, _ = env.step(action)
episode_reward += reward
state = next_state
total_rewards += episode_reward
return total_rewards / episodes
# 评估训练后的策略
average_reward = evaluate_policy(model, env)
print(f"Average reward: {average_reward}")
在这个示例中,我们定义了一个简化的电力市场环境,并使用策略网络进行训练和评估。通过这种方式,我们可以模拟多智能体的市场行为,并优化机组的报价策略。
结语
本文探讨了在电力现货市场价格预测中的几个主要上分方向,包括时间序列挖掘、ABM报价策略优化和强化学习。通过时间序列分析,我们能够构建出较为准确的预测模型,结合节假日特征和窗口特征等方法进一步提升模型性能。在ABM报价策略优化中,我们通过分段报价和考虑市场负荷变化等策略,使得机组报价更贴近现实市场行为。强化学习部分,我们通过构建多智能体强化学习模型,模拟多机组报价的动态博弈过程,进一步优化了报价策略。希望通过这些方法的介绍和示例代码,能为大家在电力市场价格预测中提供一些新的思路和方法。无论是在构建特征工程、优化报价策略还是尝试新的算法,每一步都是提升模型性能的重要环节。期待大家能从本文中获得启发,在比赛中取得优异成绩。
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!