PyTorch+AlexNet代码实训

参考文章:https://blog.youkuaiyun.com/red_stone1/article/details/122974771

数据集:
在这里插入图片描述

打标签:

import os
 
# os.path.join: 每个参数都是一个路径段,将它们连接起来形成有效的路径名。
train_txt_path = os.path.join("data", "catVSdog", "train.txt")
train_dir = os.path.join("data", "catVSdog", "train_data")
valid_txt_path = os.path.join("data", "catVSdog", "test.txt")
valid_dir = os.path.join("data", "catVSdog", "test_data")
 
def gen_txt(txt_path, img_dir): # 标签,图像
    f = open(txt_path, 'w')  # 打开一个文件,创建一个file对象

    # os.walk: 遍历一个目录树,返回目录中的每个目录和文件
    # os.walk每次迭代都会返回一个元组:
    #(当前目录的路径字符串,当前目录中所有子目录名称,当前目录所有文件名称)
    for root, s_dirs, _ in os.walk(img_dir, topdown=True):  # 获取 train文件下各文件夹名称
        # topdown用于决定遍历目录树的顺序
        # 以猫狗大战数据集为例,这里的s_dirs是cat和dog文件夹
        for sub_dir in s_dirs: # 对于猫或狗文件夹里的每个文件(每张图片)遍历
            i_dir = os.path.join(root, sub_dir)             # 获取各类的文件夹 绝对路径 ?
            img_list = os.listdir(i_dir)                    # 获取类别文件夹下所有png图片的路径 ? 应该是jpg
            # os.listdir: 用于返回指定目录中的所有文件和目录的名称列表
            for i in range(len(img_list)): # 遍历一个类别中的所有图片
                if not img_list[i].endswith('jpg'):         # 若不是png文件,跳过 ? 应该是jpg
                    continue
                #label = (img_list[i].split('.')[0] == 'cat')? 0 : 1 
                label = img_list[i].split('.')[0] # 按.分割,并取点后的**第一个部分**
                # 将字符类别转为整型类型表示
                if label == 'cat':
                    label = '0'
                else: # label == 'dog'
                    label = '1'
                img_path = os.path.join(i_dir, img_list[i])
                line = img_path + ' ' + label + '\n'
                f.write(line) # 把打好的标签写在.txt里
    f.close()
 
if __name__ == '__main__':
    # 共生成两个图片索引文件:train.txt和test.txt
    gen_txt(train_txt_path, train_dir)
    gen_txt(valid_txt_path, valid_dir)

构建数据集:

from PIL import Image
from torch.utils.data import Dataset
 
class MyDataset(Dataset):
    def __init__(self, txt_path, transform = None, target_transform = None):
        fh = open(txt_path, 'r') # 打开图片索引文件
        imgs = [] # 存储元组:(图片路径,类别(0或1))
        for line in fh: # 迭代读取文件的行
            line = line.rstrip() # 使用rstrip方法去除行末的空白符(包括\n)
            words = line.split() # 将字符串按空白符(空格、制表符等)进行分割
            imgs.append((words[0], int(words[1]))) # 类别转为整型int
            self.imgs = imgs 
            # self.transform和self.target_transform:根据读入的参数赋值
            self.transform = transform
            self.target_transform = target_transform
    # __getitem__方法和__len__方法均继承自父类Dataset
    def __getitem__(self, index):
        fn, label = self.imgs[index]
        img = Image.open(fn).convert('RGB') 
        #img = Image.open(fn)
        if self.transform is not None:
            img = self.transform(img) # self.transform对图片进行处理,推测传入的是一个函数名
        return img, label
    def __len__(self):
        return len(self.imgs)

加载数据集&数据预处理:

from torchvision import transforms
# transforms.Compose接受一个列表或元组作为参数,列表中的每个元素都是一个数据转换操作
# transforms.Compose返回一个串行操作序列
pipline_train = transforms.Compose([
    #随机旋转图片
    transforms.RandomHorizontalFlip(),
    #将图片尺寸resize到227x227(这是AlexNet的要求)
    transforms.Resize((227,227)),
    #将图片转化为Tensor格式
    transforms.ToTensor(),
    #正则化(当模型出现过拟合的情况时,用来降低模型的复杂度,加快模型收敛速度)
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 均值为0.5,标准差为0.5
    #transforms.Normalize(mean = [0.485, 0.456, 0.406],std = [0.229, 0.224, 0.225])
])
pipline_test = transforms.Compose([
    #将图片尺寸resize到227x227
    transforms.Resize((227,227)),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    #transforms.Normalize(mean = [0.485, 0.456, 0.406],std = [0.229, 0.224, 0.225])
])
train_data = MyDataset('./data/catVSdog/train.txt', transform=pipline_train)
test_data = MyDataset('./data/catVSdog/test.txt', transform=pipline_test)
 
