深度学习本科课程 实验4 卷积神经网络

二维卷积实验

1.1 任务内容

  1. 手写二维卷积的实现,并在至少一个数据集上进行实验,从训练时间、预测精 度、Loss变化等角度分析实验结果(最好使用图表展示)(只用循环几轮即可)
  2. 使用torch.nn实现二维卷积,并在至少一个数据集上进行实验,从训练时间、 预测精度、Loss变化等角度分析实验结果(最好使用图表展示)
  3. 不同超参数的对比分析(包括卷积层数、卷积核大小、batchsize、lr等)选其 中至少1-2个进行分析

1.2 任务思路及代码

python 复制代码
# 读取数据
import os
import numpy as np
import torch
import PIL
from PIL import Image
import cv2
from torch import nn
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f'当前使用的device为{device}')

data_dir = "./resizedImages"
bus_dir = data_dir + os.sep +'bus'
car_dir = data_dir + os.sep +'car'
truck_dir = data_dir + os.sep +'truck'

if not os.path.exists(data_dir):
    os.mkdir(data_dir)
if not os.path.exists(car_dir):
    os.mkdir(car_dir)
if not os.path.exists(bus_dir):
    os.mkdir(bus_dir)
if not os.path.exists(truck_dir):
    os.mkdir(truck_dir)

width = 200
height = width
path = "./车辆分类数据集/bus"
busData =  os.listdir(path)
for img_item in busData:
    if img_item != "desktop.ini":
        img = Image.open(path + os.sep + img_item)
        img = img.resize((width, height), Image.LANCZOS)
        img.save(bus_dir + os.sep + img_item)

path = "./车辆分类数据集/car"
carData =  os.listdir(path)
for img_item in carData:
    if img_item == "desktop.ini":
        continue
    img = Image.open(path + os.sep + img_item)
    img = img.resize((width, height), Image.LANCZOS)
    img.save(car_dir + os.sep + img_item)
    
path = "./车辆分类数据集/truck"
truckData =  os.listdir(path)
for img_item in truckData:
    if img_item == "desktop.ini":
        continue
    img = Image.open(path + os.sep + img_item)
    img = img.resize((width, height), Image.LANCZOS)
    img.save(truck_dir + os.sep + img_item)
# 已缩放处理过,存储在根目录下resizedImages文件夹中各分类下
python 复制代码
# 超参数设定
epochs = 10
lr = 0.001
batch_size = 32
python 复制代码
# 划分数据集
import random
import shutil
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder

train_dir = './Classfication-train_data'
test_dir = './Classfication-test_data'

if not os.path.exists(train_dir) or not os.path.exists(test_dir):
    os.mkdir(train_dir)
    os.mkdir(test_dir)
vehicles = os.listdir(data_dir)

bus_dir = train_dir + os.sep +'bus'
car_dir = train_dir + os.sep +'car'
truck_dir = train_dir + os.sep +'truck'
if not os.path.exists(car_dir):
    os.mkdir(car_dir)
if not os.path.exists(bus_dir):
    os.mkdir(bus_dir)
if not os.path.exists(truck_dir):
    os.mkdir(truck_dir)

bus_dir = test_dir + os.sep +'bus'
car_dir = test_dir + os.sep +'car'
truck_dir = test_dir + os.sep +'truck'
if not os.path.exists(car_dir):
    os.mkdir(car_dir)
if not os.path.exists(bus_dir):
    os.mkdir(bus_dir)
if not os.path.exists(truck_dir):
    os.mkdir(truck_dir)
# 项目结构:
# ./(根目录)
#     resizedImages/
#         bus/
#         car/
#         truck/
#     Classfication-train_data/
#         bus/
#         car/
#         truck/
#     Classfication-test_data/
#         bus/
#         car/
#         truck/

split_rate = 0.8
# 训练集:测试集=8:2
# 开始拷贝

