6273 字
31 分钟

词向量、音频处理与自编码器学习笔记

发布于 2026-01-31

前言#

这份笔记分为五部分:词向量与 Transformer、数据集划分与特征编码、音频预处理、自编码器,以及训练时常用的优化方法。重点放在概念之间的联系和对应代码上。

第一部分:从离散符号到语义空间#

自然语言处理(NLP)的核心挑战在于:如何将人类的离散符号(单词、字符)映射到计算机能够进行微积分运算的连续空间中。

这一部分先看词如何映射为向量,再看 Transformer 如何使用这些向量处理和生成语言。

1.1 语义的向量化 (Vectorization)#

计算机本质上是一个大型计算器,它只能处理数字。对于单词 “Apple”,计算机看到的只是 ASCII 码的组合。为了让计算机“理解”语义,我们需要将词转换为向量。

1.1.1 稀疏矩阵 (Sparse) vs. 维度灾难#

在深度学习爆发之前,我们通常使用 One-Hot Encoding(独热编码)Bag-of-Words(词袋模型)。 假设词表有 10,000 个词:

  • Apple=[0,0,...,1,...,0]Apple = [0, 0, ..., 1, ..., 0] (索引 52)
  • Banana=[0,0,...,0,...,1]Banana = [0, 0, ..., 0, ..., 1] (索引 99)

这种表示方法有两个主要限制:

  1. 稀疏性 (Sparsity):向量维度很高,且绝大多数元素为 0,会占用额外存储和计算资源。
  2. 正交性 (Orthogonality):这是最根本的问题。在欧几里得空间中,所有 One-Hot 向量两两垂直。
vApplevBanana=0\vec{v}_{Apple} \cdot \vec{v}_{Banana} = 0

这意味着,对于计算机而言,“苹果”和“香蕉”的相似度为 0,这与“苹果”和“卡车”的相似度(也是 0)没有任何区别。模型无法捕捉语义相似性

1.1.2 稠密向量 (Dense Embeddings)与分布假说#

为了解决正交性问题,我们引入了 Word Embeddings(词嵌入)。它的核心思想基于分布假说 (Distributional Hypothesis)上下文相似的词,其语义也相似。

我们将每个词映射到一个低维(如 256 维、512 维)的实数向量空间中。在这个空间里:

  • 维度 (Dimensions):不再代表具体的“第几个词”,而是代表某种潜在的语义特征(如“是否是食物”、“是否有生命”、“体积大小”),尽管这些特征通常是隐含的,人类无法直接解读。
  • 距离 (Distance):向量之间的几何距离代表了语义距离。

1.1.3 Word2Vec 的数学直觉#

经典的 Word2Vec 模型(如 Skip-gram)会呈现一些线性关系。训练充分时,向量空间中可能出现如下结果:

vKingvMan+vWomanvQueen\vec{v}_{King} - \vec{v}_{Man} + \vec{v}_{Woman} \approx \vec{v}_{Queen}

这说明模型不仅学到了词的“位置”,还学到了词与词之间的关系方向向量(例如, 的方向向量代表了“皇室化”的概念)。

在作业或工程实践中,我们很少从头训练 Embeddings,而是使用 Pre-trained Embeddings(如 BERT 的嵌入层),因为它们已经在海量文本上捕捉到了通用的语义关系。


1.2 大语言模型 (LLM) 的主要结构#

理解了输入层(Embeddings),我们来看处理层。现代 LLM 的核心几乎完全建立在 Transformer 架构之上。

1.2.1 从 RNN 到 Transformer#

在 Transformer 出现之前,RNN(循环神经网络)和 LSTM 是主流。它们按顺序处理文本:读完第一个字,生成隐状态,传给第二个字…

  • 瓶颈:无法并行计算(必须等前一个字处理完);长距离依赖遗忘(处理到第 100 个字时,可能忘了第 1 个字的信息)。

Transformer 引入了 Self-Attention (自注意力机制),允许模型一次性“看到”整个句子,并计算词与词之间的相关性权重(Attention Score)。

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V

这个公式本质上是在计算:为了理解当前这个词,我需要在这个句子里的其他词上分配多少注意力?

1.2.2 Encoder 与 Decoder#

