ch10 化成柜容量预测:Early Warning + 拦截策略 + 时序模型

事故现场:客户退货后才知道容量不够,化成时数据全在
事故 1 的复盘再深入一层——容量不够的根因是化成曲线参数变了(0.05C 截止变 0.07C),但化成柜当时的数据全是有的:电压曲线、电流曲线、温度曲线、化成截止时间,全都采上来了。问题是:这些数据采上来后没人拿来做容量预测。
复盘把化成柜 F1-012 的事故当周数据拉出来分析:
| 化成阶段 | 电压规格 | 实际电压 | 偏差 |
|---|---|---|---|
| 恒流阶段(0.05C) | 3.65V @ 50% SoC | 3.62V @ 50% SoC | -0.03V |
| 恒压阶段(4.2V) | 4.20V 维持 | 4.15V 维持 | -0.05V |
| 截止阶段(0.05C) | 截止电流 0.05C = 14A | 截止电流 0.07C = 19.6A | +5.6A |
每个阶段的数据都有,电压和电流都偏离规格——恒压阶段实际 4.15V 比规格 4.20V 低 0.05V(说明化成不充分),截止电流实际 19.6A 比规格 14A 高 40%(说明提前截止了)。如果当时有预测模型,看到恒压阶段电压持续偏低 0.05V 就应该预测出"这柜电芯容量会偏低 5-10Ah"——化成完成后 1 小时就能预测,不用等分容结果(化成完成到分容结果出来有 24 小时滞后)。
更早的预测信号在化成开始后 30 分钟——化成柜恒流阶段电压曲线斜率:正常电芯斜率 0.012 V/min,事故电芯斜率 0.008 V/min(低 33%)。斜率低 33% 意味着电化学极化更严重,化成不充分,容量会偏低。化成开始后 30 分钟就有预测信号,但当时没人看。

