采用ResNet网络+TSNE降维算法对自建图像数据集进行二维可视化显示

发布时间 2023-09-18 11:44:59作者: 修炼诗人

起因:某一天下午,我在“玩”的时候,突然接到了老板的电话,说是要对图像做可视化降维。因此,我拿到了一批图像的数据。

数据的特点

  1、数据集的图像分为4类,并且每一种类的图像多少不均衡。

  2、数据集图像是一个大文件夹,里面包含4个文件夹,而且里面命名非常不规范。

  3、数据集图像大小也不一样。

任务:采用ResNet网络+TSNE降维算法对图像数据集进行二维可视化显示

大体步骤

  1、首先对图像进行重命名,这样在读入图像的时候,可以保持一个干净整洁的方式,写入。

  2、对于重命名的数据,本来想着在ResNet算法中进行Resize,因为ResNet网络里面有一个方法是Transforms.Compose([transforms.Resize(256,256)]),也就是将图像转化为256像素*256像素的。但是后来一想,还是先把图像转换一下。

  3、采用重命名+Resize的图片作为图像数据集,进行输入到ResNet网络中。使用训练集对模型进行训练,采用训练好的模型对测试集进行提取特征。特征+标签保存为npy文件。

  4、使用TSNE降维算法对npy文件读取,并且降到二维,使用plt.show()展示降维后的结果。

步骤1的代码:图像进行重命名

import os
from PIL import Image
# 语言: Python
# 作用:批量对一个文件夹中的图片进行重命名,从0.1.2.3.4开始
# 一、首先定义图片重命名的函数
def IRename(folder_path):
    Xin_folder_path = "{}/{}".format(total_folder_path,folder_path)
    for i,filename in enumerate(os.listdir(Xin_folder_path)):
        # 检查图片格式是否为PNG、JPG、JPEG  对于字母大小写不敏感
        if(filename.endswith(".jpg") or filename.endswith(".png") or filename.endswith(".jpeg")):
            # 1 打开图像文件
            img = Image.open(os.path.join(Xin_folder_path,filename))
            # 2 分割图像文件夹的名称,split("_")
            print(filename)
            hou_Word = folder_path.split('_')
            print(hou_Word[1])
            # 3 构建新的文件名称
            new_filename = '{}_{}.{}'.format(hou_Word[1],i+1,img.format.lower()) # img.format.lower():获取图片格式
            img.close()
            # 3 重命名 图像名称
            os.rename(os.path.join(Xin_folder_path,filename),os.path.join(Xin_folder_path,new_filename))

# 二、输入总的文件夹名称,包含4个图片的文件夹名称
total_folder_path = "../image"
for i,totalfilename in enumerate(os.listdir(total_folder_path)):
    IRename(totalfilename)

  上面展示了步骤1的代码,但是后来我在完成整个任务的时候,发现对于图像的重命名是没有必要的,因为给图像打标签完全可以使用包含每一种类别图像的文件夹名称进行打标签。

步骤2的代码:Resize 图像大小

import os
from PIL import Image
# 语言: Python
# 作用:批量对一个文件夹中的图片进行重设像素大小,width=值,height=值
import glob
def convertJPEG(jpgfile,outdir,width=64,height=64):
    img = Image.open(jpgfile)
    new_ing = img.resize((width,height),Image.BILINEAR)
    new_ing.save(os.path.join(outdir,os.path.basename(jpgfile)))

for jpgfile in glob.glob('images_all/*.jpeg'):
    convertJPEG(jpgfile,"images_all_Resize")

步骤3的代码:采用ResNet网络进行重新训练,这里采用两个py文件,第一个py用来包装好测试集和训练集,完成所需工作。第二个py用来训练ResNet模型,并在最后保存测试集的特征为npy文件

import glob
import torch
# 语言: Python
# 作用:# 第一个py用来包装好测试集和训练集,完成所需工作
from torch.utils import data
from PIL import Image
import numpy as np
from torchvision import transforms
# 2 继承Dataset实现Mydataset子类
class Mydataset(data.Dataset):
    # init() 初始化方法,传入数据文件夹路径
    def __init__(self, root):
        self.imgs_path = root

    # getitem() 切片方法,根据索引下标,获得相应的图片
    def __getitem__(self, index):
        img_path = self.imgs_path[index]

    # len() 计算长度方法,返回整个数据文件夹下所有文件的个数
    def __len__(self):
        return len(self.imgs_path)

# 3、使用glob方法来获取数据图片的所有路径
all_imgs_path = glob.glob(r"../image/*/*.jpeg")  # 数据文件夹路径