并不是所有 Transformer 都是一样的。根据任务不同,架构分为两派:

架构类型代表模型核心机制适用任务
Encoder-onlyBERT, RoBERTa双向注意力 (Bidirectional):模型能同时看到上下文。例如在填空任务 “I ate an [MASK] for lunch.” 中,模型可以根据 atelunch 推断出 apple理解任务:情感分析、文本分类、命名实体识别(NER)。
Decoder-onlyGPT 系列, Llama单向因果注意力 (Causal/Autoregressive):模型只能看到当前词之前的词,被强制用来预测“下一个词”。生成任务:聊天机器人、故事创作、代码生成。

为什么聊天机器人首选 Decoder-only? 因为对话本质上是一个序列生成过程。人类说话也是根据已经说出的内容,逐字推导下一个字。Encoder-only 架构虽然理解能力强,但无法高效地进行这种自回归式的生成。

1.2.3 预训练 (Pre-training) 与微调 (Fine-tuning)#

在实际应用(如构建客服机器人)中,我们通常遵循两阶段范式:

  1. Pre-training (通识教育)
  • 数据:TB 级的互联网文本。
  • 任务:Next Token Prediction(预测下一个词)。
  • 结果:模型学会了语法、世界知识、逻辑推理。此时它是一个博学的“通才”,但不懂特定规矩。
  1. Fine-tuning (职业培训)
  • 数据:高质量的特定领域数据(如客服对话记录 Q&A)。
  • 任务:在特定数据分布上调整参数。
  • 结果:模型将原本广泛的概率分布,收敛到特定领域的表达方式上。例如,学会礼貌用语,学会只回答与产品相关的问题。

第二部分:数据预处理与特征工程#

模型效果不仅取决于网络结构,也受数据划分和特征表示影响。这一部分讨论如何切分数据集,以及如何把类别等现实概念转换为模型可用的数值特征。

2.1 数据集划分与泛化#

切分数据集(Splitting)不只是为了留一部分数据做最终评分,更重要的是观察模型是否过拟合,以及它对未见数据的泛化能力。

2.1.1 训练集、验证集与测试集 (Train / Val / Test)#

常见的训练流程会使用三个相互独立的数据集:

  1. 训练集 (Training Set)教科书。模型通过它来计算梯度,更新参数(Weights)。
  2. 验证集 (Validation Set):用于选择学习率、层数等超参数,不参与模型参数训练。训练指标很好而验证指标明显较差时,通常说明模型已经过拟合。
  3. 测试集 (Test Set):用于最终评估。模型和超参数确定前,不应根据测试结果反复调整方案。

⚠️ 数据泄露 (Data Leakage) 的陷阱: 如果你根据测试集的得分去调整模型结构,这在统计学上叫“数据泄露”。你以为模型变聪明了,其实它只是通过你的手“作弊”看过了答案。

2.1.2 进阶技巧:K-Fold 交叉验证 (Cross-Validation)#

在数据量有限的情况下(比如只有几千条数据),简单的 8:1:1 切分存在巨大的随机性。如果运气不好,切出来的验证集恰好全是很难的样本,你会误以为模型很差。

为了消除这种随机性带来的方差 (Variance),我们采用 K-Fold 交叉验证

  • 核心逻辑:将训练数据切成KK份(比如 5 份)。
  • 轮替:训练 5 次。第 1 次用 [1,2,3,4] 训练,用 [5] 验证;第 2 次用 [1,2,3,5] 训练,用 [4] 验证……以此类推。
  • 结果:取 5 次成绩的平均值。这才是模型真实的实力。

2.1.3 工程实战:处理不均匀切分#

在写代码实现 K-Fold 时,我们常遇到一个棘手问题:数据总量无法被 K 整除。 例如:100 个样本分 3 份。普通的切片逻辑 N/3 会导致最后一份数据丢失或报错。

Numpy 的 array_split 方案: 这是数据工程师的必备工具。它能自动处理余数,保证每个子集的大小差距不超过 1。

  • 输入:100 条数据,分 3 份。
  • 输出:[34, 33, 33]。 这种分层采样 (Stratified Sampling) 的思想保证了每一折训练的权重是均衡的。