for folder in vehicles: # car, bus, truck
    print(folder)
    file_names = np.array(os.listdir(os.path.join(data_dir,folder)))
    train_number = int(len(file_names) * split_rate)
    total_number = list(range(len(file_names)))
    print(f"训练集数量: {train_number}, 测试集数量{len(file_names) - train_number}")
    if len(os.listdir(train_dir + os.sep + folder)) != 0:
           continue
    random.shuffle(total_number)  # 打乱下标
    # 获得打乱下标后的训练集和测试集的数据
    train_files = file_names[total_number[0: train_number]]
    test_files = file_names[total_number[train_number:]]
    
    for file in train_files:
        if file == "desktop.ini":
            continue
        path_train = os.path.join(data_dir, folder) + os.sep + file
        path_train_copy = train_dir + os.sep + folder + os.sep + file
        shutil.copy(path_train, path_train_copy) # 将文件复制到训练集文件夹
        
    for file in test_files:
        if file == "desktop.ini":
            continue
        path_test = os.path.join(data_dir, folder) + os.sep + file
        path_test_copy = test_dir + os.sep + folder + os.sep + file
        shutil.copy(path_test, path_test_copy)  # 讲文件复制到测试集文件夹


transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))])
    # 归一化
# 相关参数
train_data = ImageFolder(root=train_dir, transform=transform)
train_loader = DataLoader(dataset=train_data, shuffle=True, batch_size=batch_size)
test_data = ImageFolder(root=test_dir, transform=transform)
test_loader = DataLoader(dataset=test_data, shuffle=True, batch_size=batch_size)

print()
print(f"训练集数量 :{len(train_data)}")
print(f"测试集数量 :{len(test_data)}")
python 复制代码
from torch import nn
# 定义卷积
def corr2d(X, K, kernel_size):
    '''
    X, shape(batch_size,H,W)
    kernel_size, shape(k_h, k_w)
    '''
    batch_size, H,W = X.shape
    k_h, k_w = kernel_size
    Y = torch.zeros((batch_size, H-k_h+1, W-k_w+1)).to(device)
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[:, i, j] = (X[:,i: i+k_h, j:j+k_w]*K).sum(dim=2).sum(dim=1)
    return Y

# 多通道输入
# 卷积核通道数 = 输入通道数
def corr2d_multi_in(X, K, kernel_size):
    res = corr2d(X[:, 0, :, :], K[0, :, :],kernel_size)
    for i in range(1, X.shape[1]):
        res += corr2d(X[:,i,:,:], K[i,:,:], kernel_size)
    return res
    
# 多通道输出
# 输出通道数 = 卷积核个数
def corr2d_multi_in_out(X, K, kernel_size):
    return torch.stack([corr2d_multi_in(X, k, kernel_size) for k in K])

# 将卷积运算封装成卷积层
class myConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size):
        super(myConv2d, self).__init__()
        if isinstance(kernel_size, int):  
            self.kernel_size = (kernel_size, kernel_size)
            self.weight = nn.Parameter(torch.randn((out_channels, in_channels) + self.kernel_size)).to(device)
            self.bias = nn.Parameter(torch.randn(out_channels, 1,1))
    def forward(self, x):
        return corr2d_multi_in_out(x, self.weight, self.kernel_size) + self.bias
python 复制代码
# 手动实现二维 CNN
import torch.nn.functional as F

class myCNN(torch.nn.Module):
    def __init__(self, num_classes):
        super(myCNN, self).__init__()
        self.conv = nn.Sequential(
            myConv2d(in_channels=3, out_channels=32, kernel_size=3),
            torch.nn.BatchNorm2d(32),
            torch.nn.ReLU(inplace=True) 
        )
        self.fc = torch.nn.Linear(32, num_classes)
    def forward(self, X):
        # 根据实验要求,图片经过一层卷积,输出(batch_size, C_out, H, W)
        out = self.conv(X)
        # 使用平均池化层将图片大小变为1*1
        out = F.avg_pool2d(out, 198) # 图片大小为200*200,卷积后为198
        out = out.squeeze()
        # 输入到全连接层
        out = self.fc(out)
        return out
