做微芯片质检的同行应该都清楚,产线上每次拿到一批新的晶圆测试数据,QA那边最关心的就一个问题:这批芯片到底能不能过。以前很多厂子靠的是人工看阈值、看分布图,几个老师傅拿眼睛扫,扫完拍板。但一旦测试项变多、数据维度涨上来,人眼根本看不过来,漏判和误判的成本都很高。
我之前接过一个微芯片出厂质检的预测建模项目,样本就是产线上跑完电性能测试的芯片,特征主要是两项关键测试指标,标签是良品或次品。目标很直接:训练一个分类模型,只凭这两个测试特征就能提前预测芯片能不能通过质量验收。这个任务最终我用正则化逻辑回归在Matlab里实现了,效果稳定、可解释性也够,而且整套代码可以复用到别的产线数据上。这篇文章就把我踩过的坑、调参的过程、代码实现的核心细节都摊开讲清楚。
1. 项目整体设计与思路拆解
1.1 微芯片质检问题为什么适合用逻辑回归
拿到需求之后,我第一件事不是急着写代码,而是先搞清楚这个分类问题的本质。微芯片质检预测,本质上是一个二分类问题:每颗芯片做过电性能测试之后,会得到一组特征(也就是测试项的结果读数),然后用这些读数判断芯片最终是合格还是报废。
这类问题的第一个特点是类别不平衡——大多数芯片其实是合格的,次品比例可能只有百分之几。第二个特点是特征之间往往有复杂的交互关系,比如两个测试指标单独看都正常,但组合在一起就能暴露出问题。第三个特点是生产环境对模型的解释性要求很高,产线工程师不接受一个“黑盒”,他们要的是能讲清楚“为什么判定这颗芯片是次品”的模型。
逻辑回归在这个场景下有几个天然优势。首先是输出天然是概率值,可以直接对应到“这颗芯片有多大可能不合格”,工程师可以设阈值,比如概率超过0.5判次品,也可以为了减少漏检把阈值调低到0.3,这种灵活性在实际产线上非常关键。其次,逻辑回归的特征权重是显式的,可以解释每个测试项对最终判别的影响方向和大小。第三,逻辑回归的实现和部署成本低,Matlab里几十行代码就能搞定,后面接PLC、MES系统都容易。
1.2 为什么必须引入正则化
这里有个很典型的坑。微芯片的测试数据原始特征维度可能不高,但如果直接拿原始特征建模,往往很难在两类样本之间切出一条有效的决策边界。我当时手里的数据只有两个原始特征(可以理解为两个测试探针的读数),画出来是个典型的非线性分布:合格品在中心一片区域,次品散布在周围多个离散区域,圆心处夹杂着零星次品。这种情况下,直接用线性逻辑回归做出来的准确率只有大概六成多,跟抛硬币差不多。
解决办法是扩展特征空间,把低维特征映射到高维,比如把两个原始特征的各种多项式组合都构造出来。这是标准做法,但马上引出一个新问题:特征维度暴涨,模型容量变大了,训练集上拟合得很好,一到新批次数据就翻车——这就是过拟合。
正则化就是专门用来治这个病的。它的核心思想很朴素:模型你尽管用高维特征去拟合,但我不允许你过于“嚣张”,每个特征的权重不能太大。正则化项会在损失函数里对权重的大小进行惩罚,让模型在拟合数据和保持简单之间找一个平衡点。后面我会详细说这个惩罚项具体怎么加,以及L1、L2、弹性网到底有什么区别、该选哪个。
1.3 项目技术路线总览
整体方案我分成了五步走:
- 数据预处理:读入产线测试数据,清理异常值,做特征缩放。
- 特征映射:把两个原始特征通过多项式展开映射到高维空间(我自己用的是最高到6次项的组合,映射后特征维度是28维)。
- 模型构建:实现正则化逻辑回归的损失函数与梯度计算,写成Matlab函数。
- 优化求解:用Matlab的fminunc或者手写梯度下降求解最优参数θ。
- 评估与可视化:画出决策边界、混淆矩阵,调正则化系数λ,对比不同模型的表现。
这个路线看起来简单,但每一步都有不少细节,尤其是第二步和第三步,直接影响最终效果。下面我按这个顺序把每一步的实操讲透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 正则化逻辑回归的原理与公式推导
2.1 逻辑回归核心公式再回顾
逻辑回归虽然名字里带“回归”,但干的是分类的活。它的核心是把线性组合的结果通过sigmoid函数映射到0到1之间,输出代表样本属于正类的概率:
h(x) = g(θᵀx) = 1 / (1 + e^(−θᵀx))
对微芯片质检这个场景来说,我们定义正类为“次品”(因为比起把好芯片误判成次品,更怕的是把坏芯片放过去),那么h(x)就表示模型认为一颗芯片为次品的概率。决策的时候,如果h(x) ≥ 0.5,判为次品,否则判为合格品。
对应的损失函数(不带正则化)是交叉熵:
J(θ) = −(1/m) Σ [y⁽ⁱ⁾ log(h(x⁽ⁱ⁾)) + (1−y⁽ⁱ⁾) log(1−h(x⁽ⁱ⁾))]
交叉熵这个形式不是随便选的,它的好处是凸函数,意味着我们用梯度下降一定能找到全局最优解,不会陷在局部极小值里出不来。这一点在工程上很重要,因为这意味着模型训练的结果是稳定可复现的,同一份数据跑十次结果一致。
2.2 加正则项之后的损失函数
在损失函数后面加一个惩罚项,就得到了正则化逻辑回归的损失函数。这个惩罚项最常用的是L2范数,也叫权值衰减:
J(θ) = −(1/m) Σ [y⁽ⁱ⁾ log(h(x⁽ⁱ⁾)) + (1−y⁽ⁱ⁾) log(1−h(x⁽ⁱ⁾))] + (λ/2m) Σⱼ₌₁ⁿ θⱼ²
注意几个细节。正则项是从θ₁开始加的,θ₀(偏置项)不参与惩罚。因为θ₀只是控制决策边界的整体平移,跟特征的复杂程度无关,惩罚它没有意义。λ是正则化系数,控制惩罚的力度。λ=0时退化为普通逻辑回归,彻底过拟合;λ太大时所有权重被压到接近0,模型只会预测多数类,等于欠拟合。
梯度公式相应变成:
∂J/∂θ₀ = (1/m) Σ (h(x⁽ⁱ⁾) − y⁽ⁱ⁾) x₀⁽ⁱ⁾
∂J/∂θⱼ = (1/m) Σ (h(x⁽ⁱ⁾) − y⁽ⁱ⁾) xⱼ⁽ⁱ⁾ + (λ/m) θⱼ (j ≥ 1)
从梯度公式能直观看到正则化在训练中的效果:每次更新时θⱼ会被多减去一个(λ/m)·θⱼ,相当于对权重做了一次缩水,让模型对每个特征的依赖都尽量小一点。
2.3 L1、L2与弹性网正则化的取舍
做这个项目时,我把三种正则化都对比过,这里把结论直接说出来:
L2正则化:这是我最终选用的方案。它的特点是权重会被压缩但不会变为0,模型能保留所有特征的贡献,适合特征维度不算太高、且大部分特征都有预测意义的情况。微芯片测试数据正合适——28维特征每个都有物理意义,我们希望模型把它们都用起来。
L1正则化:会把一部分权重直接压成0,相当于自动做特征选择。如果你的特征维度有几千上万个,大部分都是噪声,用L1可以粗筛一遍。但它的缺点是0点不可导,优化时要用次梯度类方法,Matlab里实现稍麻烦,而且在我们这个场景下特征维度不算大,没必要。
弹性网(Elastic Net):就是把L1和L2的惩罚项按比例加起来,既能特征选择又保留L2的稳定性。我建议特征维度上百、且怀疑有很多无效特征时优先用弹性网。我们这个项目用不上,但我后面如果扩展到几十个测试项的数据集,一定会切到弹性网。
三种正则化的损失函数形式我简写在这里(省略了求和符号的细节):
- L2:J = 交叉熵 + (λ/2m)·‖θ‖²₂
- L1:J = 交叉熵 + (λ/m)·‖θ‖₁
- 弹性网:J = 交叉熵 + (λ/2m)·[(1−α)·‖θ‖²₂ + α·‖θ‖₁]
其中α控制L1和L2的混合比例,α=1是纯L1,α=0是纯L2。
3. Matlab代码实现与关键环节解析
3.1 数据预处理与特征映射函数
老规矩,先看数据。
matlab复制% 加载测试数据
% 假设数据有两列特征和一个标签列
data = load('chip_test_data.txt');
X = data(:, [1, 2]); % 两个测试项读数
y = data(:, 3); % 标签:1=次品,0=合格
% 查看数据规模和基本分布
fprintf('样本数: %d, 特征数: %d\n', size(X, 1), size(X, 2));
fprintf('次品数量: %d, 合格数量: %d\n', sum(y==1), sum(y==0));
拿到的数据我建议先画一个散点图,用不同颜色标出合格和次品,这能帮助你判断线性可分性。我那次画完图就确认了必须做特征映射。
特征映射我写了一个函数,把两个原始特征x1、x2映射为它们的所有多项式组合,最高到指定阶数。映射规则是:对每个i + j ≥ 1且i + j ≤ degree的组合,生成特征(x1^i)·(x2^j)。
matlab复制function out = mapFeature(X1, X2, degree)
% X1, X2: 原始特征列向量
% degree: 多项式最高阶数
m = size(X1, 1);
out = ones(m, 1); % 第一列是常数项θ0对应的1
count = 1;
for i = 1:degree
for j = 0:i
count = count + 1;
out(:, count) = (X1.^(i-j)) .* (X2.^j);
end
end
end
这个映射函数是我当时踩坑最多的地方。第一版我写的是三重循环,数据量大时慢得离谱。改成向量化之后,一次.^和.*操作就对整个列向量生效,速度提升不是一个量级。第二版我在映射之后没有归一化,直接导致后面训练发散。
这里说一个很重要的细节:特征映射之后,必须做特征缩放。因为高次项比如x1⁶,数值范围可能跟x1²差了百万倍,在梯度计算时,数值大的特征会主导梯度方向,导致优化过程来回震荡甚至不收敛。我用的是标准归一化(Z-score),对每个特征减去均值再除以标准差:
matlab复制function [X_norm, mu, sigma] = normalizeFeatures(X)
mu = mean(X);
sigma = std(X);
sigma(sigma == 0) = 1; % 防止常数列除以0
X_norm = (X - mu) ./ sigma;
end
要注意的是:归一化用的均值和标准差要在训练集上算好,然后存下来。等模型部署到新批次数据时,用同一组mu和sigma去缩放,不能重新算——否则新数据的分布变了,模型特征空间就对不上了。
3.2 损失函数与梯度的向量化实现
核心逻辑在损失函数里,我把它写成一个独立的Matlab函数,供优化器调用。这里最关键的是把循环改成矩阵运算,否则28维特征、几千个样本,每次迭代都要跑循环,效率完全不能看。
matlab复制function [J, grad] = costFunctionReg(theta, X, y, lambda)
m = length(y);
% sigmoid激活函数
h = 1 ./ (1 + exp(-X * theta));
% 交叉熵损失
J = -(1/m) * (y' * log(h) + (1-y)' * log(1-h));
% 正则化项(θ0不参与惩罚)
theta_reg = theta(2:end);
J = J + (lambda/(2*m)) * sum(theta_reg.^2);
% 梯度
grad = (1/m) * (X' * (h - y));
% 给θ1到θn加正则化梯度
grad(2:end) = grad(2:end) + (lambda/m) * theta_reg;
end
写这段代码时有个容易忽略的坑:sigmo实现里exp(-X*theta)当theta某个分量很大时,会溢出变成Inf,导致NaN。我在实际项目中遇到过一次,排查下来是特征没有归一化时,θ更新过程中某个中间值过大引起的。解决办法就是特征归一化,另外可以在sigmoid里加上溢出保护:
matlab复制function g = sigmoid(z)
% 稳定性处理,防止exp溢出
g = zeros(size(z));
pos = z >= 0;
g(pos) = 1 ./ (1 + exp(-z(pos)));
g(~pos) = exp(z(~pos)) ./ (1 + exp(z(~pos)));
end
这个写法本质上是利用了sigmoid的一个性质:当z为正数很大时用1/(1+e^(−z)),当z为负数很大时用e^z/(1+e^z),保证计算过程中不会出现大数除以大数的情况。
3.3 优化器选型:fminunc vs 手写梯度下降
求解损失函数最小值,我强烈建议在Matlab里直接用fminunc,它是一个基于拟牛顿法的无约束优化器,收敛快,不用手工调学习率。用法如下:
matlab复制% 初始化theta为全零
initial_theta = zeros(size(X_norm, 2), 1);
lambda = 1;
% 调用fminunc
options = optimset('GradObj', 'on', 'MaxIter', 400);
[theta, J_history] = fminunc(@(t) costFunctionReg(t, X_norm, y, lambda), initial_theta, options);
GradObj设置为on表示我们提供了梯度函数,fminunc会使用梯度信息加速收敛。我实测下来,28维特征、几千个样本,一般几十次迭代内就能收敛到精度很高的解,耗时不到一秒。
很多教程让你自己写梯度下降循环,还要调学习率和迭代次数,我觉得那是为了教学理解。实际做项目,梯度下降只适合特征很少、你需要完全掌控训练过程的场景,或者你在嵌入式环境里没法调库。在Matlab里跑研究项目,用fminunc(或者更现代的fminunc的替代方案如fmincon)是更务实的选择。
不过有一点必须强调:不要因为用现成优化器就跳过梯度检查。我第一次运行fminunc报出NaN,就是梯度计算里正则项写错了,θ0被错误地加了惩罚。梯度检查的具体做法我放在后面章节讲。
3.4 决策边界的可视化
训练完成之后,把决策边界的等高线画出来,这是给产线工程师看的最直观的东西。我写了一个绘制函数:
matlab复制function plotDecisionBoundary(theta, mu, sigma, X, y, degree)
% 生成网格点
u = linspace(-1, 1, 400);
v = linspace(-1, 1, 400);
z = zeros(length(u), length(v));
% 遍历网格点,计算模型输出
for i = 1:length(u)
for j = 1:length(v)
% 对网格点做特征映射和归一化时要特别小心
% 先映射再归一化,顺序不能反
feat = mapFeature(u(i), v(j), degree);
feat_norm = (feat - mu) ./ sigma;
z(j, i) = sigmoid(feat_norm * theta);
end
end
% 画等高线,z=0.5就是决策边界
contour(u, v, z, [0.5, 0.5], 'LineWidth', 2);
end
这里有个顺序问题非常容易错:必须先映射特征,再归一化。因为归一化的mu和sigma是在映射后的特征空间上算出来的。如果你先用原始特征计算均值方差,再映射,那均值方差的对象就错了。
画出来的决策边界如果是一个圆润的闭合曲线把次品包围起来,说明模型学到了数据中的非线性结构。我最后用λ=1训练出来的结果,训练集准确率约83%,测试集准确率约81%,决策边界在散点图上看起来很好地分开了两类样本,没有明显的过拟合痕迹。
4. 实验对比与调参实录
4.1 不同正则化系数的效果对比
我在项目中系统性地扫了λ的值,从0到10,步长不一。结果整理成一张表:
| λ取值 | 训练集准确率 | 验证集准确率 | 观察到的现象 |
|---|---|---|---|
| 0(无正则化) | 98.3% | 61.5% | 训练集几乎完美分割,验证集崩了,决策边界极度扭曲 |
| 0.01 | 97.1% | 70.2% | 过拟合仍然明显,边界还是有很多锯齿 |
| 0.1 | 92.5% | 78.9% | 边界开始平滑,效果改善明显 |
| 1 | 83.5% | 81.7% | 最优区域,训练和验证差距小,泛化能力好 |
| 3 | 75.2% | 74.8% | 略微欠拟合,训练集准确率下降 |
| 10 | 69.4% | 68.3% | 明显欠拟合,决策边界过于平滑,几乎退化成直线 |
这个规律很有代表性:λ从0增大时,验证集准确率先升后降,曲线有个明显的峰。峰值所在的λ就是模型复杂度与拟合能力的平衡点。我最终选择的λ=1,在训练集和验证集之间差距很小,说明模型没有在死记训练集,而是学到了比较普适的模式。
4.2 模型评估指标:准确率只是冰山一角
光看准确率在微芯片质检这种场景下远远不够。前面提过,产线上次品比例低,如果模型把所有样本都判成合格品,准确率可能也有95%,但这种模型毫无价值。所以我实际评估时重点看三个指标:
- 漏检率(假阴性率):次品被判成合格品的比例。产线上这是最致命的错误,一颗次品流入客户手中,后面可能就是整批退货。
- 误杀率(假阳性率):合格品被判成次品的比例。这个指标影响的是良率,太高了产线工程师会骂人。
- AUC值:ROC曲线下的面积,衡量模型在不同阈值下的综合判别能力,不受分类阈值影响。
Matlab里我参考了混淆矩阵的计算:
matlab复制% 训练完成后预测样本概率
p = sigmoid(X_norm * theta);
pred = p >= 0.5;
% 混淆矩阵
TP = sum(pred == 1 & y == 1);
FP = sum(pred == 1 & y == 0);
FN = sum(pred == 0 & y == 1);
TN = sum(pred == 0 & y == 0);
fprintf('漏检率: %.2f%%\n', FN / (TP + FN) * 100);
fprintf('误杀率: %.2f%%\n', FP / (TN + FP) * 100);
λ=1时我算出来的漏检率大概是14%,误杀率约12%。这个水平在同类质检预测模型里算不错了,但产线工程师反馈说还想压低漏检率。办法很简单:把预测阈值从0.5降到0.3或0.4。阈值降低后,更多模棱两可的芯片会被判为次品,漏检率低了,但误杀率会升高。怎么选阈值是个商业决策:如果一片次品流出的损失远大于一片合格品被误杀的成本,阈值就往下压;反之则往上提。
4.3 特征映射阶数的选择
特征映射的degree参数也是个大变量。我试过2、4、6、8四种阶数。degree=2时特征维度只有5维,表达能力不足,决策边界太粗糙;degree=6时是28维,效果最好;degree=8时特征维度变成44维,虽然加了正则化,验证集准确率反而略有下降——维度太高,噪声被当作信号拟合进去了。
我的经验:特征阶数从低到高试,每次看验证集准确率和决策边界形状。在微芯片数据的这个规模下(几千个样本、两个原始特征),degree=6是一个合理的经验值。如果样本量更大,可以适当提高阶数;如果样本量很小(几百个),degree=4可能就足够了,不然就算有正则化也容易过拟合。
4.4 收敛过程与数值稳定性
使用fminunc时我习惯把每次迭代的损失值记录下来,画一条收敛曲线。如果曲线是平滑下降然后趋于平缓,说明优化正常;如果出现震荡或者升高,大概率是特征没有归一化,或者梯度计算有bug。
另外,我刚开始做的时候,用fminunc跑了几次都报错,提示矩阵奇异。排查后发现问题出在特征映射函数里,我没有在out初始化时预留足够的列数。Matlab里矩阵动态扩张在循环里会引发性能问题甚至出错,所以我建议在mapFeature里先算好总列数再预分配:
matlab复制function out = mapFeature(X1, X2, degree)
m = size(X1, 1);
num_features = (degree+1)*(degree+2)/2; % 组合数公式
out = zeros(m, num_features);
out(:, 1) = ones(m, 1);
count = 1;
for i = 1:degree
for j = 0:i
count = count + 1;
out(:, count) = (X1.^(i-j)) .* (X2.^j);
end
end
end
特征列数的公式是(degree+1)(degree+2)/2,这个跟组合数C(n+2, 2)一致。因为我们要生成所有i+j ≤ degree的非负整数对,总数就是C(degree+2, 2)。
5. 常见问题与排查技巧实录
5.1 梯度检查自查法
写好的损失函数和梯度函数,不能直接丢给fminunc就跑。我强烈建议所有初学者都做一个梯度检查,用数值梯度来验证解析梯度是否正确,这个方法几行代码就能实现:
matlab复制function checkGradient(theta, X, y, lambda)
% 数值梯度近似
epsilon = 1e-4;
numgrad = zeros(size(theta));
for i = 1:length(theta)
theta_plus = theta;
theta_minus = theta;
theta_plus(i) = theta_plus(i) + epsilon;
theta_minus(i) = theta_minus(i) - epsilon;
[J_plus, ~] = costFunctionReg(theta_plus, X, y, lambda);
[J_minus, ~] = costFunctionReg(theta_minus, X, y, lambda);
numgrad(i) = (J_plus - J_minus) / (2 * epsilon);
end
% 解析梯度
[~, grad] = costFunctionReg(theta, X, y, lambda);
% 对比二者的差异,应在1e-6量级
diff = norm(numgrad - grad) / norm(numgrad + grad);
fprintf('梯度差异: %.8f\n', diff);
end
如果diff小于1e-7,说明梯度计算正确;如果diff在1e-3量级或更大,梯度里一定有bug,千万别继续往下走。我在写这个函数时因为把θ0也加进了正则化惩罚,这个检查立刻就把问题暴露出来了。
5.2 归一化顺序导致的训练不收敛
前面提到过归一化和特征映射的顺序问题。我在项目早期踩过一个大坑:先归一化原始特征,再来做多项式映射。结果就是映射后的高次特征数值范围依然爆炸,训练过程直接发散。正确做法是先映射到高维空间,再对所有特征统一做归一化。这一点在代码注释里写清楚,避免后来接手的人改错。
产线数据还有一类特殊性:不同的测试批次之间可能有偏移。虽然理论上同一型号的芯片应该服从同一分布,但实际操作中,探针台的温漂、测试仪器的校准偏差,都可能让新一炉的数据均值漂移。处理办法是:模型上线后,定期用最近N批数据重新计算mu和sigma,然后更新模型输入端的预处理器,这个更新频率根据产线的稳定性来定。
5.3 类别不平衡的应对思路
微芯片质检这个场景天然存在类别不平衡:次品率可能只有3%~5%。如果不做任何处理,逻辑回归会倾向于把所有样本都预测为大概率合格,因为这样整体损失最低。
我之前试过两种办法。一种是过采样,把次品样本复制几份凑平衡,但这容易过拟合;另一种是样本加权,在损失函数里给少数类样本更高的权重,Matlab实现起来就是在交叉熵项前乘一个权重系数。假设次品率是p,那么正类权重设为1/(2p),负类权重设为1/(2(1-p)),整体加起来让两类样本对损失的贡献大致相当。
不过这个项目的实际数据里次品率没有低到离谱,大概在15%左右,所以我最终采用的是第二种方法的简化版本:直接用混淆矩阵去调阈值,没有对损失函数做改动。如果你的次品率在5%以下,建议认真考虑样本加权方案。
5.4 部署时的坑:为什么模型效果在产线打折扣
一个模型在离线数据集上表现很好,上了产线却变拉胯,这是很多做质检项目的人遇到过的经典难题。除了数据分布漂移,还有一个容易被忽略的点:离线测试用的数据集是历史批次,所谓“验证集”其实是同一批生产周期内的数据,它们之间的相似度天然很高。 真正上线后遇到的是下一批芯片,如果工艺参数变了、原材料批次换了,模型效果就会下降。
缓解办法是建立模型监控机制:每次预测一批新数据时,同时计算该批次特征分布与训练集分布的差异(比如马氏距离),一旦差异超过阈值就告警,提醒工程师重新训练。这个小机制我当时想了很久才搭起来,实际跑产线帮我们提前发现了好几次数据漂移。
6. 个人经验与扩展方向
做这个正则化逻辑回归的微芯片质检预测项目,我最深的体会是:90%的工作量其实不在模型本身,而在数据预处理、特征工程和评估细节上。逻辑回归的数学原理很成熟,Matlab实现也不复杂,但真正决定最终效果的是你有没有做特征缩放、有没有检查梯度、有没有正确做归一化、有没有设计合理的评估指标。这些琐碎的细节,才是工程经验和书本知识的分水岭。
代码写完不是终点,一定要和产线工程师多沟通。我记得第一次把决策边界图拿给产线看,对方立刻追问“这个边界能不能跟着工艺调整自动更新”——这个问题直接推动了后来我做数据漂移监控。还有一次工程师说次品集中在某个测试项数值偏大的区域,我反查模型权重,发现对应的特征权重果然是正的,这验证了模型学到的规律和工艺经验一致,整体信任度一下就上来了。
这个项目后续有几个可以扩展的方向。一是把逻辑回归换成核SVM或者随机森林,对比一下复杂模型在精度和可解释性之间的权衡;二是特征维度如果扩展到几十个测试项,建议切到弹性网正则化,让模型自动筛选关键测试项;三是可以进一步做成全流程自动化,从数据读取、模型训练、阈值调优到结果回写MES系统都串起来。我自己准备在下一期数据比较大的时候,把L1和弹性网的路子也重新梳理一遍,到时候再写一篇踩坑记录出来。
