集体智慧编程第四章[搜索引擎与排名]总结

爬虫程序

我们打开一个url,返回一个html文件,它的格式类似下面的内容:

"-//W2C//DTD XHTML 1.0 Transitioln//EN""http://www.w2.org/TR/xhtml/DTD/xhtml1-transtitional.dtd">
    "http://www.w3.org/1999/xhtml">
        
        
    

其中和tag中,还有内嵌的很多tag,我们需要一个个分析这些嵌套的tag,并提取其中的文字。这个就是网页爬虫。

    1 import urllib2
    2 from bs4 import BeautifulSoup
    3 c = urllib2.urlopen('http://www.51testing.com/html/39/n-860239.html')
    4 cr = c.read()
    5 soup = BeautifulSoup(cr,'html.parser')
    6 c = soup.contents ##得到所有tag,方便以后分析

其中第六行,tag的 .contents 属性可以将tag的子节点以列表的方式输出。BeautifulSoup对象本身一定会包含子节点,就是html文件内的所有html标签是BeautifulSoup对象本身的子节点。
例如:

    c = soup.contents[0] ##返回html文件内的第一个tag
    print (type[c])
    print (c)
    ##
    ##html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"

记住,换行符也是一个子节点。例如:

    c = soup.contents[1] ##返回的是第一个tag后面的换行符
    print type(c)
    print (c,'utf-8')
    ##
    ##(u'\n', 'utf-8')

接下来才是下一个真正的tag:

    c = soup.contents[2] ##返回的是节点
    print type(c)
    print (c)
    ##
    ##输出

以及它的所有子孙节点内容。
访问head节点:

    c = soup.contents
    print type(c[2].contents[1])##注意换行符也是子节点
    print (c[2].contents[1])
    ##
    ##输出

以及它的所有子孙节点内容。

建立索引

我们通过爬虫程序得到网站的信息之后,包括每一层的url信息和页面出现的单词,还包括链接上出现的单词等信息,这个时候我们就需要将这些信息存入数据库,并为它们建立索引。
本书中建立的索引如下
集体智慧编程第四章[搜索引擎与排名]总结_第1张图片
表wordlist保存了爬取到的单词的索引,表urllist保存了每个url的索引。表wordlocation保存了每个url对应页面上出现的wordid以及他们的location(出现在什么位置,比如 url1对应的页面,word1在文档的第30 59个字符的位置出现则对应 (url1,word1,30)(url1,word1,59)).另外两张表后面再谈。

查询

当我们需要搜索某些单词对应的网站时候,比如输入‘world global’则会先将搜索短语分割为单词,再在数据库中查找所有包含这两个单词的url,将结果返回给用户。这就是最最原始的搜索引擎。下面我们来提升一下这个引擎的效果。

基于内容的排名

如何在返回的大量网页中,找出真正和查询匹配的页面。这需要我们找到一种能够针对给定查询条件为网页进行评价的方法,并且在返回结果中将评价最高的排列在前面。

单词频度

最简单的思想,搜索的单词在网页中出现频率较高,则也有可能是我们要的页面。遗憾的是现实往往没这么简单。

文档位置

文档的主题有可能会出现在靠近文档的开始处,统计单词在文档中出现的位置信息,位置越往前的就越有可能是我们想要的结果。同样这也图样图森破。

单词距离

如果查询条件中有多个单词,则他们在文档中出现的位置应该很靠近。注意这里不是靠近,而是靠前。同样然并卵的一条规则。


以上规则,早期的搜索引擎的确就是这么干的。所以我们也需要用这几条规则来练练手,涉及有数据库,数据归一化以及程序设计的知识。书上的例子还是很不错的。只是我选择了cnn的网站作为实验对象,例子中的网站打不开。最重要的是体会到数据库还是很重要的,要好好学。
书中对单词距离判断那里过于简单了,直接在查询单词组中用相邻两个最近单词的距离来评价一个url,这很不科学,但作为例子而已,这点没必要太深究。

利用外部回指链接

以上是基于网页内容的评价度。现在我们提高一点逼格。通过考察外界就该网页所提供的信息–尤其是哪个页面链向了改网页,以及他们对该网页的评价,来进一步改善搜索结果。

简单计数

