为什么要说分词呢?其实这个话题挺大的。所以准备分几篇来写,这次先写第一篇。

写给别人看,也写给自己。毕竟,自己在思特奇也做了好久了,写点有意思的东西,结交一些有兴趣的朋友。

一是确实最近的一些实践给了自己很大的启发,另一方面,所谓互联网下半场的来临,那么,如果从一个增量的系统到一个存量的系统,那么数据挖掘就显得尤为重要了。怎么挖掘?或者说,怎么让计算机去帮我们挖掘呢?

不可否认,网上的一些大数据学习的资料,是翻译过来的,因为个人水平的不同,有时候很容易把人带到沟里去。尤其在牵扯到了一些算法的解释,更是莫名其妙,让不少初学者往往觉得这门课太难了。实际上,我可以说,它非常简单。简单到你几乎可以从你的生活中去恍然大悟。

经过持续的沉淀,我觉得从分词来切入是非常合适的。(这里特别感谢billing的王金山,感谢你的帮助)

我并不是想重复造轮子,而是通过造轮子,来解释一些基本的现象,以及一些算法为什么要这么用。如果你想做的更好,不停留在被动程序员的层次,那么希望你有兴趣读一下,或许对你有帮助,当然,也希望拍砖。

最近和一个朋友聊天,聊到了机器学习,说到大数据的应用,他说,网上说的这些名词,大多数都太空泛了。真正做到实践中去的,也就是几个大公司而已,中小公司基本没有这个实力。个人看数据挖掘,除了一些工具套路,实际上你真的会大数据吗?实际上,就算在大公司,目前的数据挖掘,也往往是刚起步而已。

和我说话的这个人,是今日头条的算法专家。

确实,怎么说是刚起步呢?

其实机器学习并不那么高深,所谓的机器大脑,目前阶段说穿了就一句话,"用概率学解释当前的事件是否可以映射成为另一个事件。

那么,这句话怎么理解?无论是什么学习,80%都离不开两个算法(实际是一个,另一个是这个的扩展),马尔科夫模型和贝叶斯算法。只要你能够沉淀一些这两个算法的知识和实践,当今大多开源机器学习,归类算法,推荐算法你都能玩的游刃有余。因为这些大部分都是以这两个为基础的变种。

什么东西都必须实践才能有沉淀,我一直秉持的这个观念。

分词和自然语言语音识别,现在取得了巨大的成就,基本上能把误差缩小到5%以内。甚至口音,方言,对于自然语言识别已经不是问题。分词而言,模糊的错误分词,对于识别也不再是难事。靠的是什么,靠的就是马科莫夫和贝叶斯。所以,我想从分词的角度,来阐释概率是如何计算出来的,以及显示和隐式到底是什么意思。

这不是什么了不得的技术,你我一样能轻松掌握,让我们从底层一步步来看看它是怎么实现的。

首先,要分词,先要有词库(废话)。

我们先不管词库是怎么来的(下几个帖子我会详细的解释,如何通过训练材料获得词库)。反正不是你一个个词敲击进去得到的。这样的方法简直是不可想象的。当然,你也可以这么做,不过你要花费很长的时间,还未必能达到你要的结果。现在,每天都会发生新的词汇,比如说"十动然拒"。你手动录入肯定是跟不上的。

一个真正的词库,实际一个txt足矣。

我的字库的组织方式是

一万条 4 m

一万次 16 m

一万步 30 m

一万盏 4 m

一万种 2 m

一万贯 4 m

一万遍 9 m

一万里 4 m

一万间 5 m

一行包含了三个元素,第一个是词本身,第二个是词在训练资料里出现的频率(词频),最后一个是词性标注(名词,动词,形容词,介词等等)

后两个参数我们先不用管(以后我会详细解释,为什么词典里需要后面两个参数,这两个参数是用于提炼一个句子中的关键词而是用的,也就是抽象一个句子的含义,我们需要后面两个参数),先以最简单的,只是单词为准。

我们有了词典,这里面大概有38万个中文词组。(目前以2016年6月份为准的中文词汇统计,差不多就这么多)

