3. 特征工程(上):从充放电曲线中提取健康特征

各位同学,欢迎来到特征工程部分。说实话,在电池寿命预测这个领域,我见过太多人一上来就扔模型、调参数,结果效果惨不忍睹。为什么?因为特征没做好。你想想看,模型再厉害,喂进去的是垃圾,出来的还是垃圾。

这一章,我们聊聊怎么从最原始的充放电曲线里,挖出那些真正能反映电池健康状态的特征。我个人习惯把这部分叫做「黄金特征」,因为它们直接和电池的老化机制挂钩。

核心观点: 好的特征工程,比模型本身更重要。在电池领域尤其如此。
健康特征提取 恒流充电时间 恒压充电容量 电压平台变化 CC 阶段时长 CV 阶段容量 dV/dQ 曲线 容量衰减指标 内阻变化指标 老化机理识别

3.1 恒流充电时间:最直观的退化信号

先说说恒流充电时间。这个特征简单到让人容易忽略,但它真的很有用。我参与过一个电动大巴的项目,当时就是靠这个特征提前两周预警了电池组的异常。

恒流充电时间,就是电池在 CC 阶段从起始电压充到截止电压所花的时间。随着电池老化,内阻增加,这个时间会明显缩短。为什么?因为老电池更容易达到电压上限,提前进入恒压阶段。

我的经验: 恒流充电时间对温度非常敏感。同一块电池,夏天和冬天测出来的值能差 20%。所以做特征工程时,一定要做温度归一化。

提取方法很简单,直接从充电曲线里找 CC 阶段的起止点就行:

import pandas as pd
import numpy as np

def extract_cct(charge_data):
    """
    提取恒流充电时间
    charge_data: DataFrame, 包含 'time', 'current', 'voltage' 列
    """
    # 找到电流稳定的 CC 阶段
    cc_mask = (charge_data['current'] > 0.9 * charge_data['current'].max())
    
    # 计算 CC 阶段时长
    cc_start = charge_data[cc_mask]['time'].iloc[0]
    cc_end = charge_data[cc_mask]['time'].iloc[-1]
    cct = cc_end - cc_start
    
    return cct

3.2 恒压充电容量:隐藏的内阻探针

接下来是恒压充电容量。这个特征很有意思——它反映的是电池在 CV 阶段还能「吃」进去多少电。

新电池内阻小,CC 阶段就能充进去大部分容量,CV 阶段很短。老电池呢?内阻大了,CC 阶段充不满,只能靠 CV 阶段慢慢补。所以 CV 容量会随着老化逐渐增加。

我记得有一次做数据分析,发现某批电池的 CV 容量突然飙升。当时就觉得不对劲,一查果然是电解液出了问题。这个特征就像个探针,能帮你提前发现内部异常。

健康状态 CC 时间 CV 容量 物理含义
新电池 长(~45min) 小(<5% 总容量) 内阻小,离子传输顺畅
中度老化 中等(~35min) 中等(~10% 总容量) 内阻增加,SEI 膜增厚
严重老化 短(<25min) 大(>15% 总容量) 活性锂损失,结构退化
def extract_cv_capacity(charge_data):
    """
    提取恒压充电容量
    """
    # 找到 CV 阶段(电流下降阶段)
    cv_mask = (charge_data['current'] < 0.1 * charge_data['current'].max()) & \
              (charge_data['voltage'] >= charge_data['voltage'].max() * 0.99)
    
    # 积分计算 CV 容量
    cv_capacity = np.trapz(charge_data[cv_mask]['current'], 
                           charge_data[cv_mask]['time'])
    
    return cv_capacity
注意: 提取 CV 容量时,要确保充电截止电流一致。不同截止电流下,CV 容量差异很大。我曾经因为没注意这个细节,浪费了整整一周的时间去排查模型效果差的原因。

3.3 电压平台变化:电池的「心电图」

电压平台,说白了就是电池在充放电过程中电压相对平稳的那段区间。新电池的电压平台很平坦,老电池的平台会逐渐倾斜、缩短。

我个人喜欢用中值电压来量化这个变化。中值电压就是充到一半容量时的电压值。随着老化,这个值会逐渐下降。你可以把它想象成电池的「血压」——数值变了,说明内部出问题了。

def extract_median_voltage(charge_data):
    """
    提取中值电压
    """
    # 计算累计容量
    capacity = np.cumsum(np.gradient(charge_data['time']) * charge_data['current'])
    capacity_normalized = capacity / capacity.max()
    
    # 找到 50% SOC 对应的电压
    median_voltage = np.interp(0.5, capacity_normalized, charge_data['voltage'])
    
    return median_voltage

3.4 差分电压分析(DVA):看透电池的「内脏」

终于到了 DVA。这个技术是我最想安利给各位的。它就像给电池做 CT 扫描,能让你看到正负极材料相变的过程。

DVA 的核心思想很简单:计算 dV/dQ,也就是电压对容量的导数。每个峰值对应一个相变反应。随着老化,这些峰值的位置、高度、宽度都会变化。

嗯,这里要注意一点:DVA 对噪声非常敏感。原始数据直接求导,出来的曲线跟心电图似的,根本没法看。所以一定要先做平滑处理。

from scipy.signal import savgol_filter

def compute_dva(charge_data, window_length=21, polyorder=3):
    """
    计算差分电压曲线
    """
    # 先对电压做平滑
    voltage_smooth = savgol_filter(charge_data['voltage'], 
                                   window_length, polyorder)
    
    # 计算容量增量
    dq = np.gradient(charge_data['capacity'])
    dv = np.gradient(voltage_smooth)
    
    # 避免除零
    dv[dv == 0] = np.nan
    dva = dq / dv
    
    return charge_data['capacity'], dva
关键参数:
  • 峰值位置: 反映相变反应的起始点,老化后向右偏移
  • 峰值高度: 反映反应活性,老化后降低
  • 峰值宽度: 反映反应均匀性,老化后变宽

我在实际项目中,通常会把 DVA 曲线的峰值位置和高度作为特征输入到模型中。效果比直接用原始电压曲线好得多。说白了,DVA 帮你把复杂的电化学过程浓缩成了几个关键数字,模型学起来自然更轻松。

避坑指南: 我曾经在 DVA 上吃过亏——直接用原始数据求导,结果模型训练时 loss 死活降不下去。后来发现是噪声导致的伪峰。所以记住:先平滑,再求导,最后提取特征。顺序不能乱。

好了,这一章的内容就到这里。我们讲了恒流充电时间、恒压充电容量、电压平台变化和 DVA 这四个核心特征。下一章我们会继续聊剩下的特征提取方法,包括 IC 曲线、增量容量分析等更高级的技术。


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