LTR排序之pair-wise-ranknet算法TensorFlow实现

13,774次阅读
13 条评论

$P_{i,j}=\frac{e^{o_{i,j}}}{1+e^{o_{i,j}}}$

$\begin{matrix} P_{i,j}&=&\frac{e^{o_{i,j}}}{1+e^{o_{i,j}}}\\ &=&\frac{e^{o_i-o_j}}{1+e^{o_i-o_j}} \\ &=& \frac{e^{o_i- o_k + o_k + o_j}}{1+e^{o_i-o_k + o_k -o_j}}\\&=& \frac{e^{o_{i,k} + o_{k,j}}}{1+e^{o_{i,k} + o_{k,j}}} \\&=& \frac{P_{i,k}\ P_{k,j}}{1+2P_{i,k}\ P_{k,j}\ -P_{i,k}\ -P_{k,j}}\end{matrix}$

$o_{i,j} = \ln \frac{P_{i,j}}{1-P{i,j}}$

$\begin{matrix}C(o_{ij})&=&-\hat{P}_{i,j} \ln P_{ij} – (1-\hat{P}_{ij})\ln (1-P_{ij})\\&=&-\hat{P}_{ij} o_{ij}+\ln (1+e^{o_{ij}})\end{matrix}$

$\hat{P}_{i,j}=\frac{1}{2}(1+S_{ij})$

TensorFlow的代码保存在mac上面，明天抽个时间贴到这里来。。。。，先去洗澡睡觉觉了

# -*- coding: utf-8 -*-
# @Time    : 2018/3/27 上午10:55
# @Author  : Tomcj
# @File    : tensor_ranknet.py
# @Software: PyCharm
import  tensorflow as tf
import numpy as np

BATCH_SIZE=100
y_train=[]
X_train=[]
Query=[]
array_train_x1=[]
array_train_x0=[]

feature_num = 46
h1_num = 10
def extractFeatures(split):
'''
获取特征
'''
features = []
for i in range(2, 48):
features.append(float(split[i].split(':')[1]))
return features

def extractQueryData(split):
'''
获取以下数据quryid documentid等
Format:

'''
queryFeatures = [split[1].split(':')[1]]
queryFeatures.append(split[50])
queryFeatures.append(split[53])
queryFeatures.append(split[56])

return queryFeatures
def get_microsoft_data():
'''
获取基础样本特征数据
:return:
'''
with open('/Users/leiyang/RankNet/Data/train.txt','r') as fp:
for data in fp:
split = data.split()
y_train.append(int(split[0]))
X_train.append(extractFeatures(split))
Query.append(extractQueryData(split))

def get_pair_feature(y_train,Query):
'''
获取组合样本特征
:return:
'''
pairs = []
tmp_x0=[]
tmp_x1=[]
for i in range(0, len(Query)):
for j in range(i + 1, len(Query)):
# Only look at queries with the same id
if (Query[i][0] != Query[j][0]):
break
# Document pairs found with different rating
if (Query[i][0] == Query[j][0] and y_train[i] != y_train[j]):
# Sort by saving the largest index in position 0
if (y_train[i] > y_train[j]):
pairs.append([i, j])
tmp_x0.append(X_train[i])
tmp_x1.append(X_train[j])
else:
pairs.append([j, i])
tmp_x0.append(X_train[j])
tmp_x1.append(X_train[i])

array_train_x0 = np.array(tmp_x0)
array_train_x1=np.array(tmp_x1)
print('Found %d document pairs' % (len(pairs)))
return pairs,len(pairs),array_train_x0,array_train_x1

with tf.name_scope("input"):
x1 = tf.placeholder(tf.float32,[None, feature_num],name="x1")
x2 = tf.placeholder(tf.float32,[None, feature_num],name="x2")

#添加隐层节点
with tf.name_scope("layer1"):
with tf.name_scope("w1"):
w1 = tf.Variable(tf.random_normal([feature_num, h1_num]), name="w1")
with  tf.name_scope("b1"):
b1 = tf.Variable(tf.random_normal([h1_num]), name="b1")

#此处没有添加激活函数
with tf.name_scope("h1_o1"):
h1_o1 = tf.matmul(x1,w1) + b1
h1_o1=tf.nn.relu(h1_o1)

with tf.name_scope("h2_o1"):
h1_o2 = tf.matmul(x2, w1) + b1
h1_o2 = tf.nn.relu(h1_o2)

#添加输出节点
with tf.name_scope("output"):
with tf.name_scope("w2"):
w2 = tf.Variable(tf.random_normal([h1_num, 1]), name="w2")

with tf.name_scope("b2"):
b2 = tf.Variable(tf.random_normal([1]))

h2_o1 = tf.matmul(h1_o1, w2) + b2
h2_o2 = tf.matmul(h1_o2, w2) + b2
h2_o1=tf.sigmoid(h2_o1)
h2_o2=tf.sigmoid(h2_o2)

#根据输出节点计算概率值
with tf.name_scope("loss"):
# o12 = o1 - o2
h_o12 = h2_o1 - h2_o2
pred = 1/(1 + tf.exp(-h_o12))
#此处的label_P就是真实的概率，因为前面组pair数据已经人为将相关的样本放在
#前面，所以Sij均为1，所以计算的结果就是1
lable_p = 1

cross_entropy = -lable_p * tf.log(pred) -(1-lable_p) * tf.log(1-pred)

reduce_sum = tf.reduce_sum(cross_entropy)
loss = tf.reduce_mean(reduce_sum)

with tf.name_scope("train_op"):

