数据下载
一、下载数据集并创建以下形式文件目录
train.py: 用于创建并训练模型,并生成训练完成的参数文件。
setting.py: 用于存放训练配置、超参数,包括学习率,训练次数,裁剪图片大小,每次训练图片数量,参数保存地址。
train: 存放下载的数据集(共25000张图片,其中猫狗各12500张)。
func: 自定义包,存放部分操作。
├─setting.py
├─train.py
├─train
│ ├─cat
│ └─dog
└─func
└─__init__.py
二、拆分数据,划分训练集和验证集
在func目录下新建get_address.py文件:
"""get_address""" def get_address(): """返回狗地址列表、猫地址列表、工作目录""" import os print('{:-^30}'.format('数据集')) data_file = os.listdir('./train/') print('图片或文件数量:', str(len(data_file))) # 25000 dog_file = list(filter(lambda x: x[:3] == 'dog', data_file)) cat_file = list(filter(lambda x: x[:3] == 'cat', data_file)) print('狗:', str(len(dog_file)), '\n猫:', str(len(cat_file))) # 狗:12500 猫:12500 root = os.getcwd() print('工作目录:', root) # 工作目录: L:\kaggle print('{:-^30}'.format('')) return dog_file, cat_file, root
获取猫狗图片的地址并返回。(其实,这里可与下面的arrange.py写在一起,还不太熟练)。
在func目录下新建arrange.py文件:
"""arrange.py""" def arrange(): """整理数据,移动图片位置""" import shutil import os from .get_address import get_address dog_file, cat_file, root = get_address() print('开始数据整理') # 新建文件夹 for i in ['dog', 'cat']: for j in ['train', 'val']: try: os.makedirs(os.path.join(root,j,i)) except FileExistsError as e: pass # 移动10%(1250)的狗图到验证集 for i, file in enumerate(dog_file): ori_path = os.path.join(root, 'train', file) 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) # 移动10%(1250)的猫图到验证集 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) print('数据整理完成')
通过此调用函数将train内10%的猫狗图片提取出来,并新建val目录进行存放。原来的train将作为训练集(22500张),val
作为训练集(2500张)。
将上述函数在__init__.py中进行调用(下文类似调用操作省略)
import func.arrange import func.get_address arrange = func.arrange.arrange get_address = func.get_address.get_address
三、获取图片数据并转化
在func目录下新建get_data.py文件:
"""get_data.py"""
def get_data(input_size, batch_size): """获取文件数据并转换""" from torchvision import transforms from torchvision.datasets import ImageFolder from torch.utils.data import DataLoader # 串联多个图片变换的操作(训练集) # transforms.RandomResizedCrop(input_size) 先随机采集,然后对裁剪得到的图像缩放为同一大小 # RandomHorizontalFlip() 以给定的概率随机水平旋转给定的PIL的图像 # transforms.ToTensor() 将图片转换为Tensor,归一化至[0,1] # transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) 归一化处理(平均数,标准偏差) transform_train = transforms.Compose([ transforms.RandomResizedCrop(input_size), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) # 获取训练集(通过上面的方面操作) train_set = ImageFolder('train', transform=transform_train) # 封装训练集 train_loader = DataLoader(dataset=train_set, batch_size=batch_size, shuffle=True) # 串联多个图片变换的操作(验证集) transform_val = transforms.Compose([ transforms.Resize([input_size, input_size]), # 注意 Resize 参数是 2 维,和 RandomResizedCrop 不同 transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) # 获取验证集(通过上面的方面操作) val_set = ImageFolder('val', transform=transform_val) # 封装验证集 val_loader = DataLoader(dataset=val_set, batch_size=batch_size, shuffle=False) # 输出 return transform_train, train_set, train_loader, transform_val, val_set, val_loader
此处使用pytorch 中的 ImageFolder 可以直接读取图片集数据(第一个参数决定文件夹地址),但是每个图片大小各异,且需要转化为可识别的数据。需要对读取的图片进行变换操作(即transform参数),除图片缩放外,还需进行归一化处理以减小数据复杂度和方便数据处理。通过transforms.Compose函数,可将这些图片变化操作串联,并通过ImageFolder的调用,快速获取到所需要的数据。
四、将上述操作整合至train.py文件中
填写将所需的参数、设置填写在setting.py中
"""setting.py"""
"""调整设置""" input_size = 224 # 裁剪图片大小 batch_size = 128 # 一次训练所选取的样本数(直接影响到GPU内存的使用情况) save_path = './weights.pt' # 训练参数储存地址 lr = 1e-3 # 学习率(后面用) n_epoch = 10 # 训练次数(后面用)
填写train.py
import torch from torchvision import models from torch import nn import func as f from setting import input_size, batch_size, save_path, lr, n_epoch f.arrange() # 整理数据,移动图片位置(若已经整理完成可注释) # 获取文件数据并转换成参数集 transform_train, train_set, train_loader, transform_val, val_set, val_loader = f.get_data(input_size, batch_size) 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])
五、构建卷积神经网络
使用Resnet18模型(残差网络介绍)
device = f.device() # 选择训练模式(GPU) print('训练模式:', device, '模式') # 残差网络(18指定的是带有权重的18层,包括卷积层和全连接层,不包括池化层和BN层) # pretrained=True 使用预训练模型 # 使用resnet18模型 transfer_model = models.resnet18(pretrained=True) for param in transfer_model.parameters(): # 屏蔽预训练模型的权重,只训练最后一层的全连接的权重 param.requires_grad = False # 修改最后一层维数,即把原来的全连接层替换成输出维数为2的全连接层 # 提取fc层中固定的参数 dim = transfer_model.fc.in_features # 设置网络中的全连接层为2 transfer_model.fc = nn.Linear(dim, 2) # 构建神经网络 net = transfer_model.to(device)
此处使用Resnet18模型为基础进行训练,使用预训练模型加速训练,使得模型收敛更快。另外,由于我们需要处理的是二元分类问题,全连接层输出维数需为2(损失使用交叉熵损失函数,激活使用softmax)。
在func目录下新建device.py文件:
"""device.py"""
def device(): """自动选择训练模式,尽可能使用GPU进行运算""" import torch if torch.cuda.is_available(): return torch.device('cuda:0') else: return torch.device('cpu')
选择训练模式,也可直接使用 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 进行替代。
六、创建训练和验证函数
在func目录下新建train.py文件
"""train.py""" def train(net, optimizer, device, criterion, train_loader): """训练""" net.train() batch_num = len(train_loader) running_loss = 0.0 for i, data in enumerate(train_loader, start=1): # 将输入传入GPU(CPU) 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 % 10 == 0: print('batch:{}/{} loss:{:.3f}'.format(i, batch_num, running_loss / 20)) running_loss = 0.0
optimizer.zero_grad():梯度置零(因为梯度计算是累加的)。
outputs = net(inputs):向前传播,求出预测值。
loss = criterion(outputs, labels):计算损失。
loss.backward():反向传播,计算当前梯度。
optimizer.step() :根据梯度更新网络参数。
在func目录下新建validate.py文件:
"""validate.py"""
def validate(net, device, val_loader): """验证函数""" import torch net.eval() # 测试,需关闭dropout 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) total += labels.size(0) correct += (predicted == labels).sum().item() print('测试图像的网络精度: %d %%' % (100 * correct / total))
with torch.no_grad():验证无需进行计算,停止跟踪历史记录和使用内存。
outputs = net(images):将图片数据通过神经网络,得到输出值。
_, predicted = torch.max(outputs.data, 1):规格化返回预测值。
七、确定优化器、损失函数,进行训练并保存
# 分类问题——交叉熵损失函数 criterion = nn.CrossEntropyLoss() # 优化器——随机梯度下降 # 学习率lr=10^-3; optimizer = torch.optim.SGD(net.fc.parameters(), lr=lr) # optimizer = torch.optim.Adam(net.parameters(), lr=lr) for epoch in range(n_epoch): print('第{}次训练'.format(epoch+1)) f.train(net, optimizer, device, criterion, train_loader) f.validate(net, device, val_loader) # 仅保存模型参数 torch.save(net.state_dict(), save_path)
保存的模型参数储存在save_path地址中
单次训练后准确度可达95%,经过十次训练,准确度达到97%。
八、单图片验证,并进行可视化操作
在根目录新建两个文件tk.py和test.py:
"""test.py""" def test(): from PIL import Image import torch from torchvision import models from torch import nn from setting import input_size, save_path from torchvision import transforms # ------------------------ 加载数据 --------------------------- # # 定义预训练变换 transform_val = transforms.Compose([ transforms.Resize([input_size, input_size]), # 注意 Resize 参数是 2 维,和 RandomResizedCrop 不同 transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) class_names = ['0', '180', '270', '90'] # 这个顺序很重要,要和训练时候的类名顺序一致 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # ------------------------ 载入模型并且训练 --------------------------- # transfer_model = models.resnet18(pretrained=True) for param in transfer_model.parameters(): param.requires_grad = False dim = transfer_model.fc.in_features transfer_model.fc = nn.Linear(dim, 2) # 构建神经网络 net = transfer_model.to(device) net.load_state_dict(torch.load(save_path)) net.eval() image_PIL = Image.open(r'test/image') image_tensor = transform_val(image_PIL) # 以下语句等效于 image_tensor = torch.unsqueeze(image_tensor, 0) image_tensor.unsqueeze_(0) # 没有这句话会报错 image_tensor = image_tensor.to(device) out = net(image_tensor) # 得到预测结果,并且从大到小排序 _, indices = torch.sort(out, descending=True) # 返回每个预测值的百分数 percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100 if percentage[0] > percentage[1]: out = '此图片有{:>4.1f}%可能是只猫'.format(percentage[0]) else: out = '此图片有{:>4.1f}%可能是只狗'.format(percentage[1]) return out
"""tk.py""" from tkinter import * from tkinter import filedialog from PIL import Image, ImageTk if __name__ == "__main__": root = Tk() root.title('请选择图片') frame = Frame(root, bd=2, relief=SUNKEN) frame.grid_rowconfigure(0, weight=1) frame.grid_columnconfigure(0, weight=1) canvas = Canvas(frame, bd=0) canvas.grid(row=0, column=0, sticky=N+S+E+W) frame.pack(fill=BOTH, expand=1) def printcoords(): import shutil, os File = filedialog.askopenfilename(parent=root, initialdir=os.getcwd(), title='选择图片.') img = Image.open(File) out = img.resize((336, 192), Image.ANTIALIAS) # resize image with high-quality filename = ImageTk.PhotoImage(out) canvas.image = filename canvas.create_image(0, 0, anchor='nw', image=filename) # print(File) dir = os.getcwd() + r'\test' shutil.rmtree(dir, True) os.makedirs(os.path.join(os.getcwd(), 'test')) shutil.copyfile(File, dir + r'\image') from test import test result = test() print(result) root.title(result) Button(root, text='选择', command=printcoords).pack() root.mainloop()
这部分不是重点,就不细说了。运行tk.py结果如下:
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:kaggle——猫狗识别(pytorch) - Python技术站