4、特征工程(上):从充放电曲线提取特征

各位同学,咱们今天聊聊特征工程。说实话,在BMS算法里,特征工程比模型本身更吃功夫。我见过太多人拿着原始电压电流数据就往模型里塞,结果效果惨不忍睹。说白了,你给模型喂什么,它就学什么——喂的是垃圾,吐出来的也是垃圾。

这一章,咱们重点讲三个最常用的特征:恒流充电时间、电压平台斜率、增量容量分析(ICA)。这三个特征,是我在项目里用得最多的,也是最能反映电池老化状态的。

4.1 恒流充电时间(CC Charging Time)

先说说最简单的——恒流充电时间。你想想看,电池在恒流充电阶段,从起始电压充到截止电压,需要多长时间?这个时间会随着电池老化而缩短。

为什么?因为内阻增大了,极化电压升高了,电池更快地达到了截止电压。我在某车企的项目里遇到过,同一款电池,新电池恒流充电时间约45分钟,老化到80% SOH时,只剩32分钟。这个特征非常敏感。

提取方法很简单:

# 提取恒流充电时间
def extract_cc_time(df):
    """
    df: 包含时间、电流、电压的DataFrame
    返回:恒流充电时长(秒)
    """
    # 找到恒流充电阶段(电流稳定在某个正值)
    cc_mask = (df['current'] > 0.5)  # 假设恒流充电电流 > 0.5A
    cc_periods = df[cc_mask]
    
    if len(cc_periods) == 0:
        return 0
    
    # 计算持续时间
    start_time = cc_periods['time'].iloc[0]
    end_time = cc_periods['time'].iloc[-1]
    cc_duration = end_time - start_time
    
    return cc_duration
我的小技巧:实际数据里,电流会有波动。我习惯用滑动窗口平滑一下,再判断恒流阶段。窗口大小取10个采样点,效果不错。

这里有个坑——起始电压不一致。如果两次充电的起始电压不同,恒流充电时间就没法直接比较。我建议统一归一化到相同的SOC区间,比如从20% SOC充到80% SOC。

避坑指南:我曾经遇到过,同一块电池在不同温度下,恒流充电时间差了20%。温度影响太大了!所以提取这个特征时,一定要记录温度,或者做温度补偿。

4.2 电压平台斜率(Voltage Plateau Slope)

接下来是电压平台斜率。这个特征主要针对磷酸铁锂(LFP)电池。LFP电池的电压曲线有个特点——中间有一段很平的平台区。随着老化,这个平台会逐渐倾斜。

说白了,新电池的电压平台几乎是平的,斜率接近0。老电池的平台会向上翘或者向下弯。我测量的是平台区中间段的电压变化率

提取步骤:

  1. 找到恒流充电的电压曲线
  2. 确定平台区范围(通常是SOC 20%-80%之间)
  3. 对平台区做线性拟合,斜率就是特征值
import numpy as np
from sklearn.linear_model import LinearRegression

def extract_plateau_slope(df, soc_min=20, soc_max=80):
    """
    提取电压平台斜率
    """
    # 假设已有SOC列
    plateau_mask = (df['soc'] >= soc_min) & (df['soc'] <= soc_max)
    plateau_data = df[plateau_mask]
    
    # 用容量(或时间)作为x,电压作为y
    x = plateau_data['capacity'].values.reshape(-1, 1)  # 累计容量
    y = plateau_data['voltage'].values
    
    # 线性拟合
    model = LinearRegression()
    model.fit(x, y)
    
    slope = model.coef_[0]  # 斜率,单位 V/Ah
    
    return slope
重要:斜率值很小,通常在10^-4量级。我习惯乘以1000,用mV/Ah作为单位,这样数值更直观。

嗯,这里要注意——平台区的定义。不同电池的平台区范围不一样。三元锂电池的平台区就很短,甚至没有明显的平台。所以这个特征主要针对LFP电池。我在做三元电池项目时,基本不用这个特征。

4.3 增量容量分析(ICA)

终于到了重头戏——ICA。这是我最喜欢的特征提取方法,没有之一。ICA的全称是Incremental Capacity Analysis,说白了就是看dQ/dV曲线。

为什么ICA这么牛?因为电压曲线上的微小变化,在dQ/dV曲线上会被放大。电池内部的相变过程、活性物质损失、锂离子消耗,都会在ICA曲线上留下明显的峰和谷。

我举个例子:新电池的ICA曲线有三个明显的峰,分别对应不同的电化学反应。随着老化,这些峰会降低、变宽、甚至消失。通过追踪这些峰的变化,就能判断电池的失效模式。

计算步骤:

  1. 获取恒流充电的电压-容量曲线
  2. 对电压进行等间隔采样(比如每10mV一个点)
  3. 计算dQ/dV = 容量变化量 / 电压变化量
  4. 平滑处理,去除噪声
def compute_ica(voltage, capacity, voltage_step=0.01):
    """
    计算ICA曲线
    voltage: 电压数组 (V)
    capacity: 容量数组 (Ah)
    voltage_step: 电压步长 (V)
    """
    # 对电压进行等间隔插值
    v_min = np.floor(voltage.min() * 100) / 100
    v_max = np.ceil(voltage.max() * 100) / 100
    v_grid = np.arange(v_min, v_max, voltage_step)
    
    # 插值得到对应容量
    cap_interp = np.interp(v_grid, voltage, capacity)
    
    # 计算dQ/dV
    dq = np.diff(cap_interp)
    dv = np.diff(v_grid)
    ica = dq / dv
    
    # 返回电压中点和ICA值
    v_mid = (v_grid[:-1] + v_grid[1:]) / 2
    
    return v_mid, ica
我的经验:电压步长选10mV比较合适。步长太小,噪声太大;步长太大,会丢失细节。另外,计算完ICA后,一定要做平滑。我习惯用Savitzky-Golay滤波器,窗口长度选5-7。

从ICA曲线中,我们可以提取以下特征:

特征名称 物理含义 老化趋势
峰1位置(电压) 对应负极石墨的嵌锂阶段 向高电压方向移动
峰1高度(dQ/dV值) 该阶段的容量贡献 逐渐降低
峰2位置(电压) 对应正极的相变过程 基本不变
峰2高度(dQ/dV值) 该阶段的容量贡献 逐渐降低
峰谷差值 反映极化内阻变化 差值增大
避坑指南:我曾经犯过一个错误——直接用原始数据计算ICA,结果曲线全是毛刺,根本看不出峰。后来发现,数据采样率太高(100Hz),电压噪声太大。我建议先对电压做中值滤波,窗口大小取50个点,再计算ICA。效果立竿见影。

另外,ICA对数据质量要求很高。如果充电电流不稳定,或者温度波动大,ICA曲线会严重失真。我一般只使用恒流充电段的数据,而且要求电流波动在±1%以内。

4.4 三个特征的对比与选择

最后,我给大家总结一下这三个特征的适用场景:

  • 恒流充电时间:简单粗暴,适合快速评估。但受温度和起始SOC影响大,需要做归一化。
  • 电压平台斜率:专门针对LFP电池。如果做三元电池项目,可以跳过这个特征。
  • ICA特征:信息量最大,能反映失效模式。但计算复杂,对数据质量要求高。

我个人习惯是:先用恒流充电时间做快速筛选,再用ICA做深度分析。如果项目时间紧,只提取恒流充电时间也能达到80%的效果。但如果你想发论文或者做高精度预测,ICA是绕不开的。

好了,这一章就到这里。下一章咱们继续讲特征工程的下半部分——从电压弛豫曲线和阻抗谱中提取特征。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。