ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

多变量LSTM实战:海上风电功率预测的工业数据分析全流程

2026/8/7 11:19:14 拓冰建站 浏览量
多变量LSTM实战:海上风电功率预测的工业数据分析全流程

1. 项目概述:当循环神经网络遇见海上风电

最近在复盘一个挺有意思的工业数据分析项目,核心任务是用Python预测海上风力发电的功率。这活儿听起来像是经典的时间序列预测,但真上手了才发现,海上风电这潭水,可比陆上风电深多了。海上的风,那叫一个“喜怒无常”,风向、风速、湍流强度变化极快,而且风机所处的环境——空气密度、湿度、温度,甚至海浪高度——都在实时影响着那几片大叶片的转动效率。你要是只用历史发电功率这一条数据线去做预测,那结果大概率会飘得连亲妈都不认识。

所以,这个项目的核心挑战,就变成了如何有效地利用多变量信息。我们手头不只有发电功率数据,还有来自SCADA系统(数据采集与监控系统)和气象站的一堆“帮手”:风速、风向、气压、温度、湿度,乃至齿轮箱油温、发电机绕组温度等设备状态数据。这些变量彼此交织,互相影响,共同决定了下一刻的发电量。处理这种复杂的、具有时间依赖性的多变量序列,循环神经网络(RNN),特别是它的改进型LSTM和GRU,就成了不二之选。这个案例,就是一次将多变量LSTM模型应用于海上风电功率预测的完整实战记录,从数据“洗淘”开始,到模型训练、调优,最后解读结果,我会把过程中踩过的坑和总结的技巧都摊开来聊聊。

2. 核心思路与方案选型:为什么是多变量LSTM?

在动手写代码之前,得先把路子想清楚。预测明天甚至未来几小时的发电量,对于电网调度、风机维护、电力交易都至关重要。传统的做法可能是用物理模型(计算流体动力学仿真)或者统计模型(如ARIMA)。但物理模型计算成本太高,难以实时应用;ARIMA这类模型对线性、平稳的数据很拿手,但对风电数据中复杂的非线性和多变量耦合关系,就显得力不从心了。

2.1 为什么选择循环神经网络(RNN/LSTM)?

根本原因在于数据的内在结构:时间依赖性。今天的发电量,和过去几小时的风况、设备状态强相关。RNN家族的模型,其网络结构自带“记忆”功能,能够将历史信息传递下去,用来处理这种序列数据是天作之合。但基础RNN有个著名的“梯度消失/爆炸”问题,记不住太久的过去。所以,我们实际用的是它的两个变种:长短期记忆网络(LSTM)门控循环单元(GRU)。它们通过精巧的“门”结构(输入门、遗忘门、输出门),学会了该记住什么、该忘记什么,从而能够捕捉长距离的时间依赖关系。在这个项目里,我主要用的是LSTM,因为它结构清晰,在复杂序列上的表现通常非常稳定。

2.2 为什么必须是“多变量”输入?

这是本项目区别于简单时间序列预测的关键。试想一下,两台风速相同的风机,发电功率却可能差异很大。为什么?可能一台风机迎风角(风向)更优,另一台则因为气温更高导致空气密度下降,发电效率降低。还可能因为齿轮箱温度偏高,触发了降载保护程序。因此,仅凭风速或功率历史值,信息是严重不足的。

我们将所有相关的时序变量(风速、风向、温度、气压、功率等)同时作为模型的输入特征。模型在每一个时间步,看到的都是一个特征向量,而不是一个标量。这样,LSTM单元在“记忆”历史状态时,记住的是包含多维信息的综合状态,从而能够学习到变量间复杂的动态相互作用。例如,模型可能会学到:“当风速高但湍流强度也大时,虽然瞬时功率可能冲高,但后续波动会加剧,平均功率反而要打折扣”这样的模式。

2.3 方案技术栈选型基于以上思路,我搭建了以下技术方案:

  • 核心框架TensorFlow/Keras。生态成熟,API简洁,对于快速构建和实验LSTM模型非常友好。
  • 数据处理Pandas+NumPy。数据操作的黄金搭档,用于数据加载、清洗、特征工程。
  • 可视化与分析Matplotlib+Seaborn。用于数据探索、结果可视化,分析变量间关系。
  • 评估与调优Scikit-learn。虽然不直接用于深度学习模型,但其提供的train_test_split、数据标准化StandardScaler以及各种评估指标(如mean_absolute_error)不可或缺。

