【深度学习】基于注意力机制的多实例学习(MIL)图像分类

1. 引言

1.1.研究背景

在图像分类任务中,我们通常持有一个直观的假设,即每张图像都可以清晰无误地归属于一个特定的类别标签。然而,在医学成像领域,特别是在计算病理学等复杂的应用中,情况则有所不同。在这些情况下,整个图像往往由单一的类别标签(如癌症/非癌症)进行标注,或者可能只是标记了图像中的一个或多个感兴趣区域(ROI)。

但医学图像分析的目标远不止于此。医学专家不仅想要知道一个图像是否属于某个疾病类别,他们更希望深入了解是图像中的哪些具体模式、纹理、结构或异常导致了这一分类结果。这是因为了解这些具体的病理特征有助于更精确地诊断疾病、制定治疗计划以及监测疾病的进展。

因此,在医学图像分析中,研究人员开始采用多实例学习(MIL)的方法来处理这些问题。MIL允许我们将整个图像视为一个包含多个实例(子图像或图像块)的袋子,并尝试从这些实例中识别出与类别标签最相关的特征。这种方法不仅提高了分类的准确性,而且还为我们提供了关于哪些图像部分对分类结果贡献最大的宝贵信息。

通过应用MIL,我们可以训练模型来预测整个图像的类别标签,同时还可以揭示出图像中哪些特定的模式或结构导致了这一预测。这对于提高医学图像分析的准确性、可解释性和临床价值具有重要意义。

1.2. 多实例学习(MIL)图像分类技术

在图像分类任务中,我们通常需要为每个图像分配一个明确的类别标签。然而,在某些复杂场景中,如医学图像分析,整个图像可能由单个类别标签表示,但我们更关注图像中哪些具体区域或模式导致了这一分类。多实例学习(MIL)就是为解决这类问题而提出的,它将图像视为由多个实例(如图像块)组成的“包”,并通过学习这些实例的特征和权重来进行分类。

1.2.1.MIL的基本原理

在MIL中,每个包都有一个明确的类别标签,但包中的每个实例可能并不都有标签。如果一个包中至少含有一个正例(即属于目标类别的实例),则这个包被标注为正包;否则,它被视为负包。通过这种方式,MIL可以在不完全标注的数据上进行训练,并学习如何区分不同类别的包。在图像分类中,这意味着模型能够学习到哪些图像区域与目标类别最相关。

1.2.2.MIL在图像分类中的应用**

MIL在医学图像分析等领域中得到了广泛应用。例如,在乳腺癌组织病理学图像分类中,可以将图像视为包含多个实例的包,并利用MIL模型来学习哪些图像块与癌症最相关。通过引入注意力机制,模型可以自动关注图像中的关键区域,并提高分类的准确性和可解释性。类似地,MIL也可以应用于其他医学图像分析任务,如肺部结节检测和皮肤病变识别等。

1.2.3.MIL的挑战

尽管MIL在图像分类中展现出了强大的潜力,但它也面临一些挑战。首先,数据标注问题是一个关键问题。由于MIL要求每个包都有明确的类别标签,但在实际应用中往往难以获得大量的准确标注数据。此外,如何有效地利用有限的标注数据进行模型训练也是一个需要解决的问题。其次,模型的复杂度也是一个挑战。引入注意力机制等复杂结构会增加模型的复杂度,从而增加计算成本和过拟合的风险。最后,如何以更直观、可解释的方式呈现MIL模型的预测结果也是一个待解决的问题。

1.2.3.MIL的未来展望

随着深度学习技术的不断发展和完善,多实例学习图像分类技术有望在未来得到更广泛的应用和深入的研究。一方面,我们可以探索更先进的模型结构和优化算法来提高MIL模型的性能。另一方面,我们也可以研究如何将MIL与其他技术(如弱监督学习、迁移学习等)相结合,以应对更复杂的图像分类任务。此外,提高MIL模型的可解释性和可视化程度也是未来的一个重要研究方向。通过深入研究这些问题,我们可以进一步推动MIL在图像分类领域的发展和应用。

1.3. 注意力学习机制

注意力学习机制(Attention Mechanism)是一种模拟人类注意力分配的计算模型,它在机器学习和深度学习中被广泛应用以提高模型的性能。

1.3.1. 定义与起源
  • 定义:注意力学习机制是一种计算资源分配方案,允许模型在处理大量信息时,将有限的计算资源分配给更重要的任务或数据部分。
  • 起源:注意力学习机制源于对人类视觉的研究,特别是在认知科学中,人类会选择性地关注所有信息的一部分,同时忽略其他信息。
1.3.2. 基本思想
  • 核心目标:从众多信息中选出对当前任务目标更加关键的信息。
  • 实现方式
    • 通过一个可学习的权重来表示不同输入数据的重要性。
    • 权重越大,表示对应的输入数据越重要。
    • 使用这些权重在不同的层次上对输入数据进行加权聚合,获得一个经过注意力加权的表示。
1.3.3. 分类
  • 聚焦式(Focus)注意力:自上而下的有意识的注意力,有预定目的、依赖任务的、主动有意识地聚焦于某一对象的注意力。
  • 基于显著性(Saliency-based)的注意力:自下而上的无意识的注意力,由外界刺激驱动的注意,不需要主动干预,也和任务无关。
1.3.4. 应用领域
  • 序列相关的任务:如机器翻译、语音识别、图像描述等。在这些任务中,注意力机制允许模型在处理每个序列元素时,动态地聚焦于与当前元素相关的上下文。
  • 医学图像分析:在医学成像中,如计算病理学,注意力机制可以帮助模型关注图像中与疾病最相关的区域。
1.3.5. 注意力机制的变体
  • 自注意力机制(Self-Attention Mechanism):允许序列中的每个元素与序列中的其他元素建立关联,通过计算元素之间的相对重要性来捕捉长程依赖关系。
  • 多头注意力(Multi-head Attention):利用多个查询来并行地从输入信息中选取多个信息,每个注意力关注输入信息的不同部分。
  • 软注意力(Soft Attention):通过计算目标位置与输入位置之间的关联程度,得到一个权重矩阵,该矩阵表示输入中不同位置对于当前目标位置的注意力分布。

注意力学习机制是一种强大的工具,它允许模型在处理复杂任务时,像人类一样选择性地关注关键信息,从而提高模型的性能和泛化能力。随着深度学习技术的不断发展,注意力学习机制将在更多领域得到应用。

2. MIL实现图像分类的过程

本文的目标是:

  • 学习一个模型来预测一组实例的袋子的类别标签。
  • 找出袋子中哪些实例导致了正类别标签的预测。

通过研究实现:

  • 特征提取层提取特征嵌入。
  • 嵌入被输入到MIL注意力层以获取注意力得分。该层设计为排列不变的。
  • 输入特征和其对应的注意力得分相乘。
  • 得到的输出被传递给softmax函数进行分类。

2.1.安装和设置

# 导入NumPy库,用于科学计算  
import numpy as np  
  
