[Datawhale][CS224W]标签传播算法(七)

一、概述

标签传播算法(Label Propagation)是一种基于图的半监督学习方法,其基本思路是用已标记节点的标签信息去预测未标记节点的标签信息。利用样本间的关系建图,节点包括已标注和未标注数据,其边表示两个节点的相似度,节点的标签按相似度传递给其他节点。标签数据就像是一个源头,可以对无标签数据进行标注,节点的相似度越大,标签越容易传播。

二、半监督节点分类问题-求解方法对比

方法图嵌入表示学习使用属性特征使用标注直推式归纳式
人工特征工程//
基于随机游走的方法
基于矩阵分解的方法
标签传播是/否
图神经网络
  • 人工特征工程:节点重要度、集群系数、Graphlet等。

  • 基于随机游走的方法,构造自监督表示学习任务实现图嵌入。无法泛化到新节点。

    例如:DeepWalk、Node2Vec、LINE、SDNE等。

  • 标签传播:假设“物以类聚,人以群分”,利用邻域节点类别猜测当前节点类别。无法泛化到新节点。

    例如:Label Propagation、Iterative Classification、Belief Propagation、Correct & Smooth等。

  • 图神经网络:利用深度学习和神经网络,构造邻域节点信息聚合计算图,实现节点嵌入和类别预测。

    可泛化到新节点。

    例如:GCN、GraphSAGE、GAT、GIN等。

经常与GCN相对比的模型

image-20230227194041585

三、算法——标签传播和集体分类

3.1 Label Propagation(Relational Classification)标签传播算法

3.1.1 简介

标签传播算法 (LPA) 是一种用于在图中查找社区的快速迭代算法。它仅使用网络结构作为指导来检测这些社区,并且不需要预定义的目标函数或有关社区的先验信息。 [ 1 ] ^{[1]} [1]我们通过在数据集中传播标签来将标签分配给未标记的点。该算法最早由朱晓进Zoubin Ghahramani [ 2 ] ^{[2]} [2]2002年提出。LPA 属于转导学习,因为我们想要预测已经提供给我们的未标记数据点的标签。

LPA 的工作原理是在整个网络中传播标签,并根据标签传播过程形成社区。 [ 1 ] ^{[1]} [1]

该算法背后的直觉是,单个标签可以在密集连接的节点组中迅速占据主导地位,但难以跨越稀疏连接的区域。标签将被困在一组密集连接的节点中,当算法完成时,那些以相同标签结束的节点可以被视为同一社区的一部分。

在初始条件下,节点带有一个标签,表示它们所属的社区。社区中的成员资格根据相邻节点拥有的标签而变化。此更改受节点一个度内的最大标签数限制。每个节点都用一个唯一的标签初始化,然后标签通过网络传播。因此,密集连接的群体很快就会找到一个共同的标签。当在整个网络中创建许多这样的密集(共识)组时,它们会继续向外扩展,直到无法扩展为止。

该算法的工作原理如下: [ 1 ] ^{[1]} [1]

  • 每个节点都使用唯一的社区标签(标识符)进行初始化。
  • 这些标签通过网络传播。
  • 在每次传播迭代中,每个节点都会将其标签更新为其最大数量的邻居所属的标签。关系被任意但确定性地打破。
  • 当每个节点都拥有其邻居的多数标签时,LPA 达到收敛。
  • 如果达到收敛或用户定义的最大迭代次数,LPA 将停止。

该工作原理的具体实现步骤:

  1. 初始化网络中所有节点的标签。对于给定的节点x, C x ( 0 ) = x C_x(0)=x Cx(0)=x
  2. 设置 t = 1
  3. 将网络中的节点以随机顺序排布,并设置为X
  4. 对于按特定顺序选择的每个 x ∈ X x\in X xX,令 C x ( t ) = f ( X x i 1 ( t ) , X x i 2 ( t ) , . . . , X x i k ( t ) ) C_x(t)=f(X_{x_{i_1}}(t),X_{x_{i_2}}(t),...,X_{x_{i_k}}(t)) Cx(t)=f(Xxi1(t),Xxi2(t),...,Xxik(t))这里返回在邻居中出现频率最高的标签。如果有多个最高频率的标签,则随机选择一个标签
  5. 如果每个节点的标签都达到其邻居的最大数量,则停止算法。否则,设置 t = t + 1 并转到 (3)

随着标签的传播,密集连接的节点组会迅速就唯一标签达成共识。在传播结束时,只有少数标签会保留下来——大部分都会消失。在收敛时具有相同社区标签的节点被称为属于同一社区。

LPA 的一个有趣特性是可以为节点分配初步标签以缩小生成的解决方案的范围。这意味着它可以用作半监督方式来寻找我们手工挑选一些初始社区的社区。

3.1.2 LPA算法实现[3]
1.相似矩阵构建