处理外部回指链接最为简单的做法,就是在每个网页上统计链接的数目,并将该链接总数所为针对网页的度量。回到刚才数据库关系图:
集体智慧编程第四章[搜索引擎与排名]总结_第2张图片

表link中保存了爬取到的资源中url的指向关系。比如url1的页面中有url2的链接,则(fromid:url1,toid:url2).
通过这个关系,我们可以在表中查询所有toid=指定url的记录,并计数。这个计数值就是这个url对应网页的评价度,再计算所有的url,并归一化,排个序就行。

PageRank算法

PageRank是有Google创始人发明的,并以发明者之一的Larry Page命名。现在基于这种变体已被所有大型搜索引擎采用。该算法认为网页的重要性是依据指向该网页的所有其他网页的重要性,以及这些网页中包含的链接数求得的。没错这个算法就是来评价网页重要性的。这个算法和其它算法(比如计入内容的算法加权求和得到的评价结果能改进引擎的效果)。
集体智慧编程第四章[搜索引擎与排名]总结_第3张图片
上图中,网页B,C和D均指向A,它们的PageRank值已经计算得出。B还指出另外三个网页,而C则指向其他四个网页,D只指向A。为了得到A的PageRank值,我们将指向A的每个网页的PageRank(pr)值除以这些网页中的链接总数,然后乘以阻尼因子0.85,再加上一个0.15的最小值。PR(A)的计算公式如下:

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.850.4650.54525

我们发现,由于D指向A,而且能贡献出它的全部分值,所以相比B或者C,尽管D本身的PageRank值较低,但是是加上它对A的PageRank值贡献度更大。本例中所有指向A的网页都已有了PageRank值。只有知道了指向同一网页的所有其他网页的评价后,我们才算出该网页的评价值。同样,对于这些网页的所哟其他网页,如果不先计算它们的评价值,那么这些网页的pagerank也是无法计算的。如何对一组还没有pagerank值得网页进行pangrank计算呢?
解决方法是,为所有PageRank都设置一个任意的初始值,然后反复计算,迭代若干次。在每次迭代中,每个网页的PageRank值将会越来越接近真实值。迭代所需要次数要视网络数量而定,不过对于目前正在处理的这一小组网页,20次就足够了。
集体智慧编程第四章[搜索引擎与排名]总结_第4张图片
由于有数据库操作,所以把数据库关系图放在这里

 def calcuatepagerank(self,iterations=20):
        self.con.execute("drop table if exists pagerank")
        self.con.execute("create table pagerank(urlid primary key, score)")

        #初始化每个url,让他们pagerank值都相等
        self.con.execute('insert into pagerank select rowid,1.0  from urllist')

        self.dbcommit()

        for i in  range(iterations):
            print "Iteration %d" %(i)
            for (urlid,) in self.con.execute('select rowid from urllist'):
                pr = 0.15
                #循环遍历指向当前网页的其他所有page
                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]

                    #求链接源的链接总数
                    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 inboundlinkscore(self,rows):
        uniqueurls = set([row[0] for row in rows])
        inboundcount = dict([(u,self.con.execute('select count(*) from link \
        where toid = %d' %u).fetchone()[0]) for u in uniqueurls])
        return self.normalizescores(inboundcount)

现在我们计算出了网站的各种评价度,只要对它们加权求和就能得到不同的效果,其中一些应该就是我们想要的结果。

从点击行为中学习

在线应用的一个最大优势就在于,它们会持续受到以用户行为为表现形式的反馈信息。对于搜索引擎而言,每一位用户可以通过只点击某条结果,而不选择点击其它内容,向引擎及时提供有关他对搜索结果喜好程度的信息。
为了实现这一目的,我们将构建一个人工神经网络,向其提供:查询条件中的单词,返回给用户的搜索结果,以及用户的点击决策,然后再对其加以训练。一旦网络经过了许多不同的点击训练,我们就可以利用它来改进搜索结果的排序,以更好的反映用户在过去一段时间内的实际点击情况。
书中对于神经网络的科普以及数据库设计等基础,就不讲了,重点看这个神经网络是怎么设计,并能在线训练的。
这个神经网络结构如下

集体智慧编程第四章[搜索引擎与排名]总结_第5张图片

神经网络对”World Bank”所做出的反应

上图,给出了一个针对“world bank”的查询。途中的实线代表强链接,粗体文字表示节点已经变得非常活跃。当然,最终的结果还要取决于被不断迭代修正的链接强度(也就是链接权重)。为此,只有有人执行搜索,并从结果中选择链接,我们就对该网络进行训练。在上图的网络中,许多人对world bank字符串进行搜索,并点击了world bank的链接,加强了单词与URL的关联。

神经网络设计

结构:

简单三成结构,input,hidden,output。而且本例中不是先预先创建一个隐藏层有几千个节点,全部链接均已就绪的巨大网络,我们只在需要时候创建新的隐藏节点会更加搞笑,更加简单。每传入一组以前从未见过的单词组合,该网络的构建函数就会在隐藏层中建立一个新的节点。随后函数会为单词与隐藏节点之间,以及查询节点与查询所返回的URL结果之间,建立其具有默认权重的链接。随着查询的增多,这个网络也会很大,输入层隐藏层以及输出层之间的链接将会变得非常复杂。

 def generatehiddennode(self,wordids,urls):
        if len(wordids) > 3: return None
        createkey = '_'.join([str(wi) for wi in wordids])
        res = self.con.execute('select rowid from hiddennode where create_key=\'%s\'' %createkey).fetchone()
        if res==None:
            cur = self.con.execute('insert into hiddennode(create_key) values(\'%s\')' %createkey)
            hiddenid = cur.lastrowid
            for wordid in wordids:
                self.setstrength(wordid,hiddenid,0,1.0/len(wordids))
            for urlid in urls:
                self.setstrength(hiddenid,urlid,1,0.1)
            self.con.commit()

在命令行调用这个函数

mynet = searchnet('nntest.db')
wWorld,wRiver,wBank = 101,102,103
uWorldBank,uRiver,uEarth = 201,202,203
mynet.generatehiddennode([wWorld,wBank],[uWorldBank,uRiver,uEarth])

该代码产生会产生如下结构的网络结构:


集体智慧编程第四章[搜索引擎与排名]总结_第6张图片
图6-2:人工神经网络试例

激活函数:

我们选择tanh函数(反双曲正切变换函数)作为激活函数。它是一类S型函数(sigmod function),所以该类型的函数都会呈现S形状。神经网络几乎总是利用s型函数来计算神经元的输出。

集体智慧编程第四章[搜索引擎与排名]总结_第7张图片
图6-3:tanh函数

前向传播算法:

如图6-2所示,前向传播其实就是根据这个网络拓扑图,直接计算出结果来而已。

hhX1y1=x1w1+x2w2=tanh(h),=hw1,X2=hw2,X3=hw3=tanh(X1),y2=tanh(X2),y3=tanh(X3)

反向传播:

这里其实就是利用梯度下降发不断更新图6-2中的权值w的过程:

costfunction:Y=[0.5(y1y1)2+0.5(y2y2)2+0.5(y3y3)2][y1,y2,y3]means:thetargetvalue

求能使Y最小的参数w的值。本来可以用最小二乘法,也就是
Yw=0=>w

但是,当网络很大,有上千个参数,直接求解析解有时候不现实,所以我们用梯度下降法来求数值解。
第一步:输入搜索内容(x1,x2),相关urls以及最后用户点击的url(target (y1,y2,y3) )。
第二步:根据 (x1,x2),w 计算出输出 (y1,y2,y3)
第三步:求 (y1,y2,y3) 和target (y1,y2,y3) )越不同,cost function:Y的值越大,为了缩小这个Y值,我们求 δ=Yw , 这个 δ 就是梯度,当Y对某个w的梯度大于零,表示w增加的话,损失函数Y的值也会增加。所以当梯度大于零的时候,w的值应该减小,来使损失函数的值也变小。那么到底应该减少多少呢?这就是一个经验值了,这里减少 0.5δ
第四步: w=w0.5δ
第五步:如果没有收敛就返回第一步,否则结束。
*hidden-output层的权重更新:*
Yw1=YX1X1w1=(y1y1)y1X1h=(y1y1)tanh(h)h

tips:[tanh(x)]=1[tanh(x)]2

*input-hidden层的权重更新:*
Yw1=Yhhhhw1=[YX1X1h+YX2X2h+YX3X3h]hhhw1=[(y1y1)w1+(y2y2)w2+(y3y3)w3]tanh(h)x1

