☰
python的先进制造技术工业场景模拟第一百三十六篇:读取数控连续加工尺寸时序,建立模型,预测后续加工零件尺寸漂移趋势。
2026/10/12 4:29:55 网站建设 项目流程

数控连续加工尺寸漂移预测——用时序模型捕捉"刀具在偷偷磨钝"

一、实际应用场景(真实痛点)

时间:周五凌晨 2:00,夜班车间。

地点:某轴承套圈厂数控车削车间,8 台数控车床沿墙一字排开,切削液泵嗡嗡作响。质检员小方揉着眼睛坐在三坐标检测室里,面前摊着一叠刚打出来的尺寸报告。

"又超差了。"小方把第 47 件的检测单拍在桌上,"内孔直径,前 20 件都在 φ50.012~50.018 之间,稳稳的。第 21 件开始慢慢变大,第 30 件到 φ50.025,第 40 件 φ50.031,第 47 件 φ50.038——图纸要求 φ50(0/+0.03),第 40 件以后全超。"

"刀具寿命不是设了 50 件换刀吗?"夜班班长问。

"设了,但这是第二批料。"小方翻记录,"第一批 50 件用的是正常棒料,尺寸一直很稳。换第二批料后,前 20 件也稳,后面就开始漂。我怀疑是第二批料硬度偏高,刀具磨损加速了——但问题是,我怎么知道从第几件开始尺寸会超差?难道每 10 件就全检一次?"

"你之前怎么处理的?"我问。

"首件检 + 每 10 件抽检 1 件。"小方说,"但这批料从第 21 件开始漂移,抽检碰巧第 20、30、40 件抽到了,发现了趋势。如果抽检落在 22、25、28 呢?就漏了。而且——就算我发现尺寸在变大,我也只能停机换刀。能不能提前预测:按当前磨损速度,第几件会超差?这样我可以在超差前主动换刀,而不是等抽检发现再停机。"

"你缺的不是抽检,是趋势预测。"我说,"你需要一个程序:读取数控系统连续加工的尺寸检测时序数据(件号、测量尺寸、时间戳),建立时序模型,预测后续 10~20 件的尺寸漂移趋势,给出'预计第 N 件超差'的预警。这样你在第 25 件时就知道第 38 件要超,提前安排换刀,零报废。"

"对!"小方一拍桌子,"最好还能区分:是刀具磨损在主导,还是热变形、还是料硬度波动?因为如果是料的问题,换刀没用,得调刀补。"

"这活儿我干过。"我说,"pandas 加载尺寸时序 CSV,numpy 做滑动窗口,scipy 做线性回归拟合磨损趋势线 + 指数平滑捕捉热变形分量,scikit-learn 的 SVR(支持向量回归)做非线性时序预测,用自相关性分析(ACF)判断时序特征,matplotlib 画尺寸漂移曲线+趋势预测带+残差分析图,networkx 建'误差源→尺寸漂移'归因网络。纯 Python,不依赖任何商业 SPC 软件。"

二、痛点分析(映射到滨州职业学院课程模型)

《先进制造技术》课程模块 本篇痛点对应

数控加工与 CAD/CAM 技术 加工精度控制、刀具磨损与尺寸漂移、热变形补偿

先进制造技术基础 机械制造工艺学:工艺系统误差(刀具磨损、热变形、力变形)

智能制造与数字孪生 数据驱动质量预测、虚拟量仪、数字孪生体状态估计

柔性制造系统 FMS 与先进生产管理 统计过程控制(SPC)、预防性维护触发

先进制造新模式 预测性质量控制、自适应加工

一句话总结:本篇解决一个"给定数控连续加工尺寸时序数据 → 分解趋势/季节/残差分量 → 建立时序预测模型 → 外推后续尺寸漂移 → 超差预警 + 归因分析"的时序预测问题,映射到课程就是数控加工精度控制中的刀具磨损与热变形漂移建模。

三、核心逻辑讲解(大白话)

3.1 问题本质:把尺寸漂移想成"温水煮青蛙"

零件尺寸在连续加工中慢慢变化,就像你泡在热水里感觉不到温度上升——等发现烫了,已经晚了。尺寸漂移的"元凶"通常有三个:

- 刀具磨损(主因):每切一件,刀尖就磨掉一丁点。积累到一定程度,切深变浅,孔就变大。这是线性漂移——匀速变大。

- 热变形:机床刚开机时冷,加工一会儿热了,主轴膨胀、导轨变形,尺寸偏移。这是指数曲线——开始快、后来慢、最终稳定。

- 材料硬度波动:换料时硬度变了,切削力变化导致弹性变形不同。这是阶跃变化——突然跳一下。

我们的目标:把这三个分量从尺寸数据里"拆"出来,分别建模,然后外推预测"按当前趋势,第几件会超差"。

3.2 业务逻辑 → 代码映射

输入: 数控加工尺寸时序 CSV

字段: 件号, 时间戳, 测量尺寸(mm), 理论尺寸(mm),

主轴转速(rpm), 进给速度(mm/min), 切削深度(mm)

│

▼ models.py (dataclass)

数据模型:

DimensionMeasurement(单件尺寸测量)

DriftComponent(漂移分量: 趋势/热变形/残差)

DriftPrediction(预测结果)

│

▼ data_loader.py (pandas)

数据加载器:

读 CSV → 计算偏差量 → 时间索引化

│

▼ trend_analyzer.py

趋势分析器 (scipy + sklearn):

1. 线性回归拟合刀具磨损趋势 (斜率 = 磨损速率)

2. 指数平滑分解热变形分量

3. SVR 时序预测 (RBF 核, 滑动窗口)

4. 自相关分析 (ACF) 判断时序依赖性

│

▼ anomaly_detector.py

异常/阶跃检测:

1. CUSUM 累积和控制图 (检测均值漂移)

2. 滑动 t-test (检测批次切换点)

│

▼ network_builder.py (networkx)

归因网络:

节点 = 误差源(刀具磨损/热变形/材料/其他)

边 = 贡献比例

│

▼ visualizer.py (matplotlib)

可视化:

1. 尺寸漂移曲线 + 趋势线 + 预测带

2. 残差分布直方图

3. 热变形指数衰减曲线

4. 超差预警标注

│

▼ 输出

磨损速率 + 预计超差件号 + 归因比例 + 换刀建议

3.3 为什么"抽检 + 控制图"不够用

维度 传统 SPC 控制图 时序预测模型

检测方式 事后发现(已超控制限) 事前预警(预测 N 件后超差)

趋势判断 肉眼看走势 模型量化磨损速率

多分量分解 无法分离 趋势+热变形+阶跃分别建模

预警能力 "已经超了" "第 38 件会超,现在换刀"

归因分析 无 区分刀具/热/材料

四、OOP 代码实现

4.1 项目结构

cnc_drift_predictor/

├── cnc_drift_predictor/

│ ├── __init__.py

│ ├── models.py # 数据模型

│ ├── data_loader.py # CSV加载

│ ├── trend_analyzer.py # 趋势分析与时序预测

│ ├── anomaly_detector.py # 异常/阶跃检测

│ ├── network_builder.py # 归因网络

│ ├── visualizer.py # 可视化

│ └── report_generator.py # 分析报告

├── tests/

│ ├── __init__.py

│ └── test_drift.py

├── sample_data/

│ └── cnc_dimensions.csv

├── results/

│ ├── drift_curve.png

│ ├── residual_hist.png

│ ├── thermal_curve.png

│ ├── attribution_network.png

│ └── drift_report.txt

├── run_drift.py

└── README.md

4.2 核心源码

<details><summary></summary>

"""数控加工尺寸时序数据模型 (dataclass)。"""

from dataclasses import dataclass, field

from typing import List, Optional, Dict

import numpy as np

@dataclass

class DimensionMeasurement:

"""单件尺寸测量记录。"""

piece_no: int

timestamp: str = ""

measured_size_mm: float = 0.0

nominal_size_mm: float = 0.0

spindle_speed_rpm: float = 0.0

feed_rate_mm_min: float = 0.0

cut_depth_mm: float = 0.0

@property

def deviation_mm(self) -> float:

"""尺寸偏差 = 实测 - 理论。"""

return self.measured_size_mm - self.nominal_size_mm

@dataclass

class DriftComponent:

"""漂移分量。"""

trend: np.ndarray = field(default_factory=lambda: np.array([])) # 线性趋势

thermal: np.ndarray = field(default_factory=lambda: np.array([])) # 热变形

step_change: np.ndarray = field(default_factory=lambda: np.array([])) # 阶跃

residual: np.ndarray = field(default_factory=lambda: np.array([])) # 残差

@property

def total(self) -> np.ndarray:

return self.trend + self.thermal + self.step_change + self.residual

@dataclass

class DriftPrediction:

"""漂移预测结果。"""

wear_rate_per_piece: float = 0.0 # 每件的磨损量 (mm/件)

predicted_deviations: np.ndarray = field(

default_factory=lambda: np.array([]))

exceed_piece_no: int = -1 # 预计超差件号 (-1=不超差)

exceed_probability: float = 0.0 # 超差概率

recommended_change_piece: int = -1 # 建议换刀件号

attribution: Dict[str, float] = field(default_factory=dict) # 归因

</details>

<details><summary></summary>

"""数控尺寸时序数据加载器 (pandas)。"""

import pandas as pd

import numpy as np

from pathlib import Path

from typing import List, Tuple

from .models import DimensionMeasurement

class DataLoader:

"""CSV 数据加载与预处理。"""

def load_csv(self, csv_path: str

) -> Tuple[np.ndarray, np.ndarray, List[DimensionMeasurement]]:

"""

加载 CSV。

返回: piece_nos, deviations, measurements

"""

df = pd.read_csv(csv_path)

required = ["piece_no", "measured_size_mm"]

missing = [c for c in required if c not in df.columns]

if missing:

raise ValueError(f"CSV 缺少列: {missing}")

# 填充理论尺寸

if "nominal_size_mm" not in df.columns:

df["nominal_size_mm"] = df["measured_size_mm"].median()

df = df.sort_values("piece_no")

measurements = []

for _, row in df.iterrows():

m = DimensionMeasurement(

piece_no=int(row["piece_no"]),

timestamp=str(row.get("timestamp", "")),

measured_size_mm=float(row["measured_size_mm"]),

nominal_size_mm=float(row.get("nominal_size_mm",

row["measured_size_mm"])),

spindle_speed_rpm=float(row.get("spindle_speed_rpm", 0)),

feed_rate_mm_min=float(row.get("feed_rate_mm_min", 0)),

cut_depth_mm=float(row.get("cut_depth_mm", 0)),

)

measurements.append(m)

piece_nos = np.array([m.piece_no for m in measurements])

deviations = np.array([m.deviation_mm for m in measurements])

return piece_nos, deviations, measurements

def generate_synthetic_data(self, n_pieces: int = 60,

change_point: int = 25

) -> Tuple[np.ndarray, np.ndarray]:

"""

生成合成尺寸漂移数据 (教学用)。

物理模型:

偏差 = 刀具磨损(线性) + 热变形(指数衰减) + 材料阶跃 + 噪声

"""

np.random.seed(42)

piece_nos = np.arange(1, n_pieces + 1)

# 1. 刀具磨损 (线性, 约 0.0008 mm/件)

wear = np.linspace(0, 0.045, n_pieces)

# 2. 热变形 (指数, 前10件影响大)

thermal = 0.012 * np.exp(-np.arange(n_pieces) / 8)

# 3. 材料阶跃 (第 change_point 件后硬度偏高 → 尺寸偏大)

material_step = np.zeros(n_pieces)

material_step[change_point - 1:] = 0.015

# 4. 噪声

noise = np.random.normal(0, 0.003, n_pieces)

# 总偏差

deviations = wear + thermal + material_step + noise

# 保存

sample_dir = Path("sample_data")

sample_dir.mkdir(exist_ok=True)

records = []

for i, pn in enumerate(piece_nos):

nominal = 50.0

measured = nominal + deviations[i]