# 导入Keras库,一个高级神经网络API,由TensorFlow、Theano或CNTK后端支持  
import keras  
from keras import layers  # 导入Keras的层模块,用于构建神经网络层  
# 注意:ops模块在Keras中通常不是直接导入的,除非你有特定的操作需要导入  
# from keras import ops  # 这行可以删除或替换为你需要的特定操作  
  
# 导入tqdm库,用于快速、可扩展的进度条  
from tqdm import tqdm  
  
# 导入matplotlib的pyplot模块,用于绘图  
from matplotlib import pyplot as plt  
  
# 设置matplotlib的绘图样式为"ggplot",使图表更美观  
plt.style.use("ggplot")  

2.2.数据预处理

当我们创建数据集时,特别是在处理多实例学习(Multi-Instance Learning)问题时,我们经常需要定义和分类所谓的“袋子”(bags)。这些“袋子”可以看作是一组实例(instances)的集合,其中每个实例可能属于不同的类别。在特定的场景中,我们关心的是如何根据袋子中实例的类别标签来确定整个袋子的类别标签。

下面,我们将详细阐述如何创建这样一组袋子,并根据它们的内容来分配标签。

首先,我们假设每个袋子中包含一定数量的实例,这些实例可能是图像块、文本片段或其他任何类型的数据。每个实例都有一个与之关联的类别标签,表示它是否属于某个特定的类别(例如,正类或负类)。

接下来,我们定义一个规则来确定每个袋子的类别标签。在这个场景中,我们采用一个简单的规则:如果一个袋子中至少包含一个正实例(即,属于目标类别的实例),那么整个袋子就被视为正袋子。相反,如果袋子中的所有实例都是负实例(即,不属于目标类别),那么该袋子就被视为负袋子。

为了创建这样的数据集,我们可以采取以下步骤:

  1. 生成实例:首先,我们需要生成一定数量的实例,并为每个实例分配一个类别标签(正类或负类)。这可以通过从现有数据集中抽取样本、使用模拟数据生成器或根据特定需求手动创建实例来完成。
  2. 创建袋子:接下来,我们将生成的实例组合成袋子。每个袋子可以包含任意数量的实例,具体数量可以根据实际应用场景和需求来确定。
  3. 分配袋子标签:根据之前定义的规则,我们遍历每个袋子,并检查其中的实例。如果至少有一个实例是正类,则将袋子标记为正袋子;否则,将其标记为负袋子。
  4. 构建数据集:最后,我们将所有标记好的袋子组合成一个数据集。这个数据集将用于训练多实例学习模型,以便模型能够学习如何根据袋子中实例的类别标签来预测整个袋子的类别标签。

通过这种方法,我们可以创建一个用于多实例学习问题的数据集,其中每个袋子都根据其内容被正确地分配了类别标签。这样的数据集对于训练能够处理复杂数据结构(如袋子)的机器学习模型非常有用。

2.2.1配置参数

首先需要配置一下参数:

  1. POSITIVE_CLASS
    此参数定义了在一个“正包”中应保留的类别。在多实例学习的上下文中,一个包(bag)被标记为正类(positive class),如果该包内至少含有一个属于特定类别的实例。这个特定的类别就是POSITIVE_CLASS所指定的。例如,在文本分类任务中,POSITIVE_CLASS可能代表“积极情感”或“相关主题”。

  2. BAG_COUNT
    这个参数指定了用于模型训练的数据集中训练包的总数。包(bag)是多实例学习中的一个关键概念,它包含多个实例(instances)。BAG_COUNT的值将影响模型的泛化能力和训练时间。较大的BAG_COUNT值通常意味着更丰富的训练数据,但也可能导致更长的训练时间。

  3. VAL_BAG_COUNT
    这个参数指定了用于模型验证的数据集中验证包的数量。验证集用于在训练过程中监控模型的性能,并在不实际修改模型结构或参数的情况下,对模型的泛化能力进行评估。VAL_BAG_COUNT的值应该足够大,以便能够可靠地评估模型的性能,但也不需要过大,以免浪费计算资源。

  4. BAG_SIZE
    这个参数定义了每个包中应包含的实例数量。BAG_SIZE的值将影响每个包所包含的信息量和模型学习该信息的能力。较小的BAG_SIZE值可能导致模型无法充分利用包内的所有信息,而较大的值可能会引入冗余信息或增加计算复杂性。因此,在选择BAG_SIZE时,需要根据具体任务和数据集的特点进行权衡。

  5. PLOT_SIZE
    这个参数指定了在可视化过程中要绘制的包的数量。可视化是理解和分析模型性能的重要工具,通过绘制一定数量的包,我们可以直观地了解模型在不同类型包上的表现。PLOT_SIZE的值通常根据可视化的目的和可用资源来确定。

  6. ENSEMBLE_AVG_COUNT(集成平均数量):
    这个参数(可选)指定了在集成学习中要创建并平均的模型数量。集成学习是一种通过构建多个模型并将它们的预测结果组合起来以提高性能的技术。ENSEMBLE_AVG_COUNT的值越大,通常意味着更多的模型将被用于集成,这可能会提高预测精度但也会增加计算成本。当设置为1时,表示仅使用单个模型进行预测。根据具体任务和数据集的特点,可以调整这个参数以找到性能和计算成本之间的最佳平衡点。

# 配置参数  
# 正包中需要保留的类别  
POSITIVE_CLASS = 1  
# 训练包的数量  
BAG_COUNT = 1000  
# 验证包的数量  
VAL_BAG_COUNT = 300  
# 每个包中的实例数量  
BAG_SIZE = 3  
# 要绘制的包的数量(用于可视化)  
PLOT_SIZE = 3  
# 集成学习中要创建并平均的模型数量(可选,设为1表示使用单个模型)  
ENSEMBLE_AVG_COUNT = 1  
  
# 注意:以下代码块只是配置参数的声明,并未包含实际的模型训练、验证或可视化代码。  
# 在实际使用中,您需要根据这些配置参数来编写相应的代码逻辑。  
  
# 示例:打印配置参数  
print(f"POSITIVE_CLASS: {POSITIVE_CLASS}")  
print(f"BAG_COUNT: {BAG_COUNT}")  
print(f"VAL_BAG_COUNT: {VAL_BAG_COUNT}")  
print(f"BAG_SIZE: {BAG_SIZE}")  
print(f"PLOT_SIZE: {PLOT_SIZE}")  
print(f"ENSEMBLE_AVG_COUNT: {ENSEMBLE_AVG_COUNT}")  
  
# 在这里,您可以继续编写创建数据集、训练模型、验证模型以及可视化的代码。
2.2.2.准备包

在准备多实例学习(Multi-Instance Learning)中的“包”(bags)时,我们需要考虑每个包中实例的排列对模型学习的影响。由于注意力机制(Attention Mechanism)具有排列不变性(permutation-invariant),即它对输入序列中元素的顺序不敏感,我们可以利用这一特性来简化包的构造过程。