注意:在开始之前,确保你的Python环境(建议3.8以上)已经安装了这些库。使用pip install tensorflow pandas numpy matplotlib seaborn scikit-learn可以一键搞定。如果训练速度慢,可以考虑安装带有GPU支持的TensorFlow版本。

3. 数据准备与特征工程:给模型喂“干净”的粮食

模型的表现,七分靠数据,三分靠调参。海上风电数据来自工业现场,不可避免地存在各种“噪音”和问题。

3.1 数据加载与初窥

数据通常以CSV或数据库形式提供。我们使用Pandas加载它。

import pandas as pd import numpy as np # 假设数据文件为 'offshore_wind_data.csv' df = pd.read_csv('offshore_wind_data.csv', parse_dates=['timestamp'], index_col='timestamp') print(df.head()) print(df.info()) print(df.describe())

关键操作是parse_datesindex_col,将时间戳列转换为DatetimeIndex,这为后续的时间序列重采样、滑动窗口操作奠定了基础。df.info()查看数据类型和缺失值,df.describe()查看数值分布,这是了解数据的第一步。

3.2 数据清洗:处理异常值与缺失值

海上数据常见的“坑”:

  1. 异常值:传感器故障、通信中断可能导致发电功率为0或负值(不合理),或风速超过切出风速后功率却不为0。也可能出现远超物理极限的数值。
    # 示例:基于物理常识的过滤 # 假设风机额定功率为5MW,切出风速为25m/s df = df[(df['power_kw'] >= 0) & (df['power_kw'] <= 5500)] # 留一点余量 df = df[(df['wind_speed_mps'] >= 0) & (df['wind_speed_mps'] <= 30)] # 对于风向(0-360度) df = df[(df['wind_direction_deg'] >= 0) & (df['wind_direction_deg'] < 360)]
  2. 缺失值:时间序列数据要求等间隔。我们需要检查是否存在时间戳缺失,并进行处理。
    # 确保时间索引是连续的、等间隔的(例如10分钟) full_time_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq='10T') df = df.reindex(full_time_range) # 这会为缺失的时间点创建NaN行
    对于产生的NaN,不能简单删除或填0。常用方法有:
    • 前向填充(ffill):用上一个有效值填充。适合变化缓慢的参数,如温度。
    • 线性插值(interpolate):在两点间线性填充。适合连续变化的物理量。
    • 多重插补或模型预测:更复杂但更精确。
    # 对不同列采用不同策略 df['wind_speed_mps'] = df['wind_speed_mps'].interpolate(method='linear') df['air_temperature_c'] = df['air_temperature_c'].ffill() # 对于功率,如果缺失时间短,可以插值;如果缺失时间长,可能意味着停机,需谨慎处理。

    实操心得:处理功率缺失值时,最好结合风机状态信号(如“运行”、“停机”、“故障”)。如果状态是“停机”,那么功率为0是合理的,不应插值。这个信息往往被初学者忽略,导致模型学到错误模式。

3.3 特征工程:从原始数据中提炼“精华”

原始变量可以直接用,但创造一些新特征能极大提升模型性能。

  1. 时间特征:风能和功率具有明显的日周期性和年周期性。
    df['hour_of_day'] = df.index.hour df['month_of_year'] = df.index.month df['day_of_week'] = df.index.dayofweek # 将周期性特征转换为正弦/余弦形式,因为第23小时和第0小时是相邻的,但数值23和0相差很大。 df['hour_sin'] = np.sin(2 * np.pi * df['hour_of_day'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour_of_day'] / 24)
  2. 滞后特征:虽然LSTM能记忆,但显式地提供过去几个时间点的关键变量(如风速、功率)作为特征,有时能降低模型学习难度。
    for lag in [1, 2, 3, 6]: # 滞后1,2,3,6个时间点(假设10分钟间隔,即10分钟,20分钟...前) df[f'wind_speed_lag_{lag}'] = df['wind_speed_mps'].shift(lag) # 注意:创建滞后特征后,数据集开头会产生NaN,需要在后续步骤中丢弃。
  3. 交互特征与统计特征:例如,计算风速的滚动平均值和标准差(反映湍流),或者创建“风能密度”的近似特征(与风速的三次方和空气密度相关)。
    df['wind_speed_rolling_mean_1h'] = df['wind_speed_mps'].rolling(window=6).mean() # 1小时平均 df['wind_speed_rolling_std_1h'] = df['wind_speed_mps'].rolling(window=6).std() # 1小时标准差 # 简单风能密度特征(忽略空气密度变化):风速^3 df['wind_power_cube'] = df['wind_speed_mps'] ** 3

