作者归档:隋辨

2021,新年随笔

QQ图片20210101003228

图片源于隋辨的手机

「理想主义者抑郁年」,我这样评价2020年。我们如同正在见证如此预言——「2019年是过去十年最糟糕的一年,却可能是未来十年最好的一年」。人们幡然醒悟:过去十年那种平稳有序的进步,才是人类史上的罕见现象。

「因为疫情」开始变为多数事物变化的理由,上半年我花了很多时间独处,趁着这次机会,我开始自己买菜,自己做饭,自己洗碗,做任何想要做的事。很多时候,我们没空跟自己相处,没空观察自己,以至于那么多行为和思维模式都在不经意间触发。下半年我去了北京,开始读研生活。我感慨:北京能人众多,为什么我没有早一点来到这里?

这一年我关注了很多主题:

  • 减脂、生酮饮食、健康
  • 两性道德发展
  • 工业社会以及现代性发展
  • 计算机体系知识
  • 多标签学习

这一年,我的世界模型慢慢变化。人类学家常会研究现存原始部落,以此判断一个人类现象是不是受文化因素影响而产生。我开始用猴子视角来看世界:如果没有人类社会,我们只是一只猴子,会更加幸福还是更加悲惨?

酋长说:「我们只是活着,守着本分,沿袭着先人的活法。我们从来不问为什么,从来不记录。在内心深处,我们知道自己的民族很古老,但我们好像没有办法测算时间的流逝。我父亲和祖父讲故事的时候都不会说出时间。这并不是说明他们忘了,或者搞不清楚。在他们看来,过去就是过去了。」

没有人能找到那个部落。这片大陆深处隐藏着多少个这样存在于空间与时间之外的部落我们不得而知。当你想自作聪明地将这一切赋予意义,「以个人的孤独对抗这个世界有组织的欺骗」,结果他们说,孤独是什么,我既没有听说过自由,也没有听说过孤独,那是你们那个世界的事情。

也许他们生活地更加自由呢?

我选择变得更为包容,开始理解、接纳而非看不见他人的观念。因为什么都不是理所当然的,男权社会不是,工业主义也不是,就连当下所流行的一切文化都不是。我不想只发展专业能力,不想只是成为一个专业的程序员或工程师。除了赚钱,人们应该看到自己所处的浪潮,而不只是甘心成为一个「工具人」。我开始去参加很多聚会,甚至比我本科期间参加的所有聚会还多,认识不同的人,去发现他人的生活观念,悄然自足。

现代性特征之一,就是目的与手段分离,娱乐和劳作分离。我们用衣物和语言区分了野蛮与文明,用避孕套分离了性与生育,用城市化解构了家族关系,用互联网拉开了人的距离。我们正处在一座庞然机器里,资本和计算机让每个人,都不可避免在这系统之中越陷越深。我的好奇心越发旺盛,所以,在新年我仍要继续探索和思考。

「当下腰悬木剑,身披敝袍,一人一雕,悄然西去,自此足迹所至,踏遍了中原江南之地。」2021 年我想如金庸小说一般,要思考,要继续学习,察看社会舞台剧本,要爱,要体验,也希望大家也能够继续学习、成长!

相关文章

  • 新年随笔(2020)
  • 新年随笔(2018)
  • 新年寄望

第 2 课 字符串和数据结构

第 2 课 字符串和数据结构

不会的东西可以在群里提问、或搜索查看标准库文档入门教程。( Ctrl + F )

