机器学习之集体智慧编程3:搜索与排名

[TOC]

正式开始学习之前,先看看主要内容:

  • 分别根据单词频度,单词位置 ,单词距离 ,来搜索相关网页并排序
  • pagerank网站计分
  • 神经网络模拟用户行为
  • 前馈算法
  • 反向传播学习法

源数据

由于书中数据源已经无法下载到了,所以自己从wiki中爬了一些网页的数据,此处整理出来方便使用

如果对爬虫内容不感兴趣的话可以在文末下载到相应代码,也可以得到数据源

在本文的数据分析中,我们要根据用户搜索的关键字来查找相关度最高的网页,即类似搜索引擎的功能,所以我们需要的数据有:网站网址,网站内容(以单词记录,记录位置),网站指向的链接

为了存储数据,需要建5张表:

  • wordlocation单词位置表: id,wordid(单词的id),urlid(单词所在url对应id),position(单词在文中位置)
  • wordlist单词表:id ,word,urlid(懒得连查所以这里也存了一份..)
  • urllist网页表:id,url,rank(网页分数)
  • linkwords关键字指向表:id,wordid,linkid单词指向的链接的id
  • link网页指向表:id,fromid网站的id,toid网站指向的网站的id

有了这5张表之后,我们就可以愉快的开始爬虫之旅啦

在正式开始爬取网页数据之前,我们先把要爬的网页地址全部准备好:

# 爬取网址
def getUrls(url_start='https://en.wikipedia.org/wiki/Tiger'):
    url_base = 'https://en.wikipedia.org'
    urls, allurls, newurls = set(), set(), set()
    urls.add(url_start)
    allurls.add(url_start)
    for i in range(1):
        for url_resource in urls:
            try:
                res = requests.get(url_resource, timeout=2)
                # 只看正文下的内容
                links = BeautifulSoup(str(BeautifulSoup(res.content, 'lxml')('div', id='bodyContent')), 'lxml')('a')
                for link in links:
                    if 'href' in dict(link.attrs):
                        link_to = link['href']
                        # 外链和本文连接直接忽略
                        if link_to.startswith('http') or link_to.startswith('#'): continue
                        url = parse.urljoin(url_base, link_to)
                        if url.find("'") != -1: continue
                        url = url.split('#')[0]
                        newurls.add(url)
            except:
                pass
        urls = newurls.copy()
        allurls.update(newurls)
    with open('urls.txt', 'w') as f:
        for item in allurls:
            try:
                f.write(item + '\n')
            except:
                pass

运行上述代码可以生成一个包含了千余个网址的文件,我们就要从这些文件里取得我们要爬的分析数据,由于本文的主要记录搜索和排序的功能,具体的分析过程就不赘述了,直接贴代码:

def readUrls():
    with open('urls.txt', 'r') as f:
        urls = f.read().split('\n')
        return urls


def getUrlId(conn, cursor, url):
    # 查询当前url地址在表中的位置
    cursor.execute('select id from urllist where url=%s', (url,))
    urlid = cursor.fetchone()
    # 如果没有,新增并重新获取urlid
    if urlid is None:
        cursor.execute('insert into urllist(`url`) values(%s)', (url,))
        conn.commit()
        cursor.execute('select id from urllist where url=%s', (url,))
        urlid = cursor.fetchone()
    return urlid[0]


# 根据url地址爬取相关数据,并写入数据库
def parseData(urls):
    conn = connector.connect(user='root', password='wangweijie0', database='wwj')
    cursor = conn.cursor()
    worldid = 1
    ignores = ['a', 'and', 'it', 'or', 'of', 'to', 'is', 'in', 'and', 'but', 'the', 'ma']
    # 循环urlid
    for url in urls:
        try:
            res = requests.get(url, timeout=2)

            urlid = getUrlId(conn, cursor, url)
            soup = BeautifulSoup(str(BeautifulSoup(res.content, 'lxml')('div', id='bodyContent')), 'lxml')
            splitter = re.compile('\\W+')
            words = [t.lower() for t in splitter.split(soup.get_text()) if
                     t != '' and t not in ignores]
            # 单词入库
            for position in range(len(words)):
                # 写库
                cursor.execute('insert into wordlist(`word`,`urlid`)values(%s,%s)', (words[position], urlid))
                cursor.execute('insert into wordlocation(`urlid`,`wordid`,`position`)values(%s,%s,%s)', (
                    urlid, worldid, position + 1))
                worldid += 1
            conn.commit()

            # 只看正文下的内容
            links = soup('a')
            for link in links:
                if 'href' in dict(link.attrs):
                    link_to = link['href']
                    # 外链和本文连接直接忽略
                    if link_to.startswith('http') or link_to.startswith('#'): continue
                    link_url = parse.urljoin('https://en.wikipedia.org', link_to)
                    if link_url.find("'") != -1: continue
                    link_url = link_url.split('#')[0]
                    linkid = getUrlId(conn, cursor, link_url)

                    cursor.execute('insert into link(`fromid`,`toid`)values(%s,%s)', (urlid, linkid))
                    # 查询当前href的字在word中而id不再linkwords中的第一个
                    cursor.execute(
                        'select ws.id from wordlist ws where ws.urlid=%s and ws.word=%s ',
                        (urlid, link.text))
                    wordids = cursor.fetchall()
                    if wordids is not None and len(wordids) > 0:
                        for wordid in wordids:
                            cursor.execute('select lw.wordid from linkwords lw where lw.wordid=%s ', (wordid[0],))
                            r = cursor.fetchone()
                            if r is None or len(r) == 0:
                                cursor.execute('insert into linkwords(`wordid`,`linkid`)values(%s,%s)',
                                               (wordid[0], linkid))
                                conn.commit()
                                break

            conn.commit()
        except BaseException as e:
            conn.rollback()

    cursor.close()
    conn.close()

注意:由于wiki里侧边栏连接太多,所以我只爬取了正文的内容,侧边栏和下边栏,包括文章的标题都没有爬取

准备好数据之后就可以愉快的开始我们的搜索引擎之路了:

搜索与排名

​ 根据关键字查表搜索可以得到和关键字相关的网页,但是乱序的网页中很难找到我们真正想要的,所以我们需要对网页进行一个排名,这样更容易找到用户想看到的东西,我们进行排名主要依靠单词频度,单词位置,单词距离 ,网页评分

​ 下面就对这几个评估方法展开叙述:

搜索

​ 首先,我们得构造一个方法,可以根据用户输入的关键字查找到相关的网页:

class search():
    def __init__(self):
        self.conn = connector.connect(user='root', password='wangweijie0', database='wwj')
        self.cursor = self.conn.cursor()

    def __del__(self):
        if self.conn:
            self.conn.close

    # 查询的入口,kw是关键字字符串,以空格分割
    def searchK(self, kw):
        kw.lower()
        words = kw.split(' ')
        self.result = {}
        for word in words:
            self.cursor.execute('select w.urlid,u.url from wordlist w,urllist u where w.word=%s and u.id=w.urlid',
                                (word,))
            # 查到所有连接的urlid
            urlids = self.cursor.fetchall()
            self.result[word] = urlids
        self.data = self.handledata()
        if len(self.data) == 0:
            print('无查询结果')
        else:
            self.getscoredlist()

​ 在这里,我为了方便,直接取到了和关键字相关的网页和网页在数据库中的id,由于查到的网页有很多重复的,所以构造了一个handledata方法对查询结果进行了优化:

  # 处理数据,返回共有的url和这些url出现的次数
    def handledata(self):
        # 记录原始数据,但是把数据元素从[(1,),(2,)]变成了[1,2]
        data2 = self.result.copy()
        # 记录新数据,新数据之中去除了重复的url
        newdata = {}
        for word, urls in data2.items():
            urlids = [u[0] for u in urls]
            # 把所有元素添加进来
            urlset = []
            for urlid in urlids:
                if urlid not in urlset:
                    urlset.append(urlid)
            newdata[word] = urlset
        # 通过reduce函数,把新数据中的value变成set并求交集,之后在变回list,这样得到了两个的交集
        urllist = list(reduce(lambda x, y: set(x) & set(y), [newdata[item] for item in newdata]))
        # 计算交集url出现的次数
        return dict([(k, sum([data2[word].count(k) for word in data2])) for k in urllist])

​ 调用函数,输入关键字就可以查到与之相关的网页了,虽然目前我们并没有做任何优化和排序,但是还是可以看出这些网页与关键字之间是有连续的

​ 有必要先提前解释一下result和data的意思:result代表了初始的查询结果,数据很不好看,但是是原始数据,data是对result进行了优化,得到了关键字共同出现的url和这些关键字共同出现的次数之和

归一函数

​ 由于我们再排序的时候用到了多种方法,有的方法是分数高的网页靠前,有的方法是分数低的网页靠前,所以我们需要一个归一化函数来把这些网页的评分进行优化,让他们的值域和变化方向一致

# 把数据都转化到0-1之间
    def nomalizescores(self, scores, smallbetter=True):
        vsamll = 0.00001
        # 小了好,小的分数高,小的当分子即可
        if smallbetter:
            minscore = min(scores.values())
            res = [(float(minscore) / max(vsamll, v), k) for k, v in scores.items()]
        # 大了好,大的分数高,大的当分母即可
        else:
            maxscore = max(scores.values())
            # 避免分母是0
            if maxscore == 0: maxscore = vsamll
            res = [(float(v) / maxscore, k) for k, v in scores.items()]

        return dict(res)

​ 这样我们就得到了一个值在0-1之间的评价,并且评价越高说明排名应该越靠前

单词频度

单词频度 就是用户搜索的单词在网页正文中出现的次数,我们有理由认为关键词出现次数多的网页更符合用户的预期,其排名应该更靠前

​ 由于之前已经统计过网站出现的次数,所以此处很简单:

#  根据出现次数打分,多个单词的话以出现次数之和算
def countscore(self):
    return self.nomalizescores(self.data, smallbetter=False)

单词位置

​ 另一个判断网页相关度的简单方法是单词位置,如果单词出现的位置更靠前,比如出现在标题栏或者简介栏,那么这个网页应该跟我们搜索的关键词关联更紧密

  # 根据单词之间的距离/单词位置计算
    # 其实可以把单词位置记录在单词表,可以一起查出来结果,比这样效率高得多
    # type记录计算距离还是计算位置
    def disscore(self, type='distance'):
        # 按单词之间距离来
        if type == 'distance':
            fn = lambda m, n: m - n
        # 按单词的位置来
        else:
            fn = lambda m, n: m + n

        if len(self.result) == 1:
            return 1
        positions = dict([(item, {}) for item in self.data])
        totalscores = {}
        for urlid in self.data:
            for word in self.result:
                self.cursor.execute(
                    'select wl.position from wordlocation wl,wordlist w where w.word=%s and w.urlid=%s and wl.wordid=w.id',
                    (word, urlid))
                p = self.cursor.fetchall()
                positions[urlid][word] = [item[0] for item in p]
        # item是url,ps中包含了url中的关键字和出现的位置
        for item, ps in positions.items():
            totalscores.setdefault(item, 9999)
            # 每次取两个关键字比较
            for k1 in ps:
                for k2 in ps:
                    # 不比较一样的关键字
                    if k1 == k2: continue
                    # 取两个关键字的所有值比较,取到差最小或者和最小的一对,记录
                    # 由于关键字可能不止两个,所以两两计算的结果加在一起作为最终的结果
                    totalscores[item] += min([min([abs(fn(m, n)) for m in ps[k1]]) for n in ps[k2]])
        return self.nomalizescores(totalscores, smallbetter=True)

​ 在上面的代码中,我们查到关键字的位置并构造了关于url和word的结果集,遍历结果集并把这些单词的位置求和,得到两两之间最小的位置和,这就是两个位置最靠前的单词的位置,重复这一过程,可以把多个关键字两两组合求得所有的关键字的位置总和

​ 举个栗子方便理解:

​ 查询关键字'a b c',得到了一些url,我们取一个url,可以得到这个url里这些单词出现的位置

url:{a:[10,5,7,1],b:[13,7,2],c[18,4,9]}

​ 那么我们可以先计算ab的最小位置和1+2=3,再计算ac的最小位置和1+4=5,bc的最小位置和2+4=6 ,所以最终计算结果应该是3+5+6=14

​ 上述存在重复计算,但是由于归一化函数的存在,重复计算不会影响我们的最终判定结果,所以没有做处理

​ 可能我描述的不太清楚,最主要还是理清思路,实在无法理解我说的是啥的可以debug跟一下代码,就明了

单词距离

​ 我们查询多个单词的时候,更倾向于被查询的单词在文中出现的位置相近,如果两个单词出现在同一文章中但是距离过远,那可能就不是我们想要的结果

​ 由于单词距离和单词位置都是根据每个关键字的位置来的,所以和单词位置合并到一起了,区别只是单词距离取得是位置差(也就是距离)的最小值,单词位置取得是位置和(也就是位置)的最小值

PageRank计分法

​ PageRank根据网页的权重以及网页指向其他链接的个数计算而成

​ 这个值表示了用户在浏览时到达这个网页的可能性,值越大说明用户越倾向于进入这个网页,那这个网页权重显然应该更高

​ 计算公式:

p=0.15+0.85*(p1/l1+p2/l2+...)

​ p和l分别代表了指向这个网页的权重和外链数

举个栗子:

[图片上传失败...(image-23c3aa-1513394933469)]

​ 在上图中,B网页有0.5的权重,指向4个网页,C网页有0.3的权重,指向四个网页,D网页有0.2的权重,只指向A

​ 所以我们计算A的权重:

​ Ra=0.15+0.85*(0.5/4+0.3/4+0.2/1)=0.49

​ 计算pagerank的方法其实很简单,只需要知道原网页权重和指向的个数就可以了,但是如果一组网页都没有权重,我们应该怎么办呢?

​ 解决的办法是为每个网页都设置一个初始值,本文设置的是1,然后对多有网页进行pagerank的计算,替代初始值.多次执行计算之后就可以得到非常接近真实值的pagerank

 # 计算网页分值
    def pagerank(self):
        self.cursor.execute('select id from urllist')
        urlidlist = [item[0] for item in self.cursor.fetchall()]
        self.cursor.execute('select toid,fromid from link')
        linklist = self.cursor.fetchall()
        # todata里放着所有指向这个链接的urlid
        todata = {}
        # fromdata放着所有此链接指向的链接的数量
        fromdata = {}
        for item in linklist:
            todata.setdefault(item[0], [])
            todata[item[0]].append(item[1])
            fromdata.setdefault(item[1], 0)
            fromdata[item[1]] += 1
        # 初始化所有rank为1
        ranks = dict([(urlid, 1) for urlid in urlidlist])
        # 循环30次计算pagerank,基本可以保证接近现实
        for i in range(30):
            for urlid, fl in todata.items():
                ranks[urlid] = 0.15 + 0.85 * sum([float(ranks[fromid]) / fromdata[fromid] for fromid in fl])
        for urlid in ranks:
            self.cursor.execute('update urllist set rank=%s where id=%s', (ranks[urlid], urlid))

        self.conn.commit()

神经网络,模拟用户行为

​ 在线应用最大的优势就是可以得到用户操作的实时反馈

​ 对于搜索引擎而言,可以获得用户在搜索时点击网页时的选择情况,让我们可以更好地展示用户喜欢的网页

​ 在许多神经网络中,都以一组神经元相连,我们即将学习的这种称为多层感知机 ,他们都有一层输出层和一层输出层,以及中间的隐藏层.输入层和输出层用于交互,隐藏层用于筛选和计算

​ 下面我们就开始进行模拟用户行为的神经网络训练

​ 为了保存神经元之间的数据,首先我们得建数据库:

class searchnet():
    def __init__(self):
        self.conn = connector.connect(user='root', password='wangweijie0', database='wwj')
        self.cursor = self.conn.cursor()

    def __del__(self):
        if self.conn:
            self.conn.close

    # 建表
    def createtable(self):
        self.cursor.execute(
             'create table inputhidden(`id` integer not null auto_increment,`fromid` varchar(80) not null,`toid` varchar(80) not null,`strength` float not null,primary key (`id`))auto_increment=1')
        self.cursor.execute(
             'create table hiddenoutput(`id` integer not null  auto_increment,`fromid` varchar(80) not null,`toid` varchar(80) not null,`strength` float not null,primary key (`id`))auto_increment=1')
        self.cursor.execute(
            'create table hiddennode(`id`integer not null auto_increment, `createkey` varchar(80) not null,primary key (`id`))auto_increment=1')

        self.conn.commit()

​ 这三张表分别记录了输入层到隐藏层的所有节点对应的id以及连接的强度(也可以称为权重),并记录了查询关键字

​ 有了数据库之后我们需要两个方法用于设置和获取当前连接的强度:


    # 获取链接强度,即链接权重
    def getstrength(self, fromid, toid, layer):
        # 输入-隐藏,sd(strength-default)是默认strength
        if layer == 0:
            tablename, sd = 'inputhidden', -0.2
            self.cursor.execute('select strength from inputhidden where fromid = %s and toid = %s', (fromid, toid))
            res = self.cursor.fetchone()
            if res is None:
                return sd
            return res[0]
        # 隐藏-输出
        elif layer == 1:
            tablename, sd = 'hiddenoutput', 0
            self.cursor.execute('select strength from hiddenoutput where fromid = %s and toid = %s', (fromid, toid))
            res = self.cursor.fetchone()
            if res is None:
                return sd
            return res[0]

    # 设置链接强度
    def setstrength(self, fromid, toid, strength, layer):
        # 输入-隐藏
        if layer == 0:
            tablename = 'inputhidden'
            self.cursor.execute('select strength from inputhidden where fromid=%s and toid=%s',
                                (fromid, toid))
            res = self.cursor.fetchone()
            # 无数据,插入数据
            if res is None:
                self.cursor.execute('insert into inputhidden(`fromid`,`toid`,`strength`) values(%s,%s,%s)',
                                    (fromid, toid, strength))
            # 有数据,更新
            else:
                self.cursor.execute('update inputhidden set strength=%s where fromid=%s and toid=%s',
                                    (strength, fromid, toid))
        # 隐藏-输出
        elif layer == 1:
            tablename = 'hiddenoutput'
            self.cursor.execute('select strength from hiddenoutput where fromid=%s and toid=%s',
                                (fromid, toid))
            res = self.cursor.fetchone()
            # 无数据,插入数据
            if res is None:
                self.cursor.execute('insert into hiddenoutput(`fromid`,`toid`,`strength`) values(%s,%s,%s)',
                                    (fromid, toid, strength))
            # 有数据,更新
            else:
                self.cursor.execute('update hiddenoutput set strength=%s where fromid=%s and toid=%s',
                                    (strength, fromid, toid))
        self.conn.commit()

这里我尝试把tablename赋值给一个变量,然后把变量扔到sql语句中,但是一直报错,所以不得不采用这种重复代码的方法,如果您知道怎么动态设置tablename,望告知,万分感谢

​ 在构建神经网络时,我们基本上都会先建立一个巨大的网络,把所有节点都构造出来.但是本例子中由于数据量不大,所以临时建立节点效率更高

​ 每次用户传入一组数据,我们查看这组数据的节点是否已经建立,如果没有建立的话就添加节点

# 建立链接网
    def generatehiddennode(self, words, urls):
        if len(words) > 3: return None
        # 通过排序保证key的唯一性
        createkey = ' '.join(sorted([str(word) for word in words]))
        self.cursor.execute('select id from hiddennode where createkey=%s ', (createkey,))
        res = self.cursor.fetchone()
        # 当前节点尚未建立,建立节点,设置权重(连接强度)
        if res is None:
            # 新建隐层节点
            self.cursor.execute('insert into hiddennode(`createkey`) values(%s)', (createkey,))
            for word in words:
                self.setstrength(word, createkey, 1 / len(words), 0)

            for url in urls:
                self.setstrength(createkey, url, 0.1, 1)

            self.conn.commit()

激活函数

如果每次我们都把输入值当做输出值这种线性做法来模拟网络,很多时候并不能满足我们的要求,所以需要引入非线性因素来让我们的网络功能更加丰富,也更加接近神经元(根据生物学的神经结构,刺激)

通常我们用得到的激活函数:

  • tanh双曲正切函数

    ​ [图片上传失败...(image-111699-1513394933470)]

  • Sigmoid

[图片上传失败...(image-ee5c7c-1513394933470)]

  • ReLU

前馈法

​ 通过输入正向计算输出的算法称为前馈算法,这个算法是开环的

​ 在计算神经网络的输出值之前,我们需要把要分析的链接网矩阵构建起来

​ 首先根据关键字找到相关的隐藏节点:

# 获取所有和输入层,输出层相关的隐藏节点
    def getallhiddenids(self, words, urls):
        ll = {}
        createkey = ' '.join(sorted([str(word) for word in words]))
        for word in words:
            self.cursor.execute('select toid from inputhidden where fromid=%s', (word,))
            for row in self.cursor.fetchall(): ll[row[0]] = 1

        for url in urls:
            cur = self.cursor.execute('select fromid from hiddenoutput where toid=%s', (url,))
            for row in self.cursor.fetchall(): ll[row[0]] = 1

        return list(ll.keys())

​ 构建链接矩阵:

 # 建立链接矩阵
    def setupnetword(self, words, urls):
        self.words = words
        self.urls = urls
        self.hiddens = self.getallhiddenids(words, urls)

        # 初始化输出数据,默认输入层输出都是1,wio,who,woo分别代表输入层,隐藏层,输出层的输出
        self.wio = [1.0] * len(self.words)
        self.who = [0.0] * len(self.hiddens)
        self.woo = [0.0] * len(self.urls)

        # 初始化权重矩阵,ih,ho分别代表输入层到隐藏层和隐藏层到输出层
        # 在矩阵中,隐藏层代表列名,输入输出都是行名
        self.ih = [[self.getstrength(fromid, toid, 0) for toid in self.hiddens] for fromid in self.words]
        self.ho = [[self.getstrength(fromid, toid, 1) for fromid in self.hiddens] for toid in self.urls]

​ 通过这个矩阵,我们把所有节点的相关信息(输入,输出,强度)全部拿到,就可以计算结果了

​ 为了便于计算,我们假设输入层的输出全部为1,也就是隐藏层的输入是1


    # 前馈算法,计算输出值
    def feedfoward(self):
        for i in range(len(self.words)):
            # 输入层的输出结果默认为1
            self.wio[i] = 1
        # 对每一个隐藏层,计算到这个输隐藏层的输入,再通过tanh函数计算出输出
        # j代表这是第几列,列名是隐藏层节点的名字
        for j in range(len(self.hiddens)):
            sum = 0
            # i代表第几行,行名输入层/输出层名字
            # 这里对某个隐藏节点的所有输入与连接强度的乘积求和,得到隐藏节点的输入
            for i in range(len(self.words)):
                sum += self.wio[i] * self.ih[i][j]
            # 利用激活函数求得输出
            self.who[j] = tanh(sum)
        # 同上,计算输出层的输出
        for i in range(len(self.urls)):
            sum = 0
            for j in range(len(self.hiddens)):
                sum += self.who[j] * self.ho[i][j]
            # 利用激活函数求得输出
            self.woo[i] = tanh(sum)
        return self.woo[:]

这里一定要理解,在输入-隐藏-输出这个模型中一共有3次激活(即内耗)两次传递.所以正常来说应该用到三次激活函数,两次损失函数,但是由于我们制定了输入层的输出是1,所以少用了一次激活函数

根据前馈算法中计算出的输出值,我们就知道误差有多少了,然后逆向传播,训练我们的神经网络,修改连接权重即可

  # 反向传播,训练机器,改变链接权重
    def backpropagate(self, targets, N=0.5):
        # 计算输出层的误差,这个误差也就是输出层的输入误差
        ho_deviation = [0.0] * len(self.urls)
        for i in range(len(targets)):
            # 体现在最终输出的误差
            error = targets[i] - self.woo[i]
            # 反函数求输出层的输入值并计算误差
            ho_deviation[i] = self.dtanh(self.woo[i]) * error
        # 根据输出层误差计算隐藏层误差,这个误差是隐藏层的输入误差
        ih_deviation = [0.0] * len(self.hiddens)
        for j in range(len(self.hiddens)):
            for i in range(len(self.urls)):
                error = 0
                # 把误差按当前各个神经元的传递损耗分配下去,计算一个隐藏层的全部误差
                # 通过这个损耗,已经把输出层的输入转化成了隐藏层的输出
                error += ho_deviation[i] * self.ho[i][j]
            # 对当前隐藏层的输出求反,得到了隐藏层输入,进而求得隐藏层输入误差
            ih_deviation[j] = self.dtanh(self.who[j]) * error

        # 修改隐藏层到输出层之间的连接强度
        for j in range(len(self.hiddens)):
            for i in range(len(self.urls)):
                # 应该修改的值是隐藏层的输出值*误差
                change = self.who[j] * ho_deviation[i]
                # N代表学习效率/成功率
                self.ho[i][j] += N * change
        # 修改输入层到隐藏层之间的链接强度
        for j in range(len(self.hiddens)):
            for i in range(len(self.words)):
                # 根据隐藏节点的误差求得输入层到隐藏层的误差
                change = self.wio[i] * ih_deviation[j]
                self.ih[i][j] += N * change

​ 这里我再解释一下,反向传播的时候,过了一次连接(隐藏层-输出层),两次激活(隐藏层和输出层),理解这点至关重要.

但是此处我还有一个地方比较疑惑,计算权重误差的时候居然是以上层输出实际误差,而不是实际误差/上层输出,如果有大佬能解惑的话请告诉我0.0*

​ 计算完成之后我们需要把数据保存到数据库

 def updateData(self):
        for j in range(len(self.hiddens)):
            for i in range(len(self.urls)):
                self.cursor.execute('update hiddenoutput set strength=%s where fromid=%s and toid=%s',
                                    (self.ho[i][j], self.hiddens[j], self.urls[i]))

        for j in range(len(self.hiddens)):
            for i in range(len(self.words)):
                self.cursor.execute('update inputhidden set strength=%s where fromid=%s and toid=%s',
                                    (self.ih[i][j], self.words[i], self.hiddens[j]))

        self.conn.commit()

​ 训练的完整函数

def train(self, words, urls, target):
        # 生成节点
        # 建立连接矩阵
        # 获取链接强度
        # 在反向训练之前调用前馈算法,这样可以计算好所有输出值
        # 根据target生成输出层输出结果,反向传播修改矩阵数据
        # 修改数据(输入层-隐藏层和隐藏层-输出层都要修改)
        self.generatehiddennode(words, urls)
        self.setupnetword(words, urls)
        self.feedfoward()
        targets = [0.0] * len(urls)
        targets[urls.index(target)] = 1
        self.backpropagate(targets)
        self.updateData()

这样我们的训练函数就完成了,可以自己尝试一下哦,用法:

se = searchnet()
for i in range(10):
    se.train(['机器', '学习'], ['机器', '学习', '机器学习'], '机器学习')
    print(se.getreslut(['机器', '学习'], ['机器', '学习', '机器学习']))

print(se.getreslut(['机器'], ['机器', '学习', '机器学习']))

最后,我们只需要把求结果的函数导入到排名中并分配权重就可以了,这里不做赘述

完整源码

有任何问题请私信或留言

你可能感兴趣的:(机器学习之集体智慧编程3:搜索与排名)