LP算法是基于Graph的,因此我们需要先构建一个图。我们为所有的数据构建一个图,图的节点就是一个数据点,包含labeled和unlabeled的数据。节点i和节点j的边表示他们的相似度。这个图的构建方法有很多,这里我们假设这个图是全连接的,节点i和节点j的边权重为:

img

这里,α是超参。

还有个非常常用的图构建方法是knn图,也就是只保留每个节点的k近邻权重,其他的为0,也就是不存在边,因此是稀疏的相似矩阵。

2.LPA算法

标签传播算法非常简单:通过节点之间的边传播label。边的权重越大,表示两个节点越相似,那么label越容易传播过去。我们定义一个NxN的概率转移矩阵P:

img

P i j P_{ij} Pij表示从节点i转移到节点j的概率。假设有C个类和L个labeled样本,我们定义一个 L x C L_xC LxC的label矩阵 Y L Y_L YL,第i行表示第i个样本的标签指示向量,即如果第i个样本的类别是j,那么该行的第j个元素为1,其他为0。同样,我们也给U个unlabeled样本一个UxC的label矩阵YU。把他们合并,我们得到一个NxC的soft label矩阵F=[YL;YU]。soft label的意思是,我们保留样本i属于每个类别的概率,而不是互斥性的,这个样本以概率1只属于一个类。当然了,最后确定这个样本i的类别的时候,是取max也就是概率最大的那个类作为它的类别的。那F里面有个YU,它一开始是不知道的,那最开始的值是多少?无所谓,随便设置一个值就可以了。

千呼万唤始出来,简单的LP算法如下:

1)执行传播: F = P F F=PF F=PF

2)重置F中labeled样本的标签: F L = Y L F_L=Y_L FL=YL

3)重复步骤1)和2)直到F收敛。

步骤1)就是将矩阵P和矩阵F相乘,这一步,每个节点都将自己的label以P确定的概率传播给其他节点。如果两个节点越相似(在欧式空间中距离越近),那么对方的label就越容易被自己的label赋予,就是更容易拉帮结派。步骤2)非常关键,因为labeled数据的label是事先确定的,它不能被带跑,所以每次传播完,它都得回归它本来的label。随着labeled数据不断的将自己的label传播出去,最后的类边界会穿越高密度区域,而停留在低密度的间隔中。相当于每个不同类别的labeled样本划分了势力范围。

3.变身的LP算法

我们知道,我们每次迭代都是计算一个soft label矩阵F=[YL;YU],但是YL是已知的,计算它没有什么用,在步骤2)的时候,还得把它弄回来。我们关心的只是YU,那我们能不能只计算YU呢?Yes。我们将矩阵P做以下划分:

img

这时候,我们的算法就一个运算:

img

迭代上面这个步骤直到收敛就ok了,是不是很cool。可以看到FU不但取决于labeled数据的标签及其转移概率,还取决了unlabeled数据的当前label和转移概率。因此LP算法能额外运用unlabeled数据的分布特点。

这个算法的收敛性也非常容易证明。实际上,它是可以收敛到一个凸解的:

img

所以我们也可以直接这样求解,以获得最终的YU。但是在实际的应用过程中,由于矩阵求逆需要O(n3)的复杂度,所以如果unlabeled数据非常多,那么I – PUU矩阵的求逆将会非常耗时,因此这时候一般选择迭代算法来实现。

4.LP算法的Python实现

Python环境的搭建就不啰嗦了,可以参考前面的博客。需要额外依赖的库是经典的numpy和matplotlib。代码中包含了两种图的构建方法:RBF和KNN指定。同时,自己生成了两个toy数据库:两条长形形状和两个圈圈的数据。第四部分我们用大点的数据库来做实验,先简单的可视化验证代码的正确性,再前线。

算法代码:

#***************************************************************************
#* 
#* Description: label propagation
#* Author: Zou Xiaoyi (zouxy09@qq.com)
#* Date:   2015-10-15
#* HomePage: http://blog.csdn.net/zouxy09
#* 
#**************************************************************************
 
import time
import numpy as np
 
# return k neighbors index
def navie_knn(dataSet, query, k):
    numSamples = dataSet.shape[0]
 
    ## step 1: calculate Euclidean distance
    diff = np.tile(query, (numSamples, 1)) - dataSet
    squaredDiff = diff ** 2
    squaredDist = np.sum(squaredDiff, axis = 1) # sum is performed by row
 
    ## step 2: sort the distance
    sortedDistIndices = np.argsort(squaredDist)
    if k > len(sortedDistIndices):
        k = len(sortedDistIndices)
 
    return sortedDistIndices[0:k]
 
 
