C#_Markov_心得感想
来到实验室正好有一个月了,趁着端午假期稍微轻松一些,在大改程序体系之前,想将自己在这30天中工作之一Markov回顾一下,将从真实的写程序中学习到的知识、思想记录下来。希望能和大家积极讨论!
本文会以用C#实现Markov Model为主线,分享自己的感悟。
一、简介Markov
Markov是一种概率模型,最简单的理解是通过大量数据的学习后,可以通过个数为n的词推断出第n+1个词的出现可能。能理解Markov整体思想就可以了。
所以通常将Markov Model的实现分为两个部分:
1、Markov Model数据学习部分
2、根据Markov Model生成部分
二、Markov数据学习
很多人说,当Markov的阶数(Order)变高的时候,如order=5,数据学习部分根本跑不动,为什么?因为Markov要求的是全排列。举个例子,如果学习数据中有5000种词,阶数为5,那么我们有5000的5次方种全排列可能。在存储过程中,因为数据量过大,对内存的一种挑战;在搜索并累积的过程中,如果采用不适当的数据结构,将会消耗大量,超大量时间在搜索中。而这点也就是程序跑不动的原因。
功能的需求就只是个需求,怎么写程序根本是另一回事。很多程序不会按那个需求那么直接的实现,比如说我们现在要全排列,那我们就把全排列的结果全部都存在计算机中吗?我们做不到。我们得通过其他方法来实现这个需求,这就可能会稍微拐点弯啦,但能否灵巧地解决编程难点,看的就是不同程序员的能力高低了。
统计可以得到,全排列中有大量组合是没有值的,会是个稀疏矩阵。所以我只将学习数据中出现的词组合记录下来,这个数据量将远远小于应有的全排列,内存将会有足够的空间存放。在这样的前提下,我第一次使用的List做数据结构来存储,发现程序依然跑的很慢很慢,30mins才跑了50W数据;后来灵机一动用了Dictionary(hash)做数据结构,2mins就跑了300W数据……这真是天壤之别啊!!!这就说明数据结构真是太重要了!!!千万不要小看数据结构啊!!!一定要重视!!!以前学的时候只知道哈希查询效率是O(1),但从没有这么震撼的感触。
话又说回来,为什么Markov要求是全排列,像我这样只把出现的学习一样不可以吗?生成的时候只生成我们学到的内容不可以吗?不可以!因为Markov需要平滑,因为Markov需要考虑所有可能的情况,虽然没学习到的内容出现可能会非常低罢了!
既然有这样的需求,那我们依然需要稀疏矩阵中稀疏部分的值呀,所以我们在生成部分直接求呗,很快的。这也说明了一句古话,“逃得了初一,逃不过十五”,是在一开始我们就将所有稀疏部分的值求好呢,还是需要的时候临时再求呢?这有点像ECC和RSA,一个加密快一个解密快,那就合理使用嘛!服务器若需要做大量解密工作的话,就用RSA做公钥加密算法呗,因为解密快,就会少用点资源。要灵活地看程序真正使用场景的需求,是动态还是静态?是牺牲空间还是牺牲时间?哪有谁就比谁好呢?
这算是Markov学习数据过程中,最大的感触了,其他的就不说了。
三、根据Markov生成数据
这部分给我带来的最大的感触就是,代码要一定要和执行环境挂钩!不要想当然!一切要以程序真正实现为主!必须考虑计算机内存资源的合理分配!!!(这点大家可能都没有遇到过,就不和大家解释了)
为了能让程序正常运行,我们必须合理使用内存,内存的占用、内存的释放、一次使用多大的内存、用什么数据结构存放?我们都要去考虑!
长度太长的时候,内存经常到4个G,程序就报错了。在没有找到简单解决方案后,自己写了一个内存硬盘动态交换数据并支持断续执行的代码,而这个代码也是我非常想逼逼的哈哈。这个环节我直接上代码了,由于是代码片段,所以不是很好理解= =。
//通过循环来生成长度为0-maxlength的数据 for (int i = 0; i < maxlength; i++) { //fragnum数决定着长度相同数据 “分存” 在几个txt中 //随着长度的增大数据数量会非常非常大 //故我们通过存储在多个txt中,可以保证txt文件的正常生成,可以做到 “分段” 跑数据! int fragnum = 0; for (int j = 0; ; j++) { //读取Length为i,Part为j的文件,即长度为i的第j个存储数据的txt文件 string inpath = FileDirectory + "Markov_Length_" + i.ToString() + "_Part_" + j.ToString() + ".txt"; //ListofBase用来临时存放从上面文件中读取到的length-1的pre数据 //在循环内初始化List,可控制内存开销,保证可持续发展 List<string> ListofBase = new List<string>(); Console.WriteLine(i + " " + j); try { //将inpath文件中的basic数据读进ListofBase StreamReader sr = new StreamReader(inpath, Encoding.Default); String line; while ((line = sr.ReadLine()) != null) { Console.WriteLine(line); ListofBase.Add(line); } Console.WriteLine("------------------------------"); //准备将生成的数据存储在长度为length为i+1的数据txt中 //part值由fragnum决定,fragnum在length层面上初始化为0,随后的值将一直伴随 string outpath = FileDirectory + "Markov_Length_" + (i + 1).ToString() + "_Part_" + fragnum.ToString() + ".txt"; FileStream fs = new FileStream(outpath, FileMode.Create); StreamWriter sw = new StreamWriter(fs); //modcount的大小决定从ListofBase中抽取多少个用来生成数据 //同一批数据生成的新数据存放在一个part中,用这个来具体实现对同一长度数据的 “碎片化” 存储 int modcount = 0; //遍历整个ListofBase foreach (var element in ListofBase) { //将modcount控制在(0,499999)之间 modcount = (modcount + 1) % 500000; //若modcount等于499999,则说明foreach循环已经执行了500000次 //通过更改输出txt文件名来分开存储 if (modcount == 499999) { //文件后续处理 sw.Flush(); sw.Close(); fs.Close(); //修改文件名,++fragnum outpath = FileDirectory + "Markov_Length_" + (i + 1).ToString() + "_Part_" + (++fragnum).ToString() + ".txt"; fs = new FileStream(outpath, FileMode.Create); sw = new StreamWriter(fs); } //temp用来存储ListofBase生成的数据 List<string> temp = new List<string>(); //调用ExtendPassword方法,element生成的数据存放在temp中 temp = ExtendPassword(element); //并将temp中数据存放在文件中 foreach (var every in temp) { sw.WriteLine(every); } } //文件后续处理 sw.Flush(); sw.Close(); fs.Close(); } //如果没有读到(i,j)文件,那么判断Length为i的第j+1个部分是不存在的 //跳出循环,读Length为i+1,Part为0的txt文件 catch { break; } } }
现在想想,那些安装程序、补丁程序、下载程序,关闭后还可以找到位置继续执行的功能是不是就是这段代码的特殊版?不过我也不知道那些程序是怎么写的,有机会能接触到就好啦。
不管是因为你自己要写,或是你的上级、你的客户下派了任务,还是出于其他原因,我们的重点都应该放在如何用计算机思维解决现实问题,应先了解计算机的功能,是串行计算还是并行计算?准备用过程性的编程思想还是面向对象的编程思想?既然要写程序,就要用你用的编程语言的思想、你的计算机的逻辑去解决这个问题。能将现实问题转换成巧妙的计算机程序这个能力,不是所有程序员都有的,一定要好好锻炼,路还很长,加油吧!
LIAN
大三下 2017/5/28
作者: 小LiAn
出处: http://www.cnblogs.com/cnlian/>
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(671266128@qq.com)咨询.