python 复制代码
# nn实现二维卷积
class nnCNN(nn.Module):
    def __init__(self, num_classes):
        super(nnCNN,self).__init__()
        # 三层卷积
        self.conv = nn.Sequential(
            # 这里设置了填充,因为图片大小为 200*200,运算过程出现了小数,这里是考虑不周到的地方
            nn.Conv2d(in_channels=3,out_channels=32,kernel_size=3,stride=1,padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=32,out_channels=64,kernel_size=3,stride=1,padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,stride=1,padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True)
        )
        self.fc = nn.Linear(128,num_classes)
    def forward(self,X):
        out= self.conv(X)
        out = F.avg_pool2d(out, kernel_size=out.size()[2:])
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out
python 复制代码
# 定义训练函数
import time
from torch.nn import CrossEntropyLoss
from torch.optim import SGD
criterion = CrossEntropyLoss()
criterion = criterion.to(device)
# 测试加训练
def train_with_test(net, train_loader, test_loader,batch_size=32, epochs=10, lr=0.001, optimizer=None, disp=True, skip=False):
    if disp:
        print(net)
    if optimizer == None:
        optimizer = SGD(net.parameters(), lr=lr) 
    train_batch_num = len(train_loader)
    train_loss_list = []
    train_acc= []
    train_loss_in_batch, train_acc_in_batch = [], []
    test_loss_list =[]
    test_acc=[]
    begin_time = time.time()
    for epoch in range(epochs):
        train_loss, acc = 0, 0
        train_sample_num = 0
        # acc_num = 0
        jmp = 25
        for batch_idx, (data, target) in enumerate(train_loader):
            # batch_size = 32
            if len(data) != batch_size:
                continue
            # if batch_idx == 2:
            #     break
            if skip and batch_idx < jmp:
                continue
            if skip and batch_idx % 2 == 0:
                continue # 加快训练,卷积训练太慢,主要看手动卷积的效果
            tb1 = time.time()
            data, target = data.to(device), target.to(device)
            prediction = net(data)
            loss = criterion(prediction, target)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 循环内损失率
            train_loss_in_batch.append(loss.to('cpu')/len(prediction))
            # 循环损失率
            train_loss += loss.item()
            
            # 循环内正确率
            train_batch_acc_num = (prediction.argmax(dim=1)==target).sum()
            train_acc_in_batch.append(train_batch_acc_num.to('cpu')/len(prediction))
            # 循环正确率
            acc += train_batch_acc_num
            
            train_sample_num += len(prediction)

            tb2=time.time()
            if skip or batch_idx % 4 == 0:
                print(f"\t|Train-in-batch:{batch_idx+1}/{len(train_loader)}, loss:{train_loss/train_sample_num}, acc:{acc/train_sample_num}, 耗时:{tb2-tb1}s")
            
        train_acc.append(acc.to('cpu')/train_sample_num)
        train_loss_list.append(train_loss/train_sample_num)
        # 测试
        test_batch_num = len(test_loader)
        total_loss = 0
        sample_num2=0
        acc2 = 0
        with torch.no_grad():
            for batch_idx, (data, target) in enumerate (test_loader):
                if len(data) != batch_size:
                    continue
                # if batch_idx == 2:
                #     break

                data, target = data.to(device), target.to(device)
                prediction = net(data)
                loss = criterion(prediction, target)
                # 循环总损失
                total_loss += loss.item()
                sample_num2 += len(prediction)
                # 循环总正确
                acc_num2 = (prediction.argmax(dim=1)==target).sum()
                acc2 += acc_num2

            test_loss_list.append(total_loss/sample_num2)
            test_acc.append(acc2.to('cpu')/sample_num2)
        print('***epoch: %d***train loss: %.5f***train acc:%5f***test loss:%.5f***test acc:%5f' % 
              (epoch + 1, train_loss_list[epoch], train_acc[epoch],test_loss_list[epoch],test_acc[epoch]))
        print()
        
    end_time = time.time()
    print('%d轮总用时: %.2fs'%(epochs, end_time-begin_time))
    # 返回全部损失,准确率
    return train_loss_in_batch, train_acc_in_batch, train_loss_list, train_acc, test_loss_list, test_acc

