简介这份资源面向材料科学、机械工程与增材制造领域的研究人员及工程师聚焦NiTi形状记忆合金激光金属沉积工艺参数的多目标优化难题。内容以BP神经网络结合NSGA-II遗传算法为主线通过30组小样本单道实验建立显微硬度、粗糙度与沉积速率的预测模型并对比响应面法与随机森林的预测效果最终给出激光功率1292.14W、扫描速度8.79mm/s、送粉速率16.78g/min的最优参数组合使显微硬度提升5.23%、粗糙度降低20.04%且保持高沉积速率。资源包为1个PDF文件约686KB内含完整Python复现代码覆盖数据准备、模型训练、多目标优化与结果可视化全流程并讨论多相耦合关系、工艺参数敏感度及工业应用前景。已有64人学习适合希望快速上手LMD工艺优化实验设计与数据分析的读者参考。1. 增材制造里的 NiTi 合金为什么工艺参数不能靠试错硬扛NiTi 形状记忆合金在激光金属沉积里是个出了名的“脾气大”的材料。它同时要满足三个互相拉扯的指标显微硬度得够高、表面粗糙度得够低、沉积速率还不能掉下来。做过增材制造的人都知道这三者本质上是一组矛盾——激光功率拉高沉积速率上去了但热输入过大又会让晶粒粗化、硬度下降表面球化也更严重扫描速度加快粗糙度可能改善但熔池不稳定致密度和硬度又保不住。传统做法是靠正交实验或者单因素轮换来逼近一个“还行”的参数组合问题是实验成本极高NiTi 粉末一公斤动辄上千一台 LMD 设备开机就是钱试错几十组下来周期和预算都吃不消。神经网络加遗传算法的组合恰好是冲着这个痛点来的。用前馈神经网络BP 网络去拟合“激光功率、扫描速度、送粉率、搭接率”这类工艺参数到“显微硬度、粗糙度、沉积速率”的映射关系再用 NSGA-II 这类多目标遗传算法在拟合出来的代理模型上做寻优最后拿到一组 Pareto 前沿解。这套思路在论文里很常见但真正落地复现时坑集中在数据质量、网络结构、目标权重和 Pareto 解的选择上。这篇笔记就按“数据怎么整、网络怎么搭、遗传算法怎么调、结果怎么验”的顺序把这条链路拆开讲清楚适合做金属增材、材料工艺优化、或者想把代理模型加进化算法用到实际工程里的从业者。2. 从工艺参数到性能指标数据表怎么建、网络怎么选2.1 输入输出变量的确定与数据预处理做任何代理模型第一步不是写网络而是把数据表定死。LMD 工艺里常见的可控输入有激光功率 PW、扫描速度 vmm/s、送粉率 fg/min、搭接率 η%、离焦量 dmm。输出指标按标题锁定三个显微硬度 HVHV0.2 或 HV0.5、表面粗糙度 Raμm、沉积速率 DRmm³/s 或 g/min。数据来源一般是三块已发表论文里的表格数据、自己实验室的正交实验、以及设备日志里能扒出来的稳定工况记录。数据预处理有两个必须做的动作。第一是归一化因为功率是几百瓦、速度是几百毫米每秒、送粉率是个位数量纲差两三个数量级不归一化 BP 网络的梯度会被大数值特征主导。第二是异常值剔除LMD 实验里经常出现某组参数下熔池塌陷或者粉末堵塞导致硬度或粗糙度明显偏离趋势这种点留着会让网络学偏。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 读取原始实验数据列名按实际表头调整 df pd.read_csv(lmd_niti_data.csv) # 输入特征与输出目标 feature_cols [power_W, scan_speed_mm_s, powder_feed_g_min, overlap_pct, defocus_mm] target_cols [microhardness_HV, roughness_Ra_um, deposition_rate_mm3_s] # 用 IQR 方法剔除异常值阈值 1.5 倍四分位距 mask pd.Series(True, indexdf.index) for col in feature_cols target_cols: q1, q3 df[col].quantile(0.25), df[col].quantile(0.75) iqr q3 - q1 lower, upper q1 - 1.5 * iqr, q3 1.5 * iqr mask df[col].between(lower, upper) df_clean df[mask].reset_index(dropTrue) print(f原始样本 {len(df)} 条清洗后 {len(df_clean)} 条) # 归一化输入输出都缩到 [0,1] scaler_X MinMaxScaler() scaler_y MinMaxScaler() X scaler_X.fit_transform(df_clean[feature_cols]) y scaler_y.fit_transform(df_clean[target_cols]) np.save(X_scaled.npy, X) np.save(y_scaled.npy, y)这段代码的逻辑是先按四分位距把每个维度的离群点去掉再做 MinMax 归一化。参数上IQR 的 1.5 倍是通用经验值如果数据本身很少比如少于 30 组可以放宽到 2.0 倍避免把有效样本误删。归一化器要保存下来因为后续遗传算法找到的 Pareto 解是在归一化空间里的必须用同一个 scaler 反变换回物理量纲才能拿去指导实验。2.2 BP 网络结构几层、多少神经元、怎么防过拟合标题里说的是“神经网络”在工艺参数拟合这个场景下最稳的选型是前馈 BP 网络而不是 LSTM 或 Transformer。原因很直接工艺参数到性能指标是一个静态映射没有时序依赖样本量通常也就几十到几百条上复杂结构只会过拟合。常见做法是“输入层 5 个节点 → 1 到 2 个隐藏层 → 输出层 3 个节点”隐藏层神经元数量按经验公式sqrt(n_in n_out) aa 取 1 到 10来试或者直接网格搜。激活函数隐藏层用 ReLU 或 tanh输出层用 linear因为输出是连续物理量。损失函数用 MSE优化器用 Adam学习率 1e-3 起步。防过拟合的手段有三个早停验证集 loss 连续若干轮不降就停、L2 正则、以及 Dropout小样本下 0.1 到 0.2 即可再大就欠拟合。import torch import torch.nn as nn from torch.utils.data import TensorDataset, DataLoader class LMDNet(nn.Module): def __init__(self, n_in5, n_out3): super().__init__() self.net nn.Sequential( nn.Linear(n_in, 32), nn.ReLU(), nn.Dropout(0.1), nn.Linear(32, 32), nn.ReLU(), nn.Linear(32, n_out) # 输出层不加激活回归任务 ) def forward(self, x): return self.net(x) X_tensor torch.tensor(np.load(X_scaled.npy), dtypetorch.float32) y_tensor torch.tensor(np.load(y_scaled.npy), dtypetorch.float32) dataset TensorDataset(X_tensor, y_tensor) loader DataLoader(dataset, batch_size16, shuffleTrue) model LMDNet() criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) best_loss float(inf) patience, wait 50, 0 for epoch in range(2000): model.train() for xb, yb in loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() # 简单用训练 loss 做早停演示实际应划验证集 if loss.item() best_loss: best_loss, wait loss.item(), 0 torch.save(model.state_dict(), lmd_bp_best.pth) else: wait 1 if wait patience: print(f早停于 epoch {epoch}) break网络结构上32-32 是两个隐藏层各 32 个神经元这个规模对几十到几百条样本足够。Dropout 只加在第一层之后是因为小样本下每层都加会削弱拟合能力。早停的 patience 设 50是因为 Adam 在后期 loss 波动很正常patience 太小会提前掐断。权重衰减 1e-5 是轻量正则如果发现训练 loss 和验证 loss 差距大可以加到 1e-4。2.3 用 R² 和 MAE 判断网络是否可信网络训完不能只看 loss 曲线得回到物理量纲上看误差。做法是把预测值反归一化然后对三个输出分别算 R² 和 MAE。显微硬度的 MAE 最好控制在 15 HV 以内粗糙度控制在 0.5 μm 以内沉积速率控制在 0.05 mm³/s 以内超过这个范围说明网络还没学到趋势拿去给遗传算法寻优就是“垃圾进垃圾出”。from sklearn.metrics import r2_score, mean_absolute_error model.load_state_dict(torch.load(lmd_bp_best.pth)) model.eval() with torch.no_grad(): y_pred_scaled model(X_tensor).numpy() y_true scaler_y.inverse_transform(y_tensor.numpy()) y_pred scaler_y.inverse_transform(y_pred_scaled) for i, name in enumerate(target_cols): r2 r2_score(y_true[:, i], y_pred[:, i]) mae mean_absolute_error(y_true[:, i], y_pred[:, i]) print(f{name}: R2{r2:.3f}, MAE{mae:.3f})R² 低于 0.85 就别急着进遗传算法先回头查数据是不是某组参数下实验记录有误或者输入特征漏了关键变量比如保护气流量、层间等待时间。这一步是很多复现翻车的地方——网络在归一化空间里 loss 看着很小一反变换发现硬度误差几十 HV根源往往是数据里混进了不同批次的粉末或者设备状态漂移。3. NSGA-II 多目标寻优目标函数、约束和参数怎么设3.1 把三个指标写成可优化的目标函数遗传算法要的是数值化的目标函数。显微硬度和沉积速率是“越大越好”粗糙度是“越小越好”。NSGA-II 默认是最小化所以要把硬度和沉积速率取负。同时工艺参数有物理边界功率不能超过设备上限扫描速度不能低到熔池无法连续送粉率不能高到堵嘴。这些边界在遗传算法里就是变量的上下界。import numpy as np import torch # 加载训练好的网络和归一化器 model.load_state_dict(torch.load(lmd_bp_best.pth)) model.eval() def predict_properties(x_phys): 输入物理量纲的工艺参数返回物理量纲的三个性能指标 x_scaled scaler_X.transform(np.array(x_phys).reshape(1, -1)) with torch.no_grad(): y_scaled model(torch.tensor(x_scaled, dtypetorch.float32)).numpy() return scaler_y.inverse_transform(y_scaled)[0] def objectives(x): hv, ra, dr predict_properties(x) # NSGA-II 最小化硬度和沉积速率取负 return [-hv, ra, -dr] # 变量边界[功率, 扫描速度, 送粉率, 搭接率, 离焦量] bounds np.array([ [300, 800], # 功率 W [4, 12], # 扫描速度 mm/s [2, 8], # 送粉率 g/min [30, 60], # 搭接率 % [-3, 3], # 离焦量 mm ])目标函数里直接调网络预测意味着遗传算法每评估一个个体就要过一次网络。NSGA-II 种群规模 100、迭代 200 代的话评估次数在两万次左右纯 Python 循环会慢但网络很小实际跑起来几分钟到十几分钟能出结果。如果嫌慢可以把网络导出成 ONNX 或者用 numpy 手写前向传播能再快一个量级。3.2 NSGA-II 的关键参数种群、交叉、变异NSGA-II 在工艺优化里常用的配置是种群规模 100 到 200迭代代数 200 到 500交叉概率 0.9变异概率 1/变量数这里是 0.2 左右交叉算子用模拟二进制交叉SBX变异用多项式变异。这些参数不是拍脑袋种群太小 Pareto 前沿稀疏太大评估成本高变异概率过高会退化成随机搜索过低则种群多样性不足容易卡在局部前沿。from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.core.problem import Problem from pymoo.operators.crossover.sbx import SBX from pymoo.operators.mutation.pm import PM from pymoo.operators.sampling.rnd import FloatRandomSampling class LMDProblem(Problem): def __init__(self): super().__init__(n_var5, n_obj3, n_constr0, xlbounds[:, 0], xubounds[:, 1]) def _evaluate(self, X, out, *args, **kwargs): F np.array([objectives(x) for x in X]) out[F] F algorithm NSGA2( pop_size120, samplingFloatRandomSampling(), crossoverSBX(prob0.9, eta15), mutationPM(eta20), eliminate_duplicatesTrue ) from pymoo.optimize import minimize res minimize(LMDProblem(), algorithm, (n_gen, 300), seed1, verboseTrue) np.save(pareto_F.npy, res.F) np.save(pareto_X.npy, res.X)这里 SBX 的 eta 设 15、多项式变异 eta 设 20是 pymoo 里的常用值eta 越大子代越靠近父代探索能力越弱。如果发现 Pareto 前沿分布很窄可以把交叉 eta 降到 10 左右增加探索。eliminate_duplicatesTrue很重要因为网络预测是确定性的相同输入必然相同输出不去重会浪费评估次数。3.3 Pareto 解怎么选别只看一个点NSGA-II 跑完给的是一组 Pareto 前沿解不是单一最优。选哪个点取决于工程偏好。常见做法有三种一是看哪个解在三个目标上都不差折中解用 TOPSIS 或熵权法排序二是固定沉积速率下限在满足速率的前提下选硬度最高、粗糙度最低的三是画三维 Pareto 前沿图人工挑。import numpy as np F np.load(pareto_F.npy) # 列[-HV, Ra, -DR] X np.load(pareto_X.npy) # 还原成物理量纲 HV -F[:, 0] Ra F[:, 1] DR -F[:, 2] # 策略沉积速率不低于 0.8 倍最大值在此约束下选硬度最高 dr_threshold 0.8 * DR.max() mask DR dr_threshold idx_candidates np.where(mask)[0] best_idx idx_candidates[np.argmax(HV[idx_candidates])] print(推荐工艺参数) print(f功率 {X[best_idx,0]:.1f} W, 扫描速度 {X[best_idx,1]:.2f} mm/s, f送粉率 {X[best_idx,2]:.2f} g/min, 搭接率 {X[best_idx,3]:.1f}%, f离焦量 {X[best_idx,4]:.2f} mm) print(f预测硬度 {HV[best_idx]:.1f} HV, 粗糙度 {Ra[best_idx]:.3f} μm, f沉积速率 {DR[best_idx]:.3f} mm³/s)选解这一步最容易出的问题是直接选硬度最高的点结果沉积速率低到没有工程意义。所以一定要先加工程约束再排序。另外Pareto 前沿上的解彼此不可支配没有“绝对最优”论文里如果只报一个点通常就是加了偏好或者约束之后挑出来的。4. 复现时最容易翻车的几个地方4.1 数据量太少还硬上复杂网络现象训练集 R² 能到 0.98验证集 R² 只有 0.6Pareto 解拿去实验完全对不上。 原因样本只有二三十组网络参数却有上千个直接记住了训练数据。 解决把网络缩到单隐藏层 16 个神经元加 L2 正则和早停或者改用高斯过程回归这类小样本友好的代理模型。数据少于 50 组时BP 网络不是首选。4.2 归一化器只对训练集 fit预测时忘了 transform现象遗传算法找到的解反变换后功率变成负数或者几千瓦。 原因预测时直接把物理量喂给网络没有用训练时的 scaler 做 transform。 解决把 scaler_X 和 scaler_y 用 joblib 保存预测和寻优全程走同一套变换。代码里predict_properties必须先 transform 再进网络出来再 inverse_transform。4.3 目标函数方向搞反现象Pareto 前沿上硬度越优化越低粗糙度越优化越高。 原因NSGA-II 默认最小化硬度没取负或者粗糙度取了负。 解决在 objectives 函数里明确注释每个目标的优化方向硬度和沉积速率取负粗糙度保持正。跑完先检查 F 矩阵的符号再画图。4.4 遗传算法参数照搬教程种群和代数不匹配现象迭代 50 代就收敛Pareto 前沿只有几个点分布还挤在一起。 原因种群 20、代数 50搜索空间根本没覆盖。 解决5 个变量、3 个目标种群至少 100代数至少 200。先跑一次看 Pareto 前沿点数少于 20 个点就加种群或加代数。4.5 忽略工艺约束解出物理上不可行的参数现象优化结果里扫描速度 12 mm/s 配功率 300 W实际打出来全是球化。 原因目标函数只用了网络预测没有加熔池稳定性、粉末利用率这类隐式约束。 解决在 Problem 里加 n_constr把“功率/速度比”或者“线能量”限制在实验验证过的区间内。或者后处理时把不可行解过滤掉再在前沿上选点。5. 用实验验证 Pareto 解从代理模型到真实工件代理模型加遗传算法给出的终究是预测值最后一步必须落到实验验证。我一般会从 Pareto 前沿上挑 3 个点一个偏硬度、一个偏粗糙度、一个折中再加上原始实验里最好的那组作为对照一共 4 组每组做 3 个试样。打印完测硬度用维氏硬度计、粗糙度用白光干涉仪或接触式轮廓仪、沉积速率用称重法算。把实测值和预测值放一起算误差如果硬度误差在 10% 以内、粗糙度在 15% 以内说明代理模型可信这套参数可以进工艺库。验证时有个细节容易被忽略LMD 打印的试样要统一后处理状态。如果一组打印完直接测另一组去应力退火后测硬度数据没有可比性。NiTi 合金尤其敏感打印态和热处理态的显微硬度能差几十 HV。所以验证实验要么全部打印态测要么全部同制度热处理后测别混。# 验证数据与预测数据对比 import numpy as np predicted np.array([ [420, 3.2, 1.05], [380, 2.1, 1.20], [400, 2.6, 1.12], [360, 3.8, 0.95], ]) measured np.array([ [408, 3.4, 1.01], [372, 2.3, 1.15], [391, 2.8, 1.08], [350, 4.0, 0.92], ]) for i, name in enumerate([硬度 HV, 粗糙度 μm, 沉积速率 mm³/s]): err np.abs(measured[:, i] - predicted[:, i]) / predicted[:, i] * 100 print(f{name} 平均相对误差 {err.mean():.1f}%)如果误差超标优先查两件事一是实验时粉末批次和网络训练数据是否一致二是设备实际输出功率和设定值有没有偏差。LMD 设备的激光功率衰减、送粉器标定漂移都会让实测偏离预测这类系统误差不是算法能修的。这套流程我前后跑过几轮最大的体会是神经网络和遗传算法本身都不难难的是数据表里的每一行都经得起推敲。工艺参数优化不是把代码跑通就完事Pareto 解选出来之后敢不敢上机、上机之后误差能不能收敛才是判断这套方案值不值得投入的标准。希望帮到你。本文还有配套的精品资源点击获取