词向量、音频处理与自编码器学习笔记
前言
这份笔记分为五部分:词向量与 Transformer、数据集划分与特征编码、音频预处理、自编码器,以及训练时常用的优化方法。重点放在概念之间的联系和对应代码上。
第一部分:从离散符号到语义空间
自然语言处理(NLP)的核心挑战在于:如何将人类的离散符号(单词、字符)映射到计算机能够进行微积分运算的连续空间中。
这一部分先看词如何映射为向量,再看 Transformer 如何使用这些向量处理和生成语言。
1.1 语义的向量化 (Vectorization)
计算机本质上是一个大型计算器,它只能处理数字。对于单词 “Apple”,计算机看到的只是 ASCII 码的组合。为了让计算机“理解”语义,我们需要将词转换为向量。
1.1.1 稀疏矩阵 (Sparse) vs. 维度灾难
在深度学习爆发之前,我们通常使用 One-Hot Encoding(独热编码) 或 Bag-of-Words(词袋模型)。 假设词表有 10,000 个词:
- (索引 52)
- (索引 99)
这种表示方法有两个主要限制:
- 稀疏性 (Sparsity):向量维度很高,且绝大多数元素为 0,会占用额外存储和计算资源。
- 正交性 (Orthogonality):这是最根本的问题。在欧几里得空间中,所有 One-Hot 向量两两垂直。
这意味着,对于计算机而言,“苹果”和“香蕉”的相似度为 0,这与“苹果”和“卡车”的相似度(也是 0)没有任何区别。模型无法捕捉语义相似性。
1.1.2 稠密向量 (Dense Embeddings)与分布假说
为了解决正交性问题,我们引入了 Word Embeddings(词嵌入)。它的核心思想基于分布假说 (Distributional Hypothesis):上下文相似的词,其语义也相似。
我们将每个词映射到一个低维(如 256 维、512 维)的实数向量空间中。在这个空间里:
- 维度 (Dimensions):不再代表具体的“第几个词”,而是代表某种潜在的语义特征(如“是否是食物”、“是否有生命”、“体积大小”),尽管这些特征通常是隐含的,人类无法直接解读。
- 距离 (Distance):向量之间的几何距离代表了语义距离。
1.1.3 Word2Vec 的数学直觉
经典的 Word2Vec 模型(如 Skip-gram)会呈现一些线性关系。训练充分时,向量空间中可能出现如下结果:
这说明模型不仅学到了词的“位置”,还学到了词与词之间的关系方向向量(例如, 的方向向量代表了“皇室化”的概念)。
在作业或工程实践中,我们很少从头训练 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)。
这个公式本质上是在计算:为了理解当前这个词,我需要在这个句子里的其他词上分配多少注意力?
1.2.2 Encoder 与 Decoder
并不是所有 Transformer 都是一样的。根据任务不同,架构分为两派:
| 架构类型 | 代表模型 | 核心机制 | 适用任务 |
|---|---|---|---|
| Encoder-only | BERT, RoBERTa | 双向注意力 (Bidirectional):模型能同时看到上下文。例如在填空任务 “I ate an [MASK] for lunch.” 中,模型可以根据 ate 和 lunch 推断出 apple。 | 理解任务:情感分析、文本分类、命名实体识别(NER)。 |
| Decoder-only | GPT 系列, Llama | 单向因果注意力 (Causal/Autoregressive):模型只能看到当前词之前的词,被强制用来预测“下一个词”。 | 生成任务:聊天机器人、故事创作、代码生成。 |
为什么聊天机器人首选 Decoder-only? 因为对话本质上是一个序列生成过程。人类说话也是根据已经说出的内容,逐字推导下一个字。Encoder-only 架构虽然理解能力强,但无法高效地进行这种自回归式的生成。
1.2.3 预训练 (Pre-training) 与微调 (Fine-tuning)
在实际应用(如构建客服机器人)中,我们通常遵循两阶段范式:
- Pre-training (通识教育):
- 数据:TB 级的互联网文本。
- 任务:Next Token Prediction(预测下一个词)。
- 结果:模型学会了语法、世界知识、逻辑推理。此时它是一个博学的“通才”,但不懂特定规矩。
- Fine-tuning (职业培训):
- 数据:高质量的特定领域数据(如客服对话记录 Q&A)。
- 任务:在特定数据分布上调整参数。
- 结果:模型将原本广泛的概率分布,收敛到特定领域的表达方式上。例如,学会礼貌用语,学会只回答与产品相关的问题。
第二部分:数据预处理与特征工程
模型效果不仅取决于网络结构,也受数据划分和特征表示影响。这一部分讨论如何切分数据集,以及如何把类别等现实概念转换为模型可用的数值特征。
2.1 数据集划分与泛化
切分数据集(Splitting)不只是为了留一部分数据做最终评分,更重要的是观察模型是否过拟合,以及它对未见数据的泛化能力。
2.1.1 训练集、验证集与测试集 (Train / Val / Test)
常见的训练流程会使用三个相互独立的数据集:
- 训练集 (Training Set):教科书。模型通过它来计算梯度,更新参数(Weights)。
- 验证集 (Validation Set):用于选择学习率、层数等超参数,不参与模型参数训练。训练指标很好而验证指标明显较差时,通常说明模型已经过拟合。
- 测试集 (Test Set):用于最终评估。模型和超参数确定前,不应根据测试结果反复调整方案。
⚠️ 数据泄露 (Data Leakage) 的陷阱: 如果你根据测试集的得分去调整模型结构,这在统计学上叫“数据泄露”。你以为模型变聪明了,其实它只是通过你的手“作弊”看过了答案。
2.1.2 进阶技巧:K-Fold 交叉验证 (Cross-Validation)
在数据量有限的情况下(比如只有几千条数据),简单的 8:1:1 切分存在巨大的随机性。如果运气不好,切出来的验证集恰好全是很难的样本,你会误以为模型很差。
为了消除这种随机性带来的方差 (Variance),我们采用 K-Fold 交叉验证:
- 核心逻辑:将训练数据切成份(比如 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 npfrom 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, test2.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 轴
- 香蕉 Y 轴
- 梨 Z 轴
正交性 (Orthogonality): 在几何上,X 轴垂直于 Y 轴。计算它们的点积:。 这意味着计算机认为它们之间的相似度为 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_encoded2.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):对于最高频率为 的带限信号,采样率至少应高于 ,才能避免混叠。
- 例子:人耳能听到的最高频率大约是 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:
- 分帧 (Framing):把长音频切成无数个极短的片段(例如 25 毫秒)。
- 加窗 (Windowing):为了防止切割边缘产生噪音,给每个片段乘上一个“汉宁窗”或“汉明窗”(让两头变细,中间突出)。
- 变换 (FFT):对每一小段做快速傅里叶变换。
3.2.2 Mel 刻度(Mel Scale)
物理上的频率(赫兹 Hz)是线性的,但人耳的听觉是非线性的:
- 我们能轻易分辨 100Hz 和 200Hz 的区别(低音)。
- 但很难分辨 10000Hz 和 10100Hz 的区别(高音)。
为了让 AI 像人一样“听”声音,我们需要把频率轴拉伸变形,这就有了 Mel 刻度。它放大了低频细节,压缩了高频细节。
3.2.3 分贝(Decibels)与对数压缩
声音的能量范围极大。蚊子的叫声和喷气式飞机的引擎声,能量可能相差一万亿倍。如果直接用数值表示,神经网络的梯度会瞬间爆炸。 因此,我们取对数 (),将其转换为分贝 (dB)。
3.3 工程实战:构建鲁棒的音频预处理管道
在真实项目中,我们需要处理海量的 .wav 文件。这里有一个关键的工程挑战:变长输入 (Variable Length)。
有的录音只有 2 秒,有的有 10 秒。但深度学习模型(如 CNN 或 Autoencoder)通常要求固定的输入维度。
核心策略:截断 (Truncate) 与 填充 (Pad)
我们需要设定一个“标准时长”(比如 6 秒,约 100,000 个采样点)。
- 长了就砍:超过标准时长的部分丢弃。
- 短了就补:不足的部分用 0 填充(Silence Padding)。
代码实现 (PyTorch/Torchaudio)
下面是一个工业级的音频预处理函数示例,它将原始音频文件转化为可以直接输入模型的 Tensor。
import torchimport torchaudiofrom 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)
想象你要把一张高清大图传给朋友,但带宽极低。
- 编码 (Encode):你把图片压缩成一个极小的 ZIP 包。
- 传输:发送 ZIP 包。
- 解码 (Decode):朋友根据 ZIP 包还原出图片。
如果重构结果与原图接近,说明潜在向量保留了重构所需的主要特征。
自编码器就是在这个过程中加入神经网络:
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 维度变换推导
还记得我们在第三部分生成的声谱图吗?
- 形状:
- 展平后维度:
我们的网络结构设计如下(漏斗形):
- Encoder:
- Decoder:
4.2.2 PyTorch 代码实现 (Baseline 模型)
这是一个标准的 PyTorch 模型类定义。注意观察 forward 函数中数据流动的方向。
import torchfrom 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, z4.3 损失函数与优化
模型搭建好了,怎么训练它?我们需要一个指标来衡量“还原得像不像”。
4.3.1 MSE (均方误差)
对于回归任务(重构像素值/分贝值),最常用的损失函数是 MSE (Mean Squared Error)。
它计算原始声谱图和重构声谱图之间每一个像素点的差值的平方和。
- 如果 ,说明模型的重构结果逐渐接近输入。
4.3.2 训练循环 (Training Loop) 的解剖
这是深度学习中最标准的过程:前向传播算 Loss 反向传播 更新参数。
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) 会对当前批次的数据做标准化,再通过可学习参数调整缩放和偏移。
- 和 :这是模型可以学习的参数,让它保留一定的灵活性(如果它不想完全归一化)。
- 效果:它通常能改善训练稳定性,并允许使用更大的学习率;实际收益取决于网络结构、数据和超参数。
5.2 激活函数的进化:从 ReLU 到 LeakyReLU
5.2.1 痛点:神经元坏死 (Dead ReLU)
我们之前使用的是标准的 ReLU:。
- 如果输入是正数,直接通过。
- 如果输入是负数,直接变 0。
问题来了:如果某一层的一个神经元运气不好,初始权重导致它对所有输入都输出负数,那么它的梯度永远是 0。 梯度为 0权重不更新这个神经元永远“死”了。 在一个大网络中,可能有一半的神经元都是“死”的,根本没参与工作。
5.2.2 LeakyReLU 的处理方式
LeakyReLU (Leaky Rectified Linear Unit) 对负数部分手下留情: 即使输入是负数,它也有一个微小的斜率(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)预期结果
实际差异取决于数据集和超参数。比较时应保持训练轮数、批次大小和优化器等条件一致,再观察收敛速度与验证集重构误差。
小结
这份笔记串联了以下内容:
- 理解原理:从 NLP 的词向量到 Transformer 架构,理解机器如何量化语义。
- 数据工程:掌握 Train/Test 切分和 One-Hot 编码的统计学和几何意义。
- 信号处理:利用傅里叶变换(STFT/Mel)将不可见的声波转化为可视的 Tensor。
- 模型搭建:构建 Autoencoder,理解“压缩-重构”的流形学习思想。
- 训练优化:使用 BatchNorm、LeakyReLU 和数据打乱改善训练过程。
这些概念最终都落在具体的数据处理、数学公式和代码实现上。理解每一步输入输出的变化,比记住某个模型名称更有用。
