2025-问出答案-Derf和Layer Norm

今天看到一个推送的文章:
在这里插入图片描述
好奇这是什么东西,为什么那么简单?

layer normalization什么意思?

Layer Normalization(层归一化)是一种用于深度学习模型的归一化技术,它的核心思想是对单个样本的所有特征进行归一化处理,从而加速训练并提升模型的稳定性。
顺便介绍一下常用的LN和BN,从对比中学习

对比一下 LN和BN:
在这里插入图片描述

它为什么会有效!但是听起来不合理,特征的均值有什么用?

这个问题问到了关键点上!LayerNorm 的有效性确实不是直观上那么明显,它的巧妙之处在于通过控制特征的分布来间接稳定整个学习过程。特征的均值在这个过程中扮演着“锚点”的角色。

作用机制
为什么听起来不合理?
实际上为什么有效?
1. 稳定层间输入分布
每个样本的特征是独特的,强行拉平均值似乎会破坏信息。
深度网络中,前一层参数的微小变动会导致后一层输入的整体分布发生剧烈偏移(内部协变量偏移)。LN通过重置每层输入的均值和方差,为下一层提供了一个稳定的“起跑线”
5
2. 改善梯度流动
梯度消失/爆炸问题通常通过权重来解决,调整输入特征有什么用?
激活函数(如Sigmoid, Tanh)在输入值过大或过小时会进入“饱和区”,梯度趋近于0。LN确保大部分特征值被归一化到激活函数梯度较大的敏感区间,从而让梯度能够有效地反向传播,缓解梯度消失问题
5
7
3. 保持模型表达能力
把所有特征都变成均值为0,岂不是让学习到的表示能力变弱了?
关键就在这里!LN在归一化后,引入了两个可学习的参数:缩放因子γ和平移因子β。γ和β允许网络自己决定是否要恢复原始的分布,或者学习一个更优的新分布,从而保留了非线性表达能力
4
5

在这里插入图片描述

我感觉更像是无奈之举,目的是为了可以处理可变长序列?

这个观察非常敏锐,确实触及了一个关键点:LayerNorm在Transformer等序列模型中的统治地位,在很大程度上源于它优雅地解决了BatchNorm在处理可变长序列时的“无奈”。但这并不仅仅是无奈之举,更是一个针对特定问题域的、更具优越性的解决方案。

特性
BatchNorm (BN)
LayerNorm (LN)
归一化维度
依赖批次维度,对同一特征跨样本归一化
特征维度上对单个样本独立归一化
1
2
对Batch Size的依赖
强依赖,小批量或在线学习时效果不稳定
5
不依赖,即使在批次大小为1时也能稳定工作
1
处理变长序列
困难,需要填充(padding)且统计量受填充影响
3
天然适合,每个序列独立处理,不受其他序列长度影响
1
6
训练/推理一致性
推理时使用训练集滑动平均,可能因分布变化引入偏差
3
行为在训练和推理阶段完全一致,更稳定
3

在这里插入图片描述

Derf可以代替layernorm么?然后Derf是针对所有特征么?还是单个特征?

根据最新的研究,Derf不仅可以替代LayerNorm,而且在多项任务中展现出全面超越LayerNorm的潜力。它针对的是单个特征值,而非所有特征的统计量。

特性
LayerNorm (LN)
Derf
核心机制
计算一个样本所有特征的均值和方差进行归一化
每个特征值独立应用一个固定的非线性函数
是否依赖统计量
,依赖运行时计算的统计量
,仅依赖训练好的固定参数
计算开销
相对较高(需计算均值和方差)
相对较低(纯点对点计算)
关键优势
经典稳定,能有效稳定训练过程
性能更强,泛化能力更好,计算更简单

在这里插入图片描述

Derf中可学习的参数如何学习?

