潜空间

93a9cd74bee367fcaa195319d181ea4e.jpeg

本章通过扩展嵌入的概念,将图像也包括在内,以此为基础。我们将探讨上一章介绍的K-Means聚类如何对图像嵌入执行,并介绍通过准确度和召回来衡量性能的方法。

这篇文章引入了线性判别分析形式的简单潜空间嵌入(LSE),以帮助我们理解聚类和图像性能如何工作。本章不解释LSE,因为它将在下一章中介绍。

目录:

2.1图像嵌入

2.2性能

2.3讨论

2.4结论


2.1图像嵌入

第1章解释了同一对象的不同嵌入如何适用于不同的应用程序。以图像形式嵌入书籍不适合推荐,但需要以流派形式嵌入。但是,我们无法使用流派嵌入来阅读书籍,因为此应用程序需要实际文本。

通常,当使用嵌入时,目标是找到一种嵌入方法,为我们提供适用于应用程序的相似性。

在寻找相似之处时,图像是一种复杂的嵌入形式。例如,我们希望手机应用程序帮助我们识别我们正在观察的动物。在这里,我们需要找到图像和动物物种之间的相似之处。

图2.1-动物图像作为数据集中类别的示例

e438758d065ccd89d3adb4300c8d62ec.png

图像是我们在现实世界中看到的东西的嵌入。图像包括像素,每个像素是单一颜色。上面的图像是通过组合10.000个像素(准确地说,100.000个像素的图像)创建的。

每个像素代表一块知识,因此应该有自己独特的维度。第1章展示了一本书如何通过两种流派来表现,这两种流派共有两个维度。然而,具有250x400像素的灰度图像的总尺寸为100.000!

图2.2-将图像转换为数字的过程。

afac752bc5eceb8623a6cbac0d3ca760.png

那么问题来了,像素嵌入是否可以用于对图像中的动物进行分类?

图2.1有四个动物组(即猫、狗、鸡和牛)。从Animal-10数据集中提取每组16幅图像,并将其放置在图2.3中的坐标系内(每个动物组都有独特的颜色)。

图2.3-图2.1中的四只动物分别在2D空间中用16幅图像表示。线性判别分析(LDA)用于将图像转换为2D,因为它专注于分离类别。

04e380111e59819271b2d9f5527528f3.png

细心的读者可能会认识到,该图只显示了两个维度,而不是全部100.000。为了简单起见,我们从二维开始,然后将技术扩展到所有100.000维。关键之处在于,彩色点没有清晰的单一颜色组,而是全部混合在一起。

主成分分析[2](PCA)和线性判别分析[3](LDA)都可以将图像转换为2D。PCA专注于转换没有类的数据,LDA专注于有类的数据。LDA用于将图像转换为2D,因为每个图像中的动物是事先已知的。

图像嵌入部分的其余部分解释了如何从图2.3中生成图形。你可以继续下一节2.2性能,以跳过如何生成图形的详细说明

如何根据图2.3生成图表的详细说明:

图2.3中的点表示Kaggle上托管的Animal-10数据集中的动物。数据集可以在免费登录后从网站下载:https://www.kaggle.com/datasets/alessiocorrado99/animals10

图2.4-Kaggle上的Animal-10数据集,突出显示下载按钮。

43c56f98c1412f171c764461c9c11ed5.png

将从Kaggle下载一个“archive”文件夹,其中包括一个名为raw-img的文件夹和一个称为translate.py的文件;见图2.5。我们在代码2.1中定义了数据集位置,以便以后可以访问它。

图2.5-将“raw-img”文件移动到已知位置。在本例中,它被移动到“Datasets/Animals/”文件夹中。

12f6974ff96a9280174fb27e59e5c911.png
# Define location of the animal-10 folder
animal_10_folder = "Datasets/Animals/raw-img/"

现在,我们可以通过定义它们的名称和要提取的图像数量来提取每个动物组(即猫、狗、鸡和牛)的十六个图像。

代码2.2使用这些信息浏览每个动物文件夹,加载16个图像并使其大小相同。比较相同大小的图像更容易,这是LDA等功能所必需的。

代码2.2-加载16张猫、狗、鸡和奶牛的图像。

import cv2
import os

# Define location of the animal-10 folder
animal_10_folder = "Datasets/Animals/raw-img/"

