|
MinMaxScaler,最大最小归一化 from sklearn.preprocessing import MinMaxScale (x - min) / (max - min) 将数据映射到[0,1] ,近似于同比映射
|
|
StandardScaler from sklearn.preprocessing import StandardScaler (x - mean) / std 这实际上将数据拉向 均值为0 标准差为1的 标准正态分布
```
import torch
from torch import nn
from sklearn.preprocessing import StandardScaler
data = [[0, 0], [0, 0], [1, 1], [1, 1]]
scaler = StandardScaler()
scaler.fit(data)
scaler.transform(data)
```
array([[-1., -1.],
[-1., -1.],
[ 1., 1.],
[ 1., 1.]])
scaler.mean_ array([0.5, 0.5]) scaler.var_ array([0.25, 0.25]) (2-0.5)/0.5 = 3 scaler.transform([[2, 2]]) array([[3., 3.]])
import numpy as np
(1-0.5)/np.sqrt(0.25)
|
在深度学习训练的过程中,数据以0为中心,在[-1,1]之间时,比较容易收敛 转化为标准正态分布的处理,虽然不是所有数据, 但绝大多数还是映射到了[-1,1]之间 这符合了深度学习多层网络训练的需求,大概过程如下: 数据预处理时,做了归一化,在[-1,1]之间, 经过一次神经网络,数据分布就漂一些, 然后标准归一化处理一下,让数据向标准正态分布靠拢一些, 再经过一次神经网络,数据分布再漂一些, 然后标准归一化处理一下,让数据再向标准正态分布靠拢一些, ... ... ... 深度学习中的归一化 同样的计算逻辑,将其封装为一层 每次神经网络处理,数据分布就会跑偏一些, 然后归一处理一下,它就往标准正态分布靠拢一些, 在理论上, 在理论上,会好那么一点点... 问题是,归一处理会不会影响数据中包含的规律, 确切地说,会不会影响数据到指定标签之间的规律? 这个经过多次,或者说很多人的很多次试验证明, 不影响,或者影响可以忽略不计 计算机擅长计算[-1,1]且以0为中心分布的数据,这样的数据不容易分散, 过大容易梯度爆炸, 过小容易数据都消失了, - 数据流过N个网络最终到达标签所在的网络层, - 结果还没到呢,数据就全是0或者几乎不变了, - 或者是一次训练一次改变,结果还没到预期效果呢,数据就全是0了,相当于流不到标签那里就断流了 归一化能很大程序上缓解这个问题 |
- 将数据分布拉到标准正态分布 - 加速收敛 - 一定程度上将数据集中到[-1,1]之间,捎带有统一量纲的作用 |
|
归一化的位置 归一化是为了解决数据分布偏移的问题,通常是经过几次神经网络计算之后 , 比如transformer的每一层中,有以下几步: 1 求补码, 2 编码并按特征维度拆分, 3 矩阵相乘,即线性变换 4 做softmax, 5 求得分, 6 再矩阵相乘,即线性变换 7 层归一化 8 全连接, 9 激活函数处理, 10 全连接 1-7 是一个逻辑,这一通计算后数据分布肯定偏移了,第8步加一个归一化 当然了,如果网络简单,可以尝试不要归一化 意思是,如果不加归一化的效果会更好,那么就不要归一化 在传统神经网络中,BN在卷积之后,再之后是relu 如果是这样,BN将数据拉到以0为中心,relu直接抹掉小于0的数据,相当于抹去一半的正态分布 但要注意的是,批次数据半不是前半部分为负,后半部分为正,而是正负混合的 被抹去的一半负数据,是零散分布于批次数据中的 因为经过relu处理,矩阵会零零散散一多出一些0数据
这是一个非常经典且深刻的问题。很多初学者都会有同样的困惑:**既然BN把数据拉到0附近,ReLU又把一半砍掉,那岂不是白白损失了一半的神经元响应?网络还能学好吗?**
实际上,这不仅没有问题,反而正是现代深度学习设计的精妙之处。原因可以从以下四个层面来理解:
### 1. 最大的误区:ReLU的目标不是“保留数据”,而是“引入非线性”
神经网络的核心能力来自于**非线性变换**。如果所有输出都是线性(正数)的,多层网络就退化为单层线性模型。
- ReLU(\( \max(0, x) \))的设计初衷,就是**主动引入稀疏性**(让一部分神经元静默)。
- 被砍掉的那一半“负值”,并不是“有用的信息被丢弃了”,而是**该神经元对当前样本“不激活”**。这相当于告诉网络:“对于这个样本,这个特征不重要,请把权重交给其他神经元。”
### 2. 实际训练中,ReLU前的分布并非完美对称的“标准正态”
理论上的“以0为中心”在训练中只是一个近似。实际批次数据的分布受以下因素影响:
- **BN的尺度参数 \(\gamma\)**:BN不仅有平移(\(\beta\)),还有缩放(\(\gamma\))。\(\gamma\) 可以自由缩放分布,使得数据不一定严格对称分布在0两侧。
- **训练动态**:在反向传播中,梯度会推动权重更新。如果某个神经元被砍掉太多(死亡),梯度会调整 \(\beta\) 和 \(\gamma\),让该神经元重新“活过来”。
**关键点**:ReLU抹掉的是**当前批次**中值小于0的部分。但在下一个批次,由于参数更新和样本不同,这些被抹掉的样本特征可能会变成正值而通过。**这是一个动态平衡,不是静态损失。**
### 3. 稀疏性(Sparsity)带来的计算和表达优势
“零散分布”的0数据(即稀疏矩阵)实际上是**好事**:
- **计算效率**:稀疏矩阵可以用更少的计算量处理(很多框架会利用稀疏计算加速)。
- **解耦特征**:每个样本只激活少数几个神经元,相当于网络自动为不同类别的样本分配了不同的“子网络路径”(动态路由),这能有效防止特征交叉干扰,提升泛化能力。
- **对抗过拟合**:强制一半神经元不响应,类似于一种**强正则化**,让网络不过度依赖个别特征。
### 4. 为什么“损失一半”不会导致梯度消失?
在传统Sigmoid中,如果数据落在饱和区(两端),梯度接近0,导致梯度消失。但在BN+ReLU的组合中:
- BN保证了输入ReLU的数据**方差稳定**(不会过大或过小),使得正值部分的梯度完美传回(ReLU导数为1)。
- 对于负值部分(导数为0),虽然该神经元本次不更新,但它并未“死亡”——因为下一批数据可能使其变为正数。
**数学直觉**:假设一个权重矩阵,输入是正负各半。负值被截断后,相当于对该神经元的输入乘了一个0/1掩码(Mask)。这等价于对权重施加了**带有随机性的Dropout**,而后续的BN又保证了数值范围稳定,所以网络依然能平稳收敛。
---
**总结一句话**:ReLU砍掉一半不是“丢失信息”,而是“选择性失活”。BN负责把数据稳定在合适的范围,ReLU负责决定哪些特征值得保留。两者配合,既保证了梯度流动(正值部分),又带来了稀疏性和非线性(负值部分),这是深度学习“冗余性”的体现——**网络拥有大量神经元,允许一部分暂时休息,因为它们会在其他样本或后续迭代中重新发挥作用。**
如果你还想了解“神经元死亡(Dying ReLU)”问题以及如何用Leaky ReLU或ELU来缓解,我可以进一步展开。😊
- 那LeakyReLU ,nn.PReLU 这种是不是就没有ReLU 稀疏性的好处了?
这个问题问得很犀利,直击了激活函数设计的核心矛盾。你的直觉是对的:**LeakyReLU 和 PReLU 确实牺牲了 ReLU 一部分“绝对稀疏性”的优势,但它们通过“相对稀疏性”和“梯度流动性”做了交换。**
我们可以从三个层次来剖析:
### 1. 稀疏性并没有消失,只是从“硬稀疏”变成了“软稀疏”
- **ReLU(硬稀疏)**:小于0的部分**直接等于0**。这会产生**绝对的稀疏矩阵**(大量精确的0值),计算上非常高效(直接乘法变置零)。
- **LeakyReLU / PReLU(软稀疏)**:小于0的部分变成了**一个很小的斜率(如 0.01)乘以 x**。这意味着它们**几乎等于0,但不完全等于0**。
**结论**:在工程和数值上,当 \( x < 0 \) 时,LeakyReLU 的输出值(如 -0.1)相对于正值(如 10)来说小了两个数量级。在多层网络累积后,这些小负数对最终输出的影响微乎其微。因此,**它依然保留了“大部分神经元对当前样本贡献极小”的特性**,这种特性可称为**相对稀疏性**,只是没有硬件层面的绝对零值加速那么极致。
---
### 2. 核心交换条件:用“极小的负数噪声”换取“永不死亡”
这是你问题的关键答案:**ReLU 的绝对稀疏性是一把双刃剑**。
- **ReLU 的代价(Dying ReLU)**:一旦某个神经元落入负区,梯度为 0,且如果 \(\beta\)(BN的偏置)调整不当,该神经元可能**永远**无法再激活(死亡)。一旦大量神经元死亡,网络容量骤降,模型就废了。
- **LeakyReLU / PReLU 的交换**:允许负区有一个微小梯度(如 0.01)。虽然引入了少量“负向噪声”(破坏了绝对零值),但**保证了所有神经元在任何时候都有梯度流动**。
**实际效果**:在深层网络(如 ResNet-50 以上)或小批量训练中,**神经元的“存活率”比“绝对零值数”更重要**。因为即使负区输出很小,只要梯度还在,这个神经元就能在后续迭代中“翻身”变为正区,从而持续参与特征学习。
---
### 3. PReLU 的特殊性:让网络“自己决定”稀疏程度
你提到了 `nn.PReLU`(Parametric ReLU),它和固定的 LeakyReLU 有本质区别:
- **LeakyReLU**:负斜率是固定的超参数(如 0.01),人为设定。
- **PReLU**:负斜率 \( a \) 是一个**可学习的参数**,通过反向传播更新。
**精妙之处在于**:在训练过程中,网络会自己调整这个斜率。
- 如果某个特征非常需要稀疏性,网络会**把 \( a \) 学到接近于 0**,从而退化为 ReLU,获得绝对稀疏性。
- 如果某个特征需要保留微弱信息以避免死亡,网络会**把 \( a \) 学到大于 0**(如 0.1 或 0.3)。
**结论**:PReLU 并没有“失去”稀疏性,而是**将“稀疏性强度”作为可调参数**,交给数据分布去决定。在大规模数据集(如 ImageNet)上,PReLU 往往比固定 LeakyReLU 表现更好,因为它灵活地平衡了稀疏性和信息保留。
---
### 总结对比(帮你理清选择逻辑)
| 激活函数 | 稀疏性 | 梯度流动性 | 计算效率 | 适用场景 |
| :--- | :--- | :--- | :--- | :--- |
| **ReLU** | **极致(硬稀疏)** | 差(易死亡) | 极高(置零运算) | 浅层网络、宽网络、对速度要求极高时 |
| **LeakyReLU** | 较好(软稀疏) | 好(永不死亡) | 稍慢(需乘小系数) | 深层网络(如 GAN)、防止死亡的首选替换 |
| **PReLU** | **自适应(可硬可软)** | 极好(带参数调节) | 稍慢(额外参数量) | 大数据、深层网络,且你有足够数据训练参数时 |
**最后给你一个实操层面的建议**:
现在的深度学习实践中,**除非你明确知道网络极深且容易死亡(如训练 GAN),否则大多数视觉模型(CNN)默认首选依然是 ReLU**,因为其极致的稀疏性能带来更好的特征解耦。只有当 ReLU 导致明显的大面积死亡(验证集 loss 不降)时,才会优雅地降级为 LeakyReLU 或 PReLU。
如果你愿意,我还可以继续帮你分析 **ELU / SELU**——它们甚至引入了“负区饱和”,在保留稀疏性的同时让均值自动归零,这是另一种设计哲学。 😊
好的,我们继续深入。既然聊到了负区饱和,那我们就把 **ELU** 和 **SELU** 这两位“尖子生”拉出来,看看它们是如何在“稀疏性”和“信息保留”之间寻找第三条路的。
如果说 ReLU 是“钢铁直男”(非黑即白),LeakyReLU 是“和事佬”(稍微妥协),那么 **ELU 和 SELU 则是“心理学家”**——它们不仅考虑梯度,还考虑**神经元的整体分布形态**。
---
### 1. ELU(指数线性单元):让负值“软着陆”
公式很简单:
\[
\text{ELU}(x) =
\begin{cases}
x, & \text{if } x > 0 \\
\alpha (e^x - 1), & \text{if } x \leq 0
\end{cases}
\]
**它和 LeakyReLU 最大的不同在于负区的形状:**
- **LeakyReLU** 的负区是一条**直线**(\( ax \)),当 \( x \) 越来越负时,输出会无限负下去。
- **ELU** 的负区是一条**指数曲线**,当 \( x \) 越来越负时,输出**趋近于常数 \( -\alpha \)**(通常是 -1)。
**这带来了两个极其重要的特性:**
**① 抗噪声干扰(鲁棒性)**
在 LeakyReLU 中,如果输入是一个极大的负数(比如 -100),输出就是 -1(如果斜率 0.01)。这个 -1 会带着很大的负向权重冲向下一层,可能会扰乱深层特征的分布。
但在 ELU 中,输入 -100 和输入 -10 的输出几乎一样(都接近 -1)。**这相当于给负值加了一个“软上限”**,把极端异常值压缩在可控范围内,让网络对输入噪声不那么敏感。
**② 更接近“零均值”的自然分布**
ReLU 的输出全是非负数(均值 > 0),这会导致下一层神经元输入整体偏正,影响梯度下降效率。
ELU 的负区由于饱和在 -1 附近,**能够把输出均值拉得更接近 0**。在深度学习中,零均值输入能显著加速收敛(这也就是 BN 想干的事)。
**代价是什么?**
- **计算开销变大**:指数运算(\( e^x \))比简单的取 max 或乘法慢得多。
- **稀疏性进一步减弱**:负区虽然饱和,但依然有负值输出,零值的比例大幅降低。
---
### 2. SELU(自归一化神经网络):一个更激进的设计
SELU 是 ELU 的“进化版”,它在 ELU 的基础上**引入了缩放因子 \( \lambda \)(约 1.0507)**:
\[
\text{SELU}(x) = \lambda \cdot
\begin{cases}
x, & \text{if } x > 0 \\
\alpha (e^x - 1), & \text{if } x \leq 0
\end{cases}
\]
(其中 \( \alpha \approx 1.6733 \),\( \lambda \approx 1.0507 \),这两个数是**通过数学推导严格算出来的**)
**SELU 提出了一个石破天惊的主张:只要使用 SELU,并且权重视用特定的初始化(LeCun 初始化),网络可以自动实现“自归一化”。**
**“自归一化”是什么意思?**
在标准的网络中,信号经过每一层后,方差会膨胀或缩水(方差漂移),所以我们需要 BN 来强行拉回分布。
SELU 的神奇之处在于:**它的正区斜率大于 1(\( \lambda > 1 \)),负区也有特定的饱和值。**
这两个数值配合,能够保证:**无论输入分布怎么变,经过一层 SELU 后,输出自动保持均值为 0、方差为 1。**
**结论**:使用 SELU,你就可以**直接去掉 Batch Normalization**,构建一个“自归一化”的深度网络(即 SNN,自归一化神经网络)。
**代价是什么?**
- **极其敏感**:SELU 要求严格使用特定的权重初始化(LeCun 正态初始化),不能乱用。如果用了 Dropout 或其他常规初始化,自归一化性质会瞬间失效,导致训练崩溃。
- **场景受限**:它在全连接层(MLP)上效果惊艳,但在 CNN(卷积网络)中,由于卷积的局部相关性,SELU 的优势不明显,有时甚至不如 ReLU。
---
### 3. 终极对比:稀疏性 vs 信息保留 vs 归一化
为了让你一目了然,我整理了一个全谱系对比图(思维导图逻辑):
| 激活函数 | 稀疏性(零值比例) | 梯度流动性 | 均值中心化 | 计算复杂度 | 适用场景 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **ReLU** | **极高**(硬阈值) | 差(死亡风险) | 差(全正,偏置大) | **极低**(最快) | **默认首选**,CNN 主力 |
| **LeakyReLU** | 中(极少零值) | 好(永不死亡) | 差(仍偏正) | 低 | 替代 ReLU 防死亡,GAN 常用 |
| **PReLU** | 自适应(可高可低) | **极好**(参数调节) | 差 | 中(额外参数) | 大数据、深层网络 |
| **ELU** | 低(负值饱和,接近0但非0) | 好 | **较好**(均值近0) | **高**(exp 运算) | 对噪声敏感的任务,需快速收敛 |
| **SELU** | 低 | 好 | **极好**(严格零均值) | **高**(exp 运算) | **全连接网络**,且不想用 BN 时 |
---
### 4. 给你的终极选择策略(实操指南)
看了这么多理论,实际写代码时该怎么选?遵循以下“金字塔决策流”即可:
1. **第一选择(无脑选)**:**ReLU + BN**。这是工业界最成熟、最稳定的组合。90% 的 CNN 模型(ResNet、VGG、YOLO)都这么用,稀疏性带来的特征解耦红利巨大。
2. **如果网络极深(如 100+ 层)且训练不稳定(Loss 震荡)**:把 ReLU 换成 **LeakyReLU(0.01)** 或 **PReLU**,这能保证底层神经元不死亡,给深层传递更稳定的梯度。
3. **如果你做的是全连接层的 MLP 或 自编码器(Autoencoder)**:可以尝试 **SELU**,并配合 LeCun 初始化。这能帮你省掉 BN 的计算开销,且收敛速度极快。
4. **最后考虑 ELU**:它处于一个比较尴尬的位置——计算比 LeakyReLU 慢,归一化不如 SELU 彻底。通常只在需要输出连续值且希望输出分布光滑时使用(如某些回归任务)。
---
**最后补一个冷知识**:现在的 Swish / SiLU(\( x \cdot \text{sigmoid}(x) \))和 GELU(\( x \cdot \Phi(x) \))走出了另一条路——**它们没有负区截断,而是通过平滑的门控机制实现“软稀疏”**。这些是目前大模型(如 BERT、GPT)的最爱,因为它们在保留负值信息的同时,非线性表达能力更强。
如果你对 **GELU**(高斯误差线性单元,Transformer 标配)感兴趣,我们可以接着拆解它的数学直觉和为什么它比 ReLU 更适合 NLP。你想听吗? 😊
|
|
Batch Normalization论文标题解读 这是论文的标题: Batch Normalization: Accelerating Deep Network Training byReducing Internal Covariate Shift Reducing Internal Covariate Shift 减少内部协变量偏移 加速收敛及减少数据分布的偏移 accelerating英/əkˈseləreɪtɪŋ/ 美 /əkˈseləreɪtɪŋ/ v.(使)加速;加快
数据是有分布的,或者说不管它有没有,统一将数据看作或者默认它是符合正态分布的
数据分布主要有两个衡量指标:分布中心以及其离散程序
深度学习训练不像机器学习那样一次训练就定型,或者网络有很多的层,
多次训练,每次微调模型参数... 再用新的模型参数与数据计算,得到新的分布
一次次训练后,新的分布与原有数据有分布就不一样的,或者说漂移了,这就是论文标题提到的
Internal Covariate Shift
这种分布的改变,过大会导致梯度爆炸,
过小梯度消失,数据很小后,改变量也随之就小...
|
|
BN与归一化
本质上BN与归一化算法思路一样,细节上有些差异:BN增加了可学习参数
这个细节设计的来源:
- 数据分布改变了,你说这不适合,你想拉回来
- 那拉回来多少合适?
- 你就这样要求所有数据回到标准正态分布是否太断武断,是否是一刀切行为?
- 如果一刀切了,有些激活函数,比如ReLU直接把小于0的全扔了,这是否合适呢
- 有的神经网络没有把ReLU直接放BN的后面,或者干脆就没用BN,效果也不差
- 你所认为的不适合,只是你认为的,还是实现也的确如此?
解决方法
- 到底是否适合,拉到什么程度,设计成可学习的,由模型的参数通过学习决定
也就是常说的为神经网络赋予某种能力,
- 功能给你了,有需要再用,实际不需要时,参数自动学习为0,相当于不用
- 用的时候用几分力,用到什么程度,由实际情况决定
|
|
BN核心逻辑
输入是一列的数据中的一个批次, 1 一个x对应一个y 2 求均值 3 求方差 5 映射到标准正态分布,其中分母为了防止为0,加一个微小的数 6 yi = a*xi + b, - a,b是可学习参数, - 如果a=std,b=mean的话,y那么yi就是最开始的xi - 如果a,b是其他的值的,y就是以正态分布为基础,根据数据规律漂移后的结果 图像处理用批归一化的多, 序列处理用层归一化的多 |
机器学习没有批次的概念,一个批次就是全体数据集 深度学习中有批次的概念,所有的计算都是以批次形式计算的 但有时会用到均值,标准差这些变量,同时要寻找的规律,实现是整个数据集的规律,怎么办? 先求批次的,然后逆推整个数据集的 |
|
|
|
n维特征向量某个维度在整个批次上的归一化
```
import torch
from torch import nn
def batch_norm(data):
data = torch.tensor(data).float()
print(data.shape) #torch.Size([3, 5])
# 1D要求3维[B,C,L],2D要求4维[B,C,W,H]
data = data.unsqueeze(1)
#目录仍然是一行一个样本
print(data.shape) # torch.Size([3, 1, 5])
#相当于取data[:,i]作归一化
bn = nn.BatchNorm1d(num_features=data.shape[1])
return bn(data)
```
```
#常见2维数表,一行对应一个样本
data = [[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5]]
batch_norm(data)
torch.Size([3, 5])
torch.Size([3, 1, 5])
tensor([[[-1.4142e+00, -7.0710e-01, 1.1921e-07, 7.0711e-01, 1.4142e+00]],
[[-1.4142e+00, -7.0710e-01, 1.1921e-07, 7.0711e-01, 1.4142e+00]],
[[-1.4142e+00, -7.0710e-01, 1.1921e-07, 7.0711e-01, 1.4142e+00]]],
grad_fn=NativeBatchNormBackward0)
```
```
data = [[5,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5]]
batch_norm(data)
torch.Size([3, 5])
torch.Size([3, 1, 5])
tensor([[[ 1.2480, -0.9120, -0.1920, 0.5280, 1.2480]],
[[-1.6320, -0.9120, -0.1920, 0.5280, 1.2480]],
[[-1.6320, -0.9120, -0.1920, 0.5280, 1.2480]]],
grad_fn=NativeBatchNormBackward0)
```
一个维度上修改一个数,影响了所有维度上的数据
批归一化针对的是整个批次的数据
|
|
n维特征向量在该向量的n个元素上做归一化,类似于 向量/自身的模,将所有向量的模归于1附近
import torch
from torch import nn
def layer_norm(data):
data = torch.tensor(data).float()
print(data.shape) #torch.Size([3, 5])
# 1D要求3维[B,C,L],2D要求4维[B,C,W,H]
data = data.unsqueeze(1)
#目录仍然是一行一个样本
print(data.shape) # torch.Size([3, 1, 5])
#相当于取data[:,i]作归一化,不同于BN,这里按维度进行了分组,要求需要做归一化的维度放在数据的最后一维
norm = nn.LayerNorm(normalized_shape=data.shape[2])
return norm(data)
#常见2维数表,一行对应一个样本
data = [[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5]]
layer_norm(data)
torch.Size([3, 5])
torch.Size([3, 1, 5])
tensor([[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142]],
[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142]],
[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142]]],
grad_fn=NativeLayerNormBackward0)
#常见2维数表,一行对应一个样本
data = [[5,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5]]
layer_norm(data)
torch.Size([3, 5])
torch.Size([3, 1, 5])
tensor([[[ 1.0290, -1.5435, -0.6860, 0.1715, 1.0290]],
[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142]],
[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142]]],
grad_fn=NativeLayerNormBackward0)
层归一化针对一个维度,某个维度上的数据变化,不会影响其他维度上的数据
|
|
相同点 [B,C,feature_shape] [B,C,L] 都是对[B,C]的处理,处理方式都是拉到标准正态分布 不同点
层归化一化比批归一化多一次分组
- 批归一化是按C分组,一个C,即一个特征维度上的所有数据取出来做一个归一化
- 针对整个批次,是整个批次所有数据在指定的维度分组做标准化,
- 指定的维度通常是通道C,即单个样本特征的个数/维数
- 比如,C的第1个通道,即特征的第1个元素,整个批次所有数据特征的第1个元素形成的特征图,做一个标准化
- 如果将特征中不同元素看作2维关系表中不同的列的话,批归一化就是针对列作归一化,就是特征工程中数据预处理中的归一化
- 从这点来看,特征工程中的归一化就是深度学习中的批归一化,是在每个维度做的归一化
- 层归一化
- 针对单个样本,在指定的维度上做归一化,通常指最后部分的维度
- 对于文本来说,就是[batch,seq_len,embedding_dim]是对embedding_dim做归一化
- 每个单词一个embedding_dim,是针对每个单词做归一化,归一化后,每个单词的模归于1附近
- 单词之后互不影响
- 转换为2维表,是对行做归一化,行与行之间相不影响
为什么文本适合做层归一化
- 因为文本无法按单词划分类别
- 第一个单词归于名词,第一个单词归于...词,句子过于复杂
- 无法形成机器学习中列与列不同的定义
- 也无法像图像那样,RGB三色分的这么开,一层一个颜色
- 一个单词embedding之后各个位置上元素的含义,无法区分开
- 因此就在单词的维度上做归一化
- 同样可以防止向量的模不断变大
但形成矩阵后,都是数字,我就要在embedding的维度上做批归一化,不就是防止参数矩阵变大吗?
- 个人感觉可行,因为现在模型支持的上下文长度也很长,一个句子几百字,也可以归一化一下
- 但未验证
输入参数不同
- 批归一化,处理的维度确定为第2维C,只需要输入C的维数num_features
- 层归一化,确定为[B,C,L],[B,C,H,W]为B,C之后的维度,但由于不确定是有多少维,所以要输入shape:normalized_shape
import torch data = torch.randn(2,3,4,5) norm = nn.LayerNorm(normalized_shape=[4,5]) norm(data).shape #torch.Size([2, 3, 4, 5]) 层归一化 虽然可以对多个维度进行归一,但必须是data.shape最后的维度 - 可以是最后一维,也可以是最后2维 |
|
|
import torch
from torch import nn
# The mean and standard-deviation are calculated per-dimension over
nn.BatchNorm2d()
y = \frac{x - \mathrm{E}[x]}{ \sqrt{\mathrm{Var}[x] + \epsilon}} * \gamma + \beta
参数num_features:特征数,比如像素的3色,单词的embedding_dim
nn.BatchNorm2d(num_features=3) 如果是2维关系表,对一列做归一化, - 如果是RGB3色的话,就是对[H,W]的数值做归一化 按维度的话,就是对一个维度的特征做归一化
不管原来是什么shape,统一转化为[B,C,L],然后对L做归一化
第一个维度是批次 第二个维度是特征 第三个维度是原特征图展平形成的向量/列表 对于图像[B,C,H,W] -- 将[H,W]展平求归一化 对于文件[B,C,L] -- 直接对L求归一化 归一化:减 均值 ,再除以标准差
取特征维度数据
X表示RGB三色的批次图像数据 X[B,C,H,W] 取第1个特征 X[:,0,:,:] 取第2个特征 X[:,1,:,:] 取第3个特征 X[:,2,:,:]
预测时使用的是训练时批次的均值和标准差
训练是批次的,预测通常是单个样本 单个样本的均值与标准差 浮动过大,不合适用 批次训练时,记录每个批次的均值与标准差,并以此评估整个数据集的均值与标准差 预测使用的是,训练时预测的整个数据集的 均值与标准差 这种思想是伟大的... 即保证了预测与训练的计算过程是一样的,又避免了预测时单个样本均值与标准差浮动带来的波动问题
为什么图像多使用BN(以下仅个人猜测,如有...纯属...)
图像处理用批归一化的多, - 因为图像特征数不多, - 基本就1-灰色,3-RGB,4-RGB+一个透明度, - 特征图[H,W]的元素数据是很多的 - 核心的信息更多在特征图[H,W]中,而不是RGB特征维度中 序列处理用层归一化的多 - 序列中一个单词的维度通常在100-300之间 - 单词个数,即句子长度可能是8,32,64...不等,一句话20个单词就算多了 - 核心的信息更多在单词的维度中,所以才不是像图像那样对特征图做归一化
The mean and standard-deviation are calculated per-dimension over
deviation英/ˌdiːviˈeɪʃn/美/ˌdiːviˈeɪʃn/ n.偏差; dimension英/dɪˈmenʃn/ 美/daɪˈmenʃn/ n.方面;范围;维(构成空间的因素);侧面 The mean and standard-deviation are calculated per-dimension over 核心在于“dimension”-特征维度 均值和标准差是在 特征维度 上计算的
nn.BatchNorm要求特征维度在第2个位置上,即索引为1的维度上
也不是BatchNorm的要求,实际上是torch框架的要求 在torch框架中, 文本等一维数据的shape通常为[Batch_size,特征维度(单词向量维数,embedding_dim),特征shape(单词个数)] 图像等2维数据[Batch_size,C,W,H],特征维数也是在第2个位置上 BatchNorm在torch.nn下面,自然就遵守这个约定,将特征维度放在了第2个位置上
针对一个特征层上的数据分布进行归一化,相当于对库表某列做归一化
import torch
from torch import nn
torch.manual_seed(73)
batch_size = 1
num_features=2
x=torch.linspace(start=1,end=6,steps=6).reshape(batch_size,num_features,3)
x
tensor([[[1., 2., 3.],
[4., 5., 6.]]])
bn = nn.BatchNorm1d(num_features=num_features)
bn(x)
tensor([[[-1.2247e+00, 0.0000e+00, 1.2247e+00],
[-1.2247e+00, -1.1921e-07, 1.2247e+00]]],
grad_fn=NativeBatchNormBackward0)
文本处理举例
import torch from torch import nn data = [[[1,2,3,4,5],[1,2,3,4,5],[1,20,30,400,500]]] embedding_dim = 5 data = torch.tensor(data).float() data.shape torch.Size([1, 3, 5])
批归一化:特征层必须在第2个维度,公式完全一致
层归一化,归一的是每个元素(比如每个单词)的特征数据,,相当于对库表某行做归一化 批归一化,归一的是每个特征层上的数据分布(所有数据在某个特征维度上的数据),相当于对库表某列做归一化
# 1D要求3维[B,C,L],2D要求4维[B,C,W,H]
data = [[[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5]]]
data = torch.tensor(data).float()
data = data.permute(0,2,1) # 这里不要用reshape,这里是交换维度
bn = nn.BatchNorm1d(num_features=5)
# 每个特征层上的数据分布
data
tensor([[[1., 1., 1.],
[2., 2., 2.],
[3., 3., 3.],
[4., 4., 4.],
[5., 5., 5.]]])
bn(data)
tensor([[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]]], grad_fn=NativeBatchNormBackward0)
文本处理举例,1个批次,3个单词,每个单词5个维度
import torch from torch import nn data = [[[1,2,3,4,5],[1,2,3,4,5],[1,20,30,400,500]]] embedding_dim = 5 data = torch.tensor(data).float() data.shape torch.Size([1, 3, 5])
层归一化:标准化 + 可学习参数
# normalized_shape必须是data.shape的最后一维,将这个维度的数据分布拉到正态分布
# 如果是文本,[batch_size,seq_len,embedding_dim],相当于对每个单词做了归一化
layer_norm = nn.LayerNorm(normalized_shape=embedding_dim)
#400,500相对1是一个很大的数,归一化后,接近于1,
# 这是要将每个元素的数据分布拉到标准正态分布了,有加速收敛的作用
layer_norm(data)
tensor([[[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142],
[-1.4142, -0.7071, 0.0000, 0.7071, 1.4142],
[-0.8813, -0.7928, -0.7463, 0.9773, 1.4431]]],
grad_fn=NativeLayerNormBackward0)
The mean and standard-deviation are calculated over the last `D` dimensions, where `D`
is the dimension of :attr:`normalized_shape`. For example, if :attr:`normalized_shape`
is ``(3, 5)`` (a 2-dimensional shape), the mean and standard-deviation are computed over
the last 2 dimensions of the input (i.e. ``input.mean((-2, -1))``).
:math:`\gamma` and :math:`\beta` are learnable affine transform parameters of
:attr:`normalized_shape` if :attr:`elementwise_affine` is ``True``.
The standard-deviation is calculated via the biased estimator, equivalent to
`torch.var(input, unbiased=False)`.
Shape:
- Input: :math:`(N, *)`
- Output: :math:`(N, *)` (same shape as input)
NLP Example
# NLP Example batch, sentence_length, embedding_dim = 20, 5, 10 embedding = torch.randn(batch, sentence_length, embedding_dim) layer_norm = nn.LayerNorm(embedding_dim) # Activate module layer_norm(embedding)
参数 elementwise_affine
note:: Unlike Batch Normalization and Instance Normalization, which applies scalar scale and bias for each entire channel/plane with the :attr:`affine` option, Layer Normalization applies per-element scale and bias with :attr:`elementwise_affine`. elementwise_affine: a boolean value that when set to ``True``, this module has learnable per-element affine parameters initialized to ones (for weights) and zeros (for biases). Default: ``True``. 批归一化是一个特征层(相当于数据处理时列的维度)一个可学习参数加一个偏置 层归一化是每个元素(相当于数据处理时行的维度,一行多列为一个元素)都有一个可学习参数加一个偏置 elementwise_affine为True时,初始化参数所有值为1,默认为True
没见过这种方法,目前看仅个人推测,非官方观点,仅供参考,如有雷同,纯属巧合;
警告,如果你初步接触 统计学,对正统(教材)的基本概念没有深刻的理解,请不要看下面的内容!!! 向量单位化 = 向量/向量的模 向量的模就是 向量的欧氏距离,向量的大小,L2距离, 是各个元素的平方之和再开方, 强调向量本身的长度,向量/向量本身的长 才归于1 而方差呢,方差有两个重点: 1. 每个元素 - 分布的均值,强调分布的中心 2. 平方求和,再除以样本个数,是个平均值 如此, 方差强调的是 整个分布 相对分布中心 的离散程度, 标准差就是整个分布相对分布中心的离散长度(距离或大小) 每个元素-分布均值=每个元素离分布中心的长度(距离或大小) 二者相除,就得到一个分布离散比 从物理学的角度看,就是,要比较两个事物的不同,可以看看两个事物的密度... 方差的侧重点,并不是统一量纲, 而向量单位化则是让任何一个向量100%变化模为1的单位向量,100%的统一量纲 此时向量之间的差异,完全由其夹角体现, 夹角所体现出来的差异性, 个人感觉(就是本人目前没有能力也没时间用数学严格推导证明(就是一疲惫不堪的上班族...)), 要比 纯距离的 分布离散比 有更强的表达能力,带有更多的信息...
批归一化或层归一化LayerNorm(LayerNorm中的layer就是指特征层), 它并不是将矩阵中向量的模归于1,而是 将分布 按数据的 每个特征层都拉向一个正态分布
import torch
def entorpy(x,label):
"""两个概率分布越接近其交叉熵越小
"""
# 转tensor
label = torch.tensor(label)
x1 = torch.tensor(x)
# 熵计算
# log0是负无穷,
# torch.log(torch.tensor(0.)) = -inf
# 为了避免出现此情况,为向量加上一个极小的数,通常是1e-6
x1 = x1 + 1e-6
entorpy = -label*torch.log(x1)
return entorpy.sum()
label = [0,0,1]
x1 = [0.7,0.1,0.2]
print(entorpy(x1,label)) # tensor(1.6094)
label = [0,0,1]
x1 = [0.02,0.06,0.92]
print(entorpy(x1,label)) # tensor(0.0834)
label = [0,0,1]
x1 = [0,0.0001,0.9999]
print(entorpy(x1,label)) # tensor(9.9008e-05)
模型输出分布与标签分布
交叉熵是衡量两个分布之间的差距 在AI中通常使用one-hot编码对标签进行编码,比如 [ [0, 0, 1] [0, 1, 0] [1, 0, 0] ] 对于某个样本,比如[0, 0, 1] 中的三个元素,每个元素的位置代表的是一个类别 ,[0, 0, 1] 则表示第3个类别 从概率的角度讲, [0, 0, 1]是第1类别的概率是0 [0, 0, 1]是第2类别的概率是0 [0, 0, 1]是第3类别的概率是1, 同时0+0+1 = 1,满足概率所有事物可能之和为1的条件 所以[0,0,1]是标签的分布,同时也是其概率分布 模型输出 对于第3个类别,模型输出[0.1, 0.3, 0.7]则表示该样本 在第1类别的输出是0.1,它的目标是0 在第2类别的输出是0.3,它的目标是0 在第3类别的输出是0.7,它的目标是1 或者是干扰因素多,或者是模型不够好,或者是还没有优化到极限, 总之,它现在输出了[0.1, 0.3, 0.7] 它是模型输出的分布,但不是其概率分布 0.1+0.3+ 0.7=1.1
模型输出转概率
import torch # 转概率 label = torch.tensor([0,0,1]) x1 = torch.tensor([0.1,0.3,0.7]) x1 = torch.exp(x1)/torch.exp(x1).sum() + 1e-6 print(x1) # tensor([0.2473, 0.3021, 0.4506])
模型输出是转成概率了,但有两个问题: 1. 对于第3个类别,模型在通过参数调整试图逼近标签,到0.7了,结果转了概率后变成了0.4506,离标签更远了 2. 标签由于是one-hot编码,[0,0,1]前两类是0,算了相当于没算,因为0乘以其他数还是0 工程上优化方向思考: 1. 针对概率变小的问题,a=x-min(x),a/max(a),可使最大索引位置的数值化为1,更符合分类问题的处理方式 2. 不做概率转化,只计算标签[0,0,1]中第3类位置上的数据, 即只计算标签为1的索引位置上的数据, 这里标签永远是1,模型输出则是0.7,|1-0.7|=0.3 计算代价最小,关键是这么做有实际效果吗? 经实践验证是有的,并且效果不差
神经网络】神经元ReLU、Leaky ReLU、PReLU和RReLU的比较