在准备正包(positive bags)时,我们会将至少一个带有正类别标签(如POSITIVE_CLASS)的实例随机放置在包内的其他实例中。这意味着,无论这些正实例在包内的位置如何,模型的注意力机制都能够捕捉到它们,并据此对整个包进行正确的分类。

以下是扩展后的步骤说明:

  1. 初始化包:首先,我们创建指定数量的空包,每个包将包含固定数量的实例(由BAG_SIZE定义)。

  2. 填充负实例:对于每个包(无论是正包还是负包),我们先填充一些带有负类别标签的实例。这些实例的数量取决于包的大小(BAG_SIZE)和是否需要放置正实例(对于正包)。

  3. 随机放置正实例(仅针对正包):对于正包,我们需要确保至少有一个实例带有正类别标签。我们将这个正实例随机放置在包内的某个位置。由于注意力机制的排列不变性,这个实例的确切位置并不重要。

  4. 生成多个包:根据BAG_COUNTVAL_BAG_COUNT,我们生成相应数量的训练和验证包。这些包将用于训练多实例学习模型,并在验证集上评估其性能。

  5. 准备可视化数据(可选):如果需要可视化一些包来直观理解模型的工作方式,我们可以选择PLOT_SIZE数量的包进行可视化。

  6. (可选)集成学习:如果计划使用集成学习来提高模型的性能(通过ENSEMBLE_AVG_COUNT指定),则需要为每个集成成员准备单独的训练和验证包集。

在编写代码实现这些步骤时,我们需要注意以下几点:

  • 确保正包中至少有一个正实例。
  • 随机放置正实例时,应使用随机数生成器来确保结果的随机性。
  • 如果使用集成学习,需要为每个集成成员分别准备训练和验证数据。

通过这种方式,我们可以有效地准备多实例学习所需的包,并利用注意力机制的排列不变性来简化包的构造过程。

import numpy as np
from keras.datasets import mnist

# 定义创建数据包的函数
def create_bags(input_data, input_labels, positive_class, bag_count, instance_count):
    # 初始化数据包列表和标签列表
    bags = []
    bag_labels = []

    # 将输入数据归一化
    input_data = np.divide(input_data, 255.0)

    # 初始化正样本计数器
    count = 0

    # 循环创建指定数量的数据包
    for _ in range(bag_count):
        # 从输入数据中随机选择固定数量的样本,不重复选择
        index = np.random.choice(input_data.shape[0], instance_count, replace=False)
        instances_data = input_data[index]
        instances_labels = input_labels[index]

        # 默认情况下,所有数据包的标签为0(负样本)
        bag_label = 0

        # 检查数据包中是否至少包含一个正样本
        if positive_class in instances_labels:
            # 如果包含正样本,则将数据包标签设置为1(正样本)
            bag_label = 1
            count += 1

        # 将样本数据添加到数据包列表中
        bags.append(instances_data)
        # 将数据包标签添加到标签列表中
        bag_labels.append(np.array([bag_label]))

    # 打印正样本数据包的数量
    print(f"正样本数据包数量: {count}")
    # 打印负样本数据包的数量
    print(f"负样本数据包数量: {bag_count - count}")

    # 返回转换后的样本数据和标签数组
    return (list(np.swapaxes(bags, 0, 1)), np.array(bag_labels))

# 载入MNIST数据集
(x_train, y_train), (x_val, y_val) = mnist.load_data()

# 定义正样本类别、数据包数量、每个数据包的样本数量
POSITIVE_CLASS = 5  # 假设正样本类别为数字5
BAG_COUNT = 1000      # 训练数据包数量
BAG_SIZE = 20         # 每个数据包的样本数量
VAL_BAG_COUNT = 500   # 验证数据包数量

# 创建训练数据
train_data, train_labels = create_bags(
    x_train, y_train, POSITIVE_CLASS, BAG_COUNT, BAG_SIZE
)

# 创建验证数据
val_data, val_labels = create_bags(
    x_val, y_val, POSITIVE_CLASS, VAL_BAG_COUNT, BAG_SIZE
)

2.3.创建模型

2.3.1.注意力机制的实现

在构建多实例学习模型时,注意力机制是一种有效的工具,用于确定包中不同实例对于整体预测结果的重要性。以下是注意力机制实现的一个扩展说明,以及它如何在多实例学习的模型中被使用。

1. 注意力机制的基本概念

注意力机制允许模型关注于输入数据中最重要的部分。在多实例学习的上下文中,这意味着模型可以学习如何为包中的每个实例分配不同的权重,以便在做出预测时考虑它们的不同贡献。

2. 注意力层的输出大小

注意力层的输出大小由单个包中的实例数量(BAG_SIZE)决定。然而,实际输出的可能是一个权重向量,其长度与包中的实例数量相同,并且这些权重之和等于1。这些权重将被用于计算包的加权表示。

3. 注意力机制的实现步骤

  • 初始化权重矩阵:首先,我们初始化两个权重矩阵w和v,以及(如果使用门控注意力机制)一个额外的权重矩阵u。这些矩阵将通过模型训练来学习。

  • 计算实例的嵌入表示:将包中的每个实例通过某种嵌入函数(如全连接层、卷积层等)转换为嵌入向量。

  • 计算实例得分:将每个实例的嵌入向量与权重矩阵w相乘,并通过双曲正切(tanh)函数进行非线性变换,得到每个实例的得分。这个得分表示了该实例与任务相关的程度。

  • (可选)使用门控注意力:如果使用了门控注意力机制,我们还需要计算另一个得分。这个得分通过将嵌入向量与权重矩阵u相乘,并通过sigmoid函数进行非线性变换得到。这个得分将用于控制tanh得分的权重。

  • 计算权重:将tanh得分(或tanh得分与sigmoid得分的乘积,如果使用门控注意力)与权重矩阵v相乘,得到每个实例的原始权重。然后,使用softmax函数将这些原始权重归一化,确保它们的和等于1。

  • 计算加权表示:将每个实例的嵌入向量与其对应的权重相乘,并将结果相加,得到包的加权表示。这个加权表示将作为注意力层的输出,并用于后续的预测任务。

注意力机制是一种强大的工具,它可以帮助多实例学习模型更好地理解和利用包中的信息。通过为每个实例分配不同的权重,模型可以关注于最重要的信息,并做出更准确的预测。在实现注意力机制时,我们需要注意选择合适的嵌入函数、权重矩阵的初始化方法以及损失函数等关键组件,以确保模型的有效性和性能。

from tensorflow import keras
from tensorflow.keras import layers
import tensorflow as tf