records.append({

"piece_no": pn,

"timestamp": f"2026-10-09T{8 + i//10:02d}:{i%60:02d}:00",

"measured_size_mm": measured,

"nominal_size_mm": nominal,

"spindle_speed_rpm": 2000 + np.random.randint(-100, 100),

"feed_rate_mm_min": 150 + np.random.randint(-10, 10),

"cut_depth_mm": 1.5,

})

df = pd.DataFrame(records)

df.to_csv(sample_dir / "cnc_dimensions.csv", index=False)

return piece_nos, deviations

</details>

<details><summary></summary>

"""尺寸漂移趋势分析与时序预测 (scipy + sklearn)。"""

import numpy as np

from typing import Dict, Tuple

from scipy import stats

from sklearn.svm import SVR

from sklearn.preprocessing import StandardScaler

from sklearn.pipeline import Pipeline

from sklearn.metrics import mean_absolute_error

from .models import DriftComponent, DriftPrediction

class TrendAnalyzer:

"""

尺寸漂移分析:

1. 线性回归 → 磨损速率

2. 指数平滑 → 热变形分量

3. SVR 时序预测 → 外推

4. ACF 自相关 → 时序特征

"""

def __init__(self, tolerance_mm: float = 0.030):

self.tolerance_mm = tolerance_mm

self.wear_rate_: float = 0.0

self.intercept_: float = 0.0

self.svr_pipeline_ = None

def decompose(self, piece_nos: np.ndarray,

deviations: np.ndarray) -> DriftComponent:

"""分解漂移分量。"""

n = len(deviations)

comp = DriftComponent()

# 1. 线性趋势 (刀具磨损)

slope, intercept, _, _, _ = stats.linregress(piece_nos, deviations)

self.wear_rate_ = slope

self.intercept_ = intercept

comp.trend = slope * piece_nos + intercept - np.mean(

slope * piece_nos + intercept)

# 2. 残差 (去除趋势后)

detrended = deviations - (slope * piece_nos + intercept)

# 3. 热变形 (指数平滑残差的前部)

comp.thermal = np.zeros(n)

alpha = 0.3

smoothed = [detrended[0]]

for i in range(1, n):

smoothed.append(alpha * detrended[i] + (1 - alpha) * smoothed[i-1])

comp.thermal = np.array(smoothed) * 0.4 # 取部分作为热变形

# 4. 阶跃检测 (剩余残差的大跳变)

residual_after_thermal = detrended - comp.thermal

comp.step_change = np.zeros(n)

diff = np.abs(np.diff(residual_after_thermal))

threshold = np.std(diff) * 2.5

for i in range(1, len(diff)):

if diff[i] > threshold:

comp.step_change[i:] += residual_after_thermal[i] - residual_after_thermal[i-1]

# 5. 最终残差

comp.residual = deviations - (comp.trend + comp.thermal

+ comp.step_change)

return comp

def predict_drift(self, piece_nos: np.ndarray,

deviations: np.ndarray,

forecast_pieces: int = 20

) -> DriftPrediction:

"""预测后续尺寸漂移。"""

n = len(deviations)

prediction = DriftPrediction()

# 磨损速率

prediction.wear_rate_per_piece = self.wear_rate_

# SVR 时序预测

# 构造特征: [件号, 偏差_滞后1, 偏差_滞后2, 偏差_滞后3]

max_lag = 3

X = []

y = []

for i in range(max_lag, n):

features = [piece_nos[i]]

for lag in range(1, max_lag + 1):

features.append(deviations[i - lag])

X.append(features)

y.append(deviations[i])

X = np.array(X)

y = np.array(y)

self.svr_pipeline_ = Pipeline([

('scaler', StandardScaler()),

('svr', SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.001))

])

if len(X) > 5:

self.svr_pipeline_.fit(X, y)

# 外推预测

last_deviations = list(deviations[-max_lag:])

forecast_nos = np.arange(n + 1, n + forecast_pieces + 1)

forecast_devs = []

for fn in forecast_nos:

features = [fn] + last_deviations[-max_lag:]

pred = self.svr_pipeline_.predict([features])[0]

forecast_devs.append(pred)

last_deviations.append(pred)

prediction.predicted_deviations = np.array(forecast_devs)

