3. 数据采集与清洗:从MES/SPC系统获取原始数据

在半导体制造与高端电子组装产线中,缺陷密度监控的起点并非看板本身,而是底层的数据管道。MES(制造执行系统)记录每一片晶圆或每一块基板的加工历史、设备参数与检验结果;SPC(统计过程控制)系统则按时间序列采集关键质量指标。本章将聚焦如何从这两个源头获取原始数据,并完成缺失值、异常值与重复数据的清洗,为后续的实时看板与报警阈值计算提供可靠输入。

核心原则: 数据清洗的目标不是“删除所有异常”,而是区分“真实工艺波动”与“传感器/录入错误”。错误的清洗策略会直接导致误报警或漏报警。

3.1 数据源识别与接口规范

MES与SPC系统的数据输出格式通常分为三类:

数据源 典型输出格式 采集频率 关键字段
MES 检验模块 CSV / JSON / 数据库表 每批次/每片 LotID, WaferID, Step, DefectCount, DieSize
SPC 系统 时间序列数据库 / REST API 每5分钟/每小时 Timestamp, Parameter, Value, UCL, LCL
设备自动化日志 SECS/GEM 消息 事件触发 EquipmentID, EventType, AlarmCode

以下是一个从MES系统获取原始缺陷数据的Python示例(模拟API调用):

import pandas as pd
import requests
from datetime import datetime, timedelta

# 模拟MES API端点
MES_API_URL = "http://mes-fab01.internal/api/v1/defects"

def fetch_mes_defects(lot_id: str, start_time: datetime, end_time: datetime) -> pd.DataFrame:
    params = {
        "lot": lot_id,
        "from": start_time.isoformat(),
        "to": end_time.isoformat(),
        "format": "json"
    }
    response = requests.get(MES_API_URL, params=params, timeout=30)
    response.raise_for_status()
    data = response.json()
    
    # 转换为DataFrame
    df = pd.DataFrame(data["records"])
    df["timestamp"] = pd.to_datetime(df["timestamp"])
    return df

# 示例调用
df_raw = fetch_mes_defects("LOT-2025-003", 
                           datetime(2025, 3, 1, 0, 0, 0),
                           datetime(2025, 3, 1, 23, 59, 59))
print(f"获取到 {len(df_raw)} 条原始记录")
print(df_raw.head())
工程提示: 实际产线中MES接口往往有速率限制(如每秒最多10次请求)。建议使用批量拉取+本地缓存策略,避免因超时导致数据断层。

3.2 缺失值处理策略

原始数据中的缺失值通常由传感器离线、通信中断或检验跳过引起。不同缺失模式需要不同处理方式:

  • 完全随机缺失(MCAR): 如某台设备临时断网导致5分钟数据丢失。可采用前向填充(ffill)或线性插值。
  • 随机缺失(MAR): 如缺陷密度值缺失但批次号存在。可使用同批次均值填充。
  • 非随机缺失(MNAR): 如设备报警时故意不记录缺陷数。此类缺失必须标记为“特殊事件”,不可简单填充。

以下为缺失值处理的标准流程:

def handle_missing_values(df: pd.DataFrame, strategy: str = "interpolate") -> pd.DataFrame:
    """
    处理缺陷密度数据中的缺失值
    策略: 'drop' - 删除含缺失的行
          'ffill' - 前向填充
          'interpolate' - 线性插值(默认)
          'mean_by_lot' - 按批次均值填充
    """
    df_clean = df.copy()
    
    if strategy == "drop":
        df_clean = df_clean.dropna(subset=["defect_density"])
    elif strategy == "ffill":
        df_clean["defect_density"] = df_clean["defect_density"].ffill()
    elif strategy == "interpolate":
        df_clean["defect_density"] = df_clean["defect_density"].interpolate(method="linear")
    elif strategy == "mean_by_lot":
        lot_mean = df_clean.groupby("lot_id")["defect_density"].transform("mean")
        df_clean["defect_density"] = df_clean["defect_density"].fillna(lot_mean)
    else:
        raise ValueError(f"未知策略: {strategy}")
    
    missing_before = df["defect_density"].isna().sum()
    missing_after = df_clean["defect_density"].isna().sum()
    print(f"缺失值: {missing_before} -> {missing_after}")
    return df_clean
注意: 当缺失比例超过30%时,任何填充策略都会引入显著偏差。此时应优先排查数据源链路,而非依赖算法补全。

3.3 异常值检测与处理

缺陷密度数据中的异常值可能来自:晶圆边缘效应、设备突发故障、或人为录入错误。常用的检测方法包括:

方法 适用场景 阈值设定 优缺点
IQR(四分位距) 非正态分布、有离群点 Q1 - 1.5×IQR 或 Q3 + 1.5×IQR 稳健,不受极端值影响;但对小样本敏感
Z-Score 近似正态分布 |Z| > 3 计算简单;但受异常值本身影响均值与标准差
修正Z-Score(MAD) 含离群点的非正态分布 |M| > 3.5 基于中位数,稳健性优于Z-Score
移动窗口法 时间序列实时检测 均值 ± 3×滚动标准差 适应局部趋势;窗口大小需调参

