文本特徵提取

文本挖掘模型結構示意圖


1. 分詞

分詞實例:
       提高人民生活水平:提高、高人、人民、民生、生活、活水、水平
分詞基本方法:
        最大匹配法、最大概率法分詞、最短路徑分詞方法

1.1 最大匹配法

        中文分詞在中文信息處理中是最最基礎的,無論機器翻譯亦或信息檢索還是其他相關應用,如果涉及中文,都離不開中文分詞,因此中文分詞具有極高的地位。正向最大匹配法算法如下圖:


實例:S1="計算語言學課程是三個課時",設定最大詞長MaxLen= 5,S2= " "
(1)S2=“”;S1不爲空,從S1左邊取出候選子串W="計算語言學";
(2)查詞表,“計算語言學”在詞表中,將W加入到S2中,S2=“計算語言學/ ”,並將W從S1中去掉,此時S1="課程是三個課時";
(3)S1不爲空,於是從S1左邊取出候選子串W="課程是三個";
(4)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="課程是三";
(5)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="課程是";
(6)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="課程"
(7)查詞表,W在詞表中,將W加入到S2中,S2=“計算語言學/ 課程/ ”,並將W從S1中去掉,此時S1="是三個課時";
(8)S1不爲空,於是從S1左邊取出候選子串W="是三個課時";
(9)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="是三個課";
(10)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="是三個";
(11)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W="是三"
(12)查詞表,W不在詞表中,將W最右邊一個字去掉,得到W=“是”,這時W是單字,將W加入到S2中,S2=“計算語言學/ 課程/ 是/ ”,並將W從S1中去掉,此時S1="三個課時";
。。。。。。
。。。。。。
(21)S2=“計算語言學/ 課程/ 是/ 三/ 個/ 課時/ ”,此時S1=""。
(22)S1爲空,輸出S2作爲分詞結果,分詞過程結束。
代碼如下:

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <hash_map>
using namespace std;
using namespace stdext;

class CDictionary
{
public:
	CDictionary(); //將詞典文件讀入並構造爲一個哈希詞典
	~CDictionary();
	int FindWord(string w); //在哈希詞典中查找詞
private:
	string strtmp; //讀取詞典的每一行
	string word; //保存每個詞
	hash_map<string, int> wordhash; // 用於讀取詞典後的哈希
	hash_map<string, int >::iterator worditer; //
	typedef pair<string, int> sipair;
};

//將詞典文件讀入並構造爲一個哈希詞典
CDictionary::CDictionary()
{
	ifstream infile("wordlexicon"); // 打開詞典
	if (!infile.is_open()) // 打開詞典失敗則退出程序
	{
		cerr << "Unable to open input file: " << "wordlexicon"
			<< " -- bailing out!" << endl;
		exit(-1);
	}
	while (getline(infile, strtmp, 'n')) // 讀入詞典的每一行並將其添加入哈希中
	{
		istringstream istr(strtmp);
		istr >> word; //讀入每行第一個詞
		wordhash.insert(sipair(word, 1)); //插入到哈希中
	}
}

CDictionary::~CDictionary()
{
}

//在哈希詞典中查找詞,若找到,則返回,否則返回
int CDictionary::FindWord(string w)
{
	if (wordhash.find(w) != wordhash.end())
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

#define MaxWordLength 10 // 最大詞長爲個字節(即個漢字)
#define Separator "/ " // 詞界標記

CDictionary WordDic; //初始化一個詞典

//對字符串用最大匹配法(正向或逆向)處理
string SegmentSentence(string s1)
{
	string s2 = ""; //用s2存放分詞結果
	while(!s1.empty())
	{
		int len =(int) s1.length(); // 取輸入串長度
		if (len > MaxWordLength) // 如果輸入串長度大於最大詞長
		{
			len = MaxWordLength; // 只在最大詞長範圍內進行處理
		}
		//string w = s1.substr(0, len); // (正向用)將輸入串左邊等於最大詞長長度串取出作爲候選詞
		string w = s1.substr(s1.length() - len, len); //逆向用
		int n = WordDic.FindWord(w); // 在詞典中查找相應的詞
		while(len > 2 && n == 0) // 如果不是詞
		{
			len -= 2; // 從候選詞右邊減掉一個漢字,將剩下的部分作爲候選詞
			//w = w.substr(0, len); //正向用
			w = s1.substr(s1.length() - len, len); //逆向用
			n = WordDic.FindWord(w);
		}
		//s2 += w + Separator; // (正向用)將匹配得到的詞連同詞界標記加到輸出串末尾
		w = w + Separator; // (逆向用)
		s2 = w + s2 ; // (逆向用)
		//s1 = s1.substr(w.length(), s1.length()); //(正向用)從s1-w處開始
		s1 = s1.substr(0, s1.length() - len); // (逆向用)
	}
	return s2;
}

//對句子進行最大匹配法處理,包含對特殊字符的處理
string SegmentSentenceMM (string s1)
{
	string s2 = ""; //用s2存放分詞結果
	int i;
	int dd;
	while(!s1.empty() )
	{
		unsigned char ch = (unsigned char)s1[0];
		if (ch < 128) // 處理西文字符
		{
			i = 1;
			dd = (int)s1.length();
			while (i < dd && ((unsigned char)s1[i] < 128) && (s1[i] != 10) && (s1[i] != 13)) // s1[i]不能是換行符或回車符
			{
				i++;
			}
			if ((ch != 32) && (ch != 10) && (ch != 13)) // 如果不是西文空格或換行或回車符
			{
				s2 += s1.substr(0,i) + Separator;
			}
			else
			{
				//if (ch == 10 || ch == 13) // 如果是換行或回車符,將它拷貝給s2輸出
				if (ch == 10 || ch == 13 || ch == 32) //謝謝讀者mces89的指正
				{
					s2 += s1.substr(0, i);
				}
			}
			s1 = s1.substr(i,dd);
			continue;
		}
		else
		{
			if (ch < 176) // 中文標點等非漢字字符
			{
				i = 0;
				dd = (int)s1.length();
				while(i < dd && ((unsigned char)s1[i] < 176) && ((unsigned char)s1[i] >= 161)
					&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 162 && (unsigned char)s1[i+1] <= 168)))
					&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 171 && (unsigned char)s1[i+1] <= 191)))
					&& (!((unsigned char)s1[i] == 163 && ((unsigned char)s1[i+1] == 172 || (unsigned char)s1[i+1] == 161)
					|| (unsigned char)s1[i+1] == 168 || (unsigned char)s1[i+1] == 169 || (unsigned char)s1[i+1] == 186
					|| (unsigned char)s1[i+1] == 187 || (unsigned char)s1[i+1] == 191)))
				{
					i = i + 2; // 假定沒有半個漢字
				}
				if (i == 0)
				{
					i = i + 2;
				}
				if (!(ch == 161 && (unsigned char)s1[1] == 161)) // 不處理中文空格
				{
					s2+=s1.substr(0, i) + Separator; // 其他的非漢字雙字節字符可能連續輸出
				}
				s1 = s1.substr(i, dd);
				continue;
			}
		}
		// 以下處理漢字串
		i = 2;
		dd = (int)s1.length();
		while(i < dd && (unsigned char)s1[i] >= 176)
		{
			i += 2;
		}
		s2 += SegmentSentence(s1.substr(0, i));
		s1 = s1.substr(i,dd);
	}
	return s2;
}

int main(int argc, char *argv[])
{
	string strtmp; //用於保存從語料庫中讀入的每一行
	string line; //用於輸出每一行的結果
	ifstream infile(argv[1]); // 打開輸入文件
	if (!infile.is_open()) // 打開輸入文件失敗則退出程序
	{
		cerr << "Unable to open input file: " << argv[1]
			<< " -- bailing out!" << endl;
		exit(-1);
	}
	ofstream outfile1("SegmentResult.txt"); //確定輸出文件
	if (!outfile1.is_open())
	{
		cerr << "Unable to open file:SegmentResult.txt"
			<< "--bailing out!" << endl;
		exit(-1);
	}
	while (getline(infile, strtmp, 'n')) //讀入語料庫中的每一行並用最大匹配法處理
	{
		line = strtmp;
		line = SegmentSentenceMM(line); // 調用分詞函數進行分詞處理
		outfile1 << line << endl; // 將分詞結果寫入目標文件
	}
	return 0;
}

其它基於匹配的分詞方法:
        最大匹配法(Maximum Matching method):匹配的方向是從左向右。
        逆向最大匹配法(Reverse Maximum method):匹配方向與MM法相反,是從右向左。實驗表明:對於漢語來說,逆向最大匹配法比最大匹配法更有效。
        雙向匹配法(Bi-direction Matching method):比較MM法與RMM法的分詞結果,從而決定正確的分詞。
       最佳匹配法(Optimum Matching method, OM法):將詞典中的單詞按它們在文本中的出現頻度的大小排列,高頻度的單詞排在前,頻度低的單詞排在後,從而提高匹配的速度。
         聯想-回溯法(Association-Backtracking method):採用聯想和回溯的機制來進行匹配。

1.2 最大概率法分詞

基本思想是:(1)一個待切分的漢字串可能包含多種分詞結果(2)將其中概率最大的那個作爲該字串的分詞結果


S: 有意見分歧
        W1: 有/ 意見/ 分歧/
        W2: 有意/ 見/ 分歧/


        其中,可以近似地將 P(S|W) 看作是恆等於 1 的,因爲任意假想的一種分詞方式之下生成我們的句子總是精準地生成的(只需把分詞之間的分界符號扔掉即可),而P(S)在各種分詞方式下總是相等的,所以不影響比較。所以P(W|S)約等於P(W)。
       最大概率法分詞示例:


1.3 最短路徑分詞方法

       基本思想:在詞圖上選擇一條詞數最少的路徑
       優點:好於單向的最大匹配方法
                最大匹配:獨立自主\和平\等\互利\的\原則 (6)
                最短路徑:獨立自主\和\平等互利\的\原則 (5)
      缺點:同樣無法解決大部分歧義
                 例如:結合\成分\子時

2. 文檔模型

       包含三種模型:布爾模型、向量空間模型、概率模型

2.1 布爾模型

       布爾模型是建立在經典的集合論和布爾代數的基礎上,根據每個詞在一篇文檔中是否出現,對應權值爲0或1,文檔檢索也是由布爾邏輯運算來決定的。
       優點:簡單、易理解、簡潔的形式化。
       缺點:準確匹配,信息需求的能力表達不足。

2.2 向量空間模型(VSM)

       向量空間模型中將文檔表達爲一個矢量,看作向量空間中的一個點


(1) 詞權重

         一個句子中的每個詞在決定句子的含義時貢獻度並不相同,也就是每個詞的權重不同,例如下面的句子:
       “Most scientists think that butterflies use the position of the sun in the sky as a kind of compass that allows them to determine which way is north.”
       重要的詞:butterflies, monarchs, scientists, compass
       不重要的詞:most, think, kind, sky
       詞權重就是反映每個詞的重要性的度量。

(2) 詞頻(tf)

       一個詞在一個句子中出現的次數越多,那麼這個詞在描述這個句子的含義方面貢獻度越大,可通過下面兩個式子中的一個來計算每個詞的詞權重:


(3) 逆文檔頻率(idf)

        通常來說,如果一個詞在越多的文檔中出現過,那個這個詞對某一個文檔的貢獻度應該就越小,也就是通過這個詞來區分文檔的區分度越小,可以用逆文檔頻率(idf)來度量這個概念。先定義另一個概念,文檔頻率(df),表示包含某個詞的文檔的數目。逆文檔頻率計算公式如下:
 
       有時候爲了讓idf範圍在[0,1]內,使用下面的式子來計算:
 
        VSM計算簡單,很容易表示詞權重,它的缺點是必須假設詞與詞之間的獨立性

2.3 概率模型

        概率統計檢索模型(Probabilistic Retrieval Model)是另一種普遍使用的信息檢索算法模型,它應用文檔與查詢相關的概率來計算文檔與查詢的相似度。通常利用檢索單元作爲線索,通過統計得到每個檢索單元在相關的文檔集(對應於某詢)中出現和不出現的概率以及其在與該查詢不相關的文檔集中出現和不出現的概率,最終,利用這些概率值,計算文檔與查詢的相似度。設文檔D包含t個檢索單元,分別記爲(ω1,ω2,...,ωt),其中,ωi爲第i個檢索單元的權值,可以理解爲該檢索單元的出現爲文檔D與查詢Q相關所作的“貢獻”,文檔D與查詢Q的相似度則是t個包含在D中的檢索單元“貢獻”的組合。
        在信息檢索的研究中,對於概率統計檢索模型,通常,爲了計算方便需要做一些假設,比如:假設檢索單元在相關文檔集中的分佈相互獨立,在不相關文檔集中的分佈也相互獨立。雖然這一假設與實際情況並不完全一致,比如,“中國”和“北京”如果同時出現在某一篇文檔中,則不能認爲這樣的兩個檢索單元是相互獨立的。但是,如果考慮檢索單元的相關性,則會使相應的概率計算變得非常複雜,因此,在實際中,仍然保持了這一假設。實際的效果表明,儘管概率統計檢索模型存在這樣的不足,但仍可以取得相對令人滿意的信息檢索效果。
      具體來說,在獨立性假設的前提下,同時考慮檢索單元出現在文檔中的概率以及不出現在文檔中的概率,對於給定的查詢q 的某一個檢索單元ωi,可以定義wi :
wi=log[r(N-R-n+r) / (R-r)(n-r)]
其中
N:文檔集合中文檔的總數;
R:與查詢q 相關的文檔總數;
n:含有檢索單元ωi 的文檔總數;
r:與q 相關的文檔中,含有檢索單元ωi 的文檔數。
         由於訓練集合所能提供的信息並不是十分完全,Robertson 和Sparck-Jones建議對上式進行修正,在相關的信息不完全的情況下,在每一項後面加上0.5.
         現在,我們已經獲得了各檢索單元的權值,下一步是如何利用這些權值來計算文檔與查詢的相似度。考慮我們的假設條件,由於各檢索單元的分佈相互獨立,因此,我們可以簡單的利用這些權值的乘積來計算文檔與查詢的相似度,
SC (Q, D)= logΠ(wi)=Σlogwi
        至此,我們僅討論概率統計檢索模型最基本的一種檢索思路,實際使用中的概率統計檢索模型會複雜很多,通常,在檢索單元的權值的計算中,還會考慮檢索單元在文檔中出現的頻率(tf),檢索單元在查詢中出現的頻率(qtf),以及文檔的長度(dl)等信息,BM25算法就是這樣一種在目前信息檢索系統中常用的檢索算法。BM25 檢索算法是Roberston 1994年在TREC3上提出,BM25計算文檔D和查詢Q的相似性。對查詢Q中的每一個檢索單元ωi ,一共有三個權值與之相關:
        U =(k2+1)ψ/(k2+ψ),其中k2是由用戶指定的參數,ψ是檢索單元ωi在Q中出現的頻率qtf(within query term frequency)。
        V =(k+1)φ/k*(1-b+bL)+φ其中k 和b 是用戶指定的參數,φ 是檢索單元ωi 在D 中出現的頻率tf (term frequency),L 是正則化之後的文檔長度,計算方法爲原始文檔長度除以文檔集合中平均的文檔長度。
         W就是我們上面提到的加0.5後的式子。在BM25 公式中,查詢Q 和文檔D 的分值爲SC(Q,D)= ΣUVW

3 文本間相似度的計算

3.1 基於概率模型的相關度

           wi=log[r(N-R-n+r) / (R-r)(n-r)]
           SC (Q, D)= logΠ(wi)=Σlogwi
          見上面的概率模型

3.2 基於VSM的相關度

        基於向量空間模型的常用方法:歐氏距離、向量內積、向量夾角餘弦

(1)歐氏距離


(2)向量內積


(3)向量夾角餘弦


(4)Jaccard相似度


(5)基於向量內積的幾種方法的對比


(6)基於集合計算的幾種方法


4. 特徵空間的變化

         機器學習的主要難點在於“被闡述”的詞法和“真正要表達”的語義的區別。產生這個問題的原因主要是:1.一個單詞可能有多個意思和多個用法。2. 同義詞和近義詞,而且根據不同的語境或其他因素,原本不同的單詞也有可能表示相同的意思。LSA是處理這類問題的著名技術,其主要思想就是映射高維向量到潛在語義空間,使其降維。潛在語義分析(LSA)又稱爲潛在語義索引(LSI),是一種使用數學和統計的方法對文本中的詞語進行抽取,推斷它們之間的語義關係,並建立一個語義索引,而將文檔組織成語義空間結構的方法。它的出發點是文檔的特徵項與特徵項之間存在着某種潛在的語義聯繫,消除詞之間的相關性,簡化文本向量的目的。它通過奇異值分解(SVD),把特徵項和文檔映射到同一個語義空間,對文檔矩陣進行計算,提取K個最大的奇異值,近似表示原文檔。這個映射必須是嚴格線性的而且是基於共現表的奇異值分解。
        問題提出:一詞多義和同義詞
        中心思想:用概念(或特徵)代替詞
         基本方法:利用矩陣理論中的“奇異值分解(singular value decomposition,SVD)”技術,將詞頻矩陣轉化爲奇異矩陣(K×K)

4.1 奇異值分解

         特徵值分解是一個提取矩陣特徵很不錯的方法,但是它只是對方陣而言的,在現實的世界中,我們看到的大部分矩陣都不是方陣,比如說有N個學生,每個學生有M科成績,這樣形成的一個N * M的矩陣就不可能是方陣,我們怎樣才能描述這樣普通的矩陣呢的重要特徵呢?奇異值分解可以用來幹這個事情,奇異值分解是一個能適用於任意的矩陣的一種分解的方法:

         假設A是一個N * M的矩陣,那麼得到的U是一個N * N的方陣(裏面的向量是正交的,U裏面的向量稱爲左奇異向量),Σ是一個N * M的矩陣(除了對角線的元素都是0,對角線上的元素稱爲奇異值),V’(V的轉置)是一個N * N的矩陣,裏面的向量也是正交的,V裏面的向量稱爲右奇異向量),從圖片來反映幾個相乘的矩陣的大小可得下面的圖片


        那麼奇異值和特徵值是怎麼對應起來的呢?首先,我們將一個矩陣A的轉置乘以A,將會得到一個方陣,我們用這個方陣求特徵值可以得到:  這裏得到的v,就是我們上面的右奇異向量。此外我們還可以得到:

        這裏的σ就是上面說的奇異值,u就是上面說的左奇異向量。奇異值σ跟特徵值類似,在矩陣Σ中也是從大到小排列,而且σ的減少特別的快,在很多情況下,前10%甚至1%的奇異值的和就佔了全部的奇異值之和的99%以上了。也就是說,我們也可以用前r大的奇異值來近似描述矩陣,這裏定義一下部分奇異值分解:
 
r是一個遠小於m、n的數,這樣矩陣的乘法看起來像是下面的樣子:
 
右邊的三個矩陣相乘的結果將會是一個接近於A的矩陣,在這兒,r越接近於n,則相乘的結果越接近於A。而這三個矩陣的面積之和(在存儲觀點來說,矩陣面積越小,存儲量就越小)要遠遠小於原始的矩陣A,我們如果想要壓縮空間來表示原矩陣A,我們存下這裏的三個矩陣:U、Σ、V就好了。

4.2 隱語義分析(LSA)

        輸入:term-by-document matrix
        輸出:
               U: concept-by-term matrix
               V: concept-by-document matrix
               S: elements assign weights to concepts
基本步驟
       1.建立詞頻矩陣frequency matrix
       2.計算frequency matrix的奇異值分解
              分解frequency matrix成3個矩陣U,S,V。U和V是正交矩陣(UTU=I),S是奇異值的對角矩陣(K×K)
       3.對於每一個文檔d,用排除了SVD中消除後的詞的新的向量替換原有的向量
       4.用轉換後的文檔索引和相似度計算


          之前吳軍老師在矩陣計算與文本處理中的分類問題中談到:
          “三個矩陣有非常清楚的物理含義。第一個矩陣X中的每一行表示意思相關的一類詞,其中的每個非零元素表示這類詞中每個詞的重要性(或者說相關性),數值越大越相關。最後一個矩陣Y中的每一列表示同一主題一類文章,其中每個元素表示這類文章中每篇文章的相關性。中間的矩陣則表示類詞和文章雷之間的相關性。因此,我們只要對關聯矩陣A進行一次奇異值分解,w 我們就可以同時完成了近義詞分類和文章的分類。(同時得到每類文章和每類詞的相關性)。”
        上面這段話可能不太容易理解,不過這就是LSI的精髓內容,我下面舉一個例子來說明一下,下面的例子來自LSA tutorial,具體的網址我將在最後的引用中給出:

  
       這就是一個矩陣,不過不太一樣的是,這裏的一行表示一個詞在哪些title中出現了(一行就是之前說的一維feature),一列表示一個title中有哪些詞,(這個矩陣其實是我們之前說的那種一行是一個sample的形式的一種轉置,這個會使得我們的左右奇異向量的意義產生變化,但是不會影響我們計算的過程)。比如說T1這個title中就有guide、investing、market、stock四個詞,各出現了一次,我們將這個矩陣進行SVD,得到下面的矩陣:

       左奇異向量表示詞的一些特性,右奇異向量表示文檔的一些特性,中間的奇異值矩陣表示左奇異向量的一行與右奇異向量的一列的重要程序,數字越大越重要。
       繼續看這個矩陣還可以發現一些有意思的東西,首先,左奇異向量的第一列表示每一個詞的出現頻繁程度,雖然不是線性的,但是可以認爲是一個大概的描述,比如book是0.15對應文檔中出現的2次,investing是0.74對應了文檔中出現了9次,rich是0.36對應文檔中出現了3次;
       其次,右奇異向量中一的第一行表示每一篇文檔中的出現詞的個數的近似,比如說,T6是0.49,出現了5個詞,T2是0.22,出現了2個詞。
        然後我們反過頭來看,我們可以將左奇異向量和右奇異向量都取後2維(之前是3維的矩陣),投影到一個平面上,可以得到:

        在圖上,每一個紅色的點,都表示一個詞,每一個藍色的點,都表示一篇文檔,這樣我們可以對這些詞和文檔進行聚類,比如說stock 和 market可以放在一類,因爲他們老是出現在一起,real和estate可以放在一類,dads,guide這種詞就看起來有點孤立了,我們就不對他們進行合併了。按這樣聚類出現的效果,可以提取文檔集合中的近義詞,這樣當用戶檢索文檔的時候,是用語義級別(近義詞集合)去檢索了,而不是之前的詞的級別。這樣一減少我們的檢索、存儲量,因爲這樣壓縮的文檔集合和PCA是異曲同工的,二可以提高我們的用戶體驗,用戶輸入一個詞,我們可以在這個詞的近義詞的集合中去找,這是傳統的索引無法做到的。


參考:

http://www.cnblogs.com/LeftNotEasy/archive/2011/01/19/svd-and-applications.html

北大計算機學院楊建武文本挖掘課件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章