# 测试函数(单独)
def test_epoch(net, data_loader, disp=False):
    # net.eval()
    if disp:
        print(net)
    test_batch_num = len(data_loader)
    total_loss = 0
    acc = 0
    sample_num = 0
    with torch.no_grad():
        for batch_idx, (data, target) in enumerate (data_loader):
            if len(data) != 32:
                continue
            # if batch_idx == 2:
            #     break
            tb1 = time.time()
            data, target = data.to(device), target.to(device)
            prediction = net(data)
            loss = criterion(prediction, target)
            total_loss += loss.item()
            sample_num += len(prediction)
            acc_num = (prediction.argmax(dim=1)==target).sum()
            acc += acc_num
            tb2 = time.time()
            print(f"\t|Test-in-batch:{batch_idx+1}/{len(data_loader)}, loss:{total_loss/sample_num}, acc:{acc/sample_num}, 耗时:{tb2-tb1}s")
    loss = total_loss/test_batch_num
    test_acc = acc/sample_num
    return loss, test_acc
python 复制代码
# 小批量数据,减少GPU压力
train_loader16 = DataLoader(dataset=train_data, shuffle=True, batch_size=16)
test_loader16 = DataLoader(dataset=test_data, shuffle=True, batch_size=16)
python 复制代码
# 定义画图函数
import matplotlib.pyplot as plt
def plot_batching(train_loss, train_acc):
    plt.figure(1)
    plt.xlabel('batch')
    plt.ylabel('loss')
    plt.title('Loss-Rate')
    # plt.plot([i for i in range(len(train_loss))], train_loss[i], 'b-', label=u'train_loss')
    mylist = []
    for i in range(len(train_loss)):
        mylist.append(train_loss[i].detach().numpy())
    plt.plot([i for i in range(len(train_loss))], mylist, 'b-', label=u'train_loss')
    plt.legend() 
    
    plt.figure(2)
    plt.xlabel("batch")  
    plt.ylabel("Acc")  
    plt.title("Accuracy")  
    plt.plot([i for i in range(len(train_acc))], train_acc, 'r-', label=u'train_acc')
    plt.legend() 

def plot_learning(train_loss, train_acc, test_loss, test_acc):
    plt.figure(1)
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Loss-Rate')
    plt.plot([i for i in range(len(train_loss))], train_loss, 'b-', label=u'train_loss')
    plt.legend() 
    plt.plot([i for i in range(len(test_loss))], test_loss, 'r-', label=u'test_loss')
    plt.legend() 
    
    plt.figure(2)
    plt.xlabel("epoch")  
    plt.ylabel("Acc")  
    plt.title("Accuracy")  
    plt.plot([i for i in range(len(train_acc))], train_acc, 'b-', label=u'train_acc')
    plt.legend() 
    plt.plot([i for i in range(len(test_acc))], test_acc, 'r-', label=u'test_acc')
    plt.legend()  

    plt.show()
python 复制代码
# 手动实现
torch.cuda.empty_cache()
net11 = myCNN(3).to(device)
train_l_111, train_acc_111, train_l_112, train_acc_112, test_l11, test_acc11 = train_with_test(
    net11, train_loader, test_loader, batch_size=32, epochs=1, lr=0.01, optimizer=None, disp=True, skip=True)

手动实现卷积的结果

python 复制代码
# 图表展示以batch为单位的训练结果
plot_batching(train_l_111, train_acc_111)
python 复制代码
# nn实现卷积
torch.cuda.empty_cache()
net12 = nnCNN(3).to(device)
train_l_121, train_acc_121, train_l_122, train_acc_122, test_l12, test_acc12 = train_with_test(
    net12, train_loader, test_loader, epochs=10, lr=0.001)

nn实现卷积的结果

python 复制代码
plot_learning(train_l_122, train_acc_122, test_l12, test_acc12)

探究超参数对CNN的影响