# 一 利用自定义的类来创建对象brake_dataset
brake_dataset = Mydataset(all_imgs_path)
brake_dataloader = torch.utils.data.DataLoader(brake_dataset, batch_size=4)  # 每次迭代时返回4个数据

# 为所有的图片制造相对应的标签
species = ['haemorrhage','hardexudate','normal','softexudate']

species_to_id = dict((c,i) for i,c in enumerate(species))

id_to_species =dict((v,k) for k,v in species_to_id.items())

# 二 为对应的图片,打上标签 PS:这一部分很重要DDDDDDDanger
all_labels = []
for img in all_imgs_path:
    # print(img)
    for i,c in enumerate(species):
        if c in img:
            all_labels.append(i)
# print(len(all_labels))

# 三 将图片转化为Tensor,展示图片与标签对应的关系
# 对数据进行转换处理
transform  = transforms.Compose([
    transforms.Resize((256,256)),
    transforms.ToTensor()
])

class Mydatasetpro(data.Dataset):
    # 初始化
    def __init__(self,img_paths,labels,transform):
        self.imgs = img_paths
        self.labels = labels
        self.transforms = transform
    # 切片处理
    def __getitem__(self, index):
        img = self.imgs[index]
        label = self.labels[index]
        pill_img = Image.open(img)
        pill_img = pill_img.convert('RGB')
        data = self.transforms(pill_img)
        return data,label
    # 返回长度
    def __len__(self):
        return len(self.imgs)

BATCH_SIZE = 16
brake_dataset = Mydatasetpro(all_imgs_path,all_labels,transform)
brake_dataloader = data.DataLoader(
    brake_dataset,
    batch_size=BATCH_SIZE,
    shuffle=True

)

imgs_batch,labels_batch = next(iter(brake_dataloader))
# print(imgs_batch.shape)


# 四 划分数据集和测试集
index = np.random.permutation(len(all_imgs_path))

all_imgs_path = np.array(all_imgs_path)[index]
all_labels = np.array(all_labels)[index]

s = int(len(all_imgs_path) * 0.9)
train_imgs = all_imgs_path[:s]
train_labels = all_labels[:s]
test_imgs = all_imgs_path[s:]
test_labels = all_labels[s:]

print(len(train_imgs),len(train_labels),len(test_imgs),len(test_labels))

# 将对应的数据,转化为对应的Tensor Data
train_ds  = Mydatasetpro(train_imgs,train_labels,transform)
test_ds  = Mydatasetpro(test_imgs,test_labels,transform)
print("@#$%^&^%$^&*&^%$&(*&^%$^")
train_loader  = data.DataLoader(train_ds,batch_size=BATCH_SIZE,shuffle=True)
test_loader  = data.DataLoader(test_ds,batch_size=BATCH_SIZE,shuffle=True)

 第二个py用来训练ResNet模型,并在最后保存测试集的特征为npy文件

import torch
from torchvision import models
# 语言: Python
# 作用:# 第二个py用来训练ResNet模型,并在最后保存测试集的特征为npy文件
from tqdm import tqdm
from torch import nn
from torch import optim
from Doctor0913 import brake_dataloader
from Doctor0913 import train_loader, test_loader
import ssl
import numpy as np
from tqdm import tqdm
import torch.nn.functional as F
ssl._create_default_https_context = ssl._create_unverified_context

# 1、查看一个Batch的图像+标注
train_images,train_labels = next(iter(train_loader))
print(train_images.shape) # torch.Size([4, 3, 256, 256])
print(train_labels.shape) # torch.Size([4])
# 2、将tensor转为array后即可使用plt调用
# 3、载入模型进行微调

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model_ft = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)  # 使用迁移学习,加载预训练权

in_features = model_ft.fc.in_features
model_ft.fc = nn.Sequential(nn.Linear(in_features,256),
                            nn.ReLU(),
                            nn.Linear(256,4),
                            nn.LogSoftmax(dim=1)
                            )
model_ft = model_ft.to(DEVICE) # 将模型迁移到gpu

# 优化器
loss_fn = nn.CrossEntropyLoss()

loss_fn = loss_fn.to(DEVICE)  # 将loss_fn迁移到GPU

