>- **🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客**
>- **🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)**
一:前期准备
1.设置GPU
如果设备上支持GPU就使用GPU,否则使用CPU
import torch
import torch.nn as nn
import torch vision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,random
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
2.导入数据
data_dir = '/content/drive/MyDrive/Colab Notebooks/第5天/data'
data_dir = pathlib.Path(data_dir)
data_path = list(data_dir.glob('*'))
class_names = [str(path).split("/")[-1] for path in data_paths]
#classeNames = [str(path).split("\\")[1] for path in data_paths] #windows 用这行
print(class_names)
#或者使用
#files = os.listdir(data_dir)
#print(files)
代码输出
- 第一步:使用pathlib.path()函数将字符串类型的文件夹路径转换为pathlib.path对象。
- 第二步:使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。
- 第三步:通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames中。
- 第四步: 打印classNames列表,显示每个文件所属的类别名称。
import matplotlib.pyplot as plt
from PIL import Image
#指定图像文件夹路径
image_folder = '/content/drive/MyDrive/Colab Notebooks/第5天/data/cloudy/'
#获取文件夹中的所有图像文件
image_files = [f for f in os.listdir(image_filder) if f.endswith((".jpg",".png",".jpeg"))]
#创建Matplotlib图像
fig,axes = plt.subplots(3,8,figsize = (16,6))
#使用列表推导式加载和显示图像
for ax,img_file in zip(axes.flat,image_files):
img_path = os.path.join(image_folder,img_file)
img = Image.open(img_path)
ax.imshow(img)
ax.axis('off')
#显示图像
plt.tight_layout()
plt.show()
total_datadir = '/content/drive/MyDrive/Colab Notebooks/第5天/data'
# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
train_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和std从数据中随机抽样计算得到
])
total_data = datasets.ImageFolder(total_datadir,transform = train_transforms)
total_data
3.划分数据集
train_size = int(0.8*len(total_data))
test_size = len(total_data)-train_size
train_datasize , test_datasize = torch.utils.data.random_split(total_data,[train_size,test_size])
train_datasize,test_datasize
代码输出
- train_size 表示训练集大小,通过将整体数据的80%转换为整数得到。
- test_size 表示测试集大小,是总体数据长度减去训练集大小。
使用torch.utils.data.random_split()方法进行数据集划分。该方法将总体数据total_data按照指定的大小比例([train_size,test_size])随机划分为训练集和测试集,并将划分结果分别赋值给train_dataset 和 test_dataset两个变量。
train_size,test_size
代码输出
for X,y in test_dl:
print("shape of X [N,C,H,W]:",X.shape)
print("shape of y:",y.shape,y.dtype)
break
torch.utils.data.DataLoader()参数详解
torch.utils.data.DataLoader是Pytorch中用于加载和管理数据的一个实用工具类。它允许你以小批次的方式迭代你的数据集,这对于训练神经网络和其他机器学习任务非常有用。DataLoader构造函数接受多个参数,下面是一些常用的参数及解释:
1. dataset(必须参数):这是你的数据集对象,通常是torch.utils.data.Dataset的子类,它包含了你的数据样本。
2:batch_size(可选参数):指定每个小批次中包含的样本数,默认值为1.
3:shuffle(可选参数):如果设置为True,则在每个epoch开始时对数据进行洗牌,以随机打乱样本的顺序。这对于训练数据的随机性很重要,以避免模型学习到数据的顺序性。默认值为False。
4:num_workers(可选参数):用于数据加载的子进程数量。通常,将其设置为大于0的值可以加快数据加载速度,特别是当数据集很大时。默认值为0,表示在主进程中加载数据。
5:pin_memory(可选参数):如果设置为True,则数据加载到GPU时会将数据存储在CUDA的锁业内存中,这可以加速数据传输到GPU.默认值为False。
6:drop_last(可选参数):如果设置为True,则在最后一个小批次可能包含样本数小于batch_size时,丢弃该小批次。这在某些情况下很有用,以确保所有小批次具有相同大小,默认值为False。
7:time_out(可选参数):如果设置为正整数,它定义了每个子进程在等待数据加载器传递数据时的超时时间(以秒为单位)。这可以用于避免子进程卡住的情况。默认值为 0,表示没有超时限制。
8:worker_init_in(可选参数):一个可选的函数,用于初始化每个子进程的状态。这对于设置每个子进程的随机种子或其他初始化操作很有用。
二:构建简单的CNN网络
在卷积层和全连接层之间,我们可以使用之前的torch.flatten()也可以使用下面的x.view()亦或是torch.nn.Flatten()。torch.nn.Flatten()与TensorFlow中的Flatten()层类似,前两者则仅仅是一种数据集拉伸操作(将二维数据拉伸为一维), torch.flatten()方法不会改变x本身,而是返回一个新的张量。而x.view()方法则是直接在原有数据上进行操作。
网络结构图:
上面的网络数据shape变化过程为:
3, 224, 224
(输入数据)
-> 12, 220, 220
(经过卷积层1)
-> 12, 216, 216
(经过卷积层2)-> 12, 108, 108
(经过池化层1)
-> 24, 104, 104
(经过卷积层3)
-> 24, 100, 100
(经过卷积层4)-> 24, 50, 50
(经过池化层2)
-> 60000 -> num_classes(4)
计算公式为:
import torch.nn.functional as F
class Network_bn(nn.Module):
def __init__(self):
super(Network_bn,self).__init__()
"""
nn.Conv2d()函数:
第一个参数(in_channels)是输入的channel数量
第二个参数(out_channels)是输出的channel数量
第三个参数(kerne_size)是卷积核大小
第四个参数(stride)是步长,默认为1
第五个参数(padding)是填充大小,默认为0
"""
self.conv1 = nn.Conv2d(3,12,5,1,0)
self.bn1 = nn.BatchNorm2d(12)
self.conv2 = nn.Conv2d(12,12,5,1,0)
self.bn2 = nn.BatchNorm2d(12)
self.pool1 = nn.MaxPool2d(2,2)
self.conv3 = nn.Conv2d(12,24,5,1,0)
self.bn3 = nn.BatchNorm2d(24)
self.conv4 = nn.Conv2d(24,24,5,1,0)
self.bn4 = nn.BatchNorm2d(24)
self.pool2 = nn.MaxPool2d(2,2)
self.fc1 = nn.Linear(24*50*50,len(class_names))
def forward(self,x):
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool1(x)
x = F.relu(self.bn3(self.conv3(x)))
x = F.relu(self.bn4(self.conv4(x)))
x = self.pool2(x)
x= x.view(-1,24*50*50)
#x= torch.flatten(x,1)
x = self.fc1(x)
return x
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))
model = Network_bn().to(device)
model
三:训练模型
1.设置超参数
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
2.编写训练函数
def train(dataloader,model,loss_fn,optimizer):
size = len(dataloader.dataset) #训练集的大小,一共900张图
num_batches = len(dataloader) #批次数目,29(900/32)
train_loss,train_acc =0,0 #初始化训练损失和正确率
for X,y in dataloader:#获取图片及其标签
X,y = X.to(device),y.to(devie)
#计算预测误差
pred = model(X) #网络输出
loss = loss_fn(pred,y) #计算网络输出和真实值之间的差距
#反向传播
optimizer.zero_grad() #梯度清零
loss.backward() #反向传播
optimizer.step() #更新参数
#记录acc与loss
train_loss += loss.item()
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss /= num_batches
train_acc /= size
return train_acc,train_loss
3.编写测试函数
测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器。
def test(dataloader,model,loss_fn):
size = len(dataloader.dataset) #测试集的大小,一共225张图片
num_batches = len(dataloader) #批次数目 8(225/32=7.03,向上取整)
test_loss,test_acc =0,0 #初始化测试损失和正确率
#当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs,target in dataloader:#获取图片及其标签
imgs,target = imgs.to(device),target.to(device)
#计算预测误差
target_pred = model(imgs) #网络输出
loss = loss_fn(target_pred,target) #计算网络输出和真实值之间的差距
#记录acc与loss
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_loss /= num_batches
test_acc /= size
return test_acc,test_loss
4.正式训练
1.model.train()
model.train()
的作用是启用 Batch Normalization 和 Dropout。如果模型中有BN
层(Batch Normalization)和Dropout
,需要在训练时添加model.train()
。model.train()
是保证BN层能够用到每一批数据的均值和方差。对于Dropout
,model.train()
是随机取一部分网络连接来训练更新参数。
2.model.eval()
model.eval()
的作用是不启用 Batch Normalization 和 Dropout。如果模型中有BN层(Batch Normalization)和Dropout,在测试时添加model.eval()
。model.eval()
是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。对于Dropout
,model.eval()
是利用到了所有网络连接,即不进行随机舍弃神经元。训练完train样本后,生成的模型model要用来测试样本。在model(test)
之前,需要加上model.eval()
,否则的话,有输入数据,即使不训练,它也会改变权值。这是model中含有BN层和Dropout所带来的的性质。
epochs =20
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs):
model.train()
epoch_train_acc,epoch_train_loss = train(train_dl,model,loss_fn,optimizer)
model.eval()
epoch_test_acc,epoch_test_loss = test(test_dl,model,loss_fn)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
template = ('Epoch:{:2d}, Train_acc: {:.1f}, Train_loss: {:.3f}, Test_acc: {:.1f}, Test_loss: {:.3f}')
print(template.format(epoch+1,epoch_train_acc*100,epoch_train_loss,epoch_test_acc*100,epoch_test_loss))
print('Done')
四:结果可视化
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore") #忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
plt.rcParams['figure.dpi'] = 100 #分辨率
epoch_range = range(epochs)
plt.figure(figsize=(12,3))
plt.subplot(1,2,1)
plt.plot(epoch_range,train_acc,label='Training Accuracy')
plt.plot(epoch_range,test_acc,label='Test Accuracy')
plt.legend(loc = 'lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1,2,2)
plt.plot(epoch_range,train_loss,label='Training Loss')
plt.plot(epoch_range,test_loss,label='Test Loss')
plt.legend(loc = 'upper right')
plt.title('Training and Validation Loss')
plt.show()
五:总结
测试集中的loss值仍有较大波动,可能需要对学习率进行调整