以下为基于IQR与修正Z-Score的异常值检测实现:

import numpy as np

def detect_outliers_iqr(series: pd.Series, multiplier: float = 1.5) -> pd.Series:
    """使用IQR方法检测异常值,返回布尔掩码"""
    Q1 = series.quantile(0.25)
    Q3 = series.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - multiplier * IQR
    upper_bound = Q3 + multiplier * IQR
    return (series < lower_bound) | (series > upper_bound)

def detect_outliers_modified_zscore(series: pd.Series, threshold: float = 3.5) -> pd.Series:
    """使用修正Z-Score(基于MAD)检测异常值"""
    median = series.median()
    mad = np.median(np.abs(series - median))
    if mad == 0:
        return pd.Series([False] * len(series), index=series.index)
    modified_z = 0.6745 * (series - median) / mad
    return np.abs(modified_z) > threshold

# 应用示例
df_raw["is_outlier_iqr"] = detect_outliers_iqr(df_raw["defect_density"])
df_raw["is_outlier_mad"] = detect_outliers_modified_zscore(df_raw["defect_density"])

# 异常值处理:标记但不删除,保留给人工审核
df_raw["defect_density_clean"] = df_raw["defect_density"].copy()
df_raw.loc[df_raw["is_outlier_mad"], "defect_density_clean"] = np.nan
print(f"IQR检测到异常: {df_raw['is_outlier_iqr'].sum()} 条")
print(f"MAD检测到异常: {df_raw['is_outlier_mad'].sum()} 条")
最佳实践: 在良率监控场景中,异常值不应直接删除,而应标记为“待确认”状态。许多工艺异常本身就是报警信号,过早清洗会削弱监控灵敏度。

3.4 重复数据识别与去重

重复数据通常由以下原因引起:

  • 数据重传: MES系统因网络抖动重复发送同一检验记录。
  • 多系统冗余采集: SPC与MES同时记录了同一测量点的数据。
  • 人工补录: 操作员手动录入时误提交两次。

去重策略需基于业务主键:

def deduplicate_defect_data(df: pd.DataFrame) -> pd.DataFrame:
    """
    去除重复的缺陷记录
    主键: lot_id + wafer_id + step_name + timestamp(精确到秒)
    """
    # 标记完全重复的行
    dup_mask = df.duplicated(subset=["lot_id", "wafer_id", "step_name", "timestamp"], keep="first")
    print(f"发现 {dup_mask.sum()} 条重复记录")
    
    # 保留第一条,删除后续重复
    df_dedup = df[~dup_mask].copy()
    
    # 对于时间戳接近但非完全相同的记录(如1秒内重复),合并为单条
    df_dedup = df_dedup.sort_values("timestamp")
    df_dedup["time_diff"] = df_dedup.groupby(["lot_id", "wafer_id", "step_name"])["timestamp"].diff()
    
    # 时间差小于1秒且缺陷密度相同的视为重复
    near_dup = (df_dedup["time_diff"] < pd.Timedelta(seconds=1)) & \
               (df_dedup["defect_density"] == df_dedup["defect_density"].shift(1))
    print(f"近重复记录(1秒内): {near_dup.sum()} 条")
    
    df_final = df_dedup[~near_dup].drop(columns=["time_diff"])
    return df_final

df_clean = deduplicate_defect_data(df_raw)

3.5 数据清洗全流程架构

以下SVG图展示了从MES/SPC原始数据到清洗后标准数据集的完整管道:

数据采集与清洗全流程架构 数据采集与清洗管道架构 MES 系统 批次检验 / 缺陷计数 / 设备日志 SPC 系统 时间序列 / 控制图 / 参数监控 设备自动化 SECS/GEM / 实时事件流 数据采集与解析 REST API / 数据库连接 / 文件解析 / 格式统一 缺失值处理 前向填充 / 插值 批次均值 / 删除 异常值检测 IQR / Z-Score / MAD 移动窗口法 重复数据去重 主键去重 / 近重复合并 时间窗口聚合 数据质量检查与审计 完整性校验 / 一致性检查 / 清洗日志记录 清洗后标准数据集 → 看板与报警模块

3.6 清洗效果验证与监控

数据清洗完成后,必须通过以下指标验证清洗效果:

验证指标 计算公式 可接受范围 监控频率
缺失率 缺失值数 / 总记录数 < 5% 每批次
异常率 标记异常数 / 总记录数 < 2% 每日
重复率 去重记录数 / 原始记录数 < 1% 每批次
数据完整性 必填字段非空比例 100% 实时
自动化建议: 将上述验证指标集成到数据管道的监控看板中。当缺失率或异常率超过阈值时,自动触发数据源链路检查告警,而非等待下游看板出现异常。

通过本章的数据采集与清洗流程,原始MES/SPC数据被转化为高质量、标准化的时序数据集,为第4章的缺陷密度计算与实时看板渲染奠定了可靠的数据基础。