zoukankan      html  css  js  c++  java
  • 孪生网络入门(下) Siamese Net分类服装MNIST数据集(pytorch)


    主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable

    贡献主题:https://github.com/xitu/juejin-markdown-themes

    theme: smartblue
    highlight:

    在上一篇文章中已经讲解了Siamese Net的原理,和这种网络架构的关键——损失函数contrastive loss。现在我们来用pytorch来做一个简单的案例。经过这个案例,我个人的收获有到了以下的几点:

    • Siamese Net适合小数据集;
    • 目前Siamese Net用在分类任务(如果有朋友知道如何用在分割或者其他任务可以私信我,WX:cyx645016617)
    • Siamese Net的可解释性较好。

    1 准备数据

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import torch 
    import torch.nn as nn
    import torch.nn.functional as F
    from torch.utils.data import Dataset,DataLoader
    from sklearn.model_selection import train_test_split
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    
    data_train = pd.read_csv('../input/fashion-mnist_train.csv')
    data_train.head()
    

    这个数据文件是csv格式,第一列是类别,之后的784列其实好似28x28的像素值。

    划分训练集和验证集,然后把数据转换成28x28的图片

    X_full = data_train.iloc[:,1:]
    y_full = data_train.iloc[:,:1]
    x_train, x_test, y_train, y_test = train_test_split(X_full, y_full, test_size = 0.05)
    x_train = x_train.values.reshape(-1, 28, 28, 1).astype('float32') / 255.
    x_test = x_test.values.reshape(-1, 28, 28, 1).astype('float32') / 255.
    y_train.label.unique()
    >>> array([8, 9, 7, 6, 4, 2, 3, 1, 5, 0])
    

    可以看到这个Fashion MNIST数据集中也是跟MNIST类似,划分了10个不同的类别。

    • 0 T-shirt/top
    • 1 Trouser
    • 2 Pullover
    • 3 Dress
    • 4 Coat
    • 5 Sandal
    • 6 Shirt
    • 7 Sneaker
    • 8 Bag
    • 9 Ankle boot
    np.bincount(y_train.label.values),np.bincount(y_test.label.values)
    >>> (array([4230, 4195, 4135, 4218, 4174, 4172, 4193, 4250, 4238, 4195]),
     array([1770, 1805, 1865, 1782, 1826, 1828, 1807, 1750, 1762, 1805]))
    

    可以看到,每个类别的数据还是非常均衡的。

    2 构建Dataset和可视化

    class mydataset(Dataset):
        def __init__(self,x_data,y_data):
            self.x_data = x_data
            self.y_data = y_data.label.values
        def __len__(self):
            return len(self.x_data)
        def __getitem__(self,idx):
            img1 = self.x_data[idx]
            y1 = self.y_data[idx]
            if np.random.rand() < 0.5:  
                idx2 = np.random.choice(np.arange(len(self.y_data))[self.y_data==y1],1)
            else:
                idx2 = np.random.choice(np.arange(len(self.y_data))[self.y_data!=y1],1)
            img2 = self.x_data[idx2[0]]
            y2 = self.y_data[idx2[0]]
            label = 0 if y1==y2 else 1
            return img1,img2,label
    

    关于torch.utils.data.Dataset的构建结构,我就不再赘述了,在之前的《小白学PyTorch》系列中已经讲解的很清楚啦。上面的逻辑就是,给定一个idx,然后我们先判断,这个数据是找两个同类别的图片还是两个不同类别的图片。50%的概率选择两个同类别的图片,然后最后输出的时候,输出这两个图片,然后再输出一个label,这个label为0的时候表示两个图片的类别是相同的,1表示两个图片的类别是不同的。这样就可以进行模型训练和损失函数的计算了。

    train_dataset = mydataset(x_train,y_train)
    train_dataloader = DataLoader(dataset = train_dataset,batch_size=8)
    val_dataset = mydataset(x_test,y_test)
    val_dataloader = DataLoader(dataset = val_dataset,batch_size=8)
    
    for idx,(img1,img2,target) in enumerate(train_dataloader):
        fig, axs = plt.subplots(2, img1.shape[0], figsize = (12, 6))
        for idx,(ax1,ax2) in enumerate(axs.T):
            ax1.imshow(img1[idx,:,:,0].numpy(),cmap='gray')
            ax1.set_title('image A')
            ax2.imshow(img2[idx,:,:,0].numpy(),cmap='gray')
            ax2.set_title('{}'.format('same' if target[idx]==0 else 'different'))
        break
    

    这一段的代码就是对一个batch的数据进行一个可视化:

    到目前位置应该没有什么问题把,有问题可以联系我讨论交流,WX:cyx645016617.我个人认为从交流中可以快速解决问题和进步。

    3 构建模型

    class siamese(nn.Module):
        def __init__(self,z_dimensions=2):
            super(siamese,self).__init__()
            self.feature_net = nn.Sequential(
                nn.Conv2d(1,4,kernel_size=3,padding=1,stride=1),
                nn.ReLU(inplace=True),
                nn.BatchNorm2d(4),
                nn.Conv2d(4,4,kernel_size=3,padding=1,stride=1),
                nn.ReLU(inplace=True),
                nn.BatchNorm2d(4),
                nn.MaxPool2d(2),
                nn.Conv2d(4,8,kernel_size=3,padding=1,stride=1),
                nn.ReLU(inplace=True),
                nn.BatchNorm2d(8),
                nn.Conv2d(8,8,kernel_size=3,padding=1,stride=1),
                nn.ReLU(inplace=True),
                nn.BatchNorm2d(8),
                nn.MaxPool2d(2),
                nn.Conv2d(8,1,kernel_size=3,padding=1,stride=1),
                nn.ReLU(inplace=True)
            )
            self.linear = nn.Linear(49,z_dimensions)
        def forward(self,x):
            x = self.feature_net(x)
            x = x.view(x.shape[0],-1)
            x = self.linear(x)
            return x
    

    一个非常简单的卷积网络,输出的向量的维度就是z-dimensions的大小。

    def contrastive_loss(pred1,pred2,target):
        MARGIN = 2
        euclidean_dis = F.pairwise_distance(pred1,pred2)
        target = target.view(-1)
        loss = (1-target)*torch.pow(euclidean_dis,2) + target * torch.pow(torch.clamp(MARGIN-euclidean_dis,min=0),2)
        return loss
    

    然后构建了一个contrastive loss的损失函数计算。

    4 训练

    model = siamese(z_dimensions=8).to(device)
    # model.load_state_dict(torch.load('../working/saimese.pth'))
    optimizor = torch.optim.Adam(model.parameters(),lr=0.001)
    
    for e in range(10):
        history = []
        for idx,(img1,img2,target) in enumerate(train_dataloader):
            img1 = img1.to(device)
            img2 = img2.to(device)
            target = target.to(device)
            
            pred1 = model(img1)
            pred2 = model(img2)
            loss = contrastive_loss(pred1,pred2,target)
    
            optimizor.zero_grad()
            loss.backward()
            optimizor.step()
            
            loss = loss.detach().cpu().numpy()
            history.append(loss)
            train_loss = np.mean(history)
        history = []
        with torch.no_grad():
            for idx,(img1,img2,target) in enumerate(val_dataloader):
                img1 = img1.to(device)
                img2 = img2.to(device)
                target = target.to(device)
    
                pred1 = model(img1)
                pred2 = model(img2)
                loss = contrastive_loss(pred1,pred2,target)
    
                loss = loss.detach().cpu().numpy()
                history.append(loss)
                val_loss = np.mean(history)
        print(f'train_loss:{train_loss},val_loss:{val_loss}')
    

    这里为了加快训练,我把batch-size增加到了128个,其他的并没有改变:

    这是运行的10个epoch的结果,不要忘记把模型保存一下:

    torch.save(model.state_dict(),'saimese.pth')
    

    差不多是这个样子,然后看一看验证集的可视化效果,这里使用的是t-sne高位特征可视化的方法,其内核是PCA降维:

    from sklearn import manifold
    '''X是特征,不包含target;X_tsne是已经降维之后的特征'''
    tsne = manifold.TSNE(n_components=2, init='pca', random_state=501)
    X_tsne = tsne.fit_transform(X)
    print("Org data dimension is {}. 
          Embedded data dimension is {}".format(X.shape[-1], X_tsne.shape[-1]))
          
    x_min, x_max = X_tsne.min(0), X_tsne.max(0)
    X_norm = (X_tsne - x_min) / (x_max - x_min)  # 归一化
    plt.figure(figsize=(8, 8))
    for i in range(10):
        plt.scatter(X_norm[y==i][:,0],X_norm[y==i][:,1],alpha=0.3,label=f'{i}')
    plt.legend()
    

    输入图像为:

    可以看得出来,不同类别之间划分的是比较好的,可以看到不同类别之间的距离还是比较大的,比较明显的,甚至可以放下公众号的名字。这里使用的隐变量是8。

    这里有一个问题,我内心已有答案不知大家的想法如何,假如我把z潜变量的维度直接改成2,这样就不需要使用tsne和pca的方法来降低维度就可以直接可视化,但是这样的话可视化的效果并不比从8降维到2来可视化的效果好,这是为什么呢?

    提示:一方面在于维度过小导致信息的缺失,但是这个解释站不住脚,因为PCA其实等价于一个退化的线形层,所以PCA同样会造成这种缺失;我认为关键应该是损失函数中的欧式距离的计算,如果维度高,那么欧式距离就会偏大,这样需要相应的调整MARGIN的数值。

  • 相关阅读:
    python+selenium之页面元素截图
    selenium八大定位
    http概述之URL与资源
    数组中只出现一次的数字
    数字在排序数组中出现的次数
    把数组排成最小的数
    数组中出现次数超过一半的数字
    调整数组顺序使得奇数位于偶数的前面
    旋转数组的最小值
    二维数组的查找
  • 原文地址:https://www.cnblogs.com/PythonLearner/p/14097628.html
Copyright © 2011-2022 走看看