多标签分类
HiClass 支持分层多标签分类。 这意味着一个样本可以同时属于同一层次级别的多个类别。
在本页中,我们阐述、解释并演示了如何在HiClass中实现分层多标签分类。
动机
在许多层次分类问题中,一个样本可能与层次结构中同一级别的多个类别相关联。这种情况发生在类别之间不互斥时。例如,让我们考虑一个涉及犬种分类的问题,我们的目标是根据可用数据确定狗的品种。如果不允许多个路径通过犬种层次结构,我们将不得不为每个样本分配一个单一标签,这意味着我们必须选择通过层次结构的单一路径,将狗分配到一个单一品种。然而,这并不总是反映现实,因为狗可能是多个品种的混合体。例如,一只狗可能是腊肠犬和金毛猎犬的混合体。在这种情况下,我们的目标是为样本分配腊肠犬和金毛猎犬两个标签,这需要至少两个通过层次结构的路径。下图展示了这个例子。
一张混血狗的照片示例,这只狗是腊肠犬和金毛猎犬的混血,因此需要通过多个层次路径进行正确分类。
另一个多标签分类的例子是文档分类,我们的目标是根据文档的内容对其进行分类。 这些类别通常是分层次的,例如将文档分类为“技术”、“体育”和“政治”等广泛主题,这些主题下还有“人工智能”、“足球”和“国际关系”等子类别。 一个文档可以属于多个类别,例如,一篇关于人工智能进展对国际关系影响的文本,只能通过层次结构中的多条路径正确分类。
背景 - 分类术语
为了解释我们所说的分层多标签分类,我们首先需要定义一些术语。
从最通用的(多类)到最具体的(层次多标签分类)分类问题集合。
在多类分类问题中,一个样本可以被分配到多个选项中的一个类别。 在多标签分类问题中,一个样本可以同时与多个类别相关联。 层次分类问题是一种多标签分类问题,其中类别以层次结构(如图形、树或有向无环图(DAG))组织。 在此图中,节点对应于要预测的类别。 如果未指定,通常假定一个样本在层次结构的每一级只能属于一个类别。 这意味着一个样本只能与层次结构中的一条路径相关联,从根节点开始,到叶节点结束。 在层次多标签分类中,这一限制被取消。 一个样本可以在层次结构的任何级别属于多个类别,即一个样本可以通过层次结构中的多条路径进行分类。
设计 - 目标格式
HiClass 旨在与 scikit-learn API 兼容。 对于非多标签层次分类的情况,目标数组遵循 sklearn 的多标签分类问题格式。 然而,由于没有 sklearn 特定的多标签层次格式,HiClass 实现了自己的格式扩展。 HiClass 目标格式通过在二维数组中添加一个新维度来扩展非多标签层次分类格式,该维度捕获了层次结构中的不同路径。
HiClass层次多标签分类格式扩展,适用于按犬种层次分类的样本。
这是通过嵌套的列表列表实现的,其中最后一个维度指定了通过层次结构的路径。
y = [
[["Retriever", "Golden Retriever"], ["Hound", "Dachshund"]], # sample 1
[["Hound", "Beagle"]] # sample 2
]
这里需要注意的是,我们为每条路径指定从根节点到最具体节点的整个节点列表。
即使在只有叶节点不同的情况下,我们仍然需要指定整个路径。
例如,如果样本1属于拉布拉多类而不是腊肠犬类,我们仍然需要指定从根节点到金毛猎犬和拉布拉多节点的整个路径,即[["Retriever", "Golden Retriever"], ["Retriever", "Labrador"]]。
这是使用Numpy数组进行实现的结果,Numpy数组要求目标数组具有固定的维度。
此外,通过明确指定从根节点到叶节点的整个路径,目标格式易于阅读和理解,并且也适用于不是树而是有向无环图(DAG)的层次结构。
拟合分类器
在本节中,我们概述了在HiClass中如何实现局部分类器的拟合,用于分层多标签分类。
这里,我们只关注hiclass.MultiLabelLocalClassifierPerNode和hiclass.MultiLabelLocalClassifierPerParentNode分类器的分层多标签分类情况。
如需回顾这些策略的工作原理,请访问Algorithms部分。
每个节点的本地分类器
hiclass.MultiLabelLocalClassifierPerNode 策略为层次结构中的每个节点拟合一个二元局部分类器。
hiclass.BinaryPolicy 定义了哪些样本属于给定局部分类器的正类,哪些属于负类。
HiClass 实现了局部分类器的正样本和负样本是互斥的,即一个样本只能属于局部分类器的正类或负类。
在层次多标签情况下,如果一个样本属于与局部分类器相关的任何层次路径,则该样本属于正类。
例如,示例图像被分配给Retriever分类器的正类,因为它属于Golden Retriever类,这是Retriever节点的子类。 它也被分配给Hound分类器的正类,因为它不属于Dachshund类,这是Hound节点的子类。
每个父节点的局部分类器
hiclass.MultiLabelLocalClassifierPerParentNode 为每个非叶子/父节点(即层次结构中具有子节点的节点)训练一个多类分类器。
要预测的类别是该节点的子节点。
对于多标签情况,一个样本可以属于一个节点的多个子节点。
在内部,这是通过复制样本并将每个副本分配给节点的子节点之一来实现的。
分类器不需要支持sklearn多标签格式,可以是标准的sklearn分类器。
预测
到目前为止,我们只讨论了分类器的拟合;在本节中,我们概述了如何在HiClass中实现多路径的预测。 HiClass遵循自上而下的预测策略,其中数据样本通过层次结构中的节点进行分类,从根节点开始,一直到叶节点。 在单一路径的情况下,数据样本在每一层被分配具有最高概率的标签。 这导致每个数据样本在层次结构中只有一条路径。
使用自上而下的预测策略预测样本的标签。红色数字表示每个节点的预测概率。
在上面给出的例子中,样本将被分配标签 ["Retriever", "Golden Retriever"],因为这是从根节点开始概率最高的路径。
相反,当我们希望允许多条路径通过层次结构时,我们需要指定一个不同于取最高概率的标准来为数据样本分配标签。
HiClass 为此实现了两种策略:阈值和容差。
阈值
阈值策略会在标签的概率超过给定阈值时为数据样本分配标签。 阈值 \(\lambda \in [0, 1]\) 是传递给预测函数的参数,并指定一个绝对概率值。
在上面的例子中,如果我们设置 \(\lambda = 0.6\),我们会将标签 [["Retriever", "Golden Retriever"], ["Hound", "Dachshund"]] 分配给样本,因为分配节点的概率大于0.6。
虽然这种策略易于实现和理解,但它有一个缺点,即无法为层次结构中的每个节点指定不同的阈值,需要为所有节点设置一个全局阈值。
此外,使用自上而下的预测策略,如果预测概率低于某个节点的阈值,则无论层次结构中更下方节点的概率如何,预测都会停止。
例如,如果 \(\lambda = 0.85\),则不会为样本分配任何标签,因为Retriever和Hound类的概率低于阈值,并且遍历层次结构会停止。
容差
容忍策略通过为数据样本分配标签来缓解阈值策略中绝对概率值引起的问题,如果概率在相邻节点最高概率的给定容忍范围内。 容忍度 \(\gamma \in [0, 1]\) 是一个传递给预测函数的参数,指定了一个相对概率值。
这种策略的优势在于,由于容差是相对于最高概率的,因此在每个级别上总是至少预测一个类别。
例如,使用\(\gamma = 0.3\),我们将预测标签[["Retriever", "Golden Retriever"], ["Hound", "Dachshund"], ["Hound", "Beagle"]]。
请注意,Beagle标签在第二级别被分配,因为其概率0.5在相邻节点最高概率0.8(Dachshund类)的0.3阈值范围内。
指标
我们扩展了层次化的精确度、召回率和F分数指标,以评估层次化多标签分类器的性能。 层次化的精确度、召回率和F分数定义如下,并且在Metrics中也有定义。
在这里,我们给出了一个多标签情况下的分层精度和召回率的示例。
请注意,在计算多个样本的分层精度和召回率时,我们可以定义微观和宏观平均值。 所有预测的微观精度/召回率被一起考虑,而不考虑样本。 相反,在宏观精度/召回率中,我们首先计算一个样本的分层精度/召回率,然后汇总结果。 由于样本可能具有不同数量的标签,微观和宏观平均值可能会导致不同的结果。
代码示例 - 综合应用
输出:
[[['Retriever' 'Golden Retriever']
['Hound' 'Dachshund']]
[['Retriever' 'Golden Retriever']
['' '']]
[['Hound' 'Dachshund']
['Hound' 'Beagle']]]
from sklearn.tree import DecisionTreeClassifier
from hiclass.MultiLabelLocalClassifierPerNode import MultiLabelLocalClassifierPerNode
# Define data
X_train = [[1, 2], [3, 4], [5, 6]]
X_test = [[1, 2], [3, 4], [5, 6]]
# Define Labels
Y_train = np.array([
[["Retriever", "Golden Retriever"], ["Hound", "Dachshund"]],
[["Retriever", "Labrador"]],
[["Hound", "Dachshund"], ["Hound", "Beagle"]],
], dtype=object)
# Use decision tree classifiers for every node
tree = DecisionTreeClassifier()
classifier = MultiLabelLocalClassifierPerNode(local_classifier=tree)
# Train local classifier per node
classifier.fit(X_train, Y_train)
# Predict
predictions = classifier.predict(X_test)
print(predictions)
脚本总运行时间: ( 0 分钟 0.047 秒)