python 复制代码
# 修改卷积层数
# 只用一层卷积
class nnCNNof1(nn.Module):
    def __init__(self, num_classes):
        super(nnCNNof1,self).__init__()
        # 三层卷积
        self.conv = nn.Sequential(
            # 这里设置了填充,因为图片大小为 200*200,运算过程出现了小数,这里是考虑不周到的地方
            nn.Conv2d(in_channels=3,out_channels=32,kernel_size=3,stride=1,padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
        )
        self.fc = nn.Linear(32,num_classes)
    def forward(self,X):
        out= self.conv(X)
        out = F.avg_pool2d(out, kernel_size=out.size()[2:])
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out
        
torch.cuda.empty_cache()
net13 = nnCNNof1(3).to(device)
train_l_131, train_acc_131, train_l_132, train_acc_132, test_l13, test_acc13 = train_with_test(
    net13, train_loader, test_loader, epochs=10, lr=lr)
python 复制代码
# 作图对比
plot_learning(train_l_122, train_acc_122, test_l12, test_acc12)
plot_learning(train_l_132, train_acc_132, test_l13, test_acc13)
python 复制代码
# 修改学习率
lr12 = 0.01
# lr = 0.001
torch.cuda.empty_cache()
net14 = nnCNN(3).to(device)
train_l_141, train_acc_141, train_l_142, train_acc_142, test_l14, test_acc14 = train_with_test(
    net14, train_loader, test_loader, epochs=10, lr=lr12)
python 复制代码
# 作图对比
plot_learning(train_l_122, train_acc_122, test_l12, test_acc12)
plot_learning(train_l_142, train_acc_142, test_l14, test_acc14)

小结

  1. 实验一中我将一层卷积与三层卷积进行了对比,前者loss下降的很慢,且预测正确率明显低于三层卷积,说明增加模型层数可以有助于获取更多、更高级别的特征表示 ,从而提高模型的预测能力;深度不够的模型,参数更新很慢 ,性能也不尽人意;模型深度提升三倍,训练时间也提升到接近三倍,似乎说明在本问题中模型深度与训练时间成正比
  2. 我将三层卷积模型的学习率由0.001提升至0.01,损失率得到更大幅度的下降 且仍呈圆弧状变化,说明该学习率是可被接纳的优秀学习率,初始0.001的学习率有些保守了;但从正确率来看,lr=0.01的曲线趋于平缓,似乎很难再单从学习率调整以达到更好的模型表现

二、空洞卷积实验

2.1 任务内容

  1. 用torch.nn实现空洞卷积,要求dilation满足HDC条件(如1,2,5)且要 堆叠多层并在至少一个数据集上进行实验,从训练时间、预测精度、Loss
    变化等角度分析实验结果(最好使用图表展示)
  2. 将空洞卷积模型的实验结果与卷积模型的结果进行分析比对,训练时间、 预测精度、Loss变化等角度分析
  3. 不同超参数的对比分析(包括卷积层数、卷积核大小、不同dilation的选择, batchsize、lr等)选其中至少1-2个进行分析(选做)

2.2 任务思路及代码

python 复制代码
# nn实现空洞卷积  
class nnDCNN(nn.Module):  
    def __init__(self,num_classes):  
        super(nnDCNN,self).__init__()  
        # 三层卷积
        self.conv=nn.Sequential(  
                # 添加padding,否则出现小数,下次一定要把图片缩放成2的幂
	            nn.Conv2d(in_channels=3,out_channels=32,kernel_size=3,stride=1,padding=1,dilation=1),  
	            nn.BatchNorm2d(32),  
	            nn.ReLU(inplace=True),  
	            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=2,dilation=2),  
	            nn.BatchNorm2d(64),  
	            nn.ReLU(inplace=True),  
	            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=4,dilation=5),  
	            nn.BatchNorm2d(128),  
	            nn.ReLU(inplace=True),  
	        )
        #输出层,将通道数变为分类数量  
        self.fc = nn.Linear(128,num_classes)  
    def forward(self,x):  
        out = self.conv(x)  
        out = F.avg_pool2d(out, kernel_size=out.size()[2:])  
        out = out.squeeze()  
        out = self.fc(out)  
        return out 
python 复制代码
torch.cuda.empty_cache()
net21 = nnDCNN(num_classes=3).to(device)
train_l_211, train_acc_211, train_l_212, train_acc_212, test_l21, test_acc21 = train_with_test(
    net21, train_loader, test_loader, epochs=10, lr=0.001)

与CNN进行比对

python 复制代码
plot_learning(train_l_212, train_acc_212, test_l21, test_acc21)
plot_learning(train_l_122, train_acc_122, test_l12, test_acc12)

探究超参数

