Pytorch-CNN¶

Step1. 导入所需的库¶

In [1]:
import torch
import torch.nn.functional as F
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch import optim
from torch import nn
from torch.utils.data import DataLoader
from tqdm import tqdm

Step2. 定义卷积神经网络¶

In [2]:
class CNN(nn.Module):
    def __init__(self, in_channels, num_classes=10):
        """
        定义神经网络的层数

        参数:
            in_channels : int
                输入图像的通道数,对于MNIST手写数据集, 这个是1
            num_classes : int
                我们做预测的分类的个数,对于MNIST手写数据集,这个是10
        """
        super(CNN, self).__init__()

        # 第一层:卷积层 1个输入通道,8个输出通道,3*3卷积核,步长为1, 填充为1
        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=8, kernel_size=3, stride=1, padding=1)

        # 第二层:最大池化层 2*2的窗口,步长为2
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)

        # 第三层:卷积层 8个输入通道,16个输出通道,3*3的卷积核,步长为1, 填充为1
        self.conv2 = nn.Conv2d(in_channels=8, out_channels=16, kernel_size=3, stride=1, padding=1)

        # 第四层: 全连接层
        self.fc1 = nn.Linear(16 * 7 * 7, num_classes)
    
    # 前向传播
    def forward(self, x):
        """ 
        参数:
            x : torch.Tensor
                输入的张量
        
        返回值:
                torch.Tensor
                经过网络传播后的输出张量
        激活函数:
                ReLU
        """
        x = F.relu(self.conv1(x))       #经过第一层卷积层后再经过激活函数
        x = self.pool(x)                #池化层
        x = F.relu(self.conv2(x))       #经过第二层卷积层后再经过激活函数
        x = self.pool(x)                # 池化层
        x = x.reshape(x.shape[0], -1)   #Flatten操作
        x = self.fc1(x)                 #全连接层
        return x

Step3. 硬件设置¶

In [3]:
device = "cuda" if torch.cuda.is_available() else "cpu"

Step4. 定义超参数¶

In [4]:
input_size = 784  # 28x28 pixels 
num_classes = 10  # 0-9
learning_rate = 0.001
batch_size = 64
num_epochs = 10 

Step5. 加载数据¶

In [5]:
train_dataset = datasets.MNIST(root="./Dataset/", download=True, train=True, transform=transforms.ToTensor())
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)

test_dataset = datasets.MNIST(root="./Dataset/", download=True, train=False, transform=transforms.ToTensor())
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 404: Not Found

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ./Dataset/MNIST\raw\train-images-idx3-ubyte.gz
100%|██████████| 9.91M/9.91M [00:29<00:00, 331kB/s] 
Extracting ./Dataset/MNIST\raw\train-images-idx3-ubyte.gz to ./Dataset/MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 404: Not Found

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to ./Dataset/MNIST\raw\train-labels-idx1-ubyte.gz
100%|██████████| 28.9k/28.9k [00:00<00:00, 91.9kB/s]
Extracting ./Dataset/MNIST\raw\train-labels-idx1-ubyte.gz to ./Dataset/MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 404: Not Found

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to ./Dataset/MNIST\raw\t10k-images-idx3-ubyte.gz
100%|██████████| 1.65M/1.65M [00:12<00:00, 137kB/s] 
Extracting ./Dataset/MNIST\raw\t10k-images-idx3-ubyte.gz to ./Dataset/MNIST\raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 404: Not Found

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to ./Dataset/MNIST\raw\t10k-labels-idx1-ubyte.gz
100%|██████████| 4.54k/4.54k [00:01<00:00, 4.35kB/s]
Extracting ./Dataset/MNIST\raw\t10k-labels-idx1-ubyte.gz to ./Dataset/MNIST\raw

Step6. 初始化网络¶

In [6]:
model = CNN(in_channels=1, num_classes=num_classes).to(device)

Step7. 定义损失和优化器¶

这里使用交叉熵损失进行分类(好像涉及到分类问题的大都用的是交叉熵损失),并用Adam优化器更新模型的权重

In [7]:
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

Step8. 训练网络¶

In [8]:
for epoch in range(num_epochs):
    print(f"Epoch [{epoch + 1} / N{num_epochs}]")
    for batch_index, (data, targets) in enumerate(tqdm(train_loader)):
        # 把数据加载到设备上
        data = data.to(device)
        targets = targets.to(device)

        # 前向传播
        scores = model(data)
        loss = criterion(scores, targets)

        # 反向传播
        optimizer.zero_grad()
        loss.backward()

        # 更新模型权重
        optimizer.step()
Epoch [1 / N10]
100%|██████████| 938/938 [00:13<00:00, 71.01it/s]
Epoch [2 / N10]
100%|██████████| 938/938 [00:10<00:00, 90.37it/s] 
Epoch [3 / N10]
100%|██████████| 938/938 [00:11<00:00, 84.95it/s] 
Epoch [4 / N10]
100%|██████████| 938/938 [00:09<00:00, 95.81it/s] 
Epoch [5 / N10]
100%|██████████| 938/938 [00:11<00:00, 82.45it/s]
Epoch [6 / N10]
100%|██████████| 938/938 [00:11<00:00, 83.59it/s] 
Epoch [7 / N10]
100%|██████████| 938/938 [00:11<00:00, 83.94it/s] 
Epoch [8 / N10]
100%|██████████| 938/938 [00:12<00:00, 77.47it/s] 
Epoch [9 / N10]
100%|██████████| 938/938 [00:12<00:00, 73.14it/s]
Epoch [10 / N10]
100%|██████████| 938/938 [00:12<00:00, 74.16it/s]

Step9. 模型评估¶

In [13]:
def check_accuracy(loader, model):
    """
    参数:
        loader : DataLoader
        model: nn.Module
    """
    if loader.dataset.train:
        print("Checking accruacy on training data")
    else:
        print("Checking accuracy on test data")
    
    num_correct = 0
    num_samples = 0
    model.eval()

    with torch.no_grad(): 
        for x, y in loader:
            x = x.to(device)
            y = y.to(device)

            # 前向传播
            scores = model(x)
            _, predictions = scores.max(1)
            num_correct += (predictions == y).sum()
            num_samples += predictions.size(0)

        # 计算准确率
        accuracy = float(num_correct) / float(num_samples) * 100
        print(f"Got {num_correct} / {num_samples} with accuracy {accuracy:.2f}%")
    
    model.train()
In [14]:
check_accuracy(train_loader, model)
Checking accruacy on training data
Got 59428 / 60000 with accuracy 99.05%
In [15]:
check_accuracy(test_loader, model)
Checking accuracy on test data
Got 9856 / 10000 with accuracy 98.56%