简单的三层全连接神经网络
在 PyTorch 里面可以很简单地定义三层全连接神经网络。
class simpleNet(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(simpleNet, self).__init__()
self.layer1 = nn.Linear(in_dim, n_hidden_1)
self.layer2 = nn.Linear(n_hidden_1, n_hidden_2)
self.layer3 = nn.Linear(n_hidden_2, out_dim)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
return x
对于这个三层网络,需要传递进去的参数包括:输入的维度、第一层网络的神经元个数、第二层网络神经元的个数,以及第三层网络(输出层)神经元的个数。
添加激活函数
接着改进一下网络,添加激活函数增加网络的非线性,方法也非常简单。
class Activation_Net(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Activation_Net, self).__init__()
self.layer1 = nn.Sequential(
nn.Linear(in_dim, n_hidden_1), nn.ReLU(True)
)
self.layer2 = nn.Sequential(
nn.Linear(n_hidden_1, n_hidden_2), nn.ReLU(True)
)
self.layer3 = nn.Sequential(
nn.Linear(n_hidden_2, out_dim)
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
return x
这里只需要在每层网络的输出部分添加激活函数就可以了,用到 nn.sequential(),这个函数是将网络的层组合到一起,比如上面将 nn.Linear() 和 nn.ReLU() 组合到一起作为 self.layer。注意最后一层输出层不能添加激活函数,因为输出的结果表示的是实际的得分。
添加批标准化
最后添加一个加快收敛速度的方法——批标准化。
class Batch_Net(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Batch_Net, self).__init__()
self.layer1 = nn.Sequential(
nn.Linear(in_dim, n_hidden_1),
nn.BatchNorm1d(n_hidden_1), nn.ReLU(True)
)
self.layer2 = nn.Sequential(
nn.Linear(n_hidden_1, n_hidden_2),
nn.BatchNorm1d(n_hidden_2), nn.ReLU(True)
)
self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
return x
同样使用 nn.seguential() 将 nn.BatchNorm1d() 组合到网络层中,注意批标准化一般放在全连接层的后面、非线性层(激活函数)的前面。
训练网络
网络的定义特别简单,现在用 MNIST 数据集训练网络并测试一下每种网络的结果。
MNIST 数据集是一个手写字体数据集,包含 0 到 9 这10个数字,其中有55000张训练集,10000张测试集,5000张验证集,图片大小是28x28的灰度图,如图3.28所示。
首先需要导入一些要用的包:
import torch
from torch import nn, optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
然后可以定义一些超参数,如batch_size、learning_rate 还有 num_epoches等。
# 超参数 Hyperparameters
batch_size = 64
learning_rate = 1e-2
num_epoches = 20
接着需要进行数据预处理,就像之前介绍的,需要将数据标准化,这里运用到的两数是torehvision.transforms,它提供了很多图片预处理的方法。这里使用两个方法:第一个是transforms.ToTensor(),第二个是 transforms.Normalize()。
transform.Torensor() 很好理解,就是将图片转换成 PyTorch 中处理的对象 Tensor 在转化的过程中 PyTorch自动将图片标准化了,也就是说 Tensor 的范围是0~1。接着我们使用 transforms.Normalize(),需要传入两个参数:第一个参数是均值,第二个参数是方差,做的处理就是减均值,再除以方差。
data_tf = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])]
)
这里 transforms.Compose() 将各种预处理操作组合到一起,transforms.Normalize([0.5],[0.5]) 表示减去 0.5 再除以 0.5,这样将图片转化到了 -1 ~ 1 之间,注意因为图片是灰度图,所以只有一个通道,如果是彩色的图片,有三通道,那么用 transforms.Normalize([a,b,c],[d,e,f]) 来表示每个通道对应的均值和方差。
然后读取数据集。
# 下载训练集 MNISST 手写数字训练集
train_dataset = datasets.MNIST(root="./data", train=True, transform=data_tf, download=True)
test_dataset = datasets.MNIST(root="./data", train=False, transform=data_tf)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
通过 PyTorch 的内置函数 torchvision.datasets.MNIST 导入数据集,传入数据预处理,前面介绍了如何定义自己的数据集,之后会用具体的例子说明。接着使用 torch.utils.data.DataLoader 建立一个数据迭代器,传入数据集和 batch_size,通过 shuffle=True 来表示每次迭代数据的时候是否将数据打乱。
接着导入网络,定义损害函数和优化方法。
model = simpleNet(28 * 28, 300, 100, 10)
if torch.cuda.is_available():
model.cuda()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
net.simpleNet 是定义的简单三层网络,里面的参数是28x28,300,100,10,其中输入的维度是 28x28,因为输入图片大小是28x28,然后定义两个隐藏层分别是 300 和 100,最后输出的结果必须是10,因为这是一个分类问题,一共有0~9这10个数字,所以是10分类。损失函数定义为分类问题中最常见的损失函数交叉熵,使用随机梯度下降来优化损失函数。
接着开始训练网络。
for epoch in range(num_epoches):
running_loss = 0.0
correct = 0
total = 0
for i, data in enumerate(train_loader, 0):
inputs, labels = data
if torch.cuda.is_available():
inputs, labels = inputs.cuda(), labels.cuda()
optimizer.zero_grad()
outputs = model(inputs.view(-1, 28 * 28))
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
epoch_loss = running_loss / len(train_loader)
epoch_acc = 100 * correct / total
print('Epoch [{}/{}], Loss: {:.4f}, Accuracy: {:.2f}%'
.format(epoch + 1, num_epoches, epoch_loss, epoch_acc))
最后训练完网络之后需要测试网络,通过下面的代码来测试。
# eval
model.eval()
eval_loss = 0
eval_acc = 0
for data in test_loader:
img, label = data
img = img.view(img.size(0), -1)
if torch.cuda.is_available():
img = Variable(img).cuda()
label = Variable(label).cuda()
else:
img = Variable(img)
label = Variable(label)
out = model(img)
loss = criterion(out, label)
eval_loss += loss.item() * label.size(0)
_, pred = torch.max(out, 1)
num_correct = (pred == label).sum()
eval_acc += num_correct.item()
print('Test Loss: {:.6f}, ACC: {:.6f}'
.format(eval_loss / (len(test_dataset)), eval_acc / (len(test_dataset))))