这两层权重更新对应的代码如下:

 def backProgagte(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] = self.dtanh(self.ao[k]) * error
        #计算隐藏层的误差
        hiddden_deltas = [0.0] * len(self.hiddenids)
        for j in range(len(self.hiddenids)):
            error = 0.0
            for k in range(len(self.urlids)):
                error = error + output_deltas[k] * self.wo[j][k]
            hiddden_deltas[j] = self.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
        #更新输入weights
        for i in range(len(self.wordids)):
            for k in range(len(self.urlids)):
                change = hiddden_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.setupnetword(wordids,urlids)
        self.feedorward()
        targets = [0.0]*len(urlids)
        targets[urlids.index(selectedurl)] = 1.0
        self.backProgagte(targets)
        self.updatedatabase()

总结

当我们搜索一些单词时候,搜索引擎先找到所有包含这些单词的网站页面。这是最基本的步骤,接下来是对很多的页面做一个排序,这个排序怎么排很重要。基于内容的排序就是根据页面中单词的频度,位置等信息来排序,pagerank则倾向于那些重要程度很大的网站,然而神经网络的方法则是依据点击历史来推测用户可能喜欢的网站。
我们可以在搜索网页中记录下用户的点击行为,并将搜索内容和点击行为丢入神经网络进行训练。

trainquery(wordids,urls,targeturl)

其中 wordids记录的是这次搜索的实际内容,urls是与搜索内容相关的全部站点,targeturl是这次实际点击的页面。(还可以考虑某个页面的点击次数,停留时间等等影响因素)。

我们要将这个神经网络加入到对搜索结果的排名中去,对搜索结果进行加权处理,为此需要在searcher类中新建一个神经网络对于的对象,以及新建一个方法:

class searcher:
    def __init__(self,dbname):
        self.con = sqlite3.connect((dbname))
        self.mynet = searchnet('nntest.db')
    def nnscore(self,rows,wordids):
        urlids = [urlid for urlid in set([row[0] for row in rows])]
        nnres = self.mynet.getresult(wordids,urlids)
        scores = dict([(urlids[i],nnres[i]) for i in range(len(urlids))])
        return self.normalizescores(scores)

在searcher类中query方法的结尾处,另其结果返回单词内容和对应urls:

    def query(self,q):
        rows,wordids = self.getmatchrows(q)
        print rows
        scores = self.getscorelist(rows,wordids)
        rankedscores = sorted([(score,url) for (url,score) in scores.items()],reverse=1)
        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]]

附录:代码

完整的代码如下:


# coding:'utf-8'
__author__ = 'Yueyi'
import urllib2
from bs4 import BeautifulSoup
from urlparse import urljoin
import sqlite3
import re
from math import tanh
from sqlite3 import dbapi2 as sqlite
ignorewords = (['the','of','to','and','a','in','is','it'])