3.4 数据标准化/归一化

LSTM等神经网络对输入数据的尺度非常敏感。风速可能是0-25,温度是-10到30,功率是0-5000,尺度差异巨大,会导致梯度更新不稳定。我们必须将特征缩放到相似的范围内,通常使用标准化(StandardScaler),即减去均值除以标准差,使数据服从均值为0、标准差为1的分布。

from sklearn.preprocessing import StandardScaler # 选择需要标准化的特征列,注意不要标准化目标变量‘power_kw’(我们在后续单独处理) feature_columns = ['wind_speed_mps', 'wind_direction_deg', 'air_temperature_c', 'air_pressure_hpa', 'humidity_percent', 'hour_sin', 'hour_cos', 'wind_speed_rolling_mean_1h', 'wind_power_cube'] # 初始化并拟合scaler scaler = StandardScaler() scaler.fit(df[feature_columns]) # 转换特征数据 scaled_features = scaler.transform(df[feature_columns]) # 将缩放后的特征放回DataFrame(或创建新数组) df_scaled = pd.DataFrame(scaled_features, index=df.index, columns=feature_columns) # 将目标变量和其他未缩放的特征合并回来 df_scaled['power_kw'] = df['power_kw'].values

重要提示:一定要用训练集的均值和标准差来拟合Scaler,然后用这个Scaler去转换验证集和测试集!绝对不能在整个数据集上拟合后再拆分,否则会造成数据泄露,模型评估结果会虚高。我们会在后续的序列创建步骤中处理这个问题。

4. 构建监督学习序列:将时间数据变成LSTM的“食粮”

这是将普通表格数据转化为LSTM可理解格式的关键一步。我们需要创建“样本-目标”对。

4.1 定义时间窗口

假设我们想用过去N个小时的数据(历史窗口),来预测未来M个小时的发电功率(预测窗口)。例如,用过去24小时(144个10分钟点)的数据,预测未来2小时(12个点)的功率。这里,N=144, M=12。

4.2 创建样本(X)和标签(y)

我们需要一个函数,滑动地截取时间序列,生成一个个样本。

def create_sequences(data, feature_cols, target_col, hist_window, pred_window, step=1): """ 为多变量时间序列创建样本和标签。 data: 包含所有特征和目标变量的DataFrame(已按时间排序)。 feature_cols: 用作输入的特征列名列表。 target_col: 用作预测目标的目标列名。 hist_window: 历史窗口长度(时间步数)。 pred_window: 预测窗口长度(时间步数)。 step: 滑动步长(通常为1,使用每个可能的起始点)。 """ X, y = [], [] data_array = data[feature_cols].values target_array = data[target_col].values total_length = len(data) for i in range(0, total_length - hist_window - pred_window + 1, step): # 截取历史窗口内的所有特征作为输入X X_end = i + hist_window y_start = X_end y_end = y_start + pred_window X.append(data_array[i:X_end]) # 形状: (hist_window, num_features) y.append(target_array[y_start:y_end]) # 形状: (pred_window,) return np.array(X), np.array(y) # 使用示例 hist_window = 144 # 过去24小时 (24*6) pred_window = 12 # 未来2小时 (2*6) feature_columns = ['wind_speed_mps', 'wind_direction_deg', ...] # 你的特征列 target_column = 'power_kw' X, y = create_sequences(df_scaled, feature_columns, target_column, hist_window, pred_window) print(f"样本数: {X.shape[0]}, 输入X形状: {X.shape}, 输出y形状: {y.shape}") # 输出类似: 样本数: 8000, 输入X形状: (8000, 144, 10), 输出y形状: (8000, 12)

现在,X是一个三维数组,形状为(样本数, 历史时间步长, 特征数),这正是LSTM所期望的输入形状。y是一个二维数组,形状为(样本数, 预测时间步长)

4.3 数据集划分

时间序列数据不能随机打乱划分!必须按时间顺序划分,以模拟真实的预测场景。

