"CANE: Context-Aware Network Embedding for Relation Modeling"笔记及代码解释

CANE是发表在ACL2017上的一篇network embedding的论文,文章假设一个节点和不同的邻居节点相连有不同的embedding,提出考虑上下文的embedding方法CANE.

1.  Problem Formulation

假设存在信息网络G=(V,E,T),V是顶点集,E \subseteq V\times V是边集,T是节点的文本信息。一个节点v\in V的文本信息由一个词序列S_{v} =(w_{1}, w_{2},...,w_{n_{v}}), n_{v} = |S_{v}|   表示。

Context-free Embeddings: 一个节点的embedding是固定的,不会根据它的context(与它相互影响的另一个节点)改变。

Context-aware Embeddings: CANE根据一个节点不同的context学习不同的embedding。

2. Framework

对一个节点v有两种embedding,一种是基于结构的embedding v_{s} ,一种是基于文本的 v_{t}(可以是context-free或context-aware),然后concatenate拼接它们得到v = v_{s} \oplus v_{t}

CANE希望最大化边的目标函数如下:

L = \sum_{e\in E} L_{s}(e) + L_{t}(e)

Ls是基于结构的目标函数,Lt是基于文本的。

2.1  structure-based objective

基于结构的目标函数希望衡量一个有向边的对数似然:

L_{s}(e) = w_{u,v} log p(v^{s}|u^{s})                                        (3)

继承LINE,节点v被u generate 的条件概率如下:

p(v^{s}|u^{s}) = exp(u^{s}\cdot v^{s}) / \sum _{z \in V} exp(u^{s}\cdot v^{s})                   (4)

2.2 text-based objective

基于文本的目标函数为了与结构目标函数兼容,定义如下:

条件概率的计算如公式4所示。

structure-based embedding作为参数,text-based可以通过Context-free或Context-aware方法获得。

2.3 Context-free text embedding

将一个节点的词序列作为CNN的输入,通过一下三层得到embedding:

Looking-up. 将词序列中每个词转化为对应的词向量w \epsilon R^{​{d}'},从而得到序列embedding S.

Convolution. 从序列embedding S中提取局部特征,使用卷积矩阵C \epsilon R ^{d\times (l\times {d}' )}在一个长度为l(序列S中取l个单词)的滑动窗口上进行卷积操作:

Max-pooling. 


2.4 Context-aware text embedding

给定一条边e_{u,v}以及对应的两个文本序列Su,Sv,通过卷积层得到P\epsilon R^{d\times m}Q\epsilon R^{d\times n} (m,n分别代表Su,Sv的长度).引入注意力矩阵A\epsilon R^{d\times d},相关性矩阵F计算如下:

F = tanh(P^{T}AQ)

行池化和列池化分别如下:

对于公式(3)和(6),使用spftmax优化条件概率计算代价过大,使用负采样将目标方程变为如下所示:

使用Adam算法优化该目标。

 

关键代码讲解:

实验中使用的数据data.txt包含每个节点的文本描述,每行一个节点;graph.txt每行(例[440, 50] 两个节点编号)对应一条边。

#数据处理
class dataSet:
	def __init__(self,text_path,graph_path):
		text_file,graph_file=self.load(text_path,graph_path)	
		self.edges=self.load_edges(graph_file)
		self.text,self.num_vocab,self.num_nodes=self.load_text(text_file)
		self.negative_table=InitNegTable(self.edges)

	def load(self,text_path,graph_path):
		text_file=open(text_path,'rb').readlines()
		graph_file=open(graph_path,'rb').readlines()
		return text_file,graph_file

#将边准化成数字列表[[440, 50], [440, 103], [440, 105], [440, 116], [440, 135], [440, #235],...]
	def load_edges(self,graph_file):
		edges=[]
		for i in graph_file:
			edges.append(map(int,i.strip().split('\t')))
		return edges


#节点文本中每一单词对应一个index,每个节点文本对应一个300维的list
#[[  1   2   3 ...,   0   0   0]
#[ 38  39  40 ...,   0   0   0]
# ...,
# [100 256  55 ...,   0   0   0]]
	def load_text(self,text_file):
		vocab=learn.preprocessing.VocabularyProcessor(config.MAX_LEN)
		text=np.array(list(vocab.fit_transform(text_file)))
		num_vocab=len(vocab.vocabulary_)
		num_nodes=len(text)
		return text,num_vocab,num_nodes

	#负采样
	def negative_sample(self,edges):
		node1,node2=zip(*edges)
		sample_edges=[]
		func=lambda : self.negative_table[random.randint(0,config.neg_table_size-1)]
		for i in range(len(edges)):
			neg_node=func()
			while  node1[i] == neg_node or node2[i] == neg_node:
				neg_node=func()
			sample_edges.append([node1[i],node2[i],neg_node])

		return sample_edges

	def generate_batches(self,mode=None):

		num_batch=len(self.edges)/config.batch_size
		edges=self.edges
		if mode=='add':
			num_batch+=1
			edges.extend(edges[:(config.batch_size-len(self.edges)//config.batch_size)])
		if mode != 'add':
			random.shuffle(edges)
		sample_edges=edges[:num_batch*config.batch_size]
		sample_edges=self.negative_sample(sample_edges)


		batches=[]
		for i in range(num_batch):
			batches.append(sample_edges[i*config.batch_size:(i+1)*config.batch_size])
		# print sample_edges[0]
		return batches

模型(context-aware text embedding方法)

class Model:
    def __init__(self,vocab_size,num_nodes):
            
            # '''hyperparameter'''
        with tf.name_scope('read_inputs') as scope:
            self.Text_a=tf.placeholder(tf.int32,[config.batch_size,config.MAX_LEN],name='Ta')
            self.Text_b=tf.placeholder(tf.int32,[config.batch_size,config.MAX_LEN],name='Tb')
            self.Text_neg=tf.placeholder(tf.int32,[config.batch_size,config.MAX_LEN],name='Tneg')
            self.Node_a=tf.placeholder(tf.int32,[config.batch_size],name='n1')
            self.Node_b=tf.placeholder(tf.int32,[config.batch_size],name='n2')
            self.Node_neg=tf.placeholder(tf.int32,[config.batch_size],name='n3')

        with tf.name_scope('initialize_embedding') as scope:
            self.text_embed=tf.Variable(tf.truncated_normal([vocab_size,config.embed_size/2],stddev=0.3))
            self.node_embed=tf.Variable(tf.truncated_normal([num_nodes,config.embed_size/2],stddev=0.3))
            self.node_embed=tf.clip_by_norm(self.node_embed,clip_norm=1,axes=1)

        with tf.name_scope('lookup_embeddings') as scope:
            self.TA=tf.nn.embedding_lookup(self.text_embed,self.Text_a)
            self.T_A=tf.expand_dims(self.TA,-1)
           
            self.TB=tf.nn.embedding_lookup(self.text_embed,self.Text_b)
            self.T_B=tf.expand_dims(self.TB,-1)   
           
            self.TNEG=tf.nn.embedding_lookup(self.text_embed,self.Text_neg)
            self.T_NEG=tf.expand_dims(self.TNEG,-1)

            self.N_A=tf.nn.embedding_lookup(self.node_embed,self.Node_a)
            self.N_B=tf.nn.embedding_lookup(self.node_embed,self.Node_b)
            self.N_NEG=tf.nn.embedding_lookup(self.node_embed,self.Node_neg)
        self.convA,self.convB,self.convNeg=self.conv()
        self.loss=self.compute_loss()
    def conv(self):
        W2=tf.Variable(tf.truncated_normal([2,config.embed_size/2,1,100],stddev=0.3))
        rand_matrix=tf.Variable(tf.truncated_normal([100,100],stddev=0.3))

        convA=tf.nn.conv2d(self.T_A,W2,strides=[1,1,1,1],padding='VALID')        
        convB=tf.nn.conv2d(self.T_B,W2,strides=[1,1,1,1],padding='VALID')
        convNEG=tf.nn.conv2d(self.T_NEG,W2,strides=[1,1,1,1],padding='VALID')


        hA=tf.tanh(tf.squeeze(convA))
        hB=tf.tanh(tf.squeeze(convB))
        hNEG=tf.tanh(tf.squeeze(convNEG))

        tmphA=tf.reshape(hA,[config.batch_size*(config.MAX_LEN-1),config.embed_size/2])
        ha_mul_rand=tf.reshape(tf.matmul(tmphA,rand_matrix),[config.batch_size,config.MAX_LEN-1,config.embed_size/2])

        #-----------------------need to be revise
        r1=tf.matmul(ha_mul_rand,hB,adjoint_b=True)
        r3=tf.matmul(ha_mul_rand,hNEG,adjoint_b=True)
        att1=tf.expand_dims(tf.stack(r1),-1)
        att3=tf.expand_dims(tf.stack(r3),-1)

        att1=tf.tanh(att1)
        att3=tf.tanh(att3)

        pooled_A=tf.reduce_mean(att1,2)
        pooled_B=tf.reduce_mean(att1,1)
        pooled_NEG=tf.reduce_mean(att3,1)

        a_flat=tf.squeeze(pooled_A)
        b_flat=tf.squeeze(pooled_B)
        neg_flat=tf.squeeze(pooled_NEG)

        w_A=tf.nn.softmax(a_flat)
        w_B=tf.nn.softmax(b_flat)
        w_NEG=tf.nn.softmax(neg_flat)

        rep_A=tf.expand_dims(w_A,-1)
        rep_B=tf.expand_dims(w_B,-1)
        rep_NEG=tf.expand_dims(w_NEG,-1)

        hA=tf.transpose(hA,perm=[0,2,1])
        hB=tf.transpose(hB,perm=[0,2,1])
        hNEG=tf.transpose(hNEG,perm=[0,2,1])

        rep1=tf.matmul(hA,rep_A)
        rep2=tf.matmul(hB,rep_B)
        rep3=tf.matmul(hNEG,rep_NEG)

        attA=tf.squeeze(rep1)
        attB=tf.squeeze(rep2)
        attNEG=tf.squeeze(rep3)

        return attA,attB,attNEG
    def compute_loss(self):
        p1=tf.reduce_sum(tf.multiply(self.convA,self.convB),1)
        p1=tf.log(tf.sigmoid(p1)+0.001)

        p2=tf.reduce_sum(tf.multiply(self.convA,self.convNeg),1)
        p2=tf.log(tf.sigmoid(-p2)+0.001)

        p3=tf.reduce_sum(tf.multiply(self.N_A,self.N_B),1)
        p3=tf.log(tf.sigmoid(p3)+0.001)
#后面的mul()函数依次改成multiply
        p4=tf.reduce_sum(tf.mul(self.N_A,self.N_NEG),1)
        p4=tf.log(tf.sigmoid(-p4)+0.001)


        p5=tf.reduce_sum(tf.mul(self.convB,self.N_A),1)
        p5=tf.log(tf.sigmoid(p5)+0.001)

        p6=tf.reduce_sum(tf.mul(self.convNeg,self.N_A),1)
        p6=tf.log(tf.sigmoid(-p6)+0.001)

        p7=tf.reduce_sum(tf.mul(self.N_B,self.convA),1)
        p7=tf.log(tf.sigmoid(p7)+0.001)

        p8=tf.reduce_sum(tf.mul(self.N_B,self.convNeg),1)
        p8=tf.log(tf.sigmoid(-p8)+0.001)

        rho1=0.7
        rho2=1.0
        rho3=0.1
        temp_loss=rho1*(p1+p2)+rho2*(p3+p4)+rho3*(p5+p6)+rho3*(p7+p8)
        loss=-tf.reduce_sum(temp_loss)
        return loss

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值