猫狗大战学习笔记:

1.首先简单说说代码的整体思路:

1.首先设置相关参数(输入数据的大小,每批训练数量的多少),模型的保存路径等。
2.选择是使用CPU还是GPU
3.对Cat、Dog文件进行分类存放,并分出测试集
4.对图像进行预处理
5.再对全连接层的框架、参数经行构建,并计算交叉熵,构建优化器
6.构建train()、val()函数,并在主函数中调用,对模型进行保存。

2.代码及其相应理解(完整版代码在文末)

首先是定义相应的输入格式、每次训练所抓取的样本数量,模型的保存路径,以及调用GPU还是CPU。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#这段代码一般放在读取数据之前,选择使用GPU还是CPU

input_size = 224
batch_size = 128
save_path = './weights.pt'
#用于保存一个序列化(模型)到磁盘,方便以后调用

其次,将已经下载好后的数据文件保存在train中,并分其中百分之10成val集。在train和val的文件内再将对应的猫狗图片文件分类包装,分成dog、cat文件夹保存。其中文件搬运用 shutil.move()函数实现。

data_file = os.listdir('F:\General_Electricity\Pycharm\log\\train')         #训练集文件路径
print(len(data_file)) # 25000
dog_file = list(filter(lambda x : x[:3] == 'dog', data_file))        #在train中找猫和狗的图片,并分成两个文件保存
cat_file = list(filter(lambda x : x[:3] == 'cat', data_file))
print(len(dog_file),len(cat_file)) # 12500 12500
print(cat_file[:3]) # ['cat.0.jpg', 'cat.1.jpg', 'cat.10.jpg']

root = os.getcwd() #获取当前的路径
print('current path:',root)

# 新建val测试集文件夹
for i in ['dog','cat']:
    for j in ['train','val']:
        try:
            os.makedirs(os.path.join(root,j,i)) #用于递归创建目录。
        except FileExistsError as e:
            pass

# 移动图片,shutil.move 相当于剪切操作,将train中百分之10的图片放到val中,并区分猫、狗,分开保存
for i,file in enumerate(dog_file):
    ori_path = os.path.join(root,'train',file)
    #os.path.join 将前后两个文件路径名连接,组合成路径
    if i < 0.9*len(dog_file):
        des_path = os.path.join(root,'train','dog')
    else:
        des_path = os.path.join(root,'val','dog')
    shutil.move(ori_path, des_path)
    # ori_path移到当前目录并命名为des_path

for i,file in enumerate(cat_file):
    ori_path = os.path.join(root,'train',file)
    if i < 0.9*len(cat_file):
        des_path = os.path.join(root,'train','cat')
    else:
        des_path = os.path.join(root,'val','cat')
    shutil.move(ori_path, des_path)    #操作同上

然后,再将定义图片预处理函数,包括归一化、图片裁剪等,然后进行展视。

#进行图片的预处理,归一化等操作
transform_train=transforms.Compose([   #这个类的主要作用是串联多个图片变换的操作
    transforms.RandomResizedCrop(input_size),
    #transforms.RandomResizedCrop(224) 将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小
    transforms.RandomHorizontalFlip(),
    #依据概率对PIL图片进行水平翻转,默认值为0.5
    transforms.ToTensor(),
    # 将图片转换为Tensor,归一化至[0,1]
    transforms.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
    #把0-1变换到(-1,1),其中mean、std通过上述指定,然后实现:原来的0、1变为(0-0.5)/0.5 = -1 、(1-0.5)/0.5 = 1
])
train_set=ImageFolder('train',transform=transform_train)
    #载入已经分类好的文件夹中的图片
train_loader = DataLoader(dataset=train_set, batch_size=batch_size,shuffle=True)
    #继承ImageFloder()类,batch_size(每个batch的大小), shuffle(是否进行shuffle操作), num_workers(加载数据的时候使用几个子进程)

transform_val=transforms.Compose([
    transforms.Resize([input_size,input_size]),
    #将输入PIL图像的大小调整为给定大小
    transforms.ToTensor(),
    transforms.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
])
val_set=ImageFolder('val', transform=transform_val)
val_loader = DataLoader(dataset=val_set,
                        batch_size=batch_size,
                        shuffle=False)

print(train_set.class_to_idx)  # {'cat': 0, 'dog': 1}
print(len(train_set.imgs))  # 22500
print(train_set[1][0].size())  # torch.Size([3, 224, 224])
print(val_set[1][0].size())  # torch.Size([3, 224, 224])

