
本文为365天深度学习训练营中的学习记录博客原作者K同学啊语言环境Python3.13编译器jupyter notebook深度学习环境Pytorch torch 2.12cuda12.8 torchvision 0.27在之前的案例中我们多是使用datasets.ImageFolder函数直接导入已经分类好的数据集形成Dataset然后使用DataLoader加载Dataset但是如果对无法分类的数据集我们如何导入并进行识别呢本周我将自定义一个MyDataset加载车牌数据集并完成车牌识别一、导入数据fromtorchvision.transformsimporttransformsfromtorch.utils.dataimportDataLoaderfromtorchvisionimportdatasetsimporttorchvision.modelsasmodelsimporttorch.nn.functionalasFimporttorch.nnasnnimporttorch,torchvision device torch.device(cudaiftorch.cuda.is_available()elsecpu) device1. 获取类别名importos,PIL,random,pathlibimportmatplotlib.pyplotasplt# 支持中文plt.rcParams[font.sans-serif] [SimHei]# 用来正常显示中文标签plt.rcParams[axes.unicode_minus] False# 用来正常显示负号data_dir ./015_licence_plate/data_dir pathlib.Path(data_dir) data_paths list(data_dir.glob(*)) classeNames [str(path).split(\\)[1].split(_)[1].split(.)[0]forpathindata_paths]print(classeNames)输出[川W9BR26, 藏WP66B0, 沪E264UD, 津D8Z15T, 浙E198UJ, 陕Z813VB, 甘G24298, 青SN18Q3, 云HZR899, 辽G46Z9R, 湘G0H422, 蒙D35P2J, 冀Z4K30A, 青Q31F3Y, 京X3U68P, 粤P6W0T1, 浙LD9F20, 黑AQ8U79, 津T0B1L3, 琼D0DK01, 渝V8X77K, 陕H4M02X, 沪K8W7S0, 津L612CY, 琼U2E68N, 鄂YDK772, 赣G3B80M, 陕B4H8M5, 甘J9R5K1, 贵UB312U, 浙R6PA34, 豫P21V72, 冀K3DD99, 黑DU092M, 川CQ816G, 晋N678PK, 川T65HK2, 闽FD24Q5, 桂X2R99V, 皖ZX3N01, 晋AL98Q2, 皖S9Q7H7, 川Q802LX, 琼F21DU3, 浙MZB988, 粤C035ZT, 津T127WB, 黑J92YL9, 津L93B1R, 贵R86A0C, 川C6W88E, 川STQ089, 沪DFS269, 赣K6R6S7, 新CXX059, 藏E6E0J5, 吉TY9Y56, 赣H71Y6P, 甘Y64BV5, 黑AD426J, 云T559R9, 沪W70W0S, 苏MB1B64, 青EYJ193, 苏PK4A85, 鄂W0UC59, 苏V617UX, 鲁FU211P, 川V12X5U, 沪J541UR, 冀G844UX, 冀V924RP, 吉EV33G4, 贵F1WA97, 桂L120AY, 贵S014M8, 吉A47W3K, 闽UC760E, 苏VU77Z2, 陕Z0C3J1, 豫L1Z7W9, 鄂BX6773, 宁U71Y0N, 藏Y996XF, 晋P6UE94, 苏XY2E83, 辽S041TN, 黑CW73L1, 京A300JJ, 鲁Y0XU14, 京X2U380, 琼X57T3N, 渝FF77F5, 辽G6Z0E6, 皖N7MY60, 鲁S38MC4, 新PCJ034, 黑RQ5Y82, 京T77UG0, 青Z0M0Q9, 蒙CND016, 豫QA728B, 津F06W8L, 苏D08RX8, 冀S467MS, 琼K70U4T, 皖JKT701, 贵LH972J, 鄂BS039A, 鄂XU23J4, 皖N0QZ88, 陕DK449S, 京JD27D0, 辽W5H1W1, 津WXR665, 陕WJ0K89, 吉D5MZ11, 渝CW50V0, 冀YR14S2, 苏D3F8Y8, 云LT264G, 浙Y24GV8, 川Y3X65M, 津W6ZT14, 吉HB1X58, 晋AX70H8, 云X102GR, 青SB01J4, 冀W526JG, 鲁U3Q39C, 云YJ096W, 吉X7L2E9, 冀H5T13X, 贵CP7975, 云AK07G8, 甘CWR955, 吉Z3L91G, 津FV1H93, 豫M58JC2, 陕BM7X23, 津LB6C01, 蒙N60B97, 新LM15K7, 粤EQ252R, 藏K9V1P6, 闽X5RL93, 晋S9TC51, 甘K56Q7Z, 沪S273UA, 琼GA5593, 津BSV768, 陕R3732E, 辽YJ38C7, 鲁U035VT, 津P45XM4, 闽G87BW9, 赣YF43T5, 鲁P724WT, 晋XU310C, 黑E09H0W, 晋BG8581, 贵XQ3H88, 晋N647UY, 鲁A66BH8, 京X0TZ06, 鲁M21VD0, 京U3Z30V, 沪D84FJ3, 京V50TP0, 津YQ5R63, 云M1P07Z, 冀TJL965, 吉P9R81E, 宁X8G32V, 津P62GA6, 黑J769PB, 湘FUR792, 甘GH0H42, 晋U27QD6, 桂X3Z4H5, 新ZT953E, 甘C1H52P, 吉K02XB7, 蒙GNN656, 桂M11QY4, 黑PFU129, 川T5Y5G0, 湘M67F8C, 冀A5E7G3, 赣G7070P, 沪U3U1E1, 陕E71E6U, 陕WM4T21, 陕Z76H5P, 琼A7CQ37, 粤SV8F80, 粤R439FD, 鄂F50Z5D, 冀FE78Z6, 津B2A6R7, 沪V7007Y, 藏E913GD, 赣M9X4C2, 青FT8E80, 浙YVX381, 鄂R30RS1, 京J95C8Z, 川H232TV, 吉C22XH5, 皖DV07J2, 宁CA40Q1, 甘M9SX92, 粤W740GF, 赣A5N92E, 冀EF2W60, 浙Q36YH3, 甘C2S1U4, 鲁Z5Z29U, 苏AR9U61, 陕K650MM, 宁XGN335, 贵Z60S8Z, 渝X2JN18, 甘A7Z2J0, 冀EK25G3, 琼S49A7N, 贵D497QS, 鲁B73QB0, 蒙L76UA1, 藏VY077J, 宁G2L663, 沪P25J6L, 浙E295GC, 辽JZM931, 宁U0LN56, 津RH50L5, 粤X6ZQ57, 闽X776NG, 川E4A3V0, 渝X10URdata_paths list(data_dir.glob(*)) data_paths_str [str(path)forpathindata_paths] data_paths_str2. 数据可视化plt.figure(figsize(14,5)) plt.suptitle(数据示例K同学啊,fontsize15)foriinrange(18): plt.subplot(3,6,i1)# plt.xticks([])# plt.yticks([])# plt.grid(False)# 显示图片images plt.imread(data_paths_str[i]) plt.imshow(images) plt.show()3. 标签数字化importnumpyasnp char_enum [京,沪,津,渝,冀,晋,蒙,辽,吉,黑,苏,浙,皖,闽,赣,鲁,\豫,鄂,湘,粤,桂,琼,川,贵,云,藏,陕,甘,青,宁,新,军,使] number [str(i)foriinrange(0,10)]# 0 到 9 的数字alphabet [chr(i)foriinrange(65,91)]# A 到 Z 的字母char_set char_enum number alphabet char_set_len len(char_set) label_name_len len(classeNames[0])# 将字符串数字化deftext2vec(text): vector np.zeros([label_name_len, char_set_len])fori, cinenumerate(text): idx char_set.index(c) vector[i][idx] 1.0returnvector all_labels [text2vec(i)foriinclasseNames]4. 加载数据文件importosimportpandasaspdfromtorchvision.ioimportread_imagefromtorch.utils.dataimportDatasetimporttorch.utils.dataasdatafromPILimportImageclassMyDataset(data.Dataset):def__init__(self, all_labels, data_paths_str, transform): self.img_labels all_labels# 获取标签信息self.img_dir data_paths_str# 图像目录路径self.transform transform# 目标转换函数def__len__(self):returnlen(self.img_labels)def__getitem__(self, index): image Image.open(self.img_dir[index]).convert(RGB)#plt.imread(self.img_dir[index]) # 使用 torchvision.io.read_image 读取图像label self.img_labels[index]# 获取图像对应的标签ifself.transform: image self.transform(image)returnimage, label# 返回图像和标签total_datadir ./03_traffic_sign/# 关于transforms.Compose的更多介绍可以参考https://blog.csdn.net/qq_38251616/article/details/124878863train_transforms transforms.Compose([ transforms.Resize([224,224]),# 将输入图片resize成统一尺寸transforms.ToTensor(),# 将PIL Image或numpy.ndarray转换为tensor并归一化到[0,1]之间transforms.Normalize(# 标准化处理--转换为标准正太分布高斯分布使模型更容易收敛mean[0.485,0.456,0.406], std [0.229,0.224,0.225])# 其中 mean[0.485,0.456,0.406]与std[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。]) total_data MyDataset(all_labels, data_paths_str, train_transforms) total_data5. 划分数据train_size int(0.8*len(total_data)) test_size len(total_data) - train_size train_dataset, test_dataset torch.utils.data.random_split(total_data, [train_size, test_size]) train_size,test_size(10940, 2735)train_loader torch.utils.data.DataLoader(train_dataset, batch_size16, shuffleTrue) test_loader torch.utils.data.DataLoader(test_dataset, batch_size16, shuffleTrue)print(The number of images in a training set is: ,len(train_loader)*16)print(The number of images in a test set is: ,len(test_loader)*16)print(The number of batches per epoch is: ,len(train_loader))The number of images in a training set is: 10944 The number of images in a test set is: 2736 The number of batches per epoch is: 684二、自建模型classNetwork_bn(nn.Module):def__init__(self):super(Network_bn, self).__init__() nn.Conv2d()函数 第一个参数in_channels是输入的channel数量 第二个参数out_channels是输出的channel数量 第三个参数kernel_size是卷积核大小 第四个参数stride是步长默认为1 第五个参数padding是填充大小默认为0 self.conv1 nn.Conv2d(in_channels3, out_channels12, kernel_size5, stride1, padding0) self.bn1 nn.BatchNorm2d(12) self.conv2 nn.Conv2d(in_channels12, out_channels12, kernel_size5, stride1, padding0) self.bn2 nn.BatchNorm2d(12) self.pool nn.MaxPool2d(2,2) self.conv4 nn.Conv2d(in_channels12, out_channels24, kernel_size5, stride1, padding0) self.bn4 nn.BatchNorm2d(24) self.conv5 nn.Conv2d(in_channels24, out_channels24, kernel_size5, stride1, padding0) self.bn5 nn.BatchNorm2d(24) self.fc1 nn.Linear(24*50*50, label_name_len*char_set_len) self.reshape Reshape([label_name_len,char_set_len])defforward(self, x): x F.relu(self.bn1(self.conv1(x))) x F.relu(self.bn2(self.conv2(x))) x self.pool(x) x F.relu(self.bn4(self.conv4(x))) x F.relu(self.bn5(self.conv5(x))) x self.pool(x) x x.view(-1,24*50*50) x self.fc1(x)# 最终reshapex self.reshape(x)returnx# 定义Reshape层classReshape(nn.Module):def__init__(self, shape):super(Reshape, self).__init__() self.shape shapedefforward(self, x):returnx.view(x.size(0), *self.shape) device cudaiftorch.cuda.is_available()elsecpuprint(Using {} device.format(device)) model Network_bn().to(device) model输出Using cuda deviceNetwork_bn( (conv1): Conv2d(3, 12, kernel_size(5, 5), stride(1, 1)) (bn1): BatchNorm2d(12, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (conv2): Conv2d(12, 12, kernel_size(5, 5), stride(1, 1)) (bn2): BatchNorm2d(12, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (pool): MaxPool2d(kernel_size2, stride2, padding0, dilation1, ceil_modeFalse) (conv4): Conv2d(12, 24, kernel_size(5, 5), stride(1, 1)) (bn4): BatchNorm2d(24, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (conv5): Conv2d(24, 24, kernel_size(5, 5), stride(1, 1)) (bn5): BatchNorm2d(24, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (fc1): Linear(in_features60000, out_features483, biasTrue) (reshape): Reshape() )importtorchsummary 显示网络结构 torchsummary.summary(model, (3,224,224))输出---------------------------------------------------------------- Layer (type) Output Shape Param # Conv2d-1 [-1, 12, 220, 220] 912 BatchNorm2d-2 [-1, 12, 220, 220] 24 Conv2d-3 [-1, 12, 216, 216] 3,612 BatchNorm2d-4 [-1, 12, 216, 216] 24 MaxPool2d-5 [-1, 12, 108, 108] 0 Conv2d-6 [-1, 24, 104, 104] 7,224 BatchNorm2d-7 [-1, 24, 104, 104] 48 Conv2d-8 [-1, 24, 100, 100] 14,424 BatchNorm2d-9 [-1, 24, 100, 100] 48 MaxPool2d-10 [-1, 24, 50, 50] 0 Linear-11 [-1, 483] 28,980,483 Reshape-12 [-1, 7, 69] 0 Total params: 29,006,799 Trainable params: 29,006,799 Non-trainable params: 0 ---------------------------------------------------------------- Input size (MB): 0.57 Forward/backward pass size (MB): 26.56 Params size (MB): 110.65 Estimated Total Size (MB): 137.79 ----------------------------------------------------------------注意对比观察模型的输出[-1, 7, 69]我们之前的网络结构输出都是[-1, 7]、[-1, 2]、[-1, 4]这样的二维数据如果要求模型输出结果是多维数据那么本案例将是很好的示例。提问[-1, 7, 69]中的-1是什么意思在神经网络中如果我们不确定一个维度的大小但是希望在计算中自动推断它可以使用-1。这个-1告诉 PyTorch 在计算中自动推断这个维度的大小以确保其他维度的尺寸不变并且能够保持张量的总大小不变。例如[-1, 7, 69]表示这个张量的形状是一个三维张量其中第一个维度的大小是不确定的第二维大小为7第三大小分别为69。-1的作用是使得总的张量大小等于7 * 69以适应实际的输入数据大小。在实际的使用中通常-1用在批处理维度上因为在训练过程中批处理大小可能会有所不同。使用-1可以使模型适应不同大小的批处理输入数据。三、模型训练1. 优化器与损失函数optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay0.0001) loss_model nn.CrossEntropyLoss()本周任务之一在下面的代码中我对loss进行了统计更新请补充acc统计更新部分即获取每一次测试的ACC值。任务提示pred.shape与y.shape是[batch, 7, 69]在进行acc计算时需注意from torch.autograd import Variable def test(model, test_loader, loss_model): size len(test_loader.dataset) num_batches len(test_loader) model.eval() test_loss, correct 0, 0 with torch.no_grad(): for X, y in test_loader: X, y X.to(device), y.to(device) pred model(X) test_loss loss_model(pred, y).item() test_loss / num_batches print(fAvg loss: {test_loss:8f} \n) return correct,test_loss def train(model,train_loader,loss_model,optimizer): modelmodel.to(device) model.train() for i, (images, labels) in enumerate(train_loader, 0): #0是标起始位置的值。 images Variable(images.to(device)) labels Variable(labels.to(device)) optimizer.zero_grad() outputs model(images) loss loss_model(outputs, labels) loss.backward() optimizer.step() if i % 1000 0: print([%5d] loss: %.3f % (i, loss.item()))原文print语句loss后面少了个.item()2. 模型的训练test_acc_list [] test_loss_list [] epochs 30 for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(model,train_loader,loss_model,optimizer) test_acc,test_loss test(model, test_loader, loss_model) test_acc_list.append(test_acc) test_loss_list.append(test_loss) print(Done!)输出[ 0] loss: 0.206 Avg loss: 0.059261 Epoch 2 ------------------------------- [ 0] loss: 0.061 Avg loss: 0.048103 Epoch 3 ------------------------------- [ 0] loss: 0.015 Avg loss: 0.042141 Epoch 4 ------------------------------- [ 0] loss: 0.023 Avg loss: 0.039984 Epoch 5 ------------------------------- [ 0] loss: 0.017 Avg loss: 0.037894 Epoch 6 ------------------------------- [ 0] loss: 0.022 Avg loss: 0.036756 Epoch 7 ------------------------------- [ 0] loss: 0.030 Avg loss: 0.033269 Epoch 8 ------------------------------- [ 0] loss: 0.019 Avg loss: 0.032184 Epoch 9 ------------------------------- [ 0] loss: 0.021 Avg loss: 0.031619 Epoch 10 ------------------------------- [ 0] loss: 0.014 Avg loss: 0.030702 Epoch 11 ------------------------------- [ 0] loss: 0.021 Avg loss: 0.032267 Epoch 12 ------------------------------- [ 0] loss: 0.020 Avg loss: 0.028372 Epoch 13 ------------------------------- [ 0] loss: 0.024 Avg loss: 0.028634 Epoch 14 ------------------------------- [ 0] loss: 0.025 Avg loss: 0.028026 Epoch 15 ------------------------------- [ 0] loss: 0.015 Avg loss: 0.027751 Epoch 16 ------------------------------- [ 0] loss: 0.011 Avg loss: 0.027682 Epoch 17 ------------------------------- [ 0] loss: 0.012 Avg loss: 0.026580 Epoch 18 ------------------------------- [ 0] loss: 0.007 Avg loss: 0.027022 Epoch 19 ------------------------------- [ 0] loss: 0.023 Avg loss: 0.026512 Epoch 20 ------------------------------- [ 0] loss: 0.025 Avg loss: 0.026060 Epoch 21 ------------------------------- [ 0] loss: 0.018 Avg loss: 0.026564 Epoch 22 ------------------------------- [ 0] loss: 0.013 Avg loss: 0.026626 Epoch 23 ------------------------------- [ 0] loss: 0.026 Avg loss: 0.025381 Epoch 24 ------------------------------- [ 0] loss: 0.020 Avg loss: 0.025613 Epoch 25 ------------------------------- [ 0] loss: 0.013 Avg loss: 0.026245 Epoch 26 ------------------------------- [ 0] loss: 0.025 Avg loss: 0.026597 Epoch 27 ------------------------------- [ 0] loss: 0.015 Avg loss: 0.025107 Epoch 28 ------------------------------- [ 0] loss: 0.015 Avg loss: 0.024767 Epoch 29 ------------------------------- [ 0] loss: 0.019 Avg loss: 0.025156 Epoch 30 ------------------------------- [ 0] loss: 0.009 Avg loss: 0.025432 Done!四、结果分析import numpy as np import matplotlib.pyplot as plt from datetime import datetime current_time datetime.now() # 获取当前时间 x [i for i in range(1,31)] plt.plot(x, test_loss_list, labelLoss, alpha0.8) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(current_time) # 打卡请带上时间戳否则代码截图无效 plt.legend() plt.show()