项目地址:https://github.com/zhang3399/Single-phase_load_recognition

# 项目简介

在一个单相电力线上,对电压电流数据进行录波,从波形数据上提取有效信息,通过建模 - 训练的方式,识别出电力线上正在使用的电器有哪些。

  • utils/datasets.py : 将数据集处理为输入特征和学习标签
  • utils/models.py : 定义 LSTM 模型
  • config/params.py : 配置模型参数,存储路径,识别目标等
  • results/train.py : 训练模型,验证集识别效果
  • predict.py : 使用训练好的模型进行预测

# 数据集说明

数据集包含了以下核心结构:

  1. 设备信息:
设备 ID 设备类型 工作参数
YD1 奥克斯风扇 220V, 60W
YD2 美的微波炉 220V, 输入 1150W, 输出 700W
YD11 创维电视机 220V, 50Hz, 150W
  1. ​​设备监测数据:
字段 说明 单位
time
Ic 电流值 0.001 A
Uc 电压值 0.1 V
PC 有功功率 0.0001kW
  1. 周波数据:
字段 说明
IC001 电流周波第 1 采样点
UC128 电压周波第 128 采样点
  1. ​谐波数据​​
字段 说明 单位
time
IC02 2 次电流谐波含有率 %
UC51 2 次电压谐波含有率 %
  1. 用电设备操作记录
序号 时间 设备 工作状态 操作
1 2019-01-01 00:00:00 YD1 开启 启动
2 2019-01-01 00:00:30 YD1 关闭 关闭
3 2019-01-01 00:00:00 YD1 1 档 启动
4 2019-01-01 00:00:30 YD1 2 档 关闭

# 数据预处理

处理流程

graph TB
    A[原始Excel] --> B[时间对齐]
    B --> C[状态标签编码]
    C --> D[特征工程]
    D --> E[训练/测试集分割]

关键处理步骤:

  1. 时间对齐​​:
start_times = [
    df_device['time'].min(),
    df_wave['time'].min(),
]
end_times = [
    df_device['time'].max(),
    df_wave['time'].max(),
]
# 最晚开始时间和最早结束时间
common_start = max(start_times)
common_end = min(end_times)
# 裁剪设备、周波、谐波表到共同时间范围
df_device = df_device[(df_device['time'] >= common_start) & (df_device['time'] <= common_end)]
df_wave = df_wave[(df_wave['time'] >= common_start) & (df_wave['time'] <= common_end)]
  1. 状态标签编码:(二分类与多分类)
def createLabel(df, output_path='device_encoders.json', multi_class=True):
    """
    创建设备状态标签并保存映射关系
    参数:
        df: 包含设备状态的DataFrame
        output_path: 映射关系保存路径
        multi_class: 是否使用多分类(默认True)
    """
    device_cols = [col for col in df.columns if col.startswith('YD')]
    device_encoders = {}
    for device in device_cols:
        # 获取设备的所有状态
        states = sorted(df[device].dropna().unique())
        state_count = len(states)
        if multi_class and state_count > 2:
            # 多分类映射:为每个状态分配唯一整数
            mapping = {state: i for i, state in enumerate(states)}
            print(f"{device} 状态({state_count}种): {states} → 编码: {mapping}")
            # 创建多分类标签列
            df[f'is_{device}'] = df[device].map(mapping).fillna(-1).astype(int)
        else:
            # 二分类映射:关闭→0,其他→1
            mapping = {state: (0 if str(state).strip().lower() == "关闭" else 1) for state in states}
            print(f"{device} 状态({state_count}种): {states} → 二值编码: {mapping}")
            # 创建二分类标签列
            df[f'is_{device}'] = df[device].map(mapping).fillna(-1).astype(int)
        device_encoders[device] = mapping
    # 保存映射关系为 JSON 格式
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(device_encoders, f, ensure_ascii=False, indent=4)
    print(f"映射关系已保存到 {output_path}")
    return df

二分类映射关系:

{
    "YD10": {"1档冷风": 1,"1档热风": 1,"2档冷风": 1,
        "2档热风": 1,"关闭": 0
    },
    "YD11": {"关闭": 0,"打开": 1}
    ...
}

多分类映射关系:

{
    "YD10": {"1档冷风": 0,"1档热风": 1,"2档冷风": 2,
        "2档热风": 3,"关闭": 4
    },
    "YD11": {"关闭": 0,"打开": 1}
    ....
}
  1. 特征工程:
    对电压、电流、周波和谐波数据与设备状态之间的相关性分析:
    分析结果保存在根目录下,考虑问题场景是基于电压电流,识别运行设备,只使用开源数据集的电压电流特征数据作为特征序列。