python 复制代码
# 修改层数
class nnDCNNof6(nn.Module):  
    def __init__(self, num_classes):  
        super(nnDCNNof6, self).__init__()  
        # 六层卷积
        self.conv = nn.Sequential(  
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1, dilation=1),  
            nn.BatchNorm2d(32),  
            nn.ReLU(inplace=True),  
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=2, dilation=2),  
            nn.BatchNorm2d(64),  
            nn.ReLU(inplace=True),  
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=4, dilation=5),  
            nn.BatchNorm2d(128),  
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=8, dilation=10),  
            nn.BatchNorm2d(256),  
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=16, dilation=15),  
            nn.BatchNorm2d(512),  
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=1, padding=32, dilation=20),  
            nn.BatchNorm2d(1024),  
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=1024, out_channels=2048, kernel_size=3, stride=1, padding=64, dilation=25),  
            nn.BatchNorm2d(2048),  
            nn.ReLU(inplace=True),
        )
        # 输出层,将通道数变为分类数量  
        self.fc = nn.Linear(2048, num_classes)  
        
    def forward(self, x):  
        out = self.conv(x)  
        out = F.avg_pool2d(out, kernel_size=out.size()[2:])  
        out = out.squeeze()  
        out = self.fc(out)  
        return out
        
torch.cuda.empty_cache()
net22 = nnDCNNof6(num_classes=3).to(device)
train_l_221, train_acc_221, train_l_222, train_acc_222, test_l22, test_acc22 = train_with_test(
    net22, train_loader, test_loader, epochs=10, lr=0.001)
python 复制代码
# 作图比对
plot_learning(train_l_212, train_acc_212, test_l21, test_acc21)
plot_learning(train_l_222, train_acc_222, test_l22, test_acc22)
python 复制代码
# 修改batch_size
torch.cuda.empty_cache()
net23 = nnDCNN(num_classes=3).to(device)
train_l_231, train_acc_231, train_l_232, train_acc_232, test_l23, test_acc23 = train_with_test(
    net23, train_loader16, test_loader16,batch_size=16, epochs=10, lr=0.001)
python 复制代码
# 作图比对
plot_learning(train_l_212, train_acc_212, test_l21, test_acc21)
plot_learning(train_l_232, train_acc_232, test_l23, test_acc23)

小结

  1. 空洞卷积和普通卷积的基本操作相同,都是通过卷积核与输入进行卷积操作。但它具有的扩展感受野、共享参数等性质,使得网络能更好地捕捉样本中的信息 ,因此获得了更好的正确率;空洞卷积的稀疏性也使得它比普通卷积的训练更快
  2. 在超参数实验中,我大胆尝试了将空洞卷积的模型深度翻倍:这造成了严重后果,我的电脑的GPU(显存16GB)根本无法负担这样的训练任务,说明模型深度的增加不仅会加大时间开销,更会造成GPU空间的更大占用,并增大算力负担;模型深度每增大k倍,造成的时间空间开销会增大p倍,p大于等于k。
  3. 结果上来看,小批量的训练(74.87s)要慢于 大批量的训练(66.99s),但小批量的性能略微更优,因为它可以更频繁地更新模型参数,提供更多的随机性,对模型的泛化性能更有益。

三、残差网络实验

3.1 任务内容

实现给定结构的残差网络,在 至少一个数据集上进行实验, 从训练时间、预测精度、Loss 变化等角度分析实验结果(最

好使用图表展示)

3.2 任务思路及代码

python 复制代码
class ResBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=[1, 1], padding=1):
        super(ResBlock, self).__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[0], padding=padding, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride[1], padding=padding, bias=False),
            nn.BatchNorm2d(out_channels)
        )

        self.shortcut = nn.Sequential()
        if stride[0] != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        out = self.layer(x)
        shortcut = self.shortcut(x)
        if shortcut.size(1) != out.size(1):
            shortcut = F.pad(shortcut, (0, 0, 0, 0, 0, out.size(1) - shortcut.size(1)))
        out += shortcut
        out = F.relu(out)
        return out
