基于类属属性的多标记学习——含python代码

本文介绍了一种名为LIFT的多标记学习算法,该算法通过提取类属属性来提高模型性能。文章详细解释了类属属性的概念及其提取方法,并提供了算法的具体实现流程及Python代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  本文的基本内容翻译自Lift: Multi-Label Learning with Label-Specific Features[1],含部分本人的理解,最后附带了我复现的python代码的github链接。

类属属性

  所谓多标记学习是相对于单标记学习而言的一种机器学习问题。顾名思义,模型会对输入的特征向量返回多个label的预测结果,比如说给一张图片让程序判断其中是否有“蓝天”,“白云”,“大海”,这便是一个多标记学习问题。通常我们会用一个二进制向量 y=(y1,y2,y3,...yq) y = ( y 1 , y 2 , y 3 , . . . y q ) 来表示模型的输出。每个label对应一个类别, yi=1 y i = 1 表示第 i i 个label为正例,yi=0则表示该类别为负例。
  从上面的描述中可以知道,原始特征空间应该包含了所有类别的相关信息,但它呈现给我们的只是一个特征向量而已。简单来说,类属属性便是多标记问题中每个标记类别相对应的特征信息ML Zhang认为:利用某种方法将每个label对应的类属属性提取出来,再进行后续模型的构建,是一个有助于提高模型性能的预处理步骤。实验结果也验证了这种猜想,这便是LIFT算法的渊源。

类属属性的提取

  从定义上来说,类属属性便是基于原始特征空间和对应label的一种embedding。至于如何提取类属属性有很多种方法,比如利用 L1 L 1 正则化的特征稀疏性质,可以选出和对应标记相关性最大的特征;或者可以给出个3层的神经网络来拟合原始特征向量和某个label,然后取出中间层的输出作为label的类属属性;这些都是可行的方法,至于效果如何得做实验验证。
  LIFT算法利用的是聚类方法来提取类属属性。聚类本身是一种无监督式学习的方法,经常被用在数据的预处理阶段。在LIFT算法中,作者并不是直接对原始特征向量进行聚类,而是先利用标记的正负信息进行数据的划分再分别聚类,以体现正负标记对应的样本原始特征在分布上的区别,这是算法最难理解的地方。下面我具体讲一下算法提取类属属性的过程:
  我们把标记正负类定义为: Pk={xi|(xi,Yi)D,lkYi}Nk={xi|(xi,Yi)D,lkYi} P k = { x i | ( x i , Y i ) ∈ D , l k ∈ Y i } , N k = { x i | ( x i , Y i ) ∈ D , l k ∉ Y i } Pk P k 表示的是第k个label为正的样本集合, Nk N k 表示的是第k个样本为负的样本集合。LIFT算法的第一步就是找出所有label对应的 Pk P k Nk N k ,后面的聚类操作便是在 Pk P k Nk N k 上进行的。至于聚类时的簇数如何确定,文章中定义了 m+k m k + mk m k − ,分别对应于 Pk P k Nk N k 。其中 m+k=mk=rmin(|Pk|,|Nk|) m k + = m k − = r ⋅ m i n ( | P k | , | N k | ) ,其中 r[0,1] r ∈ [ 0 , 1 ]
  聚类操作结束后,每个label我们都能得到 m+k m k + 个positive聚类中心和 mk m k − 个negative聚类中心。这 2m+k 2 m k + 个聚类中心能够反映标记 lk l k 对应的正负样本的内部结构。到这里,类属属性的提取只差一步:计算出原始特征向量到这 2m+k 2 m k + 个聚类中心的欧式距离 dki,i[1,2m+k] d k i , i ∈ [ 1 , 2 m k + ] ,用这 2m+k 2 m k + 个距离构成新的特征向量 ϕ(x)=(d1,d2,...,d2m+k) ϕ ( x ) = ( d 1 , d 2 , . . . , d 2 m k + ) ,这便是标记 lk l k 对应的类属属性。下面给出LIFT算法的完整过程:


Y=LIFT(D,r,u) Y = L I F T ( D , r , u )
输入:
D: D : 多标记学习的训练集 {(xi,Yi)|1im} { ( x i , Y i ) | 1 ≤ i ≤ m } ;
r: r : 确定聚类簇数的参数;
u: u : 测试样本;
输出:
Y: Y : 对测试样本 u u 的预测结果;

