当前位置: 首页 > article >正文

分类学习(加入半监督学习)

#随机种子固定,随机结果也固定
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)
###############################################
#seed_everything(0)这一行调用了上述函数并传入了种子值0,
# 这意味着所有的随机操作都将基于这个种子值来进行初始化,从而确保了实验的可重复性。

固定随机种子

首先,通过seed_everything(seed)函数设置所有随机数生成器的种子,确保实验结果的可重复性。这对于调试和复现实验结果非常重要。

---------------------------------------------------------------------------------------------------------------------------------

train_transform = transforms.Compose(  #进行模型变换
    [
        transforms.ToPILImage(),  #就是把224,224,3模型   -> 3,224,22
        transforms.RandomResizedCrop(224), #对图片放大裁剪,裁切出来224*224
        transforms.RandomRotation(50), #在50度以内进行旋转
        transforms.ToTensor()#变为张量
    ]
)

val_transform = transforms.Compose(  #进行模型变换
    [
        transforms.ToPILImage(),  #就是把224,224,3模型   -> 3,224,22 验证集的话就不用进行transforms模型变换
        # transforms.RandomResizedCrop(224), #对图片放大裁剪,裁切出来224*224
        # transforms.RandomRotation(50), #在50度以内进行旋转
        transforms.ToTensor()#变为张量
    ]
)

图像变换

定义了两种不同的图像变换方法train_transformval_transform。训练集使用了包括随机裁剪、旋转等在内的数据增强技术,以增加模型的泛化能力;而验证集只进行了基本的转换操作,如调整大小和转为张量。

---------------------------------------------------------------------------------------------------------------------------------

class food_Dataset(Dataset):
    def __init__(self,path,mode="train"):
        self.mode=mode
        if mode == "semi":
            self.X=self.read_file(path)
        else:
            self.X,self.Y=self.read_file(path) #给一个路径,读出来X,Y
            self.Y = torch.LongTensor(self.Y) #将标签转化成长整型
        if mode=="train":
            self.transform = train_transform
        else:
            self.transform = val_transform

    def read_file(self,path):
        #读semi文件(没有标签的)
        if self.mode=="semi":
            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
            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
                    yi[j] = i

                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): #这个函数的作用就是retuen X[item],Y[item]的值
        if self.mode=="semi":
            return self.transform(self.X[item]),self.X[item]#返回transform后的X和原始的X
        else:
            return self.transform(self.X[item]),self.Y[item] #加上transform是为了对图片进行函数增广

    def __len__(self):
        return len(self.X)

 自定义数据集类:

food_Dataset: 自定义的数据集类,用于从指定路径读取图片及其标签(或无标签),并应用相应的变换。它支持三种模式:训练(train)、验证(val)和半监督学习(semi)。该类实现了__getitem____len__方法,使得它可以被PyTorch的数据加载器(DataLoader)使用。

---------------------------------------------------------------------------------------------------------------------------------

class semiDataset(Dataset):
    def __init__(self,no_label_loder,model,device,thres=0.99):
        x,y=self.get_label(no_label_loder,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_loder,model,device,thres):
        model=model.to(device)
        pred_prob=[]#用一个列表存概率值
        labels=[]#用一个列表存对应的标签
        x=[]#把无标签的x存进去
        y=[]#把半监督学习后x对应的y存进去
        soft =nn.Softmax
        with torch.no_grad(): #半监督学习,对模型梯度不产生作用,因此加上with torch.no_grad()
          for bat_x, _ in no_label_loder:  #no_label_loder返回两个值,不需要后面的,所以用_表示
              bat_x=bat_x.to(device)
              pred=model(bat_x)#预测值
              pred_soft=soft(pred)#转化为了概率
              pred_max,pred_value=pred_soft.max(1)#既可以返回最大概率值,又可以返回最大概率值下标  、1表示横着读
              pred_prob.extend(pred_max.cpu().numpy().tolist())#extend可以合并两个列表,append不行   对应概率的数组
              labels.extend(pred_value.cpu().numpy().tolist()) #对应的标签的数组

        for index,prob in enumerate(pred_prob):
            if prob>thres:
                x.append(no_label_loder.dataset[index][1])#取到原始数据
                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)

自定义数据集类:

semiDataset: 另一个自定义数据集类,专门用于从未标记的数据中筛选出高置信度的样本及其预测标签,以便将其加入到训练集中。这个过程是通过调用模型对未标记数据进行预测来实现的。

---------------------------------------------------------------------------------------------------------------------------------

def get_semi_loader(no_label_loder,model,device,thres):
    semiset=semiDataset(no_label_loder,model,device,thres)
    if semiset.flag==False:
        return None
    else:
        semi_loader=DataLoader(semiset,batch_size=16,shuffle=False)
        return semi_loader

半监督学习数据加载器: 