事故根因:化成柜数据采上来了,但只用于事后追溯(写报告)没用于事前预测(拦截)。MES 把化成数据存到 IoTDB 时序库后,只在分容结果出来后回查化成曲线——这是反应式,不是预测式。主动防线应该是化成过程中实时预测容量,发现偏低立即拦截不让流向下工序。
排查:化成容量预测的三层缺口
| 层 | 缺口 | 后果 |
|---|---|---|
| 数据层 | 化成时序数据存了但没用 | 数据沉睡,无价值产出 |
| 模型层 | 没有容量预测模型 | 化成完成 24h 后才知容量不够 |
| 拦截层 | 预测偏低没有自动拦截 | 异常电芯流向下工序 |
容量预测的三种思路
业内化成容量预测有三种思路,按工程复杂度排序:
思路 1:规则阈值法。基于化成曲线的关键参数阈值判断,如"恒压阶段电压 < 4.18V 报警"、"截止电流 > 0.06C 报警"。简单但只能识别明显异常,对渐变漂移不敏感。
思路 2:特征工程 + 机器学习。从化成曲线提取特征(恒流斜率、恒压维持时间、截止电流、温度峰值等 20-30 个特征),用历史数据训练 XGBoost / 随机森林模型预测分容容量。中等复杂度,准确率 85-92%。
思路 3:深度学习时序模型。用 LSTM / Transformer 直接吃秒级化成电压电流曲线,端到端预测容量。最高复杂度,准确率 92-95%,但需要大量标注数据(> 10 万样本)和 GPU 推理资源。
事故 1 的电芯厂 A 当时有 2 年历史化成数据,约 50 万样本——思路 2 够用,思路 3 投资回报率不高。本期 MES 用思路 2,思路 1 作为快速基线,思路 3 留给后续迭代。
化成曲线的关键特征
事故 1 的预测信号分析发现,化成曲线的 5 个特征对容量预测有效:
| 特征 | 物理意义 | 与容量相关性 |
|---|---|---|
| 恒流阶段电压斜率 | 电化学极化程度 | -0.78(强负相关) |
| 恒压阶段维持时间 | 化成充分程度 | +0.65(强正相关) |
| 截止电流值 | 化成完成程度 | -0.72(强负相关) |
| 化成总能量 | 整体化学反应量 | +0.85(极强正相关) |
| 温度峰值 | 反应剧烈程度 | -0.45(中等负相关) |
恒流阶段电压斜率 -0.78 负相关:斜率越低,极化越严重,化成越不充分,容量越低。化成总能量 +0.85 极强正相关:总能量是电压×电流×时间的积分,能量越高化学反应越完全,容量越高。
底层原理:XGBoost 容量预测模型
特征工程
从化成曲线 IoTDB 时序数据提取 5 大类 23 个特征:
import numpy as np
import pandas as pd
from tsfresh import extract_features
def extract_formation_features(curve_df):
"""
curve_df: 化成曲线 DataFrame
columns: time, voltage, current, temperature, stage
stage: 'CC' (恒流) / 'CV' (恒压) / 'END' (截止)
"""
features = {}
# 1. 恒流阶段特征(CC stage)
cc = curve_df[curve_df.stage == 'CC']
features['cc_duration_min'] = (cc.time.max() - cc.time.min()).total_seconds() / 60
features['cc_voltage_slope'] = np.polyfit(
(cc.time - cc.time.min()).dt.total_seconds() / 60,
cc.voltage, 1)[0] # 斜率 V/min
features['cc_voltage_mean'] = cc.voltage.mean()
features['cc_voltage_std'] = cc.voltage.std()
features['cc_current_mean'] = cc.current.mean()
# 2. 恒压阶段特征(CV stage)
cv = curve_df[curve_df.stage == 'CV']
features['cv_duration_min'] = (cv.time.max() - cv.time.min()).total_seconds() / 60
features['cv_voltage_mean'] = cv.voltage.mean()
features['cv_voltage_deviation'] = 4.2 - cv.voltage.mean() # 与目标 4.2V 偏差
features['cv_current_slope'] = np.polyfit(
(cv.time - cv.time.min()).dt.total_seconds() / 60,
cv.current, 1)[0] # 电流下降斜率
# 3. 截止阶段特征(END stage)
end = curve_df[curve_df.stage == 'END']
features['end_current'] = end.current.iloc[-1] if len(end) > 0 else None
features['end_current_deviation'] = features['end_current'] - 0.05 * 280 # 与规格偏差
# 4. 全程特征
features['total_duration_min'] = (curve_df.time.max() - curve_df.time.min()).total_seconds() / 60
features['total_energy_wh'] = np.trapz(curve_df.voltage * curve_df.current,
x=(curve_df.time - curve_df.time.min()).dt.total_seconds() / 3600)
features['temp_peak'] = curve_df.temperature.max()
features['temp_mean'] = curve_df.temperature.mean()
features['temp_std'] = curve_df.temperature.std()
# 5. 时序特征(用 tsfresh 自动提取)
tsfresh_features = extract_features(curve_df[['time','voltage']],
column_id='lot_id', column_sort='time',
column_value='voltage')
return {**features, **tsfresh_features}
XGBoost 模型训练
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
def train_capacity_model(features_df, labels):
"""
features_df: 特征 DataFrame (n_samples, n_features)
labels: 实际分容容量 (n_samples,)
"""
X_train, X_test, y_train, y_test = train_test_split(
features_df, labels, test_size=0.2, random_state=42)
model = xgb.XGBRegressor(
n_estimators=300,
max_depth=6,
learning_rate=0.1,
subsample=0.8,
colsample_bytree=0.8,
reg_alpha=0.1,
reg_lambda=1.0,
random_state=42
)
model.fit(X_train, y_train,
eval_set=[(X_test, y_test)],
early_stopping_rounds=20,
verbose=False)
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MAE: {mae:.2f} Ah | R²: {r2:.4f}")
return model, mae, r2
模型性能与拦截策略
50 万历史样本训练后,模型性能:
| 指标 | 数值 |
|---|---|
| MAE(平均绝对误差) | 1.8 Ah |
| R²(决定系数) | 0.91 |
| 准确率(误差 < 3Ah) | 87% |
| 误报率(实际 ≥ 275Ah 但预测 < 275Ah) | 4.2% |
| 漏报率(实际 < 275Ah 但预测 ≥ 275Ah) | 1.8% |
模型上线后,漏报率 1.8% 是关键——即 100 个真异常电芯里有 1.8 个被漏报。误报率 4.2% 可接受——误报会触发人工复核,多花工时但不漏料。
拦截策略分三档:
| 预测容量 | 策略 | 动作 |
|---|---|---|
| ≥ 280Ah | 直接通过 | 流向下工序 |
| 275-280Ah | 预警 | Andon L4 + 人工复核 |
| < 275Ah | 自动 hold | 不流向下工序 + Andon L3 |
正确姿势:化成容量预测系统
1. 实时预测架构
化成柜完成化成后 1 分钟内触发预测:
@Service
public class CapacityPredictionService {
@Autowired private IoTdbService ioTdbService;
@Autowired private FeatureExtractor featureExtractor;
@Autowired private XgBoostModel model;
@Autowired private AndonService andonService;
@Autowired private LotService lotService;
@EventListener
public void onFormationComplete(FormationCompleteEvent evt) {
Long lotId = evt.getLotId();
Long deviceId = evt.getDeviceId();
// 1. 从 IoTDB 拉化成曲线
TimeSeries curve = ioTdbService.queryFormationCurve(
deviceId, lotId, evt.getStartTime(), evt.getEndTime());
// 2. 提取特征
Map<String, Double> features = featureExtractor.extract(curve);
// 3. 预测容量
double predictedCapacity = model.predict(features);
double specLower = 275.0;
double specUpper = 285.0;
// 4. 拦截策略
if (predictedCapacity < specLower) {
// 自动 hold + Andon L3
lotService.hold(lotId, "化成容量预测 " + predictedCapacity +
" < 规格下限 " + specLower);
andonService.call(AndonCall.builder()
.source(AndonSource.AI_PREDICTION)
.level(AndonLevel.L3)
.lotId(lotId)
.deviceId(deviceId)
.title("化成容量预测异常: " + lotId +
" 预测 " + predictedCapacity + "Ah < 275Ah")
.description("化成柜 " + deviceId + " | 预测容量 " +
predictedCapacity + " | 特征: " + features)
.build());
} else if (predictedCapacity < specLower + 5) {
// 预警 + Andon L4
andonService.call(AndonCall.builder()
.source(AndonSource.AI_PREDICTION)
.level(AndonLevel.L4)
.lotId(lotId)
.deviceId(deviceId)
.title("化成容量预测预警: " + lotId +
" 预测 " + predictedCapacity + "Ah")
.build());
}
// 5. 记录预测结果
predictionMapper.insert(lotId, deviceId, predictedCapacity,
features, evt.getStartTime());
}
}
2. 模型版本管理与 A/B 测试
模型迭代时需要 A/B 测试,避免新模型直接上线后性能回退:
CREATE TABLE model_version (
id BIGINT PRIMARY KEY,
version VARCHAR(16) NOT NULL UNIQUE, -- v1.0.0
model_path VARCHAR(256) NOT NULL, -- 模型文件路径
mae DECIMAL(8,4),
r2 DECIMAL(8,4),
accuracy DECIMAL(8,4),
false_alarm_rate DECIMAL(8,4), -- 误报率
miss_rate DECIMAL(8,4), -- 漏报率
status VARCHAR(16) NOT NULL, -- EXPERIMENT/STAGING/PRODUCTION/RETIRED
created_at TIMESTAMP,
promoted_at TIMESTAMP
);
@Service
public class ModelVersionService {
@Autowired private ModelVersionMapper mapper;
public XgBoostModel getActiveModel() {
ModelVersion v = mapper.findActive();
return XgBoostModel.load(v.getModelPath());
}
@Transactional
public void promote(String version) {
// 把当前 PRODUCTION 设为 RETIRED
mapper.retireProduction();
// 把目标版本设为 PRODUCTION
mapper.promote(version);
}
}
3. 模型重训流水线
模型每周重训一次,用最近 90 天数据:
def retrain_pipeline():
# 1. 从 IoTDB 拉最近 90 天化成曲线
curves = iotdb_client.query_recent(days=90)
# 2. 从 PostgreSQL 拉对应分容结果(label)
labels = psql_client.query_grading_results(days=90)
# 3. 特征提取
features_df = pd.DataFrame([extract_formation_features(c) for c in curves])
# 4. 训练新模型
model, mae, r2 = train_capacity_model(features_df, labels)
# 5. 与当前模型对比
current = model_repo.get_active()
if mae < current.mae * 0.95 and r2 > current.r2 * 1.02:
# 性能提升 5% 以上才上线
model_repo.save(model, version=bump_version(current.version),
mae=mae, r2=r2, status='STAGING')
# 等 1 周 A/B 测试后 promote
schedule_ab_test()
else:
log("性能未提升,不重训")
数据说话:预测系统上线后拦截统计
预测系统上线 3 个月,拦截统计:
| 维度 | 上线前 | 上线后 3 个月 | 改进 |
|---|---|---|---|
| 容量不够退货次数 | 月均 0.8 次 | 0 次 | 100% 消除 |
| 化成完成到知容量时间 | 24 小时(分容结果) | 1 分钟(预测) | -99.97% |
| 容量不够电芯流出工序 | 月均 6.7 颗 | 0 颗 | 100% 拦截 |
| 自动 hold 异常电芯 | 不存在 | 22 次 | 0 异常流出 |
| 误报触发人工复核 | 不存在 | 4.2% 误报率 | 月均 28 次复核 |
| 漏报 | 不存在 | 1.8% | 月均漏报 1.2 次 |
| 模型重训频率 | 不存在 | 周一次 | 持续优化 |
关键发现:
- 化成完成 1 分钟内预测容量——比传统等分容结果 24 小时快 1440 倍,提前 24 小时拦截异常。
- 22 次自动 hold 中 18 次是真异常,4 次是误报但被人工复核纠正——有效拦截率 82%。
- 1.8% 漏报——主要发生在化成曲线参数变更后第一柜(新参数下样本少,模型预测置信度低),通过 ch08 的 SPC 基线重置机制兜底。
面试怎么答:化成容量预测
问:化成容量预测为什么不用深度学习 LSTM?
答:3 个原因:① 样本量——LSTM 端到端时序模型需要 10 万以上标注样本,电芯厂当时 50 万样本看似够,但化成曲线是 2 小时秒级数据 = 7200 点/样本,50 万 × 7200 = 36 亿数据点,训练成本高;② 可解释性——XGBoost 特征重要性可解释(恒压维持时间贡献 35%、恒流斜率 28%、截止电流 22%),工程师能据此调工艺;LSTM 黑盒,工艺工程师不信任;③ ROI——XGBoost 准确率 91%,LSTM 准确率 92-95%,差 1-4 个百分点,但工程成本高 5-10 倍(GPU 推理 vs CPU 推理)。本期选 XGBoost 是工程权衡,LSTM 留给后续数据量到 200 万样本时迭代。
问:化成曲线的 5 个核心特征是什么?为什么有效?
答:5 个核心特征是:① 恒流阶段电压斜率 -0.78 相关——斜率低说明极化严重,化成不充分,容量低;② 恒压阶段维持时间 +0.65 相关——维持时间长说明化成充分;③ 截止电流值 -0.72 相关——电流大说明提前截止,化成不充分;④ 化成总能量 +0.85 相关——能量是 V×I×t 积分,能量高化学反应完全;⑤ 温度峰值 -0.45 相关——温度高反应剧烈但易副反应。5 个特征物理意义清晰,覆盖了化成的电化学全过程,比单一参数(如截止电压)预测能力强 3-5 倍。
问:预测偏低自动 hold 会不会误伤正常电芯?
答:会有误报,但控制在 4.2%——即 100 个正常电芯里有 4.2 个被误 hold。误报通过两层兜底:① hold 后 30 分钟内人工复核——质量工程师看化成曲线,判断是否真异常;② 累积 hold 数据回流模型——误报样本作为负样本进入下次重训,模型持续优化。事故 1 的损失 18.8 万 vs 误报成本(4.2% × 月产 12 万 × 30 元/颗人工复核 = 15 万元/月),ROI 仍然正——拦截 1 次事故省 18.8 万 vs 月度误报成本 15 万,省 3.8 万 + 客户信任度提升。
问:模型重训为什么周一次?不能每天?
答:3 个原因:① 数据量——每天重训用最近 90 天数据 = 90 × 1500 = 13.5 万样本,与周一次用 90 × 1500 = 13.5 万样本是同一份数据,每天重训浪费算力;② 稳定性——每天重训模型参数波动大,预测结果不稳定,运营难判断"这次预测偏低是模型问题还是真异常";③ A/B 测试——新模型 STAGING 后需要 1 周 A/B 测试验证性能提升 5% 才 promote,每天重训跳过 A/B 测试风险高。周一次重训是工程稳定性 vs 模型时效性的平衡。
落地清单:化成容量预测工程化动作
- IoTDB 存化成秒级曲线:每柜化成 7200 秒点,存 IoTDB 时序库,禁用 PostgreSQL 存秒级数据(性能灾难)。
- 特征工程 23 个特征:恒流斜率 + 恒压维持 + 截止电流 + 总能量 + 温度峰值 + tsfresh 自动特征,禁用单一参数预测。
- XGBoost 模型 + 周一次重训:50 万样本训练,准确率 91%,A/B 测试性能提升 5% 才 promote。
- 化成完成 1 分钟内触发预测:EventListener 监听 FormationCompleteEvent,异步预测不阻塞化成柜控制。
- 三档拦截策略:< 275Ah 自动 hold + Andon L3;275-280Ah 预警 + Andon L4;≥ 280Ah 通过。
- 模型版本管理:EXPERIMENT/STAGING/PRODUCTION/RETIRED 四态,禁用直接覆盖生产模型。
- 误报数据回流:人工复核结果累积作为负样本,下次重训用,模型持续优化。
- 黄金回归:① 化成完成后 1 分钟内必须出预测结果;② 预测 < 275Ah 必须自动 hold;③ 模型 promote 前必须 A/B 测试 1 周;④ 误报样本必须回流下次重训;⑤ 模型重训必须用最近 90 天数据。
下一章我们离开预测层进入时序数据存储,看 IoTDB 怎么把秒级点位分级归档成分钟级/小时级 KPI——这是支撑化成预测模型、SPC 控制图、OEE 计算的底层数据基础设施。
