手把手教你用R實現標記化(附程式碼、學習資料、語料庫)
作者:Rachael Tatman
翻譯:樑傅淇
本文長度為1600字,建議閱讀4分鐘
標記化是自然語言處理中的一個常見的任務。本文教你如何用R來統計單個標記(單個單詞)在文字中出現的頻率,並將這個過程寫成可複用的函式。
自然語言處理中的一個常見的任務就是標記化。通常而言,對於像英語這樣的語言來說,標記是單個的單詞,而標記化則是將一篇文章或者一系列文章分成一個個的單詞。這些標記之後會被作為其他型別的分析或者任務的輸入,比如說語法解析(自動地標記單詞間的語法關係)。
在這個教程中,你會學到怎樣去:
將文字讀入R中
挑選其中的數行文字
使用tidytext包去標記化文字
計算標記頻數(每個標記在資料集中出現的頻繁程度)
寫出可複用的函式來做上面的事情,使得工作具有更高的效率
本教程中我們會使用雙語兒童的對話轉錄語料庫。你可以在這裡(https://www.kaggle.com/rtatman/corpus-of-bilingual-childrens-speech)找到更多有關的資訊並下載它。
這些檔案是使用CLAN(http://alpha.talkbank.org/clan/)生成的,因此格式會有些奇怪。但是隻需要少量的文字處理我們就能夠像使用純文字檔案一樣來使用它們。
現在讓我們來找出孩子們接觸英語的時間和他們使用口頭語(比如說,“嗯”和“呃”)的頻率的關係。
# load in libraries we'll need
library(tidyverse) #keepin' things tidy
library(tidytext) #package for tidy text analysis (Check out Julia Silge's fab book!)
library(glue) #for pasting strings
library(data.table) #for rbindlist, a faster version of rbind
# now let's read in some data & put it in a tibble (a special type of tidy dataframe)
file_info <- as_data_frame(read.csv("../input/guide_to_files.csv"))
head(file_info)
這看起來很棒。那麼現在讓我們從這個csv檔案中獲得檔名稱並讀取其中一個檔案到R中來。
# stick together the path to the file & 1st file name from the information file
fileName <- glue("../input/", as.character(file_info$file_name[1]), sep = "")
# get rid of any sneaky trailing spaces
fileName <- trimws(fileName)
# read in the new file
fileText <- paste(readLines(fileName))
# and take a peek!
head(fileText)
# what's the structure?
str(fileText)
哎呀,什麼亂七八糟的!我們以vector的形式讀入文字,每一行都被當作一個單獨的元素,這對我們所感興趣的單詞的數量來說並不是理想的處理形式。我們可以使用一個小技巧,因為我們只對孩子們的對話感興趣,而實驗人員的話則無關緊要,所以我們可以只保留“*CHI: Child Speaking”開頭的話。我們使用以下的正則表示式來獲取這部分字串:
# "grep" finds the elements in the vector that contain the exact string *CHI:.
# (You need to use the double slashes becuase I actually want to match the character
# *, and usually that means "match any character"). We then select those indexes from
# the vector "fileText".
childsSpeech <- as_data_frame(fileText[grep("\\*CHI:",fileText)])
head(childsSpeech)
好了,現在我們有了孩子們所說的話了。但是這離我們所希望回答的問題“孩子們說了多少次‘嗯’”還很遠呢。
讓我們把我們的資料弄得更整潔一些。整潔的資料擁有以下三個特徵(https://cran.r-project.org/web/packages/tidyr/vignettes/tidy-data.html):
一個變數佔一列。
一個觀測值佔一行。
一個型別的觀測值組成一個表格。
幸運的是我們不用從零開始,我們可以用tidytext包!
# use the unnest_tokens function to get the words from the "value" column of "child
childsTokens <- childsSpeech %>% unnest_tokens(word, value)
head(childsTokens)
啊,好多了!你會注意到unnest_tokens函式已經做了大部分的預處理工作。標點符號已經被處理了,而且所有的字母都小寫化了。也許你並不是每次都需要這麼做,但是這次我們並不想區分“trees”和“Trees”。
現在讓我們看看詞頻,或者說,這個詞有多常見。
# look at just the head of the sorted word frequencies
childsTokens %>% count(word, sort = T) %>% head
我們馬上就發現了一個問題,最頻繁的一個詞實際上並非來自於孩子們所說的話語,而是註釋“chi”,代表這句話是孩子說的。所以我們要使用dplyr包的anti_join函式來去除它。
# anti_join removes any rows that are in the both dataframes, so I make a data_frame
# of 1 row that contins "chi" in the "word" column.
sortedTokens <- childsSpeech %>% unnest_tokens(word, value) %>% anti_join(data_frame(word = "chi")) %>%
count(word, sort = T)
head(sortedTokens)
太棒了!這正是我們想要的。但是隻是其中一個檔案的結果而已。我們想要比較不同的檔案。那麼現在讓我們把它流程化(這會使得接下來更容易複製這個過程)。現在我們有了一個函式,讓我們執行它並看它是否能工作。
# let's make a function that takes in a file and exactly replicates what we just did
fileToTokens <- function(filename){
# read in data
fileText <- paste(readLines(filename))
# get child's speech
childsSpeech <- as_data_frame(fileText[grep("\\*CHI:",fileText)])
# tokens sorted by frequency
sortedTokens <- childsSpeech %>% unnest_tokens(word, value) %>%
anti_join(data_frame(word = "chi")) %>%
count(word, sort = T)
# and return that to the user
return(sortedTokens)
}
函式寫好了,我們用它來處理其中一個檔案。
# we still have this fileName variable we assigned at the beginning of the tutorial
fileName
# so let's use that...
head(fileToTokens(fileName))
# and compare it to the data we analyzed step-by-step
head(sortedTokens)
好啦!這個函式的輸出和我們之前的分析的結果是完全一樣的!現在我們可以把它用來處理整個檔案集了。
還有一件事,我們得指出哪個孩子說出哪些詞。這樣的話我們得在輸出中加一列。
# let's write another function to clean up file names. (If we can avoid
# writing/copy pasting the same codew we probably should)
prepFileName <- function(name){
# get the filename
fileName <- glue("../input/", as.character(name), sep = "")
# get rid of any sneaky trailing spaces
fileName <- trimws(fileName)
# can't forget to return our filename!
return(fileName)
}
# make an empty dataset to store our results in
tokenFreqByChild <- NULL
# becuase this isn't a very big dataset, we should be ok using a for loop
# (these can be slow for really big datasets, though)
for(name in file_info$file_name){
# get the name of a specific child
child <- name
# use our custom functions we just made!
tokens <- prepFileName(child) %>% fileToTokens()
# and add the name of the current child
tokensCurrentChild <- cbind(tokens, child)
# add the current child's data to the rest of it
# I'm using rbindlist here becuase it's much more efficent (in terms of memory
# usage) than rbind
tokenFreqByChild <- rbindlist(list(tokensCurrentChild,tokenFreqByChild))
}
# make sure our resulting dataframe looks reasonable
summary(tokenFreqByChild)
head(tokenFreqByChild)
我們現在在同一個表格中有所有的資料了。讓我們來視覺化它吧!
# let's plot the how many words get used each number of times
ggplot(tokenFreqByChild, aes(n)) + geom_histogram()
這個圖表告訴我們,大部分的單詞都只用了一次,出現頻率越高的單詞數量越少。這是人類語言的一個定律,叫做齊普夫定律(https://nlp.stanford.edu/IR-book/html/htmledition/zipfs-law-modeling-the-distribution-of-terms-1.html)。
讓我們回到最初的問題:孩子們使用“嗯”的頻率和孩子們學習語言的時間長短間是否有關係。
#first, let's look at only the rows in our dataframe where the word is "um"
ums <- tokenFreqByChild[tokenFreqByChild$word == "um",]
# now let's merge our ums dataframe with our information file
umsWithInfo <- merge(ums, file_info, by.y = "file_name", by.x = "child")
head(umsWithInfo)
看起來不錯。讓我們看看,孩子們使用“嗯”的次數和和他們學習語言的月份數是否有關係。
# see if there's a significant correlation
cor.test(umsWithInfo$n, umsWithInfo$months_of_english)
# and check the plot
ggplot(umsWithInfo, aes(x = n, y = months_of_english)) + geom_point() +
geom_smooth(method = "lm")
肯定的“沒有”。在這個語料庫中的材料顯示,孩子們說“嗯”的次數和他們接觸英語的時間長短並無聯絡。
還有一些方面的事情可以使得分析過程更完善:
• 關注相對頻率,也就是在孩子們所說的話語中,“嗯”的佔比而非它的頻率
• 關注所有的語氣詞,而非單個的“嗯”
• 關注難以理解的話語(“xxx”)
我像老式教科書一樣留下給你,讀者們,一個問題。我已經教授了我在文章開頭所承諾的一切了,你一定能做到的:
• 將文字讀入R中
• 挑選其中的數行文字
• 使用tidytext包去標記化文字
• 計算標記頻數(每個標記在資料集中出現的頻繁程度)
• 寫出可複用的函式來做上面的事情,使得工作具有更高的效率
既然你已經掌握了標記化的基礎了,這裡是另外一些你可以用來鍛鍊技能的語料庫:
• Ironic Corpus(https://www.kaggle.com/rtatman/ironic-corpus)
• Stanford Natural Language Inference Corpus(https://www.kaggle.com/sohier/stanford-natural-language-inference-corpus)
• Annotated Corpus for Named Entity Recognition(https://www.kaggle.com/abhinavwalia95/entity-annotated-corpus)
• Fraudulent E-mail Corpus(https://www.kaggle.com/rtatman/fraudulent-email-corpus)
祝你好運!Happy tokenization!
原文標題:
Data Science 101(Getting started in NLP):Tokenization tutorial
原文連結:
http://blog.kaggle.com/2017/08/25/data-science-101-getting-started-in-nlp-tokenization-tutorial/
編輯:王璇
樑傅淇,軟體工程本科在讀,主修大資料分析,喜好搜尋、收集各類資訊。希望能在THU資料派平臺認識更多對資料分析感興趣的朋友,一起研究如何從資料探勘出有用的模型和資訊。
翻譯組招募資訊
工作內容:需要一顆細緻的心,將選取好的外文文章翻譯成流暢的中文。如果你是資料科學/統計學/計算機類的留學生,或在海外從事相關工作,或對自己外語水平有信心的朋友歡迎加入翻譯小組。
你能得到:定期的翻譯培訓提高志願者的翻譯水平,提高對於資料科學前沿的認知,海外的朋友可以和國內技術應用發展保持聯絡,THU資料派產學研的背景為志願者帶來好的發展機遇。
其他福利:來自於名企的資料科學工作者,北大清華以及海外等名校學生他們都將成為你在翻譯小組的夥伴。
點選文末“閱讀原文”加入資料派團隊~
為保證發文質量、樹立口碑,資料派現設立“錯別字基金”,鼓勵讀者積極糾錯。
若您在閱讀文章過程中發現任何錯誤,請在文末留言,或到後臺反饋,經小編確認後,資料派將向檢舉讀者發8.8元紅包。
同一位讀者指出同一篇文章多處錯誤,獎金不變。不同讀者指出同一處錯誤,獎勵第一位讀者。
感謝一直以來您的關注和支援,希望您能夠監督資料派產出更加高質的內容。
轉載須知
如需轉載文章,請做到 1、正文前標示:轉自資料派THU(ID:DatapiTHU);2、文章結尾處附上資料派二維碼。
申請轉載,請傳送郵件至[email protected]
公眾號底部選單有驚喜哦!
企業,個人加入組織請檢視“聯盟”
往期精彩內容請檢視“號內搜”
加入志願者或聯絡我們請檢視“關於我們”
點選“閱讀原文”報名