# Define which and how many animals should be loaded
animal_class_names = ["gatto", "cane", "gallina", "mucca"] # Cat, Dog, Chicken, Cow
n_samples_per_animal = 16

#Define image size
image_size = (224, 224)

# Function for loading images
def load_animal_images(animal_folder, animal_classes, n_images_per_animal, resize):
  # Object for storing the images
  animals = {} # {Animal_name: image[]}
    
  #Go through each folder and download n_images
  for animal_class in animal_classes:
    animal_path = f"{animal_folder}/{animal_class}/"  # E.g. "Datasets/Animals/raw-img/gatto/"
    
    # Create a list to store the images
    animals[animal_class] = []
    
    # Go through n_images_per_animal
    for image_name in os.listdir(animal_path)[:n_images_per_animal]:
        
      # Load the image 
      animal_image = cv2.imread(f"{animal_path}/{image_name}")
        
      # Resize to the same size
      animal_image_resized = cv2.resize(animal_image, resize, interpolation=cv2.INTER_AREA)
        
      # Add it to the animal's list of images
      animals[animal_class] += [animal_image_resized]
  return animals
  
# Load Animals
animals_16imgs = load_animal_images(animal_10_folder, animal_class_names, n_samples_per_animal, image_size)

现在,我们可以为每只动物显示一张图像,以检查数据是否正确加载。在代码2.3中可以找到每只动物显示一幅图像的代码。

代码2.3-每只动物显示一张图像

# Go through each animals
for animal_name in animals_16imgs:
    
  # Load the first image for the animal
  animal_image = animals_16imgs[animal_name][0] 
    
  # Show image
  cv2.imshow('Animal Image',animal_image)
  cv2.waitKey(0)

随后,可以通过使用代码2.4使用线性判别分析(LDA)将图像转换为2D。在将图像转换为2D之前,必须“拟合”LDA。

fit是教LDA如何转换数据的过程。通过首先定义变换完成后图像应该具有多少维度,然后给出哪些图像属于同一类的LDA示例(即,给出LDA图像并告诉它们中的动物)来完成拟合。它使用这些信息来确定如何转换这些和未来的图像。最后一步是使用LDA将16x4图像转换为2D。

代码2.4-将动物图像转换为2D

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA

# create LDA that transforms data to 2D
lda = LDA(n_components = 2)

# Create a list of images and a list of animals they belong to 
animal_images = []
animal_names = []
for animal_name, images in animals_16imgs.items():
    
    # Use flatten() to transforms the image to a vector
    animal_images += [image.flatten() for image in images] 
    animal_names += [animal_name for _ in images]

# Fit the LDA so it can be used to transform the data to 2D
lda.fit(animal_images, animal_names)

# Function for transforming points to 2D
def transform_w_prefitted_lda(prefitted_lda, animals):
    # Transform the data to 2D
    transformed_animals = {}
    for animal_name, images in animals.items():
        animal_vectors = [image.flatten() for image in images]
        reduced_vectors = prefitted_lda.transform(animal_vectors)
        transformed_animals[animal_name] = reduced_vectors
    return transformed_animals

# Transform images
animals_16imgs_2D = transform_w_prefitted_lda(lda, animals_16imgs)

二维点可以用代码2.5绘制成图2.3所示的图形。

代码2.5-绘制多个类中的二维点,每个类都有独特的颜色。图表应符合图2.3。

from matplotlib import pyplot as plt

def visualize_2D_points(points):
    plt_figure = plt.figure()
    for vectors_2D in points.values():
        x = [vector[0] for vector in vectors_2D]
        y = [vector[1] for vector in vectors_2D]
        plt.plot(x, y, 'o')
    plt_figure.show()
    plt.show()

visualize_2D_points(animals_16imgs_2D)

2.2性能

我们在第1章中学习了如何使用k-Means将k聚类应用于无颜色数据(无类别/无预定义动物的数据)。K-Means专注于在数据中找到K组,并在每个组的中心放置一个点。该点位于中心以最好地表示其组,因为它与所有点的距离最短。

我们的案例与K-Means稍有不同,因为我们已经知道这些组(相同动物物种的图像来自同一组)。然而,K-Means的想法可以通过在每组/动物的中心放置一个点来最好地表示它。

图2.6使用公式2.1添加了新的中心点。代码2.6显示了如何添加簇并绘制新图形。

图2.6-每组/动物的中心用黑色边框突出显示的新点标记。橙色:狗,红色:牛,绿色:鸡,蓝猫:

64e8784f41747d0d98cefb4cf1f012e0.png

公式2.1-计算组的中心

25df81636b9b1ebf4dc92244cdd7529e.png
import numpy

# Find center of each class
def find_class_centers(classes):
    centers = {}
    for name, vectors in classes.items():
        centers[name] = [numpy.mean([vector.flatten() for vector in vectors], axis=0)][0]
    return centers

centers_2D = find_class_centers(animals_16imgs_2D)

# Plot new graph with both the animals and centers
plt_figure = plt.figure()
for animal_name, vectors_2D in animals_16imgs_2D.items():
    x = [vector[0] for vector in vectors_2D] + [centers_2D[animal_name][0]]
    y = [vector[1] for vector in vectors_2D] + [centers_2D[animal_name][1]]
    plt.plot(x, y, 'o')
plt_figure.show()
plt.show()

棘手的部分是,当我们得到一个新图像并想知道上面有哪只动物时,会发生什么。

图2.7显示,我们计算了从新变换图像到每个簇的距离,因为每个簇代表一个动物组。距离越小,相似度越高;使用欧氏相似性选择最近的聚类作为最佳拟合。方程2.2显示了如何计算两点之间的相似度。

图2.7-测量到每个中心的距离,并选择最近的一个作为其组

907ec2706394f12f971360894febacd5.png

公式2.2-计算两点之间欧氏相似性分数的公式4094b324b0f5b43108809a61096878c7.png

那么,我们如何计算我们的簇识别动物的能力?一个简单的方法是首先忘记每个点属于哪个组,然后使用公式2.2计算它们最接近/最相似的组。

图2.8和代码2.7使用此方法更改每个点的颜色以匹配最近的簇(继续阅读,很快就会有意义!)。

图2.8-重新分配了点,因此它们现在属于最接近它们的组

9d55199d5c2c778f63b17321894d6598.png

代码2.7-通过计算分数最接近的类别来分配分数

# Function that distributes a list of points to the class closest to them
def distribute_points(class_centers, class_points):
    
  # Put all points into a single list
  points = [vector.flatten() for vectors in class_points.values() for vector in vectors]
    
  # Create an empty map to store the points in
  distributed_points = {center: numpy.empty((0,len(points[0]))) for center in class_centers }
    
  # Distribute points
  for point in points:
        
    #Calculate distance to each class
    distance_to_animal_classes = {}
    for animal_class, center_point in class_centers.items():
        distance_to_animal_classes[animal_class] = numpy.linalg.norm(center_point - numpy.transpose(point))
        
    #Find the closest class
    closest_animal_class = min(distance_to_animal_classes, key=lambda k : distance_to_animal_classes[k])
    
    # Add point to the closest class
    distributed_points[closest_animal_class] = numpy.append(distributed_points[closest_animal_class], [point], axis=0)
  return distributed_points
  
  
# Put points into the cluster they are closest to
redistributed_points_2D = distribute_points(centers_2D, animals_16imgs_2D)

# Visualize distribution points
visualize_2D_points(redistributed_points_2D)

当我们改变每个点的颜色以匹配最近的簇时,可能会发生四件事。下面给出了一个简单的例子来解释发生了什么。该示例使用电晕测试。电晕测试可以是正的,也可以是负的。当检测结果为阳性时,可能会发生两种情况,一种是阳性,称为真阳性,另一种是假阳性。如果测试结果为阴性,同样的情况也会发生,它可能是真的,称为真阴性,也可能是假的,称为假阴性。

我们的情况更复杂,因为我们有四个结果而不是两个(即,四个簇/动物类型)。我们当时查看单个簇,以确定其性能如何。当我们确定它的表现时,同样的四件事(真阳性、假阳性、真阴性和假阴性)也会发生。让我们重点关注蓝色聚类:前后都是蓝色的点称为真阳性(TP)。以前是另一种颜色但现在是蓝色的点称为假阳性(FP)。以前是蓝色但现在是另一种颜色的点称为假阴性(FN)。最后,前后都是另一种颜色的点称为真阴性(TN)。

图2.9说明了关注蓝色簇时的每个场景。

我们必须为每个簇继续此过程,以确定每个簇的性能。

图2.9-通过观察重新分配点后有多少真、假阳性和真、假阴性,可以衡量簇的性能

af9fc31c159c8f7f7c389fc678e93383.png

