
项目背景在光伏电站中由于沙尘因素的影响会造成发电量的下降继而导致发电收益的损失。在小型电站中这种由沙尘造成的损失可能微不足道。但对于一些中、大型电站由沙尘造成的收益损失会很明显因此需要对光伏电站进行及时清洗而清洗时间点与增加收益、节省开支息息相关。如果清洗时间点过早清洗费用大于沙尘给电站造成的收益损失会导致不必要的清洗开支如果清洗时间点过晚沙尘给电站造成的收益损失大于清洗费用又会给电站造成不必要的损失。因此选择一个合适的清洗时机尤为重要。“最佳清洗时间点”计算原理最佳清洗时间点的选择就一个原则当沙尘对光伏电站造成的损失大于此光伏电站的清洗费用时就是最佳清洗时间点。虽然原理很简单但是如何计算出“最佳清洗时间点”是一个很困难、严肃的问题。在考虑多项参数、经过各专家的研究后结合监测设备得出了一套较为严谨的计算“最佳清洗时间点”的公式。监测设备及“计算公式”原理监测设备主要由MCU、DTU固件、INA226电流监测芯片、热电偶、两块光伏电池片短路电流范围为0-1000ma组成一片自然积灰跟光伏电站的清洁度保持同步另外一片则由电机驱动的滚刷进行清洗保持清洁。由于光伏电池片的短路电流与太阳辐照成正相关灰尘遮挡会造成光伏电池片所能接收到的辐照度下降。因此自然积灰电池片和清洁电池片之间短路电流的差值就能够表征灰尘量。公式正是利用了这一特性再结合温度、电价、各类修正系数等参数从而能够动态正常积灰情况下累积收益损失增大下雨等自然清洗因素下累积收益损失减小地计算出整个电站的累计收益损失进而与电站清洗费用做对比得出“最佳清洗时间点”。“公式计算法”存在的问题通过公式虽然能够计算出电站的最佳清洗时间点但是这个计算和判断是实时的无法做到预测。例如今天公式判断出当前电站收益损失等于或大于清洗费用那就通知用户进行清洗。但是用户接收到通知后往往没办法立即清洗因为还需要进行协调人员等各项工作。在接到清洗通知与进行实际清洗操作之间往往会有几天的窗口期而在这个窗口期内收益损失会继续累积对于中大型电站而言是一笔不小的损失。因此对于清洗时间点的预测就尤为重要。对于预测选择了LSTM而通过监测设备累积的数据、公式计算得到的数据则成为了训练LSTM的原始数据。数据结构在构建光伏电站灰尘监测LSTM预测模型时由于缺乏大量真实传感器历史数据所以本项目实验中基于NOCT标准温度修正模型和青海地区的气象参数生成了5台设备跨2年、30分钟间隔的约17.5万条时序数据进行LSTM模型验证。相当于后端每30分钟接收、计算、存储一次数据最终一共收集17.5万条数据足够用于LSTM进行训练。模拟场景为0.1954元/kWh上网电价、清洗费用0.0145 元/W/年、电站大小200KW。在LSTM的训练过程中输入特征用到了isc1(清洁电池片短路电流)、isc2(积灰电池片短路电流)、temp电池片温度、p_loss功率损失、hours_since_clean距上次清洗时间小时数、day_of_year该条数据产生于一年当中的第几天。其中isc1、isc2、temp为监测数据p_loss、hours_since_clean、day_of_year为计算所得的数据。训练标签为is_cleaning_day是否清洗0/1。模拟数据的合理性为验证数据的可信性从三个维度交叉检验首先是“物理约束”灰尘监测的核心假设是积灰电池片电流iSc₂不超过清洁参考片iSc₁随机抽取3000个样本点做散点图所有点严格落在 yx 参考线下方100%通过率证明衰减模型正确嵌入了能量守恒和IV特性约束其次是“经济决策逻辑”清洗本质是损失与成本的比对只有当累积发电损失超过单次清洗成本715元基于0.1954元/kWh电价和200kW容量时才触发清洗累积损失曲线呈现经典的单调递增、触发阈值、重置归零锯齿形态61次清洗事件中67%由经济阈值驱动、33%来自降雨和定期维护等混杂因素后者增加了预测难度但也更贴近真实运维场景最后是“季节规律”月度灰尘损失率分布与青海气候较为吻合春季沙尘暴高发期损失率达12-15%为全年峰值夏季受降雨清洗效应影响骤降至2-6%的全年最低区间秋冬温和回升至8-12%。这种显著的季节性波峰波谷为LSTM学习长周期时序依赖提供了充分信号。该数据集的正样本率仅0.46%极度类别不平衡与电站实际清洗频率一致在物理规律、经济机制和统计分布上均比较合理。LSTM的应用及调优选用LSTM进行最佳清洗时间点的预测其核心原因是LSTM具有“记忆性”即它能够将上一个时间步的信息传递到下一个时间步。其次由于LSTM中“记忆单元”、“遗忘门”、“输入门”的存在能够有选择的遗忘和保留信息从而避免早期信息在长序列中被“冲淡”。而且LSTM也能有效缓解梯度爆炸和梯度消失的问题。在本项目中对最佳清洗时间点的预测高度依赖于过去的大量历史信息因此非常适合使用LSTM进行训练。阶段一最开始训练时采用了六维特征isc1、isc2、temp、p_loss、hours_since_clean、day_of_year单点标签is_cleaning_day进行训练。数据预处理规定样本窗口WINDOW_STEPS为96即一个样本包含96个序列偏移量LABEL_OFFSET为24为了做到预测将单点标签“后移”了24个序列即12小时的数据量意味着训练后模型能做到提前十二小时进行预测如输入特征是1月1日下午六点的数据对应的单点标签应该是1月2日早上六点的数据。加载数据后首先对数据进行按电站id、设备id进行排序然后将每组排好序的数据分别按时间进行排序。接着将每组排好序的数据以滑动窗口的形式进行样本切分输入特征中每个样本取96个序列每个样本所对应的特征标签为每个样本中最后一个序列所对应的特征标签之所以每个样本96个序列能对应一个标签就是因为LSTM具备“记忆性”LSTM中每个样本的最后一个时间步都能包含以往时间步的隐藏层信息。然后将切分好的样本和标签分别拼接成array数组。# 单点标签逻辑 def build_sequences(group_df, window, label_offset, features): vals group_df[features].values labels group_df[is_cleaning_day].values n_row len(vals) sequences, seq_labels [], [] for i in range(n_row - window - label_offset 1): seq vals[i: i window] label labels[i window label_offset - 1] sequences.append(seq); seq_labels.append(label) if not sequences: return np.empty((0, window, len(features))), np.empty((0,)) return np.array(sequences), np.array(seq_labels).astype(np.int32) X_list, y_list [], [] for (sid, did), grp in df.groupby([station_id, device_id]): grp grp.sort_values(d_time) X_g, y_g build_sequences(grp, WINDOW_STEPS, LABEL_OFFSET, ALL_FEATURES) if len(X_g) 0: X_list.append(X_g); y_list.append(y_g) X_all np.concatenate(X_list); y_all np.concatenate(y_list)给每个切分好的样本打上时间戳每个样本的时间戳为每个样本当中最后一个序列所对应的时间。最后根据时间进行数据的重排列保证训练集全部早于测试集。# 按时间切分 sample_d_times [] for (sid, did), grp in df.groupby([station_id, device_id]): grp grp.sort_values(d_time) times grp[d_time].values; n len(times) for i in range(n - WINDOW_STEPS - LABEL_OFFSET 1): sample_d_times.append(times[i WINDOW_STEPS - 1]) sample_d_times np.array(sample_d_times) sort_idx np.argsort(sample_d_times) X_all X_all[sort_idx]; y_all y_all[sort_idx]; sample_d_times sample_d_times[sort_idx] split_idx int(len(X_all) * TRAIN_RATIO) X_train, y_train X_all[:split_idx], y_all[:split_idx] X_test, y_test X_all[split_idx:], y_all[split_idx:]构建dataset数据集。在构建dataset数据集时应当注意两个问题一个是数据的归一化问题另一个是数据的过采样问题。由于特征的量纲有很大的差异所以一定得进行归一化防止梯度爆炸。在这个数据集中最大的一个问题就是正样本需要清洗比例过少而负样本无需清洗比例过大那么就会导致模型严重偏向负样本几乎永远预测不需要清洁。所以要对正样本进行过采样让模型看到更多正样本学会区分。对正样本进行过采样的原则是正负比例不超过 1:3最多复制 20 倍。class SequenceDataset(Dataset): def __init__(self, npz_path, scalerNone, fit_scalerFalse, oversampleTrue): data np.load(npz_path) X_raw data[X].astype(np.float32) # (N,96,6) ⬅ 阶段1 y_raw data[y].astype(np.float32) if fit_scaler: self.scaler StandardScaler() N, T, F X_raw.shape self.X torch.tensor(self.scaler.fit_transform(X_raw.reshape(-1, F)).reshape(N, T, F), dtypetorch.float32) elif scaler is not None: self.scaler scaler; N, T, F X_raw.shape self.X torch.tensor(self.scaler.transform(X_raw.reshape(-1, F)).reshape(N, T, F), dtypetorch.float32) else: self.scaler None; self.X torch.tensor(X_raw, dtypetorch.float32) self.y y_raw if oversample: pos_idx np.where(y_raw 1)[0] neg_idx np.where(y_raw 0)[0] target_pos min(len(neg_idx) // 3, len(pos_idx) * 20) repeats max(1, target_pos // len(pos_idx)) oversampled_pos np.repeat(pos_idx, repeats) self.indices np.concatenate([neg_idx, oversampled_pos]) np.random.shuffle(self.indices) else: self.indices np.arange(len(self.y)) def __len__(self): return len(self.indices) def __getitem__(self, idx): real_idx self.indices[idx] return self.X[real_idx], torch.tensor(self.y[real_idx])LSTM训练构建LSTM模型。此模型中隐藏状态维度选取64、LSTM层数选取2、Dropout 比率选取0.3、每个时间步的特征数为6分类头Linear(64→32) → ReLU → Dropout → Linear(32→1)。在构建LSTM模型时依然需要注意上述的正负样本比例极端不平衡的问题。在正负样本极端不平衡的情况下如果不做任何处理模型在初始化时由于偏置为0权重×特征的期望近似为0导致近似有sigmoid(0)0.5即模型一开始时对所有样本都输出50%正类概率。但实际情况却是99.37%的样本都是负类这就会导致模型在前几个epoch浪费算力“纠正”概率值。而最后一层的偏置直接控制最终输出的概率。所以在一开始时就需要手动去设置最后一层的偏置让模型一开始时就预测出接近真实样本比例的值。最后一层偏置的设置原则是log(正样本比例/1-正样本比例)。class DustLSTM(nn.Module): def __init__(self, input_dim6, hidden64, lstm_layers2, dropout0.3): # ⬅ 阶段1: 6 super().__init__() self.lstm nn.LSTM(input_sizeinput_dim, hidden_sizehidden, num_layerslstm_layers, batch_firstTrue, dropoutdropout if lstm_layers 1 else 0, bidirectionalFalse) self.classifier nn.Sequential( nn.Linear(hidden, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, 1)) pos_rate 0.0064; bias_val np.log(pos_rate / (1 - pos_rate)) self.classifier[-1].bias.data.fill_(bias_val) def forward(self, x, phy_idxNone, tmp_idxNone): _, (h, _) self.lstm(x); return self.classifier(h[-1]).squeeze(-1)构建训练函数。在构建训练函数时应当注意两个一问题一是每个batch训练开始之前要清除梯度值因为在Pytorch中梯度值是默认进行累加的二是在每个batch反向传播之后、参数更新之前应当进行梯度裁剪按比例缩小梯度防止梯度爆炸。def train_epoch(model, loader, criterion, optimizer, pbarNone): model.train(); total_loss, correct, total 0.0, 0, 0 for x, y in loader: x, y x.to(DEVICE), y.to(DEVICE); optimizer.zero_grad() logits model(x); loss criterion(logits, y) loss.backward(); torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() with torch.no_grad(): pred (torch.sigmoid(logits) 0.5).float() total_loss loss.item() * len(x); correct (pred y).sum().item(); total len(x) if pbar: pbar.update(len(x)); pbar.set_postfix({loss: f{loss.item():.4f}}) return total_loss / total, correct / total训练结果损失函数采用了FocalLoss损失函数因为FocalLoss是专门为解决类别极端不平衡和难易样本不平衡而设计的一种损失函数。γ选取2.0 的标准值因为γ0时能够把易分负样本的 loss 压到趋近于零避免再出现易分样本还在贡献梯度的情况。调节不同的α值分别将其调为0.99、0.50、0.75发现0.75时效果最佳因为0.99使得正样本权重过大而0.50又使得正样本权重过小0.75恰好能做到权重的平衡。模型FocalLoss α0.75, γ2.0, 12h 窗口 × 7 特征在模拟数据上得到的 AUC为0.9623说明模型对清洗事件的排序判别能力较强。概率分布数据显示负样本中位数 0.003、正样本中位数 0.49集中在 0.42~0.86 区间75% 的负样本输出低于 0.014说明模型对“不需要清洗”的判断非常确定而不能够很好的判断“需要清洗”。混淆矩阵阈值 0.5中真正例 58、假负例 67漏报率 54%假正例 709、真负例 34119Recall 偏低。虽然模型能较好地进行分类但是效果依然并不理想。指标数值AUC0.9623最佳阈值0.35精确率 Precision0.08召回率 Recall0.46F10.13负样本中位数概率0.0033正样本中位数概率0.4905假阳性 FP阈值0.5709假阴性 FN阈值0.567阶段二调优使用 BCEWithLogitsLoss(pos_weight≈217) 在模拟数据上训练取得了 AUC0.9838 的判别效果优于之前的 FocalLoss 的所有 α 配置。概率分布显示负样本中位数 0.0001、正样本中位数 0.9999两个分布极其清晰地分离模型对“不需要清洗”和“需要清洗”的判断都非常确定。仅约 1% 的负样本约 348 条被误推至 0.9999 附近属于真正的难例而非模型失控。混淆矩阵最佳阈值0.85中真正例112、假负例13漏报率10%假正例1064、真负例 33764Recall0.90、Precision0.10。与后续 Focal Loss(α0.75) 相比pos_weight 在数据上实现了更高的 AUC、更干净的分布分离和更优的召回率说明在特征工程充分的条件下BCEWithLogitsLoss pos_weight 本身就是解决极端不平衡分类的有效方案。指标pos_weight217FocalLoss α0.75AUC0.98380.9623负样本中位数0.00010.0033正样本中位数0.99990.4905Recall0.900.46Precision0.100.08FP1064709FN1367基线对比为验证 LSTM 的时序建模是否必要在同一份数据上做了两组基线对比。逻辑回归仅窗口均值特征AUC0.80XGBoost同样均值特征但具备非线性决策能力AUC0.87LSTM96 步时序输入AUC0.98。非线性带来约 7 个点增益时序信息再追加 11 个点。特征工程 非线性树的组合在 0.87 附近封顶剩下 11 个百分点的提升只能来自对 96 步内 iSc2 下降斜率的时序建模而这正是灰尘累积过程的物理本质。模型能力边界AUC逻辑回归线性 无时序0.8032XGBoost非线性 无时序0.8700LSTM非线性 时序0.9838结论LSTM 在模拟数据上取得的 AUC0.9838必须放在数据干净的前提下理解模拟数据由物理模型驱动生成不受传感器漂移、通信丢包、等真实场景噪声的干扰。正因为输入信号的纯净LSTM 才能不受干扰地从 96 步时序中提取出灰尘累积的趋势。这一结果的核心价值不在于数值本身而在于它验证了方法论的有效性 LSTM 结构确实能够在极度类别不平衡的条件下实现可靠的前瞻性预警应用于本项目当中。