那么我们如何加载到内存里去效率最高呢?

或许,你会想,最简单的办法,给定一个句子,

比如:

"哪里见过你呀,朋友"

我可以for 38万次,用字符串比较看看是否每个词在这个句子里是包含的,直接用strstr()搞定。

是的,这样的办法很暴力,在服务器上,或许你还行,在一些运行效能低一些的机器上,可能就不行了。

有没有更好的办法?做到最高的判断效率?

当然有的,这就是算法的魅力了,先看看,我们怎么组织词典数据?

我们可以以树的形式记录词典,那么,怎么把38万个词变成一棵树呢?

我们知道,每个词,无论长短,它都是由一个个中文字符组成,我们把这个字作为一个个单独的关联元素。

那么我可以这样的组织数据

b比如,这几个词,"你" "你们" "你们的" "你的" "我" "他"

我们可以用上述方法无限类推,把38万的词,转换为一棵树。

这棵树和一般的树有些不一样,为了提高检索效率,我们必须把树种的子节点,变换为一个hash数组,这样,在给定任何一个字的基础上,我们都能快速获得这个字在所在层级的子节点位置。

我们可以给出节点的设定

//永久节点模型

struct _RuneLinkNode

{

CHashTable m_hmapRuneNextMap;

_Rune m_objRune;

char m_pWord[MAX_WORD_LENGTH];

int m_nPoolIndex;

char m_cUsed; //0为未使用,1为使用

};

最后一个叶子节点,也就是一个词的末尾,必须记录整个链条上的完整词汇,也就是m_pWord,便于抽取。过程节点,如果不是一个完整的词汇,则不必在记录这个词汇。

那么,给定一个句子,如可快读的遍历这棵树呢?

其实很简单,就和小孩子的积木形状填空游戏毫无二致。

还是那句话 "哪里见过你呀,朋友"

我们可以循环从这个字的第一个字开始,在树上寻找,如果找到这个字(哪),再获取这个字的下一个字(里),在这个字(哪)的下一个hashmap数组里去寻找(里),如果这个字(里)还有子节点,那么继续在这个子节点去寻找下一个字(见),直到找不到为止。

看上去可能有点绕口,那么程序上怎么实现呢?每次需要记录上一个字的位置,很烦的,因为我根本不知道一个词会有多少个字,比如"南无阿弥佗佛妈咪妈咪哄"这个词,我要记录多少次呀?是的,看上去很烦,但是,记得程序中有一个叫做递归的东西不?我们可以用一个递归函数,34行解决这个问题。

_RuneLinkNode* CWordBase::Set_HashMap_Word_Tree(_RuneLinkNode* pRuneNode, _Rune* pRune, int nLayer)

{

int nOfficeSet = pRuneNode->m_hmapRuneNextMap.Get_Hash_Box_Data((char* )pRune->m_szRune);

if(nOfficeSet > 0)

{

_RuneLinkNode* pCurrRuneNode = m_objNodePool.Get_NodeOffset_Ptr(nOfficeSet);

if(pCurrRuneNode->m_objRune == (*pRune))

{

//如果找到了,则返回当前节点

return pCurrRuneNode;

}

}

//如果没找到,则创建新的

_RuneLinkNode* pNode = m_objNodePool.Create(nLayer);

//printf("[CWordBase::Set_HashMap_Word_Tree]pNode=0x%08x.\n", pNode);

if(NULL == pNode)

{

printf("[CWordBase::Set_HashMap_Word_Tree]node pool is empty.\n");

return NULL;

}

int nNodeOffset = m_objNodePool.Get_Node_Offset(pNode);

pNode->m_objRune = (*pRune);

int nPos = pRuneNode->m_hmapRuneNextMap.Add_Hash_Data((char* )pRune->m_szRune, nNodeOffset);

if(-1 == nPos)

{

printf("[CWordBase::Set_HashMap_Word_Tree]tree node child is full.\n");

m_objNodePool.Delete(pNode);

return NULL;

}

return pNode;

}

