> 技术文档 > 【PyTorch】图像二分类项目-部署_python pytorch 图像二分类

【PyTorch】图像二分类项目-部署_python pytorch 图像二分类


【PyTorch】图像二分类项目

【PyTorch】图像二分类项目-部署

在独立于训练脚本的新脚本中部署用于推理的模型,需要构造一个模型类的对象,并将权重加载到模型中。操作流程为:定义模型--加载权重--在验证和测试数据集上部署模型。

import torch.nn as nnimport numpy as np# 设置随机种子np.random.seed(0)
import torch.nn as nnimport torch.nn.functional as F# 定义一个函数,用于计算卷积层的输出形状def findConv2dOutShape(H_in,W_in,conv,pool=2): # 获取卷积核的大小 kernel_size=conv.kernel_size # 获取卷积的步长 stride=conv.stride # 获取卷积的填充 padding=conv.padding # 获取卷积的扩张 dilation=conv.dilation # 计算卷积后的高度 H_out=np.floor((H_in+2*padding[0]-dilation[0]*(kernel_size[0]-1)-1)/stride[0]+1) # 计算卷积后的宽度 W_out=np.floor((W_in+2*padding[1]-dilation[1]*(kernel_size[1]-1)-1)/stride[1]+1) # 如果pool不为空 if pool: # 将H_out除以pool H_out/=pool W_out/=pool # 返回H_out和W_out的整数形式 return int(H_out),int(W_out)class Net(nn.Module): def __init__(self, params): super(Net, self).__init__() # 获取输入形状 C_in,H_in,W_in=params[\"input_shape\"] # 获取初始滤波器数量 init_f=params[\"initial_filters\"] # 获取第一个全连接层神经元数量 num_fc1=params[\"num_fc1\"] # 获取类别数量 num_classes=params[\"num_classes\"] # 获取模型的dropout率,是0到1间的浮点数 # Dropout是一种正则化技术,随机关闭部分神经元(输出设为0),防止过拟合,提高泛化能力 self.dropout_rate=params[\"dropout_rate\"]  # 定义第一个卷积层 self.conv1 = nn.Conv2d(C_in, init_f, kernel_size=3) # 计算第一个卷积层的输出形状 h,w=findConv2dOutShape(H_in,W_in,self.conv1) self.conv2 = nn.Conv2d(init_f, 2*init_f, kernel_size=3) h,w=findConv2dOutShape(h,w,self.conv2) self.conv3 = nn.Conv2d(2*init_f, 4*init_f, kernel_size=3) h,w=findConv2dOutShape(h,w,self.conv3) self.conv4 = nn.Conv2d(4*init_f, 8*init_f, kernel_size=3) h,w=findConv2dOutShape(h,w,self.conv4) # 计算全连接层的输入形状 self.num_flatten=h*w*8*init_f # 定义第一个全连接层 self.fc1 = nn.Linear(self.num_flatten, num_fc1) self.fc2 = nn.Linear(num_fc1, num_classes) # 定义前向传播函数,接收输入x def forward(self, x): # 第一个卷积层 x = F.relu(self.conv1(x)) # 第一个池化层 x = F.max_pool2d(x, 2, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2, 2) x = F.relu(self.conv3(x)) x = F.max_pool2d(x, 2, 2) x = F.relu(self.conv4(x)) x = F.max_pool2d(x, 2, 2) # 将卷积层的输出展平 x = x.view(-1, self.num_flatten) # 第一个全连接层 x = F.relu(self.fc1(x)) # Dropout层 x=F.dropout(x, self.dropout_rate) # 第二个全连接层 x = self.fc2(x) # 返回输入x应用对数软最大变换后的输出 # log-softmax对数软最大值函数,常用于计算交叉熵损失函数(cross-entropy loss),因为交叉熵损失函数需要计算概率的对数。 # dim参数指定了在哪个维度上应用log-softmax。例如,如果dim=1,则对每一行应用log-softmax。 return F.log_softmax(x, dim=1)# 定义模型参数params_model={ # 输入形状 \"input_shape\": (3,96,96), # 初始过滤器数量 \"initial_filters\": 8, # 全连接层1的神经元数量 \"num_fc1\": 100, # Dropout率 \"dropout_rate\": 0.25, # 类别数量 \"num_classes\": 2,}# 创建一个CNN模型,参数为params_modelcnn_model = Net(params_model)import torch# 权重文件路径path2weights=\"./models/weights.pt\"# 加载权重文件cnn_model.load_state_dict(torch.load(path2weights))# 进入评估模式cnn_model.eval()

