|
|
发表于 2023-4-22 18:29:36
|
显示全部楼层
本帖最后由 歌者文明清理员 于 2023-4-23 21:26 编辑
以下是基于VGGNet网络模型的多标签分类网络的model.py代码实现:
- import torch
- import torch.nn as nn
- from torchvision import models
- class VGGNet(nn.Module):
- def __init__(self, num_classes):
- super(VGGNet, self).__init__()
- model = models.vgg16(pretrained=True)
- self.features = model.features
-
- # replace the last layer with a new fc layer
- in_features = model.classifier[6].in_features
- classifier = list(model.classifier.children())[:-1]
- classifier.extend([nn.Linear(in_features, num_classes)])
- self.classifier = nn.Sequential(*classifier)
-
- # sigmoid as activation function for multi-label classification
- self.sigmoid = nn.Sigmoid()
-
- def forward(self, x):
- x = self.features(x)
- x = x.view(x.size(0), -1)
- x = self.classifier(x)
- x = self.sigmoid(x)
- return x
复制代码
该代码定义了一个继承自nn.Module的神经网络类VGGNet。该类中包括了前向传播算法以及网络中所用的层。
具体来讲,VGGNet()中定义了模型并调用VGG-16模型进行了初始化。接着对模型VGG-16的最后一层进行更改,将其替换成一层输入特征维度为 in_features,输出特征维度为num_classes的全连接层。其中sigmoid激活函数用于多标签分类问题中控制每个标签是否有预测值存在。
最后,重写forward方法,使用已经修改过的VGG-16作为特征提取器,将图片features最终展开为1维向量,并且经过新的分类全连接层。最后使用sigmoid函数激活进行多标签分类预测输出num_classes个输出值,分别代表每个类别的预测概率。
以下是基于VGGNet网络模型的多标签分类网络的train.py代码实现:
- import torch
- import torch.nn as nn
- from torch.utils.data import DataLoader
- from torchvision.datasets import ImageFolder
- from torchvision import transforms
- from model import VGGNet
- import matplotlib.pyplot as plt
- # 随机种子
- torch.manual_seed(0)
- # 设置参数和数据路径
- lr = 0.01
- num_epochs = 20
- batch_size = 8
- data_path = 'path/to/dataset'
- def main():
- # 数据处理
- transform_train = transforms.Compose([transforms.RandomResizedCrop(224),
- transforms.RandomHorizontalFlip(),
- transforms.ToTensor()])
- train_dataset = ImageFolder(root=data_path + '/train/', transform=transform_train)
- train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
-
- transform_val = transforms.Compose([transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor()])
- val_dataset = ImageFolder(root=data_path + '/val/', transform=transform_val)
- val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False)
-
- # 初始化模型
- model = VGGNet(num_classes=len(train_dataset.classes))
-
- # define loss function and optimizer
- criterion = nn.BCELoss()
- optimizer = torch.optim.Adam(model.parameters(), lr=lr)
-
- # 训练模型
- train_losses = []
- val_losses = []
- for epoch in range(num_epochs):
- # 训练
- train_loss = 0.0
- model.train()
- for images, labels in train_loader:
- optimizer.zero_grad()
-
- # forward pass
- outputs = model(images)
- loss = criterion(outputs, labels.float())
-
- # backward pass and optimize
- loss.backward()
- optimizer.step()
-
- train_loss += loss.item() * images.size(0)
- train_loss = train_loss / len(train_loader.dataset)
- train_losses.append(train_loss)
-
- # validation
- val_loss = 0.0
- model.eval()
- with torch.no_grad():
- for images, labels in val_loader:
- outputs = model(images)
- loss = criterion(outputs, labels.float())
- val_loss += loss.item() * images.size(0)
- val_loss = val_loss / len(val_loader.dataset)
- val_losses.append(val_loss)
-
- print('Epoch [{}/{}], Train Loss: {:.4f}, Val Loss: {:.4f}'.
- format(epoch+1, num_epochs, train_loss, val_loss))
-
- # 保存训练数据
- torch.save(model.state_dict(), 'vgg16_multi_label_classification.pth')
-
- # visualize the training process
- plt.plot(range(num_epochs), train_losses, '-b', label='train')
- plt.plot(range(num_epochs), val_losses, '-r', label='validation')
- plt.legend(loc='lower right')
- plt.xlabel('epoch')
- plt.ylabel('loss')
- plt.show()
- if __name__ == '__main__':
- main()
复制代码 该代码首先进行了各种参数的定义(学习率、训练轮数、batch大小等)。根据这些预处理参数,通过torchvision.datasets.ImageFolder加载数据集,并进行数据增强操作。定义了一个存储“训练集”和一个存储“验证集”的变量train_dataset和val_dataset。
接下来初始化VGGNet模型,定义损失函数为BCELoss,并使用Adam算法作为优化器。
接着进入for循环,进行num_epochs轮训练,对训练集和验证集逐轮进行训练和验证。
其中,在训练过程中,每个batch的损失随机反向传播更新;在验证过程中,则使用了torch.no_grad()上下文管理器避免梯度计算。同时记录了训练数据与验证数据的损失,以便后期可视化输出。
最后保存训练好的模型,并将损失值的变化通过matplotlib库可视化输出展示出来。
以下是基于VGGNet网络模型的多标签分类网络的predict.py代码实现:
- import torch
- from torchvision import transforms
- from PIL import Image
- from model import VGGNet
- # 定义模型常量和路径
- num_classes = 6
- model_path = 'path/to/model'
- image_path = 'path/to/image'
- # 加载
- model = VGGNet(num_classes)
- model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
- model.eval()
- # 加载并处理图片
- img = Image.open(image_path).convert('RGB')
- transform = transforms.Compose([transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor()])
- img = transform(img)
- # make prediction
- with torch.no_grad():
- outputs = model(img.unsqueeze(0))
- _, predicted = torch.topk(outputs.data, k=num_classes)
-
- # map predicted label index to class name
- class_names = ['LABEL_0', 'LABEL_1', 'LABEL_2', 'LABEL_3', 'LABEL_4', 'LABEL_5']
- predicted_labels = [class_names[idx] for idx in predicted.cpu()[0]]
- # 打印结果
- print(f"Predicted labels: {predicted_labels}")
- # visualize the prediction results, add predicted classes' names and probabilities on the image
- import numpy as np
- import cv2
- import matplotlib.pyplot as plt
- outputs_np = outputs.data.numpy()[0]
- output_idx = np.argsort(-outputs_np)
- for i in range(num_classes):
- label = class_names[output_idx[i]]
- proba = outputs_np[output_idx[i]]
- text = f"{label} ({proba:.2f})"
- if proba > 0.5:
- color = (0, 255, 0) # 如果概率>0.5,绿色, which means a strong confident prediction
- else:
- color = (0, 0, 255) # 如果概率<=0.5,红色
- cv2.putText(img=np.array(img.permute((1, 2, 0)))[:, :, ::-1],
- text=text,
- org=(10, (i+1)*30),
- fontFace=cv2.FONT_HERSHEY_SIMPLEX,
- fontScale=0.7,
- color=color,
- thickness=2)
- plt.imshow(np.array(img.permute((1, 2, 0))))
- plt.show()
复制代码
该代码首先根据预定义的超参数(num_classes、model_path、image_path)载入训练好的模型文件,并加载待分类预测图片路径。
接着对图片进行数据增强操作并传给模型,通过topk函数输出前k个预测的标签。其中class_names为每个label的名称或者代号等等。
接下来在图像上添加文本框框和类别概率值,将预测结果可视化并可以通过matplotlib库进行展示。
最后输出预测的结果到控制台。
以下是基于VGGNet网络模型的多标签分类网络的evaluate.py代码实现:
- import torch
- import torch.nn as nn
- from torch.utils.data import DataLoader
- from torchvision.datasets import ImageFolder
- from torchvision import transforms
- from tqdm import tqdm
- from model import VGGNet
- # 常量和路径
- num_classes = 6
- model_path = 'path/to/model'
- test_path = 'path/to/testdataset'
- # 数据
- transform_test = transforms.Compose([transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor()])
- test_dataset = ImageFolder(test_path,transform=transform_test)
- test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)
- # 模型
- model = VGGNet(num_classes)
- model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
- model.eval()
- # define loss function
- criterion = nn.BCELoss()
- # 处理模型
- hamming_loss = []
- accuracy = []
- precision = []
- recall = []
- f_beta = []
- with torch.no_grad():
- for idx, (images, labels) in tqdm(enumerate(test_loader), total=len(test_loader)):
- outputs = model(images)
- predicted = (outputs > 0.5).float() # threshold = 0.5
-
- hamming_loss.append((predicted != labels.float()).sum().item()/(num_classes*len(labels)))
-
- tp = ((predicted == 1) & (labels.float() == 1)).sum().item()
- fp = ((predicted == 1) & (labels.float() == 0)).sum().item()
- tn = ((predicted == 0) & (labels.float() == 0)).sum().item()
- fn = ((predicted == 0) & (labels.float() == 1)).sum().item()
-
- accuracy.append((tp + tn) / (num_classes*len(labels)))
- precision.append(tp / (tp + fp + 1e-7))
- recall.append(tp / (tp + fn + 1e-7))
- f_beta.append(((1+0.5**2)*precision[-1]*recall[-1])/((0.5**2)*precision[-1]+recall[-1]+1e-7))
- print("Hamming Loss: {:.4f}".format(sum(hamming_loss)/len(hamming_loss)))
- print("Accuracy: {:.4f}".format(sum(accuracy)/len(accuracy)))
- print("Precision: {:.4f}".format(sum(precision)/len(precision)))
- print("Recall: {:.4f}".format(sum(recall)/len(recall)))
- print("F-beta Score: {:.4f}".format(sum(f_beta)/len(f_beta)))
复制代码
该代码首先加载测试集数据,并导入之前已经定义好的模型。同时定义loss function和评价指标。
然后进入预测阶段,在循环中预测每个测试图片中所有label值的预测结果,然后与真实label进行比较,计算Hamming Loss、Accuracy、Precision、Recall、F-beta等评价指标。(因为是多标签分类任务,所以需要对每个标签分别计算评价指标)。在求得预测结果后可视化评价指标的平均值展示。
最终输出评估结果到控制台。需要注意的是,在这里设定了一个阈值为0.5的二分类器,任何大于0.5过滤即赋值为1,小于等于0.5的赋值为零。
由于数据集制作的具体过程与具体场景有关系,因此我这里提供一些常用的操作和相应的代码示例供参考:
1. 将文件按照标签分类存放在不同的文件夹下,建立“train”和“val”两个子文件夹。
- import os
- import random
- from shutil import copy2
- def split_data(src_dir, train_ratio=0.8):
- train_dir = "path/to/train"
- val_dir = "path/to/val"
- if not os.path.exists(train_dir):
- os.makedirs(train_dir)
- if not os.path.exists(val_dir):
- os.makedirs(val_dir)
- for class_name in os.listdir(src_dir):
- class_path = os.path.join(src_dir, class_name)
- train_class_path = os.path.join(train_dir, class_name)
- val_class_path = os.path.join(val_dir, class_name)
- if not os.path.exists(train_class_path):
- os.makedirs(train_class_path)
- if not os.path.exists(val_class_path):
- os.makedirs(val_class_path)
- file_list = os.listdir(class_path)
- random.shuffle(file_list)
- train_files = file_list[:int(len(file_list) * train_ratio)]
- val_files = file_list[int(len(file_list) * train_ratio):]
- for file in train_files:
- src_file = os.path.join(class_path, file)
- dst_file = os.path.join(train_class_path, file)
- copy2(src_file, dst_file)
- for file in val_files:
- src_file = os.path.join(class_path, file)
- dst_file = os.path.join(val_class_path, file)
- copy2(src_file, dst_file)
复制代码
该代码首先定义了一个split_data函数,该函数实现对原始数据的切分操作,并将其按照类别存放在train和val文件夹下。其中src_dir 是原始数据集所在的路径,train_ratio表示训练集比例。
2. 对图像进行预处理,如裁剪、调整大小等。
- from PIL import Image
- def preprocess_image(image_path, output_size):
- image = Image.open(image_path)
- image = image.resize(output_size, resample=Image.BILINEAR)
- return image
复制代码
该代码定义了一个preprocess_image函数,该函数读取指定路径的图像并进行resize操作。output_size为调整后图片的尺寸。可以根据 需要添加更多的预处理步骤。
3. 数据增强,包括随机裁剪、水平翻转、添加噪声等。
- import torchvision.transforms as transforms
- transform_train = transforms.Compose([
- transforms.RandomResizedCrop(224),
- transforms.RandomHorizontalFlip(),
- transforms.ColorJitter(
- brightness=0.4,
- contrast=0.4,
- saturation=0.4,
- hue=0.1
- ),
- transforms.ToTensor(),
- ])
- transform_val = transforms.Compose([
- transforms.Resize(size=(224, 224)),
- transforms.ToTensor(),
- ])
复制代码
该代码定义了两个预处理变换(transform),transform_train用于训练集,transform_val用于测试集/验证集。transform_train具有RandomResizedCrop、RandomHorizontalFlip、ColorJitter等增强功能,transform_val仅进行简单的resize和toTensor。
4. 加载数据集。
- from torchvision.datasets import ImageFolder
- train_dataset = ImageFolder(root='path/to/train', transform=transform_train)
- val_dataset = ImageFolder(root='path/to/val', transform=transform_val)
- train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
- val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers)
复制代码
该代码使用ImageFolder类加载数据。注意到这里的root为划分训练集和验证机后得到的train和val文件夹路径。 |
|