作者: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]
公众号底部菜单有惊喜哦!
企业,个人加入组织请查看“联盟”
往期精彩内容请查看“号内搜”
加入志愿者或联系我们请查看“关于我们”
点击“阅读原文”报名