从大学毕业到工作的开始几年,一直觉得大学期间学的线性代数,离散数学,概率论简直是浪费时间。

那时候实际做的代码,大部分都是数据进销存。数据输入到数据库介质中的转换,CS,BS架构都写过一些。总觉得现实生活中的逻辑,基本就是柴米油盐那么点东西,根本不需要复杂的算法。最多用点排序算是最给面子了。

真正接触算法的魅力,是在写游戏的时候。那时候写寻路算法,第一次听说A*,不喜欢用别人写好的,于是自己实现了一遍,还真别说,第一次写的很吃力。这些用到的算法,基本都是大学的知识(那时候几乎全还给老师了),才忽然意识到,这些知识原来可以这么用啊。于是开始对之前学的离散数学有了点好感。

后来工作有机会写了一个App Stone的爬虫,那时候Ipone2刚刚开始卖。抓取数据,与之前的数据形成对比,显示软件更新频率,价格曲线图,搜集降价推荐。新出软件,发现在接近50万个软件中,做比较是一件很困难的事情,每天的数据要一一比对,甚至之前几天的历史价格进行比对。以及根据评论量生成对应的数据曲线。才发现自己的智商不够用了。(后来才知道,这个叫做数据挖掘)。当时没有什么趁手的工具,几乎自己实现了一遍,虽然有些地方写的非常笨拙,但是真正的开始注重算法在数据筛选中的运用。

可是到了那时候,我还是没有入算法的门。

直到我开始尝试些一个分词工具,那时候工作需要,需要写一个分词筛选器,自己一直用C++,那时候,没有好的C分词工具,github还没有开始流行,网上的资料也不多。当时Java有Lucene,被Java藐视了,于是一怒之下,给自己立了一个小目标,一个短小精悍的C++分词器,并不是重复造轮子,而是,就是想实现一个更好使的,赌的就是一口气。

当时用了Tire树实现了一个分词器,600多行代码,就是之前的那个帖子所说的那个。

但是还是不能在商业中运用,为什么?新词层出不穷,我真的是无力一点点的去增加词库新词。如果这样的话,别的工作别做了。有没有办法自动识别新词呢?

当时google还在,自己的E文很差,当时有几篇很好的论文被我忽略了,比较可惜。可惜老天爷还是照顾我的,最终被我找到了李开复的一篇关于自然语言的论文。

这里,我知道了,其实中文的词汇库,在没有字典的情况下,也是可以把一个句子切分出来的。

这就是马尔科夫算法。(我一直觉得,这个算法足以和图灵比肩),后来发现,确实这个算法的论文作者和图灵是一个级别的人物。

那么,什么是马尔科夫算法?你可以去知乎,去百度知识搜索。

但是我觉得那些描述,可能把一个简单的事情讲复杂了。实际上它非常的简单好理解。

简单到就在我们的生活中,我们几乎时时刻刻在用,只是你不知道,这个过程实际可以翻译成一个马尔科夫过程。

不相信?那么我举个例子好了。

今天我在看新闻,"由于国际油价上涨,今天晚上24点后,汽油涨4毛钱。"

经常开车的你,在心里问候某些人的时候,肯定第一时间想到了,"不行,要在涨价前去加满油。不过,可能加油站会很多车,要多花点时间排队。"

这就是一个典型的马尔科夫过程。

