TPGR代码详解 Large-Scale Interactive Recommendation with Tree-Structured Policy Gradient

本文主要是介绍TPGR代码详解 Large-Scale Interactive Recommendation with Tree-Structured Policy Gradient,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

论文阅读笔记链接
github代码链接

目录

  • 论文内容
    • MDP过程
    • 训练TPGR过程:
    • 采样过程
  • 结构讲解
  • DEBUG过程
  • 代码详解
    • 整体流程
    • run函数
    • 1. PRE_TRAIN()
      • init()
      • make_graph()
      • train()
      • evaluate
    • 一些问题记录
      • 参数相关
    • 2. Tree()
      • init()
      • construct_tree()
        • build_mapping()
        • hierarchical_code()
        • pca_clustering()
    • 3. TPGR()
      • init()
      • make_graph()
      • evaluate
      • train
  • 补充知识
    • 函数
    • GRU
    • 报错

论文内容

论文阅读笔记

MDP过程

  1. state:用户历史交互记录(itemID编码为embedding,reward编码为one-hot)经过SRU编码后拼接上user_status(给正/负反馈的数量,给连续正/负反馈的数量)
  2. action:选择为用户 i i i推荐的产品 j j j
  3. reward: R ( s , a ) = r i , j + α ( c p − c n ) R(s,a)=r_{i,j}+\alpha(c_p-c_n) R(s,a)=ri,j+α(cpcn),前者表示环境模拟器中标准化[-1~1]后的分数,后者表示连续正反馈数量减去连续负反馈数量。会映射到10维的向量。

训练TPGR过程:

  1. 给定孩子数 c c c和所有物品的 e m b e d d i n g embedding embedding(rating,VAE, MF),建立一颗层次聚类树。
  2. 算出非叶节点数量 L L L,对应策略网络数量,进行随机初始化。
  3. 根据Sampling Episode采样出一条路径 ( s 1 , p 1 , r 1 . . . . s n , p n , r n ) (s_1, p_1, r_1....s_n, p_n, r_n) (s1,p1,r1....sn,pn,rn),通过连乘选路径时做选择的概率( π θ {\pi}_{\theta} πθ输出做某个选择的概率),将路径映射到动作 a t a_t at
  4. 使用策略梯度下降进行训练,已知 ( a t , s t , G ) (a_t,s_t,G) (at,st,G) G G G为累积收益。

采样过程

  1. s t a t e state state输入 d d d个策略网络(物品id映射为embedding,reward经过one-hot编码,输入SRU,再与user_status拼接,得到state),做 d d d次选择,每次从 1 ∼ c 1\sim c 1c中选一个,同时记录下每次选择的 n o d e _ i n d e x node\_index node_index
  2. p t = ( c 1 , c 2 , . . . , c d ) p_t=(c_1,c_2,...,c_d) pt=(c1,c2,...,cd),将 p t p_t pt映射到物品 a t a_t at,获取 r t r_t rt(将物品推荐给用户了,有反馈)。
  3. 根据状态转移函数得到 s t + 1 s_{t+1} st+1,重复上述过程,直到得到 E = ( s 1 , p 1 , r 1 . . . . s n , p n , r n ) E=(s_1, p_1, r_1....s_n, p_n, r_n) E=(s1,p1,r1....sn,pn,rn)
    state表示

代码顺序:

  1. 先预训练rnn(recommender调用tpgr中的PRE_TRAIN)
  2. 再建树(recommender调用tpgr中的Tree)
  3. 最后训练TPGR(recommender调用tpgr中的TPGR)

结构讲解

在github下载代码解压后,可以看见目录如下
在这里插入图片描述

  • data目录中的rating存放的是数据集,形式是:user item rating;可以根据需要替换成自己的数据集。
  • src目录就是所有代码啦

看代码前我们先看看readme文件,如果某个代码中有Readme一定要先看看哦,里面通常包括了整个项目的简单介绍,包括一些参数设置。
其README文件和解释如下:

  1. run_time_tools文件中的mf_with_bias()使用PMF获取item的embedding,将结果item_embeddings_value存在rating_file(config文件定义的)中。
In run_time_tools.py, mf_with_bias() is to gain embeddings of items by utilizing the PMF model; 
  1. clustering_vector_constructor()按照rating/vae/mf,建立平衡层次聚类树,存在result_file_path中。
clustering_vector_constructor() is to construct item representation in order to building the balanced hierarchical clustering tree.
  1. tpgr.py中有三个类,PRE_TRAIN:rnn预训练; TREE:构建树; TPGR:TPGR训练。
In tpgr.py, class PRE_TRAIN, TREE and TPGR correspond to the rnn pretraining step, tree constructing step and model training step of the TPGR model.
  1. config参数如下,具体含义都写出来了,就不解释了:
[META]
ACTION_DIM: the embedding dimension of each action (item).
STATISTIC_DIM: the dimension of the statistic information, in our implementation, there are 9 kinds of statistic information.
REWARD_DIM: the dimension of the one-hot reward representation.
MAX_TRAINING_STEP: the maximum training steps.
DISCOUNT_FACTOR: discount factor for calculating cumulated reward.
EPISODE_LENGTH: the number of recommendation interactions of each episode.
LOG_STEP: the number of interval steps for printing evaluation logs when training the TPGR model.[ENV]
RATING_FILE: the name of the original rating file, the rating files are stored in data/rating as default.
ALPHA: to control the ratio of the sequential reward as described in the paper.
BOUNDARY_RATING: to divide positive and negative rating.
MAX_RATING: maximum rating in the rating file.
MIN_RATING: minimum rating in the rating file.[TPGR]
PRE_TRAINING: bool type (T/F), indicating whether conducting pretraining step.
PRE_TRAINING_STEP: the number of pretraining steps. # 30
PRE_TRAINING_MASK_LENGTH: control the length of the historical rewards to recover when pretraining. # 32
PRE_TRAINING_SEQ_LENGTH: the number of recommendation interactions of each episode when pretraining. # 64
PRE_TRAINING_MAX_ITEM_NUM: the maximum number of items to be considered when pretraining. # 64
PRE_TRAINING_RNN_TRUNCATED_LENGTH: rnn truncated length. # 32
PRE_TRAINING_BATCH_SIZE: the batch size adopted in pre-training step.
PRE_TRAINING_LOG_STEP: the number of interval steps for printing evaluation logs when pre-training.
PRE_TRAINING_LEARNING_RATE: initialized learning rate for AdamOptimizer adopted in pre-training step.
PRE_TRAINING_L2_FACTOR: l2 factor for l2 normalization adopted in pre-training step.CONSTRUCT_TREE: bool type (T/F), indicating whether conducting tree building step.
CHILD_NUM: children number of the non-leaf nodes in the tree.
CLUSTERING_TYPE: clustering type, PCA or KMEANS.
CLUSTERING_VECTOR_TYPE: item representation type, RATING, VAE or MF.
CLUSTERING_VECTOR_VERSION: the version of the item representation.RNN_MODEL_VS: the version of the pretrained rnn.
TREE_VS: the version of the tree.LOAD_MODEL: bool type (T/F), indicating whether loading existing model.
MODEL_LOAD_VS: the version of the model to load from.
MODEL_SAVE_VS: the version of the model to save to.HIDDEN_UNITS: the hidden units list for each policy network, seperated by ','.
SAMPLE_EPISODES_PER_BATCH: the number of sample episodes for each user in a training batch.
SAMPLE_USERS_PER_BATCH: the number of sample users in a training batch.
EVAL_BATCH_SIZE: batch size of episodes for evaluation.LEARNING_RATE: initialized learning rate for AdamOptimizer.
L2_FACTOR: l2 factor for l2 normalization.
ENTROPY_FACTOR: to control the smooth of the possibility distribution of the policy.