plt.imshow(np.transpose(train_set[1][0],[1,2,0])+0.5)
    #np.transpose()转换数据模式,plt.inshow()展示图片
plt.show()

之后,再定义相应的全连接层框架,将模型载入CPU或GPU中,计算交叉熵等数据,再编写train、text函数,主要利用数据反向传播,不断更新各个梯度的值,将loss不断减小,并对整体进行优化,具体可看(13条消息) pytorch知识点_风声向寂的博客中,反向传播的知识点。

transfer_model = models.resnet18(pretrained=True)
    #调用模型进行权重等计算
for param in transfer_model.parameters():
    param.requires_grad = False
    # param.requires_grad = False表示只计算全连接层的权重,忽略预训练模型的权重

    # 修改最后一层维数,即 把原来的全连接层 替换成 输出维数为2的全连接层
dim = transfer_model.fc.in_features
    #最后fc层的输入
transfer_model.fc = nn.Linear(dim, 2)
    # 就是制造出一个全连接层的框架,即y=X*W.T + b,对给定一个具体的输入X,就会输出相应的y

net = transfer_model.to(device)
    #将模型载入GPU
criterion = nn.CrossEntropyLoss()
    # 计算交叉熵
optimizer = torch.optim.SGD(net.fc.parameters(), lr=1e-3)
    #  torch.optim.SGD()优化器    net.fc.parameters()为参数信息

def train():
    net.train()
    batch_num = len(train_loader)   #得到每批训练的数量
    running_loss = 0.0
    for i, data in enumerate(train_loader,start=1):
        # 将输入传入GPU
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()   #梯度归零
        outputs = net(inputs)   #前向传播求出预测的值
        loss = criterion(outputs, labels)   #求损失函数
        loss.backward()     #反向传播计算每个梯度的值
        optimizer.step()    #根据梯度下降进行更新

        # 计算误差并显示
        running_loss += loss.item()
        if i % 20 == 0: #每循环20次就打印一次结果
            print(
                'batch:{}/{} loss:{:.3f}'.format(i, batch_num, running_loss / 20))
            running_loss = 0.0


#测试函数
def validate():
    net.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data in val_loader:
            images, labels = data
            images, labels = images.to(device), labels.to(device)
            outputs = net(images)
            _, predicted = torch.max(outputs.data, 1)
            #_表示具体的value,即输出的最大值,predicted表示value所在的index, torch.max()找到输出的最大值
            total += labels.size(0) #更新测试图片的数量
            correct += (predicted == labels).sum().item()   #将对比后所有结果,即0-1相加,再转换为python数字

    print('Accuracy of the network on the test images: %d %%' %
          (100 * correct / total))

最后,调用循环调用train、val函数。

n_epoch = 10
for epoch in range(n_epoch):
    print('epoch {}'.format(epoch+1))
    train()
    validate()
torch.save(net.state_dict(), save_path)
    #将权重和偏置存入磁盘

以下为全部代码:

import os
import shutil
import torch
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import models
from torch import nn
import numpy as np
import matplotlib.pyplot as plt


device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#这段代码一般放在读取数据之前,选择使用GPU还是CPU

input_size = 224
batch_size = 128
save_path = './weights.pt'
#用于保存一个序列化(模型)到磁盘,方便以后调用

data_file = os.listdir('F:\General_Electricity\Pycharm\log\\train')         #训练集文件路径
print(len(data_file)) # 25000
dog_file = list(filter(lambda x : x[:3] == 'dog', data_file))        #在train中找猫和狗的图片,并分成两个文件保存
cat_file = list(filter(lambda x : x[:3] == 'cat', data_file))
print(len(dog_file),len(cat_file)) # 12500 12500
print(cat_file[:3]) # ['cat.0.jpg', 'cat.1.jpg', 'cat.10.jpg']

root = os.getcwd() #获取当前的路径
print('current path:',root)

# 新建val测试集文件夹
for i in ['dog','cat']:
    for j in ['train','val']:
        try:
            os.makedirs(os.path.join(root,j,i)) #用于递归创建目录。
        except FileExistsError as e:
            pass

# 移动图片,shutil.move 相当于剪切操作,将train中百分之10的图片放到val中,并区分猫、狗,分开保存
for i,file in enumerate(dog_file):
    ori_path = os.path.join(root,'train',file)
    #os.path.join 将前后两个文件路径名连接,组合成路径
    if i < 0.9*len(dog_file):
        des_path = os.path.join(root,'train','dog')
    else:
        des_path = os.path.join(root,'val','dog')
    shutil.move(ori_path, des_path)
    # ori_path移到当前目录并命名为des_path

