负采样

介绍

负采样作为无监督训练的重要手段,是指采样与给定顶点没有直接边关系的顶点。同邻居采样类似,负采样也有不同的实现策略,如随机、按顶点入度等。作为GNN的一种常见算子,负采样支持扩展和面向场景的自定义。

用法

接口

负采样算子以边或顶点类型作为输入。当输入边类型时,表示“在该边类型下采样与给定顶点无直接关联的顶点”,候选集为边的目的顶点中与给定顶点不直接相连的顶点。当输入顶点类型时,表示“在该类型的顶点中采样与给定顶点无关联的顶点”,此时需要用户指定候选顶点集。采样结果组织成Nodes对象(类似一跳邻居采样,但不存在Edges对象)。一个负采样操作可具体分为以下3步实现:

  • 通过g.negative_sampler()定义负采样算子,得到NegativeSampler对象S

  • 调用S.get(ids), 得到Nodes对象;

  • 调用Nodes对象的接口获取具体的值;

def negative_sampler(object_type, expand_factor, strategy="random"):
"""
Args:
  object_type(string): 边类型或顶点类型
  expand_factor(int):  负采样个数
  strategy(string):    采样策略,具体参考下文的详细解释
Return:
  NegativeSampler对象
"""
def NegativeSampler.get(ids, **kwargs):
""" 对指定顶点ids进行负采样
Args:
  ids(numpy.ndarray): 一维int64数组
  **kwargs: 扩展参数,不同采样策略需要的参数可能不同
Return:
  Nodes对象
"""

示例

es = g.edge_sampler("buy", batch_size=3, strategy="random")
ns = g.negative_sampler("buy", 5, strategy="random")

for i in range(5):
    edges = es.get()
    neg_nodes = ns.get(edges.src_ids)
    
    print(neg_nodes.ids)          # shape为(3, 5)
    print(neg_nodes.int_attrs)    # shape为(3, 5, count(int_attrs))
    print(neg_nodes.float_attrs)  # shape为(3, 5, count(float_attrs))
# 1. 负采样一跳邻居顶点
g.V().outNeg(edge_type).sample(count).by(strategy)

# 负采样二跳邻居顶点
g.V().outNeg(edge_type).sample(count).by(strategy).outNeg(edge_type).sample(count).by(strategy)

# 在给定顶点候选集上负采样
g.V().Neg(node_type).sample(count).by("node_weight")

负采样策略

GL目前已支持以下几种负采样策略,对应产生NegativeSampler对象时的strategy参数。

strategy 说明
random 随机负采样,不保证true-negative
in_degree 以顶点入度分布为概率进行负采样,保证true-negative
node_weight 以顶点权重为概率进行负采样样,保证true-negative

按指定属性条件的负采样

GL提供了按照给定的属性列来进行负采样的功能,在g.negative_sampler 里新增参数,并且要求输入为正样本对(src_ids, dst_ids)。

  • 定义

def negative_sampler(object_type, expand_factor, strategy='random', 
                     conditional=True, #新增参数,下面均为新增参数(可选)
                     unique=False,
                     int_cols=[],
                     int_props=[],
                     float_cols=[],
                     float_props=[],
                     str_cols=[],
                     str_props=[]):
"""
Args:
    object_type(string): 边类型或顶点类型
    expand_factor(int): 负采样个数
    strategy(string): 采样策略,支持random, in_degree, node_weight
    conditional(bool): 是否使用按条件负采样。按条件负采样时该值设为True
    unique(bool): 负样本是否需要是unique的。
    int_cols(list): 指定的int类型属性的下标,表示在这些指定的属性下进行负采样。比如输入的正样
        本对里dst_ids的int属性有3个,int_cols=[0,1]表示,在第一个int属性和dst_ids的第1个
        int属性一样的节点,以及第2个int属性和dst_ids的第2个属性一样的节点里选取负样本。
    int_props(list): int_cols里每个属性采样的比例。比如int_cols=[0,1],int_props=[0.1,0.2],
        表示在和dst_ids的第1个int属性一样的点里采样expand_factor*0.1个负样本,在和dst_ids的
        第2个int属性一样的点里采样expand_factor*0.2个负样本。
    float_cols(list): 指定的float类型属性的下标,同int_cols。
    float_props(list): float_cols的每个属性所占比例,同int_props。
    str_cols(list): 指定的string类型属性的下标,同int_cols。
    str_props(list): str_cols的每个属性所占比例,同int_props.
Return:
    NegativeSampler对象
"""

注意:
负采样时,会在指定属性条件里按照strategy指定的策略负采样,要求sum(int_props) + sum(float_props) + sum(str_props) <= 1,如果该值<1,剩下的负样本采样时不再按照指定属性条件,只按照strategy采样。

  • 接口

def get(src_ids, dst_ids):
""" 对指定的src_ids, dst_ids正样本对进行负采样。
Args:
    src_ids(numpy.ndarray): 一维int64数组,正样本源节点的ids
    dst_ids(numpy.ndarray): 一维int64数组,正样本目的节点的ids
Return:
    Nodes对象
"""

采样时会去除所有src_ids的全部邻居。

  • 示例

"""
假设点类型为item,它有3个int属性,1个float属性,1个string属性。
正样本为:
    src_ids = np.array([1,2,3,4,5])
    dst_ids = np.array([6,2,3,5,9])
现在需要在按照'node_weight'策略从给定的点表里进行负采样,并且要求在第1个int属性值等于dst_ids
的第1个int属性的点里采样2个负节点,在第1个string属性值等于dst_ids的第1个string属性值的点里采样
2个负样本
"""
s = g.negative_sampler('item',
                       expand_factor=4,
                       strategy='node_weight',
                       conditional=True,
                       unique=False,
                       int_cols=[0],
                       int_props=[0.5],
                       str_cols=[0],
                       str_props=[0.5])
src_ids = np.array([1,2,3,4,5])
dst_ids = np.array([6,2,3,5,9])
nodes = s.get(src_ids, dst_ids)