过程:
for k=1:1:q k = 1 : 1 : q do d o
  找到标记 lk l k 对应的正样本集合 Pk P k 和负样本集合 Nk N k ,分别在这两个集合上进行 k k 均值聚类,聚类簇数为mk=mk+=mk=rmin(|Pk|,|Nk|)。计算原始特征向量到这 2m+k 2 m k + 个中心的欧式距离构成新的特征向量空间 ϕk ϕ k 。利用类属属性特征 ϕk ϕ k Yk Y k 训练二分类器 βk β k
end e n d
for f o r k=1:1:q k = 1 : 1 : q do d o
  计算未知实例 u u 2mk+个中心的欧式距离构成新的特征向量,利用二分类器 βk β k 预测其输出 yk y k ;
end e n d
return r e t u r n Y={y1,y2,y3,...,yq} Y = { y 1 , y 2 , y 3 , . . . , y q }


代码块

import numpy as np
from mlab.releases import latest_release as mb
import evaluate as ev
import sys
from sklearn.preprocessing import scale
import scipy.io as sio
from Clustering import *
from copy import copy
from sklearn.model_selection import KFold

sys.path.append('.\libsvm-3.22\python')

from svmutil import *

class LIFT(object):
    def __init__(self, ratio, svm_type):
    #svm_type:
    #1:Linear;2:RBF;3:Polynomial
        self.ratio = ratio
        self.type = svm_type
        self.svm = []
        self.binary = []

    def fit(self, X, Y):
        train_data,train_target = copy(X),copy(Y)
        num_train, dim = train_data.shape
        tmp_value, num_class = train_target.shape
        self.P_Centers,self.N_Centers = [],[]
        for i in range(num_class):
            print('Performing clustering for the', i+1, '-th class')
            p_idx = train_target[:,i]==1
            n_idx = train_target[:,i]==0
            p_data = train_data[p_idx,:]
            n_data = train_data[n_idx,:]
            k1 = int(mb.ceil(min(sum(p_idx)*self.ratio,
                     sum(n_idx)*self.ratio)))
            k2 = k1
            if k1==0:
                label = 1 if sum(p_idx)>sum(n_idx) else 0
                self.binary.append(label)
                POS_C = []
                NEG_C = []
            else:
                self.binary.append(2)
                if p_data.shape[0] == 1:
                    POS_C = p_data
                else:
                    POS_IDX,POS_C = KMeans(p_data,k1)

                if n_data.shape[0] == 1:
                    NEG_C = n_data
                else:
                    NEG_IDX,NEG_C = KMeans(n_data,k2)
            self.P_Centers.append(POS_C)
            self.N_Centers.append(NEG_C)

        for i in range(num_class):
            print('Building classifiers: ',i+1,'/',num_class)
            if self.binary[i] != 2:
                self.svm.append([])
            else:
                centers = np.vstack((self.P_Centers[i],
                                     self.N_Centers[i]))
                num_center = centers.shape[0]
                if num_center >= 5000:
                    print('Too many cluster centers!')
                    sys.exit()
                else:
                    blocksize = 5000-num_center
                    num_block = int(mb.ceil(num_train*1.0/blocksize))
                    data = np.zeros((num_train,num_center))
                    for j in range(1,num_block):
                        low = (j-1)*blocksize
                        high = j*blocksize
                        tmp_mat = np.vstack((centers,train_data[low:high,:]))
                        Y = mb.pdist(tmp_mat)
                        Z = mb.squareform(Y)
                        data[low:high,] = Z[num_center:num_center+blocksize,0:num_center]
                        #data.append(Z[num_center:num_center+blocksize,0:num_center].tolist())
                    low = (num_block-1)*blocksize
                    high = num_train
                    tmp_mat = np.vstack((centers,train_data[low:high,:]))
                    Y = mb.pdist(tmp_mat)
                    Z = mb.squareform(Y)
                    data[low:high,] = Z[num_center:num_center+blocksize,0:num_center]
                    #data.append(Z[num_center:num_center+blocksize,0:num_center].tolist())
                training_instance = copy(data)    
                training_label = train_target[:,i]
                if self.type == 1:
                    para = '-t 0 -b 1 -q'
                elif self.type == 2:
                    para = '-t 1 -b 1 -q'
                else:
                    para = '-t 2 -b 1 -q'
                self.svm.append(svm_train(training_label.tolist(),
                                          training_instance.tolist(),
                                          para))

    def predict(self, test_X, test_Y):
        test_data,test_target = copy(test_X),copy(test_Y)
        num_test, num_class = test_target.shape
        Pre_Labels = np.zeros(test_target.shape)
        Outputs = copy(Pre_Labels)
        for i in range(num_class):
            if self.binary[i] != 2:
                Pre_Labels[:,i] = self.binary[i]
                Outputs[:,i] = self.binary[i]
            else:
                centers = np.vstack((self.P_Centers[i],
                                     self.N_Centers[i]))
                num_center = centers.shape[0]
                data = np.zeros((num_test,num_center))
                if num_center >= 5000:
                    print('Too many cluster centers!')
                    sys.exit()
                else:
                    blocksize = 5000-num_center
                    num_block = int(mb.ceil(num_test*1.0/blocksize))
                    for j in range(1,num_block):
                        low = (j-1)*blocksize
                        high = j*blocksize
                        tmp_mat = np.vstack((centers,test_data[low:high,:]))
                        Y = mb.pdist(tmp_mat)
                        Z = mb.squareform(Y)
                        data[low:high,] = Z[num_center:num_center+blocksize,0:num_center]
                        #data.append(Z[num_center:num_center+blocksize,0:num_center].tolist())
                    low = (num_block-1)*blocksize
                    high = num_test
                    tmp_mat = np.vstack((centers,test_data[low:high,:]))
                    Y = mb.pdist(tmp_mat)
                    Z = mb.squareform(Y)
                    data[low:high,] = Z[num_center:num_center+blocksize,0:num_center]
                    #data.append(Z[num_center:num_center+blocksize,0:num_center].tolist())
                new_test_data = copy(data)
                Pre_Labels[:,i],tmp,tmpoutputs = svm_predict(test_target[:,i].tolist(), \
                                                             new_test_data.tolist(), \
                                                             self.svm[i],'-b 1')
                pos_index = 0 if self.svm[i].label[0]==1 else 1
                Outputs[:,i] = np.array(tmpoutputs)[:,pos_index]

        return [ev.HammingLoss(Pre_Labels,test_target),
                ev.rloss(Outputs,test_target),
                ev.Coverage(Outputs,test_target),
                ev.OneError(Outputs,test_target),
                ev.avgprec(Outputs,test_target)]#,
                #ev.MacroAveragingAUC(Outputs,test_target)]