class MILAttentionLayer(layers.Layer):
    """多实例学习中基于注意力机制的深度层实现。

    参数:
      weight_params_dim: 正整数。权重矩阵的维度。
      kernel_initializer: 'kernel' 矩阵的初始化器。
      kernel_regularizer: 应用于 'kernel' 矩阵的正则化函数。
      use_gated: 布尔值,是否使用门控机制。

    返回:
      2D 张量列表,长度为 BAG_SIZE。
      张量是应用 softmax 后的注意力分数,形状为 `(batch_size, 1)`。
    """

    def __init__(
        self,
        weight_params_dim,
        kernel_initializer="glorot_uniform",  # 权重初始化器,默认为 Glorot 均匀分布
        kernel_regularizer=None,  # 权重正则化器,如果为 None,则不使用正则化
        use_gated=False,  # 是否使用门控机制
        **kwargs  # 其他参数
    ):
        super().__init__(**kwargs)  # 调用父类初始化方法

        # 初始化属性
        self.weight_params_dim = weight_params_dim
        self.use_gated = use_gated

        # 获取初始化器和正则化器
        self.kernel_initializer = keras.initializers.get(kernel_initializer)
        self.kernel_regularizer = keras.regularizers.get(kernel_regularizer)

        # 初始化 v, w, u 的初始化器和正则化器
        self.v_init = self.kernel_initializer
        self.w_init = self.kernel_initializer
        self.u_init = self.kernel_initializer
        self.v_regularizer = self.kernel_regularizer
        self.w_regularizer = self.kernel_regularizer
        self.u_regularizer = self.kernel_regularizer

    def build(self, input_shape):
        # 输入形状,输入是列表形式的 2D 张量,形状为 (batch_size, input_dim)
        input_dim = input_shape[0][1]

        # 添加权重参数 v
        self.v_weight_params = self.add_weight(
            shape=(input_dim, self.weight_params_dim),
            initializer=self.v_init,
            name="v",
            regularizer=self.v_regularizer,
            trainable=True,
        )

        # 添加权重参数 w
        self.w_weight_params = self.add_weight(
            shape=(self.weight_params_dim, 1),
            initializer=self.w_init,
            name="w",
            regularizer=self.w_regularizer,
            trainable=True,
        )

        # 如果使用门控机制,添加权重参数 u
        if self.use_gated:
            self.u_weight_params = self.add_weight(
                shape=(input_dim, self.weight_params_dim),
                initializer=self.u_init,
                name="u",
                regularizer=self.u_regularizer,
                trainable=True,
            )
        else:
            self.u_weight_params = None

        # 设置输入构建完成标志
        self.input_built = True

    def call(self, inputs):
        # 计算每个输入实例的注意力分数
        instances = [self.compute_attention_scores(instance) for instance in inputs]

        # 将实例堆叠成一个张量
        instances = tf.stack(instances)

        # 对实例应用 softmax,使得输出的和为 1
        alpha = tf.nn.softmax(instances, axis=0)

        # 分割 alpha 以重新创建与输入相同的张量数组
        return [alpha[i] for i in range(alpha.shape[0])]

    def compute_attention_scores(self, instance):
        # 保留原始实例,以备使用门控机制
        original_instance = instance

        # 计算 tanh(v * h_k^T)
        instance = tf.keras.backend.tanh(tf.tensordot(instance, self.v_weight_params, axes=1))

        # 如果使用门控机制
        if self.use_gated:
            instance *= tf.keras.backend.sigmoid(tf.tensordot(original_instance, self.u_weight_params, axes=1))

        # 计算 w^T * (tanh(v * h_k^T)) / w^T * (tanh(v * h_k^T) * sigmoid(u * h_k^T))
        return tf.tensordot(instance, self.w_weight_params, axes=1)
2.3.2.定义可视化函数

在多实例学习的环境中,为了更好地理解模型的行为和性能,我们提供了一个强大的可视化工具。这个工具不仅能够帮助我们直观地看到不同类别下的包数量分布,还能在模型训练完成后,展示每个包的类别标签预测结果以及与之关联的实例得分。

1. 绘制类别与包数量的关系图

通过该可视化工具,我们可以根据PLOT_SIZE所指定的数量,绘制出不同类别下的包数量分布图。这个图表将清晰地展示出每个类别所拥有的包的数量,从而帮助我们了解数据集的分布情况,以及模型在各类别上的学习难度。

2. 展示模型预测结果与实例得分

在模型训练完成后,该可视化工具还能够展示每个包的类别标签预测结果。这意味着,我们可以直观地看到模型对于每个包的预测类别,从而评估模型的分类性能。

更重要的是,该工具还能展示与每个包关联的实例得分。这个得分是基于注意力机制计算出的,它反映了包中每个实例对于预测结果的贡献程度。通过观察这些实例得分,我们可以深入了解模型在做出预测时的决策依据,以及哪些实例对预测结果产生了重要影响。

3. 功能扩展

为了进一步提升可视化工具的实用性,我们还可以考虑以下功能扩展:

  1. 交互式探索:允许用户通过点击或悬停来查看特定包的详细信息,如实例的具体特征、预测概率等。
  2. 动态更新:在模型训练过程中,实时更新可视化结果,以便用户能够观察模型性能的变化。
  3. 多维度展示:除了展示类别与包数量的关系外,还可以增加其他维度的可视化,如特征分布、模型误差分析等。

通过这些功能扩展,我们的可视化工具将变得更加丰富和强大,为用户提供更全面、更深入的模型理解和性能评估体验。

import numpy as np
import matplotlib.pyplot as plt

# 定义绘制数据包和注意力权重的函数
def plot(data, labels, bag_class, predictions=None, attention_weights=None, PLOT_SIZE=10, BAG_SIZE=20):
    """
    绘制数据包和注意力权重的实用工具。

    参数:
      data: 包含实例数据包的输入数据。
      labels: 输入数据关联的数据包标签。
      bag_class: 所需的数据包类别的字符串名称。
        选项有:"positive"(正样本)或 "negative"(负样本)。
      predictions: 模型预测的类别标签。
        如果未指定,将使用真实标签。
      attention_weights: 输入数据中每个实例的注意力权重。
        如果未指定,将不会显示这些值。
      PLOT_SIZE: 要绘制的数据包数量。
      BAG_SIZE: 每个数据包中的实例数量。
    """

    # 将标签转换为一维数组
    labels = np.array(labels).reshape(-1)

    # 根据数据包类别筛选标签和数据
    if bag_class == "positive":
        if predictions is not None:
            # 使用模型预测筛选正样本数据包
            labels_indices = np.where(predictions.argmax(1) == 1)[0]
            bags = np.array(data)[:, labels_indices[0:PLOT_SIZE]]
        else:
            # 使用真实标签筛选正样本数据包
            labels_indices = np.where(labels == 1)[0]
            bags = np.array(data)[:, labels_indices[0:PLOT_SIZE]]

    elif bag_class == "negative":
        if predictions is not None:
            # 使用模型预测筛选负样本数据包
            labels_indices = np.where(predictions.argmax(1) == 0)[0]
            bags = np.array(data)[:, labels_indices[0:PLOT_SIZE]]
        else:
            # 使用真实标签筛选负样本数据包
            labels_indices = np.where(labels == 0)[0]
            bags = np.array(data)[:, labels_indices[0:PLOT_SIZE]]

    else:
        print(f"没有 {bag_class} 类")
        return

    # 打印数据包类别标签
    print(f"数据包类别标签是 {bag_class}")

    # 绘制选定的数据包
    for i in range(PLOT_SIZE):
        figure, axes = plt.subplots(1, BAG_SIZE, figsize=(8, 8))
        plt.subplots_adjust(hspace=0.5, wspace=0.5)  # 调整子图之间的间距
        print(f"数据包编号: {labels_indices[i]}")
        for j in range(BAG_SIZE):
            image = bags[j][i]
            axes[j].grid(False)  # 关闭网格
            axes[j].imshow(image, cmap='gray')  # 显示图像,使用灰度色图
            axes[j].set_title(f"Instance {j+1}")  # 设置子图标题
            axes[j].axis('off')  # 关闭坐标轴
        plt.show()