import numpy as np
from sklearn.model_selection import train_test_split
def robust_k_fold_split(df, k=5, test_ratio=0.2):
"""
工程级的数据切分方案
"""
# 1. 彻底隔离测试集 (Lockbox)
# random_state 确保实验可复现
train_val, test = train_test_split(df, test_size=test_ratio, shuffle=True, random_state=42)
# 2. 生成 K-Fold 子集
# np.array_split 完美解决 "余数" 问题,保证每个 Fold 大小均衡
folds = np.array_split(train_val, k)
print(f"测试集大小: {len(test)}")
print(f"K-Fold 每个子集的大小: {[len(f) for f in folds]}")
return folds, test

2.2 类别特征编码与维度#

计算机无法理解“颜色:红色”或“部门:财务部”。我们需要将这些离散的类别 (Categorical Features) 映射到数学空间中。

2.2.1 为什么不能直接用 1, 2, 3?#

假设有一个特征“水果”,包含 {苹果, 香蕉, 梨}。

  • 错误做法 (Label Encoding):苹果=1,香蕉=2,梨=3。
  • 后果:神经网络是基于距离计算的。它会认为 (2-1) < (3-1),即“香蕉和苹果的距离”比“梨和苹果的距离”更近。这在逻辑上是荒谬的,因为它们应该是平等的。

2.2.2 One-Hot Encoding (独热编码) 的几何本质#

One-Hot Encoding 的本质是升维。它将不同的类别映射到了高维空间的不同坐标轴上,建立了一个正交基 (Orthogonal Basis)

  • 苹果 X 轴 [1,0,0][1, 0, 0]
  • 香蕉 Y 轴 [0,1,0][0, 1, 0]
  • 梨 Z 轴 [0,0,1][0, 0, 1]

正交性 (Orthogonality): 在几何上,X 轴垂直于 Y 轴。计算它们的点积:1×0+0×1+0×0=01\times0 + 0\times1 + 0\times0 = 0。 这意味着计算机认为它们之间的相似度为 0,且彼此独立,互不干扰。这才是类别特征的正确表达方式。

2.2.3 代码实现注意事项#

在 Python 中,我们通常使用 Pandas 的 get_dummies。但在工程落地时,有几个细节需要注意。

import pandas as pd
def safe_one_hot_encoding(df, column_name):
# 1. 检查列是否存在 (防御性编程)
if column_name not in df.columns:
raise ValueError(f"列名 {column_name} 不存在")
# 2. 执行独热编码
# dtype=int: 强制输出 0/1 (否则较新版 Pandas 默认输出 True/False,PyTorch 无法识别)
dummies = pd.get_dummies(df[column_name], prefix=column_name, dtype=int)
# 3. 拼接并清理
# axis=1 表示横向拼接
df_encoded = pd.concat([df, dummies], axis=1)
# 注意:通常我们会 drop 掉原始列,防止特征冗余
# df_encoded.drop(column_name, axis=1, inplace=True)
return df_encoded

2.2.4 警惕“维度灾难”#

One-Hot 编码虽然好,但不能滥用。 如果“用户 ID”有 100 万个取值,One-Hot 编码会产生 100 万个特征列,内存和计算成本都很高。

  • 类别数较少时可以使用 One-Hot Encoding。
  • 单词、ID 等高基数类别更适合使用 Embedding,将其映射到低维稠密向量。具体阈值取决于数据量、内存和模型结构,不必固定为 50。

第三部分:音频采样与频谱特征#

音频需要先采样成数字信号,再转换为适合模型处理的时域或频域特征。下面从采样率开始,整理傅里叶变换和 Mel 声谱图的计算过程。

3.1 声音的本质与数字化#

计算机听不到声音,它只能记录电压的变化。

3.1.1 采样率 (Sample Rate) 与奈奎斯特理论#

当你对着麦克风说话时,声波是连续的(模拟信号)。计算机通过“采样”将其数字化:每秒钟记录多少次振幅。

  • 采样率 (如 16kHz):意味着每秒钟记录 16,000 个数据点。
  • 奈奎斯特-香农采样定理 (Nyquist-Shannon Theorem):对于最高频率为 ff 的带限信号,采样率至少应高于 2f2f,才能避免混叠。
  • 例子:人耳能听到的最高频率大约是 20kHz。因此,为了保证高保真音质,CD 的采样率通常定为 44.1kHz(略高于 20kHz 的两倍)。在语音识别任务中,为了节省算力,我们常用 16kHz。

3.1.2 时域 (Time) vs. 频域 (Frequency)#

最原始的音频数据是波形图 (Waveform)

  • 横轴:时间。
  • 纵轴:振幅(声音大小)。

但波形图对 AI 来说很难理解。比如“高音 C”和“低音 C”在波形上只是波峰密集程度不同,很难直观区分。 我们需要打开频域 (Frequency Domain)。通过傅里叶变换,我们可以把一段复杂的波形拆解成无数个正弦波的叠加。


3.2 从波形到声谱图 (Spectrogram)#

在实际工程中(例如构建语音情感识别系统),我们几乎从不直接把波形喂给模型,而是将其转化为声谱图。这就把“听觉问题”转化为了“视觉问题”(图像分类/处理)。

3.2.1 STFT (短时傅里叶变换)#

由于声音是随时间变化的(你先说了“Hello”,后说了“World”),我们不能对整段音频做一次傅里叶变换(那样会丢失时间信息)。 我们必须使用 STFT

  1. 分帧 (Framing):把长音频切成无数个极短的片段(例如 25 毫秒)。
  2. 加窗 (Windowing):为了防止切割边缘产生噪音,给每个片段乘上一个“汉宁窗”或“汉明窗”(让两头变细,中间突出)。
  3. 变换 (FFT):对每一小段做快速傅里叶变换。

3.2.2 Mel 刻度(Mel Scale)#

物理上的频率(赫兹 Hz)是线性的,但人耳的听觉是非线性的:

  • 我们能轻易分辨 100Hz 和 200Hz 的区别(低音)。
  • 但很难分辨 10000Hz 和 10100Hz 的区别(高音)。

为了让 AI 像人一样“听”声音,我们需要把频率轴拉伸变形,这就有了 Mel 刻度。它放大了低频细节,压缩了高频细节。

3.2.3 分贝(Decibels)与对数压缩#

声音的能量范围极大。蚊子的叫声和喷气式飞机的引擎声,能量可能相差一万亿倍。如果直接用数值表示,神经网络的梯度会瞬间爆炸。 因此,我们取对数 (),将其转换为分贝 (dB)dB=20×log10(AmplitudeReference)dB = 20 \times \log_{10}(\frac{Amplitude}{Reference})


3.3 工程实战:构建鲁棒的音频预处理管道#

在真实项目中,我们需要处理海量的 .wav 文件。这里有一个关键的工程挑战:变长输入 (Variable Length)。 有的录音只有 2 秒,有的有 10 秒。但深度学习模型(如 CNN 或 Autoencoder)通常要求固定的输入维度。

核心策略:截断 (Truncate) 与 填充 (Pad)#

我们需要设定一个“标准时长”(比如 6 秒,约 100,000 个采样点)。

  • 长了就砍:超过标准时长的部分丢弃。
  • 短了就补:不足的部分用 0 填充(Silence Padding)。

代码实现 (PyTorch/Torchaudio)#

下面是一个工业级的音频预处理函数示例,它将原始音频文件转化为可以直接输入模型的 Tensor。