# 超差判断

all_deviations = np.concatenate([deviations, forecast_devs])

all_nos = np.concatenate([piece_nos, forecast_nos])

exceed_mask = np.abs(all_deviations) > self.tolerance_mm

if np.any(exceed_mask):

first_exceed_idx = np.where(exceed_mask)[0][0]

prediction.exceed_piece_no = int(all_nos[first_exceed_idx])

# 超差概率 (基于预测偏差与公差的距离)

margin = self.tolerance_mm - abs(forecast_devs[0])

prediction.exceed_probability = max(0, min(1,

1 - margin / self.tolerance_mm * 0.5))

# 建议换刀件号 (在超差前 3 件)

if prediction.exceed_piece_no > 0:

prediction.recommended_change_piece = max(

int(piece_nos[-1]),

prediction.exceed_piece_no - 3)

# 归因分析

total_var = np.var(deviations)

if total_var > 0:

comp = self.decompose(piece_nos, deviations)

prediction.attribution = {

"刀具磨损": min(1.0, np.var(comp.trend) / total_var),

"热变形": min(1.0, np.var(comp.thermal) / total_var),

"材料/阶跃": min(1.0, np.var(comp.step_change) / total_var),

"随机噪声": min(1.0, np.var(comp.residual) / total_var),

}

# 归一化

total_attr = sum(prediction.attribution.values())

if total_attr > 0:

prediction.attribution = {

k: v / total_attr

for k, v in prediction.attribution.items()

}

return prediction

def acf(self, deviations: np.ndarray,

max_lag: int = 20) -> np.ndarray:

"""自相关函数。"""

n = len(deviations)

mean = np.mean(deviations)

c0 = np.sum((deviations - mean) ** 2) / n

acf_vals = np.zeros(max_lag + 1)

acf_vals[0] = 1.0

for lag in range(1, max_lag + 1):

if lag >= n:

break

num = np.sum((deviations[:-lag] - mean) *

(deviations[lag:] - mean)) / (n - lag)

acf_vals[lag] = num / (c0 + 1e-8)

return acf_vals

</details>

<details><summary></summary>

"""异常/阶跃检测 (CUSUM + 滑动 t-test)。"""

import numpy as np

from typing import List, Tuple

class AnomalyDetector:

"""

检测尺寸时序中的均值漂移和异常点。

"""

def __init__(self, cusum_threshold: float = 5.0,

t_test_window: int = 10):

self.cusum_threshold = cusum_threshold

self.t_test_window = t_test_window

def cusum(self, deviations: np.ndarray) -> List[int]:

"""

CUSUM 累积和控制图。

返回检测到的漂移起始点索引列表。

"""

mean = np.mean(deviations)

std = np.std(deviations, ddof=1)

if std == 0:

return []

# 标准化

z = (deviations - mean) / (std + 1e-8)

# CUSUM (单方向, 检测正向漂移)

cusum_pos = np.zeros(len(z))

for i in range(1, len(z)):

cusum_pos[i] = max(0, cusum_pos[i-1] + z[i] - 0.5)

# 检测超过阈值的位置

threshold = self.cusum_threshold

drift_starts = []

in_drift = False

for i, val in enumerate(cusum_pos):

if val > threshold and not in_drift:

drift_starts.append(i)

in_drift = True

elif val <= threshold:

in_drift = False

return drift_starts

def sliding_t_test(self, deviations: np.ndarray,

window_size: int = None) -> List[Tuple[int, float]]:

"""

滑动 t-test 检测均值变化点。

返回: [(变化点索引, p-value), ...]

"""

if window_size is None:

window_size = self.t_test_window

n = len(deviations)

change_points = []

for i in range(window_size, n - window_size):

before = deviations[i - window_size:i]

after = deviations[i:i + window_size]

if len(before) < 3 or len(after) < 3:

continue

# Welch's t-test

mean1, mean2 = np.mean(before), np.mean(after)

var1 = np.var(before, ddof=1)

var2 = np.var(after, ddof=1)

denom = np.sqrt(var1/window_size + var2/window_size)

if denom == 0:

continue