# 绘制验证数据包,每个类别绘制一些示例
plot(val_data, val_labels, "positive", PLOT_SIZE=5, BAG_SIZE=20)
plot(val_data, val_labels, "negative", PLOT_SIZE=5, BAG_SIZE=20)
2.3.3.构建模型

在构建模型时,我们需要确保模型能够有效地处理多实例学习的特点,其中每个“包”包含多个实例,并且这些实例对于最终的类别预测有不同的贡献。以下是对创建模型过程的改写和适当扩展:

模型构建

首先,我们将为输入数据中的每个实例生成嵌入表示。嵌入表示是一种将原始数据(如文本、图像或数值特征)转换为固定大小向量的技术,这些向量能够捕捉数据的内在结构和语义信息。在多实例学习的上下文中,嵌入表示将用于捕获每个实例的特征。

接下来,我们将使用注意力机制来计算每个实例的权重。注意力机制允许模型根据实例与任务的相关性来分配不同的权重。具体来说,我们将通过一个可学习的函数(如神经网络层)来计算每个实例的得分,并使用softmax函数将这些得分转换为权重,以确保权重之和为1。

在计算得到每个实例的权重之后,我们将这些权重与它们的嵌入表示进行加权求和,从而得到整个包的表示。这个加权求和的过程实际上是对包中所有实例的嵌入表示进行聚合,以形成一个能够代表整个包的单一向量。

最后,我们将使用softmax函数来输出类别概率。softmax函数将包的表示映射到一组概率值上,这些概率值表示该包属于不同类别的可能性。这些概率值将作为模型的最终输出,用于进行类别预测。

模型训练

在模型训练过程中,我们将使用带有标签的数据集来优化模型的参数。具体来说,我们将通过最小化预测类别与真实类别之间的交叉熵损失来训练模型。交叉熵损失是衡量模型预测与真实标签之间差异的一种指标,通过反向传播算法和梯度下降方法,我们可以更新模型的参数以最小化损失并提高预测准确性。

通过这个过程,我们可以构建一个能够处理多实例学习任务的模型,该模型能够利用注意力机制来捕捉包中不同实例的重要性,并输出准确的类别概率。

2.3.4.提取特征
from tensorflow import keras
from tensorflow.keras import layers

# 定义创建模型的函数
def create_model(instance_shape):
    # 初始化输入列表和嵌入表示列表
    inputs, embeddings = [], []

    # 定义共享的密集层
    shared_dense_layer_1 = layers.Dense(128, activation="relu")
    shared_dense_layer_2 = layers.Dense(64, activation="relu")

    # 为数据包中的每个实例创建输入和嵌入表示
    for _ in range(BAG_SIZE):
        inp = layers.Input(shape=instance_shape)  # 创建输入层
        flatten = layers.Flatten()(inp)  # 将输入展平
        dense_1 = shared_dense_layer_1(flatten)  # 第一个共享密集层
        dense_2 = shared_dense_layer_2(dense_1)  # 第二个共享密集层
        inputs.append(inp)  # 将输入层添加到列表
        embeddings.append(dense_2)  # 将嵌入表示添加到列表

    # 调用注意力层
    alpha = MILAttentionLayer(
        weight_params_dim=256,  # 权重参数的维度
        kernel_regularizer=keras.regularizers.L2(0.01),  # 权重的 L2 正则化
        use_gated=True,  # 使用门控机制
        name="alpha",  # 层的名称
    )(embeddings)  # 将嵌入表示传递给注意力层

    # 将注意力权重与嵌入表示相乘
    multiply_layers = [
        layers.multiply([alpha[i], embeddings[i]]) for i in range(len(alpha))
    ]

    # 连接乘法层的输出
    concat = layers.concatenate(multiply_layers, axis=1)

    # 分类输出节点
    output = layers.Dense(2, activation="softmax")(concat)  # 二分类问题

    # 返回模型
    return keras.Model(inputs=inputs, outputs=output)
2.3.5.类别权重

在处理多实例学习问题时,特别是当数据集中存在类别不平衡现象时,类别权重的使用变得尤为重要。类别不平衡指的是某些类别的样本数量远多于其他类别,这可能导致模型在训练过程中偏向于多数类,从而忽视了少数类的学习。

为了解决这个问题,我们可以引入类别权重这一概念。类别权重允许我们在训练过程中给不同类别的样本分配不同的权重,以平衡模型对不同类别的关注度。具体来说,对于少数类(即正样本或稀有类别),我们可以给予更高的权重,以鼓励模型更多地关注这些类别的样本。

以您给出的例子为例,假设我们有1000个包,其中大约90%的包不包含任何正标签(即负样本),而只有大约10%的包包含正标签(即正样本)。这种数据分布是典型的类别不平衡情况。

为了处理这种不平衡,我们可以在训练模型时引入类别权重。具体来说,我们可以给正样本分配一个较高的权重,比如权重为5(或根据具体情况调整),而给负样本分配一个较低的权重,比如权重为1。这样,模型在训练过程中就会更加关注正样本,从而提高对正样本的识别能力。

类别权重的引入可以通过多种方式实现,具体取决于所使用的机器学习框架或库。在大多数深度学习框架中,如TensorFlow或PyTorch,都提供了设置类别权重的选项。通过设置类别权重,我们可以帮助模型更好地处理不平衡数据,提高模型在少数类上的性能。

需要注意的是,类别权重的设置应该根据具体的数据集和任务进行调整。过高的权重可能会导致模型过度关注少数类,而忽略多数类的学习;而过低的权重则可能无法有效地平衡类别不平衡问题。因此,在实际应用中,我们需要根据数据的具体情况和模型的表现来调整类别权重的值。

from tensorflow import keras
from tensorflow.keras import layers