# 假设数据按时间排序 train_ratio, val_ratio = 0.7, 0.15 # 训练集70%,验证集15%,测试集15% train_split = int(len(X) * train_ratio) val_split = int(len(X) * (train_ratio + val_ratio)) X_train, y_train = X[:train_split], y[:train_split] X_val, y_val = X[train_split:val_split], y[train_split:val_split] X_test, y_test = X[val_split:], y[val_split:] print(f"训练集: {X_train.shape}, 验证集: {X_val.shape}, 测试集: {X_test.shape}")

5. 构建与训练多变量LSTM模型

数据准备好了,终于可以搭建模型了。

5.1 模型架构设计

我们将构建一个序列到序列(Seq2Seq)的模型。输入是(batch_size, hist_window, num_features),输出是(batch_size, pred_window)

from tensorflow import keras from tensorflow.keras import layers def build_lstm_model(input_shape, pred_window): """ 构建多变量LSTM预测模型。 input_shape: (hist_window, num_features) pred_window: 要预测的未来时间步数 """ model = keras.Sequential([ # 第一层LSTM,返回整个序列的输出(return_sequences=True),以便堆叠 layers.LSTM(units=128, return_sequences=True, input_shape=input_shape, dropout=0.2, recurrent_dropout=0.2), # 添加Dropout防止过拟合 # 第二层LSTM,可以只返回最后一个时间步的输出 layers.LSTM(units=64, return_sequences=False, dropout=0.2), # 全连接层,将LSTM输出映射到预测窗口的维度 layers.Dense(units=32, activation='relu'), layers.Dropout(0.1), # 输出层:线性激活,输出pred_window个值 layers.Dense(units=pred_window) ]) model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001), loss='mse', # 回归任务常用均方误差 metrics=['mae']) # 同时监控平均绝对误差,更直观 return model # 获取输入形状 num_features = X_train.shape[2] model = build_lstm_model((hist_window, num_features), pred_window) model.summary() # 打印模型结构,查看参数量

5.2 模型训练与回调函数

训练深度学习模型需要耐心和技巧。我们使用验证集来监控模型是否过拟合,并采用回调函数来优化训练过程。

# 定义回调函数 callbacks = [ # EarlyStopping: 当验证集损失不再下降时,提前停止训练,防止过拟合 keras.callbacks.EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True), # ReduceLROnPlateau: 当验证损失停滞时,降低学习率,有助于精细调优 keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-6), # ModelCheckpoint: 保存验证集上表现最好的模型 keras.callbacks.ModelCheckpoint('best_lstm_wind_model.h5', monitor='val_loss', save_best_only=True) ] # 开始训练 history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, # 设置一个较大的epoch,靠EarlyStopping来停止 batch_size=32, # 根据你的GPU内存调整,常用32, 64, 128 callbacks=callbacks, verbose=1 )

实操心得patience(耐心)参数设置很重要。设太小,模型可能还没充分学习就停止了;设太大,会浪费计算资源。对于风电数据,20-30是一个不错的起点。batch_size会影响训练速度和梯度稳定性,内存够大就用大一点的,如64或128,训练更快;内存小或数据噪声大,可以用小一点的,如32,梯度估计更准。

5.3 训练过程可视化

训练结束后,绘制损失曲线是分析模型学习状态的必要步骤。

import matplotlib.pyplot as plt def plot_training_history(history): fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) # 绘制损失 ax1.plot(history.history['loss'], label='训练损失') ax1.plot(history.history['val_loss'], label='验证损失') ax1.set_xlabel('Epoch') ax1.set_ylabel('Loss (MSE)') ax1.set_title('训练与验证损失') ax1.legend() ax1.grid(True) # 绘制MAE ax2.plot(history.history['mae'], label='训练MAE') ax2.plot(history.history['val_mae'], label='验证MAE') ax2.set_xlabel('Epoch') ax2.set_ylabel('MAE') ax2.set_title('训练与验证平均绝对误差') ax2.legend() ax2.grid(True) plt.tight_layout() plt.show() plot_training_history(history)

理想的曲线是训练和验证损失都稳步下降,并最终趋于平稳,且两者之间差距不大。如果验证损失很早就开始上升而训练损失持续下降,那就是典型的过拟合了。

6. 模型评估、预测与结果分析

模型训练好了,是骡子是马,得拉出来在从未见过的测试集上遛遛。

6.1 在测试集上评估

# 加载保存的最佳模型(如果使用了ModelCheckpoint) # best_model = keras.models.load_model('best_lstm_wind_model.h5') # 或者直接使用训练好的model(如果restore_best_weights=True) test_loss, test_mae = model.evaluate(X_test, y_test, verbose=0) print(f"测试集 MSE: {test_loss:.4f}") print(f"测试集 MAE: {test_mae:.4f} kW")

MAE(平均绝对误差)是一个很直观的指标,它告诉你平均而言,模型的预测值与真实值相差多少千瓦。例如,MAE=150 kW,对于一台5MW的风机,误差率约为3%,这是一个可以接受的工业水平。

6.2 进行预测并可视化

我们随机选取测试集中的几个样本,将模型的预测结果与真实值进行对比。

# 随机选择几个测试样本进行可视化 import random sample_indices = random.sample(range(len(X_test)), 4) # 选4个样本 plt.figure(figsize=(15, 10)) for i, idx in enumerate(sample_indices): plt.subplot(2, 2, i+1) # 获取样本和预测 X_sample = X_test[idx:idx+1] # 保持三维结构 y_true = y_test[idx] y_pred = model.predict(X_sample, verbose=0)[0] # 得到预测的12个点 # 创建时间轴 (未来2小时,共12个点) future_steps = np.arange(pred_window) plt.plot(future_steps, y_true, 'b-o', label='真实功率', linewidth=2) plt.plot(future_steps, y_pred, 'r--s', label='预测功率', linewidth=2) plt.fill_between(future_steps, y_true, y_pred, alpha=0.2, color='gray') plt.xlabel('未来时间步 (每步10分钟)') plt.ylabel('功率 (kW)') plt.title(f'测试样本 {idx} 预测对比') plt.legend() plt.grid(True, linestyle='--', alpha=0.7) plt.tight_layout() plt.show()

6.3 误差分布与残差分析

只看几个样本不够,我们需要从统计上理解模型的误差特性。

# 对整个测试集进行预测 y_test_pred = model.predict(X_test, verbose=0) # 计算每个预测时间点的平均绝对误差 mae_per_step = np.mean(np.abs(y_test - y_test_pred), axis=0) plt.figure(figsize=(10, 5)) plt.bar(range(pred_window), mae_per_step) plt.xlabel('预测时间步 (未来)') plt.ylabel('平均绝对误差 (kW)') plt.title('不同预测步长的误差分布') plt.grid(True, axis='y', linestyle='--', alpha=0.7) plt.show()

通常,预测误差会随着预测步长(预测更远的未来)而增大。这个图能清晰地告诉我们,模型的预测能力在时间上的衰减情况。

# 残差分析:预测误差 vs 真实值 residuals = y_test - y_test_pred # 我们取第一个预测时间步的残差来分析 residuals_step0 = residuals[:, 0] plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.scatter(y_test[:, 0], residuals_step0, alpha=0.5) plt.axhline(y=0, color='r', linestyle='--') plt.xlabel('真实功率 (kW)') plt.ylabel('预测残差 (kW)') plt.title('残差 vs 真实值') plt.grid(True) plt.subplot(1, 2, 2) plt.hist(residuals_step0, bins=50, edgecolor='black') plt.xlabel('预测残差 (kW)') plt.ylabel('频次') plt.title('残差分布直方图') plt.grid(True, axis='y') plt.tight_layout() plt.show()

理想的残差图应该是围绕0水平线随机分布,没有明显的模式(如漏斗形、曲线形),且直方图近似正态分布。如果出现模式,说明模型有系统性偏差,可能漏掉了某个重要特征或关系。

7. 常见问题、调优策略与避坑指南

在实际操作中,你几乎一定会遇到下面这些问题。这里是我总结的一些排查思路和调优技巧。

7.1 模型表现不佳(误差大)

  • 问题现象:训练集和验证集误差都很大,或者收敛很慢。
  • 排查与解决
    1. 数据质量再检查:回去仔细检查数据清洗和特征工程步骤。异常值真的处理干净了吗?缺失值填充方法合理吗?特别是功率为0的点,是正常停机还是数据错误?这是最常见的原因。
    2. 特征是否足够/相关:你提供的特征真的能解释功率变化吗?尝试增加更多可能相关的特征,如更精细的滚动统计特征(不同时间尺度的均值、方差)、风机状态编码(运行、待机、故障的one-hot编码)、相邻风机的数据(空间相关性)等。
    3. 模型容量不足:尝试增加LSTM层的单元数(如从64增加到128、256),或者堆叠更多的LSTM层(例如3层)。更大的模型能学习更复杂的模式。
    4. 学习率调整:初始学习率(0.001)可能不合适。尝试使用LearningRateScheduler回调或手动尝试0.0005, 0.0001。
    5. 窗口长度调整hist_window(历史窗口)太短,模型看不到足够的历史信息;太长,会引入噪声并增加计算负担。尝试24小时(144)、48小时(288)、72小时(432)等不同长度。

7.2 模型过拟合

  • 问题现象:训练损失持续下降,但验证损失在某个点后开始上升,两者差距越来越大。
  • 排查与解决
    1. 增强正则化:这是首要手段。增加LSTM层和全连接层的Dropout率(如从0.2增加到0.3、0.4)。也可以尝试在LSTM层添加recurrent_dropout
    2. 简化模型:如果模型容量过大(层数多、单元数多),而数据量相对较少,就容易过拟合。尝试减少一层LSTM,或减少单元数。
    3. 数据增强:对于时间序列,可以尝试轻微的时间扭曲、添加高斯噪声等方法来人工扩充训练数据,但需谨慎,要保证物理合理性。
    4. 早停(EarlyStopping):确保你正确使用了EarlyStopping回调,并设置了合理的patience

7.3 模型欠拟合

  • 问题现象:训练损失和验证损失都很高,且下降得很慢或很早就停滞了。
  • 排查与解决
    1. 增加模型复杂度:与过拟合相反,增加LSTM层数或单元数。
    2. 减少正则化:降低Dropout率,甚至暂时移除Dropout层看看效果。
    3. 更复杂的架构:尝试使用双向LSTM(Bidirectional LSTM),它同时从过去和“未来”(在序列内)学习上下文,有时能提升性能。或者尝试注意力机制(Attention),让模型学会关注历史序列中更重要的部分。
    4. 特征工程升级:可能当前特征无法有效表征问题。深入研究领域知识,构造更有物理意义或统计意义的特征。

7.4 训练不稳定或非常慢

  • 问题现象:损失曲线震荡剧烈,或者每个epoch耗时很长。
  • 排查与解决
    1. 检查数据标准化:确保所有连续数值特征都经过了标准化。这是稳定训练的基础。
    2. 调整批次大小(Batch Size):增大batch_size(如从32到64、128)可以使梯度估计更稳定,减少震荡,同时可能利用GPU并行加速。但太大可能会降低模型泛化能力。
    3. 梯度裁剪(Gradient Clipping):在model.compile的优化器中设置clipnormclipvalue,防止梯度爆炸。
      optimizer = keras.optimizers.Adam(learning_rate=0.001, clipnorm=1.0)
    4. 使用GPU:如果可用,确保TensorFlow能检测并使用GPU。这能极大提升训练速度。

7.5 预测结果存在系统性偏差

  • 问题现象:残差图显示,在功率高或低时,预测值系统性偏低或偏高。
  • 排查与解决
    1. 目标变量变换:如果功率值范围很大(0-5000),且分布严重偏斜,可以尝试对目标变量y进行变换,例如取对数log(1+y),训练后再变换回来。这有时能改善模型对高值区域的拟合。
    2. 分区间建模:针对风机不同的工作区间(如低风速区、额定功率区、切出区)分别建立模型,因为其功率曲线特性不同。
    3. 检查特征与目标的非线性关系:尝试在输入特征中加入多项式特征(如风速的平方、立方),或者使用更复杂的网络结构(如在每个LSTM层后添加更密集的全连接层)来捕捉非线性。

这个项目从数据到模型,每一步都充满了选择和权衡。没有一劳永逸的“银弹”参数,最好的模型永远是针对你的特定数据集、经过反复迭代和调试得到的。多画图(数据分布图、损失曲线、预测对比图、残差图),多分析,理解模型在“想”什么、错在哪里,是提升模型效果的不二法门。海上风电预测是一个典型的工业AI应用场景,将领域知识(流体力学、风机工程)与数据驱动方法(深度学习)结合,才能做出既靠谱又实用的预测系统。