前言
文章概述《深度学习详解》- 3.6
书中介绍了深度学习中的几个核心概念和技术,包括动量(momentum)、均方根(sigma)、分类与回归的关系、softmax函数的应用以及交叉熵损失函数的选择。首先,动量和均方根都是用于调整学习率的技巧,尽管它们都考虑了过去的梯度信息,但动量关注的是梯度的方向而均方根则只考虑梯度的大小。其次,文章讨论了分类与回归的基本区别及联系,并通过独热编码方式解决了类别标识问题。接着,详细介绍了softmax函数的作用,即通过将输出归一化到0到1之间并突出最大值,使得模型能够进行概率分类。此外,文中还探讨了交叉熵作为分类问题的标准损失函数,相比于均方误差,交叉熵能更好地指导模型收敛至全局最优解。最后,提出了批量归一化(Batch Normalization, BN)技术,旨在改善模型训练过程中的梯度消失或爆炸问题,从而提高模型训练效率和性能。通过对这些关键技术和概念的阐述,文章提供了深度学习中常见问题的有效解决方案,有助于读者理解深度学习模型的工作原理及其优化策略。
个人学习笔记以及概念梳理,望对大家有所帮助。
思维导图3.6
分类优化算法(补充)
涉及的一些术语
术语 | 解释 |
分类 (Classification) | 一种监督学习任务,其目标是将输入数据分配到预定义的类别中。 |
回归 (Regression) | 一种监督学习任务,其目的是预测连续值的目标变量。 |
独热向量 (One-Hot Vector) | 一种表示分类变量的方法,其中每个类别对应向量中的一个元素,只有一个元素为1,其余均为0。 |
Softmax 函数 | 一种激活函数,用于将向量转换为概率分布,使得每个元素在0到1之间,并且所有元素之和为1。 |
Sigmoid 函数 | 一种激活函数,常用于二分类问题,输出介于0到1之间,可以视为概率。 |
交叉熵 (Cross Entropy) | 一种损失函数,用于衡量模型预测的概率分布与真实分布之间的差异。最小化交叉熵相当于最大化模型的似然性。 |
批量归一化 (Batch Normalization, BN) | 一种正则化技术,通过对每一批数据进行归一化处理,以加速训练过程并提高模型性能。 |
均方根 (Root Mean Square, RMS) | 在优化器中使用,通过平方梯度来衡量梯度的大小,而不考虑方向。 |
学习过程遇到的一些问题的理解:
1分类问题中的损失函数选择
选择建议:
数据平衡性:如果数据集类别分布相对均衡,可以选择二元交叉熵。
模型类型:如果是支持向量机,通常使用 Hinge 损失。
类别不平衡:在处理类别不平衡的问题时,考虑使用焦点损失。
模型解释性:如果需要较强的模型解释性,可以考虑使用 Hinge 损失和支持向量机。
代码运行
仿 实践(DNN)深度神经网络-分类任务 (详情可见,Docs (feishu.cn) )
Urbansound8K 是目前应用较为广泛的用于自动城市环境声分类研究的公共数据集。本文获取部分为标记的数据集,展示从标记数据到成功分类的全过程。
其中人工初步处理后数据集一共包含1658条已标注的声音片段(<=4s),包含5个分类:"car" "human" "barking" "music" "others"。
注:因为是人工处理,可能对部分声音分类存在个体理解差异,主要是展示声音分类处理的整体过程,精度可能会有所偏差。
实现功能: 将长度为4秒的语音分类为5种:"car" "human" "barking" "music" "others"。
同时输出每一种分类的精度。
处理过程
数据获得:
下载完成后,进行解压,将其中的fold{i}每一个文件夹里的音频移到一个文件夹中。
数据集制作:
import os
import librosa
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 定义要读取的目录
directory = r'.\datasets\4s' #此处修改为先前存储所有视频的文件夹
# 设置 MFCC 参数
n_mfcc = 13 # 提取的 MFCC 数量
hop_length = 512 # 跳跃长度
target_duration = 4 # 目标音频时长(秒)
# 创建一个空列表来存储数据
data = []
# 遍历目录中的所有 .wav 文件
for filename in os.listdir(directory):
if filename.endswith(".wav"):
filepath = os.path.join(directory, filename)
# 使用 librosa 读取音频文件
y, sr = librosa.load(filepath, sr=None)
# 检查音频文件的时长是否为4秒
duration = librosa.get_duration(y=y, sr=sr)
if abs(duration - target_duration) < 0.1: # 允许一定的误差范围
# 提取文件名中的分类信息
parts = filename.split("-")
category_code = parts[1]
# 根据分类信息移动文件到相应的类别文件夹
if category_code in ["1", "8"]:
category = "car"
elif category_code == "2":
category = "human"
elif category_code == "3":
category = "barking"
elif category_code == "9":
category = "music"
else:
category = "others" # "car" "human" "barking" "music" "others"
# 计算 MFCC 特征
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc, hop_length=hop_length)
# 对每个 MFCC 系数取平均值
mfccs_mean = np.mean(mfccs, axis=1)
# 将特征添加到数据列表中
data.append({
'filename': filename,
'class': category,
**{f'mfcc_{i}': mfcc for i, mfcc in enumerate(mfccs_mean)}
})
# 创建 Pandas DataFrame
df = pd.DataFrame(data)
# 保存为 CSV 文件
df.to_csv(os.path.join(directory, 'mfcc_dataset.csv'), index=False)
运行后,成功获得数据集,对应列名分别为 'filename' 'class' 'mfcc_0' 'mfcc_1' 'mfcc_2' 'mfcc_3' ... 'mfcc_12' 其中'class' 是对应的类别("car" "human" "barking" "music" "others") 而mfcc_{} 则是 MFCC 系数相关的特征
显示数据集相关数据:
# 加载CSV文件
file_path = './datasets/4s/mfcc_dataset.csv'
data = pd.read_csv(file_path)
#显示前5个数据
print(data.head())
# 计算类别数量
category_counts = data['class'].value_counts()
# 绘制柱状图
plt.figure(figsize=(10, 6))
bars = category_counts.plot(kind='bar', color='skyblue')
# 添加数值标签
for bar in bars.patches:
# 获取柱子的高度
height = bar.get_height()
# 在柱子上方添加文本标签
plt.text(bar.get_x() + bar.get_width() / 2, height, f'{height}', ha='center', va='bottom')
plt.title('Number of Samples per Class')
plt.xlabel('Class')
plt.ylabel('Number of Samples')
plt.xticks(rotation=0)
plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域
plt.show()
结果如下:
声音数据类型分布情况
正式开始分类任务:
使用以ReLU 为激活函数的全连接层 (nn.Linear) 构建简单的神经网络来进行分类。
导入包
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
数据集相关处理
# 定义数据集类
class AudioDataset(Dataset):
def __init__(self, features, labels, transform=None):
self.features = features
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
feature = self.features[idx]
label = self.labels[idx]
if self.transform:
feature = self.transform(feature)
return feature, label
# 加载数据集
dataset_path = r'.\datasets\4s\mfcc_dataset.csv'
data = pd.read_csv(dataset_path)
# 提取特征和标签
X = data.drop(['filename', 'class'], axis=1).values
y = data['class']
# 对类别标签进行编码
encoder = LabelEncoder()
y = encoder.fit_transform(y)
# 将标签转换为 one-hot 编码
num_classes = len(np.unique(y))
y = np.eye(num_classes)[y]
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.long)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.long)
# 创建数据集对象
train_dataset = AudioDataset(X_train, y_train)
test_dataset = AudioDataset(X_test, y_test)
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
定义模型
# 定义模型
class DNN(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(DNN, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim // 2)
self.fc3 = nn.Linear(hidden_dim // 2, output_dim)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
# 初始化模型
input_dim = X_train.shape[1]
hidden_dim = 128
output_dim = num_classes
model = DNN(input_dim, hidden_dim, output_dim)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
训练模型
# 训练模型
num_epochs = 50
for epoch in range(num_epochs):
model.train()
for batch_features, batch_labels in train_loader:
optimizer.zero_grad()
# 这里需要将 one-hot 编码的标签转换回整数索引
batch_labels = torch.argmax(batch_labels, dim=1)
outputs = model(batch_features)
loss = criterion(outputs, batch_labels)
loss.backward()
optimizer.step()
评估模型
# 评估模型
model.eval()
y_true = []
y_pred = []
with torch.no_grad():
for features, labels in test_loader:
# 同样地,在测试阶段也需要将 one-hot 编码的标签转换回整数索引
labels = torch.argmax(labels, dim=1)
outputs = model(features)
_, predicted = torch.max(outputs.data, 1)
y_true.extend(labels.numpy())
y_pred.extend(predicted.numpy())
# 计算精度和召回率
report = classification_report(y_true, y_pred, target_names=encoder.classes_)
print("Classification Report:")
print(report)
# 绘制混淆矩阵
cm = confusion_matrix(y_true, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=encoder.classes_)
disp.plot()
plt.show()
# 从 classification_report 中提取 Precision 和 F1-Score
report_data = []
lines = report.split('\n')
for line in lines[2:-3]: # 跳过报告的头部和尾部
row_data = line.split(' ')
# 清除空白项
row_data = [item for item in row_data if item]
# 检查是否有足够的数据
if len(row_data) >= 4:
row = {}
row['class'] = row_data[0]
row['precision'] = float(row_data[1].strip('%')) / 100.0
row['recall'] = float(row_data[2].strip('%')) / 100.0
row['f1_score'] = float(row_data[3].strip('%')) / 100.0
report_data.append(row)
# 提取 class names, precision 和 F1-Score
classes = [row['class'] for row in report_data]
precisions = [row['precision'] for row in report_data]
f1_scores = [row['f1_score'] for row in report_data]
# 绘制折线图
plt.figure(figsize=(10, 5))
# 绘制 Precision 折线图
plt.plot(classes, precisions, marker='o', label='Precision', linestyle='--', linewidth=2)
# 绘制 F1-Score 折线图
plt.plot(classes, f1_scores, marker='s', label='F1-Score', linestyle='-', linewidth=2)
# 设置图表标题和坐标轴标签
plt.title('Precision and F1-Score per Class')
plt.xlabel('Class')
plt.ylabel('Score')
plt.legend()
# 显示图表
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
运行结果如下:
各类指标图像化
混淆矩阵:
可见,整体的分类效果还是不错的。
这里就不进一步优化, 下面,我们来验证以下内容的情况。
验证
1尝试无dropout下,使用不同的激活函数观察其带来的变化。
分析:
ReLU(蓝色):在早期的几个周期内表现最好,但在后续的周期中逐渐下降。
LeakyReLU(橙色):开始时表现良好,但随后逐渐落后于其他激活函数。
Sigmoid(绿色):在整个训练过程中表现最差,可能由于其饱和特性导致梯度消失的问题。
Tanh(红色):在训练初期表现不佳,但在后期逐渐提高并最终超过 LeakyReLU。
Softplus(紫色):开始时表现较差,但在训练过程中逐渐提升,最后超过了除 ReLU 外的所有其他激活函数。
总体来看,Softplus 表现出了较好的稳定性,尽管在早期不如 ReLU 和 Tanh,但它在训练过程中持续改进并在最后达到了最高的准确性。
代码如下:
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt
# 定义数据集类
class AudioDataset(Dataset):
def __init__(self, features, labels, transform=None):
self.features = features
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
feature = self.features[idx]
label = self.labels[idx]
if self.transform:
feature = self.transform(feature)
return feature, label
# 加载数据集
dataset_path = r'.\datasets\4s\mfcc_dataset.csv'
data = pd.read_csv(dataset_path)
# 提取特征和标签
X = data.drop(['filename', 'class'], axis=1).values
y = data['class']
# 对类别标签进行编码
encoder = LabelEncoder()
y = encoder.fit_transform(y)
# 将标签转换为 one-hot 编码
num_classes = len(np.unique(y))
y = np.eye(num_classes)[y]
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.long)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.long)
# 创建数据集对象
train_dataset = AudioDataset(X_train, y_train)
test_dataset = AudioDataset(X_test, y_test)
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
# 定义模型
class DNN(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, activation=nn.ReLU()):
super(DNN, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim // 2)
self.fc3 = nn.Linear(hidden_dim // 2, output_dim)
self.activation = activation()
def forward(self, x):
x = self.activation(self.fc1(x))
x = self.activation(self.fc2(x))
x = self.fc3(x)
return x
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# 记录不同激活函数下的准确率
activations = [nn.ReLU, nn.LeakyReLU, nn.Sigmoid, nn.Tanh, nn.Softplus]
accuracy_history = {activation.__name__: [] for activation in activations}
# 训练模型
num_epochs = 50
input_dim = X_train.shape[1]
hidden_dim = 128
output_dim = num_classes
for activation in activations:
model = DNN(input_dim, hidden_dim, output_dim, activation)
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(num_epochs):
model.train()
for batch_features, batch_labels in train_loader:
optimizer.zero_grad()
# 将 one-hot 编码的标签转换回整数索引
batch_labels = torch.argmax(batch_labels, dim=1)
outputs = model(batch_features)
loss = criterion(outputs, batch_labels)
loss.backward()
optimizer.step()
# 评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
for features, labels in test_loader:
# 同样地,在测试阶段也需要将 one-hot 编码的标签转换回整数索引
labels = torch.argmax(labels, dim=1)
outputs = model(features)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
accuracy_history[activation.__name__].append(accuracy)
print(f'Epoch [{epoch+1}/{num_epochs}], Activation: {activation.__name__}, Accuracy: {accuracy:.2f}%')
# 绘制不同激活函数下的准确率变化折线图
plt.figure(figsize=(10, 5))
for activation_name, accuracies in accuracy_history.items():
plt.plot(range(1, num_epochs + 1), accuracies, label=activation_name)
plt.title('Accuracy over Epochs for Different Activations')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.grid(True)
plt.show()
2尝试使用不同的dropout率并观察其对模型预测精度的影响。
分析:
Dropout 率为 0.0(蓝色):没有应用 dropout,模型在训练过程中表现出较高的波动性,尤其是在早期的几个周期内,然后逐渐趋于稳定。
Dropout 率为 0.2(橙色):相比于没有 dropout 的情况,该模型在训练过程中更加稳定,虽然初始精度较低,但随着训练的进行,精度逐渐提高。
Dropout 率为 0.4(绿色):与 0.2 相比,精度曲线更平滑,表明模型在训练过程中收敛得更快,而且在训练结束时达到最高精度。
Dropout 率为 0.6(红色):精度曲线仍然保持平稳,但相比之前两种情况,精度稍低一些。
Dropout 率为 0.8(紫色):精度最低,说明过高 dropout 率可能会降低模型的表现。
总的来说,dropout 率为 0.4 的模型在训练过程中表现最优,既避免了过拟合又保持了良好的精度。相比之下,没有 dropout 或者 dropout 率过高的模型都可能导致欠拟合或过拟合问题。
代码如下:
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt
# 定义数据集类
class AudioDataset(Dataset):
def __init__(self, features, labels, transform=None):
self.features = features
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
feature = self.features[idx]
label = self.labels[idx]
if self.transform:
feature = self.transform(feature)
return feature, label
# 加载数据集
dataset_path = r'.\datasets\4s\mfcc_dataset.csv'
data = pd.read_csv(dataset_path)
# 提取特征和标签
X = data.drop(['filename', 'class'], axis=1).values
y = data['class']
# 对类别标签进行编码
encoder = LabelEncoder()
y = encoder.fit_transform(y)
# 将标签转换为 one-hot 编码
num_classes = len(np.unique(y))
y = np.eye(num_classes)[y]
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 转换为 PyTorch 张量
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.long)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.long)
# 创建数据集对象
train_dataset = AudioDataset(X_train, y_train)
test_dataset = AudioDataset(X_test, y_test)
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
class DNN(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, activation=nn.ReLU, dropout_rate=0.0):
super(DNN, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim // 2)
self.fc3 = nn.Linear(hidden_dim // 2, output_dim)
self.activation = activation() # 实例化激活函数
self.dropout = nn.Dropout(p=dropout_rate)
def forward(self, x):
x = self.activation(self.fc1(x))
x = self.dropout(x)
x = self.activation(self.fc2(x))
x = self.dropout(x)
x = self.fc3(x)
return x
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# 记录不同 dropout 率下的准确率
dropout_rates = [0.0, 0.2, 0.4, 0.6, 0.8]
accuracy_history = {rate: [] for rate in dropout_rates}
# 训练模型
num_epochs = 50
input_dim = X_train.shape[1]
hidden_dim = 128
output_dim = num_classes
for dropout_rate in dropout_rates:
model = DNN(input_dim, hidden_dim, output_dim, dropout_rate=dropout_rate)
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(num_epochs):
model.train()
for batch_features, batch_labels in train_loader:
optimizer.zero_grad()
# 将 one-hot 编码的标签转换回整数索引
batch_labels = torch.argmax(batch_labels, dim=1)
outputs = model(batch_features)
loss = criterion(outputs, batch_labels)
loss.backward()
optimizer.step()
# 评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
for features, labels in test_loader:
# 同样地,在测试阶段也需要将 one-hot 编码的标签转换回整数索引
labels = torch.argmax(labels, dim=1)
outputs = model(features)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
accuracy_history[dropout_rate].append(accuracy)
print(f'Epoch [{epoch+1}/{num_epochs}], Dropout Rate: {dropout_rate}, Accuracy: {accuracy:.2f}%')
# 绘制不同 dropout 率下的准确率变化折线图
plt.figure(figsize=(10, 5))
for dropout_rate, accuracies in accuracy_history.items():
plt.plot(range(1, num_epochs + 1), accuracies, label=f'Dropout Rate: {dropout_rate}')
plt.title('Accuracy over Epochs for Different Dropout Rates')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.grid(True)
plt.show()
最后,基于这两个尝试说明
这只是基于特定数据集和网络结构的一个实验结果,实际选择最佳激活函数应根据具体情况而定。在某些情况下,ReLU 可能仍然是更好的选择,因为它简单有效并且计算效率高。此外,还可以考虑其他因素,如过拟合风险、模型复杂性和计算资源等。以及对于其他数据集和模型结构,最优的 dropout 率可能会有所不同。在实践中,建议尝试多种 dropout 率以找到最适合你的任务的最佳值。
补充资料(ChatGBT)
小结(实验)
数据集与任务
- 数据集:使用了UrbanSound8K数据集的一部分,包含了1658条已标注的4秒音频片段,分为5个类别:“car”、“human”、“barking”、“music”和“others”。
- 任务:对这5类音频进行分类。
数据预处理
- 使用MFCC(Mel频率倒谱系数)作为特征,提取了13个MFCC系数的平均值。
- 数据集被划分为训练集和测试集,比例为80%和20%。
模型架构
- 构建了一个简单的深度神经网络(DNN),包含两层ReLU激活函数的隐藏层和一个输出层。
- 使用PyTorch框架实现模型,采用Adam优化器和交叉熵损失函数进行训练。
实验结果
- 在50个epoch的训练后,模型在测试集上表现良好,分类报告展示了各个类别的精确度、召回率和F1分数。
- 混淆矩阵显示了模型在不同类别间的预测准确性。
补充尝试
- 尝试无dropout下,使用不同的激活函数观察其带来的变化。
- 尝试使用不同的dropout率并观察其对模型预测精度的影响。