选择题考点

范数

L2 范数(欧几里得范数)计算公式:

∥X∥2=∑i=1nxi2\|X\|_2 = \sqrt{\sum_{i=1}^{n} x_i^2}

即把每个元素平方后求和,再开平方。

范数(norm) 是衡量一个向量”大小”或”长度”的函数。常见的有:

  • L0 范数:非零元素的个数

    ∥x∥0=#{i:xi≠0}\|x\|_0 = \#\{i : x_i \neq 0\}

    严格说它不是真正的范数,但常用于表示稀疏性。

  • L1 范数:绝对值之和

    ∥x∥1=∑i=1n|xi|\|x\|_1 = \sum_{i=1}^{n} |x_i|

    也叫曼哈顿距离,常用于 Lasso 正则化,能让参数变稀疏。

  • L2 范数:平方和开根号

    ∥x∥2=∑i=1nxi2\|x\|_2 = \sqrt{\sum_{i=1}^{n} x_i^2}

    就是欧几里得距离,常用于 Ridge 正则化、权重衰减。

  • L∞ 范数:最大绝对值

    ∥x∥∞=maxi|xi|\|x\|_\infty = \max_i |x_i|

所以 L2 范数只是 Lp 范数中 p=2p=2 的特例。

在机器学习中,L1 正则化容易产生稀疏解,L2 正则化让参数整体变小、更平滑,防止过拟合。

正则化

正则化是什么?

正则化就是在模型原来的损失函数上,再加一个”惩罚项”,限制模型参数不要太大、太复杂。

一般形式:

总损失=原损失+λ⋅正则项\text{总损失} = \text{原损失} + \lambda \cdot \text{正则项}

其中 λ\lambda 控制正则化强度:

  • λ\lambda 越大,惩罚越强,模型越简单;
  • λ\lambda 越小,惩罚越弱,模型越接近不加正则化;
  • λ=0\lambda = 0 就是不正则化。

L1 正则化是什么?

L1 正则化也叫 Lasso,惩罚项是权重绝对值之和:

L1 正则项=∑i|wi|L1 \text{ 正则项} = \sum_i |w_i|

所以目标函数类似:

minw(原损失+λ∑i|wi|)\min_w \left( \text{原损失} + \lambda \sum_i |w_i| \right)

作用:

  • 让很多权重变成 0;
  • 可以做 特征选择;
  • 适合高维数据,比如特征很多但真正有用的很少。

为什么能让权重变 0?

因为 L1 的约束区域是”菱形”,有尖角,最优解很容易落在坐标轴上。
一旦某个权重落在坐标轴上,它就变成 0。

直观理解

L1 正则化像是告诉模型:

不要用太多特征,能不用就不用。

所以它产生的是 稀疏解。

L2 正则化是什么?

L2 正则化也叫 Ridge 回归,在神经网络里常叫 权重衰减。
它的惩罚项是权重平方和:

L2 正则项=∑iwi2L2 \text{ 正则项} = \sum_i w_i^2

目标函数类似:

minw(原损失+λ∑iwi2)\min_w \left( \text{原损失} + \lambda \sum_i w_i^2 \right)

有时会写成 λ2∑iwi2\frac{\lambda}{2}\sum_i w_i^2,只是为了求导方便。

作用:

  • 让所有权重整体变小;
  • 不让某个特征权重特别大;
  • 降低模型方差,提高泛化能力;
  • 缓解过拟合;
  • 对病态矩阵问题也有改善。

为什么有效?

L2 正则化相当于给权重加了一个”约束球”,让解不要跑得太远。
它不会像 L1 那样把权重压成 0,而是让权重变得平滑、均匀地小。

直观理解

L2 正则化像是告诉模型:

你可以用很多特征,但每个特征的权重别太夸张。

所以它产生的是 非稀疏的小权重。

L1 和 L2 对比