可学习参数
角色与功能
学习特性
α (缩放因子)
控制erf函数对输入x的敏感度。α越大,函数在零点附近的斜率越陡峭,输出变化越快
1
通常为标量(单个值),与输入张量的具体位置无关,所有元素共享同一参数
1
2
s (平移因子)
对输入x进行水平位移,与α协同工作,微调函数的中心区域,确保大部分激活值落在梯度较大的敏感区间
1
3
通常为标量(单个值),所有元素共享同一参数。研究表明,将其设为向量(逐通道)并未带来显著提升
1
γ (缩放因子)
erf变换后,对输出进行缩放,恢复或增强模型的表达能力。功能类似于LayerNorm中的仿射变换参数
1
2
通常是逐通道的向量,其维度与输入张量的通道维度相同,为不同特征通道提供独立的缩放
1
β (偏移因子)
erf变换和γ缩放后,对输出进行整体平移,进一步调整输出的分布中心
1
2
通常是逐通道的向量,其维度与输入张量的通道维度相同,为不同特征通道提供独立的偏移
1

在这里插入图片描述

简单来说,在Derf函数 Derf(x) = γ * erf(αx + s) + β中,输入 x代表的是神经网络激活张量中的每一个单独的、具体的特征值。

特性
LayerNorm (层归一化)
Derf (动态误差函数)
操作对象
一个样本的所有特征构成的向量
张量中的每一个独立的特征值 (标量)
x的含义
一个向量,例如一个词嵌入的所有维度
一个单一的数值,例如某个神经元在特定位置的输出值
处理方式
计算整个向量的均值(μ)和方差(σ),然后用它们来归一化向量中的每个值
对每个数值独立地应用一个固定的非线性函数 erf
是否依赖统计量
,依赖当前输入动态计算的统计量
,仅依赖训练好的固定参数 αs
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1627e5d2d25244dabbebc4b00280e30e.png) # 具体计算一下过程? ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/903d008ec80c403ebbdd36af22413023.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f59268987e5a421c9dd3391966f86f46.png) # 是不是可以用一个神经元的激活函数可以取代这个DERF加上神经元激活这么一个组合呢?

Derf本质上确实是一个精心设计的非线性修正函数,它作用于神经元的输出之上。

特性对比
您的设想:用一个激活函数取代
Derf 的实际方案
核心机制
寻找或设计一个单一的、更强大的激活函数,同时完成非线性映射和分布修正。
保留原有激活函数(如ReLU、GELU),在其之后增加一个独立的、专门用于“分布整形”的点对点函数。
设计逻辑
希望一个组件解决所有问题(特征变换+稳定分布)。
职责分离:激活函数负责引入非线性;Derf负责稳定激活值的分布,优化训练动态。
关键性质
通常不专门针对“稳定训练分布”所需的数学性质进行设计。
其设计明确满足四大关键性质:零中心性、有界性、中心敏感性和单调性
6
7

在这里插入图片描述
在这里插入图片描述