import torch
import torchaudio
from torchaudio import transforms
def preprocess_audio_pipeline(file_path, target_length=100000):
"""
将任意长度的 wav 文件转换为标准化的 Mel 声谱图 (dB)。
参数:
file_path: 音频路径
target_length: 固定的采样点数 (例如 100k 对应约 6.25秒 @ 16kHz)
"""
# 1. 加载音频
# waveform shape: [Channels, Time_Steps]
waveform, sample_rate = torchaudio.load(file_path)
# 2. 统一长度 (Truncate / Pad)
current_len = waveform.size(1)
if current_len > target_length:
# 情况A: 太长,截断
waveform = waveform[:, :target_length]
elif current_len < target_length:
# 情况B: 太短,右侧补零
pad_amount = target_length - current_len
# F.pad 参数格式: (左填充, 右填充)
waveform = torch.nn.functional.pad(waveform, (0, pad_amount))
# 3. 特征提取: Mel Spectrogram
# 这些参数通常需要根据具体任务调优
mel_transform = transforms.MelSpectrogram(
sample_rate=sample_rate,
n_fft=1024, # FFT 窗口大小,决定了频域分辨率
hop_length=256, # 步长,决定了时间轴分辨率
n_mels=32 # Mel 滤波器组数量,决定了特征图的高度
)
mel_spec = mel_transform(waveform)
# 4. 幅度转分贝 (Amplitude to dB)
# 对数变换,极大约束数值范围,利于模型训练
db_transform = transforms.AmplitudeToDB(top_db=80)
mel_spec_db = db_transform(mel_spec)
# 返回形状: [Channels, n_mels, time_frames]
# 例如: [1, 32, 391]
return mel_spec_db

代码解读: 这段代码完成一个标准的 ETL(Extract, Transform, Load)过程。最终输出的张量(Tensor)可以看作一张单通道图片

  • 高度 = 32 (Mel 频段数)
  • 宽度 = 391 (时间步长)
  • 数值 = 分贝 (声音响度)

得到这个矩阵后,就可以把它作为神经网络的输入。

得到 Mel 声谱图后,就可以把它作为自编码器的输入。

第四部分:自编码器#

自编码器使用输入本身作为训练目标,通过编码器压缩特征,再由解码器重构输入,不需要额外的人工标签。

4.1 瓶颈与流形#

4.1.1 核心逻辑:有损压缩 (Lossy Compression)#

想象你要把一张高清大图传给朋友,但带宽极低。

  1. 编码 (Encode):你把图片压缩成一个极小的 ZIP 包。
  2. 传输:发送 ZIP 包。
  3. 解码 (Decode):朋友根据 ZIP 包还原出图片。

如果重构结果与原图接近,说明潜在向量保留了重构所需的主要特征。

自编码器就是在这个过程中加入神经网络: Input(x)EncoderLatentCode(z)DecoderOutput(x^)Input (x) \xrightarrow{Encoder} Latent Code (z) \xrightarrow{Decoder} Output (\hat{x})

4.1.2 瓶颈层 (Bottleneck) 的作用#

中间层(Latent Space)通常比输入维度小。如果输入是 25,000 维,中间层仍保留 25,000 维,模型更容易学成接近恒等映射(Identity Function),难以得到有效的压缩表示。

把数据压缩到较小的瓶颈层(例如 256 维),可以限制模型直接复制输入,并促使它保留对重构更有用的特征。

  • 流形学习假说 (Manifold Learning Hypothesis):高维数据(如 25024 维的声谱图)通常分布在一个低维的流形(如 256 维的曲面)上。自编码器的任务就是找到这个流形。

4.2 全连接网络的实战实现#

卷积神经网络(CNN)常用于处理声谱图,这里先使用全连接层(nn.Linear)说明自编码器的基本结构。

这意味着我们需要先将二维的图像“拍扁” (Flatten) 成一维向量。

4.2.1 维度变换推导#

还记得我们在第三部分生成的声谱图吗?

  • 形状:32 (Mel bins)×391 (Time frames)×2 (Channels)32 \text{ (Mel bins)} \times 391 \text{ (Time frames)} \times 2 \text{ (Channels)}
  • 展平后维度:d=32×391×2=25,024d = 32 \times 391 \times 2 = 25,024

我们的网络结构设计如下(漏斗形):

  • Encoder:2502482242056102451225625024 \to 8224 \to 2056 \to 1024 \to 512 \to \mathbf{256}
  • Decoder:25651210242056822425024\mathbf{256} \to 512 \to 1024 \to 2056 \to 8224 \to 25024

4.2.2 PyTorch 代码实现 (Baseline 模型)#

这是一个标准的 PyTorch 模型类定义。注意观察 forward 函数中数据流动的方向。

