文章目录
引言
本篇文字是为了深入了解深度神经网络(DNN)和卷积神经网络(CNN)的基础知识和实战技巧,并使用PyTorch框架进行实际操作和模型实现。
Abstract
The purpose of this article is to gain an in-depth understanding of the fundamentals and practical techniques of deep neural networks (DNN) and convolutional neural networks (CNN), as well as practical operation and model implementation using the PyTorch framework.
1. 张量与GPU的运用
1.1 张量基础
张量是PyTorch中的基础数据结构,可以看作是多维数组。以下是一些张量的基本操作:
import torch
#张量的基本运算
x = torch.tensor([1, 2, 3])
print(x)
y = torch.tensor([4, 5, 6])
z = x + y
print(z)
#张量的变换
a = torch.rand(2, 3)
print(a)
#变换张量形状
b = a.view(3, 2)
print(b)
1.2 模型部署GPU
在PyTorch中使用GPU
# 检查是否有可用的GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
# 将张量移动到GPU
x = torch.tensor([1, 2, 3], device=device)
print(x)
2. 深度神经网络(DNN)及卷积神经网络(CNN)基本原理
2.1 DNN到CNN的转变
深度神经网络(DNN)由多个隐藏层组成,每个隐藏层包含多个神经元。以下是一个简单的DNN例子:
import torch.nn as nn
import torch.nn.functional as F
class SimpleDNN(nn.Module):
def __init__(self):
super(SimpleDNN, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
卷积神经网络(CNN)主要用于处理图像数据。以下是一个简单的CNN例子:
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3, 1) #input output kernel_size stride
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.fc1 = nn.Linear(9216, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2) #较少计算量核增加特征的鲁棒性
x = torch.flatten(x, 1)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
2.2 数据集划分
数据集的获取与准备
from torchvision import datasets, transforms
transform = transforms.Compose([transforms.ToTensor()])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)
数据集划分
from torch.utils.data import DataLoader
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
2.3 网络训练
模型构建与初始化
model = SimpleCNN().to(device)
损失函数与优化器选择
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
训练过程与监控
def train(model, device, train_loader, optimizer, criterion, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
if batch_idx % 10 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}')
for epoch in range(1, 11):
train(model, device, train_loader, optimizer, criterion, epoch)
2.4 测试与评估
模型测试方法
def test(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += criterion(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset):.0f}%)\n')
test(model, device, test_loader)
2.5 模型应用对比
DNN和CNN的效果对比
dnn_model = SimpleCNN().to(device)
cnn_model = SimpleDNN().to(device)
test(dnn_model, test_loader)
test(cnn_model, test_loader)
3. 小批量梯度下降与训练技巧
3.1 小批量概念
小批量梯度下降(Mini-Batch Gradient Descent)是介于全批量梯度下降和随机梯度下降之间的一种优化算法。它每次使用一个小批量(mini-batch)数据进行参数更新,从而在计算效率和收敛速度之间取得平衡。
3.2 实战操作
下面我们将使用PyTorch实现一个简单的神经网络,并使用小批量梯度下降进行训练。
准备数据集
我们使用MNIST手写数字数据集作为示例数据集。
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 定义数据转换
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 加载训练和测试数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
# 定义数据加载器
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
定义模型(CNN)
import torch.nn as nn
import torch.nn.functional as F
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.fc1 = nn.Linear(9216, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, 2)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
训练和测试函数
import torch.optim as optim
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
def train(model, device, train_loader, optimizer, criterion, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
if batch_idx % 10 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}')
def test(model, device, test_loader, criterion):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += criterion(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.0f}%)\n')
训练模型
使用小批量梯度下降训练模型,并测试其性能。
for epoch in range(1, 11):
train(model, device, train_loader, optimizer, criterion, epoch)
test(model, device, test_loader, criterion)
3.3 参数优化
在深度学习中,参数优化是提高模型性能的关键步骤。以下是常见的参数优化技巧:
学习率调整策略
学习率调整策略是通过动态调整学习率来加速模型收敛和避免局部最优解。
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=0.7)
for epoch in range(1, 11):
train(model, device, train_loader, optimizer, criterion, epoch)
test(model, device, test_loader, criterion)
scheduler.step()
正则化技术
正则化技术通过在损失函数中加入正则项来防止过拟合。以下是使用L2正则化的例子:
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.01)
权重衰减(weight decay)也称为L2正则化,设置为0.01。这是一种防止模型过拟合的方法,通过在损失函数中加入关于模型权重的惩罚项,促使权重向零靠近,从而简化模型。较大的权重衰减意味着更强的正则化效果。
早停法
早停法通过在验证集上的性能不再提升时提前停止训练来防止过拟合。以下是一个早停法的实现思路:
best_accuracy = 0.0
patience = 5
trigger_times = 0
for epoch in range(1, 51):
train(model, device, train_loader, optimizer, criterion, epoch)
accuracy = test(model, device, test_loader, criterion)
if accuracy > best_accuracy:
best_accuracy = accuracy
trigger_times = 0
else:
trigger_times += 1
if trigger_times >= patience:
print('Early stopping!')
break
4 手写数字识别案例
以下是使用LeNet-5网络实现手写数字识别的完整案例
import torch
import torchvision as tv
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import argparse
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.backends.cudnn.enabled = False
# 定义网络结构,只是定义,没有运行顺序
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
#构造网络有两种方式一个是seqential还有一个是module,前者在后者中也可以使用,这里使用的是sequential方式,将网络结构按顺序添加即可
self.conv1 = nn.Sequential( #input_size=(1*28*28)
#第一个卷积层,输入通道为1,输出通道为6,卷积核大小为5,步长为1,填充为2保证输入输出尺寸相同
nn.Conv2d(1, 6, 5, 1, 2), #padding=2保证输入输出尺寸相同
#激活函数,两个网络层之间加入,引入非线性
nn.ReLU(), #input_size=(6*28*28)
#池化层,大小为2步长为2
nn.MaxPool2d(kernel_size=2, stride=2),#output_size=(6*14*14)
)
self.conv2 = nn.Sequential(
nn.Conv2d(6, 16, 5),
nn.ReLU(), #input_size=(16*10*10)
nn.MaxPool2d(2, 2) #output_size=(16*5*5)
)
#全连接层,输入是16*5*5特征图,神经元数目120
self.fc1 = nn.Sequential(
nn.Linear(16 * 5 * 5, 120),
nn.ReLU()
)
#全连接层神经元数目输入为上一层的120,输出为84
self.fc2 = nn.Sequential(
nn.Linear(120, 84),
nn.ReLU()
)
#最后一层全连接层神经元数目10,与上一个全连接层同理
self.fc3 = nn.Linear(84, 10)
# 定义前向传播过程,输入为x,也就是把前面定义的网络结构赋予了一个运行顺序
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
# nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
return x
#使得我们能够手动输入命令行参数,就是让风格变得和Linux命令行差不多
parser = argparse.ArgumentParser()
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #模型保存路径
parser.add_argument('--net', default='./model/net.pth', help="path to netG (to continue training)") #模型加载路径
opt = parser.parse_args()
# 超参数设置
EPOCH = 8 #遍历数据集次数
BATCH_SIZE = 64 #批处理尺寸(batch_size)一次训练的样本数,相当于一次将64张图送入
LR = 0.001 #学习率
# 定义数据预处理方式,将图片转换成张量的形式,因为后续的操作都是以张量形式进行的
transform = transforms.ToTensor()
#下载四个数据集
# 定义训练数据集
trainset = tv.datasets.MNIST(
root='./data/',
train=True,
download=True,
transform=transform)
# 定义训练批处理数据
trainloader = torch.utils.data.DataLoader(
trainset,
batch_size=BATCH_SIZE,
shuffle=True,
)
# 定义测试数据集
testset = tv.datasets.MNIST(
root='./data/',
train=False,
download=True,
transform=transform)
# 定义测试批处理数据
testloader = torch.utils.data.DataLoader(
testset,
batch_size=BATCH_SIZE,
shuffle=False,
)
# 定义损失函数loss function 和优化方式(采用SGD)
net = LeNet().to(device)
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数,通常用于多分类问题上
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9) #梯度下降法求损失函数最小值
# 训练
if __name__ == "__main__":
#八次遍历训练
for epoch in range(EPOCH):
sum_loss = 0.0
# 读取下载的数据集
for i, data in enumerate(trainloader):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
# 梯度清零
optimizer.zero_grad()
# forward + backward正向传播以及反向传播更新网络参数
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 每训练100个batch打印一次平均loss,基本上是一直减小的,一个epoch有9个因为是6w张,一次batch64个
sum_loss += loss.item()
if i % 100 == 99:
print('[%d, %d] loss: %.03f'
% (epoch + 1, i + 1, sum_loss / 100))
sum_loss = 0.0
# 每跑完一次epoch测试一下准确率
with torch.no_grad():
correct = 0
total = 0
for data in testloader:
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = net(images)
# 取得分最高的那个类
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('第%d个epoch的识别准确率为:%d%%' % (epoch + 1, (100 * correct / total)))
#torch.save(net.state_dict(), '%s/net_%03d.pth' % (opt.outf, epoch + 1))
5 总结
在本周总结中,介绍了PyTorch中深度神经网络和卷积神经网络的基础知识和实战操作。通过对张量与GPU的运用、DNN和CNN的基本原理、小批量梯度下降与训练技巧的详细讲解,可以从基础知识入手,逐步掌握深度学习模型的原理与应用,并通过手写数字识别案例,了解如何在实际项目中应用这些技巧。