# 模型搭建

考虑电压电流数据呈时间连续,设备启停在过往与当前时刻的电压电流具有相应的特征变化,因此采用时序模型。

核心

  • LSTM :长短期记忆网络,擅长处理时间序列数据,能够捕捉时间序列中的长期依赖关系。

  • 识别方式 :采用 i~j 组时间序列电流电压数据,识别第 j 组时间序列下的电器状态, i~jparams.py 参数 WINDOW_SIZE 值控制,即 滑动窗口 ,LSTM 模型学习步长。

# 模型训练

训练说明:

  • 二分类
    • datahandl.py 处理:将 df = createLabel(df, multi_class=False) 改为 multi_class=False ,将状态标签编码为二分类标签。
    • 执行 train.py :训练完成后,模型保存在 results/train 文件中,可执行 predict.py 进行推理。
  • 多分类
    • datahandl.py 处理:将 df = createLabel(df, multi_class=True) 改为 multi_class=True ,将状态标签编码为多分类标签。
    • 执行 train_plus.py :可以训练,但是目前由于数据量不足,泛化能力较差,推理模块 predict_plus.py 暂时未实现。

方案一: (二分类模型如下)

模型为每个设备配置一个输出头,预测该设备是否在运行。

class ApplianceLSTM(nn.Module):
    """多输出电器状态识别LSTM模型"""
    def __init__(self, input_size, hidden_size, num_layers, num_outputs):
        super(ApplianceLSTM, self).__init__()
        # 减少隐藏层大小和层数
        self.hidden_size = hidden_size
        self.num_layers = min(num_layers, 2)  # 限制层数
        # LSTM 层 - 添加 dropout
        self.lstm = nn.LSTM(
            input_size,
            self.hidden_size,
            self.num_layers,
            batch_first=True,
            bidirectional=False,  # 移除双向结构减少复杂度
            dropout=0.3 if self.num_layers > 1 else 0  # 层间 dropout
        )
        # 简化全连接层
        self.fc = nn.Sequential(
            nn.Linear(self.hidden_size, 32),
            nn.ReLU(),
            nn.Dropout(0.4),  # 增加 dropout 比例
            nn.LayerNorm(32),  # 添加批归一化
            nn.Linear(32, 16),
            nn.ReLU()
        )
        # 共享特征提取,独立输出层
        self.output_layers = nn.ModuleList([
            nn.Sequential(
                nn.Linear(16, 1),
                nn.Sigmoid()
            ) for _ in range(num_outputs)
        ])
    def forward(self, x):
        # 初始化隐藏状态
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        # LSTM 前向传播
        out, _ = self.lstm(x, (h0, c0))
        out = out[:, -1, :]  # 只取最后一个时间步的输出
        # 通过全连接层
        out = self.fc(out)
        # 每个电器一个输出头
        outputs = [output_layer(out) for output_layer in self.output_layers]
        # 合并输出为 [batch_size, num_outputs]
        return torch.cat(outputs, dim=1)

方案二:(多输出头的多类别预测)
模型为每个设备配置一个输出头,并根据设备状态数量,设计对应的输出维度。

class ApplianceLSTM_plus(nn.Module):
    """支持不同设备不同状态数的多输出LSTM模型"""
    def __init__(self, input_size, hidden_size, num_layers, states_per_device):
        super(ApplianceLSTM, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = min(num_layers, 2)
        self.states_per_device = states_per_device  # 每个设备的状态数列表
        # LSTM 层
        self.lstm = nn.LSTM(
            input_size,
            hidden_size,
            num_layers,
            batch_first=True,
            bidirectional=False,
            dropout=0.3 if num_layers > 1 else 0
        )
        # 共享的全连接层
        self.fc = nn.Sequential(
            nn.Linear(hidden_size, 32),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.LayerNorm(32),
            nn.Linear(32, 16),
            nn.ReLU()
        )
        # 为每个设备创建独立的输出层(状态数不同)
        self.output_layers = nn.ModuleList([
            nn.Linear(16, states) for states in self.states_per_device
        ])
    def forward(self, x):
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        out, _ = self.lstm(x, (h0, c0))
        out = out[:, -1, :]  # 取最后一个时间步的输出
        out = self.fc(out)
        # 每个设备独立输出(列表形式,每个元素对应一个设备的 logits)
        outputs = [layer(out) for layer in self.output_layers]
        return outputs

识别结果:
由于数据体量过小,有些过拟合,存在少量将关闭状态识别为开启状态的问题。