import torch
from torch import nn
class Autoencoder(nn.Module):
def __init__(self, input_dim=25024, latent_dim=256):
super(Autoencoder, self).__init__()
# --- 编码器 (Encoder) ---
# 任务:降维,提取特征
self.encoder = nn.Sequential(
nn.Linear(input_dim, 8224),
nn.ReLU(), # 激活函数引入非线性
nn.Linear(8224, 2056),
nn.ReLU(),
nn.Linear(2056, 1024),
nn.ReLU(),
nn.Linear(1024, 512),
nn.ReLU(),
nn.Linear(512, latent_dim), # 瓶颈层:256
nn.ReLU()
)
# --- 解码器 (Decoder) ---
# 任务:升维,重构数据
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 512),
nn.ReLU(),
nn.Linear(512, 1024),
nn.ReLU(),
nn.Linear(1024, 2056),
nn.ReLU(),
nn.Linear(2056, 8224),
nn.ReLU(),
nn.Linear(8224, input_dim) # 回归到原始维度
# 注意:最后一层通常不加 ReLU,因为输出可能包含负数(取决于数据预处理)
)
def forward(self, x):
# x 的形状: [Batch_Size, 25024]
z = self.encoder(x) # 压缩得到 Latent Code
x_hat = self.decoder(z) # 解码得到重构数据
return x_hat, z

4.3 损失函数与优化#

模型搭建好了,怎么训练它?我们需要一个指标来衡量“还原得像不像”。

4.3.1 MSE (均方误差)#

对于回归任务(重构像素值/分贝值),最常用的损失函数是 MSE (Mean Squared Error)

L=1Ni=1Nxix^i2L = \frac{1}{N} \sum_{i=1}^{N} ||x_i - \hat{x}_i||^2

它计算原始声谱图xx和重构声谱图x^\hat{x}之间每一个像素点的差值的平方和。

  • 如果 L0L \to 0,说明模型的重构结果逐渐接近输入。

4.3.2 训练循环 (Training Loop) 的解剖#

这是深度学习中最标准的过程:前向传播\to算 Loss \to反向传播 \to更新参数

from torch import optim
def train_model(dataloader, model, epochs=20):
# 1. 定义损失函数与优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 2. 设备自适应 (Mac MPS / NVIDIA CUDA / CPU)
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
model.to(device)
for epoch in range(epochs):
total_loss = 0
for batch_audio in dataloader:
# Flatten: [Batch, 2, 32, 391] -> [Batch, 25024]
# 必须拍扁才能喂给全连接层
inputs = batch_audio.view(batch_audio.size(0), -1).to(device)
# --- 前向传播 ---
reconstructed, _ = model(inputs)
# --- 计算 Loss ---
loss = criterion(reconstructed, inputs)
# --- 反向传播 ---
optimizer.zero_grad() # 清空过往梯度
loss.backward() # 计算当前梯度
optimizer.step() # 更新权重
total_loss += loss.item()
print(f"Epoch {epoch+1}, Average Loss: {total_loss / len(dataloader):.4f}")

第五部分:自编码器训练优化#

基础模型训练不稳定时,常见原因包括梯度消失、梯度爆炸,以及网络各层输入分布持续变化。下面分别介绍几个常用处理方法。

5.1 批归一化 (Batch Normalization)#

5.1.1 痛点:内部协变量偏移#

想象你在学开车。

  • 第一天,教练让你开平路(数据分布 A)。你学会了。
  • 第二天,教练突然让你开山路(数据分布 B)。你之前学的参数(油门控制)可能完全失效了,必须重新适应。

在深层网络中,每一层的输入都是上一层的输出。随着参数不断更新,每一层输出的数据分布都在剧烈波动。这就导致后一层的神经元必须不停地去适应新的分布,学习效率极低。

5.1.2 批归一化的处理方式#

Batch Normalization (BN) 会对当前批次的数据做标准化,再通过可学习参数调整缩放和偏移。 y=xμBσB2+ϵγ+βy = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \cdot \gamma + \beta

  • γ\gamma β\beta:这是模型可以学习的参数,让它保留一定的灵活性(如果它不想完全归一化)。
  • 效果:它通常能改善训练稳定性,并允许使用更大的学习率;实际收益取决于网络结构、数据和超参数。

5.2 激活函数的进化:从 ReLU 到 LeakyReLU#

5.2.1 痛点:神经元坏死 (Dead ReLU)#