for i,file in enumerate(cat_file):
    ori_path = os.path.join(root,'train',file)
    if i < 0.9*len(cat_file):
        des_path = os.path.join(root,'train','cat')
    else:
        des_path = os.path.join(root,'val','cat')
    shutil.move(ori_path, des_path)    #操作同上

#进行图片的预处理,归一化等操作
transform_train=transforms.Compose([   #这个类的主要作用是串联多个图片变换的操作
    transforms.RandomResizedCrop(input_size),
    #transforms.RandomResizedCrop(224) 将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小
    transforms.RandomHorizontalFlip(),
    #依据概率对PIL图片进行水平翻转,默认值为0.5
    transforms.ToTensor(),
    # 将图片转换为Tensor,归一化至[0,1]
    transforms.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
    #把0-1变换到(-1,1),其中mean、std通过上述指定,然后实现:原来的0、1变为(0-0.5)/0.5 = -1 、(1-0.5)/0.5 = 1
])
train_set=ImageFolder('train',transform=transform_train)
    #载入已经分类好的文件夹中的图片
train_loader = DataLoader(dataset=train_set, batch_size=batch_size,shuffle=True)
    #继承ImageFloder()类,batch_size(每个batch的大小), shuffle(是否进行shuffle操作), num_workers(加载数据的时候使用几个子进程)

transform_val=transforms.Compose([
    transforms.Resize([input_size,input_size]),
    #将输入PIL图像的大小调整为给定大小
    transforms.ToTensor(),
    transforms.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
])
val_set=ImageFolder('val', transform=transform_val)
val_loader = DataLoader(dataset=val_set,
                        batch_size=batch_size,
                        shuffle=False)

print(train_set.class_to_idx)  # {'cat': 0, 'dog': 1}
print(len(train_set.imgs))  # 22500
print(train_set[1][0].size())  # torch.Size([3, 224, 224])
print(val_set[1][0].size())  # torch.Size([3, 224, 224])

plt.imshow(np.transpose(train_set[1][0],[1,2,0])+0.5)
    #np.transpose()转换数据模式,plt.inshow()展示图片
plt.show()

transfer_model = models.resnet18(pretrained=True)
    #调用模型进行权重等计算
for param in transfer_model.parameters():
    param.requires_grad = False
    # param.requires_grad = False表示只计算全连接层的权重,忽略预训练模型的权重

    # 修改最后一层维数,即 把原来的全连接层 替换成 输出维数为2的全连接层
dim = transfer_model.fc.in_features
    #最后fc层的输入
transfer_model.fc = nn.Linear(dim, 2)
    # 就是制造出一个全连接层的框架,即y=X*W.T + b,对给定一个具体的输入X,就会输出相应的y

net = transfer_model.to(device)
    #将模型载入GPU
criterion = nn.CrossEntropyLoss()
    # 计算交叉熵
optimizer = torch.optim.SGD(net.fc.parameters(), lr=1e-3)
    #  torch.optim.SGD()优化器    net.fc.parameters()为参数信息

def train():
    net.train()
    batch_num = len(train_loader)   #得到每批训练的数量
    running_loss = 0.0
    for i, data in enumerate(train_loader,start=1):
        # 将输入传入GPU
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()   #梯度归零
        outputs = net(inputs)   #前向传播求出预测的值
        loss = criterion(outputs, labels)   #求损失函数
        loss.backward()     #反向传播计算每个梯度的值
        optimizer.step()    #根据梯度下降进行更新

        # 计算误差并显示
        running_loss += loss.item()
        if i % 20 == 0: #每循环20次就打印一次结果
            print(
                'batch:{}/{} loss:{:.3f}'.format(i, batch_num, running_loss / 20))
            running_loss = 0.0


#测试函数
def validate():
    net.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data in val_loader:
            images, labels = data
            images, labels = images.to(device), labels.to(device)
            outputs = net(images)
            _, predicted = torch.max(outputs.data, 1)
            #_表示具体的value,即输出的最大值,predicted表示value所在的index, torch.max()找到输出的最大值
            total += labels.size(0) #更新测试图片的数量
            correct += (predicted == labels).sum().item()   #将对比后所有结果,即0-1相加,再转换为python数字

    print('Accuracy of the network on the test images: %d %%' %
          (100 * correct / total))



n_epoch = 10
for epoch in range(n_epoch):
    print('epoch {}'.format(epoch+1))
    train()
    validate()
torch.save(net.state_dict(), save_path)
    #将权重和偏置存入磁盘