DEBUG过程

  1. 运行时,如果出现缺少包而报错,则使用conda install packagename安装包。
  2. 安装完需要的包后,运行程序,发现报错FileNotFoundError: [Errno 2] No such file or directory: '../data/run_time/demo_data_env_objects'
    找到对应语句:
# if you replace the rating file without renaming, you should delete the old env object file before you run the code
env_object_path = '../data/run_time/%s_env_objects'%self.config['ENV']['RATING_FILE']####省略了很多代码行###
# dump the env object
utils.pickle_save({'r_matrix': self.r_matrix, 'user_to_rele_num': self.user_to_rele_num}, env_object_path)

找到utils中“pickle_save”函数

def pickle_save(object, file_path):f = open(file_path, 'wb')pickle.dump(object, f)
  • pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。

发现data目录下不存在run_time这个文件夹,于是手动创建一个。

  1. 创建目录了,还是有报错:Ran out of input。找到报错位置,还是和pickle相关:
def pickle_load(file_path):f = open(file_path, 'rb')return pickle.load(f)  

原因:load的文件为空,就会出现这种错误。发现上一步并未成功保存文件。
保存时报错'module' object is not callable,检查后发现pickle.dump写错了(上面是正确的)

  1. 总之就是遇到bug就一个一个改,就可以跑起来啦:
    在这里插入图片描述

代码详解

这部分讲解整个代码框架。

整体流程

  1. main函数使用configparse函数解析配置文件
  2. 创建Recommender实例,除了初始化函数只有一个run函数
  3. 调用Recommender中的run函数

run函数

首先获取配置文件参数值,之后:

  1. 判断是否需要预训练,调用tpgr.py进行预训练
        if self.config['TPGR']['PRE_TRAINING'] == 'T':log.log('start pre-training', True)pre_train = PRE_TRAIN(self.config, self.sess) # TPGR中的类for i in range(pre_training_step):pre_train.train()log.log('end pre-training', True)
  1. 判断是否需要构建树,tpgr.py进行构建
        if self.config['TPGR']['CONSTRUCT_TREE'] == 'T':log.log('start constructing tree', True)tree = Tree(self.config, self.sess)tree.construct_tree()log.log('end constructing tree', True)
  1. 训练TPGR模型
log.log('start training tpgr', True)tpgr = TPGR(self.config, self.sess)for i in range(0, max_training_step):if i % log_step == 0:tpgr.evaluate()log.log('evaluated\n', True)tpgr.train()log.log('end training tpgr')

可以看到,每一部分都用到了tpgr中的类,下面我们看看tpgr文件的构成。tpgr由三个类构成:
在这里插入图片描述

1. PRE_TRAIN()

先创建类pre_train = PRE_TRAIN(self.config, self.sess),再训练pre_train.train()
目的预训练rnn参数,获得更好的state。输出是:W(SRU(action_embedding,reward,pre_statistic),pre_statistic)+b,groundtruth是reward。
共有以下几个函数:

init()

  1. init(): 主要是从config文件中获取参数,创建batch_size个Env。
    self.env = [Env(self.config, self.user_num, self.item_num, self.r_matrix, self.user_to_rele_num) for i in range(max(self.user_num, self.batch_size))]创建多个env实例。
    然后调用make_graph()

Env():创建对象时,读取config文件内容(action维度,episode长度等);加载数据集,存储user_num(用户数), item_num, r_matrix(存储评分矩阵), user_to_rele_num(用户u给出的高分数)等信息。(其中 item_num, r_matrix 存储在env_object_path中(‘…/data/run_time/demo_data_env_objects’))
在这里插入图片描述
然后通过run_time_tools.mf_with_bias(self.config),获取item_embedding,并存到item_embedding_file_path(‘…/data/run_time/demo_data_item_embedding_dim8’)。(如果之前运行过,就直接loda item_embedding_file_path就好啦)
在这里插入图片描述

run_time_tools.mf_with_bias()主要过程:使用训练数据(80%),定义并训练user_embs和item_embs矩阵,使得二者相乘的结果接近rating真实值,将item_embeddings作为结果(和MF差不多)。
后续tpgr.py会用它查表,获取item embedding,作为rnn的一个输入。建立平衡树的时候,会用到item embedding,如果是MF-based方法,也会用到它。

Env类其他函数:

  1. get_init_datareturn self.user_num, self.item_num, self.r_matrix, self.user_to_rele_num 这四个刚好是env需要的初始化输入
  2. resetself.user_id = user_id,其他属性设为0
  3. get_relevant_item_numreturn self.user_to_rele_num[self.user_id] 返回该用户评分高于3.5的item数量
  4. get_reward :初始化reward = [0.0, False];传入item_id,根据get_rating得到item评分,进行归一化处理得到reward[0],并按照reward[0]大小统计各种变量(con_neg_count等)。最后reward[0] += self.alpha * sr # sr是高分数量减去低分数量;item全部遍历后或者达到episode_length时,reward[1] = True
  5. get_statistic :获取参数,如all_neg_count(获取reward时就更新过了),这部分和rnn输出拼接起来作为state
  6. get_rating: 从评分矩阵返回评分 self.r_matrix[self.user_id, item_id]
  7. 属性:在这里插入图片描述

