周三早上,增材制造实验室,FDM 桌面级 3D 打印机刚跑完一轮 ABS 材料翘边对比试验。
"这批测试件,12 组参数组合,每组打 5 件,翘边率从 0% 到 80% 不等,"实验室助教小林指着打印平台,"你能看到床温 80℃、环境温度 25℃、首层速度 30mm/s、层高 0.3mm 那组翘了 4 件,但床温 100℃、关着机箱门、首层速度 15mm/s 的那组一件没翘。问题是——到底是哪个参数在起作用?是床温、是环境温度、还是首层速度?它们之间有没有组合效应?"
我点开他导出的 CSV。
"这表里有什么?"小林问。
"每组试验的编号、材料类型(ABS/PLA/PA)、喷嘴温度(℃)、热床温度(℃)、环境温度(℃)、首层打印速度(mm/s)、首层层高(mm)、填充密度(%)、有无 enclosure(机箱门)、冷却风扇转速(%)、材料是否烘干、翘边结果(翘边/未翘边),以及翘边发生位置(角落/边缘/整体),"我指着屏幕,"但它就是一张试验记录表。你能看到床温高+关门的组翘边少,但说不清:床温从 80℃ 提到 100℃ 贡献了多少? enclosure 是必要条件还是充分条件?首层速度从 30 降到 15 是不是边际递减?下次换 PA 材料,这套参数还能用吗?"
"我就想干一件事,"小林说,"给我一个程序:把这堆试验数据灌进去,自动告诉我'翘边的 Top 5 关键参数',最好还能量化'哪种参数组合最容易翘边'。我拿去跟学生说'做 ABS 打印,床温必须 ≥95℃ 且首层速度 ≤20mm/s,否则翘边概率 >60%',而不是靠试。"
"比如材料类型决定了收缩应力大小,床温决定了第一层附着力,环境温度决定了冷却梯度——这三个是主因,但首层速度和层高会放大或缩小效应,"我接话,"用 pandas 做参数编码和特征工程,scikit-learn 的随机森林分类做特征重要性+决策边界,scipy 做卡方检验确认离散参数的显著性,matplotlib 画参数组合散点+翘边分布+ROC+混淆矩阵+特征重要性+参数交互热图+因果网络,networkx 建'工艺参数→翘边风险'的因果链路。"
"对,"小林点头,"别给我黑盒,要能说清楚'为什么床温比喷嘴温度影响大'。我看得懂,能拿去跟学生讲'ABS 的玻璃化转变温度 105℃,床温不到 100℃ 第一层就处于玻璃态收缩,粘不住'。"
"用 pandas 做特征工程,随机森林分类+卡方双管齐下,scipy 做显著性检验,matplotlib 出 6 图+报告,存 results/,"我开工程,"数据自包含,合成一批含 10 个工艺参数、180 组试验的翘边数据,下载就能跑。"
敲了行原型:
# 翘边 = 第一层收缩应力 > 附着力
# 收缩应力 = f(材料收缩率, 冷却梯度, 层厚)
# 附着力 = f(床温, 环境温度, 首层速度, 有无 enclosure)
# 随机森林: 捕捉非线性 + 参数组合效应
# 卡方检验: 确认离散参数(enclosure/材料)的显著性
"完整版 OOP 封好,"我说,"数据加载器、特征工程器、翘边分类器(随机森林+卡方)、交互分析器、因果网络、可视化器,输出关键参数排序+参数组合风险矩阵+6图+报告。"
小林凑近看:"那以后看报告:随机森林特征重要性排第一的是'热床温度'(26%),第二是'环境温度'(18%),第三是'首层打印速度'(15%),第四是'有无 enclosure'(12%),第五是'材料类型'(11%)。卡方确认 enclosure(p<0.001)和材料类型(p=0.003)显著。交互热图显示:床温<90℃ 且 环境温度<28℃ 时翘边率 78%。结论:ABS 打印必须床温≥95℃ + 关门 + 首层速度≤20mm/s,翘边率可压到 5%以下。"
"对,"我接话,"3D 打印不是'打出来就行',是'每一层的热历史都要可控'。数字孪生里挂打印质量节点,这套就是工艺员的'翘边预警器'。"
一、实际应用场景(真实痛点)
场景设定:FDM/FFF 3D 打印实验室或小型生产单元,使用 ABS、PLA、PA 等材料打印功能件。翘边(warping/curling)是首层冷却收缩导致的最常见缺陷,直接影响零件尺寸精度和后续堆叠成功率。
现场原话(叙事化):
"不是我们调不好,"小林说,"是参数太多了。喷嘴温度、床温、环境温度、首层速度、层高、填充、风扇、enclosure——你让我调哪个?上次我把床温从 80 提到 100,翘边好了,但表面拉丝严重。后来发现关门(enclosure)效果更明显,床温 90℃+关门 翘边率就 3%。那我到底该提床温还是关门?哪个性价比高?换 PA 材料又不一样了。"
"最坑的是组合效应,"小林补充,"床温 85℃ 时开风扇翘边率 60%,关风扇就 15%。但床温 100℃ 时开不开风扇都差不多。你单看床温或单看风扇都看不出问题,得两个放一起看。以前没人系统做这个分析,全凭经验试。"
核心矛盾:"多参数试错 + 凭经验调参" 与 "数据驱动的参数重要性排序 + 卡方显著性 + 组合风险矩阵 + 可解释工艺窗口" 之间的断层。
二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)
《先进制造技术》模块 本篇痛点对应
增材制造(3D打印)技术:FDM 工艺原理与缺陷 翘边机理+工艺参数优化
先进制造技术基础:材料热物理性能 收缩应力+玻璃化转变
智能制造与数字孪生:工艺数据驱动 试验数据挖掘+质量预测
柔性制造系统FMS:过程质量控制 参数组合风险分级
一句话总结:我们需要一个"3D打印翘边试验数据→特征工程+随机森林分类+卡方检验+交互热图+因果网络程序",用
"pandas" 做数据清洗/参数编码,
"scikit-learn" 随机森林分类+特征重要性,
"scipy" 卡方检验+显著性验证,
"matplotlib" 画参数分布+ROC+混淆矩阵+特征重要性+交互热图+因果网络,
"networkx" 建因果链路,实现从"凭经验试参数"到"数据归因+组合风险量化+工艺窗口推荐"。
三、核心逻辑讲解(大白话)
3.1 问题本质:把翘边想成"披萨饼底卷边"
把 3D 打印翘边想成披萨饼底在烤盘上卷起来:
* 翘边 = 饼底边缘翘起来了,粘不住烤盘
* 收缩应力 = 饼底受热膨胀再冷却收缩,边缘往外拉
* 附着力 = 饼底和烤盘之间的粘合力
* 翘边条件 = 收缩应力 > 附着力
* 床温 = 烤盘预热温度(越热饼底越软越粘)
* 环境温度 = 烤箱里整体温度(越热冷却越慢,收缩越小)
* 首层速度 = 你铺饼底的快慢(越慢铺得越实,粘得越牢)
* Enclosure = 烤箱门(关门=保温,开门=冷风灌进来)
* 材料类型 = 面饼种类(有的面本身弹性大,有的容易缩)
* 分类器 = 看了 180 个"参数组合→翘没翘"的试验结果,学会了"什么组合最容易翘"
3.2 业务逻辑 → 代码映射
加载3D打印翘边试验数据
│
▼ WarpDataLoader (pandas)
读取表:
试验编号, 材料类型, 喷嘴温度, 热床温度,
环境温度, 首层速度, 首层层高, 填充密度,
有无enclosure, 冷却风扇转速, 材料是否烘干,
翘边结果(翘边/未翘边), 翘边位置
│
▼ WarpFeatureEngineer (pandas + numpy)
特征工程:
材料类型 → one-hot (ABS/PLA/PA)
enclosure → 二值(0/1)
材料烘干 → 二值(0/1)
数值特征 → 标准化
特征矩阵 X, 目标 y(翘边=1/未翘边=0)
│
▼ WarpClassifier (scikit-learn + scipy)
双模型归因:
随机森林分类 → 特征重要性(全局)
卡方检验(scipy) → 离散参数显著性
逻辑回归(基线) → 效应方向
│
▼ WarpInteractionAnalyzer (numpy)
交互效应:
床温 × 环境温度 网格 → 翘边率热图
首层速度 × 风扇转速 网格 → 翘边率热图
│
▼ WarpCausalNetwork (networkx)
因果网络:
节点: 10个工艺参数 + 翘边风险
边: 影响方向 + 重要性权重
│
▼ WarpVisualizer (matplotlib)
可视化:
1. 参数组合散点图(颜色=翘边)
2. 翘边率随关键参数变化曲线
3. ROC曲线
4. 混淆矩阵
5. 特征重要性柱状图
6. 床温×环境温度交互热图
7. 因果网络图
│
▼ SyntheticWarpData (numpy)
合成数据:
10参数 × 180组试验, 含噪声和交互效应
可复现
3.3 为什么不能只看"哪个参数影响最大"
视角 问题
单因素改参数 忽略了参数间的交互(床温×环境)
凭经验设窗口 不同材料完全不同
随机森林 捕捉非线性+组合效应
卡方检验 确认离散参数(材料/enclosure)的真实贡献
交互热图 揭示"两个参数一起调才有效"
3.4 分析前后对比
维度 传统方式 本程序
参数选择 凭经验逐个试 随机森林+卡方双验证排序
工艺窗口 "大概 100℃ 差不多" 床温≥95℃ + 关门 + 首层≤20mm/s
组合效应 看不见 热图量化风险矩阵
置信度 "应该没问题" p值+交叉验证 AUC
输出 报废件 6图+报告+工艺窗口推荐
四、OOP 代码实现
4.1 项目结构
warp_risk_analyzer/
├── warp_risk_analyzer/
│ ├── __init__.py
│ ├── warp_data_loader.py # 数据加载
│ ├── warp_feature_engineer.py # 特征工程
│ ├── warp_classifier.py # 翘边分类+卡方
│ ├── warp_interaction_analyzer.py # 交互分析
│ ├── warp_causal_network.py # 因果网络
│ ├── visualizer.py # 可视化
│ └── synthetic_warp_data.py # 合成数据
├── tests/
│ ├── __init__.py
│ └── test_warp.py
├── results/
│ ├── param_scatter.png
│ ├── warp_rate_curve.png
│ ├── roc_curve.png
│ ├── confusion_matrix.png
│ ├── feature_importance.png
│ ├── interaction_heatmap.png
│ ├── causal_network.png
│ ├── warp_detail.csv
│ └── warp_report.txt
└── run_warp.py
4.2 核心源码
<details>
<summary></summary>
"""3D打印翘边试验数据加载器"""
import pandas as pd
from pathlib import Path
from typing import Optional
class WarpDataLoader:
"""读取翘边试验数据集"""
def __init__(self, filepath: str = "warp_test_data.csv",
encoding: str = "utf-8"):
self.filepath = Path(filepath)
self.encoding = encoding
def load(self) -> pd.DataFrame:
if not self.filepath.exists():
raise FileNotFoundError(self.filepath)
df = pd.read_csv(self.filepath, encoding=self.encoding)
req = ["test_id", "material", "nozzle_temp_c", "bed_temp_c",
"ambient_temp_c", "first_layer_speed_mm_s",
"first_layer_height_mm", "infill_percent",
"has_enclosure", "fan_speed_percent",
"material_dried", "warp_result", "warp_location"]
miss = [c for c in req if c not in df.columns]
if miss:
raise ValueError(f"缺列: {miss}")
# 类型转换
num_cols = ["nozzle_temp_c", "bed_temp_c", "ambient_temp_c",
"first_layer_speed_mm_s", "first_layer_height_mm",
"infill_percent", "fan_speed_percent"]
for c in num_cols:
df[c] = pd.to_numeric(df[c], errors="coerce")
# 二值化目标
df["is_warp"] = (df["warp_result"] == "翘边").astype(int)
# 二值化离散特征
df["has_enclosure"] = df["has_enclosure"].map(
{"是": 1, "否": 0, True: 1, False: 0}).fillna(0).astype(int)
df["material_dried"] = df["material_dried"].map(
{"是": 1, "否": 0, True: 1, False: 0}).fillna(0).astype(int)
df = df.dropna(subset=["bed_temp_c", "is_warp"]).reset_index(drop=True)
return df
def summary(self, df: pd.DataFrame) -> str:
s = f"试验组数: {len(df)}\n"
s += f"翘边率: {df['is_warp'].mean()*100:.1f}%\n"
s += f"材料分布: {df['material'].value_counts().to_dict()}\n"
s += f"有enclosure: {df['has_enclosure'].sum()}/{len(df)}"
return s
</details>
<details>
<summary></summary>
"""特征工程 (pandas + numpy)"""
import numpy as np
import pandas as pd
from typing import Tuple, Dict, List
class WarpFeatureEngineer:
"""构建特征矩阵"""
def __init__(self):
self.material_categories_ = None
self.feature_names_ = None
def fit(self, df: pd.DataFrame):
self.material_categories_ = sorted(df["material"].unique())
def transform(self, df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray]:
X_list = []
for _, row in df.iterrows():
features = []
# 材料 one-hot
for mat in self.material_categories_:
features.append(1.0 if row["material"] == mat else 0.0)
# 离散参数
features.append(float(row["has_enclosure"]))
features.append(float(row["material_dried"]))
# 数值特征
features.append(float(row["nozzle_temp_c"]) / 10.0)
features.append(float(row["bed_temp_c"]) / 10.0)
features.append(float(row["ambient_temp_c"]) / 10.0)
features.append(float(row["first_layer_speed_mm_s"]))
features.append(float(row["first_layer_height_mm"]) * 100)
features.append(float(row["infill_percent"]) / 10.0)
features.append(float(row["fan_speed_percent"]) / 10.0)
X_list.append(features)
X = np.array(X_list)
y = df["is_warp"].values
self.feature_names_ = (
[f"mat_{m}" for m in self.material_categories_] +
["enclosure", "dried", "nozzle_temp", "bed_temp",
"ambient_temp", "first_layer_speed", "first_layer_height",
"infill", "fan_speed"]
)
return X, y
def get_feature_names(self) -> List[str]:
return self.feature_names_
</details>
<details>
<summary></summary>
"""翘边分类器 (scikit-learn + scipy)"""
import numpy as np
from typing import Dict
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import (roc_auc_score, roc_curve, confusion_matrix,
accuracy_score, classification_report)
from scipy.stats import chi2_contingency
import warnings
warnings.filterwarnings("ignore")
class WarpClassifier:
"""随机森林分类 + 卡方检验"""
def __init__(self, random_state: int = 42):
self.random_state = random_state
self.rf = RandomForestClassifier(
n_estimators=200, max_depth=8, class_weight="balanced",
random_state=random_state)
self.lr = LogisticRegression(class_weight="balanced",
random_state=random_state)
self.feature_names_ = None
self.rf_importance_ = None
def fit(self, X: np.ndarray, y: np.ndarray, feature_names: list):
self.feature_names_ = feature_names
self.rf.fit(X, y)
self.lr.fit(X, y)
importances = self.rf.feature_importances_
self.rf_importance_ = dict(
sorted(zip(feature_names, importances),
key=lambda x: x[1], reverse=True))
def evaluate(self, X: np.ndarray, y: np.ndarray) -> Dict:
skf = StratifiedKFold(n_splits=5, shuffle=True,
random_state=self.random_state)
cv_auc = cross_val_score(self.rf, X, y, cv=skf, scoring="roc_auc")
cv_f1 = cross_val_score(self.rf, X, y, cv=skf, scoring="f1")
y_pred = self.rf.predict(X)
y_prob = self.rf.predict_proba(X)[:, 1]
fpr, tpr, _ = roc_curve(y, y_prob)
cm = confusion_matrix(y, y_pred)
return {
"cv_auc_mean": round(cv_auc.mean(), 3),
"cv_auc_std": round(cv_auc.std(), 3),
"cv_f1_mean": round(cv_f1.mean(), 3),
"train_accuracy": round(accuracy_score(y, y_pred), 3),
"fpr": fpr,
"tpr": tpr,
"y_pred": y_pred,
"y_prob": y_prob,
"confusion_matrix": cm,
}
def chi_square_test(self, df: "pd.DataFrame") -> Dict:
"""卡方检验: 离散参数与翘边的关联显著性"""
from scipy import stats
import pandas as pd
results = {}
discrete_params = ["material", "has_enclosure", "material_dried"]
for param in discrete_params:
if param not in df.columns:
continue
ct = pd.crosstab(df[param], df["is_warp"])
if ct.shape[0] >= 2 and ct.shape[1] >= 2:
chi2, p, dof, expected = stats.chi2_contingency(ct)
results[param] = {
"chi2": round(chi2, 3),
"p_value": round(p, 6),
"significant": p < 0.05,
"dof": dof,
}
return results
def predict_proba_warp(self, X: np.ndarray) -> np.ndarray:
return self.rf.predict_proba(X)[:, 1]
def get_top_factors(self, n: int = 5) -> list:
return list(self.rf_importance_.items())[:n]
</details>
<details>
<summary></summary>
"""交互效应分析 (numpy)"""
import numpy as np
import pandas as pd
from typing import Dict
class WarpInteractionAnalyzer:
"""分析两个参数的交互效应对翘边率的影响"""
def __init__(self):
pass
def warp_rate_heatmap(self, df: pd.DataFrame,
x_col: str, y_col: str,
x_bins: int = 5, y_bins: int = 5) -> Dict:
"""生成翘边率交互热图数据"""
x_edges = np.linspace(df[x_col].min(), df[x_col].max(), x_bins + 1)
y_edges = np.linspace(df[y_col].min(), df[y_col].max(), y_bins + 1)
heatmap = np.zeros((y_bins, x_bins))
counts = np.zeros((y_bins, x_bins))
for i in range(y_bins):
for j in range(x_bins):
mask = (
(df[x_col] >= x_edges[j]) & (df[x_col] < x_edges[j+1]) &
(df[y_col] >= y_edges[i]) & (df[y_col] < y_edges[i+1])
)
if mask.sum() > 0:
heatmap[i, j] = df[mask]["is_warp"].mean() * 100
counts[i, j] = mask.sum()
else:
heatmap[i, j] = np.nan
return {
"heatmap": heatmap,
"counts": counts,
"x_edges": x_edges,
"y_edges": y_edges,
}
</details>
<details>
<summary></summary>
"""翘边因果网络 (networkx)"""
import networkx as nx
import numpy as np
from typing import Dict
class WarpCausalNetwork:
"""构建工艺参数→翘边风险因果网络"""
def __init__(self):
self.G = nx.DiGraph()
def build(self, feature_importance: Dict,
chi2_results: Dict,
df: pd.DataFrame) -> nx.DiGraph:
self.G.clear()
self.G.add_node("翘边风险", ntype="defect", weight=2.0)
for feat, imp in feature_importance.items():
chi2_sig = chi2_results.get(feat, {}).get("significant", False)
self.G.add_node(feat, ntype="param",
weight=imp, sig=chi2_sig)
edge_weight = imp * 1.2 if chi2_sig else imp * 0.6
self.G.add_edge(feat, "翘边风险", weight=round(edge_weight, 4))
return self.G
</details>
<details>
<summary></summary>
"""可视化 (matplotlib + networkx)"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
import networkx as nx
plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
class WarpVisualizer:
def __init__(self, results_dir: str = "results"):
self.results_dir = Path(results_dir)
self.results_dir.mkdir(exist_ok=True)
def param_scatter(self, df: pd.DataFrame):
"""参数组合散点图(颜色=翘边)"""
fig, axes = plt.subplots(2, 3, figsize=(16, 10))
axes = axes.flatten()
params = ["bed_temp_c", "ambient_temp_c", "first_layer_speed_mm_s",
"nozzle_temp_c", "first_layer_height_mm", "fan_speed_percent"]
titles = ["热床温度", "环境温度", "首层速度", "喷嘴温度", "首层层高", "风扇转速"]
for i, (param, title) in enumerate(zip(params, titles)):
if i >= 6:
break
ax = axes[i]
ok = df[df["is_warp"] == 0]
bad = df[df["is_warp"] == 1]
ax.scatter(ok[param], [0]*len(ok), c="#27AE60", alpha=0.6,
s=30, label="未翘边", zorder=3)
ax.scatter(bad[param], [1]*len(bad), c="#E74C3C", alpha=0.6,
s=30, label="翘边", zorder=3)
ax.set_xlabel(title)
ax.set_yticks([0, 1])
ax.set_yticklabels(["未翘边", "翘边"])
ax.set_title(f"{title} vs 翘边", fontsize=11)
ax.grid(axis="x", alpha=0.3)
if i == 0:
ax.legend(fontsize=8)
plt.suptitle("关键参数 vs 翘边结果", fontsize=14, fontweight="bold")
plt.tight_layout()
plt.savefig(self.results_dir/"param_scatter.png", dpi=150, bbox_inches="tight")
plt.close()
def warp_rate_curve(self, df: pd.DataFrame):
"""翘边率随关键参数变化曲线"""
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
params = ["bed_temp_c", "ambient_temp_c", "first_layer_speed_mm_s"]
xlabels = ["热床温度 (℃)", "环境温度 (℃)", "首层速度 (mm/s)"]
for i, (param, xlabel) in enumerate(zip(params, xlabels)):
ax = axes[i]
df_copy = df.copy()
df_copy["bin"] = pd.cut(df_copy[param], bins=8)
warp_rates = df_copy.groupby("bin")["is_warp"].mean() * 100
bin_centers = [interval.mid for interval in warp_rates.index]
ax.plot(bin_centers, warp_rates.values, "o-", color="#E74C3C",
linewidth=2, markersize=6)
ax.fill_between(bin_centers, warp_rates.values, alpha=0.2,
color="#E74C3C")
ax.set_xlabel(xlabel)
ax.set_ylabel("翘边率 (%)")
ax.set_title(f"翘边率 vs {xlabel}", fontsize=11)
ax.grid(alpha=0.3)
plt.suptitle("翘边率随关键参数变化趋势", fontsize=13, fontweight="bold")
plt.tight_layout()
plt.savefig(self.results_dir/"warp_rate_curve.png", dpi=150, bbox_inches="tight")
plt.close()
def roc_curve(self, fpr, tpr, auc_score: float):
fig, ax = plt.subplots(figsize=(7, 7))
ax.plot(fpr, tpr, "b-", linewidth=2.5, label=f"ROC (AUC={auc_score:.3f})")
ax.plot([0,1], [0,1], "k--", linewidth=1.5)
ax.set_xlabel("假阳性率 (FPR)")
ax.set_ylabel("真阳性率 (TPR)")
ax.set_title("ROC 曲线 - 翘边风险预判", fontsize=13, fontweight="bold")
ax.legend(loc="lower right")
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir/"roc_curve.png", dpi=150, bbox_inches="tight")
plt.close()
def confusion_matrix(self, cm: np.ndarray):
fig, ax = plt.subplots(figsize=(6, 6))
im = ax.imshow(cm, cmap="Blues", aspect="auto")
ax.set_xticks([0, 1])
ax.set_yticks([0, 1])
ax.set_xticklabels(["未翘边", "翘边"])
ax.set_yticklabels(["未翘边", "翘边"])
ax.set_xlabel("预测")
ax.set_ylabel("实际")
for i in range(2):
for j in range(2):
ax.text(j, i, str(cm[i, j]), ha="center", va="center",
fontsize=16, fontweight="bold",
color="white" if cm[i, j] > cm.max()/2 else "black")
plt.colorbar(im, ax=ax)
ax.set_title("混淆矩阵", fontsize=13, fontweight="bold")
plt.tight_layout()
plt.savefig(self.results_dir/"confusion_matrix.png", dpi=150, bbox_inches="tight")
plt.close()
def feature_importance(self, importance: Dict):
fig, ax = plt.subplots(figsize=(10, 6))
names = list(importance.keys())[:8]
vals = list(importance.values())[:8]
colors = plt.cm.Reds(np.array(vals) / max(vals))
ax.barh(range(len(names)), vals[::-1], color=colors[::-1],
edgecolor="black", height=0.6)
ax.set_yticks(range(len(names)))
ax.set_yticklabels(names[::-1], fontsize=10)
ax.set_xlabel("特征重要性")
ax.set_title("翘边关键因素排序", fontsize=13, fontweight="bold")
ax.grid(axis="x", alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir/"feature_importance.png", dpi=150, bbox_inches="tight")
plt.close()
def interaction_heatmap(self, heatmap_data: Dict,
x_label: str = "热床温度(℃)",
y_label: str = "环境温度(℃)"):
fig, ax = plt.subplots(figsize=(10, 8))
heatmap = heatmap_data["heatmap"]
im = ax.imshow(heatmap, cmap="RdYlGn_r", aspect="auto", origin="lower")
ax.set_xticks(range(heatmap.shape[1]))
ax.set_yticks(range(heatmap.shape[0]))
x_edges = heatmap_data["x_edges"]
y_edges = heatmap_data["y_edges"]
ax.set_xticklabels([f"{x_edges[i]:.0f}" for i in range(len(x_edges)-1)])
ax.set_yticklabels([f"{y_edges[i]:.0f}" for i in range(len(y_edges)-1)])
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.set_title("热床温度 × 环境温度 → 翘边率(%)", fontsize=13, fontweight="bold")
plt.colorbar(im, ax=ax, label="翘边率 (%)")
plt.tight_layout()
plt.savefig(
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!