课程纲要和笔记

  1. 数据结构
    • 复习变量
      • 变量是内存的名字,赋值过程就是名字的重新绑定。例子:a = 1 a = a + 1
    • 复习数据类型
      • int, float: + - * \ \\ %
      • str: + == replace() split() format() strip() notice:*
      • bool: or and not
      • str(), float(), int():特定的类型有特定的运算,所以需要进行类型转换。
  • 数据结构是什么以及意义是?
    • 上述数据类型是最基本的、不可分割的数据结构。例子:学生信息表:学号、身份证、姓名。
    • 由于内存的性质,我们需要把现实世界的数据用线性的结构表示。数据结构,要考虑其结构操作
  • list 列表
    • 数据:本质上是表格,线性排列,可以是任何类型,
    • 操作:
    • append() [] * n +
    • pop() pop(i)
    • array[i] = n
    • fruits.index(x)
    • 更多特性
  • tuple 不可变列表
  1. 字符串和文件读写
  • f = open(file_name, 'w', encoding="utf-8") f.close()
    • 如何存储文件?文件读写
    • 需要 close 因为操作系统的规定。
    • f.read() , f.readline()
    • f.write()
  1. 简要复习上一课的内容,解答作业
    1. 编写一个程序:能顺序地输入两个数,回车后,程序将在在屏幕上打印两个数分别加减乘除的结果,程序代码应该包括你的注释
    2. 编写一个程序:能先输入字符串 s,然后输入一个整数 n,程序将把 n 个字符串拼接打印出来。

课后作业

  1. 编程作业,程序满足:自定义一个list里面包含一些整数,自行对其进行增删改查操作,转换成str类型,按照utf-8编码输出为txt文件。

  2. 复制下列代码,并利用它在屏幕上打印出Google likes Python

L = [
    ['Apple', 'Google', 'Microsoft'],
    ['Java', 'Python', 'Ruby', 'PHP'],
    ['likes', 'dislikes', 'own']
]

附录

  • 注意:上课的时候突然卡壳了!readline() 在遇到行尾的时候返回空白字符串,转成bool型是False,正确的按行读取并且加载到数组中应该是这样:

    array = []
    with open("test.txt", "r", encoding="utf-8") as f:
      text = f.readline()
      array.append(text)
      while text:
          text = f.readline()
          array.append(text)
    print(array)
  • 上课代码

    if __name__ == '__main__':
      print("hello")
      a = 1
      a = a + 1
      a = "test"
    
      print(a.capitalize())
      print(a == b)
      user = input()
      age = input()
      welcome = "hello, {0}, {1}".format(user, age)
      print(welcome)
      b = "hello"
      n = int(input())
      #   integer
      #   int(n) * str(b)   --> str
      print(n * b)
      flag = True
      # not
      # not True = False
      # or
      # (bool) and (bool)
      print(flag and False)
    
      #   原子
      #   学生表 :学号(str or int)、身份证(str)、姓名(str)。
      #   学生表 : x1 学号身份证姓名
      #   学生表 : x2 学号身份证姓名
      #   图 1-256
      array = [2, "s", 1.16 ]
      a = [1, 9, 3]
      b = [2, 2,2]
      matrix = [
          a ,
          b ,
          [  2,3,5  ]
      ]
    
      array.append("hello")
      array = ["s"] * 9
    
      new_list = array + matrix
      print(new_list)
      new_list.pop(3)
    
      new_list[0] = "T"
    
      new_list.clear()
      print(new_list)
    
      print(new_list.index("s"))
      # (list) .index( )
    
      t1 = tuple([1, 2, 4, 5, 8])
    
      table = tuple(["monday", "tuesday"])
      print(table[0])
    
      string = "s12255122sw"
      #   []
      print(string[1:-2])
      print(string[-2:])
      #   gbk
      #   utf-8
      #   01010 -->  a
      #   01010 -->  b
      #
      f = open("test.txt", "w", encoding="utf-8")
      # r: read w:write
      f.write(string * 10)
      f.close()
    
      # array = []
      #
      with open("test.txt", "r", encoding="utf-8") as f:
          text = "2"
          while text:
              text = f.readline()
              array.append(text)
    
      print(array)
    
      a = float(input())
      b = float(input())
      print(a * b, a + b , a / b)
    
      s = input()
      n = int(input())
      print(n * s)

第 1 课 变量和类型,用内存的视角看数据

第 1 课 变量和类型,用内存的视角看数据

注意,任何情况下,新手应该从官方的教程和API教程开始:Python 文档,而不是网上良莠不齐的资料。同时,不要成为语言律师 ( language lawyer )。

  • 运行 Python 脚本的两种运行方法是什么?
  • 什么是内存?
  • 什么是变量?
  • 什么是类型?
  • 变量和字面量有什么关系?
  • 当我们 print 的时候,计算机发生了什么?

课程纲要

  • 程序如何运行(了解)
    • 抽象
    • 内存
    • 运行 Python 脚本发生了什么(用命令行、用IDE)
  • 变量与类型(掌握,请在课后阅读官方教程加深理解)
    • 变量的意义及其命名
    • 变量内存中的表现(以后再深入,先大致了解)
    • 布尔类型及逻辑运算
    • 数字及算术运算
    • 整数
    • 字符串及其操作
    • 拼接、*、strip()、split() 等操作
    • 转义符号
    • 字符串编码
    • 「烫烫烫烫」是什么(utf-8, gbk 等编码问题,深入可以了解 字符编码笔记:ASCII,Unicode 和 UTF-8
    • str(), float(), int()
    • input(), print(), len()
  • 加餐(学有余力的同学可看)
    • 数字编码
    • 整数:补码和原码
    • 小数:浮点数
    • 计算机体系结构
    • 冯诺依曼结构
    • 当我们 print 的时候,计算机发生了什么?
    • 真正的互联网,已经死了

课后作业

  1. 编写一个程序:能顺序地输入两个数,回车后,程序将在在屏幕上打印两个数分别加减乘除的结果,程序代码应该包括你的注释
  2. 编写一个程序:能先输入字符串 s,然后输入一个整数 n,程序将把 n 个字符串拼接打印出来。

授课笔记

  • 程序如何运行(了解)
    • 计算机最重要的概念——抽象是什么?出租车的例子
    • 内存
    • CPU就像我们的大脑,内存就像我们的草稿本
    • 是一组线性的空间
    • 运行 Python 脚本发生了什么?
    • IDE: pycharm, idle 命令行调用:cmd, bash, ssh
    • 学有余力的同学可以学后面的计算机体系结构
  • 变量与类型(掌握,请在课后阅读官方教程加深理解)
    • 变量的意义及其命名
    • 为啥需要变量?因为这是一个变动的世界
    • 如何命名?用英文单词,函数和变量 = 小写 + 下划线, 类名使用驼峰命名法(PyCharm 会提示你)
    • 类型是什么?
    • 变量内存中的表现(以后再深入,先大致了解)
    • 变量和字面量有什么关系?
    • 布尔类型及逻辑运算
    • why: 因为我们需要根据不同情况去运行不同的解决方法,比如登录账号的时候。
    • and, or, not
    • 数字及算术运算
    • why: int 和 float 处理生活的数据
    • 加减乘除,求幂,取模,// 向下取整
    • 字符串及其操作
    • 跨行输入、拼接、*、strip()、split() 等操作
    • 转义符号
    • 字符串编码
    • 「烫烫烫烫」是什么(utf-8, gbk 等编码问题,深入可以了解 字符编码笔记:ASCII,Unicode 和 UTF-8
    • str(), float(), int()
    • input(), print(), len()
  • 加餐(学有余力的同学可看)

    • 数字编码

    • 整数:补码和原码

    • 小数:浮点数

    • 计算机体系结构

    • 冯诺依曼结构:我们需要先大致了解计算机的体系结构:

    • gda
    • 1182611-20190530163801121-1848365582
    • 当我们 print 的时候,计算机发生了什么?

    • 真正的互联网,已经死了

2020年,我的年度消费总结

物质

年度日用品——床上用品
这一年,我对物质没有太多的需求,甚至连数码产品测评都不怎么看。现在我只关注实用性,经常就是用拼多多。没有什么比床单、枕头更能直接地改变人生的幸福感。每天的八个小时你都在享用它,那为什么不提高这方面的品质呢?尤其是它让你心情更好。与其买个没必要换的新平板,为啥不买能更加显著改变生活质量的东西呢?

年度食物——鸡胸肉

由于疫情,我在家用各种不同的手法做鸡胸肉,炒、煎、炸、焖。独居的日子里,做菜就像写代码一样有趣。它富含蛋白质、比猪牛肉便宜、低脂、比鱼肉容易处理,有什么比鸡胸肉好呢。

文化

年度反英雄电视剧——《黑袍纠察队》

我已经对传统的超人英雄电影感到厌烦,尤其是 DC 粗制滥造的电视剧,套路严重,do loop: obstacle——talk —— grow——battle ——win 。非常喜欢这部剧,与其说它解构了超人英雄,不如说是把超人英雄放下神坛。超人英雄首先是人,那么就会和现实所有人一样贪嗔痴,我认为这是自从《黑镜》第一季第一集以来,少数让人眼前焕然一新的剧情了。

年度爽剧——《后翼弃兵》和《终生》第一季
都是爽剧套路,不过爽的有层次。前者的镜头语言超越了绝大多数国产电视剧和电影。后者可以认为是《肖申克的救赎》的加长版——令人感动,且我看了一个通宵。

年度垃圾剧——《美丽新世界》
实在没办法,想吐槽一下。简直浪费时间,改编自《同名小说》,这部剧的改编失败在于,小说想要表达的内核和剧已经完全不同了。我不知道编剧是不是非常享受剧中的上等人生活。

年度科幻短篇——《黄金原野》
我愿称它为中国的《星际穿越》。大刘这次站在了人文与科幻的路口,故事有点中国人才有的美感。

年度科幻小说——《献给阿尔吉侬的花束》
悲剧色彩,人生的际遇,从弱智到天才,再跌回谷底,是怎样的感觉?小说的目的中,有一个是用别人的眼睛去体验生活,这本书完美诠释了这一点。

年度小说——《幻影书》保罗·奥斯特

故事很讨巧。我跟主人公产生了共鸣,因此感受人生的幻灭、虚无。连一个人在世上留下的最后一个作品都消失了,那么他的存在的意义呢?讲实话我讨厌那些令人沮丧的小说,尤其是本书作者的弟子「村上春树」所写的那些关于孤独、丧失意义感的小说。但这本书是例外。它竟让人有某种探索生活的决心,去爱的勇气。(相反的,保罗·奥斯特《布鲁克林的荒唐事》很甜很好)

年度音乐人——王雁盟
人老了,越来越不喜欢新歌,因此常常听徐佳莹的改编歌曲,室友因此常常吐槽我。今年发现了王雁盟,算是更新了一下,除了 The Piano Guys 之外,这算是我最喜欢的纯音乐作者。他的《玛奇朵漂浮》是多么令人沉醉呢,就像做了一个关于夏天的梦,我飞入了棉花云中,飘摇啊飘摇。

年度社科书籍——《被劫持的私生活》
以前互联网公知肉唐僧开山之作。目前他已经退隐。这本书阐述的是两性关系的发展史,如果你对两性道德、女权主义感兴趣,那你应该看看了。

年度心理学书籍——《非暴力沟通》
虽然看上去这本书是指沟通技巧,但我感到很神奇的是,作者似乎将心理学的理念穿插进了这些技巧中。所谓沟通,并不只是跟他人,还可以是跟自己沟通。可惜的是我们大多数人都不会跟别人好好说话,更不用说和自我沟通。

第 0 课 Hello, world!

第 0 课 Hello, world!

1.前言

“软件定义世界”,不仅因为「移动支付让超市不再找零,使口香糖销量大减」这样的小事,还说明未来程序将不可避免地深入人们的生活,而且会重构现实世界。

以前的生活需要一个门卫守停车场,而现在可以做到只需要监视器和二维码,意味着什么?

  1. 停车场不再可能发生门卫让自己的熟人进来,或中饱私囊的事情。程序把流程写死,可以定义社会规则。
  2. 停车场不再需要一个人守在门口,只需电力和少数维护成本。程序将替换机械的工作,节省人力成本,提高生产力。
  3. 用算法对调度各区域的停车场车位,人们将不会浪费时间在找停车场上。程序可以对资源进行抽象,用数学让效率最大化

用最短路算法实现地图导航、用协同过滤推荐调节商品供需关系…… 从这点来看,只要人们还需要提高生产力,软件就会持续地改变世界。

编程对不是计算机的专业有什么用?

8岁时李笑来向他妈妈要了10元钱,去少年宫学习计算机编程。后来在新东方当老师,编写《TOEFL核心词汇21天突破》一书时,他用自己编写的批处理程序,短时间内完成了海量词汇处理工作。在现在看来,写这样一本按词频收集单词的书,对一个过了四级的学过编程的人其实毫无难处,难的是在一个「少年宫」还流行的年代,还学到了编程,并且在自己的领域内发现了计算机的能力。

计算机是人大脑的延伸,学编程是学会用计算机的视角看世界。更重要的信息的搜集能力,和快速学习的能力,前者是靠的是领域经验,后者更多的是靠天赋和后天的积累。所以本节课重点不教编程,只教搜集。

2. 方法

接下来你需要学习如何使用搜索引擎,不要怀疑,大多数人并不懂搜索。网上已经有很多资料了:

上面的技巧,不需要全部都会,知道一点点就可以了,大多数搜索引擎都可以使用。

推荐两个搜索引擎:
http://bing.com/
https://duckduckgo.com/

下面是锻炼你搜索能力的题目,安装两个软件,在上面写出代码,并且正确运行。

网上其实有无数的教程和博客去说这两件事情,那么问题其实是,如何快速找到并了解一个新事物、一个未知的逻辑?

请完成下一节的练习

3. 安装编程环境

根据自己的操作系统,安装下面两个软件。目前不需要知道下面两个软件是什么,先安装好再说。

3. Hello, world! 开始你的第一个程序

配置好解释器,下载安装好后在 pycharm 新建一个项目,新建一个 py 文件,内容是:

print("Hello, world!")

复制、粘贴、运行就可以了。

日程

虽然简单,做完后,请把运行成功的全屏截图,发到我的邮箱kevtyle@hotmail.com,过期算一次不交作业哦。

做完后,请把运行成功的全屏截图,发到下列邮箱:

python[\at]benearyou.com (为防止垃圾邮件骚扰,把 [\at] 改成 @ 为真实邮箱)

这次作业改为课后作业,课程简介更新了,请仔细查看。

直播 12月27日 20:00(暂定),直播当天发布下一次课的内容。

课后作业 12月30日 23:59

作业 12月28日 23:59 截止。

隔一天一次直播,即下一次课为12月29日 20:00

SVM 拓展:RankSVM

基本思想

GBRank 和 RankSVM 都是用来解决 LTR 问题的 pairwise 方法。利用 \Phi(q,d) 得出 query 和文档的特征向量,x1、x2分别是d1、d2的特征,取(x1, x2)为正样本,(x2, x1)为负样本,代入 SVM 模型中。

介绍

RankSVM 是在2002年提出的,之前工作关于LTR的工作貌似只有 Pointwise 相关的,比如 PRanking,这样的排序学习算法Work需要含有档位标注的训练样本,一般有以下几种获取方式:

  1. 需要人工/专家标注
  2. 诱导用户对展现的搜索结果进行反馈

这样就会存在会成本高、可持续性低、受标准者影响大等缺点。

而 RankSVM 只需要根据搜索引擎的点击日志构建 Pair 对即可,相对于先前的工作在算法的实用性上有了非常大的改善。

训练样本设计

对于 q 为一次 query,和文档集合 D = \{d_1, \dots,d_m\} ,算法按照相关性给出给出一个文档排序 r^* 。算法一般找不到最优的 r^* ,而是通过其排序顺序 r_{f(q)} 和最优值得差距来评估搜索排序函数 f

一般搜索引擎都会记录用户搜索后展现的结果以及用户的点击情况,这种日志成本较低,并且改造系统也较为方便,而RankSVM的训练样本就是从这种点击日志中进行提取。

由于用户点击的doc概率和排序的位置影响很大,虽然一般都是偏爱相关性较大的doc,但是如果这种doc排在很后面的话其实用户也几乎不会点,所以 RankSVM 就只考虑top级别的点击日志,一般为top 10

另外在构建训练数据时同一 query 下认为用户被点击 doc 相关性要高于没有被点击的 doc ,但是由于用户是从上往下浏览网页的,所以排在前面的 doc 被点击的概率会大于后面的 doc ,因此 RankSVM 使用的最终策略为:

Pair 构建策略:给定一组排序情况 (doc_1,doc_2,doc_3,\cdots) ,以及 C 记录了用户点击 doc 的情况,则有
doc_i{\overset{r^*}{<}} doc_j 对于所有pair1 \leq i ,同时 i \in Cj \notin C

r^* 表示应有的优化排序,也就是被点击文档的相关性要大于排在该文档前面并且未被点击的文档,看上去很绕口,看个栗子:

假如某个用户搜索某个 query 得到首页10个doc,按顺序使用 doc_i 进行表示,如果该用户点击了 1,3,7 三个文档,则认为有:

doc_3 {\overset{r^*}{<}} doc_2 \\
doc_7 {\overset{r^*}{<}} doc_2 \\
doc_7 {\overset{r^*}{<}} doc_4 \\
doc_7 {\overset{r^*}{<}} doc_5 \\
doc_7 {\overset{r^*}{<}} doc_6

表示该 querydoc_3 的相关性要高于 doc_2 ,理应排在前面,而 doc7 的相关性也应该要高于 doc_{2,4,5,6} ,但是可以发现未见 doc_1 的相关性鉴定,这是由于 doc_1 已经是排在了第一位,本身位置点击概率就就是最高的,所以无法判断与其他文档相关性的高低,同理, doc_{8,9,10} 也未纳入相关性的排序中。

训练样本可以通过这样方式进行构建,但是遗憾的是并没有一种机器学习算法能直接使用这种数据进行训练学习-_-

搜索函数的理论框架

在上面的栗子中,我们希望用新算法完成 doc_{1,3,7} 排在top 3,那这样的文档的真实相关性高的将会排到前面,RankSVM 采用Kendall’s Tau (肯德尔等级相关系数)来统计实际排序与算法排序的度量,先看下面两个变量:

  1. P 表示排序序列中保持一致性的 Pair 对数量,也就是真实相关性高的排在第的前面。
  2. Q 表示排序序列中保持不一致的 Pair 对数量(逆序数),也就是由于算法的误差导致真实相关性低的排在了高的前面
  3. 同时 P+Q=\binom{m}{2}m 表示序列中文档的数量,因为长度为 m 的序列可能组成的 Pair 对为 m 的2组合

\tau 的计算方式为:

\tau(r_a,r_b)=\frac{P-Q}{P+Q}=1-\frac{2Q}{\binom{m}{2}}

r_a 为真实排序, r_b 为算法排序

比如实际文档中的顺序为:

d_1{\overset{r^*}{<}}d_2{\overset{r^*}{<}}d_3{\overset{r^*}{<}}d_4{\overset{r^*}{<}}d_5

但是算法的排序顺序为:

d_3{\overset{\hat{r}}{<}}d_2{\overset{\hat{r}}{<}}d_1{\overset{\hat{r}}{<}}d_4{\overset{\hat{r}}{<}}d_5

因此可以发现算法排序中有3对 Pair 不一致了( \{d_2,d_3\}\{d_1,d_2\}\{d_1,d_3\} ),所以 Q=3P=4+3+2+1-3 = 7 ,最终的 \tau(r,\hat{r})=\frac{7-3}{10}=4
\tau 的值越大,表示排序效果越接近真实,比如上面的 \tau(r,r)=\frac{10-0}{10}=1

还证明了由逆序数 Q 的一个式子为平均准确率指标的下界(具体证明看原文附录):

 AvgPrec(\hat{r}) \geq \frac{1}{R} \left[Q+\binom{R+1}{2}\right]^{-1} \left(\sum_{i=1}^{R} \sqrt{i}\right)^2

R 为排序出现的文档中与 query 相关文档数量(这个是[Mean Average Precision](http://kubicode.me/2016/02/15/Machine Learning/Learning-To-Rank-Base-Knowledge/#MAP)中的标注,与 Pair 对的样本稍微有点差别)
从这个角度也可以看到 \tau 来衡量排序效果好坏的合理性.

现在可以定义学习排序函数得问题,对于有 m 个文档的 D 集合上,有固定且未知分布的概率 P{r(q, r^*)} ,目标是学习到一个 f(q) ,其使得肯德尔系数 \tau 最大化。

\tau_{P}(\mathrm{f})=\int \tau\left(\mathrm{r}_{\mathrm{f}(\mathrm{q})}, \mathrm{r}^{*}\right) d \operatorname{Pr}\left(\mathrm{q}, \mathrm{r}^{*}\right)

以下算法采用经验风险最小化,假设现在有 nq_i 作为训练样本,他们各自的目标排序为 r_i^* ,也就是:

(q_1,r_1^*),(q_2,r_2^*),(q_3,r_3^*),\cdots,(q_n,r_n^*)

给定一个大小为 n 且独立同分布的训练样本 S ,其中算法排序为 \hat{r}_i ,则排序算法的优化目标是将下列式子

\tau_{S}(\mathrm{f})=\frac{1}{n} \sum_{i=1}^{n} \tau\left(\mathrm{r}_{\mathrm{f}\left(\mathrm{q}_{i}\right)}, \mathrm{r}_{i}^{*}\right)

最大化。

RankSVM 排序

假设能找到一个排序算法 f 能使得上面的 \tau_s 得到最大化,对于一个指定的查询 q ,每个文档 d_i 使用特征向量映射方法 \Phi(q,d_i)\vec{w} 是通过学习调整的权重向量。 \Phi(q,d) 是映射到描述查询 q 和文档 d 之间的匹配的特征的映射,例如网页 title, h1, h2 标题共有的单词数,或者 d 的页面排名。

如果是直接使用线性排序方法:

\left(\mathrm{d}_{i}, \mathrm{~d}_{j}\right) \in \mathrm{f}_{\vec{w}}(\mathrm{q}) \Longleftrightarrow \vec{w} \Phi\left(\mathrm{q}, \mathrm{d}_{i}\right)>\vec{w} \Phi\left(\mathrm{q}, \mathrm{d}_{j}\right)

\vec{w} 表示权重向量,线性排序下,排序分数为权重 x 特征向量

上图表示一个二维的权重特征图,在排序的顺序的就是特征在权重上的投影位置顺序,比如单从 W_1 维度进行观察可以看到的排序顺序为{1,2,3,4},而如果按 W_2 维度则是{2,3,1,4}

在这里,间隔被定义为当数据点投射到排序向量 w 时两个数据点之间的最近距离,如图 d_1,d_2

image-20201130191345976

为了最大化 \tau_s ,可以最小化 Q 来代替,也就是说对于线性的排序, Q=0 就是表示下面的等式全成立(也就是最大化)

\forall (d_i,d_j) \in r_1^* : \vec{w} \Phi(q,d_i)>\vec{w} \Phi(q,d_j) \\
… \\
\forall (d_i,d_j) \in r_n^* : \vec{w} \Phi(q,d_i)>\vec{w} \Phi(q,d_j)

不幸的是,优化这个是一个NP问题
但我们可以让他们绝大多数都成立,SVM 在由于软间距最大时可以看到熟悉的身影,可以通过引入(非负)松弛变量 \xi_{i,j,k} 并按如下方式最小化上限 \sum\xi_{i,j,k} 来使用SVM技术来近似解。:

\begin{aligned}
&\min[\frac{1}{2} \vec{w} \cdot \vec{w} + C \sum \xi_{i,j,k}]
\\ \\
\text{s.t. }&\forall (d_i,d_j) \in r_1^* : \vec{w} \Phi(q,d_i) \geq \vec{w} \Phi(q,d_j) + 1 - \xi_{i,j,1} \\
&\cdots \\
&\forall (d_i,d_j) \in r_n^* : \vec{w} \Phi(q,d_i) \geq \vec{w} \Phi(q,d_j) +1 - \xi_{i,j,n} \\
&\forall_i \forall_j \forall_k:\xi_{i,j,k} \geq 0
\end{aligned}

\xi 为松弛项, C 表示平衡项

因此优化该问题时可以将约束转为:

\vec{w} \Phi(q,d_i)-\vec{w} \Phi(q,d_j) \geq 1 - \xi_{i,j,1}

并且由于是线性排序,可以进一步精简为:

\vec{w} \left(\Phi(q,d_i)-\Phi(q,d_j)\right) \geq 1 - \xi_{i,j,1}

Pair 对中只可能 d_i 是否排在 d_j 前面是一个二值结果,所以我们可以将 d_i 排在 d_j 前面的 Pair 为正标签,否则为负标签:

y=\left\{
\begin{aligned}
+1 & \quad if \quad \vec{w} \Phi(q,d_i)>\vec{w} \Phi(q,d_j) \\
-1 & \quad otherwise\\
\end{aligned}
\right.

则最终可以将约束可以写成:

y_i \cdot \vec{w} \left(\Phi(q,d_i)-\Phi(q,d_j)\right) \geq 1 - \xi_{i,j,1}

其中传统的偏置项在 RankSVM 是不需要的,以为正好 Pair 相减时就消掉了

所以这样一个问题等价于 SVM 应用于成对向量差上的分类问题。完全将上面构建的样本转为一个分类问题,使用 SVM 的对偶形式进行求解,并且还可以使用核函数进行非线性的分类。 \vec{w} 对应了排序函数,可以对query-doc pair进行打分和排序。

所以 RankSVM 可以看做是一个回归问题,模型应用的时候,使用当前样本的预估值作为排序的依据。

但是样本的构造就发生了变化,每一条样本表示的是一个序关系。所以,损失函数变为(样本 x_i 排名好于 x_j

\begin{matrix} minimize &&\ \ \ \ \frac{1}{2}|w|^2+C\sum_{ij} \xi_{ij}\\s.t. && (wx_i+b) -(wx_j+b) \ge 1-\xi_{ij}\\ &&\xi_{ij}\ge 0 \end{matrix}

上式为了方便理解,没有进行化简,上式也可以依葫芦画瓢引入核函数。

RankSVM 可以看做是寻找一个超平面,使得点对 (x_i , x_j) 对平面的几何间隔差最大。

rsv(q,d_i)= \vec{w}^* \Phi(q,d_i) = \sum_l^n \alpha_{k,l}^*y_i(\Phi(q_k,d_l) \cdot \Phi(q,d_j))

testbinary_classifiy

上面是表示两个不同的 query 所表示的特征空间,不同的形状表示文档与对应 query 的相关性档位,三角形 x_1 表示相关性档位高,圆圈 x_2 表示相关性档位一般,叉叉 x_3 表示相关性档位差。
将这些样本转为 Pair 对之后可以有:

img

可以发现 x_1-x_3x_1-x_2 为正样本,而 x_2-x_1x_3-x_1 为负样本,因此可以形成对应的训练数据进行训练,其实这样形成的样本是对称的,因此在实际使用中一般只保留一侧即可。

总结

RankSVM 很好的解决原始训练样本构建难的问题,根据点击日志构建样本,既考虑了doc之间的顺序,又保证了可持续性,并且其 Pair 对的训练正好可以使用SVM进行求最优化,而SVM分类器已经是非常成熟并且广泛使用的一种机器学习算法。
因此 RankSVM 虽然在2002年就提出,但是至今在工业界还是广泛使用,但是现在的主要难点是样本 Pair 对的构建,针对不同的场景也不同的策略,不一定只是根据点击顺序,实际使用中还要考虑新样本数据。

参考

  1. Joachims T. Optimizing search engines using clickthrough data[C], Proceedings of the eighth ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2002:133-142.

  2. Learning to Rank for Information Retrieval and Natural Language Processing, Hang Li

  3. 原始博文来源于 [RankSvm-基于点击数据的搜索排序算法](http://kubicode.me/2016/03/30/Machine Learning/RankSvm-Optimizing-Search-Engines-using-Clickthrough-Data/),但他转述错了论文的某些细节部分,感觉像机翻,所以修正写好重发。

  4. LTR中RankSVM算法的基本思路是什么?

  5. RankSVM原理