对比项 L1 正则化 L2 正则化
别名 Lasso Ridge、权重衰减
惩罚项 ∑i|wi|\sum_i \lvert w_i \rvert ∑iwi2\sum_i w_i^2
权重特点 很多变成 0 整体变小,但很少为 0
是否稀疏 是 否
特征选择 可以 不可以
可导性 在 0 处不可导 处处可导
优化 常用坐标下降、近端梯度 梯度下降、闭式解
适用场景 高维、稀疏特征 一般防过拟合、共线性

为什么要做正则化?

核心原因:防止过拟合。

模型如果太复杂,参数太大,就会:

  • 记住训练数据里的噪声;
  • 在新数据上表现很差;
  • 对输入的小变化非常敏感。

正则化通过限制参数大小或数量,让模型更简单。
这符合奥卡姆剃刀原则:

能简单解决问题,就不要用复杂模型。

另外,从贝叶斯角度看:

  • L2 正则化对应权重服从 高斯先验;
  • L1 正则化对应权重服从 拉普拉斯先验。

L2 范数与 L2 正则化的区别

  • L2 范数:是长度,确实要开根号

    ∥w∥2=∑iwi2\|w\|_2 = \sqrt{\sum_i w_i^2}

  • L2 正则化:通常用的不是 L2 范数本身,而是 L2 范数的平方

    Ω(w)=∥w∥22=∑iwi2\Omega(w) = \|w\|_2^2 = \sum_i w_i^2

    有时写成 12∥w∥22\frac{1}{2}\|w\|_2^2,只是为了求导方便。

tanh 激活函数

定义

tanh 激活函数:

tanh⁡(x)=ex−e−xex+e−x\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}

也可以写成:

tanh⁡(x)=e2x−1e2x+1\tanh(x) = \frac{e^{2x} - 1}{e^{2x} + 1}

导数

ddxtanh⁡(x)=1−tanh⁡2(x)\frac{d}{dx}\tanh(x) = 1 - \tanh^2(x)

等价形式:

ddxtanh⁡(x)=sech⁡2(x)=1cosh⁡2(x)\frac{d}{dx}\tanh(x) = \operatorname{sech}^2(x) = \frac{1}{\cosh^2(x)}

它的导数范围是:

0<tanh⁡′(x)≤10 < \tanh'(x) \le 1

在 x=0x = 0 时最大:

tanh⁡′(0)=1\tanh'(0) = 1

当 xx 远离 0 时,导数迅速接近 0。

输入正无穷或负无穷会梯度爆炸吗?

不会梯度爆炸,反而会梯度消失。

因为:

limx→+∞tanh⁡(x)=1,limx→−∞tanh⁡(x)=−1\lim_{x \to +\infty} \tanh(x) = 1, \qquad \lim_{x \to -\infty} \tanh(x) = -1

所以:

limx→±∞tanh⁡′(x)=1−(±1)2=0\lim_{x \to \pm\infty} \tanh'(x) = 1 - (\pm 1)^2 = 0

也就是说:

  • 输入正无穷:输出趋近 1,导数趋近 0;
  • 输入负无穷:输出趋近 -1,导数趋近 0;
  • 在饱和区,梯度几乎为 0。

因此 tanh 本身不会导致梯度爆炸,它的导数最大只有 1,反而容易造成梯度消失。

梯度爆炸通常来自权重矩阵过大、连乘层数太多等原因,而不是 tanh 直接造成的。

值域

tanh⁡(x)∈(−1,1)\tanh(x) \in (-1, 1)

也就是值域是 (−1,1)(-1, 1)。

它取不到 -1 和 1,只能无限接近。

相比 sigmoid 的值域 (0,1)(0, 1),tanh 是零中心的,输出有正有负,通常更有利于神经网络优化。

会用在 LSTM 中吗?

会,而且 LSTM 中非常常用。

LSTM 里通常有两类激活函数:

  • sigmoid:用于门控,比如输入门、遗忘门、输出门,因为门需要输出 0 到 1 之间的值;
  • tanh:用于生成候选记忆和输出隐藏状态。