# 定义创建模型的函数
def create_model(instance_shape):
    # 初始化输入列表和嵌入表示列表
    inputs, embeddings = [], []

    # 定义共享的密集层
    shared_dense_layer_1 = layers.Dense(128, activation="relu")
    shared_dense_layer_2 = layers.Dense(64, activation="relu")

    # 为数据包中的每个实例创建输入和嵌入表示
    for _ in range(BAG_SIZE):
        inp = layers.Input(shape=instance_shape)  # 创建输入层
        flatten = layers.Flatten()(inp)  # 将输入展平
        dense_1 = shared_dense_layer_1(flatten)  # 第一个共享密集层
        dense_2 = shared_dense_layer_2(dense_1)  # 第二个共享密集层
        inputs.append(inp)  # 将输入层添加到列表
        embeddings.append(dense_2)  # 将嵌入表示添加到列表

    # 调用注意力层
    alpha = MILAttentionLayer(
        weight_params_dim=256,  # 权重参数的维度
        kernel_regularizer=keras.regularizers.L2(0.01),  # 权重的 L2 正则化
        use_gated=True,  # 使用门控机制
        name="alpha",  # 层的名称
    )(embeddings)  # 将嵌入表示传递给注意力层

    # 将注意力权重与嵌入表示相乘
    multiply_layers = [
        layers.multiply([alpha[i], embeddings[i]]) for i in range(len(alpha))
    ]

    # 连接乘法层的输出
    concat = layers.concatenate(multiply_layers, axis=1)

    # 分类输出节点
    output = layers.Dense(2, activation="softmax")(concat)  # 二分类问题

    # 返回模型
    return keras.Model(inputs=inputs, outputs=output)
2.3.6.建立并训练模型
import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
from tqdm import tqdm  # 进度条库

# 定义训练模型的函数
def train(train_data, train_labels, val_data, val_labels, model):
    # 训练模型
    # 准备回调函数
    # 保存最佳权重的路径

    # 从包装器中获取文件名
    file_path = "/tmp/best_model.weights.h5"

    # 初始化模型检查点回调函数
    model_checkpoint = ModelCheckpoint(
        file_path,
        monitor="val_loss",
        verbose=0,
        mode="min",
        save_best_only=True,
        save_weights_only=True,
    )

    # 初始化早停法(Early Stopping)回调函数
    # 监控验证数据上的模型性能,并在泛化误差停止下降时停止训练
    early_stopping = EarlyStopping(
        monitor="val_loss", patience=10, mode="min"
    )

    # 编译模型
    model.compile(
        optimizer="adam",
        loss="sparse_categorical_crossentropy",
        metrics=["accuracy"],
    )

    # 拟合模型
    model.fit(
        train_data,
        train_labels,
        validation_data=(val_data, val_labels),
        epochs=20,
        class_weight=compute_class_weights(train_labels),  # 计算类别权重的函数
        batch_size=1,
        callbacks=[early_stopping, model_checkpoint],
        verbose=0,
    )

    # 加载最佳权重
    model.load_weights(file_path)

    return model

# 计算类别权重的函数(示例实现)
def compute_class_weights(labels):
    # 这里应实现计算类别权重的逻辑
    pass

# 构建模型
instance_shape = train_data[0][0].shape  # 假设 train_data 是已定义的
ENSEMBLE_AVG_COUNT = 5  # 假设 ENSEMBLE_AVG_COUNT 是已定义的,表示模型集合的数量
models = [create_model(instance_shape) for _ in range(ENSEMBLE_AVG_COUNT)]

# 显示单个模型的架构
print(models[0].summary())

# 训练模型集合
trained_models = [
    train(train_data, train_labels, val_data, val_labels, model)
    for model in tqdm(models)  # 使用 tqdm 显示训练进度
]
Model: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ Layer (type)        ┃ Output Shape      ┃ Param # ┃ Connected to         ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ input_layer         │ (None, 28, 28)0-                    │
│ (InputLayer)        │                   │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ input_layer_1       │ (None, 28, 28)0-                    │
│ (InputLayer)        │                   │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ input_layer_2       │ (None, 28, 28)0-                    │
│ (InputLayer)        │                   │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ flatten (Flatten)(None, 784)0 │ input_layer[0][0]    │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ flatten_1 (Flatten)(None, 784)0 │ input_layer_1[0][0]  │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ flatten_2 (Flatten)(None, 784)0 │ input_layer_2[0][0]  │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dense (Dense)(None, 128)100,480 │ flatten[0][0],       │
│                     │                   │         │ flatten_1[0][0],     │
│                     │                   │         │ flatten_2[0][0]      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dense_1 (Dense)(None, 64)8,256 │ dense[0][0],         │
│                     │                   │         │ dense[1][0],         │
│                     │                   │         │ dense[2][0]          │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ alpha               │ [(None, 1),33,024 │ dense_1[0][0],       │
│ (MILAttentionLayer)(None, 1), (None, │         │ dense_1[1][0],       │
│                     │ 1)]               │         │ dense_1[2][0]        │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ multiply (Multiply)(None, 64)0 │ alpha[0][0],         │
│                     │                   │         │ dense_1[0][0]        │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ multiply_1          │ (None, 64)0 │ alpha[0][1],         │
│ (Multiply)          │                   │         │ dense_1[1][0]        │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ multiply_2          │ (None, 64)0 │ alpha[0][2],         │
│ (Multiply)          │                   │         │ dense_1[2][0]        │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ concatenate         │ (None, 192)0 │ multiply[0][0],      │
│ (Concatenate)       │                   │         │ multiply_1[0][0],    │
│                     │                   │         │ multiply_2[0][0]     │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dense_2 (Dense)(None, 2)386 │ concatenate[0][0]    │
└─────────────────────┴───────────────────┴─────────┴──────────────────────┘
 Total params: 142,146 (555.26 KB)
 Trainable params: 142,146 (555.26 KB)
 Non-trainable params: 0 (0.00 B)

2.4.模型评估

模型评估与集成

在这一阶段,我们将对已经构建并训练好的模型进行评估。为了确保评估的准确性和全面性,我们不仅会评估每个独立模型的性能,还将通过集成多个模型的预测结果来得到一个更加稳健的预测。

首先,为了深入理解模型的内部工作机制,特别是注意力层如何影响预测结果,我们为每个模型创建了一个关联的“中间模型”。这个中间模型将允许我们访问注意力层的权重,从而了解模型在做出预测时是如何为不同的实例分配权重的。

接下来,我们将使用这些模型来对测试集或验证集进行预测。由于我们可能有多个模型(可能是使用不同的算法、架构或参数配置训练的),我们将为每个模型单独计算预测结果。这些预测结果将作为我们集成策略的基础。

集成策略是机器学习中的一个重要概念,它通过结合多个模型的预测结果来提高整体性能。在我们的场景中,我们将采用简单的平均集成方法,即对每个模型的预测结果进行平均,以得到最终的预测结果。这种方法假设每个模型都在某种程度上捕捉到了数据的不同方面,因此将它们的结果平均起来可以减少单个模型的偏差和方差,从而提高预测的准确性。

最后,我们将使用适当的评估指标(如准确率、召回率、F1分数等)来评估最终预测的性能。这些指标将帮助我们了解模型在分类任务上的表现,并为我们提供改进模型的方向。通过不断迭代和优化模型,我们可以进一步提高预测的准确性,并为实际应用提供更可靠的解决方案。

import numpy as np
from tensorflow import keras

