//  [python]
def _SURGE(self):

        hparams = self.hparams
        hist_input = tf.concat(
            [self.item_history_embedding, self.cate_history_embedding], 2
        )
        X = hist_input
        self.mask = self.iterator.mask
        self.real_mask = tf.cast(self.mask, tf.float32)
        self.real_sequence_length = tf.reduce_sum(self.mask, 1)
        self.target_item_embedding = self.target_item_embedding[:,:40]
        self.max_n_nodes = int(X.get_shape()[1])

        with tf.name_scope('learnable_graph'):
            ## Hyperparameter study - metric learning的实现

            # 1.sequence (add self loop)
#              A = tf.matmul(X, tf.transpose(X, (0,2,1))) # B*L*L
            #  #  #  A = tf.linalg.band_part(tf.ones_like(A),0,1) # B*L*L
#              A = tf.linalg.band_part(tf.ones_like(A),0,3) # B*L*L

            # 1.complete graph
            #  A = tf.matmul(X, tf.transpose(X, (0,2,1))) # B*L*L
            #  A = tf.ones_like(A) # B*L*L

            # 2. inner-product similarity
            #  A = tf.matmul(X, tf.transpose(X, (0,2,1))) # B*L*L

            # 2.cosine similarity
            # method1
            #  X_norm = tf.sqrt(tf.reduce_sum(tf.square(X), axis=-1)) # B*L*F -> B*L
            #  X_norm = tf.matmul(tf.expand_dims(X_norm, 2),tf.expand_dims(X_norm, 1)) # B*L*1 x B*1*L = B*L*L
            #  A = A/X_norm
            # method2
            #  X_norm = tf.nn.l2_normalize(self.X,dim=2)
            # 应该生成个新变量，因为归一化之后，后面的embedding会影响；或者后面embedding都归一化，试试效果吧
            X_norm = tf.nn.l2_normalize(X,dim=2)
            A = tf.matmul(X_norm, tf.transpose(X_norm, (0,2,1))) # B*L*L
            # 原来没加，会影响后面mask后的排序的图构建（之前cosine效果不稳定的原因就是这个）
            # 不对吧，cosine的阈值就是-1～1
            #  A = tf.nn.softmax(A, axis=-1)
            A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))

            # 再加上时间关系
            #  A = A + tf.linalg.band_part(tf.ones_like(A),0,3) # B*L*L

            # 考虑query的cosine
#              X_fts = tf.concat([X, tf.tile(tf.expand_dims(self.target_item_embedding, 1), [1, self.max_n_nodes, 1])], -1)
            #  #  X_fts = tf.layers.dense(X_tfs, 40, use_bias=False)
            #  X_fts = tf.nn.l2_normalize(X_fts,dim=2)
            #  A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
#              A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))

            #  3.weighted cosine similarity
#              self.weighted_tensor = tf.layers.dense(tf.ones([1, 1]), 40, use_bias=False)
            #  X_fts = X * tf.expand_dims(self.weighted_tensor, 0)
            #  #  cosine
            #  X_fts = tf.nn.l2_normalize(X_fts,dim=2)
            #  A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
            #  #  # 原来没加，会影响后面mask后的排序的图构建（之前cosine效果不稳定的原因就是这个）
            #  #  A = tf.nn.softmax(A, axis=-1)
            #  # 将softmax换为min-max再看看还有没有梯度消失的问题
#              A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))

            #  3.weighted cosine similarity(fcn)
            #  X_fts = self._fcn_weighted_net(X, 40, 'cosine_weighs')
            #  X_fts = tf.nn.l2_normalize(X_fts,dim=2)
            #  A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
            #  A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))


            # 考虑query的weighted cosine
#              X_tar = tf.concat([X, tf.tile(tf.expand_dims(self.target_item_embedding, 1), [1, self.max_n_nodes, 1])], -1)
#              X_fts = tf.layers.dense(X_tar, 40, use_bias=False)
#              # weights
#              self.weighted_tensor = tf.layers.dense(tf.ones([1, 1]), 40, use_bias=False)
#              X_fts = X_fts * tf.expand_dims(self.weighted_tensor, 0)
#              #  cosine
#              X_fts = tf.nn.l2_normalize(X_fts,dim=2)
#              A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
#              #  # 原来没加，会影响后面mask后的排序的图构建（之前cosine效果不稳定的原因就是这个）
#  #              A = tf.nn.softmax(A, axis=-1)
#              # 将softmax换为min-max再看看还有没有梯度消失的问题
            #  A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))


            # 4.gat_att
#              X_fts = tf.layers.dense(X, 40, use_bias=False)
            #  f_1 = tf.layers.dense(X_fts, 1)
            #  f_2 = tf.layers.dense(X_fts, 1)
            #  A = f_1 + tf.transpose(f_2, (0,2,1)) # B*L*1 + B*L*1 -> B*L*L
            #  A = tf.nn.leaky_relu(A)
            #  # softmax具有可加性，优于sigmoid
#              A = tf.nn.softmax(A, axis=-1)

            # 4.learnable graph - gat_att_multi
            #  A = []
            #  for i in range(4):
                #  X_fts = tf.layers.dense(X, 40, use_bias=False)
                #  f_1 = tf.layers.dense(X_fts, 1)
                #  f_2 = tf.layers.dense(X_fts, 1)
                #  A += [tf.nn.softmax(tf.nn.leaky_relu(f_1 + tf.transpose(f_2, (0,2,1))), axis=-1)]
            #  A = tf.reduce_mean(tf.stack(A, 0), 0)

            # 5.naive_att
            #  # 可能是向量内积导致难以学习（上面weighted cosine的元素积就没有问题，但是效果不稳定），A中一个节点的所有边的分数完全一样，这样导致在后面构建图的时候用的是greater_equal生成完全图；如果用greater则一条边也连不上
            #  #  X_fts = tf.layers.dense(self.X, 40, use_bias=False, activation=tf.nn.relu)
            #  #  将非线性去掉再看看还有没有梯度消失的问题
            #  X_fts = tf.layers.dense(X, 40, use_bias=False)
            #  #  X_fts = tf.nn.l2_normalize(X_fts,dim=2)
            #  A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
            #  #  A = tf.nn.softmax(A, axis=-1)
            #  #  将softmax换为min-max再看看还有没有梯度消失的问题
            #  A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))
#
#              # 5.naive_att(fcn version)
            #  #  X_fts = self._fcn_transform_net(X, [40], scope="fcn_alpha")
            #  X_fts = self._fcn_transform_net_rmact(X, [40], scope="fcn_alpha")
            #  #  X_fts = self._fcn_transform_net_rmact_rmb(X, [40], scope="fcn_alpha")
            #  A = tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1))) # B*L*L