# build a big graph (normalized weight matrix)
def buildGraph(MatX, kernel_type, rbf_sigma = None, knn_num_neighbors = None):
    num_samples = MatX.shape[0]
    affinity_matrix = np.zeros((num_samples, num_samples), np.float32)
    if kernel_type == 'rbf':
        if rbf_sigma == None:
            raise ValueError('You should input a sigma of rbf kernel!')
        for i in xrange(num_samples):
            row_sum = 0.0
            for j in xrange(num_samples):
                diff = MatX[i, :] - MatX[j, :]
                affinity_matrix[i][j] = np.exp(sum(diff**2) / (-2.0 * rbf_sigma**2))
                row_sum += affinity_matrix[i][j]
            affinity_matrix[i][:] /= row_sum
    elif kernel_type == 'knn':
        if knn_num_neighbors == None:
            raise ValueError('You should input a k of knn kernel!')
        for i in xrange(num_samples):
            k_neighbors = navie_knn(MatX, MatX[i, :], knn_num_neighbors)
            affinity_matrix[i][k_neighbors] = 1.0 / knn_num_neighbors
    else:
        raise NameError('Not support kernel type! You can use knn or rbf!')
    
    return affinity_matrix
 
 
# label propagation
def labelPropagation(Mat_Label, Mat_Unlabel, labels, kernel_type = 'rbf', rbf_sigma = 1.5, \
                    knn_num_neighbors = 10, max_iter = 500, tol = 1e-3):
    # initialize
    num_label_samples = Mat_Label.shape[0]
    num_unlabel_samples = Mat_Unlabel.shape[0]
    num_samples = num_label_samples + num_unlabel_samples
    labels_list = np.unique(labels)
    num_classes = len(labels_list)
    
    MatX = np.vstack((Mat_Label, Mat_Unlabel))
    clamp_data_label = np.zeros((num_label_samples, num_classes), np.float32)
    for i in xrange(num_label_samples):
        clamp_data_label[i][labels[i]] = 1.0
    
    label_function = np.zeros((num_samples, num_classes), np.float32)
    label_function[0 : num_label_samples] = clamp_data_label
    label_function[num_label_samples : num_samples] = -1
    
    # graph construction
    affinity_matrix = buildGraph(MatX, kernel_type, rbf_sigma, knn_num_neighbors)
    
    # start to propagation
    iter = 0; pre_label_function = np.zeros((num_samples, num_classes), np.float32)
    changed = np.abs(pre_label_function - label_function).sum()
    while iter < max_iter and changed > tol:
        if iter % 1 == 0:
            print "---> Iteration %d/%d, changed: %f" % (iter, max_iter, changed)
        pre_label_function = label_function
        iter += 1
        
        # propagation
        label_function = np.dot(affinity_matrix, label_function)
        
        # clamp
        label_function[0 : num_label_samples] = clamp_data_label
        
        # check converge
        changed = np.abs(pre_label_function - label_function).sum()
    
    # get terminate label of unlabeled data
    unlabel_data_labels = np.zeros(num_unlabel_samples)
    for i in xrange(num_unlabel_samples):
        unlabel_data_labels[i] = np.argmax(label_function[i+num_label_samples])
    
    return unlabel_data_labels

测试代码:

#***************************************************************************
#* 
#* Description: label propagation
#* Author: Zou Xiaoyi (zouxy09@qq.com)
#* Date:   2015-10-15
#* HomePage: http://blog.csdn.net/zouxy09
#* 
#**************************************************************************
 
import time
import math
import numpy as np
from label_propagation import labelPropagation
 
# show
def show(Mat_Label, labels, Mat_Unlabel, unlabel_data_labels): 
    import matplotlib.pyplot as plt 
    
    for i in range(Mat_Label.shape[0]):
        if int(labels[i]) == 0:  
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Dr')  
        elif int(labels[i]) == 1:  
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Db')
        else:
            plt.plot(Mat_Label[i, 0], Mat_Label[i, 1], 'Dy')
    
    for i in range(Mat_Unlabel.shape[0]):
        if int(unlabel_data_labels[i]) == 0:  
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'or')  
        elif int(unlabel_data_labels[i]) == 1:  
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'ob')
        else:
            plt.plot(Mat_Unlabel[i, 0], Mat_Unlabel[i, 1], 'oy')
    
    plt.xlabel('X1'); plt.ylabel('X2') 
    plt.xlim(0.0, 12.)
    plt.ylim(0.0, 12.)
    plt.show()  
 
 
def loadCircleData(num_data):
    center = np.array([5.0, 5.0])
    radiu_inner = 2
    radiu_outer = 4
    num_inner = num_data / 3
    num_outer = num_data - num_inner
    
    data = []
    theta = 0.0
    for i in range(num_inner):
        pho = (theta % 360) * math.pi / 180
        tmp = np.zeros(2, np.float32)
        tmp[0] = radiu_inner * math.cos(pho) + np.random.rand(1) + center[0]
        tmp[1] = radiu_inner * math.sin(pho) + np.random.rand(1) + center[1]
        data.append(tmp)
        theta += 2
    
    theta = 0.0
    for i in range(num_outer):
        pho = (theta % 360) * math.pi / 180
        tmp = np.zeros(2, np.float32)
        tmp[0] = radiu_outer * math.cos(pho) + np.random.rand(1) + center[0]
        tmp[1] = radiu_outer * math.sin(pho) + np.random.rand(1) + center[1]
        data.append(tmp)
        theta += 1
    
    Mat_Label = np.zeros((2, 2), np.float32)
    Mat_Label[0] = center + np.array([-radiu_inner + 0.5, 0])
    Mat_Label[1] = center + np.array([-radiu_outer + 0.5, 0])
    labels = [0, 1]
    Mat_Unlabel = np.vstack(data)
    return Mat_Label, labels, Mat_Unlabel
 
 
def loadBandData(num_unlabel_samples):
    #Mat_Label = np.array([[5.0, 2.], [5.0, 8.0]])
    #labels = [0, 1]
    #Mat_Unlabel = np.array([[5.1, 2.], [5.0, 8.1]])
    
    Mat_Label = np.array([[5.0, 2.], [5.0, 8.0]])
    labels = [0, 1]
    num_dim = Mat_Label.shape[1]
    Mat_Unlabel = np.zeros((num_unlabel_samples, num_dim), np.float32)
    Mat_Unlabel[:num_unlabel_samples/2, :] = (np.random.rand(num_unlabel_samples/2, num_dim) - 0.5) * np.array([3, 1]) + Mat_Label[0]
    Mat_Unlabel[num_unlabel_samples/2 : num_unlabel_samples, :] = (np.random.rand(num_unlabel_samples/2, num_dim) - 0.5) * np.array([3, 1]) + Mat_Label[1]
    return Mat_Label, labels, Mat_Unlabel
 
 
# main function
if __name__ == "__main__":
    num_unlabel_samples = 800
    #Mat_Label, labels, Mat_Unlabel = loadBandData(num_unlabel_samples)
    Mat_Label, labels, Mat_Unlabel = loadCircleData(num_unlabel_samples)
    
    ## Notice: when use 'rbf' as our kernel, the choice of hyper parameter 'sigma' is very import! It should be
    ## chose according to your dataset, specific the distance of two data points. I think it should ensure that
    ## each point has about 10 knn or w_i,j is large enough. It also influence the speed of converge. So, may be
    ## 'knn' kernel is better!
    #unlabel_data_labels = labelPropagation(Mat_Label, Mat_Unlabel, labels, kernel_type = 'rbf', rbf_sigma = 0.2)
    unlabel_data_labels = labelPropagation(Mat_Label, Mat_Unlabel, labels, kernel_type = 'knn', knn_num_neighbors = 10, max_iter = 400)
    show(Mat_Label, labels, Mat_Unlabel, unlabel_data_labels)

3.2 Iterative Classification

考虑使用节点标签子集的知识为网络中的节点分配标签的半监督学习问题。具体来说,我们得到一个由图表示的网络 G G G有一组节点在 V V V和边集和 E E E表示节点之间的关系。每个节点在我∈在 v i ∈ V \displaystyle v_{i}\in V viV由其属性描述:特征向量 x i ∈ X x_i\in X xiX及其标签(或类别)和 y i ∈ Y y_i\in Y yiY.

V V V可以进一步分为两组节点: L L L,我们知道正确标签值(观察到的变量)的节点集,以及在 U U U,必须推断其标签的节点。集体分类任务是标记节点在 U U U使用标签集中的标签.

在这种情况下,传统的分类算法假设数据是从某个分布 (iid) 中独立且相同地提取的。这意味着为未观察到标签的节点推断的标签彼此独立。在执行集体分类时,不会做出这一假设。相反,可以使用三种不同类型的相关性来确定分类或标签在 v v v:

  1. 标签之间的相关性在 v v v和观察到的属性在 v v v. 使用特征向量的传统 iid 分类器是使用这种相关性的方法的一个例子。
  2. 标签之间的相关性在 v v v以及邻域节点的观察属性(包括观察标签)在 v v v
  3. 标签之间的相关性在 v v v以及附近物体的未观察到的标签在 v v v

集合分类是指利用上述三类信息对一组相互关联的对象进行组合分类。

参考文献

[1] 标签传播

[2] 朱晓进和 Zoubin Ghahramani. 通过标签传播从标记和未标记数据中学习。卡内基梅隆大学计算机科学学院,宾夕法尼亚州匹兹堡,技术报告。CMU-CALD-02–107, 2002。

[3] 标签传播算法(Label Propagation)及Python实现

[4] 半监督节点分类:标签传播和消息传递[斯坦福CS224W图机器学习]

[5] 半监督节点分类:标签传播和消息传递

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值