get_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) #针对具有64个通道的二维数据的批量归一化层,目的是在训练过程中对该层输入数据进行归一化处理,以加快训练速度和提高模型性能。
        self.relu1=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)  # 128*56*56
        )

        self.layer2 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 1, 1),  # -> 256*56*56  (卷积)
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2)  # 256*28*28
        )

        self.layer3 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1),  # -> 512*28*28  (卷积)
            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)  #拉直
        self.relu2=nn.ReLU()
        self.fc2=nn.Linear(1000,num_class)#1000->分类的类别

    def forward(self,x):
        x=self.conv1(x)
        x=self.bn1(x)
        x=self.relu1(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

CNN模型架构

myModel是一个简单的卷积神经网络(CNN),设计用于分类任务。它包含了多个卷积层、批量归一化层、ReLU激活函数和最大池化层,最后通过全连接层输出分类结果。

---------------------------------------------------------------------------------------------------------------------------------

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
        semi_loss = 0.0
        train_acc = 0.0
        val_acc = 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())
            #pred.detach().cpu().numpy()取张量数据,np.argmax(),axis=1 的意思是取一行最大的坐标,
            #也就是取概率最大的坐标,与标签相对比,相等就是Ture,反之False
        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())
                #pred.detach().cpu().numpy()取张量数据,np.argmax(),axis=1 的意思是取一行最大的坐标,
                #也就是取概率最大的坐标,与标签相对比,相等就是Ture,反之False
            print("半监督数据集的训练准确率为",semi_acc/semi_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.dataset.__len__())
        plt_val_acc.append(val_acc / val_loader.dataset.__len__())  # 记录准确率
        if plt_val_acc[-1]>0.7:
            semiloader=get_semi_loader(no_label_loader,model,device,thres) #如果正确率达到0.7,就可以训练
        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_val_acc)
    plt.plot(plt_val_acc)
    plt.title("acc")
    plt.legend(["train", "val"])
    plt.show()

训练与验证流程 (train_val 函数)

  • 初始化:将模型移动到GPU(如果可用),准备记录损失和准确率的列表。

  • 循环遍历epochs:对于每个epoch:

    • 执行训练阶段:计算训练损失和准确率。
    • 如果存在半监督数据,则对其进行处理。
    • 执行验证阶段:计算验证损失和准确率,检查是否需要保存最佳模型。
    • 根据验证准确率达到阈值,更新半监督数据加载器。
    • 输出当前epoch的信息,包括时间消耗、损失和准确率。
  • 绘图:使用matplotlib绘制训练和验证的损失与准确率变化曲线,帮助直观了解模型的学习过程。

---------------------------------------------------------------------------------------------------------------------------------

train_path=r"D:\python_data\classify\food_classification\food-11_sample\training\labeled"
val_path=r"D:\python_data\classify\food_classification\food-11_sample\validation"
no_label_path=r"D:\python_data\classify\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) #数据加载器
val_loader = DataLoader(val_set,batch_size=16,shuffle=True)
no_label_loader = DataLoader(no_label_set,batch_size=16,shuffle=False)#False是因为没有标签的照片不能打乱
#batch_size=4:此参数指定了每个批次(batch)中包含的数据样本数量
#为4。这意味着每次迭代过程中,DataLoader都会从train_set中取出4
#个样本组成一个批次返回给用户。选择合适的batch_size对模型训练的
#速度和性能都有影响。
#shuffle=True:在每个epoch开始前,如果设置为True,则DataLoader会将数据集中的数据顺序打乱。


# model = myModel(11)

from torchvision.models import resnet18
model=resnet18(pretrained=True)  #pretrained=True 就是不仅用大佬的架构,还用大佬的参数 False 只用架构,不用参数
in_fetures=model.fc.in_features #提取模型分类的输出头
model.fc= nn.Linear(in_fetures,11)

lr=0.001  #学习率
loss = nn.CrossEntropyLoss() #直接调用交叉熵损失
optimizer = torch.optim.AdamW(model.parameters(),lr=lr,weight_decay=1e-4) #优化器AdamW
#model.parameters(): 这个方法会返回一个生成器,包含了模型中所有可学习参数(即网络层的权重和偏置等)
# 。通过传递这个生成器给优化器,告诉优化器需要更新哪些参数。
#weight_decay=1e-4: 权重衰减系数,用于指定L2正则化的强度。L2正则化是一种防止过拟合的技术,通过对损
# 失函数添加一个与权重大小成比例的惩罚项来实现。1e-4意味着对于每个权重w,都会在损失函数中加上权重衰减有
# 助于控制模型复杂度,避免模型过度拟合训练数据。
device="cuda" if torch.cuda.is_available() else "cpu" #如果满足就是cude 否则就是CPU
save_path = "model_save/best_model.pth"  #保存最好的模型
epochs=4
thres =0.1
train_val(model,train_loader,val_loader,no_label_loader,device,epochs,optimizer,loss,thres,save_path)


http://www.kler.cn/a/576134.html

相关文章:

  • c# 修改邮件附件名称
  • Flask 打包为exe 文件
  • git如何解除远程仓库 改变远程仓库地址
  • fastapi+angular就业管理系统
  • 慕慕手记项目日记 2025-3-7 项目基本环境搭建
  • 如何用FFmpeg高效拉流(避坑指南)
  • 捣鼓180天,我写了一个相册小程序
  • Zypher Network :基于零知识证明方案为 AI 赋予可信框架
  • leetcode麻烦又易忘记题目
  • Python Flask框架学习汇编
  • ReferenceError: assignment to undeclared variable xxx
  • C/C++基础知识复习(50)
  • 九、Redis 并发控制:单线程原理与 Pipeline 批量优化
  • 部署Nagios Core服務器安裝好了部署了aapenal 作為網頁服務器設定了防火墻可視化的軟件來每日監測服務器的狀況.
  • 计算机毕业设计SpringBoot+Vue.js周边游平台(源码+文档+PPT+讲解)
  • createrepo centos通过nginx搭建本地源
  • 实现NTLM relay攻击工具的Python代码示例
  • TensorFlow的pb模型
  • 如何在PHP爬虫中处理异常情况的详细指南
  • Python基于Django的图书馆管理系统【附源码、文档说明】