# 移动模型至cuda设备if torch.cuda.is_available(): device = torch.device(\"cuda\") cnn_model=cnn_model.to(device) 
import time # 定义一个函数,用于部署模型def deploy_model(model,dataset,device, num_classes=2,sanity_check=False): # num_classes:类别数,默认为2 # sanity_check:是否进行完整性检查,默认为False pass # 获取数据集长度 len_data=len(dataset) # 初始化输出张量 y_out=torch.zeros(len_data,num_classes) # 初始化真实标签张量 y_gt=np.zeros((len_data),dtype=\"uint8\") # 将模型移动到指定设备 model=model.to(device) # 存储每次推理的时间 elapsed_times=[] # 在不计算梯度的情况下执行以下代码 with torch.no_grad(): for i in range(len_data): # 获取数据集中的一个样本 x,y=dataset[i] # 将真实标签存储到张量中 y_gt[i]=y # 记录开始时间 start=time.time() # 进行推理 y_out[i]=model(x.unsqueeze(0).to(device)) # 计算推理时间 elapsed=time.time()-start # 将推理时间存储到列表中 elapsed_times.append(elapsed) # 如果进行完整性检查,则只进行一次推理 if sanity_check is True: break # 计算平均推理时间 inference_time=np.mean(elapsed_times)*1000 # 打印平均推理时间 print(\"average inference time per image on %s: %.2f ms \" %(device,inference_time)) # 返回推理结果和真实标签 return y_out.numpy(),y_gtimport torchfrom PIL import Imagefrom torch.utils.data import Datasetimport pandas as pdimport torchvision.transforms as transformsimport os# 设置随机种子,使得每次运行代码时生成的随机数相同torch.manual_seed(0)class histoCancerDataset(Dataset): def __init__(self, data_dir, transform,data_type=\"train\"):  # 获取数据目录 path2data=os.path.join(data_dir,data_type) # 获取数据目录下的所有文件名 self.filenames = os.listdir(path2data) # 获取数据目录下的所有文件的完整路径 self.full_filenames = [os.path.join(path2data, f) for f in self.filenames] # 获取标签文件名 csv_filename=data_type+\"_labels.csv\" # 获取标签文件的完整路径 path2csvLabels=os.path.join(data_dir,csv_filename) # 读取标签文件 labels_df=pd.read_csv(path2csvLabels) # 将标签文件的索引设置为文件名 labels_df.set_index(\"id\", inplace=True) # 获取每个文件的标签 self.labels = [labels_df.loc[filename[:-4]].values[0] for filename in self.filenames] # 获取数据转换函数 self.transform = transform def __len__(self): # 返回数据集的长度 return len(self.full_filenames) def __getitem__(self, idx): # 根据索引获取图像 image = Image.open(self.full_filenames[idx]) # 对图像进行转换 image = self.transform(image) # 返回图像和标签 return image, self.labels[idx]import torchvision.transforms as transforms# 创建一个数据转换器,将数据转换为张量data_transformer = transforms.Compose([transforms.ToTensor()])data_dir = \"./data/\"# 传入数据目录、数据转换器和数据集类型histo_dataset = histoCancerDataset(data_dir, data_transformer, \"train\")# 打印数据集的长度print(len(histo_dataset))

from torch.utils.data import random_split# 获取数据集的长度len_histo=len(histo_dataset)# 训练集取数据集的80%len_train=int(0.8*len_histo)# 验证集取数据集的20%len_val=len_histo-len_train# 将数据集随机分割为训练集和验证集train_ds,val_ds=random_split(histo_dataset,[len_train,len_val])# 打印训练集和验证集的长度print(\"train dataset length:\", len(train_ds))print(\"validation dataset length:\", len(val_ds))

 

# 部署模型 y_out,y_gt=deploy_model(cnn_model,val_ds,device=device,sanity_check=False)# 打印输出和真实值的形状print(y_out.shape,y_gt.shape)

使用预测输出计算模型在验证数据集上的精度

from sklearn.metrics import accuracy_score# 获取预测y_pred = np.argmax(y_out,axis=1)print(y_pred.shape,y_gt.shape)# 计算精度 acc=accuracy_score(y_pred,y_gt)print(\"accuracy: %.2f\" %acc)

 

# 部署在CPU上device_cpu = torch.device(\"cpu\")y_out,y_gt=deploy_model(cnn_model,val_ds,device=device_cpu,sanity_check=False)print(y_out.shape,y_gt.shape)

复制data文件夹中的sample_submission.csv文件并命名为test_labels.csv

path2csv=\"./data/test_labels.csv\"# 读取csv文件,并存储到DataFrame中labels_df=pd.read_csv(path2csv)# 显示DataFrame的前几行labels_df.head()

data_dir = \"./data/\"# 创建测试数据集histo_test = histoCancerDataset(data_dir, data_transformer,data_type=\"test\")# 打印测试数据集的长度print(len(histo_test))

 

# 用测试数据集部署y_test_out,_=deploy_model(cnn_model,histo_test, device, sanity_check=False)# 使用np.argmax函数对y_test_out进行操作,得到y_test_predy_test_pred=np.argmax(y_test_out,axis=1)# 打印y_test_pred的形状print(y_test_pred.shape)

from torchvision import utilsimport numpy as npimport matplotlib.pyplot as plt%matplotlib inlinenp.random.seed(0)# 定义一个函数,用于显示图像和标签def show(img,y,color=True): # 将图像转换为numpy数组 npimg = img.numpy() # 将图像的维度从(C,H,W)转换为(H,W,C) npimg_tr=np.transpose(npimg, (1,2,0)) # 如果color为False,则将图像转换为灰度图像 if color==False: npimg_tr=npimg_tr[:,:,0] plt.imshow(npimg_tr,interpolation=\'nearest\',cmap=\"gray\") else: # 否则,直接显示图像 plt.imshow(npimg_tr,interpolation=\'nearest\') # 显示图像的标签 plt.title(\"label: \"+str(y)) # 定义一个网格大小grid_size=4# 随机选择grid_size个图像的索引rnd_inds=np.random.randint(0,len(histo_test),grid_size)print(\"image indices:\",rnd_inds)# 从histo_test中获取grid_size个图像x_grid_test=[histo_test[i][0] for i in range(grid_size)]# 从y_test_pred中获取grid_size个标签y_grid_test=[y_test_pred[i] for i in range(grid_size)]# 将grid_size个图像组合成一个网格x_grid_test=utils.make_grid(x_grid_test, nrow=4, padding=2)print(x_grid_test.shape)# 设置图像的大小plt.rcParams[\'figure.figsize\'] = (10.0, 5)# 显示图像和标签show(x_grid_test,y_grid_test)