class crawler1:
    def __init__(self,dbname):
        self.con = sqlite3.connect(dbname)
    def __del__(self):
        self.con.close()
    def dbcommit(self):
        self.con.commit()
    def getentryid(self,table,field,value,createnew=True):
        con = self.con.execute('select rowid from %s where %s=\'%s\'' %(table,field,value))
        res = con.fetchone()
        if res==None:
            cur=self.con.execute('insert into %s (%s) values (\'%s\')'%(table,field,value))
            return cur.lastrowid
        else:
            return res[0]


    def addtoindex(self,url,soup):
        if self.isindexed(url): return
        print( 'Indexing ' + url)
        #获取每个单词
        text = self.gettextonly(soup)
        words = self.separatewords(text)
        #得到URL的id   或者插入url,返回最后一个id
        urlid = self.getentryid('urllist','url',url)

        #将每个单词与该url关联
        for i in range(len(words)):
            word = words[i]
            if word in ignorewords: continue
            wordid = self.getentryid('wordlist','word',word)
            self.con.execute('insert into wordlocation(urlid,wordid,location) values(%d,%d,%d) '%(urlid,wordid,i))


    def gettextonly(self,soup):
        v = soup.string
        if v==None:     ##不含有字符,就可能嵌套有tag
            c=soup.contents
            resulttext = ''
            for t in c:
                subtext=self.gettextonly(t)
                resulttext += subtext + '\n'
            return resulttext
        else:
            return v.strip()


    def separatewords(self,text):
        splitter = re.compile('\\W*')
        return [s.lower() for s in splitter.split(text) if s!=' ']


    def isindexed(self,url):
        u = self.con.execute('select rowid from urllist where url=\'%s\'' %url).fetchone()
        if u!=None:
            v = self.con.execute('select * from wordlocation where urlid=%d'%u[0]).fetchone()
            if v!=None: return True
        return False


    def addlinkref(self,urlFrom,urlTo,linkText):
        words = self.separatewords(linkText)
        fromid = self.getentryid('urllist','url',urlFrom)
        toid = self.getentryid('urllist','url',urlTo)
        if fromid == toid: return
        cur = self.con.execute('insert into link(fromid,toid) values(%d,%d)' %(fromid,toid))
        linkid = cur.lastrowid
        for word in words:
            if word in ignorewords: continue
            wordid = self.getentryid('wordlist','word',word)
            self.con.execute('insert into linkwords(linkid,wordid) values(%d,%d)' %(linkid,wordid))


    def crawl(self,pages,depth=2):
        for i in range(depth):
            newpages = set()
            for page in pages:
                try:
                    c = urllib2.urlopen(page)
                except:
                    print ('Could not open %s' %page)
                    continue
                soup = BeautifulSoup(c.read(),'html.parser')
                self.addtoindex(page,soup)
                try:
                    links = soup('a')
                    for link in links:
                        if('href' in dict(link.attrs)):
                            url=urljoin(page,link['href'])
                            if url.find("'") !=-1: continue
                            url = url.split('#')[0]
                            if url[0:4]=='http' and not self.isindexed(url):
                                newpages.add(url)
                            linkText = self.gettextonly(link)
                            self.addlinkref(page,url,linkText)
                    self.dbcommit()
                except:
                    print('Could not parse page %s'%page)
            pages=newpages


    def calcuatepagerank(self,iterations=20):
        self.con.execute('drop table if exists pagerank')
        self.con.execute('create table pagerank(urlid primary key, score)')

        #初始化每个url,让他们pagerank值都相等
        self.con.execute('insert into pagerank select rowid,1.0 from urllist')
        self.dbcommit()

        for i in  range(iterations):
            print "Iteration %d" %(i)
            for (urlid,) in self.con.execute('select rowid from urllist'):
                pr = 0.15

                #循环遍历指向当前网页的其他所有page
                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]

                    #求链接源的链接总数
                    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 createindextables(self):
        self.con.execute("create table urllist(url)")
        self.con.execute('create table wordlist(word)')
        self.con.execute('create table wordlocation(urlid,wordid,location)')
        self.con.execute('create table link(fromid interger,toid integer)')
        self.con.execute('create table linkwords(wordid,linkid)')
        self.con.execute('create index wordidx on wordlist(word)')
        self.con.execute('create index urlidx on urllist(url) ')
        self.con.execute('create index wordurlidx on wordlocation(wordid)')
        self.con.execute('create index urltoidx on link(toid)')
        self.con.execute('create index urlfrom on link(fromid)')
        self.dbcommit()