# 定义预测函数
def predict(data, labels, trained_models):
    # 收集每个模型的信息
    models_predictions = []
    models_attention_weights = []
    models_losses = []
    models_accuracies = []

    ENSEMBLE_AVG_COUNT = len(trained_models)  # 模型集合的数量

    for model in trained_models:
        # 在数据上预测输出类别
        predictions = model.predict(data)
        models_predictions.append(predictions)

        # 创建中间模型以获取 MIL 注意力层权重
        intermediate_model = keras.Model(model.input, model.get_layer("alpha").output)

        # 预测 MIL 注意力层权重
        intermediate_predictions = intermediate_model.predict(data)

        # 调整注意力权重的形状
        attention_weights = np.squeeze(np.swapaxes(intermediate_predictions, 1, 0))
        models_attention_weights.append(attention_weights)

        # 评估损失和准确率
        loss, accuracy = model.evaluate(data, labels, verbose=0)
        models_losses.append(loss)
        models_accuracies.append(accuracy)

    # 打印平均损失和准确率
    print(
        f"平均损失和准确率分别是 {np.sum(models_losses, axis=0) / ENSEMBLE_AVG_COUNT:.2f}"
        f" 和 {100 * np.sum(models_accuracies, axis=0) / ENSEMBLE_AVG_COUNT:.2f}%"
    )

    # 返回模型预测结果和注意力权重的平均值
    return (
        np.sum(models_predictions, axis=0) / ENSEMBLE_AVG_COUNT,
        np.sum(models_attention_weights, axis=0) / ENSEMBLE_AVG_COUNT,
    )

# 在验证数据上评估并预测类别和注意力分数
class_predictions, attention_params = predict(val_data, val_labels, trained_models)

# 绘制验证数据的一些结果
plot(
    val_data,
    val_labels,
    "positive",
    predictions=class_predictions,
    attention_weights=attention_params,
)
plot(
    val_data,
    val_labels,
    "negative",
    predictions=class_predictions,
    attention_weights=attention_params,
)
10/10 ━━━━━━━━━━━━━━━━━━━━ 1s 53ms/step
 10/10 ━━━━━━━━━━━━━━━━━━━━ 1s 39ms/step
The average loss and accuracy are 0.03 and 99.00 % resp.

3.结论和展望

3.1.结论

经过对模型性能的分析和可视化结果的解读,我们可以得出以下结论。首先,我们注意到在注意力机制的应用下,每个包内所有实例的注意力权重总和始终为1。这一设计确保了模型在聚合包内实例信息时能够合理分配关注度,进而做出准确的预测。

对于被模型正预测的包,即模型判断该包包含正标签的类别时,我们可以观察到导致正标签的实例拥有显著更高的注意力分数。这意味着模型在做出正预测时,能够准确识别并聚焦于对预测结果贡献最大的关键实例。

然而,对于被模型负预测的包,即模型判断该包不包含正标签的类别时,情况则稍显复杂。在这种情况下,存在两种可能性:一是包内所有实例的注意力分数大致相似,表明模型认为这些实例对于确定包的类别标签都没有特别显著的作用;二是包内某个实例拥有相对较高的注意力分数(尽管这一分数远低于正例中的实例),这可能是因为该实例的特征空间与正例的特征空间较为接近,但并未达到足以改变包的整体标签的程度。

这一结论不仅验证了注意力机制在多实例学习中的有效性,还为我们提供了改进模型性能的潜在方向。例如,我们可以尝试进一步优化注意力机制,使其能够更准确地识别并区分关键实例与无关实例,从而提高模型在复杂数据集上的分类准确性。此外,对于负预测包中相对较高的注意力分数实例,我们可以进一步分析其特征,以探索是否存在其他影响模型性能的因素。

3.2. 展望

基于本文的讨论,我们可以展望几个未来改进和扩展模型的方向:

  1. 注意力机制的优化:尽管当前的注意力机制已经能够识别并聚焦于对预测结果贡献最大的关键实例,但其性能仍有提升的空间。未来的工作可以集中在开发更先进的注意力机制上,例如通过引入自注意力或层次注意力结构,来更好地捕捉实例之间的依赖关系和包的整体结构。

  2. 不平衡数据的处理:针对负预测包中相对较高的注意力分数实例,我们可以进一步探索如何处理数据不平衡问题。除了使用类别权重之外,还可以考虑采用过采样少数类或欠采样多数类的方法,以及合成新的训练样本来增强模型的泛化能力。

  3. 特征工程的深化:深入了解和分析模型在不同特征上的表现,可以帮助我们进一步优化特征工程。通过特征选择、特征转换或特征融合等方法,我们可以提高特征的表达能力和区分度,从而提高模型的预测准确性。

  4. 集成学习的拓展:当前我们已经采用了简单的平均集成方法来结合多个模型的预测结果。未来,我们可以尝试使用更复杂的集成策略,如加权集成、堆叠集成或投票集成等,以充分利用不同模型的优点,并进一步提高整体模型的性能。

  5. 可解释性的增强:为了更好地理解模型的决策过程并提高其可信任度,我们可以研究如何增强模型的可解释性。例如,通过可视化技术展示注意力权重在包内实例上的分布,或者采用模型压缩和蒸馏技术来提取更简洁的模型结构。

综上所述,未来的工作将围绕注意力机制的优化、不平衡数据的处理、特征工程的深化、集成学习的拓展以及可解释性的增强等方面展开,以不断提升模型的性能并拓展其应用范围。

参考文献

[1] Keras官方示例. “Attention-based Multiple Instance Learning for Classification.” Keras.io. 访问日期: [2024-6-15]. https://keras.io/examples/vision/attention_mil_classification/

附录1:示例代码

import numpy as np
import keras
from keras import layers
from keras import backend as K
from tqdm import tqdm
import matplotlib.pyplot as plt

plt.style.use("ggplot")

# 创建数据集
# 我们将创建一组数据包,并根据它们的内容分配标签。
# 如果一个数据包中至少有一个正样本实例,那么这个数据包就被视为正包。
# 如果它不包含任何正样本实例,那么这个数据包将被视为负包。

# 配置参数
# POSITIVE_CLASS: 正包中要保留的期望类别。
# BAG_COUNT: 训练数据包的数量。
# VAL_BAG_COUNT: 验证数据包的数量。
# BAG_SIZE: 数据包中实例的数量。
# PLOT_SIZE: 要绘制的数据包数量。
# ENSEMBLE_AVG_COUNT: 创建并一起平均的模型数量。(可选:通常可以提高性能 - 设置为1表示单一模型)

POSITIVE_CLASS = 1
BAG_COUNT = 1000
VAL_BAG_COUNT = 300
BAG_SIZE = 3
PLOT_SIZE = 3
ENSEMBLE_AVG_COUNT = 1

# 准备数据包
# 注意力操作符是一个排列不变操作符,正样本标签的实例会在正包中随机放置。