# Adam损失函数
optimizer = optim.Adam(model_ft.fc.parameters(), lr=0.0001)
epochs = 30  # 迭代次数
steps = 0
running_loss = 0
print_every = 10
train_losses, test_losses = [], []
lasds = []
tmp = []
acc = []
for epoch in range(epochs):
    model_ft.train()
    running_loss = 0
    # 遍历训练集数据
    for imgs, labels in tqdm(train_loader):
        steps += 1
        labels = torch.tensor(labels, dtype=torch.long)
        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)
        optimizer.zero_grad()  # 梯度归零
        outputs = model_ft(imgs)
        loss = loss_fn(outputs, labels)


        loss.backward()  # 反向传播计算梯度
        optimizer.step()  # 梯度优化
        running_loss += loss.item()
    print(f"Epoch {epoch + 1}/{epochs}"
          f"...Train loss: {running_loss / len(train_loader):.3f} ")
    with torch.no_grad():
        test_loss = 0
        accuracy = 0
        # 遍历测试集数据
        for imgs, labels in test_loader:
            labels = torch.tensor(labels, dtype=torch.long)
            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)
            outputs = model_ft(imgs)
            # 现将 特征放到cpu上面,再numpy
            outputs = outputs.cpu().detach().numpy()
            labels1 = np.array([int(i) for i in labels])
            # 将对应的特征+标签进行抽取并保存
            for i in labels1:
                label1 = i
                lasds.append(label1)
            for i in outputs:
                tmp.append(i)
            labels = labels.to(DEVICE)
            outputs = torch.tensor(outputs)
            outputs = outputs.to(DEVICE)

            loss = loss_fn(outputs, labels)

            test_loss += loss.item()
            ps = torch.exp(outputs)
            top_p, top_class = ps.topk(1, dim=1)
            equals = top_class == labels.view(*top_class.shape)
            accuracy += torch.mean(equals.type(torch.FloatTensor)).item()
        acc.append(accuracy / len(test_loader))
        print(f"Test accuracy: {accuracy / len(test_loader):.3f}")
lapsds = torch.Tensor(lasds)
tmp = torch.Tensor(tmp)
print(acc)
# with open('ResNet_Test_FTLoss.txt', 'w') as f:
#     for item in loss_FocalLoss:
#         f.write("%s\n" % item)
np.save("Feature_ResNet_Test", tmp)
np.save("Label_ResNet_Test", lasds)b

步骤4的代码:将image_feature.npy文件+label.npy文件传到TSNE降维算法中,进行二维可视化展示

import numpy as np
from matplotlib import pyplot as plt
# # 语言: Python
# # 作用:#  将image_feature.npy文件+label.npy文件传到TSNE降维算法中,进行二维可视化展示
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

train = np.load('Feature_ResNet_Test_FTLoss.npy')
labels12 = np.load('Label_ResNet_Test_FTLoss.npy')

# -===================================================================================
# 降到二维:平面图展示
asdasfa = 1
if asdasfa ==1:
    labels_huitu_string = ['haemorrhage', 'hardexudate', 'normal', 'softexudate']
    tsne = TSNE(n_components=2, learning_rate=150).fit_transform(train)

    plt.figure(figsize=(12, 6))
    plt.scatter(tsne[:, 0], tsne[:, 1], c=labels12)
    plt.show()
# -===================================================================================
if asdasfa == 2:
    model_pca = PCA(n_components=2)
    X_PCA = model_pca.fit_transform(train)
    # # 绘图
    labels_huitu = [0, 1, 2, 3]
    Colors = ['red', 'orange', 'yellow', 'green']
    labels_huitu_string = ['haemorrhage', 'hardexudate', 'normal', 'softexudate']

    plt.figure(figsize=(8, 6), dpi=80) # figsize定义画布大小,dpi定义画布分辨率
    plt.title('Transformed samples via sklearn.decomposition.PCA')
    #分别确定x和y轴的含义及范围
    plt.xlabel('x_values')
    plt.ylabel('y_values')
    for tlabel in labels_huitu:
        # pca读取数据
        x_pca_data=X_PCA[labels12==tlabel,0]
        y_pca_data=X_PCA[labels12==tlabel,1]
        plt.scatter(x=x_pca_data,y=y_pca_data,s=20,c=Colors[tlabel],label=labels_huitu_string[tlabel])
    plt.legend(loc="upper right") #输出标签信息在右上角
    plt.grid()
    plt.show()
# -===================================================================================

经过TSNE降维之后的图像数据集的特征,进行二维可视化展示

  到此,整个任务就结束了。撒花完结!!!!!!!!!!!!!!

题外话1:后来在写这一篇博客的时候,突然想到了还没有开始干活,就写了两个程序,头都大了。

题外话2:使用模型提取图像特征的时候,不要将训练集+测试集 一起打包放入模型中,会让你头疼的。图如下所示: