1. 程式人生 > >C#_Markov_心得感想

C#_Markov_心得感想

面向對象 pan 結果 機會 客戶 == bsp foreach循環 概率

來到實驗室正好有一個月了,趁著端午假期稍微輕松一些,在大改程序體系之前,想將自己在這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

C#_Markov_心得感想