相关选择题:ReLU 换成 tanh

把 ReLU 替换成 tanh,可能会导致什么?
A. 神经元死亡
B. 梯度消失
C. 计算复杂
D. 过拟合

正确答案:B. 梯度消失

选项 分析
A. 神经元死亡 这是 ReLU 的典型问题:负区间梯度为 0,神经元可能永久不更新。tanh 不会”死亡”。

对比 ReLU 和 tanh

激活函数 优点 缺点
ReLU 正区间导数恒为 1,缓解梯度消失;计算简单 负区间梯度为 0,可能神经元死亡
tanh 零中心,输出有正有负 饱和区导数趋近 0,容易梯度消失;计算稍复杂

ReLU 激活函数

ReLU 是什么?

ReLU 的公式非常简单:

ReLU(x)=max⁡(0,x)\mathrm{ReLU}(x) = \max(0, x)

也就是说:

  • 如果 x>0x > 0,输出就是 xx,导数(梯度)是 1;
  • 如果 x≤0x \le 0,输出就是 0,导数(梯度)是 0。

图像上,正半轴是直线 y=xy = x,负半轴是 y=0y = 0。

“负区间梯度为 0”是什么意思?

在神经网络中,一个神经元通常先计算线性部分:

z=w1x1+w2x2+⋯+bz = w_1 x_1 + w_2 x_2 + \cdots + b

然后经过激活函数:

a=ReLU(z)a = \mathrm{ReLU}(z)

反向传播时,我们需要计算损失对权重 ww 的梯度。根据链式法则:

∂L∂w=∂L∂a⋅∂a∂z⋅∂z∂w\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w}

其中:

∂a∂z=ReLU′(z)\frac{\partial a}{\partial z} = \mathrm{ReLU}'(z)

而 ReLU 的导数是:

ReLU′(z)={1,z>00,z≤0\mathrm{ReLU}'(z) = \begin{cases} 1, & z > 0 \\ 0, & z \le 0 \end{cases}

所以,当 z≤0z \le 0 时,梯度为 0。

这意味着:

如果某个神经元的线性输出 zz 总是小于等于 0,那么在反向传播时,这个神经元对权重的梯度永远是 0。

梯度为 0,权重就不会更新:

w←w−η⋅0=ww \leftarrow w - \eta \cdot 0 = w

为什么可能”永久不更新”?

假设某个神经元一开始的权重和偏置不好,导致对于训练集中所有样本,它的线性输出 zz 都是负数。

那么:

  • 前向传播:输出永远是 0;
  • 反向传播:梯度永远是 0;
  • 权重更新:永远不变。

既然权重不变,那么 zz 仍然对所有样本都是负数,输出还是 0,梯度还是 0……

这就形成了一个死循环:

z≤0⇒输出 0⇒梯度 0⇒权重不变⇒z 仍然≤0z \le 0 \Rightarrow \text{输出 } 0 \Rightarrow \text{梯度 } 0 \Rightarrow \text{权重不变} \Rightarrow z \text{ 仍然} \le 0

这个神经元就相当于”死掉了”,不再参与学习。这就是”神经元死亡”。

为什么会发生这种情况?

常见原因:

  1. 学习率太大
    一次更新把权重推得太远,导致很多神经元的输入变成负数。
  2. 初始化不好
    权重初始化使得某些神经元一开始就进入负区间。
  3. 数据分布问题
    某些特征总是导致该神经元的加权和为负。

一旦死亡,这个神经元对任何输入都输出 0,相当于从网络中”消失”了。

有什么影响?

  • 网络的有效容量变小,因为死掉的神经元不再起作用。
  • 模型表达能力下降,可能欠拟合。
  • 如果大量神经元死亡,训练会变差。

怎么缓解?

常用的改进版 ReLU:

  • Leaky ReLU:负区间给一个很小的斜率,比如 0.01

    f(x)=max⁡(0.01x,x)f(x) = \max(0.01x, x)

    这样负区间梯度不为 0,神经元不容易死。

  • Parametric ReLU (PReLU):负区间的斜率作为可学习参数。

  • ELU:负区间平滑,有非零梯度。

  • 小心设置学习率:不要太大。

  • 合适的初始化:如 He 初始化。

总结

“ReLU 的典型问题:负区间梯度为 0,神经元可能永久不更新”意思是:

当 ReLU 神经元的输入为负时,输出为 0,导数也为 0。反向传播时梯度为 0,权重无法更新。如果这个神经元对所有输入都处于负区间,它就会一直输出 0,永远学不到东西,相当于”死亡”。

所以 ReLU 虽然计算简单、能缓解梯度消失,但存在 Dead ReLU 的风险。

学习率与 batch size

学习率 / batch size 越大,泛化性不一定越差。
它和泛化性不是简单的单调关系,而是存在一个”合适范围”。太小或太大都可能不好。

为什么关注 η/B?

SGD 的更新是:

θt+1=θt−η⋅1B∑i=1B∇Li\theta_{t+1} = \theta_t - \eta \cdot \frac{1}{B} \sum_{i=1}^{B} \nabla L_i

其中:

  • η\eta:学习率
  • BB:batch size

梯度估计的噪声大小,通常和下面这个量有关:

ηB\frac{\eta}{B}

可以把它理解为 每单位数据的学习步长,也叫 噪声尺度。

  • η/B\eta/B 越大:更新噪声越大,参数跳得越厉害;
  • η/B\eta/B 越小:更新越平滑,越接近全批量梯度下降。
η/B 效果
太小 噪声小,易收敛到尖锐极小值,泛化可能差
适中 噪声合适,易找到平坦极小值,泛化较好
太大 训练不稳定,可能发散,泛化差

学习率 / batch size 越大,泛化性不一定会越差。
在一定范围内,适当增大反而可能提升泛化;但过大时会导致训练不稳定,泛化变差。
关键是要让 η/B\eta/B 处于合适范围,而不是一味追求大或小。

神经元

在神经网络里,神经元通常指人工神经元,也叫节点、单元。它是网络的基本计算单元。

一句话概括:

单个神经元:多个输入,一个输出;一层神经元:多个输入,多个输出。

归纳偏置

归纳偏置(inductive bias,也叫归纳偏好)是机器学习中的一个核心概念。简单说:

归纳偏置是模型在学习过程中,对”什么样的规律更可能成立”所做出的先验假设或偏好。

它不是从训练数据里学来的,而是模型结构、算法或正则化项本身自带的。

为什么需要归纳偏置?

机器学习的目标是:从有限训练数据中,学到一个能泛化到新数据的规律。

但问题是:

  • 给定有限个训练样本,能拟合它们的函数有无数个;
  • 这些函数在训练集上表现一样,但在新数据上可能完全不同;
  • 模型必须选一个。

靠什么选?就靠归纳偏置。

举个例子:

  • 给你几个点,你可以用直线拟合,也可以用复杂曲线穿过每个点;
  • 线性回归的归纳偏置是”数据关系大致是线性的”;
  • 决策树的归纳偏置是”决策边界是轴对齐的矩形划分”;
  • CNN 的归纳偏置是”图像特征具有局部性、平移等变性”。

没有归纳偏置,学习算法就无法从有限数据推广到未知数据。

常见模型的归纳偏置

模型 归纳偏置
线性回归 输出与输入近似线性关系
逻辑回归 线性决策边界,概率由 sigmoid 给出
决策树 轴对齐划分,局部常量预测
KNN 相近样本有相近标签,局部平滑
SVM 最大间隔,偏好简单边界
CNN 局部连接、权重共享、平移等变性
RNN 顺序依赖、时间权重共享、平稳性
Transformer 全局交互、参数共享、弱局部性、无位置编码时置换等变
L2 正则化 偏好较小的权重
L1 正则化 偏好稀疏权重

所以,几乎任何模型都有归纳偏置,区别只是强弱和类型不同。

强归纳偏置 vs 弱归纳偏置

强归纳偏置

模型自带很强的假设,比如 CNN:

  • 假设局部性;
  • 假设平移不变/等变;
  • 参数共享。

优点:

  • 数据少时也能学好;
  • 训练快;
  • 对图像任务很合适。

缺点:

  • 如果任务不符合这些假设,性能会受限;
  • 不够灵活。

弱归纳偏置

模型假设少,比如标准 Transformer:

  • 全局注意力,不假设局部性;
  • 不假设顺序,需要位置编码;
  • 参数共享,但注意力模式由数据决定。

优点:

  • 灵活,表达能力强;
  • 数据量大时能学到复杂规律。

缺点:

  • 需要更多数据;
  • 训练成本高;
  • 容易过拟合,需要正则化。

所以之前说”Transformer 归纳偏置弱”,意思是它不像 CNN 那样内置了强烈的局部性和平移不变性,而是更依赖数据和位置编码来学习结构。

一句话:

归纳偏置就是模型自带的”世界观”,决定了它更容易学到什么样的规律。

全 0 初始化

在做图像分类时,如果指的是多层神经网络或卷积神经网络(CNN),把参数全部初始化为 0,通常不能正常训练,或者说训练会失败。

核心原因是:对称性无法被打破。

为什么全 0 初始化不行?

假设一个隐藏层有两个神经元,它们的权重和偏置都初始化为 0:

w1=w2=0,b1=b2=0w_1 = w_2 = 0, \quad b_1 = b_2 = 0

对于同一个输入 xx,两个神经元的输出完全相同:

z1=w1x+b1=0,z2=w2x+b2=0z_1 = w_1 x + b_1 = 0, \qquad z_2 = w_2 x + b_2 = 0

经过激活函数后:

a1=a2a_1 = a_2

反向传播时,它们得到的梯度也完全相同:

∂L∂w1=∂L∂w2\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial w_2}

于是更新后:

w1=w2w_1 = w_2

仍然完全一样。

这样,这一层的多个神经元永远保持相同,等价于只有一个神经元。网络失去了表达多个不同特征的能力,无法学习复杂的图像特征。

这就是所谓的对称性问题。

C++:struct vs class

在 C++ 中,struct 和 class 几乎完全一样,唯一本质区别是:

默认访问权限和默认继承权限不同。

核心区别:

对比项 struct class
默认成员访问权限 public private
默认继承权限 public private

C++:浅拷贝 vs 深拷贝

对比项 浅拷贝 深拷贝
复制内容 只复制成员值 复制指针指向的数据
指针成员 两个对象共享同一内存 各自独立内存
风险 重复释放、悬空指针 安全,但开销大
默认行为 默认拷贝构造是浅拷贝 需要自己实现

C++:抽象类

含有纯虚函数的类叫抽象类。纯虚函数写法:

virtual void func() = 0;

抽象类不能实例化对象,只能作为基类被继承。

class Animal {
public:
    virtual void speak() = 0;  // 纯虚函数
};
// Animal a;  // 错误,不能创建抽象类对象

指向抽象类的指针

虽然不能创建抽象类对象,但可以定义指向抽象类的指针或引用,用来实现多态。

class Dog : public Animal {
public:
    void speak() override { cout << "汪汪"; }
};
Animal* p = new Dog();  // 基类指针指向派生类对象
p->speak();             // 调用 Dog::speak

所以”抽象指针”如果指这个,就是:指向抽象基类的指针,用于多态调用。

C++:虚函数

虚函数是用 virtual 修饰的成员函数,允许在派生类中重写,并通过基类指针或引用调用时,动态绑定到实际对象类型的函数。