t_stat = (mean1 - mean2) / denom

df = (var1/window_size + var2/window_size)**2 / (

(var1/window_size)**2/(window_size-1) +

(var2/window_size)**2/(window_size-1) + 1e-8)

# 近似 p-value (双尾)

from scipy import stats

p_value = 2 * (1 - stats.t.cdf(abs(t_stat), df))

if p_value < 0.01:

change_points.append((i, p_value))

return change_points

</details>

<details><summary></summary>

"""尺寸漂移归因网络 (networkx)。"""

import networkx as nx

import numpy as np

from typing import Dict

class AttributionNetworkBuilder:

"""

构建误差源归因网络:

节点 = 误差源 + 尺寸偏差

边权重 = 方差贡献比例

"""

def __init__(self, attribution: Dict[str, float]):

self.attribution = attribution

self.graph_ = nx.DiGraph()

def build(self) -> nx.DiGraph:

"""构建网络。"""

self.graph_.clear()

# 添加源节点

for source, weight in self.attribution.items():

self.graph_.add_node(source, node_type="source",

weight=weight)

# 添加目标节点

self.graph_.add_node("尺寸漂移", node_type="target")

# 添加边

for source, weight in self.attribution.items():

self.graph_.add_edge(source, "尺寸漂移", weight=weight)

return self.graph_

</details>

<details><summary></summary>

"""尺寸漂移可视化 (matplotlib + networkx)。"""

import numpy as np

import matplotlib.pyplot as plt

import networkx as nx

from pathlib import Path

from typing import Dict

plt.rcParams["font.sans-serif"] = ["SimHei", "WenQuanYi Micro Hei", "DejaVu Sans"]

plt.rcParams["axes.unicode_minus"] = False

class DriftVisualizer:

def __init__(self, results_dir: str = "results"):

self.results_dir = Path(results_dir)

self.results_dir.mkdir(exist_ok=True)

def drift_curve(self, piece_nos: np.ndarray,

deviations: np.ndarray,

trend_line: np.ndarray = None,

predicted_nos: np.ndarray = None,

predicted_devs: np.ndarray = None,

tolerance_mm: float = 0.030,

exceed_piece: int = -1):

"""尺寸漂移曲线 + 趋势 + 预测 + 超差标注。"""

fig, ax = plt.subplots(figsize=(12, 6))

# 实际偏差

ax.plot(piece_nos, deviations, 'b-o', markersize=4,

linewidth=1.5, label="实际偏差", alpha=0.8)

# 趋势线

if trend_line is not None:

ax.plot(piece_nos, trend_line, 'r--', linewidth=2,

label="磨损趋势线")

# 预测

if predicted_nos is not None and predicted_devs is not None:

ax.plot(predicted_nos, predicted_devs, 'g--o', markersize=4,

linewidth=1.5, label="预测偏差", alpha=0.7)

# 公差带

ax.axhline(y=tolerance_mm, color='orange', linestyle='-',

linewidth=1, alpha=0.7, label=f"+公差 {tolerance_mm}mm")

ax.axhline(y=-tolerance_mm, color='orange', linestyle='-',

linewidth=1, alpha=0.7, label=f"-公差 {tolerance_mm}mm")

ax.axhline(y=0, color='black', linewidth=0.5)

# 超差标注

if exceed_piece > 0:

ax.axvline(x=exceed_piece, color='red', linestyle=':',

linewidth=2, alpha=0.7,

label=f"预计超差: 第{exceed_piece}件")

ax.set_xlabel("件号", fontsize=12)

ax.set_ylabel("尺寸偏差 (mm)", fontsize=12)

ax.set_title("数控连续加工尺寸漂移趋势与预测",

fontsize=14, fontweight='bold')

ax.legend(fontsize=10)

ax.grid(alpha=0.3)

plt.tight_layout()

plt.savefig(self.results_dir / "drift_curve.png",

dpi=150, bbox_inches="tight")

plt.close()

def residual_hist(self, residuals: np.ndarray):

"""残差分布直方图。"""

fig, ax = plt.subplots(figsize=(7, 5))

ax.hist(residuals, bins=20

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询