make_graph()

初始化时就调用了,主要是定义了整个前向传播过程,loss函数定义。

  1. 首先建立了多个placeholder(action, rnn_state, reward, statistic),长度为rnn预训练长度32。从env中获取action_embeddings(也就是PMF分解得到的物品embedding);
  2. 调用create_sru,输入self.rnn_input_dim, self.rnn_output_dim:对W,b等参数进行初始化,运算过程函数。返回self.rnn(unit函数,定义遗忘门,重置门,记忆单元;返回隐藏层和记忆单元拼接), self.rnn_variables(5个参数)
	# 创建SRU#init_matrix表示从“服从指定正态分布的序列”中随机取出指定个数的值Wf = tf.Variable(self.init_matrix([rnn_input_dim, rnn_output_dim])) bf = tf.Variable(self.init_matrix([rnn_output_dim]))Wr = tf.Variable(self.init_matrix([rnn_input_dim, rnn_output_dim]))br = tf.Variable(self.init_matrix([rnn_output_dim]))U = tf.Variable(self.init_matrix([rnn_input_dim, rnn_output_dim]))sru_variables = [Wf, Wr, U, bf, br]def unit(x, h_c):pre_h, pre_c = tf.unstack(h_c)# forget gatef = tf.sigmoid(tf.matmul(x, Wf) + bf)# reset gater = tf.sigmoid(tf.matmul(x, Wr) + br)# memory cellc = f * pre_c + (1 - f) * tf.matmul(x, U)# hidden stateh = r * tf.nn.tanh(c) + (1 - r) * xreturn tf.stack([h, c]) # 拼接起来,输出隐藏层和记忆层  
  1. 进行查表操作,找到每一个action对应的嵌入(8维,共32个);将reward映射为one-hot形式,维度为reward_dim。构造rnn输入pre_ars:拼接action embedding + reward + pre_statistic,27维,32个;初始隐藏层,初始化为0了(2,batch_size,27)。
  2. SRU输出tmp_state = self.rnn(self.pre_ars[i], tmp_state)循环32次,结果保存在cur_rnn_states_list,32个(2,?,27) 其中2代表[h,c],27是维度。
  3. 整体训练过程了! W ( S R U ( ) , s t a t i c ) + b W(SRU(),static)+b W(SRU(),static)+b定义variable变量,W(36,64) b(64,)其中64是max_item_num,36是 rnn_output_dim+statistic_dim。定义了l2损失。
  4. 预测值self.pn_outputs = [tf.matmul(tf.concat([self.cur_rnn_states_list[i][0], self.pre_statistic[i]], axis=1), self.W) + self.b for i in range(self.pre_train_truncated_length)] rnn输出和pre_statistic拼接起来,和w相乘再加上b,共32个值
  5. 真实值:placeholder (?,64); mask也是placeholder(有的地方被设置成0了)
  6. loss函数:设置了两种loss函数 all_zero_loss(将预测结果视为0矩阵)和loss((1.0 / tf.reduce_sum(self.mask)*求和{根号下(mask *(预测值-真实值))} 开根号)。一共也是32个loss。
self.all_zero_loss = tf.pow((1.0 / tf.reduce_sum(self.mask)) * tf.reduce_sum(tf.square(self.mask * (tf.cast(tf.constant(np.zeros(shape=[self.batch_size, elf.max_item_num])), dtype=tf.float32)self.expected_pn_outputs))), 0.5) # mask掉的是看不见的
self.loss = [tf.pow((1.0 / tf.reduce_sum(self.mask)) * tf.reduce_sum(tf.square(self.mask * (self.pn_outputs[i] - self.expected_pn_outputs))), 0.5)  for i in range(self.pre_train_truncated_length)]

train()

  1. 首先构造了batch_size个用户,每个用户产生了max_item_num个action,然后一个用户一个物品地训练
    ① 首先随机选取batch_size个用户id,每个用户都运行 self.env[i].reset(user_id):记下idself.user_id = user_id把各个属性重置为零(step_count,con_neg_count,con_zero_count,all_neg_count,history_items)。
    ② 定义action_value_list:batch_size个预训练action列表[1,2,3…64];然后打乱顺序;再取出前pre_train_seq_length列
    ③ 对每一个user env类(共batch_size个),调用env中函数获取reward:首先从评分矩阵返回对该物品的评分,并进行归一化,reward[0] += self.alpha * sr
  2. 循环pre_train_seq_length次:sampled_action = action_value_list[:, i],先取出一列action的值(相当于每个env对应的item);ars存储action(最终有64个list,长度为batch_size),reward,statistic(9个固定值除以32,all_neg_count,all_pos_count, con_neg_count等)。 第j个env中用户对应的item为sampled_action[j],ars存储反馈等值。 对batch_size用户采样64个item,并存储他们的reward,statistic
  3. 运行evaluate函数
  4. 训练模型,初始化ground_truth和mask_value,都是(batch_size,64)的矩阵,表示用户对第i个采样的反馈;运行pre_rnn_state_list,初始化为0
  5. 开始预训练了,循环64次。取出ars中的actions,rewards(长度为batchsize);存入ground_truth;初始化mask。
  6. 喂数据:pre_rnn_state_list[0],ground_truth,mask_value 以及ars数据。注意action是0~i+1,也就是第i次及其前面的action(一共传入i个action),reward都传进去。然后run pre_train_op最小化损失, cur_rnn_states_list[i]rnn前向传播过程。都是一轮一轮训练的哦!pre_rnn_state_list.append(rnn_state)
  7. 训练5轮就存储rnn_variables,即rnn的参数。这也是预训练的最终目的
    在这里插入图片描述
    注意!!训练长度64,但是是分了两部分(可能是因为episode=32?),前32和后32if i < self.pre_train_truncated_length。前32次,pre_actions长度逐渐递增,rnn输入之一pre_rnn_state一直是0,输出存起来了,训练pre_train_op[i]。后32次要一个一个取出来pre_rnn_state作为输入,ground_truth和mask_value计算方法没有变,变的是pre_actions(长度一直为32了):1-32;2-33…32-63作为pre,训练pre_train_op[31](都只训练最后一次,预测值也是只训练最后一个矩阵,因为输入都给完了)。整个64步都走完,才有pre_training_steps+1。这一整个步骤又会重复pre_training_step次。 最后保存参数

所以预训练rnn的目的是,输出结果拼接上固定值后经过全连接的结果与reward尽量相似。(有点不太明白,为什么这样就可以衡量state好坏呢)似乎是通过前i个序列(item embedding,reward,固定参数)预测第i个reward
真实值:一个矩阵,行数代表batch,列数代表采样该action获取的reward。预测值也是这样的一个矩阵,不过训练32次就有32个矩阵
有两个loss,all_zero_loss是干嘛的?应该是为了做对比

  1. init_matrix():return tf.random_normal(shape, stddev=0.1)
  2. _get_initial_ars()

evaluate

这里只是评估,并没有训练

  1. 首先和train一样,创建batch_size个用户,reset;创建action_value_list [0,1,2…,63],共batch_size个。

  2. 获取batch_size中用户对action_value_list的reward、statistic,用ars存储
    在这里插入图片描述

  3. 初始化ground_truth,mask_value,运行rnn_state(全为0)

  4. 循环64次,取出ars中的数据:① 取出第一轮采样的action(一共64轮,长度为batch_size) ② 为ground_truth赋值,存储对应action的对应reward。就是一个矩阵,batchsize行,64列(对应action)。 ③ mask_value,把ground_truth对应reward位置全改成0 ④ 当循环<32时,直接喂数据(第i列的action),并run cur_rnn_states_list,rnn前向传播过程,只运行一轮,传入27位的拼接向量以及隐藏层(0初始化),输出(2,?,27)

  5. 循环次数>=32时:pre_actions = ars[0][i - self.pre_train_mask_length] 然后把pre_actions内容对应的mask值改成0,再喂数据,run。这个地方是保证,能看见的数据长度只有32?

            feed_dict = {self.pre_rnn_state: rnn_state,self.expected_pn_outputs: ground_truth,self.mask: mask_value,self.pre_actions[0]: ars[0][i], # batchsize长度self.pre_rewards[0]: ars[1][i], self.pre_statistic[0]: ars[2][i]}rnn_state = self.sess.run(self.cur_rnn_states_list[0], feed_dict=feed_dict)
  1. run 损失函数,并输出
    之后进行训练,最后再调用evaluate,训练多次后,保存rnn参数utils.pickle_save(self.sess.run(self.rnn_variables), self.rnn_file_path + 's%d' % self.pre_training_steps)
    对rnn进行预训练后,之后就不用预训练啦~
    后面我就把运行预训练的部分注释掉啦~
    在这里插入图片描述

一些问题记录

参数相关

  1. pre_train_truncated_length(32,rnn预训练长度):action 的placeholder数量,也就是训练时要输入多少个action
    在这里插入图片描述

  2. max_item_num(64):决定了W和b的参数大小,有点像最后一层神经元个数。
    在这里插入图片描述
    action_value_list是由[1,2,3…64]组成的
    在这里插入图片描述

  3. pre_train_seq_length(64,预训练时每一轮的交互长度)代码中和max_item_num是一样的,用于控制action_value_list长度;控制采样多少次,ars长度

  4. pre_train_mask_length(32,在预训练时控制要恢复的历史奖励的长度)mask的都看不见了
    在这里插入图片描述

2. Tree()

先初始化tree = Tree(self.config, self.sess) ,再调用建树函数tree.construct_tree()
将item_embedding(VAE,rating,MF)聚类(k-means-based, PCA-based),结果文件包括id_to_code, code_to_id,dataset,child_num,clustering_type。
id_to_code :[父节点位置,叶节点位置], code_to_id:叶节点在最后一层的位置对应的一开始在数据集中的位置。

init()

init():定义各种参数,如非叶节点子节点数量child_num(伪代码中是给出总item数和深度d,计算c;实际代码直接给出了c),聚类方式PCA,clustering_vector_file_path,tree_file_path。创建env对象。
根据总item数和c,计算深度:self.bc_dim = int(math.ceil(math.log(self.env.item_num, self.child_num)))

construct_tree()

主要是调用 build_mapping()函数:id_to_code, code_to_id = self.build_mapping(),并将输出id_to_code,code_to_id,dataset,child_num,clustering_type转为obj并存储utils.pickle_save(obj, self.tree_file_path)
在这里插入图片描述

build_mapping()

主要是两个映射。

  1. 初始化全为0的数组 id_to_code,[item_num, bc_dim] 这个相当于辈分列表
  2. 获取聚类需要的物品embedding(rating,VAE,MF):run_time_tools.clustering_vector_constructor(self.config, self.sess),将结果id_to_vector(user num * item num)存入clustering_vector_file_path。(如果之前保存过结果,就直接打开文件就好了)
    在这里插入图片描述
  3. 调用hierarchical_code(self, item_list, code_range, id_to_code, id_to_vector函数,首先传入:从0 ∼ \sim 物品数组成的list,(0 ∼ c h i l d _ n u m b c _ d i m \sim {child\_num}^{bc\_dim} child_numbc_dim)组成的tuple,id_to_code(会被修改), id_to_vector。经过内部的递归调用,id_to_code会变化,是一个
  4. code_to_id :code_to_id = self.get_code_to_id(id_to_code) 返回一个一维数组,总长度代表叶节点数量,值表示一开始在数据集中的位置。也就是item在叶节点编号对应的数据集中的id
    def get_code_to_id(self, id_to_code):result = -np.ones(shape=[int(int(math.pow(self.child_num, float(self.bc_dim))))], dtype=int)for i in range(len(id_to_code)):code = id_to_code[i]result[self.get_index(code)] = iprint('leaf num count: %d\nitem num count: %d'%(len(result), int(np.sum([int(item>=0) for item in result]))))return result
  1. 返回(id_to_code, code_to_id)
hierarchical_code()

先是100个物品传入,通过聚类分成十个一组,共10个列表 --> 再分别将十个列表传入,通过聚类分成一个一组,共10个列表 --> 再将一个一组的列表传入,这时就会修改id_to_code。
在这里插入图片描述

id_to_code:是一个二维数组,第一列代表父节点在整棵树的位置,第二维代表当前节点在当前子树的位置(有点类似于排辈分)。比如[5,0]就代表当前节点的爸爸是他那一辈排第五的,而当前节点在他这一辈是老大,排第一。

  1. 先判断item_list长度,如果为1,就要改变id_to_code:id_to_code[item_list[0]] = self.get_code(code_range[0]),并返回“辈分排名”。
  2. 传入item_list和id_to_vector,进行聚类pca_clustering(),返回结果item_list_assign,是10个列表(每个列表是n个一组的item list(n=总长度除以孩子数10))。如传入100个物品,就会返回10个list,每个list有10个元素;最后一次是传入10个物品,返回10个长度为1的list。
  3. 开始递归调用,重复十次(因为分成了10类)
 for i in range(self.child_num): # 10个组里,每个组都继续递归调用;直到len(item_list)=1self.hierarchical_code(item_list_assign[i], (code_range[0]+i*range_len, code_range[0]+(i+1)*range_len), id_to_code, id_to_vector)

① 先是把分好的十个组一个一个放进去调用hierarchical_code,传入的第二个参数代表传入数据的起始位置((0,10),(10,20)…(90,100))
② 长度为10的列表传入后,还是先进行聚类,这次返回的是[[83], [95], [20], [92], [65], [58], [98], [42], [94], [90]]。(返回10个list);然后继续传入hierarchical_code,这次item_list长度为1。
③ 当传入递归的列表长度为1时,要修改id_to_code了!运行id_to_code[item_list[0]] = self.get_code(code_range[0]),传入当前在原始数据集中的位置。id_to_code[item]等于返回的二元数组

    def get_code(self, id):code = np.zeros(dtype=int, shape=[self.bc_dim])for i in range(self.bc_dim):c = id % self.child_numcode[self.bc_dim-i-1] = cid = int(id / self.child_num)if id == 0:breakreturn code
pca_clustering()
  1. 输入为(0~item_num)的列表以及每个item对应的向量;首先找到每个item对应的向量,然后调用sklearn中的pca.fit(data)
  2. 然后建立投影,w是pca中的参数(代表所保留的特征个数):item_to_projection = [(item, np.dot(id_to_vector[item], w)) for item in item_list](这里像是按聚类相似度排序?),然后按照从小到大排序存入result,然后10个一组(10也是算出来的,传入的物品数除以孩子数,int(math.ceil(len(result) * 1.0 / self.child_num)) item_list_assign.append([result[j][0] for j in range(start, end)])

三种聚类方式: kmeans_clustering(),random_clustering(),pca_clustering()

小小总结一下,这里就是建立平衡层次聚类树的过程,首先根据item embedding(三种获取方式)进行聚类,然后不停递归调用,建立聚类层次树。还有两个映射操作id_to_code :[父节点位置,叶节点位置], code_to_id:item在叶节点中的编号对应在数据集中的id

3. TPGR()

先创建对象,再运行evaluate函数。tpgr = TPGR(self.config, self.sess), tpgr.evaluate(),再train
在创建变量时可以看出,分成了训练集和评估集

init()

定义了损失函数哦

  1. 首先是从配置文件中读取参数,读取前面保存的的obj文件(rnn参数,建树的结果)
  2. 建立了env对象,forward_env = Env(self.config),有很多数据集中的属性,以及使用PMF获取embedding的函数、获取reward的函数。
  3. 使用刚刚初始化env对象时的值,再次创建64000个env对象。
  4. layer_units = [self.statistic_dim + self.rnn_output_dim] + self.hidden_units + [self.child_num] 这里不知道是什么意思,(state + 隐藏层 + 孩子数 目前是[36,7])
  5. self.aval_val = self.get_aval() ,调用时首先获取node_num_before_depth_i,创建一个aval_list(child_num, node_num_before_depth_i(self.bc_dim)),然后又开始递归调用函数rec_get_aval。
    最后的aval_list如下,表示 每个非叶节点的每个子树中可用的item
  • 行数代表孩子节点数列表示非叶节点数量(policy数量)

  • 第i列表示标号为i的policy各个孩子节点可用的nodes;第一列代表节点0,也就是根节点。他一共有10个孩子节点0~9,每个孩子节点都还有10个孩子节点可用。行数=孩子数

  • 后面十列表示第二层那十个节点,他们也有10个孩子节点,但是都是叶节点了,每个子节点只有一个节点可用
    在这里插入图片描述

make_graph()

  1. 创建很多个placeholder:forward_action(None),forward_reward(None),forward_statistic(None,statistic_dim),forward_rnn_state(2, None, rnn_output_dim),cur_q(None)(Q值),cur_action(None)
  2. 创建常量:action_embeddings(value=self.forward_env.item_embedding)PMF生成的物品embedding,bc_embeddings(value=self.id_to_code)[爸爸辈分,儿子辈分]
  3. 创建RNN输入需要的值。forward_a_emb(使用forward_action查表action_embeddings),one_hot_reward(使用tf.one命令将经过转换的forward_reward映射到reward_dim维),forward_ars(拼接[forward_a_emb, one_hot_reward, forward_statistic],这就是rnn输入哦)
  4. 初始化 rnn initial_states:拼接两个0值填充的tensor:tf.stack([tf.zeros([self.train_batch_size, self.rnn_output_dim]), tf.zeros([self.train_batch_size, self.rnn_output_dim])]) shape:(2,64000,27)
  5. 加载前面预训练rnn得到的obj文件,并用参数创建create_sru,返回函数rnn和参数rnn_variables
  6. 定义rnn_state:使用rnn函数rnn(forward_ars, forward_rnn_state),给rnn两个输入(item embedding+reward+statistic)+ forward_rnn_state(placeholder)
  7. 定义user_state:拼接rnn隐藏层和固定参数[rnn_state[0], forward_statistic]。(rnn函数会返回两个值[h, c],隐藏层的和记忆细胞)
  8. 定义需要更新的参数W_list和b_list,初始化时正太分布, shape=[node_num_before_depth_i(self.bc_dim), layer_units[i], layer_units[i + 1]]。(11,36,10):11表示非叶节点数,36是statistic_dim + rnn_output_dim,10是孩子数;layer_units = [self.statistic_dim + self.rnn_output_dim] + self.hidden_units + [self.child_num]
  9. 将hidden state映射到action。定义常量code2id(value=code_to_id),树中的位置对应在数据集中的索引。
  10. Variable变量:aval_list和aval_eval_list。tf.Variable(np.tile(np.expand_dims(self.aval_val, 1), [1, self.train_batch_size, 1]), dtype=tf.float32),tile() 函数将原矩阵按照维度复制;np.expand_dims:用于扩展数组的形状; 首先expand_dims把aval_val扩展成(10,1,11),再经过tile变为(10, 64000, 11)。相当于复制了train_batch_size份和eval_batch_size份aval_val。aval_val本来是(10,11),表示 每个非叶节点的每个子树中可用的item数。 eval_probs=[]
  11. 又开始创建常量了,全部为0值:pre_shift(大小为train_batch_size),pre_mul_choice(大小为train_batch_size),action_index(大小为train_batch_size),pre_shift_eval(大小为eval_batch_size),pre_max_choice_eval(大小为eval_batch_size),action_index_eval(大小为eval_batch_size)。
  12. 赋值aval_list_t = aval_list,aval_eval_list_t = aval_eval_list;就是刚刚定义为Variable的变量。

后面开始进行采样过程,总采样次数应该是episode次(也就是需要为用户推荐多少item),每一次采样需要做d次选择,输入state,通过策略网络从1~child_num中做选择,并记录下节点索引。做了d次选择后, p t p_t pt存储每次做的选择,然后需要把路径映射到action。获取reward,更新state(h)。
前面算出的11,是非叶节点数,也是需要初始化的策略网络数

  1. 采样过程,做d次选择:循环深度次数 for i in range(self.bc_dim)2次:① 计算forward_index self.forward_index = self.node_num_before_depth_i(i) + self.child_num * self.pre_shift + tf.cast(self.pre_mul_choice, tf.int32) (第i层前面的节点索引+孩子数*pre_shift + pre_mul_choice,是一个shape为64000的tensor)② 给h(h就是RL的输入state)赋值,if i == 0一开始赋user_state(拼接rnn隐藏层和固定参数(?,36)),之后将user_state增加一个维度(tf.expand_dims(self.user_state, axis=1)):tf.expand_dims(self.user_state, axis=1) ,(?, 1, 36) ③ for k in range(len(self.W_list)):循环len(self.W_list)次(1次),W_list是前面定义的Variable变量,它的长度是len(self.layer_units) - 1,layer_units的长度又是state + 隐藏层 (为空)+ 孩子数[36,7],长度减一是1(如果有隐藏层,就要先经过一层隐藏层)。 ④ if k == (len(self.W_list) - 1)(k是循环的最后一轮)定义移动概率forward_prob(否则给h赋值), if i == 0self.forward_prob = tf.nn.softmax(tf.matmul(h, self.W_list[k][0]) + self.b_list[k][0], axis=1),就是经过一层全连接 外面包一层softmax,最终维度是(?,10),不就是输入state,返回选择10个节点的概率嘛(但是不是最终概率,后面还会处理的)。W的维度是(11,36,10):11表示非叶节点数,36是statistic_dim + rnn_output_dim,10是孩子数;self.W_list[k][0]维度是(36, 10)。b的维度是(11,10),h维度是(?,36)(后来会修改成 (?, 1, 36))

W维度(11,36,10)的含义:有11个一模一样的二维数组(36,10),36行,10列。10应该代表可选子树数量,11代表策略网络个数
下面写了一个例子,方便理解三维向量的意思。随机生成了(2,3,5):生成了两个一样的二维数组(3,5)

x = np.random.random([2,3,5])
x = array([[[0.90213642, 0.86585019, 0.63359569, 0.54320493, 0.61305485],[0.92393074, 0.54836019, 0.06053656, 0.57022926, 0.78738432],[0.24339059, 0.68440075, 0.42425335, 0.34418762, 0.42389787]],[[0.99615986, 0.16249693, 0.79286802, 0.45866597, 0.9477994 ],[0.80744563, 0.38976343, 0.56733192, 0.62285278, 0.038492  ],[0.11921885, 0.00678705, 0.67478142, 0.12596062, 0.58212996]]])

在这里插入图片描述

  1. 还是刚刚for i in range(self.bc_dim)循环里面:修改pre_shift (一开始全赋值为0)self.pre_shift = self.child_num * self.pre_shift + tf.cast(self.pre_mul_choice, tf.int32)
  2. 修改gather_index(64000, 10, 3)

evaluate

  1. 先计算eval_step_num,也就是每次采样多少;再将初始化eval_batch_size * eval_step_num个用户env( 共创建了5000个env),并传入用户id进行reset(从0~499,10个轮回)。这样每一个env都有对应的userID了

  2. 初始化ars:_get_initial_ars,调用env中获取reward的函数返回item_id,reward,statistic。① 循环5000次 ② 先随机选itemid,调用env[i]中函数获取选择item_id得到的reward,同时得到get_statistic。因此最终的result包括了5000组结果:item_id,reward,statistic。对应的是用户0~499。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/4d40047335ac48009bd2ef48e6284afb.png 400x)

  3. 初始化entropy_list,用于存储指标rmse

  4. 为每位用户采样episode:① 算出首尾:0-5000。② 零值初始化rnn_state,step_count=0;self.update_avalable_items(ars[0][0][start:end]) ③ 开始赋值,5000为一个batchsize:self.forward_action: ars[0][step_count][start:end],self.forward_reward: ars[1][step_count][start:end],self.forward_statistic: ars[2][step_count][start:end],self.forward_rnn_state: rnn_state (ars[0]代表action,也就是itemID,ars[1]代表reward,ars[2]代表固定值;这些placeholder都是在make_graph中定义的)。

  5. 开始run了:run_list = [self.forward_sampled_action_eval, self.rnn_state, self.eval_probs, self.update_aval_eval_list],其中① forward_sampled_action_eval是查表操作,目的是将action索引转成数据集中的;② rnn_state是运行rnn函数,传入forward_ars和forward_rnn_state,输出h和c的stack;③ eval_probs存储了每次选择时算出的概率;④ update_aval_eval_list是删除了选过的item,并进行赋值。
    在这里插入图片描述

  6. sampled_action, rnn_state, probs = result_list[0:3]进行赋值:数据集中对应的action(5000,),运行rnn的结果(2,5000,27),每次选择item的概率list(5000,20)(长度为20,因为需要选择2次,每次有10个选项)

  7. 计算rmse: rmse = np.power(np.mean(np.square(probs - 1.0 / self.child_num)), 0.5) 这里不太明白。append到entropy_list中。step_count+1

  8. 为ars添加[]

  9. 循环5000次,衡量action好坏了!查找sampled_action的reward,像之前一样append到ars中,如果reward[1]=True(item全部遍历后或者达到episode_length),stop_flag = True。 好奇怪 step_count应该每次都+1啊,不对!传入的user不一样,是不同的env

  10. 结束上面的循环后, 取出前500个用户的reward,并计算训练集的平均奖励和测试集的平均奖励,以及平均rmse

  11. tp_list存储每个用户推荐列表中的高分数量;rele_list存储用户历史评分高于3.5的数量

  12. 循环user_num次,要衡量rating了:self.forward_env.reset(j),这是最开始传入config文件初始化的哦,返回ratings(list 32) ratings = [self.forward_env.get_rating(ars[0][k][j]) for k in range(0, len(ars[0]))],ars[0]是(32,5000),k是0~31,代表获取给用户生成的32长度的episode的rating;tp = len(list(filter(lambda x: x>=self.boundary_rating, ratings)))返回分数大于等于3.5的个数,也相当于这个用户的高分数量。

  13. 计算precision(高分数量除以32)和recall(高分数量除以用户历史高分数量)以及f1值;计算训练集各个指标的平均值train_ave_precision,train_ave_recall,train_ave_f1;测试集的指标:train_ave_f1,test_ave_precision,test_ave_recall,test_ave_f1

  14. 保存training_steps的模型,W_list,b_list (‘…/data/result/result_log/20210725160757_0.0_demo_data’)。保存结果: self.storage.append([train_ave_reward, train_ave_precision, train_ave_recall, train_ave_f1, test_ave_reward, test_ave_precision, test_ave_recall, test_ave_f1, ave_rmse])

  15. 输出结果:这里的0是训练次数,与training_steps相关,它只在train中更改;
    在这里插入图片描述
    然后就返回recommender中,开始调用train了!

train

  1. 每一个batch选的用户数=4000, 训练的时候每个用户采样的episode=16
  2. 循环4000次,从1~400(boundry_user_id)中随机选userID;嵌套循环16次,self.env[i*self.sample_episodes_per_batch+j].reset(user_id),传入userID进行重置。(self.env()在初始化的时候,初始化了64000个)这个地方就是先选出4000个用户id,再重置该用户的前16个env,刚刚好64000.
  3. 初始化ars:64000个 item_id,reward,statistic;给用户随机选item,然后获取reward,train_batch_size=64000
  4. self.update_avalable_items(ars[0][0]),传入64000个itemid,目的是删除树中选过的item(爸爸和儿子都要删除?),① sampled_codes = self.id_to_code[sampled_items],从爸爸找儿子(64000,2) ② aval_val_tmp = np.tile(np.expand_dims(self.aval_val, axis=1), [1, self.train_batch_size, 1])(10,64000,11); ③ 开始循环,修改aval_val_tmp,并且run assign_aval_list,也就是赋值操作。
        for i in range(len(sampled_codes)):code = sampled_codes[i]index = 0for c in code: # 爸爸和儿子辈分c = int(c)aval_val_tmp[c][i][index] -= 1index = self.child_num * index + 1 + c #
  1. 用0初始化rnn_state;step_count = 0;stop_flag = False;action_list = []
  2. 根据策略采样action,先赋值
feed_dict = {self.forward_action: ars[0][step_count],#长度为64000self.forward_statistic:ars[2][step_count],self.forward_reward: ars[1][step_count],self.forward_rnn_state: rnn_state}
  1. run_list =[self.forward_sampled_action, self.rnn_state, self.update_aval_list],其中
    ① forward_sampled_action是查表操作,目的是将action索引转成数据集中的id;
    ② rnn_state是运行rnn函数,传入forward_ars和forward_rnn_state,输出[h,c];
    ③ update_aval_eval_list是删除了选过的item。赋值给sampled_action, rnn_state
  2. action_list.append(sampled_action)step_count += 1(最后加到了31),ars加[]
  3. 又开始循环64000次,reward = self.env[j].get_reward(sampled_action[j]),这里是返回选取的item的reward,共32个,每个长度为64000。也就是要衡量采样结果好不好了。
  4. 接下来标准化Q值, 就是在算累积收益
        for i in reversed(range(len(qs))): # 折扣值c_reward = self.discount_factor * c_reward + qs[i]qs[i] = c_reward
  1. qs_mean_list 取平均;又对qs进行标准化。这里传入的matrix是(31,16)
    def standardization(self, q_matrix):q_matrix -= np.mean(q_matrix)std = np.std(q_matrix)if std == 0.0:return q_matrixq_matrix /= stdreturn q_matrix
  1. 初始化rnn(2,64000,27)
  2. 强化学习更新!
        for i in range(step_count):feed_dict = {self.forward_action: ars[0][i],self.forward_reward: ars[1][i],self.forward_statistic: ars[2][i],self.forward_rnn_state: rnn_state,self.cur_action: ars[0][i + 1],self.cur_q: qs[i]} # Q值 _, rnn_state = self.sess.run([self.train_op, self.rnn_state], feed_dict=feed_dict)

总结:传入采样生成的action,reward等记录(是为用户随机选item,得到reward;大小为batchsize),返回推荐结果(长度为64000),然后衡量好坏。

补充知识

这部分是遇到的一些没见过的或者是需要巩固的知识点:

函数

  1. Batch Size定义:一次训练所选取的样本数。Batch Size的大小影响模型的优化程度和速度。同时其直接影响到GPU内存的使用情况,假如你GPU内存不大,该数值最好设置小一点
  2. configparser:用来读取配置文件的包配置文件的格式如下:中括号“[ ]”内包含的为section。section 下面为类似于key-value 的配置内容。
  3. 读文件时,r,rb,w,wb等的含义:

‘r’:只读。该文件必须已存在。

‘r+’:可读可写。该文件必须已存在,写为追加在文件内容末尾。

‘rb’:表示以二进制方式读取文件。该文件必须已存在。

‘w’:只写。打开即默认创建一个新文件,如果文件已存在,则覆盖写(即文件内原始数据会被新写入的数据清空覆盖)。

‘w+’:写读。打开创建新文件并写入数据,如果文件已存在,则覆盖写。

‘wb’:表示以二进制写方式打开,只能写文件, 如果文件不存在,创建该文件;如果文件已存在,则覆盖写。

‘a’:追加写。若打开的是已有文件则直接对已有文件操作,若打开文件不存在则创建新文件,只能执行写(追加在后面),不能读。

‘a+’:追加读写。打开文件方式与写入方式和’a’一样,但是可以读。需注意的是你若刚用‘a+’打开一个文件,一般不能直接读取,因为此时光标已经是文件末尾,除非你把光标移动到初始位置或任意非末尾的位置。(可使用seek() 方法解决这个问题,详细请见下文Model 8 示例)

  1. 创建一个不含有重复值的列表,不要再用list.append()方法了,因为要提前判断是否在列表内。可以采用 set add() 方法,用于给集合添加元素,如果添加的元素在集合中已存在,则不执行任何操作。
  2. coo_matrix构建稀疏矩阵coo_matrix((data, (i, j)), [shape=(M, N)]),data[:] 表示原始矩阵中的数据;i[:] 是行的指示符号,如row的第0个元素是0,就代表data中第一个数据在第0行;j[:] 是列的指示符号;如col的第0个元素是0,就代表data中第一个数据在第0列;总结一下,根据i和j的位置确定data的每个元素位于矩阵哪个位置。但是主要用来创建矩阵,因为 coo_matrix 无法对矩阵的元素进行增删改等操作
  3. 之前将评分数据转换为矩阵都是先用numpy初始化空矩阵,再一个一个读取数据集填充。现在可以使用coo_matrix方法啦!self.r_matrix = coo_matrix((rating[:, 2], (rating[:, 0].astype(int), rating[:, 1].astype(int)))).todok()
  4. todok():采用字典来记录矩阵中不为 0 的元素。字典的 key 存的是记录元素的位置信息的元组, value 是记录元素的具体值。 这篇文章总结的很好
  5. tqdm 是一个快速,可扩展的Python进度条,可以在 Python 长循环中添加一个进度提示信息,用户只需要封装任意的迭代器 tqdm(iterator)。
  6. filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。filter(function, iterable)该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
  7. tf.nn.embedding_lookup函数:选取一个张量里面索引对应的元素。tf.nn.embedding_lookup(params, ids):params可以是张量也可以是数组等,id就是对应的索引。
  8. tf.stack用于拼接矩阵。tf.concat是沿某一维度拼接shape相同的张量,拼接生成的新张量维度不会增加。而tf.stack是在新的维度上拼接,拼接后维度加1。
  9. math.ceil() “向上取整”, 即小数部分直接舍去,并向正数部分进1。
  10. math.log(10,2)以2为底10的对数。
  11. tf.nn.embedding_lookup(params,id):根据id,寻找embeddings中的第id行。
  12. tf.cast()函数的作用是执行 tensorflow 中张量数据类型转换: cast(x, dtype, name=None),第一个参数 x: 待转换的数据(张量)第二个参数 dtype: 目标数据类型;第三个参数 name: 可选参数,定义操作的名称.
  13. ·tf.random_normal()·函数用于从“服从指定正态分布的序列”输出指定维度的值。
  14. tf.unstack()是一个矩阵分解的函数
  15. PCA方法:PCA(n_components=None, copy=True, whiten=False)
    (1)n_components: int, float, None 或 string,PCA算法中所要保留的主成分个数,也即保留下来的特征个数,如果 n_components = 1,将把原始数据降到一维;
    (2)copy:True 或False,默认为True,即是否需要将原始训练数据复制;
    (3)whiten:True 或False,默认为False,即是否白化,使得每个特征具有相同的方差。
    (4)方法 :fit(X,y=None)表示用数据X来训练PCA模型。函数返回值:调用fit方法的对象本身。比如pca.fit(X),表示用X对pca这个对象进行训练。n_components_返回所保留的特征个数
    拓展:fit()可以说是scikit-learn中通用的方法,每个需要训练的算法都会有fit()方法,它其实就是算法中的“训练”这一步骤。 主成分分析(PCA)的概念很简单——减少数据集的变量数量,同时保留尽可能多的信息。
    (5)fit_transform(X):用X来训练PCA模型,同时返回降维后的数据。
  16. 创建常量:创建一个常量tensor,按照给出value来赋值,可以用shape来指定其形状。value可以是一个数,也可以是一个list。
tf.constant(value,dtype=None,shape=None,name='Const',verify_shape=False
)
  1. filter(function, iterable),过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换
  2. gc.collect()可以清内存
  3. ConfigParser模块在python中用来读取.conf配置文件,每个节可以有多个参数(键=值)
  4. tf.equal(x, y, name=None)逐个元素进行判断,如果相等就是True,不相等,就是False。

GRU

在这里插入图片描述
GRU是LSTM网络的一种效果很好的变体,它较LSTM网络的结构更加简单,而且效果也很好,因此也是当前非常流形的一种网络。GRU既然是LSTM的变体,因此也是可以解决RNN网络中的长依赖问题。
图中的 z t z_t zt r t r_t rt分别表示更新门和重置门。更新门用于控制前一时刻的状态信息被带入到当前状态中的程度,更新门的值越大说明前一时刻的状态信息带入越多。重置门控制前一状态有多少信息被写入到当前的候选集 h t ~ \tilde{h_t} ht~,重置门越小,前一状态的信息被写入的越少。

  • 截断反向传播(truncated backpropagation)
    在配置文件中看见“rnn truncated length”这个描述,不是很懂“截断”的意思,所以百度了一下。大概是指训练时的长度,应该和batch size差不多吧。
  • 假设我们训练含有1000000个数据的序列,如果全部训练的话,整个的序列都feed进RNN中,容易造成梯度消失或爆炸的问题,所以解决的方法就是truncated backpropagation,我们将序列截断来进行训练(num_steps)。

报错

1.dictionary changed size during iteration:找了一个多小时,发现是传入字典的时候就出问题了!key值当中,int和str没有统一!!

这篇关于TPGR代码详解 Large-Scale Interactive Recommendation with Tree-Structured Policy Gradient的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/685765

相关文章

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

SQL 中多表查询的常见连接方式详解

《SQL中多表查询的常见连接方式详解》本文介绍SQL中多表查询的常见连接方式,包括内连接(INNERJOIN)、左连接(LEFTJOIN)、右连接(RIGHTJOIN)、全外连接(FULLOUTER... 目录一、连接类型图表(ASCII 形式)二、前置代码(创建示例表)三、连接方式代码示例1. 内连接(I

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(