#              A = (A - tf.reduce_min(A, -1, keepdims=True)) / (tf.reduce_max(A, -1, keepdims=True) - tf.reduce_min(A, -1, keepdims=True))


            # 5.naive_att_multi
            #  A = []
            #  for i in range(4):
                #  X_fts = tf.layers.dense(X, 40, use_bias=False, activation=tf.nn.relu)
                #  A += [tf.matmul(X_fts, tf.transpose(X_fts, (0,2,1)))] # B*L*L
            #  A = tf.reduce_mean(tf.stack(A, 0), 0)


            # mask graph to 0/1
            A = A * tf.expand_dims(self.real_mask, -1) * tf.expand_dims(self.real_mask, -2)
            # 余弦相似度就是0-1

            # 计算相对阈值
            #  import pdb; pdb.set_trace()
            A_flatten = tf.reshape(A, [tf.shape(A)[0],-1])
            if 'kwai' in socket.gethostname():
                sorted_A_flatten = tf.contrib.framework.sort(A_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
            else:
                sorted_A_flatten = tf.sort(A_flatten, direction='DESCENDING', axis=-1) # B*L -> B*L
            # 1.这种处理方式下，边的数量的前提是完全图，所以不合理，很容易取到0元素，因为用的greater_equal，所以容易构建完全图。还是用count_nonzero，对每个batch进行count
            #  num_edges = tf.cast(self.sequence_length * self.sequence_length, tf.float32)
            # 2.这个问题在于A的矩阵是动态的，边的数量也是动态的，构建图
            num_edges = tf.cast(tf.count_nonzero(A, [1,2]), tf.float32) # B
            to_keep_edge = tf.cast(tf.math.ceil(num_edges * self.threshold), tf.int32)
            if 'kwai' in socket.gethostname():
            #  if 'rl3' in socket.gethostname():
                n_graphs = tf.shape(X)[0]
                threshold_index = tf.stack([tf.range(n_graphs), tf.cast(to_keep_edge, tf.int32)], 1) # B*2
                #  threshold_score = tf.batch_gather(sorted_A_flatten, threshold_index) # indices[:-1]=(B) + data[indices[-1]=() --> (B)
                threshold_score = tf.gather_nd(sorted_A_flatten, threshold_index) # indices[:-1]=(B) + data[indices[-1]=() --> (B)
            else:
                threshold_score = tf.gather_nd(sorted_A_flatten, tf.expand_dims(tf.cast(to_keep_edge, tf.int32), -1), batch_dims=1) # indices[:-1]=(B) + data[indices[-1]=() --> (B)
            #  threshold_score = tf.reduce_min(tf.nn.top_k(A_tmp, threshold_index, sorted=False).values)

            #  pp3 = sorted_A_flatten[0] # should be different
            #  pp4 = sorted_A_flatten[0][-1] # should be 0
            #  pp5 = threshold_score[0] # should be smaller than pp3
            #  self.pp3=tf.Print(pp3,["pp3:", pp3])
            #  self.pp4=tf.Print(pp4,["pp4:", pp4])
            #  self.pp5=tf.Print(pp5,["pp5:", pp5])

            #  A = tf.cast(tf.greater_equal(A, self.threshold), tf.float32)
            #  A = tf.cast(tf.greater_equal(A, tf.expand_dims(tf.expand_dims(threshold_score, -1), -1)), tf.float32)
            #  self.A = tf.cast(tf.greater(A, tf.expand_dims(tf.expand_dims(threshold_score, -1), -1)), tf.float32)
            A = tf.cast(tf.greater(A, tf.expand_dims(tf.expand_dims(threshold_score, -1), -1)), tf.float32)
            #  A = tf.cast(tf.greater_equal(A, tf.expand_dims(threshold_score, -1)), tf.float32)
            # 其实不能用greater_equal，这样的话mask也会变为1，但其实后面asap也有一个mask的过程，所以其实无所谓。
            #  A = tf.cast(tf.greater(A, 0.0), tf.float32)

            # 这个地方节点的度没有问题。
            #  pp2 = tf.sort(tf.cast(tf.count_nonzero(A, -1), tf.float32)[0], direction='DESCENDING', axis=-1)
            #  [pp2:][8 6 5...]
            #  [pp2:][17 16 15...]
            #  self.pp2=tf.Print(pp2,["pp2:", pp2])

            ## >>>>>>> ablation_study weighted gcn >>>>>>
            # mask graph to 0/0~1
            #  A = A * tf.expand_dims(self.real_mask, -1) * tf.expand_dims(self.real_mask, -2)
            #  A_mask = tf.cast(tf.greater_equal(A, self.threshold), tf.float32)
            #  A = A * A_mask
            ## <<<<<<< ablation_study weighted gcn <<<<<<
            self.A = A
            self.X = X

        with tf.name_scope('graph_coarsening'):
            ## Hyperparameter study: ratio, layers and sum

            # 1 layer - 对照实验，用原始特征，即知道核心item后，用原始特征也能取得不错的效果。但问题在于score是根据融合后的特征选择的。
            #  tmp_X = X
            #  X, A = self._interest_fusion(X, A, 0, shared=False, reuse=False)
            #  X = tmp_X

            #  for l in range(2):
                #  reuse = False if l==0 else True
                #  X, A = self._interest_fusion(X, A, layer=l, reuse=reuse)

            #  last
            for l in range(self.pool_layers):
                reuse = False if l==0 else True
                X, A, cluster_fea, alphas = self._interest_fusion(X, A, layer=l, reuse=reuse)

            # sum
            #  readout = []
            #  readout = [X]
            #  for l in range(self.pool_layers):
                #  reuse = False if l==0 else True
                #  X, A, cluster_fea, alphas = self._interest_fusion(X, A, layer=l, reuse=reuse)
                #  readout += [X]
            #  X = sum(readout)

            #  不留存每一步的特征再求和，而是留存每一步的readout，共给最后一层
            #  readout = []
            #  for l in range(self.pool_layers):
            #      reuse = False if l==0 else True
            #      X, A, cluster_fea, alphas = self._interest_fusion(X, A, layer=l, reuse=reuse)
            #      readout += [cluster_fea]
            #  #  cluster_fea = sum(readout)
            #  cluster_fea = tf.concat(readout, 1)


        with tf.name_scope('flatten_sequence'):

            if self.watch_length:
                pp1 = tf.reduce_max(tf.reduce_sum(self.mask, 1))
                pp2 = tf.shape(self.mask)
                pp3 = tf.shape(X)
                self.pp1=tf.Print(pp1,["pp1:", pp1]) # 要对应名称，因为执行顺序可能不一样
                self.pp2=tf.Print(pp2,["pp2:", pp2])
                self.pp3=tf.Print(pp3,["pp3:", pp3])

            # flatten graph to sequence according to initial position
            output_shape = self.mask.get_shape()
            '''
            argsort的参数stable，默认为False，设置为True确保相同元素保序，实际测试时True和False都保序。
            因为乱序尚未实现：https://www.tensorflow.org/api_docs/python/tf/argsort?hl=zh-cn
            sort没有stable参数，相同元素谁前谁后无所谓。
            '''
            if 'kwai' in socket.gethostname():
                sorted_mask_index = tf.contrib.framework.argsort(self.mask, direction='DESCENDING', stable=True, axis=-1) # B*L -> B*L
                sorted_mask = tf.contrib.framework.sort(self.mask, direction='DESCENDING', axis=-1) # B*L -> B*L
            else:
                sorted_mask_index = tf.argsort(self.mask, direction='DESCENDING', stable=True, axis=-1) # B*L -> B*L
                sorted_mask = tf.sort(self.mask, direction='DESCENDING', axis=-1) # B*L -> B*L
            sorted_mask.set_shape(output_shape)
            sorted_mask_index.set_shape(output_shape)
            X = tf.batch_gather(X, sorted_mask_index) # B*L*F  < B*L = B*L*F
            self.mask = sorted_mask
            self.real_mask = tf.cast(self.mask, tf.float32)
            self.sequence_length = tf.reduce_sum(self.mask, 1) # B

            # 按batch 切割graph和特征，加速训练
            self.to_max_length = tf.range(tf.reduce_max(self.sequence_length)) # l
            X = tf.gather(X, self.to_max_length, axis=1) # B*L*F -> B*l*F
            self.mask = tf.gather(self.mask, self.to_max_length, axis=1) # B*L -> B*l
            self.real_mask = tf.cast(self.mask, tf.float32)
            self.sequence_length = tf.reduce_sum(self.mask, 1) # B

            if self.watch_length:
                pp4 = tf.reduce_max(tf.reduce_sum(self.mask, 1))
                pp5 = tf.shape(self.mask)
                pp6 = tf.shape(X)
                self.pp4=tf.Print(pp4,["pp4:", pp4])
                self.pp5=tf.Print(pp5,["pp5:", pp5])
                self.pp6=tf.Print(pp6,["pp6:", pp6])
                #  [pp1:][50]
                #  [pp2:][2500 50]
                #  [pp3:][2500 50 40]
                # 有问题

            # prediction - Our(AGRU 省略求attention，利用输出的cluster score)
            #  _, alphas = self._attention_fcn(self.target_item_embedding, X, 'AGRU', False, return_alpha=True)
            _, final_state = dynamic_rnn_dien(
                VecAttGRUCell(hparams.hidden_size),
                inputs=X,
                att_scores = tf.expand_dims(alphas, -1),
                sequence_length=self.sequence_length,
                dtype=tf.float32,
                scope="gru"
            )
            model_output = tf.concat([self.target_item_embedding, final_state, cluster_fea, self.target_item_embedding*cluster_fea], 1)
            #  model_output = tf.concat([self.target_item_embedding, final_state, cluster_fea], 1)

            # prediction - DIN
            #  attention_output = self._attention_fcn(self.target_item_embedding, X, 'DIN', False)
            #  att_fea = tf.reduce_sum(attention_output, 1)
            #  self.hist_embedding_sum = tf.reduce_sum(hist_input*tf.expand_dims(self.real_mask, -1), 1)
            #  model_output = tf.concat([self.target_item_embedding, self.hist_embedding_sum, att_fea, cluster_fea], -1)

            # prediction - GRU
            #  rnn_outputs, final_state = dynamic_rnn(
                #  GRUCell(self.hidden_size),
                #  inputs=X,
                #  sequence_length=self.sequence_length,
                #  dtype=tf.float32,
                #  scope="gru",
            #  )
            #  model_output = tf.concat([final_state, cluster_fea, self.target_item_embedding], -1)

            # prediction - AGRU 效果不如我们共享的策略，但是比直接应用agru要好（dien的删减版，不如dien）
            #  _, alphas = self._attention_fcn(self.target_item_embedding, X, 'AGRU', False, return_alpha=True)
            #  _, final_state = dynamic_rnn_dien(
                #  VecAttGRUCell(hparams.hidden_size),
                #  inputs=X,
                #  att_scores = tf.expand_dims(alphas, -1),
                #  sequence_length=self.sequence_length,
                #  dtype=tf.float32,
                #  scope="gru2"
            #  )
            #  self.hist_embedding_sum = tf.reduce_sum(hist_input*tf.expand_dims(self.real_mask, -1), 1)
            #  model_output = tf.concat([self.target_item_embedding, final_state, self.hist_embedding_sum, self.target_item_embedding*self.hist_embedding_sum, cluster_fea], 1)

            # prediction - DIEN 效果不好，我们的兴趣提取层和rnn功能重复，会干扰学习
            #  X = tf.batch_gather(hist_input, sorted_mask_index) # B*L*F  < B*L = B*L*F
            #  rnn_outputs, _ = dynamic_rnn_dien(
            #      GRUCell(hparams.hidden_size),
            #      inputs=X,
            #      sequence_length=self.sequence_length,
            #      dtype=tf.float32,
            #      scope="gru1"
            #  )
            #  _, alphas = self._attention_fcn(self.target_item_embedding, rnn_outputs, 'DIEN', False, return_alpha=True)
            #  _, final_state = dynamic_rnn_dien(
            #      VecAttGRUCell(hparams.hidden_size),
            #      inputs=rnn_outputs,
            #      att_scores = tf.expand_dims(alphas, -1),
            #      sequence_length=self.sequence_length,
            #      dtype=tf.float32,
            #      scope="gru2"
            #  )
            #  self.hist_embedding_sum = tf.reduce_sum(hist_input*tf.expand_dims(self.real_mask, -1), 1)
            #  #  model_output = tf.concat([self.target_item_embedding, final_state, self.hist_embedding_sum, self.target_item_embedding*self.hist_embedding_sum], 1)
            #  model_output = tf.concat([self.target_item_embedding, final_state, self.hist_embedding_sum, self.target_item_embedding*self.hist_embedding_sum, cluster_fea], 1)

        return model_output

#      def _get_loss(self):
        #  """Make loss function, consists of data loss, regularization loss and graph loss
        #
        #  Returns:
        #      obj: Loss value
        #  """
        #
        #  self.data_loss = self._compute_data_loss()
        #  self.regular_loss = self._compute_regular_loss()
        #  #  self.graph_loss = self._compute_graph_loss()
        #  #  self.loss = tf.add(self.data_loss, self.regular_loss)
        #  #  self.loss = self.data_loss + self.regular_loss + self.graph_loss
        #
        #  #  self.LP_loss = self._compute_LP_loss()
        #  #  self.entr_loss = self._compute_entr_loss()
        #
        #  #  self.loss = self.data_loss + self.regular_loss + self.LP_loss + self.entr_loss
        #  #  self.loss = self.data_loss + self.regular_loss + self.entr_loss
        #  self.loss = self.data_loss + self.regular_loss + self.LP_loss
        #
#          #  self.loss = self.data_loss + self.regular_loss + self.graph_loss + self.entr_loss

        return self.loss