python 复制代码
class nnResNet(nn.Module):
    def __init__(self, num_classes=3) -> None:
        super(nnResNet, self).__init__()
        self.in_channels = 64

        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(64)
        )

        self.conv2 = self.set_layer(64, [[1, 1], [1, 1]])
        self.conv3 = self.set_layer(128, [[2, 1], [1, 1]])  
        self.conv4 = self.set_layer(256, [[2, 1], [1, 1]])  
        self.conv5 = self.set_layer(512, [[2, 1], [1, 1]])  
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Sequential(
            nn.Linear(512, 256),
            nn.Linear(256, 128),
            nn.Linear(128, num_classes)
        )

    def set_layer(self, out_channels, strides):
        layers = []
        for stride in strides:
            layers.append(BasicBlock(self.in_channels, out_channels, stride))
            self.in_channels = out_channels
        return nn.Sequential(*layers)

    # 图片的大小为 200*200
    def forward(self, x):
        out = self.conv1(x)  # [32, 64, 200, 200]
        out = self.conv2(out)  # [32, 64, 200, 200]
        out = self.conv3(out)  # [32, 128, 100, 100]
        out = self.conv4(out)  # [32, 256, 50, 50]
        out = self.conv5(out)  # [32, 512, 25, 25]
        out = F.avg_pool2d(out, 25)  # [32, 512, 1, 1]
        out = out.squeeze()  # [32, 512]
        out = self.fc(out)
        return out
python 复制代码
torch.cuda.empty_cache()
net31=nnResNet(num_classes=3).to(device)
train_l_311, train_acc_311, train_l_312, train_acc_312, test_l31, test_acc31 = train_with_test(
    net31, train_loader16, test_loader16, batch_size=16, epochs=10, lr=0.001, disp=True, skip=True)
python 复制代码
# 绘制训练过程
plot_learning(train_l_312, train_acc_312, test_l31, test_acc31)

小结

  1. 从训练结果来看,首先,由loss曲线,知本次训练的学习率过低,应使它接近于一个凹函数。
  2. 其次,从正确率来看,残差模型达到了本次实验的最高正确度(>0.8)。
  3. 从训练时间来看,残差网络的效率也很高,并没有比卷积和空洞卷积慢出很多;而残差网络的深度远远高于卷积网络,说明它具有更强的学习能力,能够学习更复杂的特征。
  4. test_acc与train_acc平齐甚至更高,说明模型没有过拟合,残差网络具有更优异的优化和收敛性质。

实验总结

首先,本次实验学习了卷积、空洞卷积和残差网络三种模型,掌握了手动实现它们的训练过程,了解了它们的性质和优势。

其次,实验中有很多模型训练的地方值得优化,例如处理数据集图像时索性取最大值,将每张图片都缩放至200*200,这一点造成了维数降低时出现了小数,通过引入padding解决了这一问题。更加妥善的做法是采用2的幂,而不是随便一个数字。

最后,使用超参数的能力有所提升,包括对batch、学习率和模型深度的理解,增加了对机器学习实践的经验。

相关推荐
静静AI学堂27 分钟前
Yolo11改策略:卷积改进|SAC,提升模型对小目标和遮挡目标的检测性能|即插即用
人工智能·深度学习·目标跟踪
martian6651 小时前
【人工智能离散数学基础】——深入详解数理逻辑:理解基础逻辑概念,支持推理和决策系统
人工智能·数理逻辑·推理·决策系统
Schwertlilien1 小时前
图像处理-Ch7-图像金字塔和其他变换
图像处理·人工智能
凡人的AI工具箱1 小时前
每天40分玩转Django:Django类视图
数据库·人工智能·后端·python·django·sqlite
千天夜1 小时前
深度学习中的残差网络、加权残差连接(WRC)与跨阶段部分连接(CSP)详解
网络·人工智能·深度学习·神经网络·yolo·机器学习
一勺汤1 小时前
YOLOv8模型改进 第二十五讲 添加基于卷积调制(Convolution based Attention) 替换自注意力机制
深度学习·yolo·计算机视觉·模块·yolov8·yolov8改进·魔改
凡人的AI工具箱1 小时前
每天40分玩转Django:实操图片分享社区
数据库·人工智能·后端·python·django
小军军军军军军1 小时前
MLU运行Stable Diffusion WebUI Forge【flux】
人工智能·python·语言模型·stable diffusion
诚威_lol_中大努力中2 小时前
关于VQ-GAN利用滑动窗口生成 高清图像
人工智能·神经网络·生成对抗网络
中关村科金2 小时前
中关村科金智能客服机器人如何解决客户个性化需求与标准化服务之间的矛盾?
人工智能·机器人·在线客服·智能客服机器人·中关村科金