class Base {
public:
    virtual void show() { cout << "Base"; }
};
class Derived : public Base {
public:
    void show() override { cout << "Derived"; }
};
Base* p = new Derived();
p->show();  // 输出 Derived,而不是 Base

关键点:

  • 实现运行时多态。
  • 基类指针指向派生类对象时,调用的是派生类版本。
  • 有虚函数表的开销。
  • 析构函数常声明为虚函数,防止派生类资源泄漏。

C++:派生类

派生类是通过继承从已有类(基类)创建的新类。

class Base {
public:
    int a;
};
class Derived : public Base {  // Derived 是派生类
public:
    int b;
};

派生类:

  • 继承基类的成员(公有继承时,public 成员可访问)。
  • 可以添加自己的成员。
  • 可以重写基类的虚函数。
  • 可以有多个基类(多重继承)。

派生类对象包含基类部分和自身部分。

C++:模板

模板是 C++ 的泛型编程工具,让函数或类可以适用于多种数据类型,而不必为每种类型重写代码。

函数模板

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}
int x = max(3, 5);
double y = max(2.5, 1.8);

类模板

template <typename T>
class Box {
    T value;
public:
    Box(T v) : value(v) {}
    T get() { return value; }
};
Box<int> b1(10);
Box<string> b2("hello");

模板在编译时实例化,生成对应类型的代码。

C++:智能指针

C++11 中三种主要的智能指针是:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

它们都在头文件:

#include <memory>

你写的 shared、weak、unique 基本对应这三个。

std::unique_ptr:独占所有权

  • 同一时间只能有一个 unique_ptr 拥有某个对象。

  • 不能拷贝,只能移动:

    std::unique_ptr<int> p1 = std::make_unique<int>(10);
    // std::unique_ptr<int> p2 = p1; // 错误,不能拷贝
    std::unique_ptr<int> p2 = std::move(p1); // 可以移动

  • 离开作用域时自动释放对象。

  • 开销小,几乎和裸指针一样。

  • 适合”一个对象只归一个所有者”的场景。

std::shared_ptr:共享所有权

  • 多个 shared_ptr 可以指向同一个对象。

  • 内部有引用计数,最后一个 shared_ptr 销毁时释放对象。

    std::shared_ptr<int> p1 = std::make_shared<int>(10);
    std::shared_ptr<int> p2 = p1; // 引用计数变为 2

  • 适合多个地方需要共享同一个对象的场景。

  • 注意:引用计数操作是线程安全的,但对象本身的读写不是线程安全的。

  • 可能产生循环引用问题。

std::weak_ptr:弱引用,不增加引用计数

  • weak_ptr 指向由 shared_ptr 管理的对象,但不增加引用计数。

  • 它不影响对象生命周期。

  • 主要用来解决 shared_ptr 的循环引用问题。

  • 不能直接访问对象,需要先 lock() 提升为 shared_ptr:

    std::shared_ptr<int> sp = std::make_shared<int>(10);
    std::weak_ptr<int> wp = sp;
    if (auto locked = wp.lock()) {
        std::cout << *locked << std::endl;
    } else {
        std::cout << "对象已释放" << std::endl;
    }

三者对比

智能指针 所有权 引用计数 能否拷贝 主要用途
unique_ptr 独占 无 不能,只能移动 独占资源管理
shared_ptr 共享 有 可以 多个所有者共享对象
weak_ptr 不拥有 不增加计数 可以 观察 shared_ptr,解决循环引用

C++:new 与堆内存

在 C++ 中,new 用来在堆(heap)上动态分配内存,并返回指向该内存的指针。
如果直接用裸 new 分配,必须自己手动释放,否则会造成内存泄漏。但现代 C++ 推荐用智能指针或容器,它们会自动释放。


选择题考点
https://mingsm17518.github.io/2026/09/24/秋招学习清单/选择题考点/
作者
Ming
发布于
2026年9月24日
许可协议