美文网首页
机器学习之集体智慧编程3:搜索与排名

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

作者: 冷鸢J | 来源:发表于2017-12-16 11:29 被阅读0次

    [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:搜索与排名

          本文链接:https://www.haomeiwen.com/subject/ozlswxtx.html