if __name__ == '__main__':
    path = 'F:/PyLift/dataset/'
    dataset = ['birds','CAL500','corel5k','emotions',
               'enron','genbase','Image','languagelog',
               'recreation','scene','slashdot','yeast']
    ratio = 0.1
    svm_type = 1#Linear
    for i in range(3,4):
        dataset_path = path + dataset[i] + '.mat'
        tmp = sio.loadmat(dataset_path)
        data,target = scale(tmp['data']),tmp['target'].T
        target[target==-1] = 0
        kf = KFold(n_splits=10, shuffle=True, random_state=2017)
        result = []
        for dev_index,val_index in kf.split(data):
            train_data, test_data = data[dev_index], data[val_index]
            train_target, test_target = target[dev_index], target[val_index]
            lift = LIFT(ratio,svm_type)
            lift.fit(train_data,train_target)
            result.append(lift.predict(test_data,test_target))     
        res_mean = np.mean(result,0)
        res_std = np.std(result,0)
        print('Hamming Loss:',res_mean[0],'+-',res_std[0])
        print('Ranking Loss:',res_mean[1],'+-',res_std[1])
        print('Coverage:',res_mean[2],'+-',res_std[2])
        print('One Error:',res_mean[3],'+-',res_std[3])
        print('Average Precision:',res_mean[4],'+-',res_std[4])
        #print('Macro Averaging AUC:',res_mean[5],'+-',res_std[5])

上述代码只给出了LIFT类的代码,另外还有两个py文件,一个是KMeans.py,一个是evaluate.py。完整代码的github地址为:https://github.com/ZesenChen/PyLift。其中还包含了libsvm的源码和常用的几个多标记dataset,有兴趣的可以自己做实验,性能和论文中给出的实验结果基本相同。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值