我们在图2.8中使用的方法是有问题的,因为我们只使用现有的点来评估性能。这些点被用于创建/拟合LDA,LDA自然更善于正确获取这些点。好的是,我们在Animal-10数据集中有更多的图像可以测试!代码2.8显示了如何加载每个动物类别的128幅图像并将其转换为2D。

代码2.8-为每个动物类别加载128张图像,并使用代码2.2和2.4中定义的函数将其转换为2D。

# Load 128 images for each animal 
animals_all = load_animal_images(animal_10_folder, animal_class_names, 128, image_size)

#Transform points with the prefitted LDA
animals_all_2D = transform_w_prefitted_lda(lda, animals_all)

一个簇的性能如何可以通过Precision和Recall来衡量[4]。精确性衡量真阳性和假阳性之间的比率。召回率测量真阳性和假阴性之间的比率。

精度方程如方程2.3所示,召回方程如方程2.4所示。代码2.9计算所有512个图像(每个类别128个图像)的每个聚类的阳性和阴性以及精度和召回率。

公式2.3——计算簇精度的公式。

0ca9b5ec6979180f1f6d69eca916f642.png

公式2.4-计算簇召回的公式。

473f19e7b7ea612103b1e5116538df6c.png

代码2.9-使用所有512(128*4类)2D图像计算每个簇的性能

def calculate_accuracy_redistributed(original_points, redistributed_points):
    clusters_accuracy = {}
    for cluster_name in original_points:
        original_cluster_points = set([tuple(point.flatten()) for point in original_points[cluster_name]])
        redistributed_cluster_points = set([tuple(point.flatten()) for point in redistributed_points[cluster_name]])

        #Find how many in the redistributed set is also in the original set
        true_positive = len(original_cluster_points.intersection(redistributed_cluster_points)) 
        false_positive = len(redistributed_cluster_points) - true_positive
        false_negative = len(original_cluster_points) - true_positive

        accuracy = {"precision": true_positive / (true_positive + false_positive), 
                    "recall": true_positive / (true_positive + false_negative), 
                    "metric": { "TP": true_positive, 
                                "FP": false_positive, 
                                "FN": false_negative}
        }
        clusters_accuracy[cluster_name] = accuracy

    return clusters_accuracy
  
# Distribute all points
redistributed_all_points_2D = distribute_points(centers_2D, animals_all_2D)
 
# Calculate Accuracy
performance_2D = calculate_accuracy_redistributed(animals_all_2D, redistributed_all_points_2D)
 
# Show Accuracy
def show_accuracy(name, performance_dict):
    print(name)
    for key, value in performance_dict.items():
        print(f"{key}: {value}")
 
show_accuracy("2D Points", performance_2D)

2D图像的精度和召回结果见表2.1。平均准确率为34%,平均召回率为33%,这一点都不太好!但等等,我们只使用了两个维度?让我们对所有100.000个维度进行相同的计算,看看它是否比以前更好。

表2.1-使用LDA将点转换为2D时的精度和召回

f1684480dd8094b75754dc30e469c51c.png

表2.2显示了所有100.000维度的结果,以及代码2.10如何获取这些结果。平均准确率为31%,平均召回率为30%,这比以前还要糟糕!LDA表现得更好,因为它专注于将每个组分开,并使它们更为明显,即使只是一点点。

表2.2-在不进行任何转换的情况下的精度和召回

86058401854727d7a7903c1fbc6555c9.png

代码2.10-使用所有维度而不转换数据时计算聚类精度的代码

# Find Cluster centers with 16 points
centers_allD = find_class_centers(animals_16imgs)

# Redistribute points to match the closest cluster
redistributed_points_allD = distribute_points(centers_allD, animals_all)

# Calculate accuracy
performance_allD = calculate_accuracy_redistributed(animals_all, redistributed_points_allD)

# Print accuracy
show_accuracy("All-D Points", performance_allD)

但是等等…LDA只使用了两个维度…如果两者都转换数据并让其具有更多维度呢?LDA的最大维数是“类数-1”,在我们的例子中是4–1=3。表2.3显示了当我们使用所有三个维度时的结果,代码2.11显示了如何获得它。平均准确率为38%,平均召回率为38%。虽然仍然不是很好,但比以前更好!

表2.3-使用LDA将点转换为3D时的精度和召回

aec7ca086042359d639204b697603802.png