油价上涨和加油站的车辆多少看上去是完全不同的两个事件,但是在现实生活中确是有着隐形的关联。(其实机器学习并不那么高深,所谓的机器大脑,目前阶段说穿了就一句话,"用概率学解释当前的事件是否可以映射成为另一个事件。)

比如,油价上涨是一个概率事件,那么,与之相对,车辆去排队加油也是一个概率事件。单独的来看,这之间没什么必然联系。然而,这里存在着一个隐形的链条,那就是,我们每次听说油价上涨的时候,路过加油站都能看到车排队。这就是一个隐形的关系。只不过,我们的大脑一瞬间完成了这个关联并得出结论,导致我们本身忽略了思考这个过程是怎么得来的。

再举一个例子。

最近雾霾是一个大问题,害的人人都在戴口罩。你可能会想,又雾霾,肯定高速都封闭了,火车票买不到(请怪12306),汽车票估计也买不到。

这又是一个马尔科夫过程。

雾霾,火车票和汽车票的订票没有直接关系。可以看成独立的事件,但是之间有一层隐形的关系链条。

只不过我们的大脑很厉害,在想这些问题的时候几乎是秒过,而我们常常忽略了其中的过程,为什么会如此。

你可能会问,这些和分词有什么关系?

当然有,分词实际上,就是把以一个个的文字(雾霾,汽油涨价),与词(汽车票难定,加油站排队)映射起来的过程。

好了,我们来看看,如果在没有词库的基础上,我们是怎么做到分词的。

首先,凭空想象肯定还是不行的,我们还是需要一些基础的字典(请注意,这里是字典,不是词典)。

字典里包括什么呢?

我们知道,一个句对应中的文字的位置,可以大致分为4种。

B 字在句的首部(Begin)

M 字在句的中部(Middle)

E 字在句的尾部(End)

S 字不在句中被包含(Single)

其实,还可以继续细化。状态不一定只有4种。可以是6种,比如B B1 M E1 E2 S等等。

我们可以把所有的字和词都总结一下,看看每个字在全部已知语句中的含量。

比如,我让计算机去读一本《红楼梦》,把红楼梦中的所有句子全部拆出来。并全部开始作为语句训练的籽料。

那么,我们会得出类似以下结果,比如:"见"字

(B)见:-7.475730(作为句子的Begin,它出现的概率是7.475730)

(E)见:-6.240821(作为句子的End,它的概率是6.240821)

(M)见:-7.354118(作为句子的Middle,它的概率是7.354118)

(S)见:-6.230461(作为单独成句的概率,它的概率是6.230461)

以一篇文章全部出现文字的概率为100000,那么这些多少就能说明一个字在全部句子中的分量。

如果我让计算机去学习更多的著作,那么理论上,就越接近一个合理的值。

目前,字典的训练,大多以人民日报的文章为准,因为那里的错别字最少,词汇使用最准确。

仔细想想,通过我们的不断的训练,我们得到了一个接近实际使用率的字典概率,这里包含了每个文字的BEMS概率。这是不是一个模糊化的过程?(计算机概率论的核心,就是把一个复杂的事务变的简单,比如无限的句子,抽取每个状态的概率。这个状态是有限的,所以,实际上,我们在做的就是把复杂的问题去掉不关心的枝节,只保留最关心的状态,并记录这个状态可能出现的概率。这就是状态表)

其实这个表隐藏了一个关系,一个稍纵即逝的关系(计算机不像人脑,它是死的,需要你指定这个关系)

(B)见:-7.475730(作为句子的Begin,它出现的概率是7.475730)

(E)见:-6.240821(作为句子的End,它的概率是6.240821)

(M)见:-7.354118(作为句子的Middle,它的概率是7.354118)

(S)见:-6.230461(作为单独成句的概率,它的概率是6.230461)

红字就是隐藏的关系,代表这个概率所对应的模糊化的状态值。

我们还需要什么呢?

字典还需要,所有字(不区分任何文字),在句子中作为头和位的总概率。

对应这样一个矩阵表

B E M S

B -3.14e+100 -0.510825623765990 -0.916290731874155 -3.14e+100

E -0.5897149736854513 -3.14e+100 -3.14e+100 -0.8085250474669937

M -3.14e+100 -0.33344856811948514 -1.2603623820268226 -3.14e+100

S -0.7211965654669841 -3.14e+100 -3.14e+100 -0.6658631448798212

任何一个字,在前面是一个B的时候,后面不可能是B和S,只可能是M和E。以此类推。得到一个状态和状态之间的映射概率关系。

有这个还不行,

还需要最后一个初始关系,也就是说,你阅读过的所有句子,作为第一个字,对应BEMS的概率。

-0.26268660809250016 -3.14e+100 -3.14e+100 -1.4652633398537678

我们可以看到,在这里E和M是-3.14e+100(无穷小,也就是概率为0,句子中的第一个字肯定不可能是中间字和结尾字)

好了,有了这个关系,基础的原料我们就有了。

让我们来造词吧。(C++一共100来行代码实现马尔科夫过程)

void CHmmDict::Viterbi(const char* pData, int nLen, vector<_rune>& objRuneList, vector& objResList)

{

//首先把句子拆成一个个字

Sentance_To_Rune(pData, nLen, objRuneList);

printf("[CHmmDict::Viterbi]objRuneList Count=%d.\n", objRuneList.size());

int nRowCount = (int)objRuneList.size();

int nColCount = (int)RUNE_POS_ALL;

int nMatrixCount = nRowCount * nColCount;

//马尔科夫模型

//组建显式矩阵(字权重矩阵)

vector objWeightMatrix(nMatrixCount);

//组建隐式矩阵(字状态矩阵)

vector objStatusMatrix(nMatrixCount);

//显式矩阵第一行,第一个字初始状态的概率,对应BEMS

//第一个字的概率为 初始概率 + 字的初始概率

for (int nCol = 0; nCol < nColCount; nCol++)

{

if(nCol == RUNE_POS_B)

{

objWeightMatrix[0 + nCol * nRowCount] = m_dbStart[nCol] + Get_Rune_Prob(objRuneList[0], m_hashMapB);

objStatusMatrix[0 + nCol * nRowCount] = -1;

}

else if(nCol == RUNE_POS_E)

{

objWeightMatrix[0 + nCol * nRowCount] = m_dbStart[nCol] + Get_Rune_Prob(objRuneList[0], m_hashMapE);

objStatusMatrix[0 + nCol * nRowCount] = -1;

}

else if(nCol == RUNE_POS_M)

{

objWeightMatrix[0 + nCol * nRowCount] = m_dbStart[nCol] + Get_Rune_Prob(objRuneList[0], m_hashMapM);

objStatusMatrix[0 + nCol * nRowCount] = -1;

}

else

{

objWeightMatrix[0 + nCol * nRowCount] = m_dbStart[nCol] + Get_Rune_Prob(objRuneList[0], m_hashMapS);

objStatusMatrix[0 + nCol * nRowCount] = -1;

}

}

//填充矩阵的其余部分(按字的顺序)

for(int nRow = 1; nRow < nRowCount; nRow++)

{

for (int nCol = 0; nCol < nColCount; nCol++)

{

int nCurrPos = nRow + nCol*nRowCount;

objWeightMatrix[nCurrPos] = MIN_DOUBLE;

objStatusMatrix[nCurrPos] = RUNE_POS_E;

double dbCurrProb = MIN_DOUBLE;

if(nCol == RUNE_POS_B)

{

dbCurrProb = Get_Rune_Prob(objRuneList[nRow], m_hashMapB);

}

else if(nCol == RUNE_POS_E)

{

dbCurrProb = Get_Rune_Prob(objRuneList[nRow], m_hashMapE);

}

else if(nCol == RUNE_POS_M)

{

dbCurrProb = Get_Rune_Prob(objRuneList[nRow], m_hashMapM);

}

else

{

dbCurrProb = Get_Rune_Prob(objRuneList[nRow], m_hashMapS);

}

//寻找和上一个字的对应关系,取概率最高的关系作为当前概率(算法核心)

for (int nPreCol = 0; nPreCol < nColCount; nPreCol++)

{

int nOldPos = nRow - 1 + nPreCol * nRowCount;

double dbTemp = objWeightMatrix[nOldPos] + m_dbTransProb[nCol + nPreCol*nColCount] + dbCurrProb;

if (dbTemp > objWeightMatrix[nCurrPos])

{

objWeightMatrix[nCurrPos] = dbTemp;

objStatusMatrix[nCurrPos] = nPreCol;

}

}

}

}

//打印隐式矩阵

/*

printf("=============(Status Matrix)=================\n");

for(int nRow = 0; nRow < nRowCount; nRow++)

{

for (int nCol = 0; nCol < nColCount; nCol++)

{

int nCurrPos = nRow + nCol*nRowCount;

printf("%d ", objStatusMatrix[nCurrPos]);

}

printf("\n");

}

printf("=============(Status Matrix)=================\n");

*/

//获得最后末尾的S和E,因为末尾的字只可能是这两个状态之一

double dbEndE = objWeightMatrix[nRowCount - 1 + RUNE_POS_E*nRowCount];

double dbEndS = objWeightMatrix[nRowCount - 1 + RUNE_POS_S*nRowCount];

short sStat = RUNE_POS_E;

if(dbEndE < dbEndS)

{

sStat = RUNE_POS_S;

}

objResList.resize(nRowCount);

for(int i = nRowCount - 1; i >= 0; i--)

{

objResList[i] = sStat;

sStat = objStatusMatrix[i + sStat*nRowCount];

}

}

这里的核心思想就是

我先把一个语句拆成若干个字(因为计算机中,中文并不是占据1个字节,在GBK下是2个字节,在utf8下是3个字节)

然后根据每个字,生成如下的矩阵(比如句子是"哪里见过你朋友")

B E M S

在这里,每个字,都根据和上一个字的BEMS关系,和上一个字的概率叠加,得出最可能存在的概率表,比如上一个是B的概率最大,后面肯定是M或者是E,不可能是S。

同时,得到一个隐式的矩阵

=============(Status Matrix)=================

-1 -1 -1 -1

3 0 0 3

1 2 2 1

3 0 0 3

3 0 0 3

3 0 0 3

3 0 0 3

=============(Status Matrix)=================

这个矩阵是对应上面的概率矩阵,最高概率关系对应的BEMS状态。

通过这个矩阵,我们用维斯比算法求最短路径,即可得到句子的分词。

切分结果是:

哪里见过你朋友

哪里/见/过/你/朋友/

看,在没有词库的基础上,我们用马尔科夫过程,一样可以得到词组。(实际上是可能成为词组的最高概率),实际上,这些代码和Tire的词典可以结合使用,Tire无法分词的句子用HMM即可继续分词,生成的新词,可以进入Tire词库,如此循环。就形成了一个可以自动识别新词并添加新词的词库,不需要人工干预的动态分词AI。

其实很多分词是这么做的,百度最开始也是这么做的,不到1000行代码。

只不过,为了更精确,百度会每天用一些语料去训练字典,并把训练好的字典替换到正式环境,因为读的文章越多,切词就越准确,根本不怕有什么新词出现。

其实马尔科夫模型是AI的基础,80%的AI底层都和这个算法先关,为什么用分词去说呢?因为这个最能直接表达马尔科夫过程。实际上,不只是分词,阿里巴巴那些技术专家说的"用户画像","消费习惯关联","产品联想"的AI,实际上也都是以这个为基础的,并不是什么神秘的东西,其实一通百通。

想玩?还是我的github,去拿下来自己玩个够吧。

freeeyes/fenci

最近AlphaGo 60连胜(限定快棋,30秒必须落子),打败了包括柯洁聂卫平等在内的一票围棋高手,所有人都觉得不可思议。很多人觉得人工智能已经超越了人类。但是实际上,它还是有弱点的。几千年的围棋,定式在一定程度上影响了现代的大师们。定式本身是一种概率运算体系。而在围棋上,所谓AI的大局观,只是在一个巨大的Hash散列(361个棋子)上求平均概率最大的事件。而围棋大师还不适应这种思维方式而已(定式是在局部求的最优解)。我觉得,只要人们学会了在前50个子能够找到对付Hash AI的方法去思考模式,后期计算机AI就没有什么机会了,主要AlphaGo赢在前50个子的思维模式。

那么下一贴讲什么呢?我们来看看更刺激的,用户画像是怎么做的吧。

也就是,把一个用户行为归类为几个行为方向。(聚类),光这样还不行,我们必须要解决"像"这个问题,也就是说,我们要把一批看似毫不相干的行为,归类为一个大类。这部分,也用到了马尔科夫模型。很有趣哦。

python写一个类600行代码_带你领略算法的魅力,一个600行代码的分词功能实现(二)...相关推荐

  1. python写一个类600行代码_带你领略算法的魅力,一个600行代码的分词功能实现(一)...

    为什么要说分词呢?其实这个话题挺大的.所以准备分几篇来写,这次先写第一篇. 写给别人看,也写给自己.毕竟,自己在思特奇也做了好久了,写点有意思的东西,结交一些有兴趣的朋友. 一是确实最近的一些实践给了 ...

  2. python写梦幻西游手游脚本辅助_深入解析Lua脚本加密技术,给游戏代码加上“紧箍咒”...

    不少安全专家表示,在互联网上失去对代码的控制,就像把银行的设计图交给抢劫犯一样. Lua是一种被广泛用于游戏开发中的计算机语言,方便开发者定制自己所需的功能.其中,红遍全球的<愤怒的小鸟> ...

  3. Python正则表达式工具类文件的封装实例,提供了多个实例,并且在代码中包含中文注释

    Python正则表达式工具类文件的封装实例,提供了多个实例,并且在代码中包含中文注释 import reclass RegexUtils:'''正则表达式工具类'''def __init__(self ...

  4. html旋转代码_付费?是不可能的!20行Python代码实现一款永久免费PDF编辑工具

    PDF(Portable Document Format),中文名称便携文档格式是我们经常会接触到的一种文件格式,文献.文档...很多都是PDF格式.它以格式稳定的优势,使得我们在打印.分享.传输过程 ...

  5. python写csv文件按升序排列_用python给csv里的数据排序的具体代码

    1.使用argparse组件,获取命令行参数:使用re组件,获取需要查找的字符串所在行 2.使用pandas组件,对文件进行排序. 3.命令行执行数据获取及排序,写入文件: 以下是完整代码: #cod ...

  6. python中的类是什么意思_如何理解python中的类和方法(转)

    一.python中类和对象的概念 首先,我们先来说说什么是类.看了很多关于python类的介绍,大多都介绍如何使用,但是对于概念却一笔带过,一个初学编程的小伙伴很难理解. 概括的说:类可以比作是某种类 ...

  7. .net new一个类为什么报空指针_谈谈.NET对象生命周期

    不用程序员操心的堆 - 托管堆 程序在计算机上跑着,就难免会占用内存资源来存储在程序运行过程中的数据,我们按照内存资源的存取方式将内存划分为堆内存和栈内存. 栈内存通常使用的场景是:对存取速度要求较高 ...

  8. python年份天干地支代码_农历天干地支算法源代码大全(javascript、vbscript、C#、flash、C++、C等等【转】...

    文章提供计算农历天干地支及当年属相的算法源程序,使用的语言为Javascript.VBScript.C#等. 一.C# 代码(1): 原来还准备自己写算法,并研究农历规则.发现那太难和麻烦了,光是农历 ...

  9. python在规划类专业的作用_城乡规划学Python、Gis有哪些具体的作用?

    感觉已经回答过很多遍类似的问题了,但是还是很开心你Python和GIS都提到了,请允许我,粘贴一点,自己再写一点. 这两个的确用处很大,但都是,用到了用处很大.你如果不会他们,估计一直用不上,也只是一 ...

最新文章

  1. 通过Android重审GET和POST请求
  2. 容器化Spring Data Cassandra应用程序
  3. surface php老是用不了,surface pro7触摸屏没反应怎么办
  4. 2020快手移动游戏行业玩家数据价值报告
  5. 如何需求分析和编写测试用例
  6. 安装iis 出现ASP无法访问的解决方法
  7. 网站未备案不能访问,怎么用ip加端口的方式建站?
  8. 双方确认函_影片份额转让合同约定第一出品方出具确认函后合同生效,未出具而受让方支付投资款的,合同也生效...
  9. 数据结构题集C语言版严蔚敏
  10. Java中汉字生成拼音首拼和五笔码实例
  11. 项目从 tomcat7部署到tomcat8
  12. 02初尝有限元分析——悬臂梁案例
  13. matlab 用m_map画地形水深图
  14. hyperledger java_hyperledger fabric 1.4 使用java开发智能合约
  15. 打官司除了找律师,还能找谁?
  16. 凸优化第三章凸函数 3.3共轭函数
  17. ubuntu18.04装coturn
  18. 红楼梦人物出场统计python_Python程序设计习题3——红楼梦人物出场次数统计
  19. 【2022版】Java多线程与高并发面试题总结,108道题含答案解析。
  20. 有什么方法判断网站后台是用什么语言写的

热门文章

  1. 学会“放弃与妥协”,学会“自我牺牲”,学会“善待痛苦”
  2. scatter函数绘制散点图——MATLAB
  3. 10 3在c语言中的意思,维生素c3十是什么意思
  4. Arcgis学习笔记(初级到精通)
  5. linux升级mysql
  6. iis安装出现“当前标识没有对“C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files”的写访问权限”
  7. 网络、互联网、因特网的基本概念与组成
  8. TP5学习(四):控制器
  9. python中print函数的用法_Python中print函数使用方法
  10. 人身保险的误区(一)