TM01. Term frequency
From tokenization, stopword filtering, stemming/lemmatization, to counting term frequency.
Last updated
From tokenization, stopword filtering, stemming/lemmatization, to counting term frequency.
Last updated
社會科學家在做研究的過程經常會需要探究文本特徵,過去常用的方法是內容分析法,透過大量的編碼或搭配質性分析輔助軟體來分析。當我們嘗試用資訊科學的方法來解決這類的需求時,可以先想想看我們是如何「描述文本特徵」的。比方說,我們常常在日常交談中提到,某報寫的新聞經常唱衰某個政治人物、某作家的文章大量且細碎地描述周遭景物的細節、某總統候選人習慣使用「他們」來區分他者,藉以透過訊息暗示來凝聚粉絲形成我群。在這些例子中,為何我們會覺得某個媒體、某種文體或者某人的文章有一種固定的習慣、手法、操作?這是因為某些詞語的使用次數達到讓我們感到突出、明顯的地步,而這正是數量上的特徵。因此,文本探勘在做的事情就是量化文本的特徵,而最基礎的動作便是計算字詞的出現次數(詞頻),再來才是字詞間的脈絡,也就是每個字的前後關係。
但在計算詞頻前,我們要先去除文本中的「雜訊」。以英文文本來說,通常會先去除代名詞(he, we, ...)、介系詞(in, to, from)、冠詞(the, a)等停用詞(Stopword),然後用stemming或lemmatization方法標準化字根,也就是去除如動名詞(-ing)、時間副詞(-ed)、比較級與最高級(-er, -est)的影響。當做完這些前處理後,便可以計算每個字的出現次數,即為詞頻,即可視為該篇文章的特徵。詞頻經常被視覺化為文字雲來使得文本特徵的概念易於被理解,也是文本分類、比較、推薦的基礎。下圖為一典型的文字處理流程,包含斷詞、篩除停用詞、詞幹萃取、計算詞頻的過程。
本節將會用wikipedia某條目的內文來分析詞頻。輸入的文本是一篇用Python套件所取回的Wikipedia文章內容,目標輸出是按出現次數由大而小排列的詞頻,在Python裡詞頻通常會存在一個Dictionary中。
Hindus (Hindustani: [ˈɦɪndu] (listen)) are persons who regard themselves as culturally, ethnically, or religiously adhering to aspects of Hinduism. Historically, the term has also been used as a geographical, cultural, and later religious identifier for people living in the Indian subcontinent.The historical meaning of the term Hindu has evolved with time. Starting with the Persian and Greek references to the land of the Indus in the 1st millennium BCE through the texts of the medieval era, the term Hindu implied a geographic, ethnic or cultural identifier for people living in the Indian subcontinent around or beyond the Sindhu (Indus) river. By the 16th century, the term began to refer to residents of the subcontinent who were not Turkic or Muslims.The historical development of Hindu self-identity within the local South Asian population, in a religious or cultural sense, is unclear. Competing theories state that Hindu identity developed in the British colonial era, or that it may have developed post-8th century CE after the Islamic invasion and medieval Hindu-Muslim wars. A sense of Hindu identity and the term Hindu appears in some texts dated between the 13th and 18th century in Sanskrit and Bengali. The 14th- and 18th-century Indian poets such as Vidyapati, Kabir and Eknath used the phrase Hindu dharma (Hinduism) and contrasted it with Turaka dharma (Islam). The Christian friar Sebastiao Manrique used the term 'Hindu' in religious context in 1649. In the 18th century, the European merchants and colonists began to refer to the followers of Indian religions collectively as Hindus, in contrast to Mohamedans for Mughals and Arabs following Islam. By the mid-19th century, colonial orientalist texts further distinguished Hindus from Buddhists, Sikhs and Jains, but the colonial laws continued to consider all of them to be within the scope of the term Hindu until about mid-20th century.
使用字串類別的split()
函式斷詞。英文斷詞最簡單的方法是使用字串類別的split()
函式,在該函式中加入據以作為斷詞依據的字元(如英文的空白),就可以把該字串切為一個由多個字詞所組成的List如下。
['What’s', 'in', 'a', 'name?', 'That', 'which', 'we', 'call', 'a', 'rose', 'by', 'any', 'other', 'name', 'would', 'smell', 'as', 'sweet.']
將文本視為一個很長的字元List text
,然後一一讀取裡面每個字元(for ch in text:
),邊讀邊存,當讀取到空白的時候(if ch == " ":
),便把前面存起來的字元當成一個字串append
到List tokens
中。
這邊比較難理解的應該是if tok:
,我們可以寫個簡單的程式測試如下,當空字串作為if
的Condition時,會相當於False
,所以程式會印出"No"。就上面程式的意思是說,如果我取到一個空白字元" ",那就偵測看看,底下所取到的tok
是空的還是有字,有字的話便把他加入到tokens
這個List中。那沒有這個if tok:
的判斷式會有什麼樣的結果?你可以改改程式看會有什麼結果。
除了使用split()
法一)外,我們也可嘗試使用NLTK套件的斷詞器(方法二),或者自己寫一個相當於split()
功能的斷詞函式(法三)
第一種方法是使用字串的內建函式split()
來斷詞,該函式的輸入為據以斷詞的字元。由於英文的斷詞基準是看到空白就斷開(會有一些問題),因此,最簡單的就是把空白字元當成斷詞字元如.split(" ")
。
這三個斷詞方法產生的結果多少會有所不同,你可以比較看看有什麼不同(練習TM01_2)。第一種當然是沒有處理標點符號的問題,用NLTK斷詞他會把標點符號作為單一字詞斷開,只要用str.isalpha()
就可一一判斷每個斷開的字詞是否是英文字詞,目前自行斷詞的結果和str.split()
結果是一模一樣的。但NTLK的Tokenizer和其他兩個相較下,計算速度應該會較慢,你可以上網查詢如何計算程式的執行時間。
['Hindus', '(Hindustani:', '[ˈɦɪndu]', '(listen))', 'are', 'persons', 'who', 'regard', 'themselves', 'as', 'culturally,', 'ethnically,', 'or', 'religiously', 'adhering', 'to', 'aspects', 'of', 'Hinduism.', 'Historically,', 'the', 'term', 'has', 'also', 'been', 'used', 'as', 'a', 'geographical,', 'cultural,', 'and', 'later', 'religious', 'identifier', 'for', 'people', 'living', 'in', 'the', 'Indian', 'subcontinent.The', 'historical', 'meaning', 'of', 'the', 'term', 'Hindu', 'has', 'evolved', 'with', 'time.', 'Starting', 'with', 'the', 'Persian', 'and', 'Greek', 'references', 'to', 'the', 'land', 'of', 'the', 'Indus', 'in', 'the', '1st', 'millennium', 'BCE', 'through', 'the', 'texts', 'of', 'the', 'medieval', 'era,', 'the', 'term', 'Hindu', 'implied', 'a', 'geographic,', 'ethnic', 'or', 'cultural', 'identifier', 'for', 'people', 'living', 'in', 'the', 'Indian', 'subcontinent', 'around', 'or', 'beyond', 'the', 'Sindhu', '(Indus)', 'river.', 'By', 'the', '16th', 'century,', 'the', 'term', 'began', 'to', 'refer', 'to', 'residents', 'of', 'the', 'subcontinent', 'who', 'were', 'not', 'Turkic', 'or', 'Muslims.The', 'historical', 'development', 'of', 'Hindu', 'self-identity', 'within', 'the', 'local', 'South', 'Asian', 'population,', 'in', 'a', 'religious', 'or', 'cultural', 'sense,', 'is', 'unclear.', 'Competing', 'theories', 'state', 'that', 'Hindu', 'identity', 'developed', 'in', 'the', 'British', 'colonial', 'era,', 'or', 'that', 'it', 'may', 'have', 'developed', 'post-8th', 'century', 'CE', 'after', 'the', 'Islamic', 'invasion', 'and', 'medieval', 'Hindu-Muslim', 'wars.', 'A', 'sense', 'of', 'Hindu', 'identity', 'and', 'the', 'term', 'Hindu', 'appears', 'in', 'some', 'texts', 'dated', 'between', 'the', '13th', 'and', '18th', 'century', 'in', 'Sanskrit', 'and', 'Bengali.', 'The', '14th-', 'and', '18th-century', 'Indian', 'poets', 'such', 'as', 'Vidyapati,', 'Kabir', 'and', 'Eknath', 'used', 'the', 'phrase', 'Hindu', 'dharma', '(Hinduism)', 'and', 'contrasted', 'it', 'with', 'Turaka', 'dharma', '(Islam).', 'The', 'Christian', 'friar', 'Sebastiao', 'Manrique', 'used', 'the', 'term', "'Hindu'", 'in', 'religious', 'context', 'in', '1649.', 'In', 'the', '18th', 'century,', 'the', 'European', 'merchants', 'and', 'colonists', 'began', 'to', 'refer', 'to', 'the', 'followers', 'of', 'Indian', 'religions', 'collectively', 'as', 'Hindus,', 'in', 'contrast', 'to', 'Mohamedans', 'for', 'Mughals', 'and', 'Arabs', 'following', 'Islam.', 'By', 'the', 'mid-19th', 'century,', 'colonial', 'orientalist', 'texts', 'further', 'distinguished', 'Hindus', 'from', 'Buddhists,', 'Sikhs', 'and', 'Jains,', 'but', 'the', 'colonial', 'laws', 'continued', 'to', 'consider', 'all', 'of', 'them', 'to', 'be', 'within', 'the', 'scope', 'of', 'the', 'term', 'Hindu', 'until', 'about', 'mid-20th', 'century.']
['What’s', 'in', 'a', 'name?', 'That', 'which', 'we', 'call', 'a', 'rose', 'by', 'any', 'other', 'name', 'would', 'smell', 'as', 'sweet.']
類似上述斷詞結果顯然有一些令人不滿意之處,尤其是用split()
或自行斷詞。這些情況包含name?
後面有個問號,sweet.
後面有句號,That
的首字為大寫,這些狀況均會影響之後計算詞頻的結果。因此,我們得做一點處理,比方說,要去除標點符號,或者像是冠詞、代名詞、介系詞等可能在分析上無意義的詞、字首大寫的情形。在計算詞頻後的數節將會一一處理這些情形。
你可比較看看用上述自己寫的斷詞結果和用split()
的結果有何不同(Hint: 計算一下兩個tokens
長度是否相同)。
用split()
和自己撰寫tokenizer的結果都是tokens
list。請問,要如何用程式來比較看看,這兩個tokens
list有何不同?
由於NLTK可以幫忙解決標點符號的問題,所以這邊使用NLTK的tokenizer來做英文斷詞,斷詞完後使用Counter來計算詞頻,計算完後的結果用Counter的函式.most_common()
來列出前20大詞頻。前20大詞頻如程式的輸出所述,以定冠詞、不定冠詞、連接詞、標點符號、介系詞等居多。從這個結果,至少可以觀察到三點問題,其一是標點符號應該要去除,其二是,冠詞、連接詞、介系詞等可能都要去除、the和The由於首字大寫的關係被視為不同的字,可能需要合併。
從Tokenization一節就可以發現,用字串內建函式split()
以及手動斷詞的結果差不多,但和nltk做取詞的結果差非常多,最主要的差異出現在標點符號上。標點符號若利用內建函式來斷詞會被視為字詞的一部分,因而會影響詞頻統計的結果,比方說,在句末的字和該字不在句末會差一個句號而被分開計算出現次數,括號、逗號都有相同的情形。因此,調整斷詞品質的第一件事是先把標點符號給移除了。string類別本身就自帶有標點符號,可以把它印出來看看。
以下的程式都會用函式來撰寫。對於函式要如何設計、如何寫,可以參閱Function一節並做練習。
以下的tokens
是NLTK斷詞完後的結果,NTLK會將標點符號斷為單詞,和字母區隔開來,只需要偵測該單詞(法一)是否是標點符號,或者偵測該單詞(法二)是否是字母就好了。
由於NLTK會把標點符號斷為單一tok,所以我可以用for-loop跑過所有的tokens(NLTK斷詞後的結果),逐一檢視他是否在標點符號(string.punctuation
)中。要檢測某個字是否為更長的字的子字串,只要用if str_a in str_b
的語法就可以偵測。以這個例子來說,他是不希望該tok為標點符號,所以要用not in
。
結果如下,可以看得出來數字和'–'
的符號都保留下來了。
['Rembrandt', 'Harmenszoon', 'van', 'Rijn', 'also', 'US', 'Dutch', 'ˈrɛmbrɑnt', 'ˈɦɑrmə', 'n', 'soːn', 'vɑn', 'ˈrɛin', 'listen', '15', 'July', '1606', '–', '4', 'October', '1669', 'was', 'a', 'Dutch', 'draughtsman', 'painter', 'and', 'printmaker', 'An', 'innovative', 'and', 'prolific', 'master', 'in', 'three', 'media', 'he', 'is', 'generally', 'considered', 'one', 'of', 'the', 'greatest', 'visual', 'artists', 'in', 'the', 'history', 'of', 'art', 'and', 'the', 'most', 'important', 'in', 'Dutch', 'art', 'history', 'Unlike', 'most', 'Dutch', 'masters', 'of', 'the', '17th', 'century', 'Rembrandt', "'s", 'works', 'depict', 'a', 'wide', 'range', 'of', 'style', 'and', 'subject', 'matter', 'from', 'portraits', 'and', 'self-portraits', 'to', 'landscapes', 'genre', 'scenes', 'allegorical', 'and', 'historical', 'scenes', 'and', 'biblical', 'and', 'mythological', 'themes', 'as', 'well', 'as', 'animal', 'studies', 'His', 'contributions', 'to', 'art', 'came', 'in', 'a', 'period', 'of', 'great', 'wealth', 'and', 'cultural', 'achievement', 'that', 'historians', 'call', 'the', 'Dutch', 'Golden', 'Age', 'when', 'Dutch', 'art', 'especially', 'Dutch', 'painting', 'although', 'in', 'many', 'ways', 'antithetical', 'to', 'the', 'Baroque', 'style', 'that', 'dominated', 'Europe', 'was', 'extremely', 'prolific', 'and', 'innovative', 'and', 'gave', 'rise', 'to', 'important', 'new', 'genres', 'Like', 'many', 'artists', 'of', 'the', 'Dutch', 'Golden', 'Age', 'such', 'as', 'Jan', 'Vermeer', 'of', 'Delft', 'Rembrandt', 'was', 'also', 'an', 'avid', 'art', 'collector', 'and', 'dealer', 'Rembrandt', 'never', 'went', 'abroad', 'but', 'he', 'was', 'considerably', 'influenced', 'by', 'the', 'work', 'of', 'the', 'Italian', 'masters', 'and', 'Netherlandish', 'artists', 'who', 'had', 'studied', 'in', 'Italy', 'like', 'Pieter', 'Lastman', 'the', 'Utrecht', 'Caravaggists', 'and', 'Flemish', 'Baroque', 'Peter', 'Paul', 'Rubens', 'After', 'he', 'achieved', 'youthful', 'success', 'as', 'a', 'portrait', 'painter', 'Rembrandt', "'s", 'later', 'years', 'were', 'marked', 'by', 'personal', 'tragedy', 'and', 'financial', 'hardships', 'Yet', 'his', 'etchings', 'and', 'paintings', 'were', 'popular', 'throughout', 'his', 'lifetime', 'his', 'reputation', 'as', 'an', 'artist', 'remained', 'high', 'and', 'for', 'twenty', 'years', 'he', 'taught', 'many', 'important', 'Dutch', 'painters.Rembrandt', "'s", 'portraits', 'of', 'his', 'contemporaries', 'self-portraits', 'and', 'illustrations', 'of', 'scenes', 'from', 'the', 'Bible', 'are', 'regarded', 'as', 'his', 'greatest', 'creative', 'triumphs', 'His', 'self-portraits', 'form', 'a', 'unique', 'and', 'intimate', 'biography', 'in', 'which', 'the', 'artist', 'surveyed', 'himself', 'without', 'vanity', 'and', 'with', 'the', 'utmost', 'sincerity', 'Rembrandt', "'s", 'foremost', 'contribution', 'in', 'the', 'history', 'of', 'printmaking', 'was', 'his', 'transformation', 'of', 'the', 'etching', 'process', 'from', 'a', 'relatively', 'new', 'reproductive', 'technique', 'into', 'a', 'true', 'art', 'form', 'along', 'with', 'Jacques', 'Callot']
設計好去除標點符號的函式後,便在計算詞頻前加入去除標點符號的函式看看結果,再和之前沒有去除標點符號的前10大詞頻比較看看,可以發現不受標點符號影響了。
在信息檢索中,為節省存儲空間和提高搜索效率,在自然語言處理數據(或文本)之前或之後會自動過濾掉某些字或詞,這些字或詞即被稱為Stop Words(停用詞)。不要把停用詞與安全口令混淆。 這些停用詞都是人工輸入、非自動化生成的,生成後的停用詞會形成一個停用詞表。但是,並沒有一個明確的停用詞表能夠適用於所有的工具。甚至有一些工具是明確地避免使用停用詞來支持短語搜索的。
對於一個給定的目的,任何一類的詞語都可以被選作停用詞。通常意義上,停用詞大致分為兩類。一類是人類語言中包含的功能詞,這些功能詞極其普遍,與其他詞相比,功能詞沒有什麼實際含義,比如'the'、'is'、'at'、'which'、'on'等。但是對於搜尋引擎來說,當所要搜索的短語包含功能詞,特別是像The Who、The The或Take That等複合名詞時,停用詞的使用就會導致問題。另一類詞包括詞彙詞,比如'want'等,這些詞應用十分廣泛,但是對這樣的詞搜尋引擎無法保證能夠給出真正相關的搜索結果,難以幫助縮小搜索範圍,同時還會降低搜索的效率,所以通常會把這些詞從問題中移去,從而提高搜索性能。
先自NLTK套件載入停用詞,以下所示即為NLTK所定義的英文停用詞,注意到這些全為小寫,並且我們先把英文停用詞先存到一個stopword_list
來使用。
在斷詞再去除標點符號後,會得到一個新的tokens list,接下來就是用for-loop一一偵測tokens中的每個tok
是否在stopword_list
中,如果tok不在停用詞中的話,就把該tok append到tokens_clean
中。但觀看結果顯然會受大小寫影響,尤其是句首的定冠詞和不定冠詞,以及I等主詞,因此,在移除前要先做英文的大小寫轉換。
處理英文的大小寫轉換有兩種作法,Solution 1是設計一個lowercase()
函式把所有tokens都先轉成小寫,然後再移去停用詞,這樣做會導致所有文字都被轉為小寫,這樣後續計算詞頻不管是否詞彙在句首都會列計到同一個詞;Solution 2 是在偵測是否為停用詞時臨時轉小寫,這樣的結果是不會把原本的內容全部轉為小寫,但句首大寫的詞在算詞頻時會被列計為另外一個詞。
前面我們一共做了三個程序來去除大小寫、停用詞、標點符號的影響,而在大小寫轉換上有兩種Solution,可以比較看看這兩種Solution所跑出來的詞頻結果。在這個資料案例裡,只有專有名詞大小寫有差別,詞頻數量看似並沒有差別。你覺得哪一種轉法比較好呢?我比較常用第二種轉法。
注意到我是用lowercase()
這個函式把tokens
整個List轉小寫。
詞幹提取要解決的問題:接下來的Stemming和Lemmatization都是為了去除字尾動名詞、過去式/完成式、和比較級/最高級的影響,例如若只是單純的詞頻統計,沒有要看文句結構的話,那start、 started、starting應該要被算為同一個字。可是前面的做法會把這三個字當成不同的字來計算詞頻,因此要想辦法解決這樣的問題。
In linguistic morphology and information retrieval, stemming is the process of reducing inflected (or sometimes derived) words to their word stem, base or root form—generally a written word form. The stem need not be identical to the morphological root of the word; it is usually sufficient that related words map to the same stem, even if this stem is not in itself a valid root. Algorithms for stemming have been studied in computer science since the 1960s. Many search engines treat words with the same stem as synonyms as a kind of query expansion, a process called conflation.
Example. A stemmer for English operating on the stem cat should identify such strings as cats, catlike, and catty. A stemming algorithm might also reduce the words fishing, fished, and fisher to the stem fish. The stem need not be a word, for example the Porter algorithm reduces, argue, argued, argues, arguing, and argus to the stem argu.
類似Stemming的另一個方法式Lemmatization。詞型還原的演算法可以解決動詞、名詞、形容詞或副詞等詞,並會依照大小寫來進行判斷。Stemming比較單純是把詞尾、詞綴給去除,但Lemmatization則是讀取WordNet詞網的資料,透過字庫比對來抽取詞彙的原型。所以Lemmatization在不是-ed型的過去分詞尤其會表現得更好。
我們先來寫些簡單的Code測試一下Lemmatization與Stemming的效果。
和Stemming一樣,是把Lemmatization做在移除停用詞後,然後就進入詞頻的計算。
wikipedia為python上的一個套件,你會需要先安裝他,可以用pip install wikipedia
來安裝。至於要如何使用它,可查閱。
第二種方法是使用的斷詞套件,是Python上的自然語言處理套件。如同下面的程式碼,第一次使用NTLK的Tokenizer應該會需要下載punkt
,其為nltk套件中的Punkt tokenizer。
如何計算程式的執行時間?可上網google "running time python",便可找到在stackoverflow上的說明「」如下。
停用詞的定義可參見。停用詞的偵測都是有個停用詞表,例如英文停用詞表、中文停用詞表,然後偵測斷詞後的tokens有沒有包含停用詞在裡頭。但必須注意的是,並不是所有的分析都要去除停用詞的,例如你想要知道某人的演講風格、寫作風格做歸類,那可能就要考慮納入停用詞來進行分析。
何謂Stemming?以下說明內容來自。但這部分英文的解說會比中文解說清楚。從下面wikipedia所舉的例子可以看出來,詞幹提取的結果會處理掉現在分詞、過去分詞、比較級字尾,進而使得這些詞可以被當同一個詞來統計。
底下詞幹提取的方法使用了NLTK套件的Snowball演算法。Stemming with Snowball algorithm implemented by NLTK. Reference: 。詞幹提取的時機是在移除停用詞後。但底下目前這個版本的移除停用詞並無解決大小寫的問題,你可以自行解決(輸出中只列出了詞頻2以上的結果)。
以上文字處理過程,最常見的應用就是文字雲。文字雲通常把字詞依照其出現次數重複繪製後,丟入線上文字雲的軟體就可以自動產生依照詞頻的文字雲,如。