代码2.11-使用LDA将聚类精度转换为3D时计算聚类精度的代码

# Transform to 3D
lda = LDA(n_components = 3)
lda.fit(animal_images, animal_names)
animals_3D = transform_w_prefitted_lda(lda, animals_all)

# Find Cluster centers with 16 points
centers_3D = find_class_centers(animals_3D)

# Redistribute points to match the closest cluster
redistributed_points_3D = distribute_points(centers_3D, animals_3D)

# Calculate accuracy
performance_3D = calculate_accuracy_redistributed(animals_3D, redistributed_points_3D)

# Print accuracy
show_accuracy("3D Points", performance_allD)

2.3讨论

3D LDA仅得分38%,因为它只使用每组16幅图像来训练LDA,并且与未变换图像一样,它只查看像素颜色。

当训练时,更多的图像为LDA提供了更多的上下文,因为它有更多的图像可以绘制在图上的示例。

如果我们有16张图像而不是一张图像,那么更容易判断同一组的未来点在哪里。同样,128张图片提供的信息比16张图片更多。图2.10绘制了每组128幅图像;左图使用在64(16*4)个图像上训练的LDA,右图使用在512(128*4)张图像上训练过的LDA。该图说明了当在更多图像上训练时,LDA如何更好地分离点。代码2.11显示了如何绘制这两个图。

LDA仅在变换图像时查看像素颜色。该方法受到限制,因为它将像素的颜色优先于图像中的颜色。这意味着棕色的猫和棕色的狗会比白色的狗和棕色的猫更相似!

图2.10–使用LDA绘制的512张动物图像,每组16张图像(左)和128张图像(右)

a628b0dc4e7d8dd68b9060e021445058.png

代码2.11-创建一个新的LDA,将图像转换为2D,并使用每组128个图像进行训练。

# Create a new LDA
lda_128 = LDA(n_components = 2)

# Create a list of images and a list of animals they belong to 
animal_images = []
animal_names = []
for animal_name, images in animals_all.items():
    # Use flatten() to transforms the image to a vector
    animal_images += [image.flatten() for image in images] 
    animal_names += [animal_name for _ in images]

# Fit the LDA so it can be used to transform the data to 2D
lda_128.fit(animal_images, animal_names)
animals_2D_128 = transform_w_prefitted_lda(lda_128, animals_all)

# Plot Graphs
visualize_2D_points(animals_all_2D)
visualize_2D_points(animals_2D_128)

在第3章“图像潜空间嵌入简介”中,我们将利用我们所了解的图像嵌入和LDA的优点和缺点来提高动物识别应用程序的准确性。


2.4结论

我们现在完成了第2章以及图像嵌入和准确性的介绍。

第一部分“图像嵌入”解释了图像由像素组成,每个像素代表一种颜色。每种颜色都是一个维度,就像第一章中的“人生旅程”和“小说”是书籍的维度一样。我们使用了来自四个动物组(即猫、狗、鸡和牛)的64幅图像来展示如何在Python中使用图像嵌入。使用线性判别分析(LDA)将每个图像转换为2D,从而可以将它们绘制在图中并显示它们的相似性。

第二部分,簇性能,研究了我们如何通过在每个动物组的中心放置一个点来表示它们。这些点可以通过计算图像最接近哪个中心来帮助识别图像中的动物。精度和召回是确定方法识别正确动物的两种方法。精度检查有多少图像被识别为正确的动物,召回检查每个中心在包括其物种的所有图像方面有多好。在没有变换的图像和使用LDA变换为2D和3D的图像上测试了性能。

Precision和Recall表明,用LDA变换的图像比未变换的图像更能识别正确的动物种类(38%对31%)。原因是LDA对图像进行变换,以使每个组更清晰。然而,图像和LDA只关注颜色,而不是图像中的形式。这意味着这两种方法都认为棕色猫和棕色狗比棕色狗和白色狗更相似。

参考引用

[1] Corrado Alessio, Animals-10, Kaggle.com

[2] Casey Cheng, Principal Component Analysis (PCA) Explained Visually with Zero Math (2022), Towardsdatascience.com

[3] YANG Xiaozhou, Linear Discriminant Analysis, Explained (2020), Towardsdatascience.com

[4] Google Developers, Classification: Precision and Recall (2022), developers.google.com

☆ END ☆

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。

扫描二维码添加小编↓

ef5976f387759a14ca954fe4459930ec.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值