4. 特征工程:电压、电流、温度特征提取、时间序列特征构造、特征选择方法

各位同学,欢迎来到第四讲。

上一章我们聊了数据预处理,把原始数据洗得干干净净。但光洗干净还不够,你得让数据“说话”。

说白了,原始电压、电流、温度只是一堆数字。SOC预测需要的是这些数字背后的规律。怎么把规律挖出来?这就是特征工程要干的事。

我个人习惯把特征工程看作“厨师备菜”。菜洗好了(预处理),现在要切丁、切片、腌制(特征提取),最后还要挑出最入味的几样(特征选择)。

4.1 电压特征提取:不只是看瞬时值

电压是SOC预测最直接的信号。但直接用原始电压,效果往往不好。

为什么?因为电池电压有“回弹效应”。放电时电压骤降,静置后又回升。你直接拿当前电压去预测SOC,误差会很大。

我常用的电压特征:

  • 电压差(ΔV):当前电压与上一时刻电压的差值。反映电压变化速率。
  • 电压变化率(dV/dt):单位时间内的电压变化。放电末期这个值会急剧增大,是SOC接近0%的强烈信号。
  • 电压滑动窗口统计:过去N个点的均值、标准差、最大值、最小值。比如过去10秒的平均电压,比瞬时值稳定得多。
  • 电压与开路电压的差值:如果有OCV曲线,计算当前电压与对应SOC的OCV差值。这个差值反映了极化内阻的影响。

重要提示:电压特征对温度极其敏感。同一SOC下,0℃和40℃的电压能差0.3V以上。所以电压特征一定要和温度特征搭配使用。

4.2 电流特征提取:积分不是万能的

电流特征,大家第一反应就是“安时积分”。没错,安时积分是SOC估算的基石。但光靠积分,误差会累积。

我在项目中遇到过一个问题:一个客户用纯安时积分,跑了100个循环后,SOC误差漂到了15%。为什么?因为电流传感器有零点漂移,每次积分都多算一点点。

所以电流特征要这样提取:

  • 累积容量(Ah):对电流时间积分。这是基础特征。
  • 平均电流:过去一段时间内的平均放电/充电电流。反映负载强度。
  • 电流变化率(dI/dt):电流突变的剧烈程度。大电流脉冲会导致极化电压突变,影响SOC估算。
  • 电流方向标志:充电为+1,放电为-1,静置为0。这个离散特征有时比连续值更有用。
  • 电流平方(I²):与焦耳热成正比。间接反映电池内部温升。

我的小技巧:电流特征最好做“分区间统计”。比如分别统计充电段、放电段、静置段的平均电流和累积容量。这样模型能学到不同工况下的特性。

4.3 温度特征提取:别只看表面温度

温度影响化学反应速率,直接影响SOC估算精度。但很多人只用一个表面温度传感器,这不够。

嗯,这里要注意:电池内部温度和表面温度可以差5-10℃,特别是在大倍率充放电时。

温度特征我这样处理:

  • 表面温度(T_surf):直接测量值。
  • 温度变化率(dT/dt):温升速率。异常温升往往预示着内阻增大或热失控前兆。
  • 温升(ΔT):当前温度与初始温度的差值。反映累积发热量。
  • 温度与电流的交互特征:比如 T × I,T × I²。温度越高、电流越大,极化效应越明显。

我曾经踩过的坑:有一次模型在夏天表现很好,冬天却完全失效。后来发现是温度特征没做归一化。夏天30℃和冬天-10℃的数据混在一起训练,模型学偏了。记住:温度特征一定要做标准化,或者至少缩放到[0,1]区间。

4.4 时间序列特征构造:让模型记住历史

SOC预测本质上是一个时间序列问题。当前SOC不仅取决于当前测量值,还取决于过去一段时间的状态。

说白了,电池有“记忆效应”。你刚经历了一个大电流放电,和刚经历了一个小电流放电,即使当前电压相同,SOC也可能不同。

时间序列特征构造方法:

  1. 滑动窗口特征:取过去N个时间步的电压、电流、温度,计算统计量(均值、方差、偏度、峰度)。窗口大小一般取10-30个点(对应1-3秒)。
  2. 差分特征:一阶差分(当前值减上一时刻值)、二阶差分。捕捉变化趋势。
  3. 滞后特征(Lag Features):直接把过去几个时刻的值作为新特征。比如 t-1, t-2, t-3 时刻的电压。
  4. 累积特征:从放电开始到现在的累积容量、累积能量。反映放电深度。

下面是我常用的一个时间序列特征构造代码示例:

import pandas as pd
import numpy as np

def create_ts_features(df, window=10):
    """
    构造时间序列特征
    df: 包含 voltage, current, temperature 的DataFrame
    window: 滑动窗口大小
    """
    df_feat = df.copy()
    
    # 滑动窗口统计
    for col in ['voltage', 'current', 'temperature']:
        df_feat[f'{col}_mean_{window}'] = df[col].rolling(window).mean()
        df_feat[f'{col}_std_{window}'] = df[col].rolling(window).std()
        df_feat[f'{col}_max_{window}'] = df[col].rolling(window).max()
        df_feat[f'{col}_min_{window}'] = df[col].rolling(window).min()
    
    # 差分特征
    df_feat['voltage_diff'] = df['voltage'].diff()
    df_feat['current_diff'] = df['current'].diff()
    
    # 滞后特征(取过去3个点)
    for lag in [1, 2, 3]:
        df_feat[f'voltage_lag_{lag}'] = df['voltage'].shift(lag)
        df_feat[f'current_lag_{lag}'] = df['current'].shift(lag)
    
    # 累积容量(安时积分)
    dt = 0.1  # 假设采样间隔0.1秒
    df_feat['cum_ah'] = (df['current'] * dt).cumsum()
    
    return df_feat.dropna()

注意:构造滞后特征和滑动窗口特征时,前N行会变成NaN。记得用 dropna() 或填充策略处理。我一般直接丢掉前N行,因为数据量通常足够大。

4.5 特征选择方法:别让模型“消化不良”

特征构造完了,你可能得到了几十甚至上百个特征。但特征不是越多越好。

你想想看,如果加入一个完全无关的特征(比如当前时间戳的秒数),模型不仅学不到东西,还可能过拟合。特征选择就是帮你“去芜存菁”。

我常用的三种特征选择方法:

方法 原理 适用场景 我的经验
过滤法(Filter) 计算每个特征与目标变量的相关性(皮尔逊系数、互信息) 初步筛选,快速剔除无关特征 先跑一遍,把相关系数低于0.1的特征直接扔掉
包裹法(Wrapper) 用模型反复训练,评估特征子集的效果(如递归特征消除RFE) 特征数量较少(<50)时效果好 计算量大,但结果可靠。我一般用随机森林做RFE
嵌入法(Embedded) 在模型训练过程中自动进行特征选择(如Lasso回归、树模型特征重要性) 特征数量多,且模型本身支持 最推荐。LightGBM或XGBoost训练完直接看feature_importance

具体操作步骤:

  1. 第一步:过滤法快速筛选。计算每个特征与SOC的皮尔逊相关系数,保留绝对值大于0.2的特征。
  2. 第二步:嵌入法精挑细选。用LightGBM训练一次,查看特征重要性,保留累计重要性达到95%的前K个特征。
  3. 第三步:人工复核。看看选出来的特征是否合理。比如“当前秒数”这种特征重要性很高,那大概率是数据泄露,要剔除。

核心原则:特征选择不是一锤子买卖。我建议每次模型迭代后都重新做一次特征选择。因为随着数据量增加,特征的重要性排序可能会变化。

4.6 本章知识体系总览

下面这张图总结了本章的核心逻辑。从原始数据到最终特征集,一共四步:

特征工程核心流程 原始数据 电压、电流、温度 特征提取 电压特征 电流特征 / 温度特征 时间序列构造 滑动窗口 / 差分 滞后特征 / 累积特征 选择 过滤 嵌入 关键要点 ① 电压特征要关注变化率,不要只看瞬时值 ② 电流特征要配合温度一起使用,避免积分漂移 ③ 时间序列特征让模型“记住”历史状态,提升预测精度 ④ 特征选择用“过滤法+嵌入法”组合,效率高且可靠

好了,特征工程这部分就讲到这里。记住一句话:好的特征让模型事半功倍,坏的特征让模型事倍功半。下一章我们会把这些特征喂给模型,看看实际效果如何。


公众号:蓝海资料掘金营,微信deep3321