我们之前使用的是标准的 ReLUy=max(0,x)y = max(0, x)

  • 如果输入是正数,直接通过。
  • 如果输入是负数,直接变 0。

问题来了:如果某一层的一个神经元运气不好,初始权重导致它对所有输入都输出负数,那么它的梯度永远是 0。 梯度为 0\rightarrow权重不更新\rightarrow这个神经元永远“死”了。 在一个大网络中,可能有一半的神经元都是“死”的,根本没参与工作。

5.2.2 LeakyReLU 的处理方式#

LeakyReLU (Leaky Rectified Linear Unit) 对负数部分手下留情: y={x,if x>00.01x,if x0y = \begin{cases} x, & \text{if } x > 0 \\ 0.01x, & \text{if } x \le 0 \end{cases} 即使输入是负数,它也有一个微小的斜率(0.01)。这就保证了梯度(信息)依然可以回流,神经元有机会通过更新权重“复活”。


5.3 训练动态控制:打乱数据 (Shuffling)#

DataLoader 中,有一个看似不起眼的参数 shuffle=True

  • 不打乱:模型可能会背诵数据的顺序(例如:第 1 个一定是猫,第 2 个一定是狗)。这不是学习,这是作弊。
  • 打乱:打破数据之间的时序相关性。每次 Batch 看到的组合都是随机的,迫使模型去学习数据本身的特征,而不是顺序的规律。

5.4 改进后的自编码器代码#

下面在基础模型中加入 Batch Normalization 和 LeakyReLU,便于比较训练表现。

class ImprovedAE(nn.Module):
def __init__(self, input_dim=25024):
super(ImprovedAE, self).__init__()
# --- 升级版编码器 ---
self.encoder = nn.Sequential(
# Layer 1
nn.Linear(input_dim, 8224),
nn.BatchNorm1d(8224), # Trick 1: 加速收敛的神器
nn.LeakyReLU(0.1), # Trick 2: 防止神经元坏死 (负斜率0.1)
# Layer 2
nn.Linear(8224, 2056),
nn.BatchNorm1d(2056),
nn.LeakyReLU(0.1),
# Layer 3
nn.Linear(2056, 1024),
nn.BatchNorm1d(1024),
nn.LeakyReLU(0.1),
# ... 中间层省略,逻辑同上 ...
# Bottleneck Layer
nn.Linear(512, 256),
nn.BatchNorm1d(256),
nn.LeakyReLU(0.1)
)
# --- 升级版解码器 ---
self.decoder = nn.Sequential(
nn.Linear(256, 512),
nn.BatchNorm1d(512),
nn.LeakyReLU(0.1),
# ... 中间层省略 ...
nn.Linear(2056, 8224),
nn.BatchNorm1d(8224),
nn.LeakyReLU(0.1),
# Output Layer
# 输出层通常不需要 BN,直接映射回原始空间
nn.Linear(8224, input_dim)
)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return decoded, encoded
# 在训练时,别忘了开启 Shuffle
# dataloader = DataLoader(dataset, batch_size=25, shuffle=True)

预期结果#

实际差异取决于数据集和超参数。比较时应保持训练轮数、批次大小和优化器等条件一致,再观察收敛速度与验证集重构误差。


小结#

这份笔记串联了以下内容:

  1. 理解原理:从 NLP 的词向量到 Transformer 架构,理解机器如何量化语义。
  2. 数据工程:掌握 Train/Test 切分和 One-Hot 编码的统计学和几何意义。
  3. 信号处理:利用傅里叶变换(STFT/Mel)将不可见的声波转化为可视的 Tensor。
  4. 模型搭建:构建 Autoencoder,理解“压缩-重构”的流形学习思想。
  5. 训练优化:使用 BatchNorm、LeakyReLU 和数据打乱改善训练过程。

这些概念最终都落在具体的数据处理、数学公式和代码实现上。理解每一步输入输出的变化,比记住某个模型名称更有用。

词向量、音频处理与自编码器学习笔记
https://mj3622.github.io/posts/学习笔记/数据科学/数据科学2/
作者
Minjer
发布于
2026-01-31
许可协议
CC BY-NC-SA 4.0