原理内容

为改善一项机器学习或深度学习的任务,首先想到的是从模型、数据、优化器等方面进行优化,使用方法比较方便。不过有时尽管如此,但效果还不是很理想,此时,我们可尝试一下其他方法,如模型集成、迁移学习、数据增强等优化方法。本文我们将介绍利用模型集成来提升任务的性能。

模型集成是提升分类器或预测系统效果的重要方法,目前在机器学习、深度学习国际比赛中时常能看到利用模型集成取得佳绩的事例。其在生产环境也是人们经常使用的方法。模型集成的原理比较简单,有点像多个盲人摸象,每个盲人只能摸到大象的一部分,但综合每人摸到的部分,就能形成一个比较完整、符合实际的图像。每个盲人就像单个模型,那如果集成这些模型犹如综合这些盲人各自摸到的部分,就能得到一个强于单个模型的模型。实际上模型集成也和我们通常说的集思广益、投票选举领导人等原理差不多,是1+1>2的有效方法。

当然,要是模型集成发挥效应,模型的多样性也是非常重要的,使用不同架构、甚至不同的学习方法是模型多样性的重要体现。如果只是改一下初始条件或调整几个参数,有时效果可能还不如单个模型。
具体使用时,除了要考虑各模型的差异性,还要考虑模型的性能。如果各模型性能差不多,可以取各模型预测结果的平均值;如果模型性能相差较大,模型集成后的性能可能还不及单个模型,相差较大时,可以采用加权平均的方法,其中权重可以采用SLSQP、Nelder-Mead、Powell、CG、BFGS等优化算法获取。
接下来,通过使用PyTorch来具体实现一个模型集成的实例,希望通过这个实例,使读者对模型集成有更进一步的理解。

代码

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.backends.cudnn as cudnn
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from collections import Counter
from  torchsummary import summary

import  os
from tqdm import tqdm

#定义一些超参数
BATCHSIZE=15 # batchsize必须是testdata的整数倍
EPOCHES=10
LR=0.001

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# -------------------------------------------------------------------------------------------
#	模型定义
# -------------------------------------------------------------------------------------------
# mobilenet-v2模型
def mbnet():
	model = models.mobilenet_v2(pretrained=True)
	# 前面的参数保持不变
	for param in model.parameters():
		param.requires_grad = False

	fc = nn.Sequential(
		nn.Dropout(0.2),
		nn.Linear(1280, 3),
	)
	model.classifier = fc
	model = model.to(device)
	# print("[INFO] Model Layer:  ", summary(model, (3, 224, 224)))
	return model

# mnasnet模型
def mnasnet():
	model = models.MNASNet(alpha=1)
	# 前面的backbone保持不变
	for param in model.parameters():
		param.requires_grad = False

	fc = nn.Sequential(
		nn.Dropout(0.2),
		nn.Linear(1280, 3),
	)
	model.classifier = fc
	model = model.to(device)
	# print("[INFO] Model Layer:  ", summary(model, (3, 224, 224)))
	return model

#  resnet 18模型
def resnet18(fc_num=256, class_num=3):
	model = models.resnet18(pretrained=True)
	# 前面的backbone保持不变
	for param in model.parameters():
		param.requires_grad = True

	# 只是修改输出fc层,新加层是trainable
	fc_inputs = model.fc.in_features
	model.fc = nn.Sequential(
		nn.Linear(fc_inputs, fc_num),
		nn.ReLU(),
		nn.Dropout(0.4),
		nn.Linear(fc_num, class_num)
	)

	model = model.to(device)
	# print("[INFO] Model Layer:  ", summary(model, (3, 224, 224)))
	return model

#  resnet 152模型
def resnet152(fc_num=256, class_num=3, train_all =False):
	model = models.resnet152(pretrained=True)
	# 前面的backbone保持不变
	for param in model.parameters():
		param.requires_grad = False

	# 只是修改输出fc层,新加层是trainable
	fc_inputs = model.fc.in_features
	model.fc = nn.Sequential(
		nn.Linear(fc_inputs, fc_num),
		nn.ReLU(),
		nn.Dropout(0.4),
		nn.Linear(fc_num, class_num)
	)
	#  修改所有参数层
	if train_all:
		for param in model.parameters():
			param.requires_grad = True
		torch.load("./models/best_loss.pt")
	model = model.to(device)
	# print("[INFO] Model Layer:  ", summary(model, (3, 224, 224)))
	return model

