第 4 课用 L2 把岭回归的验证误差从 23.96 拉回 0.25那是回归任务损失是平方误差。这一课把标签换成 0/1输出变成概率损失换成交叉熵log loss。换完会碰见岭回归里没有的事数据完全线性可分时不加 L2 权重会一直往无穷长loss 趋近 0 却永远取不到极大似然解不存在。L2 在分类里的第一作用不是防过拟合而是让解存在且有界。全程只用 numpy把 sigmoid、交叉熵、梯度、λ 扫描手推并真跑一遍。求解器一直在变scikit-learn 1.9 给 LogisticRegression 加了 callbacks、原生 float32 与 LBFGS 的 GPU 支持但目标函数和梯度形式没变。环境准备只需要 numpy不需要装 sklearn。本机实测环境 Python 3.13.14 / Windows 10 / numpy 2.5.1。python -c import numpy, sys, platform; print(sys.version.split()[0], platform.platform(), numpy.__version__) pip install numpy2.5NumPy 2.5.0 于 2026-06-21 发布停止支持 Python 3.11复现时别混用 1.x。先造一份带噪声的二分类数据真实边界取 w* [-0.3, 1.6, -0.9]叠加标准差 0.25 的噪声后二值化。训练集 200 条、验证集 600 条种子不同验证集不参与拟合。import numpy as np def sigmoid(z): # exp(-logaddexp(0,-z)) 等价于 1/(1exp(-z))但不会溢出 return np.exp(-np.logaddexp(0.0, -z)) def bce_from_logit(z, y): # softplus(z) - y*z直接对 logit 算交叉熵全程不取 log(0) return np.logaddexp(0.0, z) - y * z def make_data(n, seed, noise0.25): r np.random.default_rng(seed) X r.normal(size(n, 2)) z X np.array([1.6, -0.9]) - 0.3 r.normal(scalenoise, sizen) return X, (z 0).astype(float) X_tr, y_tr make_data(200, 11) X_va, y_va make_data(600, 12) print(train, X_tr.shape, 正类占比, round(float(y_tr.mean()), 3)) print(val , X_va.shape, 正类占比, round(float(y_va.mean()), 3))train (200, 2) 正类占比 0.475 val (600, 2) 正类占比 0.428为什么分类不能用平方误差sigmoid 套上后仍能用 MSE 训练但它「错得越离谱、学得越慢」。取 z -8、y 1此时 p 0.00033535属于严重错判。两种损失对 logit 的梯度import numpy as np def sigmoid(z): return np.exp(-np.logaddexp(0.0, -z)) z np.array([-8.0]) p sigmoid(z) g_bce p - 1.0 # 交叉熵对 logit 的梯度 g_mse 2 * (p - 1.0) * p * (1 - p) # MSE 对 logit 的梯度多出 p(1-p) 因子 print(p , round(float(p[0]), 8)) print(交叉熵对 z 的梯度:, round(float(g_bce[0]), 8)) print(MSE 对 z 的梯度 :, round(float(g_mse[0]), 8)) print(相差倍数:, round(float(g_bce[0] / g_mse[0]), 1))p 0.00033535 交叉熵对 z 的梯度: -0.99966465 MSE 对 z 的梯度 : -0.00067025 相差倍数: 1491.5差 1491.5 倍。MSE 梯度多了个 p(1-p)p 贴近 0 或 1 时把梯度掐死交叉熵梯度是 p - y接近 ±1不随置信度衰减。再看曲面凸性t 从 -4 扫到 4统计二阶差分为负的区间占比。损失最低点 t下弯区间占比MSE0.1263%交叉熵0.140%交叉熵全程凸从任意起点滚进同一个最小值MSE 有 63% 区间下弯两侧还有平坦平台。第 3 课的平方损失搬不到分类上原因就在这。踩坑一naive sigmoid 会把 loss 算成 inf 和 nan第 1 课实测过np.exp([1000, 999, 998])的溢出。这里更隐蔽z 无论正负朴素写法1/(1np.exp(-z))都会出事。import warnings import numpy as np def sigmoid_naive(z): return 1.0 / (1.0 np.exp(-z)) def sigmoid(z): return np.exp(-np.logaddexp(0.0, -z)) def bce_from_logit(z, y): return np.logaddexp(0.0, z) - y * z y1 np.ones(1) for zval in (-800.0, 800.0): z np.array([zval]) with warnings.catch_warnings(recordTrue) as caught: warnings.simplefilter(always) p sigmoid_naive(z) naive -np.mean(y1 * np.log(p) (1 - y1) * np.log(1 - p)) msgs [f{type(r.message).__name__}: {r.message} for r in caught] print(fz{zval:7.0f} p_naive{p[0]:.6g} loss_naive{naive}) for m in msgs: print( , m) print(f p_stable{sigmoid(z)[0]:.6g} loss_stable{float(bce_from_logit(z, y1)[0]):.6g})z -800 p_naive0 loss_naiveinf RuntimeWarning: overflow encountered in exp RuntimeWarning: divide by zero encountered in log p_stable0 loss_stable800 z 800 p_naive1 loss_naivenan RuntimeWarning: divide by zero encountered in log RuntimeWarning: invalid value encountered in multiply p_stable1 loss_stable0z-800 时exp(800)溢出p 变成 0log(0)再报除零loss 是 infz800 时 p 变成 1log(1-p)得 -inf乘 0 变成0 * -inf结果 nan。用np.logaddexp重写后同样输入给出 800 和 0都是有限值错得离谱罚 800对得离谱不罚。这也是 PyTorch 的BCEWithLogitsLoss直接吃 logit 而不吃概率的原因。手推梯度只比线性回归多一个 sigmoid目标函数取「平均交叉熵 ½λ‖w₁₋‖²」截距 w₀ 不进惩罚项第 4 课坑二讲过J(w) mean[ softplus(z) − y·z ] ½·λ·‖w₁₋‖²其中 z Xw、p sigmoid(z)对 w 求导得 ∇J Xᵀ(p − y)/n λw₁₋和第 3 课的 Xᵀ(Xw − y)/n 几乎一样只是把线性预测换成 sigmoid 输出。没有闭式解只能迭代import numpy as np def sigmoid(z): return np.exp(-np.logaddexp(0.0, -z)) def add_bias(X): return np.column_stack([np.ones(len(X)), X]) def loss_grad(w, A, y, lam): z A w p sigmoid(z) J float(np.mean(np.logaddexp(0.0, z) - y * z) 0.5 * lam * (w[1:] w[1:])) g A.T (p - y) / len(y) g[1:] lam * w[1:] # 截距不惩罚 return J, g def train(A, y, lam, steps6000, lr0.5): w np.zeros(A.shape[1]) for _ in range(steps): _, g loss_grad(w, A, y, lam) w - lr * g return w给X_tr / X_va补上偏置列后扫一遍 λA_tr, A_va add_bias(X_tr), add_bias(X_va) print(f{lambda:9}{train_loss:12}{val_loss:11}{val_acc:10}{|w|:9}) for lam in [0.0, 1e-4, 1e-3, 1e-2, 1e-1, 1.0]: w train(A_tr, y_tr, lam) z A_va w J, _ loss_grad(w, A_va, y_va, lam) acc float(np.mean((z 0) (y_va 1))) nw float(np.linalg.norm(w[1:])) Jtr, _ loss_grad(w, A_tr, y_tr, lam) print(f{lam:9}{Jtr:12.4f}{J:11.4f}{acc:10.3f}{nw:9.3f})lambda train_loss val_loss val_acc |w| 0.0 0.1168 0.1104 0.955 10.943 0.0001 0.1224 0.1178 0.955 10.133 0.001 0.1539 0.1552 0.955 7.206 0.01 0.2588 0.2620 0.948 3.676 0.1 0.4571 0.4543 0.945 1.388 1.0 0.6352 0.6305 0.898 0.305这份数据上 val_loss 单调上升λ0 反而最好没有第 4 课的 U 型谷底。不是算错200 样本对 2 特征本来就不易过拟合交叉熵又是凸的加惩罚只会把权重往 0 拽、抬高 loss。第 4 课的 U 型来自 25 样本拟合 21 列多项式「样本少、特征多」的前提在这里不成立。λ 该不该加看数据是否病态不是习惯性给默认值。真正需要 L2 的场景完全可分数据换一份严格线性可分的数据两簇高斯各 50 个点中心 (-2,-2) 和 (2,2)用 w [0, 1, 1] 直接判就有 100% 准确率。λ 设为 0跑 20 万步import numpy as np def sigmoid(z): return np.exp(-np.logaddexp(0.0, -z)) def make_separable(n, seed): r np.random.default_rng(seed) X np.vstack([r.normal([-2, -2], 0.5, (n // 2, 2)), r.normal([2, 2], 0.5, (n // 2, 2))]) A np.column_stack([np.ones(n), X]) y np.r_[np.zeros(n // 2), np.ones(n // 2)] return A, y A, y make_separable(100, 5) def loss_grad(w, A, y, lam): z A w p sigmoid(z) J float(np.mean(np.logaddexp(0.0, z) - y * z) 0.5 * lam * (w[1:] w[1:])) g A.T (p - y) / len(y) g[1:] lam * w[1:] return J, g for lam in (0.0, 0.01): w np.zeros(A.shape[1]) print(f--- lambda {lam} ---) for step in range(1, 200001): _, g loss_grad(w, A, y, lam) w - 0.5 * g if step in (1, 1000, 10000, 50000, 100000, 200000): J, _ loss_grad(w, A, y, lam) print(fstep{step:7} loss{J:.8f} |w|{np.linalg.norm(w):.4f})--- lambda 0.0 --- step1 loss0.11454533 |w|0.7353 step1000 loss0.00036165 |w|3.0544 step10000 loss0.00004150 |w|4.0377 step50000 loss0.00000898 |w|4.7660 step100000 loss0.00000462 |w|5.0889 step200000 loss0.00000237 |w|5.4169 --- lambda 0.01 --- step1 loss0.11724894 |w|0.7353 step1000 loss0.02342776 |w|1.8037 step10000 loss0.02342775 |w|1.8038 step50000 loss0.02342775 |w|1.8038 step100000 loss0.02342775 |w|1.8038 step200000 loss0.02342775 |w|1.8038λ0 那组 loss 从 0.1145 掉到 0.0000023720 万步后仍在降|w| 从 0.7353 涨到 5.4169也没停。权重整体放大会让每个样本被推得更「确信」loss 无限接近 0 却永远到不了 0——最优解在无穷远处数值上就是解不存在。λ0.01 那组 1000 步就收敛loss 稳在 0.02342775|w| 稳在 1.803810 万步后一位小数没变。岭回归没这问题完全拟合时训练误差能取到 0解是有限值。所以 L2 在分类里的意义比第 4 课更基础一层把「没有解」变成「有唯一解」。这也解释了 scikit-learn 的 LogisticRegression 为何默认带正则C1.0要关掉得显式写Cnp.inf——C 是正则强度倒数越小惩罚越重。版本变化也记一下penalty自 1.8 弃用、1.10 移除改用l1_ratio和 C1.9 的 callbacksProgressBar、ScoringMonitor最先落在 LBFGS 求解器上示例监控的正是d2_log_loss_score。求解器在换手推的目标函数没换。踩坑记录坑二把 0/1 硬标签喂给 log loss。评估时若传predict出来的类别而不是predict_proba的概率一个错判就是-log(0)import warnings import numpy as np with warnings.catch_warnings(recordTrue) as caught: warnings.simplefilter(always) loss -np.mean(np.ones(1) * np.log(0.0)) for r in caught: print(f{type(r.message).__name__}: {r.message}) print(loss , loss)RuntimeWarning: divide by zero encountered in log loss inf坑三特征量纲和截距。第 4 课实测过固定 λ 把某列缩小 100 倍验证误差从 0.4387 涨到 0.6702。逻辑回归同理‖w‖² 优先砍需要大系数的列等于凭空改掉 λ 的含义。罚之前先标准化并把 w₀ 排除在惩罚项外。小结这一课换了三样标签变 0/1、输出过 sigmoid、损失换成平均交叉熵梯度仍是 Xᵀ(p − y)/n λw₁₋和第 3 课几乎一样。新知识是「完全可分 无正则 ⇒ 解不存在」λ 在这里是存在性条件样本充足、特征少时加 λ 反而抬高 loss。建议把make_separable两簇中心距离从 4 改成 1.5 重跑看 λ0 的发散速度怎么变。第 6 课进入分类评估指标准确率在类别不平衡时怎么骗人混淆矩阵、精确率召回率与 log loss 该看哪个。文内所有数字均为本机 Python 3.13.14 numpy 2.5.1 实测输出。
企业数字化 ERP 产品动态
相关推荐
Owlfy技能共享平台:动态代理与沙箱隔离技术解析 1. 项目概述:Owlfy技能共享平台解析最近在开发者社区发现一个挺有意思的开源项目Owlfy,它解决了MCP(模块化技能组件)生态中一个长期存在的痛点——技能复用问题。简单来说,这个工具允许开发者直接从第三方技能商城复制… · 2026/11/5 5:45:11
富士康郑州国企合资项目解析:智能制造与产业升级 1. 富士康郑州国企合资项目背景解析富士康科技集团近期在郑州启动了首个与国有企业合资成立的新公司,同步推进生产线扩建和员工宿舍公寓建设项目。这一"增存并举"的战略举措,标志着富士康在河南的产业布局进入新阶段。作为全球最大的电子制造服… · 2026/11/5 5:45:11
链式哈希表实现原理与C++实战指南 1. 为什么需要自己实现链式哈希表在大学数据结构课程和面试准备中,哈希表都是必考的重点内容。但很多同学只是停留在理论层面,真正动手实现时总会遇到各种问题。我自己在准备面试时,就曾被要求现场手写哈希表实现,当时因为缺乏实际… · 2026/11/5 5:44:11
【adb】AndroidStudio调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 6:31:27
编译过程理解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 6:31:27
零基础实现IEC61850(七)细说数据更新 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 6:31:27
KytyPS5 开源PS5模拟器完全指南:手把手在 PC 上编译并首次启动 KytyPS5 开源PS5模拟器完全指南:手把手在 PC 上编译并首次启动 【免费下载链接】KytyPS5 PlayStation 5 translation layer for Windows, Linux and macOS 项目地址: https://gitcode.com/gh_mirrors/ky/KytyPS5
想不装主机、直接在 PC 上玩 PS5 游戏&#x… · 2026/11/5 6:31:27
SuperSplat 高斯泼溅编辑 3 坑排查指南 SuperSplat 高斯泼溅编辑 3 坑排查指南 【免费下载链接】super-splat 3D Gaussian Splat Editor 项目地址: https://gitcode.com/gh_mirrors/su/super-splat
在浏览器里直接做 3D Gaussian Splat 编辑的人,多半会先碰到 SuperSplat。真正卡人的很少是渲染&am… · 2026/11/5 6:31:27
Google Attestation Key 1 简介
因为最近要用到这个,所以简单写写。
定义: Attestation Key是Android Key Attestation 的信任根,用来给设备TEE/StrongBox里的认证证书链做信任背书,让后端服务器可信地确认:这个密钥真的跑在安全硬件里&… · 2026/11/5 6:30:27
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38