这样,我们只需要按照肉眼的方式,读一遍句子,所有的词就被切分出来了。因为每层树的子节点都是hashmap,所以词的命中几乎和你读一遍句子for循环的次数是一致的,也就是说,"哪里见过你呀,朋友" 9个字我只需要9次循环就得到了全部的分词,比38万次强多了吧。分词结果是

[Cut]/哪里/见过/你/呀/,/朋友/

你看,机械分词就这么简单。那么,你可能会问。

分词是简单了,但是加载38万的词库,要消耗不少时间吧。

是的,38万个词,形成树,在我的测试服务器上,要消耗40秒左右。

我不可能为没次加载都付出40秒,启动太慢了,尤其在程序core掉的时候,有没有办法达到0秒启动呢?

当然,借助共享内存,我们可以把一棵树完整的压到共享内存中去,这样,当程序重启的时候,我只要得到共享内存的首地址,整个树就会瞬间还原了。怎么做呢?

树的主节点,可以是共享内存的开始节点。后面的叶子节点中的数据,我们不在用Node指针记录,直接用相对主节点的偏移量记录即可。使用的时候,我根据主节点的内存地址+偏移长度,直接还原成当前指针就可以了。所有的节点,都使用pool内存池的方式存储,用到添加节点的时候,从pool里面取得就行。

整个机械分词法+共享内存,也就500来行代码就搞定了,有兴趣可以看看我的实现。

完整代码,请看我的

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

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

    从大学毕业到工作的开始几年,一直觉得大学期间学的线性代数,离散数学,概率论简直是浪费时间. 那时候实际做的代码,大部分都是数据进销存.数据输入到数据库介质中的转换,CS,BS架构都写过一些.总觉得现实 ...

  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. 【FAQ】使用 LOAD 載入外部中文字檔 *.TXT, 中文字卻成為亂碼之解決
  2. Andriod:安卓线程实现页面的自动跳转
  3. vim 有用命令-20190217
  4. 如何读取jar包外的properties文件和log4j.properties
  5. .NET Core 如何调试 CPU 爆高?
  6. 为什么电脑不能打字_为什么不能用电脑验光仪测出来的度数直接配眼镜?
  7. memcached 如果进程占用cpu很高
  8. php发表图片文章代码,PHP实现发表文章时自动保存图片_php
  9. Python定时任务轻量解决方案---Schedule
  10. 【算法】剑指 Offer 30. 包含min函数的栈
  11. Java将每半年发布一个版本
  12. 设计模式--spring源码中使用策略模式(Strategy Pattern)
  13. confluence 编辑器这次没有加载_喵的Unity游戏开发之路 - 多场景:场景加载
  14. 谷歌卫星地图下载助手
  15. 和利时服务器通信协议,和利时网关UDP通信协议.pdf
  16. SATA学习笔记 1 --- ATA、IDE、ATAPI、SCSI、SATA、SAS等概念澄清
  17. JavaScript之Ajax(一篇入门Ajax就够了)
  18. 十分钟玩转3D绘图:WxGL完全手册(第二版)
  19. java 两张图片合成
  20. 解决 from scipy.misc import comb ImportError: cannot import name ‘comb‘ 问题

热门文章

  1. OpenFeign 的 9 个坑,每个都能让你的系统奔溃
  2. JavaScript 霸榜、TypeScript 爆发、开源吞噬世界,GitHub 年度报告正式发布!
  3. 虾米回应“关闭”消息:不予置评;明年 Win 10 或将原生运行安卓应用;Perl 项目治理新规| 极客头条...
  4. 危险的转变:Python正在从简明转向臃肿,从实用转向媚俗
  5. 你越努力,编程水平越差!这样学 Python ,更容易成为高手!
  6. 程序员的基本功:为什么非要用 Python 做数据分析?Excel 不好吗?
  7. ​新冠疫情给 CTO 们带来的几点启示
  8. 雷军亲曝小米 10 四大猛料!
  9. 如何走出物联网死亡之井?
  10. 2019 年被“杀”死的那些技术!