<think>我们正在讨论使用SVT-AV1编码器C++将YUV视频编码为AV1格式的方法。根据用户需求,我将提供实现步骤示例代码。 首先,SVT-AV1是一个开源的AV1编码器实现,由Intel开发,具有高性能可伸缩性。使用它进行编码的基本步骤包括初始化编码器、配置参数、读取YUV帧、编码帧并写入输出文件。 以下是使用C++SVT-AV1编码YUV为AV1的基本步骤: 1. 安装SVT-AV1:需要先下载并编译SVT-AV1库。 2. 包含必要的头文件。 3. 初始化编码器并设置配置参数。 4. 打开输入YUV文件输出AV1文件。 5. 循环读取YUV帧,进行编码,并将编码后的数据写入输出文件。 6. 清理资源。 下面是一个简化的示例代码框架: 注意:实际使用中需要根据YUV文件的格式(如分辨率、像素格式)设置编码器参数。 ```cpp #include <cstdio> #include <cstdlib> #include <SvtAv1Enc.h> int main() { // 初始化编码器 EbComponentType* svt_encoder = NULL; EbSvtAv1EncConfiguration config; memset(&config, 0, sizeof(config)); // 设置编码器参数 config.source_width = 1920; // 视频宽度 config.source_height = 1080; // 视频高度 config.encoder_bit_depth = 8; // 输入YUV的位深,8或10 config.input_padded_width = config.source_width; // 通常需要根据编码器要求对齐,这里简单设置为宽度 config.input_padded_height = config.source_height; config.color_format = EB_YUV420; // YUV420格式 // 其他配置(根据需求调整) config.enc_mode = 8; // 编码模式(0-13),数字越大速度越慢质量越好 config.profile = 0; // AV1 profile (0: Main, 1: High, 2: Professional) // 初始化编码器 EbErrorType res = svt_av1_enc_init_handle(&svt_encoder, NULL, &config); if (res != EB_ErrorNone) { fprintf(stderr, "Error initializing encoder\n"); return 1; } // 设置配置 res = svt_av1_enc_set_parameter(svt_encoder, &config); if (res != EB_ErrorNone) { fprintf(stderr, "Error setting parameters\n"); svt_av1_enc_deinit_handle(svt_encoder); return 1; } // 初始化编码器 res = svt_av1_enc_init(svt_encoder); if (res != EB_ErrorNone) { fprintf(stderr, "Error initializing encoder\n"); svt_av1_enc_deinit_handle(svt_encoder); return 1; } // 打开输入YUV文件输出AV1文件 FILE* in_file = fopen("input.yuv", "rb"); if (!in_file) { perror("Failed to open input file"); svt_av1_enc_deinit(svt_encoder); svt_av1_enc_deinit_handle(svt_encoder); return 1; } FILE* out_file = fopen("output.av1", "wb"); if (!out_file) { perror("Failed to open output file"); fclose(in_file); svt_av1_enc_deinit(svt_encoder); svt_av1_enc_deinit_handle(svt_encoder); return 1; } // 计算一帧YUV420的大小(8位) size_t frame_size = config.source_width * config.source_height * 3 / 2; uint8_t* frame_buf = new uint8_t[frame_size]; // 编码循环 EbBufferHeaderType input_buf; memset(&input_buf, 0, sizeof(input_buf)); input_buf.size = sizeof(input_buf); input_buf.p_buffer = frame_buf; input_buf.p_app_private = NULL; input_buf.timestamp = 0; while (fread(frame_buf, 1, frame_size, in_file) == frame_size) { // 设置帧类型(这里假设都是视频帧) input_buf.pic_type = EB_AV1_INVALID_PICTURE; input_buf.flags = 0; // 发送帧到编码器 res = svt_av1_enc_send_picture(svt_encoder, &input_buf); if (res != EB_ErrorNone) { fprintf(stderr, "Error sending picture\n"); break; } // 接收编码后的数据 EbBufferHeaderType* output_buf = NULL; res = svt_av1_enc_get_packet(svt_encoder, &output_buf, 0); if (res == EB_ErrorNone && output_buf != NULL) { // 写入输出文件 fwrite(output_buf->p_buffer, 1, output_buf->n_filled_len, out_file); // 释放输出buffer svt_av1_release_out_buffer(&output_buf); } input_buf.timestamp++; } // 冲刷编码器(获取剩余的数据包) EbBufferHeaderType* output_buf; while (svt_av1_enc_get_packet(svt_encoder, &output_buf, 1) == EB_ErrorNone) { if (output_buf != NULL) { fwrite(output_buf->p_buffer, 1, output_buf->n_filled_len, out_file); svt_av1_release_out_buffer(&output_buf); } } // 清理资源 delete[] frame_buf; fclose(in_file); fclose(out_file); svt_av1_enc_deinit(svt_encoder); svt_av1_enc_deinit_handle(svt_encoder); return 0; } ``` 注意:以上代码仅为示例,实际使用中需要根据具体情况进行调整,例如错误处理、参数配置等。 编译命令示例(假设已经正确安装SVT-AV1): g++ encode_example.cpp -o encoder -lSvtAv1Enc 请确保在编译时链接SVT-AV1库。 另外,YUV文件的格式必须与编码器设置一致(如分辨率、位深颜色格式)。如果YUV文件是10位的,需要设置`config.encoder_bit_depth=10`,并且帧大小需要相应调整。 以上代码基于SVT-AV1的API文档编写,但请注意API可能随版本更新而变化。建议参考最新的官方文档。 §§相关问题§§ 1. 如何调整SVT-AV1编码器的参数以平衡编码速度压缩质量? 2. 除了SVT-AV1,还有哪些开源的AV1编码器? 3. 如何将编码后的AV1视频流进行封装(例如为MP4或WebM格式)? </think>### 使用C++通过SVT-AV1将YUV编码为AV1的实现方法 以下是完整的C++实现步骤(基于SVT-AV1官方API): #### 1. **环境准备** - 安装SVT-AV1库: ```bash git clone https://github.com/AOMediaCodec/SVT-AV1 mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) sudo make install ``` #### 2. **核心代码实现** ```cpp #include <cstdio> #include <cstdlib> #include <SvtAv1Enc.h> int main() { // 初始化编码器 EbComponentType* encoder = nullptr; EbSvtAv1EncConfiguration config; memset(&config, 0, sizeof(config)); // 设置编码参数(示例:1080p 30fps YUV420) config.source_width = 1920; config.source_height = 1080; config.frame_rate = 30; config.encoder_bit_depth = 8; // 8位YUV config.color_format = EB_YUV420; config.enc_mode = 8; // 平衡速度质量 config.profile = 0; // Main profile // 创建编码器实例 EbErrorType err = svt_av1_enc_init_handle(&encoder, nullptr, &config); if (err != EB_ErrorNone) { fprintf(stderr, "编码器初始化失败\n"); return 1; } // 打开文件 FILE* yuvFile = fopen("input.yuv", "rb"); FILE* av1File = fopen("output.av1", "wb"); if (!yuvFile || !av1File) { perror("文件打开失败"); return 1; } // 计算YUV帧大小 const size_t frameSize = config.source_width * config.source_height * 3 / 2; uint8_t* frameBuffer = new uint8_t[frameSize]; // 编码循环 EbBufferHeaderType inputBuf; memset(&inputBuf, 0, sizeof(inputBuf)); inputBuf.size = sizeof(inputBuf); inputBuf.p_buffer = frameBuffer; uint64_t frameCnt = 0; while (fread(frameBuffer, 1, frameSize, yuvFile) == frameSize) { // 设置帧属性 inputBuf.n_filled_len = frameSize; inputBuf.flags = EB_BUFFERFLAG_SHOW_EXT; inputBuf.pic_type = EB_AV1_KEY_PICTURE; inputBuf.timestamp = frameCnt++; // 发送帧到编码器 err = svt_av1_enc_send_picture(encoder, &inputBuf); if (err != EB_ErrorNone) break; // 接收编码数据 EbBufferHeaderType* outputBuf = nullptr; while (svt_av1_enc_get_packet(encoder, &outputBuf, 0) == EB_ErrorNone) { if (outputBuf->n_filled_len > 0) { fwrite(outputBuf->p_buffer, 1, outputBuf->n_filled_len, av1File); } svt_av1_release_out_buffer(&outputBuf); } } // 冲刷编码器 EbBufferHeaderType* outputBuf; while (svt_av1_enc_get_packet(encoder, &outputBuf, 1) == EB_ErrorNone) { if (outputBuf->n_filled_len > 0) { fwrite(outputBuf->p_buffer, 1, outputBuf->n_filled_len, av1File); } svt_av1_release_out_buffer(&outputBuf); } // 清理资源 delete[] frameBuffer; fclose(yuvFile); fclose(av1File); svt_av1_enc_deinit(encoder); svt_av1_enc_deinit_handle(encoder); return 0; } ``` #### 3. **关键参数说明** 1. **分辨率设置**: ```cpp config.source_width = 1920; // 宽度 config.source_height = 1080; // 高度 ``` 2. **位深色彩格式**: ```cpp config.encoder_bit_depth = 8; // 8位深 config.color_format = EB_YUV420; // YUV420格式 ``` 3. **编码模式**: - `enc_mode` 范围 0-13,值越高压缩率越好但速度越慢 - 推荐值:实时编码用 8,离线压缩用 10+ #### 4. **编译运行** ```bash g++ encode.cpp -o encoder -lSvtAv1Enc ./encoder ``` #### 5. **性能优化建议** 1. **多线程加速**: ```cpp config.thread_count = 8; // 使用8个CPU线程 ``` 2. **预设配置文件**: ```cpp config.rc_mode = 1; // 1=CQP, 2=VBR, 3=CBR config.qp = 32; // 目标量化参数 ``` > 注意:YUV文件需为I420格式(YYYY UU VV平面排列),完整测试序列可下载[官方样例](https://media.xiph.org/video/derf/)[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万物琴弦光锥之外

给个0.1,恭喜老板发财

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值