# train_data 和test_data包含多有的训练与测试数据,调用DataLoader批量加载
# batch_size: 每个小批量(batch)包含的样本数量。
# 在训练过程中,模型不会一次性处理整个数据集,而是分成多个小批量逐一输入模型进行训练
# shuffle: 数据洗牌-随机打乱数据集的顺序-使模型在训练时不会对数据顺序敏感
trainloader = torch.utils.data.DataLoader(dataset=train_data, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(dataset=test_data, batch_size=32, shuffle=False)
# 类别信息也是需要我们给定的
classes = ('cat', 'dog') # 对应label=0,label=1

查看最终制作的数据集(图片&标签):

import numpy as np
examples = enumerate(trainloader) # 方便迭代trainloader中的每个批量数据并同时获取它们的索引
batch_idx, (example_data, example_label) = next(examples) # next: 获取枚举对象的下一个元素
# 批量展示图片
for i in range(4):
    plt.subplot(1, 4, i + 1) #
    plt.tight_layout()  #自动调整子图参数,使之填充整个图像区域
    img = example_data[i]
    img = img.numpy() # FloatTensor转为ndarray
    img = np.transpose(img, (1,2,0)) # 把channel那一维放到最后
    img = img * [0.5, 0.5, 0.5] + [0.5, 0.5, 0.5]
    plt.imshow(img)
    plt.title("label:{}".format(example_label[i]))
    plt.xticks([])
    plt.yticks([])
plt.show()

搭建AlexNet神经网络结构:

class AlexNet(nn.Module):
    """
    Neural network model consisting of layers propsed by AlexNet paper.
    """
    def __init__(self, num_classes=2):
        """
        Define and allocate layers for this neural net.
        Args:
            num_classes (int): number of classes to predict with this model
        """
        super().__init__() # 继承父类的__init__方法
        # input size should be : (b x 3 x 227 x 227)
        # The image in the original paper states that width and height are 224 pixels, but
        # the dimensions after first convolution layer do not lead to 55 x 55.
        
        # nn.Sequential是一个用于构建神经网络的容器,它按顺序将各个模块(层)组合在一起,形成一个神经网络模型
        self.net = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4),  # (b x 96 x 55 x 55) 
            # nn.Conv2d还可以继续添加参数:padding 表示边缘填充空白像素的宽度
            nn.ReLU(),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),  # 局部响应归一化
            # 目前更多采用的是Batch Normalization
            nn.MaxPool2d(kernel_size=3, stride=2),  # (b x 96 x 27 x 27)
            nn.Conv2d(96, 256, 5, padding=2),  # (b x 256 x 27 x 27)
            nn.ReLU(),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),  # (b x 256 x 13 x 13)
            nn.Conv2d(256, 384, 3, padding=1),  # (b x 384 x 13 x 13)
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, padding=1),  # (b x 384 x 13 x 13)
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, padding=1),  # (b x 256 x 13 x 13)
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2),  # (b x 256 x 6 x 6)
        )
        # classifier is just a name for linear layers
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5, inplace=True), # 
            nn.Linear(in_features=(256 * 6 * 6), out_features=500),
            nn.ReLU(),
            nn.Dropout(p=0.5, inplace=True),
            nn.Linear(in_features=500, out_features=20),
            nn.ReLU(),
            nn.Linear(in_features=20, out_features=num_classes),
        )
 
    def forward(self, x):
        """
        Pass the input through the net.
        Args:
            x (Tensor): input tensor
        Returns:
            output (Tensor): output tensor
        """
        x = self.net(x)
        x = x.view(-1, 256 * 6 * 6)  # reduce the dimensions for linear layer input
        # x.view: 改变张量形状。
        # -1表示自动计算,后面的256*6*6表示将第二个维度变成这个尺寸,以便作为全连接层的输入。
        return self.classifier(x)

将模型部署到GPU/CPU:

#创建模型,部署gpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = AlexNet().to(device) # 这里的AlexNet是类名,通过.to(device)方法将模型移动到指定的设备
#定义优化器
optimizer = optim.Adam(model.parameters(), lr=0.001) # PyTorch提供的Adam优化器
# model.parameters()返回模型中所有需要训练的参数迭代器
# lr是Adam优化器的学习率,控制每次参数更新的步长大小

定义训练过程:

def train_runner(model, device, trainloader, optimizer, epoch):
    #训练模型, 启用 BatchNormalization 和 Dropout, 将BatchNormalization和Dropout置为True
    model.train()
    total = 0
    correct =0.0
 
    #enumerate迭代已加载的数据集,同时获取数据和数据下标
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        #把模型部署到device上
        inputs, labels = inputs.to(device), labels.to(device)
        #初始化梯度
        optimizer.zero_grad()
        #保存训练结果
        outputs = model(inputs)
        #计算损失和
        #多分类情况通常使用cross_entropy(交叉熵损失函数), 而对于二分类问题, 通常使用sigmod
        loss = F.cross_entropy(outputs, labels)
        #获取最大概率的预测结果
        #dim=1表示返回每一行的最大值对应的列下标
        predict = outputs.argmax(dim=1)
        total += labels.size(0)
        correct += (predict == labels).sum().item()
        #反向传播
        loss.backward()
        #更新参数
        optimizer.step()
        if i % 100 == 0:
            #loss.item()表示当前loss的数值
            print("Train Epoch{} \t Loss: {:.6f}, accuracy: {:.6f}%".format(epoch, loss.item(), 100*(correct/total)))
            Loss.append(loss.item())
            Accuracy.append(correct/total)
    return loss.item(), correct/total
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值