基於統計的無詞典的高頻詞抽取(三)——子串歸並


由於最近換了工作,需要熟悉新的工作環境,工作內容也比較多,所以一直沒有更新文章,趁着今晚有空,就繼續寫寫這系列的文章。

前面兩篇,我們已經實現了后綴數組的排序,高頻字串的抽取,也初有成效,如下圖:

接下來,我們就繼續對結果進行進一步的精確化,使用子串歸並來實現:

首先,我先舉一個可能不大適合的例子來大概解釋一下什么叫做子串歸並。假設,某個語料庫中,統計到“你”出現了100次,而“你好”也剛好出現了100次,那么,我們舍棄“你”這個結果,保留“你好”;我們為什么這樣做呢?從這個簡單的例子可以看出,出現“你”子的時候,一定會出現“你好”,那么根據成詞的規則,我們保存長的子串(一般來說,子串選取長度在[2,4]這個區間內)。

好了,現在,我們再從上面的規則中進行加深:

① 我們先限定抽取串的長度在[2,4]中,以避免抽取過長的字符串;

② 當字符串S被抽取(其中S的長度為L,L≥2,S出現的次數為 t),且S的后綴sfx(i)(其中i∈(0,L))在文本中出現的次數也等於t時(S的后綴出現的次數可能大於t,但不會小於t,這是很好理解的,例如,“中國人”出現次數為10,則其一個后綴“國人”僅在這個字符串中出現的次數就為10,而假設還有一個字符串“美國人”出現5次,那么“國人”這個字符串就出現了15次,大於10),S的后綴sfx(i)將不再被抽取;

③ 當然僅僅當母串的次數等於子串次數作為歸並的條件,這限定太過苛刻,我們將可以調整歸並的策略,引入閥值t,t根據語料庫的大小進行調整(t取[0.1,0.3]較為合理;語料庫越大時,應適當調低;文本越小時,應適當調高);假設字符串S1是S2的前綴,S1出現的次數t1,S2出現的次數為t2。當(t- t)/t2 <t 時,舍棄,S1不作高頻串抽取;

我們將過程進行細化和限定,處理過程看起來稍微合理,下面,我使用C#將此過程轉換為程序語言(注,由於時間關系,該代碼未做任何優化,性能欠佳,大家可以對代碼進行優化或自己實現)

 1 public static void RemoveSubString(List<StringFrequency> stringFrequency, string str, int[] pat, int[] lcp)
 2 {
 3     var _STACK = new Stack<StringFrequency>();
 4     var _PSTACK = new Stack<int>();
 5     var isContinu = true;
 6     var p = 0;
 7     var count = stringFrequency.Count();
 8     for (var i = 0; i < count; i++)
 9     {
10         isContinu = _PSTACK.Contains(i) ? false : true;
11         if (isContinu)
12         {
13             p = i;
14             var tmp = str.Substring(pat[stringFrequency[i].Position], lcp[stringFrequency[i].Position]);
15             for (var j = i + 1; j < count; j++)
16             {
17                 if (!_PSTACK.Contains(j))
18                 {
19                     var cur = str.Substring(pat[stringFrequency[j].Position], lcp[stringFrequency[j].Position]);
20                     if (Convert.ToSingle((stringFrequency[i].Times - stringFrequency[j].Times) / stringFrequency[j].Times)>0.3f)
21                         break;
22                     if (tmp.Contains(cur))
23                     {
24                         _STACK.Push(stringFrequency[j]);
25                         _PSTACK.Push(j);
26                     }
27                     else if (cur.Contains(tmp))
28                     {
29                         tmp = cur;
30                         _STACK.Push(stringFrequency[i]);
31                         _PSTACK.Push(i);
32                     }
33                 }
34             }
35         }
36     }
37     while (_STACK.Count() > 0)
38     {
39         stringFrequency.Remove(_STACK.Pop());
40     }
41 }

上面代碼指向前,我們需要使用一個根據出現次數排序的 List<StringFrequency> stringFrequency 集合:

1 var sortList = stringFquency.OrderByDescending(x => x.Times).ToList();
2 //str為語料庫,a為字符串的后綴數組,lcpList為LCP掃描結果,詳見上一篇
3 RemoveSubString(sortList, str, a, lcpList);

如果不出所料,上面的代碼執行效率是不盡人意的,即使上面做了一定程度上的減少掃描次數,但是大體上的時間復雜度要達O(n2),效率奇低;而如果使用基於散列表的算法(將所有重復串及其頻次存放於散列表中,然后依次去判斷表中的每一項的所有可能子串是否存在,存在且頻次相同,則歸並,理論上可達O(n),但是次算法空間開銷太大,容易造成內存泄露,大家可以參考《基於散列的子串歸並算法》這篇論文)。。。我們可以結合上一篇文章中掃描LCP的過程,並在此過程中完成子串歸並的動作;這篇文章僅僅描述子串歸並的原理,此處就不再詳述解決的過程;

好了,第三部分就先講到這里,如果覺得文章對您有用或者對其他人有幫助,請幫忙點文章下面的“推薦”;如果文章有任何紕漏,歡迎指正,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM