机器学习入门--带着贝叶斯公式一步步实现单词拼写纠正器,这一篇就够了

开篇先看几张张熟悉的图:


这里写图片描述

这里写图片描述


这里写图片描述

很多朋友可能都熟悉这种界面,那今天我们就揭开其中奥秘

一、贝叶斯公式是什么

回忆里想起小时候,不对, 应该是高中的时候咱都学过一些概率知识 ,什么排列、组合,古典概型。。。等名词,不过如此,那就直入主题先简单聊聊贝叶斯公式。

说起贝叶斯公式,不得不提 条件概率:

所考虑的是事件A已发生的条件下B发生的概率

为此我翻看了尘封已久的《概率论》 ,有如下结论:

P(AB)=P(A)P(B|A)

翻译过来AB就是同时发生的概率等于A发生的概率乘上A发生的条件下B发生的概率,其中人们习惯用P(某事)来表示某事发生的概率。

举个形象的例子:

将一枚硬币抛掷两次,观察其出现正反面的情况,设事件A为“至少有一次为正面”,事件B为“两次掷出同一面”。
那么这里抛两次一共有{正正,正反,反正,反反}四种可能
那么A有{正正,正反,反正}三种可能==》P(A)=3/4
而B则有{正正,反反}两种可能==》P(B)=1/2
那么重点来了,P(B|A)表示A发生的情况下也就是{正正,正反,反正}发生时,出现{正正,反反}的概率,显然{反反}在A发生的情况下不可能发生,而{正正}有1/3的概率发生,所以==》P(B|A)=1/3
同理P(A|B)表示B发生的情况下A发生的概率,显然就是1/2,==》P(A|B)=1/2
而P(AB)表示A、B同时发生的概率,观察发现只有{正正}能满足,得到==》P(AB)=1/4=P(B|A)*P(A)
这里买下一个伏笔,其实不难发现P(BA)=P(AB)=P(B|A)*P(A)=P(A|B)*P(B)


这里写图片描述

这点要是抗不住了中就不用往下看啦。

Ok,条件概率明白以后,我们开启揭开贝叶斯公式模式

上一个例子留下了一个伏笔:

P(AB)=P(B|A)*P(A)=P(A|B)*P(B)

这里就不给于证明了,想想应该是能明白的。其实说白了这就是贝叶斯公式,那位客观笑了,就这么简单?对,就是这么简单,可别小看了这个小公式,这可是一个思想,是在贝叶斯死后几十年才被世人所发现其用途的一个思想,俗称“逆概”

说可能没什么概念,再举个栗子,我们就能明白其中之奥妙了:

话说一个学校有男生女生,男生占比为60%,女生占比为40%,
而男生全是穿裤子的,女生则不然,女生有70%穿裤子,30%穿裙子
下面请听题:
1.对面走过来一个美眉,请问她穿裤子的概率是多大,想都不想是:70%
2.远处走过来一个穿裤子的学生,那么该学生是女生的概率有多大?这。。那,,,额,,我算算。。。。
怎么样,是不是比第一个问题复杂多了,这就是个典型的“逆概”问题,这个问题,不用贝叶斯公式,用点数学知识还是能求出来的:P(女|库)=女库/(男库+女库)就可以了,但是在自然界中,很多时候并不能求得(男裤+女裤)的数量(这种例子很多很多,就不列举了,毕竟咱目的不是为了研究数学,而是用数学)
第二个问题用贝叶斯公式来解释就是:P(女|库)=P(库|女)*P(女)/P(库)=70%*40%/(60%+28%)=7/22。

二、单词拼写纠错器实现原理分析

至此,今天咱们这个小算法所用到的数学知识就说完了,下面进入主题———–
就以开篇的图为例 ,咱们输入了一个lovl,首先这个单词是错误的,那么实际是想入的是什么呢?那位说话了,你二叉么,我怎么知道实际想输入什么?什么love、lovely、loll、lov 。。。。。。。。。,我怎么知道要输入哪个。
对,说的很对,鬼知道想输入哪个,他实际想输入什么咱不知道,那可不可以这样想,根据他输错的这个单词,咱们找出所有和他输错的这个单词像的单词,然后把其中在英文中出现频率最多的词作为他想输入的单词如何?

其实机器学习不就是以概率说话的么?

OK,继续,直接上公式,咱这里就是求P(love|lovl)、P(lovely|lovl)、P(loll|lovl)、P(lov|lovl)。。。。。。哪个概率最大不是么

P(love|lovl)为例,这个概率值得意思是输入了一个错误单词lovl,那么他是想输入love的概率。要求他的概率,套用贝叶斯公式,P(love|lovl)=P(lovl|love)*P(love)/P(lovl),这不就可以了么?
而分母中P(lovl)的值无法求得,但是这个单词确实是已经输错了,已经成了一个定值,所以说P(love|lovl)∝P(lovl|love)*P(love),而P(love)的使用频率我们是可以基于一个语料库求得到的,也就是说P(love)是一个先验概率,即已知值。

那么问题就转化为求P(lovl|love)得值,这个值是很不好求得,为什么呢?看个例子
把love输成lovl和把love输成oove的概率哪个大?这个显然前者可能性比后者大,大多少呢?这个就没办法量化了

但是我能肯定的是把love输入成lovl的概率要比把love输入成lovll的概率大

所以说咱这里只能定性的分析,即默认使 love输错一个字母得到错误单词的概率相等,即P(lovl|love)=P(lovel|lovle)=P(lvoe|love)=P(lov|love)(这里的转换要注意),而假设输错两个字母得到的错误单词的概率相等,但是这个概率值肯定要远远小于输入一个字母的概率,这个不用多做解释了吧。

那么问题最终转化为找先找一步输错时(包括增删改插四种情况)可能的正确单词的概率进行比较,即P(love)、P(lova)、P(loll)、P(lov)。。。。。谁的概率最大,当然一步纠错后得到的单词可能也不是正确单词,那么咱可以进行二步纠错,比如输入了lovlle,需要纠正两次才能得到正确单词love,或者loll。说了这么多啰嗦的话只为帮助各位理解,形象点表示就是:

P(正确单词|错误单词)∝P(错误单词|正确单词)*P(正确单词)∝P(正确单词)

三、python实现纠正器

下面直接上代码了,可能讲得原理什么的迷迷糊糊,但我保证,代码走一遍,绝对就明明白白了,毕竟实践出真知嘛。

首先声明,我是用python中最简单的语法实现此功能,帮助大家理解,用高级语法二十行就搞定就什么都看不明白了。

首先得到每个单词的使用频率,即P(正确单词)

# 读取big.txt 内容,big.txt就是原理中提到的语料库,这个不是死的
open_r = open("big.txt", 'r')
big_info = open_r.read()
open_r.close()

# 提炼出单词的方法,即把标点符号什么的都去掉了,并且把单词都转化为小写
def words(info):
    return re.findall("[a-z]+", info.lower())
# 计算词出现的次数
def wordIndex(wordtext):
    words = {}
    for word in wordtext:
        if word in words:
            words[word] = words[word] + 1
        else:
            words[word] = 1
    return words

#调用方法得到每个单词使用的次数
words_map = wordIndex(words(big_info))

其中alphabet = 'abcdefghijklmnopqrstuvwxyz'

OK,words_map 就是我们得到的以正确单词为key,使用次数为value的一个字典。

下面进行第一步排除,毕竟不是每次输入的都是错误单词对吧,也就是说,输入的一个单词如果在words_map中有这个key,那么就认为输入的是正确的单词,返回他就好了

# 如果存在该单词则返回该单词
def known(word):
    word1 = []
    for word_one in word:
        if word_one in words_map:
            word1.append(word_one)
    return word1

紧接着,如果发现输入的单词words_map中没有,那么我们就进行一步纠正看他在words_map中有多少个单词能满足。

# 一次纠正得到一个集合
def knomn_edit1(words):
    edit1_word = []
    n = len(words)
    for j in range(n):
        # print(i)
        word = words[j]
        m = len(word)
        for i in range(m):
            if i != m - 1:
                edit1_word.append(word[0:i] + word[i + 1:])  # 删
                if i != m - 2:
                    edit1_word.append(word[0:i] + word[i + 1] + word[i] + word[i + 2:])  # 移动
                else:
                    edit1_word.append(word[0:i] + word[i + 1] + word[i])  # 移动
                for ch in alphabet:
                    edit1_word.append(word[0:i] + ch + word[i + 1:])  # 替换
                    edit1_word.append(word[0:i] + ch + word[i:])  # 插入
            else:
                edit1_word.append(word[0:(m - 1)])  # 删
                for ch in alphabet:
                    edit1_word.append(word[0:(m - 1)] + ch)  # 替换
                    edit1_word.append(word[0:m] + ch)  # 插入
    return edit1_word

对一步纠正这里的情况想象不出来的,可以在纸上画几张图试试,就能明白其中奥秘了。
如果一步纠正得到的结果中没有正确单词,那么我们有必要进行二步纠正了,也就是循环调用下一步纠正的方法:

# 二次纠正
def know_edit2(word):
    words1 = knomn_edit1(word)
    words2 = knomn_edit1(words1)
    edit2_word = []
    for word2 in words2:
        if word2 in words_map:
            edit2_word.append(word2)
    return edit2_word

下面整合方法:

def correct(word):
    canword = known([word]) or known(knomn_edit1([word])) or know_edit2([word])
    if len(canword) == 0:#如果两步纠正还是没得到正确单词,那么就默认返回输入的错误单词
        canword = [word]
        print(canword)
    else:
        #得到words_map 中value值最大的key
        max_info = max(canword, key=lambda w: words_map[w])
        #打印出找到的使用次数最多的单词和使用次数
        print(str(max_info) + "----P---" + str(words_map[max_info]))

四、测试:

测试一:

word2 = 'misk'
correct(word2)

得到:

miss----P---112

测试二:

word2 = 'lovl'
correct(word2)

得到:

love----P---484

Tips:

代码中所用语料库不是特别大 ,用时基本在毫秒级别,效果还是可以的。
有问题欢迎加群:780239930 ,共同探讨。
代码和数据集已上传github,点击查看,欢迎star、fork

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值