搜索与排名

爬虫 #

书中的爬虫代码非常的老了,并且网站也无法访问了,大家可以忽略

建立索引 #

image

  • urllist: 索引过的 URL
  • wordlist: 单词列表
  • wordlocation: 单词在文档中的位置
  • linktable: 不同链接之间的关系
  • linkwords: 单词和链接之间的关系

因为网站也无不工作了,这里采用网上已经生成的 DB 直接下载即可。

关于其他的基础查询部分,这里都不涉及。

准备好环境我们可以体验下

>>> e = searchengine.searcher("searchindex.db")
>>> e.getmatchrows('function')
select w0.urlid,w0.location from wordlocation w0 where w0.wordid=1111
([(1, 3226), (186, 340), (186, 546), (58, 2488), (123, 430), (123, 866), (123, 1268), (123, 1294), (123, 1345), (123, 1385), (123, 1821), (123, 1847), (123, 1897), (123, 1929), (82, 1673), (327, 2431), (327, 2465), (327, 3571), (327, 3586), (327, 3606), (139, 460), (139, 611), (139, 1250), (139, 1328), (171, 1284), (171, 3363), (171, 4774), (96, 1141), (96, 1412), (372, 828), (372, 872), (372, 923), (372, 946), (372, 973), (372, 990), (372, 1000), (372, 1013), (372, 1032), (372, 1058), (372, 1069), (372, 1135), (372, 1159), (372, 1188), (372, 1211), (372, 1278), (255, 417), (255, 706), (156, 914), (208, 584), (226, 352), (226, 388), (226, 424), (226, 433), (226,

返回的2个值是 urlid wordlocation,多个值就是 urlid wordlocation wordlocation

Content-Based Ranking 基于内容的排名 #

根据几个模式来进行排名

  1. 单词频次: 查询文档中如果频次出现的越多,相关度
  2. 文档位置: 文档的主体比较靠近开始的地方
  3. 单词距离: 多个单词,靠近的应该就更有相关度
  def getscoredlist(self,rows,wordids):
    totalscores=dict([(row[0],0) for row in rows])

    # 后面来填充
    weights=[]
    for (weight,scores) in weights:
      for url in totalscores:
        totalscores[url]+=weight*scores[url]

    return totalscores

  def geturlname(self,id):
    return self.con.execute(
    "select url from urllist where rowid=%d" % id).fetchone()[0]

  def query(self,q):
    # 查询所有的 row 和 word id
    rows,wordids=self.getmatchrows(q)
    # 给其打分
    scores=self.getscoredlist(rows,wordids)
    rankedscores=[(score,url) for (url,score) in scores.items()]
    rankedscores.sort()
    rankedscores.reverse()
    # 返回排序的结果
    for (score,urlid) in rankedscores[0:10]:
      print '%f\t%s' % (score,self.geturlname(urlid))
    return wordids,[r[1] for r in rankedscores[0:10]]

归一化函数 #

将那些可能越大越好和越小越好的分数进行归一化,返回 0 - 1,0最不匹配,1最为匹配

  def normalizescores(self,scores,smallIsBetter=0):
    vsmall=0.00001 # Avoid division by zero errors
    if smallIsBetter:
      minscore=min(scores.values())
      return dict([(u,float(minscore)/max(vsmall,l)) for (u,l) in scores.items()])
    else:
      maxscore=max(scores.values())
      if maxscore==0: maxscore=vsmall
      return dict([(u,float(c)/maxscore) for (u,c) in scores.items()])

单词频度 #

就是使用单词出现的频次来记录得分。

def frequencyscore(self,rows):
    counts=dict([(row[0],0) for row in rows])
    for row in rows: counts[row[0]]+=1
    return self.normalizescores(counts)

文档位置 #

def locationscore(self,rows):
    # 给每一个 url 分配了一个 100000 的预留长度
    locations=dict([(row[0],1000000) for row in rows])
    for row in rows:
      # 计算 row 查询出来的所有的位置的 SUM,越大就越远
      loc=sum(row[1:])
      # 如果这次算出来的位置比之前的要小,就更新,取最好成绩
      if loc<locations[row[0]]: locations[row[0]]=loc
    
    return self.normalizescores(locations,smallIsBetter=1)

单词距离 #

def distancescore(self,rows):
    # 如果只有 1 个单词,就不存在距离了
    if len(rows[0])<=2: return dict([(row[0],1.0) for row in rows])

    # 初始话,给每个 URL 分配了一个 100000 的预留长度
    mindistance=dict([(row[0],1000000) for row in rows])

    for row in rows:
      # 这里计算了每个单词之间的距离,取最小的,作为最终的成绩
      dist=sum([abs(row[i]-row[i-1]) for i in range(2,len(row))])
      if dist<mindistance[row[0]]: mindistance[row[0]]=dist
    return self.normalizescores(mindistance,smallIsBetter=1)

一般认为谁被链接的多,说明这个网站的质量越高。

计数器 #

我们直接去系统中查询出现的频次就可以,但是缺点就是相关度会变得很低。因为比如那种综合性的网页会指向所有的页面。因此有个 googlepagerank 算法

PageRank #

image

比如这里要计算的话,公式如下

PR(A) = 0.15 + 0.85 * ( PR(B)/links(B) + PR(C)/links(C) + PR(D)/links(D) )      
      = 0.15 + 0.85 * ( 0.5/4 + 0.7/5 + 0.2/1 )      
      = 0.15 + 0.85 * ( 0.125 + 0.14 + 0.2)      
      = 0.15 + 0.85 * 0.465      
      = 0.54525

但是我们计算 A 的时候需要 B 已经包含了,那么我们需要初始化,方法如下

  def calculatepagerank(self,iterations=20):
    #  清单当前的 pagerank 标
    self.con.execute('drop table if exists pagerank')
    self.con.execute('create table pagerank(urlid primary key,score)')
    
    #  初始化每个都 1.0
    for (urlid,) in self.con.execute('select rowid from urllist'):
      self.con.execute('insert into pagerank(urlid,score) values (%d,1.0)' % urlid)
    self.dbcommit()
    
    for i in range(iterations):
      print "Iteration %d" % (i)
      for (urlid,) in self.con.execute('select rowid from urllist'):
        pr=0.15
        
        # 循环遍历指向当前网页的所有页面
        for (linker,) in self.con.execute(
        'select distinct fromid from link where toid=%d' % urlid):
          # 得到链接源的 pagerank 值
          linkingpr=self.con.execute(
          'select score from pagerank where urlid=%d' % linker).fetchone()[0]

          # 计算新 pagerank 然后更新
          linkingcount=self.con.execute(
          'select count(*) from link where fromid=%d' % linker).fetchone()[0]
          pr+=0.85*(linkingpr/linkingcount)
        self.con.execute(
        'update pagerank set score=%f where urlid=%d' % (pr,urlid))
      self.dbcommit()

使用这个算法就很简单了

  def pagerankscore(self,rows):
    # 查询所有的 pagerank
    pageranks=dict([(row[0],self.con.execute('select score from pagerank where urlid=%d' % row[0]).fetchone()[0]) for row in rows])
    maxrank=max(pageranks.values())
    # 用最大作为基数,算个百分比就好
    normalizedscores=dict([(u,float(l)/maxrank) for (u,l) in pageranks.items()])
    return normalizedscores

使用链接文本 #

就是如果 A 链接到 B,那么 A 在链接 B 的时候,往往会在超链接里面编写一些说明,这些说明非常的有价值

  def linktextscore(self,rows,wordids):
    linkscores=dict([(row[0],0) for row in rows])
    for wordid in wordids:
      cur=self.con.execute('select link.fromid,link.toid from linkwords,link where wordid=%d and linkwords.linkid=link.rowid' % wordid)
      # 如果包含这个关键字的链接中包含了 toid,那说明这个链接价值很高,把 from 的 pr 加上去
      for (fromid,toid) in cur:
        if toid in linkscores:
          pr=self.con.execute('select score from pagerank where urlid=%d' % fromid).fetchone()[0]
          linkscores[toid]+=pr
    maxscore=max(linkscores.values())
    normalizedscores=dict([(u,float(l)/maxscore) for (u,l) in linkscores.items()])
    return normalizedscores

Learning from Clicks 从点击中学习 #

image

使用神经网络来完成这个事情,从左边的 输入层 到最后的 输出层 中间都是 隐藏层

设置数据库 #

  1. HiddenNode: 代表隐藏层节点

create table hiddennode(create_key)

  1. Wordhidden: 单词层到隐藏层

create table wordhidden(fromid,toid,strength)

  1. HiddenURL: 隐藏层到输出层

create table hiddenurl(fromid,toid,strength)

Feeding Forward 前馈法 #

image

是用一个函数,用节点输入得出响应强度,X 轴代表了节点的输入,输入为 0 的时候快速爬高,2 就几乎不动了,作为一种 S 型函数,上图就是一个 tanh 函数

构建网络 #

def setupnetwork(self, wordids, urlids):
    # 单词列表
    self.wordids = wordids
    # 查询到所有的 hidden id
    self.hiddenids = self.getallhiddenids(wordids, urlids)
    self.urlids = urlids

    # 输出值
    self.ai = [1.0] * len(self.wordids)
    self.ah = [1.0] * len(self.hiddenids)
    self.ao = [1.0] * len(self.urlids)

    # 根据链接的强度,构建权重矩阵
    ## word -> hidden
    self.wi = [[self.getstrength(wordid, hiddenid, 0)
                for hiddenid in self.hiddenids]
                for wordid in self.wordids]
    ## hidden -> url
    self.wo = [[self.getstrength(hiddenid, urlid, 1)
                for urlid in self.urlids]
                for hiddenid in self.hiddenids]

前馈算法 #

def feedforward(self):
    # 将输入的 word 的 下标置为 1
    for i in range(len(self.wordids)):
        self.ai[i] = 1.0

    # 隐藏层激活
    for j in range(len(self.hiddenids)):
        sum = 0.0
        # 单词层到隐藏层的权重累加
        for i in range(len(self.wordids)):
            sum = sum + self.ai[i] * self.wi[i][j]
        # tanh 一下,转为 0 - 1
        self.ah[j] = tanh(sum)

    # 输出层激活
    for k in range(len(self.urlids)):
        sum = 0.0
        # 隐藏到输出层权重累加
        for j in range(len(self.hiddenids)):
            sum = sum + self.ah[j] * self.wo[j][k]
        # tanh 一下,转为 0 - 1
        self.ao[k] = tanh(sum)

    return self.ao[:]

这样整个流程就跑通了,不过整个网络并没有训练,现在得到的结果都是一样的

if __name__ == '__main__':
    mynet = searchnet("nn.db")
    wWorld, wRiver, wBank = 101, 102, 103
    uWorldBank, uRiver, uEarth = 201, 202, 203
    print mynet.getresult([wWorld, wBank], [uWorldBank, uRiver, uEarth])
    [0.07601250837541616, 0.07601250837541616, 0.07601250837541616]

Training with Backpropagation 反向传播算法训练 #

为什么叫反向传播算法,因为这个算法是从输出层到隐层的过程,也就是说,从最后一个神经元到第一个神经元的过程。这样的话,就可以用后向传播算法来训练了。

对于输出层调整顺序如下

  1. 计算节点当前输出结果和期望之间的差距
  2. 利用 dtanh 函数确定节点总输入需要如何变化
  3. 改变买个外部回指链接的强度值,按照学习比例调整

对于隐层调整顺序如下

  1. 将每个输出链接的强度值乘以其目标节点所需的该变量,累加求和
  2. 利用 dtanh 函数确定节点总输入需要如何变化
  3. 改变每个输入的 强度值,按照学习比例调整
def backPropagate(self, targets, N=0.5):
    # 计算输出层的误差
    output_deltas = [0.0] * len(self.urlids)
    for k in range(len(self.urlids)):
        # 偏差量
        error = targets[k] - self.ao[k]
        # 如何变化
        output_deltas[k] = dtanh(self.ao[k]) * error

    # 计算隐藏层的误差
    # 这里就体现了反向传播,输出层的变化要向上游所有的隐藏层进行变化
    hidden_deltas = [0.0] * len(self.hiddenids)
    for j in range(len(self.hiddenids)):
        error = 0.0
        # 遍历输出层的每个链接的误差值和 w->h 的权重比例计算,求和
        for k in range(len(self.urlids)):
            error = error + output_deltas[k] * self.wo[j][k]
        hidden_deltas[j] = dtanh(self.ah[j]) * error

    # 更换输出层
    for j in range(len(self.hiddenids)):
        for k in range(len(self.urlids)):
            change = output_deltas[k] * self.ah[j]
            self.wo[j][k] = self.wo[j][k] + N * change

    # 更新隐藏层
    for i in range(len(self.wordids)):
        for j in range(len(self.hiddenids)):
            change = hidden_deltas[j] * self.ai[i]
            self.wi[i][j] = self.wi[i][j] + N * change


# 训练查询
def trainquery(self, wordids, urlids, selectedurl):
    # 生成一个隐藏层
    self.generatehiddennode(wordids, urlids)

    # 初始化神经网络
    self.setupnetwork(wordids, urlids)
    self.feedforward()
    targets = [0.0] * len(urlids)
    targets[urlids.index(selectedurl)] = 1.0
    # 训练网络
    error = self.backPropagate(targets)
    # 更新到数据库里面
    self.updatedatabase()

训练 #

勘误参考 #

comments powered by Disqus