# -------------------------------------------------------------------------------------------
#	数据加载
# -------------------------------------------------------------------------------------------
def data_process(batch_size=BATCHSIZE, dataset='./data'):
	image_transforms = {
		'train': transforms.Compose([
			transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)),
			transforms.RandomRotation(degrees=15),
			transforms.RandomHorizontalFlip(),
			transforms.CenterCrop(size=224),
			transforms.ToTensor(),
			transforms.Normalize([0.485, 0.456, 0.406],
								 [0.229, 0.224, 0.225])
		]),
		'valid': transforms.Compose([
			transforms.Resize(size=256),
			transforms.CenterCrop(size=224),
			transforms.ToTensor(),
			transforms.Normalize([0.485, 0.456, 0.406],
								 [0.229, 0.224, 0.225])
		])
	}

	train_directory = os.path.join(dataset, 'train')
	valid_directory = os.path.join(dataset, 'valid')

	data = {
		'train': datasets.ImageFolder(root=train_directory, transform=image_transforms['train']),
		'valid': datasets.ImageFolder(root=valid_directory, transform=image_transforms['valid'])
	}

	train_data_size = len(data['train'])
	valid_data_size = len(data['valid'])

	train_data = DataLoader(data['train'], batch_size=batch_size, shuffle=True)
	valid_data = DataLoader(data['valid'], batch_size=batch_size, shuffle=True)

	print("[INFO] Train data / Test data number: ", train_data_size, valid_data_size)
	return train_data, valid_data, train_data_size, valid_data_size


# -------------------------------------------------------------------------------------------
#	集成表决
# -------------------------------------------------------------------------------------------
def process(mlps, trainloader, testloader, valid_data_size):
	optimizer = torch.optim.Adam([{"params": mlp.parameters()} for mlp in mlps], lr=LR)
	loss_function = nn.CrossEntropyLoss()

	for ep in range(EPOCHES):
		print("Epoch: {}/{}".format(ep + 1, EPOCHES))
		print("[INFO] Begin to train")
		for img, label in tqdm(trainloader):
			img, label = img.to(device), label.to(device)
			optimizer.zero_grad()  # 10个网络清除梯度
			for mlp in mlps:
				mlp.train()
				out = mlp(img)
				loss = loss_function(out, label)
				loss.backward()  # 网络们获得梯度
			optimizer.step()

		pre = []
		vote_correct = 0
		mlps_correct = [0 for i in range(len(mlps))]

		print("[INFO] Begin to valid")
		for img, label in tqdm(testloader):
			img, label = img.to(device), label.to(device)
			for i, mlp in enumerate(mlps):
				mlp.eval()
				out = mlp(img)

				_, prediction = torch.max(out, 1)  # 按行取最大值
				pre_num = prediction.cpu().numpy()
				mlps_correct[i] += (pre_num == label.cpu().numpy()).sum()

				pre.append(pre_num)
			arr = np.array(pre)
			pre.clear()
			result = [Counter(arr[:, i]).most_common(1)[0][0] for i in range(BATCHSIZE)]
			vote_correct += (result == label.cpu().numpy()).sum()
		print("epoch:" + str(ep) + "集成模型的正确率" + str(vote_correct / valid_data_size))

		for idx, coreect in enumerate(mlps_correct):
			print("模型" + str(idx) + "的正确率为:" + str(coreect / valid_data_size))

if __name__ == '__main__':
	mlps = [mbnet(),  resnet152(),  mnasnet()]
	train_data, valid_data, train_data_size, valid_data_size = data_process()
	process(mlps=mlps, trainloader=train_data , testloader=valid_data, valid_data_size=valid_data_size)

效果:

pytorch 一个模型两个分类分支 pytorch多模型融合_深度学习


模型准确性整体上还是有了较大提升。