项目参考
文章目录
- 数据集预处理
- 样本分辨率分布不均
- 删除不符合要求的图片
- 数据增强
- 数据更均衡(下采样)
- 图像颜色进行归一化
- 生成数据集与数据加载器
- 生成数据集
- 生成数据加载器
- 模型搭建
- 使用ResNet18进行训练
- 模型测试
- 模型调优
- 5.1迁移学习
- 画loss曲线
- 5.2使用恰当学习率
- 5.3数据增强
- 模型验证
数据集预处理
样本分辨率分布不均
查看数据分布情况
图像宽高分布:
数据分布图:
'''
数据样本分析
画出数据量条形图
画出图像分辨率散点图
'''
import os
import PIL.Image as Image
import matplotlib.pyplot as plt
import numpy as np
def plot_resolution(dataset_root_path):
img_size_list = [] # 承接所有图片的长宽数据
# 获取根目录下所有路径
for root, dirs, files in os.walk(dataset_root_path): # 利用os.walk访问文件、文件夹
print(f"root={root}")
print(f"files={files}")
for file_i in files:
# print(f"file_i={file_i}")
file_i_full_path = os.path.join(root, file_i)
img_i = Image.open(file_i_full_path)
img_i_size = img_i.size # 获取单张图像的长宽
img_size_list.append(img_i_size)
print("1---------")
print(img_size_list)
print("2---------")
width_list = [img_size_list[i][0] for i in range(len(img_size_list))]
height_list = [img_size_list[i][1] for i in range(len(img_size_list))]
# print(width_list) # 640
# print(height_list) # 346
plt.rcParams["font.sans-serif"] = ["SimHei"] # 设置中文字体
plt.rcParams["font.size"] = 8
plt.rcParams["axes.unicode_minus"] = False # 该语句解决图像中的“-”负号的乱码问题
# 散点图
plt.scatter(width_list, height_list, s=1)
plt.xlabel("宽")
plt.ylabel("高")
plt.title("图像宽高分布")
plt.show()
# 画出条形图
def plot_bar(dataset_root_path):
file_name_list = []
file_num_list = []
# os.walk 是一个用于遍历目录树的函数,返回一个生成器(generator),它产生目录中每个文件的三元组:(dirpath, dirnames, filenames)。
for root, dirs, files in os.walk(dataset_root_path):
if len(dirs) != 0:
for dir_i in dirs:
file_name_list.append(dir_i)
file_num_list.append(len(files))
# 去掉根目录下文件夹的统计
file_num_list = file_num_list[1:]
# 求均值,并把均值以横线形式显示出来
mean = np.mean(file_num_list)
print("mean = ", mean)
# 柱间的距离(x轴)
bar_positions = np.arange(len(file_name_list))
print(f"bar_positions={bar_positions}")
fig, ax = plt.subplots() # 定义画的区间和子画
ax.bar(bar_positions, file_num_list, 0.5) # 画柱图,参数:柱间的距离,柱的值,柱的宽度
ax.plot(bar_positions, [mean for i in bar_positions], color="red") # 显示平均值
plt.rcParams["font.sans-serif"] = ["SimHei"] # 设置中文字体
plt.rcParams["font.size"] = 8
plt.rcParams["axes.unicode_minus"] = False # 该语句解决图像中的“-”负号的乱码问题
ax.set_xticks(bar_positions) # 设置x轴的刻度
ax.set_xticklabels(file_name_list, rotation=90) # 设置x轴的标签,旋转90度(横着放)
ax.set_ylabel("类别数量")
ax.set_title("数据分布图")
plt.show()
if __name__ == '__main__':
dataset_root_path = r"../dataset"
plot_resolution(dataset_root_path)
plot_bar(dataset_root_path)
删除不符合要求的图片
需要排除图像宽高过大和过小的数据(图像高宽均保持在200到2000以内)
需要排除图像宽高比例不协调的数据(图像宽高比例低于0.5的数据)
from PIL import Image
import os
dataset_root_path = "../dataset"
min = 200 # 短边
max = 2000 # 长边
ratio = 0.5 # 短边 / 长边
delete_list = [] # 承接所有图片的长宽数据
for root, dirs, files in os.walk(dataset_root_path):
for file_i in files:
file_i_full_path = os.path.join(root, file_i)
img_i = Image.open(file_i_full_path)
img_i_size = img_i.size # 获取单张图像的长宽
# 删除单边过短的图片
if img_i_size[0] < min or img_i_size[1] < min:
print(file_i_full_path, " 不满足要求")
delete_list.append(file_i_full_path)
# 删除单边过长的图片
if img_i_size[0] > max or img_i_size[1] > max:
print(file_i_full_path, " 不满足要求")
delete_list.append(file_i_full_path)
# 删除宽高比例不当的图片
long = img_i_size[0] if img_i_size[0] > img_i_size[1] else img_i_size[1]
short = img_i_size[0] if img_i_size[0] < img_i_size[1] else img_i_size[1]
if short / long < ratio:
print(file_i_full_path, " 不满足要求", img_i_size[0], img_i_size[1])
delete_list.append(file_i_full_path)
print(delete_list)
for file_i in delete_list:
# 即便出错程序也会继续执行
try:
print("正在删除", file_i)
os.remove(file_i)
except:
pass
数据增强
类别和类别之间的数据不均衡,利用数据增强来加强数据
对样本数少于平均数的进行数据增强,样本数多于平均数的不进行处理
增强方式:水平翻转,垂直翻转
'''
使用翻转进行数据增强
'''
import os
import cv2
import numpy as np
# 水平翻转
def Horizontal(image):
return cv2.flip(image, 1, dst=None) # 水平镜像
# 垂直翻转
def Vertical(image):
return cv2.flip(image, 0, dst=None) # 垂直镜像
if __name__ == '__main__':
from_root = r"../dataset"
save_root = r"../enhance_dataset"
threshold = 200
for a, b, c in os.walk(from_root):
for file_i in c:
file_i_path = os.path.join(a, file_i) # 获取每个图片的完整路径
# 获取文件所在的子目录名字
# 将 file_i_path 分割成两部分
# 其中 split[0] 是文件路径的目录部分,split[1] 是文件名部分
"""
file_i_path=../dataset\厨余垃圾_生肉\img_澳洲牛肉_203.jpeg
split=('../dataset\\厨余垃圾_生肉', 'img_澳洲牛肉_203.jpeg')
dir_loc=厨余垃圾_生肉
下面三行代码的含义就是获取地址/a/b/c中的b
file_i_path=../dataset\厨余垃圾_生肉\img_澳洲牛肉_203.jpeg
save_path=../enhance_dataset\厨余垃圾_生肉
"""
# print(f"file_i_path={file_i_path}")
split = os.path.split(file_i_path)
# print(f"split={split}")
dir_loc = os.path.split(split[0])[1]
# print(f"dir_loc={dir_loc}")
save_path = os.path.join(save_root, dir_loc)
print(file_i_path)
print(save_path)
# 判断保存路径是否存在
if os.path.isdir(save_path) == False:
os.makedirs(save_path)
# 读取图片
# 参数 -1 表示返回原始图像,不进行任何修改或压缩
img_i = cv2.imdecode(np.fromfile(file_i_path, dtype=np.uint8), -1)
# 保存原始图像
# 为什么不用imread,因为路径包含中文名,用的话会报错
# file_i[:-5]是去掉.jpeg这个后缀
cv2.imencode('.jpg', img_i)[1].tofile(os.path.join(save_path, file_i[:-5] + "_original.jpg")) # 保存图片
# 对于数据量不足阈值的
if len(c) < threshold:
img_horizontal = Horizontal(img_i)
# cv2.imencode返回值是一个包含两个元素的元组
# 其中第一个元素是一个布尔值,表示编码是否成功,第二个元素是包含编码后图像数据的 NumPy 数组。
cv2.imencode('.jpg', img_horizontal)[1].tofile(
os.path.join(save_path, file_i[:-5] + "_horizontal.jpg")) # 保存图片
img_vertical = Vertical(img_i)
cv2.imencode('.jpg', img_vertical)[1].tofile(
os.path.join(save_path, file_i[:-5] + "_vertical.jpg")) # 保存图片
else:
pass
数据更均衡(下采样)
为了让样本数更均衡,对数据量过多的数据进行下采样
import os
import random
img_root = r"../enhance_dataset"
threshold = 300
for a, b, c in os.walk(img_root):
# 将数据量大于300张之后的全部删除
if len(c) > threshold:
delete_list = []
for file_i in c:
file_i_full_path = os.path.join(a, file_i)
delete_list.append(file_i_full_path)
# 将数据全部打乱,保证删除更随机
random.shuffle(delete_list)
print(delete_list)
delete_list = delete_list[threshold:]
for file_delete_i in delete_list:
os.remove(file_delete_i)
print("将会删除", file_delete_i)
处理后数据分布情况:
请添加图片描述
图像颜色进行归一化
from torchvision.datasets import ImageFolder
import torch
from torchvision import transforms as T
from tqdm import tqdm
# 对每张图片进行转化处理
transform = T.Compose([
T.RandomResizedCrop(224), # 将图片尺寸压缩为224
T.ToTensor(), # 可以被torch处理
])
def getStat(train_data):
train_loader = torch.utils.data.DataLoader(
train_data, batch_size=1, shuffle=False, num_workers=0, pin_memory=True)
mean = torch.zeros(3)
std = torch.zeros(3)
# tqdm 是一个 Python 的进度条库,用于在循环中显示进度条
for X, _ in tqdm(train_loader):
for d in range(3):
# 分别读取RGB三通道的平均值,其他NHW不变
mean[d] += X[:, d, :, :].mean() # N, C, H ,W
std[d] += X[:, d, :, :].std()
mean.div_(len(train_data))
std.div_(len(train_data))
return list(mean.numpy()), list(std.numpy())
if __name__ == '__main__':
train_dataset = ImageFolder(root=r'../enhance_dataset', transform=transform)
print(getStat(train_dataset))
生成数据集与数据加载器
生成数据集
在txt中,左侧为图片路径,右侧为标签(从0开始计算)
import os
import random
train_ratio = 0.9
test_ratio = 1 - train_ratio
rootdata = r"../enhance_dataset"
train_list, test_list = [], []
class_flag = -1
for a, b, c in os.walk(rootdata):
for i in range(0, int(len(c) * train_ratio)):
# class_flag为标签,为每个文件夹都编了号
# 其他垃圾_一次性杯 0
train_data = os.path.join(a, c[i]) + '\t' + str(class_flag) + '\n'
train_list.append(train_data)
for i in range(int(len(c) * train_ratio), len(c)):
test_data = os.path.join(a, c[i]) + '\t' + str(class_flag) + '\n'
test_list.append(test_data)
class_flag += 1
# 数据打乱
random.shuffle(train_list)
random.shuffle(test_list)
with open('train.txt', 'w', encoding='UTF-8') as f:
for train_img in train_list:
f.write(str(train_img))
with open('test.txt', 'w', encoding='UTF-8') as f:
for test_img in test_list:
f.write(test_img)
train.txt
生成数据加载器
batch= 10,channel =3,size = 512 * 512
label等于10个数字
'''
2.2.2 生成数据加载器
'''
import torch
from PIL import Image
import torchvision.transforms as transforms
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
from torch.utils.data import Dataset
# 数据归一化与标准化
transform_BZ = transforms.Normalize(
mean=[0.46402064, 0.45047238, 0.37801373], # 取决于数据集
std=[0.2007732, 0.196271, 0.19854763]
# ([0.6421935, 0.57428855, 0.5088307], [0.21137896, 0.21977238, 0.22956973])
)
class LoadData(Dataset):
def __init__(self, txt_path, train_flag=True):
self.imgs_info = self.get_images(txt_path)
self.train_flag = train_flag
self.img_size = 512
self.train_tf = transforms.Compose([
transforms.Resize(self.img_size),
transforms.RandomHorizontalFlip(), # 对图片进行随机的水平翻转
transforms.RandomVerticalFlip(), # 随机的垂直翻转
transforms.ToTensor(), # 把图片改为Tensor格式
transform_BZ # 图片标准化的步骤
])
self.val_tf = transforms.Compose([ # 简单把图片压缩了变成Tensor模式
transforms.Resize(self.img_size),
transforms.ToTensor(),
transform_BZ # 标准化操作
])
def get_images(self, txt_path):
with open(txt_path, 'r', encoding='utf-8') as f:
imgs_info = f.readlines()
# 划分为左边是图片路径,右侧为标签
imgs_info = list(map(lambda x: x.strip().split('\t'), imgs_info))
return imgs_info # 返回图片信息
def padding_black(self, img): # 如果尺寸太小可以扩充
w, h = img.size
scale = self.img_size / max(w, h)
img_fg = img.resize([int(x) for x in [w * scale, h * scale]])
size_fg = img_fg.size
size_bg = self.img_size
img_bg = Image.new("RGB", (size_bg, size_bg))
img_bg.paste(img_fg, ((size_bg - size_fg[0]) // 2,
(size_bg - size_fg[1]) // 2))
img = img_bg
return img
def __getitem__(self, index): # 重载__getitem__,返回真正想返回的东西
img_path, label = self.imgs_info[index]
img = Image.open(img_path) # 打开图片
img = img.convert('RGB') # 转换为RGB 格式
img = self.padding_black(img)
if self.train_flag:
img = self.train_tf(img)
else:
img = self.val_tf(img)
label = int(label)
return img, label
def __len__(self):
return len(self.imgs_info)
def WriteData(fname, *args):
with open(fname, 'a+') as f:
for data in args:
f.write(str(data) + "\t")
f.write("\n")
if __name__ == "__main__":
train_dataset = LoadData("train.txt", True)
print("数据个数:", len(train_dataset))
# 一次处理10个图片
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=10,
shuffle=True)
for image, label in train_loader:
print("image.shape = ", image.shape)
# print("image = ",image)
print("label = ", label)
模型搭建
使用ResNet18进行训练
步骤:搭建模型->搭建训练函数->搭建验证函数->搭建数据加载器->损失函数与优化器->开始训练
from torchvision.models import resnet18
model = resnet18(num_classes=55) # 样本一共有55个分类
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData, WriteData
from torchvision.models import resnet18
# 训练函数
def train(dataloader, model, loss_fn, optimizer, device):
size = len(dataloader.dataset)
avg_loss = 0
# 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。、
# enumerate枚举
for batch, (X, y) in enumerate(dataloader): # 固定格式:batch:第几批数据,不是批次大小,(X,y):数值用括号
# 将数据存到显卡
X, y = X.to(device), y.to(device)
# 得到预测的结果pred
pred = model(X)
loss = loss_fn(pred, y)
avg_loss += loss
# 反向传播,更新模型参数
optimizer.zero_grad() # 清零之前的梯度,以防止梯度累积。
loss.backward() # 反向传播计算梯度
optimizer.step() # 根据梯度更新模型参数
# 每训练10次,输出一次当前信息
if batch % 10 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
# 当一个epoch完了后返回平均 loss
avg_loss /= size
# 用于将 PyTorch 张量(avg_loss)的值转换为 NumPy 数组,并将其从计算图中分离(detach,不带有梯度),最后将其从 GPU 移动到 CPU。
avg_loss = avg_loss.detach().cpu().numpy()
return avg_loss
# 验证函数(测试函数)
def validate(dataloader, model, loss_fn, device):
size = len(dataloader.dataset)
# 将模型转为验证模式
# 因为不需要将前向传播输出结果和真实值比较后进行反向传播,不需要方向传播
# model.eval() 会关闭 BatchNorm 和 Dropout 的随机性。
model.eval()
# 初始化test_loss 和 correct, 用来统计每次的误差
test_loss, correct = 0, 0
# 测试时模型参数不用更新,所以no_gard()
# 防止梯度的计算和参数的更新。评估模式主要用于不需要计算梯度的情况,提高推断速度。
# 非训练, 推理期用到
with torch.no_grad():
# 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
for X, y in dataloader:
# 将数据转到GPU
X, y = X.to(device), y.to(device)
# 将图片传入到模型当中就,得到预测的值pred
pred = model(X)
# 计算预测值pred和真实值y的差距
test_loss += loss_fn(pred, y).item()
# 统计预测正确的个数(针对分类)
# argmax(1) 将返回每行最大值的索引,也就是在第一个维度上取最大值的位置。
"""
pred.argmax(1): 获取每个样本预测的类别索引。
(pred.argmax(1) == y): 对比预测的类别索引与实际标签 y 是否相等,生成一个布尔值的张量,其中相等的位置为 True,不相等的位置为 False。
.type(torch.float): 将布尔值的张量转换为浮点数张量,其中 True 被转换为 1.0,False 被转换为 0.0。
.sum(): 对所有元素求和,得到正确预测的样本数量。
.item(): 将张量中的单个元素的值提取为 Python 的标量值。
correct += ...: 将当前批次(或迭代)的正确预测数量累加到 correct 变量中,用于最后计算总体准确率。
这样,correct 变量最终包含整个数据集上的正确预测数量。
"""
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= size
correct /= size
print(f"correct = {correct}, Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
return correct, test_loss
if __name__ == '__main__':
# 构建数据加载器
batch_size = 128
# # 给训练集和测试集分别创建一个数据集加载器
train_data = LoadData("train.txt", True)
valid_data = LoadData("test.txt", False)
# 当pin_memory=True时,数据将被复制到 CUDA 固定内存中,这样在数据传输到 GPU 时速度更快,但会占用额外的系统内存
# num_workers=4:开启4个线程读取数据
train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size,
shuffle=True)
valid_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)
# 如果显卡可用,则用显卡进行训练
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
model = resnet18(num_classes=55) # 共55个分类
model = model.to(device)
# 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
# 定义优化器,用来训练时候优化模型参数,随机梯度下降法
learning_rate = 1e-3
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
epochs = 50
loss_ = 10
save_root = "output/"
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
time_start = time.time()
avg_loss = train(train_dataloader, model, loss_fn, optimizer, device)
time_end = time.time()
print(f"train time: {(time_end - time_start)}")
# (dataloader, model, loss_fn, device)jif
val_accuracy, val_loss = validate(valid_dataloader, model, loss_fn, device)
# 写入数据
WriteData(save_root + "resnet18_no_pretrain.txt",
"epoch", t,
"train_loss", avg_loss,
"val_loss", val_loss,
"val_accuracy", val_accuracy)
if t % 5 == 0:
torch.save(model.state_dict(),
save_root + "resnet18_no_pretrain_epoch" + str(t) + "_loss_" + str(avg_loss) + ".pth")
torch.save(model.state_dict(), save_root + "resnet18_no_pretrain_last.pth")
if avg_loss < loss_:
loss_ = avg_loss
torch.save(model.state_dict(), save_root + "resnet18_no_pretrain_best.pth")
模型测试
'''
单图测试
'''
import torch
from torchvision.models import resnet18
from PIL import Image
import torchvision.transforms as transforms
import os
transform_BZ = transforms.Normalize(
mean=[0.46402064, 0.45047238, 0.37801373], # 取决于数据集
std=[0.2007732, 0.196271, 0.19854763]
)
def padding_black(img, img_size=512): # 如果尺寸太小可以扩充
w, h = img.size
scale = img_size / max(w, h)
img_fg = img.resize([int(x) for x in [w * scale, h * scale]])
size_fg = img_fg.size
size_bg = img_size
img_bg = Image.new("RGB", (size_bg, size_bg))
img_bg.paste(img_fg, ((size_bg - size_fg[0]) // 2,
(size_bg - size_fg[1]) // 2))
img = img_bg
return img
if __name__ == '__main__':
img_path = r'enhance_dataset/可回收物_鼠标/img_鼠标_1_original.jpg'
val_tf = transforms.Compose([ # 简单把图片压缩了变成Tensor模式
transforms.Resize(512),
transforms.ToTensor(),
transform_BZ # 标准化操作
])
# 如果显卡可用,则用显卡进行训练
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
finetune_net = resnet18(num_classes=55).to(device)
state_dict = torch.load(r"resnet18_no_pretrain_best.pth")
# print("state_dict = ",state_dict)
finetune_net.load_state_dict(state_dict)
finetune_net.eval()
with torch.no_grad():
# finetune_net.to(device)
img = Image.open(img_path) # 打开图片
img = img.convert('RGB') # 转换为RGB 格式
img = padding_black(img)
img = val_tf(img)
# C,H,W(本身) ->N,C,H,W,
img_tensor = torch.unsqueeze(img, 0) # 在0的位置进行扩张
img_tensor = img_tensor.to(device)
result = finetune_net(img_tensor)
# print("result = ",result.argmax(1))
id = result.argmax(1).item()
file_list = []
for a, b, c in os.walk("dataset"):
if len(b) != 0:
file_list = b
print("预测结果为:", file_list[id])
模型调优
5.1迁移学习
pretrained=True 使用预训练好的权重
# 第一次运行时从网上下载resnet18的模型,然后会把这个模型保存在电脑c盘中
finetune_net = resnet18(pretrained=True)
# 把fc层替换成55个输出,原本输出是1000
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 55)
# 使用 Xavier 初始化方法对新的全连接层的权重进行初始化。
nn.init.xavier_normal_(finetune_net.fc.weight)
# 最后一层以外的所有参数
parms_1x = [value for name, value in finetune_net.named_parameters()
if name not in ["fc.weight", "fc.bias"]]
# 最后一层10倍学习率
parms_10x = [value for name, value in finetune_net.named_parameters()
if name in ["fc.weight", "fc.bias"]]
finetune_net = finetune_net.to(device)
# 定义损失函数,计算相差多少,交叉熵,
loss_fn = nn.CrossEntropyLoss()
# 定义优化器,用来训练时候优化模型参数,随机梯度下降法
learning_rate = 1e-4
# 分层学习率
optimizer = torch.optim.Adam([# 控制模型在不同层用不同学习率
{
'params': parms_1x # 学习率1e-4
},
{
'params': parms_10x,
'lr': learning_rate * 10 # 学习率为1e-3
}], lr=learning_rate)
finetune_net = resnet18(pretrained=True):加载预训练的 ResNet-18 模型。pretrained=True 表示使用在 ImageNet 数据集上预训练的权重。
在 PyTorch 中,
pretrained=True
表示加载一个在大规模数据集上预训练过的模型权重。通常,这种预训练是在像ImageNet 这样的庞大数据集上完成的,该数据集包含大量的图像和类别,例如物体、动物等。
使用pretrained=True
的效果是,模型的初始权重将被设置为在预训练数据集上学到的特征。这对于迁移学习非常有用,因为预训练的模型已经学到了通用的图像特征,这些特征可以在新的任务上进行微调或直接使用。
在上述代码中,resnet18(pretrained=True)
会加载一个在 ImageNet 数据集上训练过的 ResNet-18
模型,其权重已经包含了对各种图像特征的学习。这可以提高模型在新任务上的性能,尤其是当新任务的数据集相对较小或与 ImageNet数据集有一定的相似性时。
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 55):将模型的最后一层(全连接层)替换为一个具有 55 个输出单元的线性层。这是为了适应目标任务的类别数。
nn.init.xavier_normal_(finetune_net.fc.weight):使用 Xavier 初始化方法对新的全连接层的权重进行初始化。
parms_1x 和 parms_10x:分别获取模型最后一层以外的所有参数和最后一层参数。这是为了设置不同的学习率。
分层训练(Layer-wise learning rates)的主要目的是在微调(fine-tuning)过程中更灵活地调整网络的参数。在迁移学习中,模型通常包含预训练模型的权重和新任务的最后一层(通常是全连接层)。
原始的预训练模型可能已经学到了一些通用的特征,而新任务的输出层需要适应新的类别。由于这两部分的性质不同,对它们应用相同的学习率可能不是最优的选择。因此,通过为模型的不同部分设置不同的学习率,我们可以更精细地调整参数,以更好地适应新任务。
这种设置允许更大的权重更新用于适应新任务的输出,而较小的权重更新则用于保留预训练模型的知识。这有助于确保在微调过程中不会过度调整预训练模型的权重,而只专注于适应新任务的相关部分。
画loss曲线
# -*-coding:utf-8-*-
from matplotlib import pyplot as plt
import numpy as np
def ReadData(data_loc):
epoch_list = []
train_loss_list = []
test_loss_list = []
test_accuracy_list = []
# open(data_loc,"r").readlines()
with open(data_loc, "r") as f:
linedata = f.readlines()
for line_i in linedata:
data = line_i.split('\t') # 因为原本的一行有空格隔开,将每个字符串分隔开
print("data = ", data)
epoch_i, train_loss_i, test_loss_i, test_accuracy_i = data[1], data[3], data[5], data[7]
epoch_list.append(int(epoch_i))
train_loss_list.append(float(train_loss_i))
test_loss_list.append(float(test_loss_i))
test_accuracy_list.append(float(test_accuracy_i))
# print(epoch_list)
# print(train_loss_list)
# print(test_loss_list)
# print(test_accuracy_list)
return epoch_list, train_loss_list, test_loss_list, test_accuracy_list
# 画两条线
def DrawLoss(train_loss_list, train_loss_list_2):
# 两条线
plt.style.use('dark_background')
plt.title("Loss")
plt.xlabel("epoch")
plt.ylabel("loss")
train_loss_list = train_loss_list[:10]
# 横坐标
epoch_list = [i for i in range(len(train_loss_list))]
# 横坐标,纵坐标,线条宽度
p1, = plt.plot(epoch_list, train_loss_list, linewidth=3)
p2, = plt.plot(epoch_list, train_loss_list_2, linewidth=3)
# 图例
plt.legend([p1, p2], ["with pretrain", "no pretrain"])
plt.show()
def DrawAcc(train_loss_list, train_loss_list_2):
plt.style.use('dark_background')
plt.title("Accuracy")
plt.xlabel("epoch")
plt.ylabel("accuracy")
train_loss_list = train_loss_list[:10]
epoch_list = [i for i in range(len(train_loss_list))]
p1, = plt.plot(epoch_list, train_loss_list, linewidth=3)
p2, = plt.plot(epoch_list, train_loss_list_2, linewidth=3)
plt.legend([p1, p2], ["with pretrain", "no pretrain"])
plt.show()
if __name__ == '__main__':
data_1_loc = "output/resnet18.txt"
data_2_loc = "output/resnet18_no_pretrain.txt"
_, train_loss_list, test_loss_list, test_accuracy_list = ReadData(data_1_loc)
_, train_loss_list_2, test_loss_list_2, test_accuracy_list_2 = ReadData(data_2_loc)
DrawLoss(train_loss_list, train_loss_list_2)
DrawAcc(test_accuracy_list, test_accuracy_list_2)
5.2使用恰当学习率
5.3数据增强
模型验证
第一份代码是用来求矩阵值,有多少数据就有多少行,每一行表示该数据softmax后的值
第二份代码,利用第一份代码求出的csv文件,用于求混淆矩阵和精确率召回率等信息
import torch
from torch.utils.data import DataLoader
from utils import LoadData,WriteData
import torch.nn as nn
from torchvision.models import resnet18
from tqdm import tqdm
import os
import pandas as pd
def test(dataloader, model, device):
pred_list = []
# 将模型转为验证模式
model.eval()
# 测试时模型参数不用更新,所以no_gard()
# 非训练, 推理期用到
with torch.no_grad():
# 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
for X, y in tqdm(dataloader):
# 将数据转到GPU
X, y = X.to(device), y.to(device)
# 将图片传入到模型当中就,得到预测的值pred
pred = model(X)
pred_softmax = torch.softmax(pred, 1).cpu().numpy()
pred_list.append(pred_softmax.tolist()[0])
return pred_list
if __name__=='__main__':
batch_size = 1
# # 给训练集和测试集分别创建一个数据集加载器
test_data = LoadData("test.txt", False)
test_dataloader = DataLoader(dataset=test_data, num_workers=4, pin_memory=True, batch_size=batch_size)
# 如果显卡可用,则用显卡进行训练
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
model = resnet18(num_classes=55)
model.load_state_dict(torch.load("output/resnet18_e_best.pth"))
model.to(device)
# 定义损失函数,计算相差多少,交叉熵,
# loss_fn = nn.CrossEntropyLoss()
'''
获取结果
'''
# 获取模型输出
pred_list = test(test_dataloader, model,device)
print("pred_list = ", pred_list)
'''
获取文件夹列表
'''
file_name_list = []
data_root = r"enhance_dataset"
for a,b,c in os.walk(data_root):
if len(b) != 0:
print(b)
file_name_list = b
# 表头file_name_list
df_pred = pd.DataFrame(data=pred_list, columns=file_name_list)
print(df_pred)
# index=False不需要最左侧的012345
df_pred.to_csv('pred_result.csv', encoding='gbk', index=False)
混淆矩阵:混淆矩阵中的数字是样本数或者概率
from sklearn.metrics import * # pip install scikit-learn
import matplotlib.pyplot as plt # pip install matplotlib
import pandas as pd # pip install pandas
import os
'''
读取数据
需要读取模型输出的标签(predict_label)以及原本的标签(true_label)
'''
target_loc = "./test.txt" # 真实标签所在的文件
target_data = pd.read_csv(target_loc, sep="\t", names=["loc", "type"])
true_label = [i for i in target_data["type"]]
predict_loc = "./pred_result.csv" # 3.ModelEvaluate.py生成的文件
predict_data = pd.read_csv(predict_loc, encoding="GBK") # ,index_col=0)
# 获取标签:取数值最大的那一位
predict_label = predict_data.to_numpy().argmax(axis=1)
# 获取最大数值分数
predict_score = predict_data.to_numpy().max(axis=1)
'''
常用指标:精度,查准率,召回率,F1-Score
'''
# 精度,准确率, 预测正确的占所有样本种的比例
accuracy = accuracy_score(true_label, predict_label)
print("精度: ", accuracy)
# 查准率P(准确率),precision(查准率)=TP/(TP+FP)
precision = precision_score(true_label, predict_label, labels=None, pos_label=1,
average='macro') # 'micro', 'macro', 'weighted'
print("查准率P: ", precision)
# 查全率R(召回率),原本为对的,预测正确的比例;recall(查全率)=TP/(TP+FN)
recall = recall_score(true_label, predict_label, average='macro') # 'micro', 'macro', 'weighted'
print("召回率: ", recall)
# F1-Score
f1 = f1_score(true_label, predict_label, average='macro') # 'micro', 'macro', 'weighted'
print("F1 Score: ", f1)
'''
混淆矩阵
'''
label_names = []
data_root = r"./enhance_dataset"
for a, b, c in os.walk(data_root):
if len(b) != 0:
print(b)
label_names = b
# 存入真实值,预测值,横坐标的标签
confusion = confusion_matrix(true_label, predict_label, labels=[i for i in range(len(label_names))])
plt.matshow(confusion, cmap=plt.cm.Oranges) # Greens, Blues, Oranges, Reds
plt.rcParams["font.sans-serif"] = ["SimHei"] # 设置中文字体
plt.rcParams["font.size"] = 8
plt.rcParams["axes.unicode_minus"] = False # 该语句解决图像中的“-”负号的乱码问题
plt.colorbar()
for i in range(len(confusion)):
for j in range(len(confusion)):
# 每个矩阵中都标注上值
plt.annotate(confusion[j, i], xy=(i, j), horizontalalignment='center', verticalalignment='center')
plt.ylabel('True label')
plt.xlabel('Predicted label')
# 90度旋转
plt.xticks(range(len(label_names)), label_names, rotation=270)
plt.yticks(range(len(label_names)), label_names)
plt.title("Confusion Matrix")
plt.show()
confusion_matrix 是一个用于计算混淆矩阵的函数,通常在机器学习库(如Scikit-learn)中提供。
混淆矩阵的行和列分别对应于真实标签和模型预测结果的类别。
单元格(i,j)中的值表示真是标签为第i类,模型预测结果为第j类的样本数量,颜色越深表示值越大
通过绘制混淆矩阵的热图,可以直观地展示模型在不同类别上的预测结果,帮助我们快速了解模型的性能,从而进行进一步的分析和改进。
混淆矩阵能够帮助我们发现模型在特定类别上的预测偏差,从而指导我们对模型进行调优和改进。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1754374510a5153303.html
评论列表(0条)