前两天学校有课加上要回家所以没学习,今天开始继续学习(●ˇ∀ˇ●)
今天学第五节下:图片分类半监督
一、机器学习
监督学习(Supervised Learning)是机器学习的一种方法,在这种方法中,模型通过学习带有标签的训练数据来进行预测或分类。换句话说,监督学习的任务是根据一组已知输入和相应的输出(标签),学习一个函数或模型,以便在面对新的、未知的输入时能够预测出对应的输出。
无监督学习(Unsupervised Learning)是机器学习的一种方法,与监督学习不同,无监督学习不依赖于标注的数据。它的目标是从未标注的输入数据中提取结构或模式,而不是预测具体的标签或输出。
半监督学习(Semi-supervised Learning) 是介于监督学习和无监督学习之间的一种机器学习方法。它结合了少量标注数据和大量未标注数据,用于训练机器学习模型。这种方法主要用于当标注数据很昂贵、获取困难或耗时,而未标注数据较容易获得的情境。
半监督学习之中,无标签数据通过模型以预测值作为真实值,当作有标签数据
二、项目思路
- 把无标签数据读作数据集
- 从无标签数据集中取一批数据,通过模型得到预测值,包括置信度(概率)和标签。如果置信度大于某一值,就把其标签和x加入新的数据集semi_dataset(用于储存无标签数据集中打上标签的数据)
- 把semi_dataset当作有标签数据集进行训练
import random
import torch
import torch.nn as nn
import numpy as np
import os
from PIL import Image #读取文件数据
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm #查看读取进度
from torchvision import transforms
import time
import matplotlib.pyplot as plt
from model_utils.model import initialize_model
def seed_everything(seed):
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
random.seed(seed)
np.random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
#################################################################
seed_everything(0)
###############################################
HW = 224 #深度学习领域一般让图片大小为224,而文件中的图片是512*512
#数据变换 训练集和验证集的数据变换不同,验证和测试的时候要用原图,训练的时候才需要不同的数据变换进行训练
train_transform = transforms.Compose(
[
transforms.ToPILImage(), #图片为224*224*3,模型需要3*224*224
transforms.RandomResizedCrop(224), #对图片随机放大裁切
transforms.RandomRotation(50), #对图片50°以内随机旋转
transforms.ToTensor() #变为张量进行模型运行
]
)
val_transform = transforms.Compose(
[
transforms.ToPILImage(), #图片为224*224*3,模型需要3*224*224
transforms.ToTensor() #变为张量进行模型运行
]
)
#定义数据集
class food_Dataset(Dataset):
def __init__(self, path, mode="train"): #初始化,传入地址,得到数据集x和标签y
self.mode = mode
if mode == "semi":
self.X = self.read_file(path)
else:
self.X, self.Y = self.read_file(path)
self.Y = torch.LongTensor(self.Y) #标签转为长整型
if mode == "train":
self.transform = train_transform #数据增广
else:
self.transform = val_transform
# 读文件
def read_file(self, path):
if self.mode == "semi":
for i in tqdm(range(11)):
file_list = os.listdir(path) # 列出文件夹下所有文件名字
xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8) # 读为整型
for j, img_name in enumerate(file_list): # enumerate()既可以读到下标,也可以读到内容
img_path = os.path.join(path, img_name)
img = Image.open(img_path)
img = img.resize((HW, HW))
xi[j, ...] = img # ...表示后面的维度表示一致,把图片放到第j个
print("读到了%d个数据" % len(xi))
return xi
else:
for i in tqdm(range(11)):
file_dir = path + "/%02d" % i
file_list = os.listdir(file_dir) # 列出文件夹下所有文件名字
xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8) # 读为整型
yi = np.zeros(len(file_list), dtype=np.uint8)
for j, img_name in enumerate(file_list): # enumerate()既可以读到下标,也可以读到内容
img_path = os.path.join(file_dir, img_name)
img = Image.open(img_path)
img = img.resize((HW, HW))
xi[j, ...] = img # ...表示后面的维度表示一致,把图片放到第j个
yi[j] = i # 标签
# 把所有的xi,yi在0维合并
if i == 0:
X = xi
Y = yi
else:
X = np.concatenate((X, xi), axis=0)
Y = np.concatenate((Y, yi), axis=0)
print("读到了%d个数据" % len(Y))
return X, Y
def __getitem__(self, item):
if self.mode == "semi":
return self.transform(self.X[item]), self.X[item]
else:
return self.transform(self.X[item]), self.Y[item]
def __len__(self):
return len(self.X)
class semiDataset(Dataset):
def __init__(self, no_label_loader, model, device, thres=0.99):
x, y = self.get_label(no_label_loader, model, device, thres)
if x == []:
self.flag = False
else:
self.flag = True
self.X = np.array(x)
self.Y = torch.LongTensor(y)
self.transform = train_transform
def get_label(self,no_label_loader, model, device, thres):
model = model.to(device)
pred_prob = []
labels = []
x = []
y = []
soft = nn.Softmax()
with torch.no_grad():
for bat_x, _ in no_label_loader:
bat_x = bat_x.to(device)
pred = model(bat_x)
pred_soft = soft(pred)
pred_max, pred_value = pred_soft.max(1)
pred_prob.extend(pred_max.cpu().numpy().tolist())
labels.extend(pred_value.cpu().numpy().tolist())
for index, prob in enumerate(pred_prob):
if prob > thres:
x.append(no_label_loader.dataset[index][1]) #调用到原始的getitem
y.append(labels[index])
return x, y
def __getitem__(self, item):
return self.transform(self.X[item]), self.Y[item]
def __len__(self):
return len(self.X)
def get_semiloader(no_label_loader, model, device, thres):
semiset = semiDataset(no_label_loader, model, device, thres)
if semiset.flag == False:
return None
else:
semi_loader = DataLoader(semiset, batch_size=16, shuffle=False)
return semi_loader
#模型
class myModel(nn.Module):
def __init__(self, num_class): #搭建模型框架
super(myModel, self).__init__()
#图片输入为3*224*224,要变为512*7*7,然后拉直、全连接、分类
self.conv1 = nn.Conv2d(3, 64, 3, 1, 1) #64*224*224
self.bn1 = nn.BatchNorm2d(64) #归一化
self.relu = nn.ReLU()
self.pool1 = nn.MaxPool2d(2) #64*112*112
self.layer1 = nn.Sequential( #这样合并更方便,且美观
nn.Conv2d(64, 128, 3, 1, 1), #128*112*112
nn.BatchNorm2d(128), #归一化
nn.ReLU(),
nn.MaxPool2d(2) #127*56*56
)
self.layer2 = nn.Sequential(
nn.Conv2d(128, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2) #256*28*28
)
self.layer3 = nn.Sequential(
nn.Conv2d(256, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2) #512*14*14
)
self.pool2 = nn.MaxPool2d(2) #512*7*7
self.fc1 = nn.Linear(25088, 1000) #卷积 25088->1000
self.relu2 = nn.ReLU()
self.fc2 = nn.Linear(1000, num_class) #1000->num_class(11)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool1(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.pool2(x)
x = x.view(x.size()[0], -1) #拉直
x = self.fc1(x)
x = self.relu2(x)
x = self.fc2(x)
return x
#训练函数
def train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path):
model = model.to(device)
semi_loader = None
plt_train_loss = [] #记录所有轮次的loss
plt_val_loss = []
plt_train_acc = [] #记录准确率
plt_val_acc = []
max_acc = 0.0 #保存准确率最高的模型
for epoch in range(epochs): #冲锋的号角
train_loss = 0.0
val_loss = 0.0
train_acc = 0.0
val_acc = 0.0
semi_loss = 0.0
semi_acc = 0.0
start_time = time.time()
model.train() #模型调为训练模式
for batch_x, batch_y in train_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
train_bat_loss = loss(pred, target)
train_bat_loss.backward()
optimizer.step() #更新模型的作用
optimizer.zero_grad()
train_loss += train_bat_loss.cpu().item()
train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
plt_train_loss.append(train_loss / train_loader.__len__())
plt_train_acc.append(train_acc / train_loader.dataset.__len__()) #记录准确率
if semi_loader != None:
for batch_x, batch_y in semi_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
semi_bat_loss = loss(pred, target)
semi_bat_loss.backward()
optimizer.step() #更新模型的作用
optimizer.zero_grad()
semi_loss += train_bat_loss.cpu().item()
semi_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
print("半监督的数据集的训练准确率为",semi_acc/train_loader.dataset.__len__())
model.eval()
with torch.no_grad():
for batch_x, batch_y in val_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
val_bat_loss = loss(pred, target)
val_loss += val_bat_loss.cpu().item()
val_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
plt_val_loss.append(val_loss/ val_loader.__len__())
plt_val_acc.append(val_acc / val_loader.dataset.__len__())
if epoch % 3 == 0 and plt_val_acc[-1] > 0.6:
semi_loader = get_semiloader(no_label_loader, model, device, thres)
if val_acc > max_acc:
torch.save(model, save_path)
max_acc = val_loss
print("[%03d/%03d] %2.2f sec(s) Trainloss: %.6f |Valloss: %.6f Trainacc: %.6f |Valacc: %.6f" % \
(epoch, epochs, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1], plt_train_acc[-1], plt_val_acc[-1]))
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title("loss")
plt.legend(["train", "val"])
plt.show()
plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title("acc")
plt.legend(["train", "val"])
plt.show()
# path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11\training\labeled" #训练数据的路径,r能去除转义字符的作用
# train_path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11\training\labeled"
# val_path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11\validation"
train_path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11_sample\training\labeled"
val_path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11_sample\validation"
no_label_path = r"D:\大学\学习\深度学习\课程代码\第四五节_分类代码\food_classification\food-11_sample\training\unlabeled\00"
#读数据集
train_set = food_Dataset(train_path, "train")
val_set = food_Dataset(val_path, "val")
no_label_set = food_Dataset(no_label_path, "semi")
train_loader = DataLoader(train_set, batch_size=16, shuffle=True) #train_loader把train_set变为一批数据
val_loader = DataLoader(val_set, batch_size=16, shuffle=True)
no_label_loader = DataLoader(no_label_set, batch_size=16, shuffle=False) #shuffle一定要等于false,因为是对无标签数据进行预测
# model = myModel(11)
#用大佬们的模型
# from torchvision.models import resnet18
# model = resnet18(pretrained=True) #pretrained=True用架构和参数,=False时则从0开始训练
# in_features = model.fc.in_features #替换分类头
# model.fc = nn.Linear(in_features, 11) #把模型的分类头改为自己的输入维度11
#引用模型
# model = initialize_model("resnet18", 11, use_pretrained=True)
model, _ = initialize_model("vgg", 11, use_pretrained=True)
lr = 0.001 #学习率
loss = nn.CrossEntropyLoss() #损失用交叉熵损失
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) #优化器
device = "cuda" if torch.cuda.is_available() else "cpu" #设备
save_path = "model_save/best_model_zhou.pth" #保存最好的模型
epochs = 15 #训练轮次
thres = 0.99
train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path)
1374



被折叠的 条评论
为什么被折叠?