def create_bags(input_data, input_labels, positive_class, bag_count, instance_count):
    # 设置数据包。
    bags = []
    bag_labels = []

    # 归一化输入数据。
    input_data = np.divide(input_data, 255.0)

    # 计数正样本。
    count = 0

    for _ in range(bag_count):
        # 随机选择固定大小的样本子集。
        index = np.random.choice(input_data.shape[0], instance_count, replace=False)
        instances_data = input_data[index]
        instances_labels = input_labels[index]

        # 默认情况下,所有数据包都被标记为0。
        bag_label = 0

        # 检查数据包中是否至少有一个正类。
        if np.any(instances_labels == positive_class):
            # 正包将被标记为1。
            bag_label = 1
            count += 1

        bags.append(instances_data)
        bag_labels.append(np.array([bag_label]))

    print(f"正包数量: {count}")
    print(f"负包数量: {bag_count - count}")

    return (list(np.swapaxes(bags, 0, 1)), np.array(bag_labels))

# 加载MNIST数据集。
(x_train, y_train), (x_val, y_val) = keras.datasets.mnist.load_data()

# 创建训练数据。
train_data, train_labels = create_bags(
    x_train, y_train, POSITIVE_CLASS, BAG_COUNT, BAG_SIZE
)

# 创建验证数据。
val_data, val_labels = create_bags(
    x_val, y_val, POSITIVE_CLASS, VAL_BAG_COUNT, BAG_SIZE
)

# 以下是模型创建、训练、评估和可视化的代码,由于篇幅过长,这里省略了中间部分的注释。
# 每个函数和类都有相应的注释说明它们的功能和参数。

# ...(此处省略中间代码段的中文注释)

# 绘制验证数据包的结果。
def plot(data, labels, bag_class, predictions=None, attention_weights=None):
    """绘制数据包和注意力权重的实用工具。

    参数:
      data: 包含实例数据包的输入数据。
      labels: 输入数据关联的数据包标签。
      bag_class: 所需的数据包类别的字符串名称。
        选项有:"positive"(正样本)或 "negative"(负样本)。
      predictions: 模型预测的类别标签。
        如果未指定,将使用真实标签。
      attention_weights: 输入数据中每个实例的注意力权重。
        如果未指定,将不会显示这些值。
    """
    # 函数实现...

# 训练模型集合。
trained_models = [
    train(train_data, train_labels, val_data, val_labels, model)
    for model in tqdm(models)
]

# 在验证数据上评估并预测类别和注意力分数。
class_predictions, attention_params = predict(val_data, val_labels, trained_models)

# 绘制验证数据的一些结果。
plot(
    val_data,
    val_labels,
    "positive",
    predictions=class_predictions,
    attention_weights=attention_params,
)
plot(
    val_data,
    val_labels,
    "negative",
    predictions=class_predictions,
    attention_weights=attention_params,
)
# 定义多实例学习注意力层
class MILAttentionLayer(layers.Layer):
    """多实例学习中基于注意力的深度层实现。

    参数:
      weight_params_dim: 正整数,权重矩阵的维度。
      kernel_initializer: 'kernel'矩阵的初始化器。
      kernel_regularizer: 应用于'kernel'矩阵的正则化函数。
      use_gated: 布尔值,是否使用门控机制。

    返回:
      BAG_SIZE长度的2D张量列表。
      张量是应用softmax后的注意力分数,形状为`(batch_size, 1)`。
    """

    # 类初始化方法...
    # 这里包含了类的初始化代码,定义了构造函数和属性。

    # 构建层的方法...
    # 这里包含了根据输入形状创建权重参数的代码。

    # 调用层的方法...
    # 这里实现了注意力分数的计算和softmax操作。

    # 计算注意力分数的方法...
    # 这里实现了注意力机制的核心计算,包括门控机制的应用。

# 绘制数据包和注意力权重的工具函数
def plot(data, labels, bag_class, predictions=None, attention_weights=None):
    """绘制数据包和注意力权重的实用工具。

    参数:
      data: 包含实例数据包的输入数据。
      labels: 输入数据关联的数据包标签。
      bag_class: 所需的数据包类别的字符串名称。
      predictions: 模型预测的类别标签。
      attention_weights: 输入数据中每个实例的注意力权重。
    """
    # 函数实现...
    # 这里实现了根据类别筛选数据包、显示图像和注意力权重的逻辑。

# 创建模型的函数
def create_model(instance_shape):
    # 从输入中提取特征。
    # 这里实现了模型的构建,包括共享的密集层和注意力层。

# 计算类别权重的函数
def compute_class_weights(labels):
    # 计算类别权重。
    # 这里实现了针对不平衡数据的类别权重计算。

# 训练模型的函数
def train(train_data, train_labels, val_data, val_labels, model):
    # 训练模型。
    # 这里实现了模型训练的配置和过程,包括回调函数的设置。

# 评估模型的函数
def predict(data, labels, trained_models):
    # 评估模型。
    # 这里实现了对模型预测的收集和平均,以及注意力权重的获取。

# 绘制验证数据的结果
# 以下是使用plot函数绘制验证数据的正包和负包的结果。
plot(val_data, val_labels, "positive")
plot(val_data, val_labels, "negative")

# 构建和训练模型集合
# 以下是构建模型集合并进行训练的代码。
instance_shape = train_data[0][0].shape
models = [create_model(instance_shape) for _ in range(ENSEMBLE_AVG_COUNT)]
print(models[0].summary())  # 显示单个模型的架构
trained_models = [train(train_data, train_labels, val_data, val_labels, model) for model in tqdm(models)]

# 在验证数据上评估并预测类别和注意力分数
class_predictions, attention_params = predict(val_data, val_labels, trained_models)

# 绘制验证数据的一些结果
# 以下是使用plot函数绘制验证数据的结果,包括正包和负包的图像及其预测和注意力权重。
plot(
    val_data,
    val_labels,
    "positive",
    predictions=class_predictions,
    attention_weights=attention_params,
)
plot(
    val_data,
    val_labels,
    "negative",
    predictions=class_predictions,
    attention_weights=attention_params,
)

相关推荐

  1. 基于深度学习图像分类方法

    2024-06-16 13:08:01       21 阅读
  2. 基于 PyTorch Python 深度学习注意力机制

    2024-06-16 13:08:01       13 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 13:08:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 13:08:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 13:08:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 13:08:01       20 阅读

热门阅读

  1. leecode N皇后

    2024-06-16 13:08:01       8 阅读
  2. HTML基础标签解析:H1-H6、DIV与P的正确使用方法

    2024-06-16 13:08:01       8 阅读
  3. Vue3 中 props 与 emit 用法

    2024-06-16 13:08:01       8 阅读
  4. django orm 查询返回指定关键字

    2024-06-16 13:08:01       8 阅读
  5. 【AI原理解析】— 星火大模型

    2024-06-16 13:08:01       7 阅读
  6. 基于SpringBoot+Spark搭建本地计算引擎服务

    2024-06-16 13:08:01       9 阅读
  7. Pytorch-Padding Layers

    2024-06-16 13:08:01       9 阅读