class searcher:
    def __init__(self,dbname):
        self.con = sqlite3.connect((dbname))
        self.mynet = searchnet('nntest.db')
    def __del__(self):
        self.con.close()
    def commit(self):
        self.con.commit()


    def getscorelist(self,rows,wordids):
        totalscores = dict([(row[0],0) for row in rows])
        weights = [(1.0,self.locationscore(rows)),
                   (1.0,self.frequencyscore(rows)),
                   (1.0,self.pagerankscore(rows)),
                   (1.0,self.linktextscore(rows,wordids)),
                   (1.0,self.distancescore(rows)),
                   (1.6,self.nnscore(rows,wordids))]
        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()


    def getmatchrows(self,q):
        fieldlist = 'w0.urlid'
        tablelist = ''
        clauselist = ''
        wordids = []

        #根据空间查分单词
        words = q.split(' ')
        tablenumber = 0
        for word in words:
            wordrow = self.con.execute('select rowid from wordlist where word=\'%s\'' %word).fetchone()
            if wordrow!=None:
                wordid = wordrow[0]
                wordids.append(wordid)
                if tablenumber>0:
                    tablelist += ','
                    clauselist += ' and '
                    clauselist += ' w%d.urlid=w%d.urlid and ' %(tablenumber-1,tablenumber)
                fieldlist += ',w%d.location' % tablenumber
                tablelist += 'wordlocation w%d'%tablenumber
                clauselist += 'w%d.wordid=%d' %(tablenumber,wordid)
                tablenumber += 1
        fullquery = 'select %s from %s where %s'%(fieldlist,tablelist,clauselist)
        print fullquery
        cur = self.con.execute(fullquery)
        rows = [row for row in cur]
        return rows,wordids


    def query(self,q):
        rows,wordids = self.getmatchrows(q)
        print rows
        scores = self.getscorelist(rows,wordids)
        rankedscores = sorted([(score,url) for (url,score) in scores.items()],reverse=1)
        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]]


    def normalizescores(self,scores,smallISBetter = 0):
        vsmall = 0.00001
        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):
        locations = dict([(row[0],100000) for row in rows])
        for row in rows:
            loc = sum(row[1:])
            if loc0]]: locations[row[0]] = loc
        return self.normalizescores(locations,smallISBetter=1)


    def distancescore(self,rows):
        if len(rows[0]) <=2: return dict([(row[0],1.0) for row in rows])
        mindistance = dict([(row[0],100000) 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)


    def inboundlinkscore(self,rows):
        uniqueurls = set([row[0] for row in rows])
        inboundcount = dict([(u,self.con.execute('select count(*) from link where toid = %d' %u).fetchone()[0]) for u in uniqueurls])
        return self.normalizescores(inboundcount)

    def pagerankscore(self,rows):
        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


    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)
            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)+1)/(maxscore+1)) for (u,l) in linkscores.items()])
        return normalizedscores

    def clickandtraining(self,wordids,urls,url):
        self.mynet.trainquery(wordids,urls,url)

    def nnscore(self,rows,wordids):
        urlids = [urlid for urlid in set([row[0] for row in rows])]
        nnres = self.mynet.getresult(wordids,urlids)
        scores = dict([(urlids[i],nnres[i]) for i in range(len(urlids))])
        return self.normalizescores(scores)