with tf.Session() as sess :
# step 1 解析microsoft数据集
get_microsoft_data()
#step 2 获取 pair组合
pairs,datasize,array_train_x0,array_train_x1=get_pair_feature(y_train,Query)
init = tf.global_variables_initializer()
sess.run(init)
for epoch in range(0,10000):
start=(epoch*BATCH_SIZE)%datasize
end=min(start+BATCH_SIZE,datasize)
sess.run(train_op, feed_dict={x1: array_train_x0[start:end,:], x2: array_train_x1[start:end,:]})
if epoch % 1000== 0 :
l_v = sess.run(loss, feed_dict={x1:array_train_x0, x2:array_train_x1})

result_0=sess.run(h2_o1,feed_dict={x1:array_train_x0, x2:array_train_x1})
result_1=sess.run(h2_o2,feed_dict={x1:array_train_x0, x2:array_train_x1})
#使用所有的样本计算模型预测的准确率
print  np.sum(result_0>result_1)*1.0/datasize
# print  sess.run(cross_entropy,feed_dict={x1:array_train_x0, x2:array_train_x1})
# print "------ epoch[%d] loss_v[%f] ------ "%(epoch, l_v)

[fanctdl filename=’microsoft数据集’ filesize=’2G’ href=’http://research.microsoft.com/en-us/projects/mslr/’ filedown=’微软官方下载’][/fanctdl]

xiaoben_319 评论达人 LV.1
2018-04-28 15:29:53 回复

Macintosh  Chrome  中国广东省广州市联通
2018-04-28 15:37:20 回复

@xiaoben_319 文章中使用的是微软的数据，你可以点击文件下载链接会自动跳转到微软数据链接，格式类似libsvm格式

Macintosh  Chrome  中国广东省广州市联通
cryyrc 评论达人 LV.1
2018-05-09 20:07:03 回复

1. 代码94行注释说明没有激活函数，但是代码中有加relu、sigmoid？另外我看原理里似乎也没提到要加激活函数？
2. 我照您代码运行的结果里，loss居高不下；我把激活函数去掉后，loss则始终是nan，您觉得可能是什么原因呢？

Macintosh  Safari  中国香港
cryyrc 评论达人 LV.1
2018-05-10 15:35:57 回复

@cryyrc nan的问题弄好了，现在loss降得很慢，您有啥好的经验吗？

Macintosh  Safari  中国浙江省杭州市联通
2018-05-11 20:39:09 回复

@cryyrc 那么你的loss是否是正常水平？你的迭代次数设置多少？

iPhone  Chrome  中国广东省广州市联通
cryyrc 评论达人 LV.1
2018-05-12 19:12:59 回复

accuracy您是用啥的，ndcg吗？我的之前的accuracy（if pred == label_p）只有0.5左右，这是正常水平吗？
谢谢

Macintosh  Safari  中国浙江省杭州市电信
2018-05-14 08:46:51 回复

@cryyrc 0.5还是比较低的，准确度是使用了相对排序准确率，比如A与B为一个组合，如果A的顺序的确排在B前面，则命中一个有效结果，除以总的组合个数，计算得到准确率；
回答你之前的一个问题，代码94行原来是没哟relu函数的，后面是我自己添加的

Macintosh  Chrome  中国广东省广州市联通
cryyrc 评论达人 LV.1
2018-05-14 11:36:43 回复

还有个问题是：inference的时候应该是以h2_o1和h2_o2作为reference score的预测值吧？但是经过sigmoid之后，h2_o1、h2_o2都在(0,1)上，o1、o2是在[0,4]上的，这个是怎么对上的一直想不通。

Macintosh  Safari  中国浙江省杭州市联通
2018-05-14 12:12:22 回复

@cryyrc h2_o1、h2_o2是用来计算i和j商品的得分，也就是用于后续索引i和索引j结果的相对顺序概率Pij，这里用了sigmoid使其处于[0,1]有点误导的含义，我晚上回家重新整理这篇文章

Macintosh  Chrome  中国广东省广州市联通
cryyrc 评论达人 LV.1
2018-05-14 15:58:05 回复

我试了试下面这俩条路，结果差很多，您觉得原因是啥呢：

1. 按您的code（使label_p始终为1）来，有3点区别是：
output层去掉了sigmoid；
另外layer1那层relu之前加了个batch_normalization（不加的时候我的loss会nan，遂加之）；
cross_entropy改用了tf.nn.sigmoid_cross_entropy_with_logits（也是为了解决nan）。
这样的结果是一个epoch之后loss就降到0左右了（accuracy=0.99）。
—— epoch[0] loss_v[313.779449] ——
0.6056278521548547
—— epoch[1000] loss_v[0.000299] ——
0.9999951988976669

2. 然后我把label_p改成了0或1的形式，其他参数和第一种相同，这次acc始终0.5+，试着把batch_size加到10000，跑了100000个epoch后acc也才到0.6。
— epcoh: 0 loss_v: 3025.997314453125 acc: 0.5300270590833341 —
— epcoh: 1000 loss_v: 0.9345578551292419 acc: 0.5659493897177698 —
— epcoh: 2000 loss_v: 0.7046830654144287 acc: 0.5705210409417381 —

— epcoh: 98000 loss_v: 0.6801395416259766 acc: 0.5912141276199131 —
— epcoh: 99000 loss_v: 0.674396276473999 acc: 0.5931556957184217 —

按说这俩应该是一个意思，为什么结果差这么多，百思不得其解。

Macintosh  Safari  中国浙江省杭州市电信
2018-05-14 18:51:51 回复

@cryyrc 这个网站的评论模板不太，层数越多越不好看，你可以贴一个github的gist文件？单独在评论一下贴出gist地址或者可以通过service@deeplearn.me联系我

Macintosh  Chrome  中国广东省广州市联通
cryyrc 评论达人 LV.1
2018-05-15 00:12:57 回复