class searchnet:
    def __init__(self,dbname):
        self.con = sqlite.connect(dbname)

    def __del__(self):
        self.con.close()

    def maketables(self):
        self.con.execute('DROP table  IF EXISTS hiddennode');
        self.con.execute('DROP table  IF EXISTS wordhidden');
        self.con.execute('DROP table  IF EXISTS hiddenurl');
        self.con.execute('create table hiddennode(create_key)')
        self.con.execute('create table wordhidden(fromid,toid,strength)')
        self.con.execute('create table hiddenurl(fromid,toid,strength)')
        self.con.commit()

    def getstrength(self,fromid,toid,layer):
        if layer==0: table='wordhidden'
        else: table = 'hiddenurl'
        res = self.con.execute('select strength from %s where fromid = %d and toid=%d' %(table,fromid,toid)).fetchone()
        if res ==None:
            if layer==0: return -0.2
            if layer==1: return 0
        return res[0]

    def setstrength(self,fromid,toid,layer,strength):
        if layer==0: table = 'wordhidden'
        else: table='hiddenurl'
        res = self.con.execute('select rowid from %s where fromid=%d and toid=%d'%(table,fromid,toid)).fetchone()
        if res==None:
            self.con.execute('insert into %s(fromid,toid,strength) values(%d,%d,%f)' %(table,fromid,toid,strength))
        else:
            rowid = res[0]
            self.con.execute('update %s set strength=%f where rowid=%d' %(table,strength,rowid))

    def generatehiddennode(self,wordids,urls):
        if len(wordids) > 3: return None
        createkey = '_'.join([str(wi) for wi in wordids])
        res = self.con.execute('select rowid from hiddennode where create_key=\'%s\'' %createkey).fetchone()
        if res==None:
            cur = self.con.execute('insert into hiddennode(create_key) values(\'%s\')' %createkey)
            hiddenid = cur.lastrowid

            for wordid in wordids:
                self.setstrength(wordid,hiddenid,0,1.0/len(wordids))
            for urlid in urls:
                self.setstrength(hiddenid,urlid,1,0.1)
            self.con.commit()
    def getallhiddenids(self,wordids,urlids):
        l1={}
        for wordid in wordids:
            cur = self.con.execute('select toid from wordhidden where fromid=%d' %wordid)
            for row in cur: l1[row[0]] = 1
        for urlid in urlids:
            cur = self.con.execute('select fromid from hiddenurl where toid=%d' %urlid)
            for row in cur: l1[row[0]] = 1
            return l1.keys()

    def setupnetword(self,wordids,urlids):
        #值列表
        self.wordids = wordids
        self.hiddenids = self.getallhiddenids(wordids,urlids)
        self.urlids = urlids

        #节点输出
        self.ai = [1.0]*len(self.wordids)   ##返回lan长度的list,里面全是1
        self.ah = [1.0]*len(self.hiddenids)
        self.ao = [1.0]*len(self.urlids)

        #创建权重矩阵
        self.wi = [[self.getstrength(wordid,hiddenid,0) for hiddenid in self.hiddenids]for wordid in self.wordids]
        self.wo = [[self.getstrength(hiddenid,urlid,1) for urlid in self.urlids] for hiddenid in self.hiddenids]

    def feedorward(self):
        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]
            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]
            self.ao[k] = tanh(sum)
        return self.ao[:]

    def getresult(self,wordids,urlids):
        self.setupnetword (wordids,urlids)
        return self.feedorward()

    def dtanh(self,y):
        return 1.0-y*y
    def backProgagte(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] = self.dtanh(self.ao[k]) * error
        #计算隐藏层的误差
        hiddden_deltas = [0.0] * len(self.hiddenids)
        for j in range(len(self.hiddenids)):
            error = 0.0
            for k in range(len(self.urlids)):
                error = error + output_deltas[k] * self.wo[j][k]
            hiddden_deltas[j] = self.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
        #更新输入weights
        for i in range(len(self.wordids)):
            for k in range(len(self.urlids)):
                change = hiddden_deltas[j] * self.ai[i]
                self.wi[i][j] = self.wi[i][j] + N*change
    def updatedatabase(self):
        for i in range(len(self.wordids)):
            for j in range(len(self.hiddenids)):
                self.setstrength(self.wordids[i],self.hiddenids[j],0,self.wi[i][j])
        for j in range(len(self.hiddenids)):
            for k in range(len(self.urlids)):
                self.setstrength(self.hiddenids[j],self.urlids[k],1,self.wo[j][k])
        self.con.commit()

    def trainquery(self,wordids,urlids,selectedurl):
        self.generatehiddennode(wordids,urlids)
        self.setupnetword(wordids,urlids)
        self.feedorward()
        targets = [0.0]*len(urlids)
        targets[urlids.index(selectedurl)] = 1.0
        self.backProgagte(targets)
        self.updatedatabase()


def crawlprogramme():
    c = crawler1('cnn1.db')
    c.calcuatepagerank()

    pages = ['http://edition.cnn.com/']
    crawler = crawler1('cnn1.db')
    crawler.createindextables();
    crawler.crawl(pages)

def searchnntest():
    mynet = searchnet('nntest.db')
    wWorld,wRiver,wBank = 101,102,103
    uWorldBank,uRiver,uEarth = 201,202,203
    mynet.maketables()
    mynet.generatehiddennode([wWorld,wBank],[uWorldBank,uRiver,uEarth])
    print mynet.getresult([wWorld,wBank],[uWorldBank,uRiver,uEarth])
    print('training...')
    mynet.trainquery([wWorld,wBank],[uWorldBank,uRiver,uEarth],uWorldBank)
    print mynet.getresult([wWorld,wBank],[uWorldBank,uRiver,uEarth])
    allurls=[uWorldBank,uRiver,uEarth]
    for i in range(30):
        mynet.trainquery([wWorld,wBank],allurls,uWorldBank)
        mynet.trainquery([wRiver,wBank],allurls,uRiver)
        mynet.trainquery([wWorld],allurls,uEarth)
    print('------')
    print mynet.getresult([wWorld,wBank],allurls)
    print mynet.getresult([wRiver,wBank],allurls)
    print mynet.getresult([wBank],allurls)

def searchandnntrain():
    s = searcher('cnn1.db')
    wordids,urls = s.query('world')
    urlid = input('input your click url id:')
    s.clickandtraining(wordids,urls,urlid)

searchandnntrain();






你可能感兴趣的:(机器学习,数据挖掘)