原创 2017-02-26 余文华
乐享数据DataScientists
text2vec简介
text2vec包是由Dmitriy Selivanov于2016年10月所写的R包。此包主要是为文本分析和自然语言处理提供了一个简单高效的API框架。由于其由C++所写,同时许多部分(例如GloVe)都充分运用RcppParallel等包进行并行化操作,处理速度得到加速。并且采样流处理器,可以不必把全部数据载入内存才进行分析,有效利用了内存,可以说该包是充分考虑了NLP处理数据量庞大的现实。
text2vec包也可以说是一个文本分析的生态系统,可以进行词向量化操作(Vectorization)、Word2Vec的“升级版GloVe词嵌入表达(与word2vec比较见下图[8])、主题模型分析以及相似性度量四大方面,可以说非常的强大和实用。详情可见[官网]( http://text2vec.org/index.html)
现在就以官网给出的例子,分别来看看这个生态系统的使用吧!
一、词向量操作——进行情感分析
1.1 基本步骤
在前一期已经对基础文本分析做了一个简单的小结,运用text2vec包进行文本分析也大同小易,主要分三步:
构建一个文档-词频矩阵(document-term matrix,DTM)或者词频共现矩阵( term-co-occurrence matrix,TCM);
在DTM基础上拟合模型,包括文本(情感)分类、主题模型、相似性度量等。并进行模型的调试和验证;
最终在新的数据上运用拟合好的模型。
1.2 情感分析Demo
以text2vec包提供的影评数据为例,对5000条电影评论进行情感分析(评论正面VS.负面)。首先加载text2vec包,并运用data.table包进行数据读取。
#install.packages("text2vec")
library(text2vec)
library(data.table)
数据准备
首先运用Setkey为数据设置唯一的“主键”,并划分为训练集和测试集。
data("movie_review")
setDT(movie_review)
setkey(movie_review, id)
set.seed(2016L)
all_ids = movie_review$id
train_ids = sample(all_ids, 4000)
test_ids = setdiff(all_ids, train_ids)
train = movie_review[J(train_ids)]
test = movie_review[J(test_ids)]
文档向量化
文档向量化是text2vec的主要步骤,创建词表(vocabulary)前需要设置itoken分词迭代器,然后用create_vocabulary创建词表,形成语料文件,构建DTM矩阵。
prep_fun = tolower
#代表词语划分到什么程度
tok_fun = word_tokenizer
#步骤1.设置分词迭代器
it_train = itoken(train$review,
preprocessor = prep_fun,
tokenizer = tok_fun,
ids = train$id,
progressbar = FALSE)
#步骤2.分词#消除停用词
stop_words = c("i", "me", "my", "myself", "we", "our", "ours", "ourselves", "you", "your", "yours")
#分词函数
vocab = create_vocabulary(it_train, stopwords = stop_words)
#对低频词的修建
pruned_vocab = prune_vocabulary(vocab,
term_count_min = 10, #词频,低于10个都删掉
doc_proportion_max = 0.5,
doc_proportion_min = 0.001)
#步骤3.设置形成语料文件
vectorizer = vocab_vectorizer(pruned_vocab)
#进行hash化,提效降内存#2-ngrams增加文字信息量
#h_vectorizer = hash_vectorizer(hash_size = 2 ^ 14, ngram = c(1L, 2L))
#步骤4.构建DTM矩阵
dtm_train = create_dtm(it_train, vectorizer)
#=========================================
#优化方法#标准化,加入惩罚项
#dtm_train_l1_norm = normalize(dtm_train, "l1")
#转为TFIDF步骤
#1.设置TFIDF编译器#tfidf = TfIdf$new()
#2.转换成TFIDF格式
#dtm_train_tfidf = fit_transform(dtm_train, tfidf)
#dtm_test_tfidf = create_dtm(it_test, vectorizer) %>%
# transform(tfidf)
#或者写为
#dtm_test_tfidf = create_dtm(it_test, vectorizer) %>%
# transform(tfidf)
基于Logistic的情感标注
运用glmnet包中的binomial函数族进行Logistic的情感标注。并设置alpha=1惩罚项进行L1惩罚。(不懂的同志出门左转,在公众号历史文章“正则化及其R实现”查看)。
library(glmnet) NFOLDS = 4 glmnet_classifier = cv.glmnet(x = dtm_train, y = train[['sentiment']],
family = 'binomial',
# L1 penalty
alpha = 1,
# interested in the area under ROC curve
type.measure = "auc",
# 5-fold cross-validation
nfolds = NFOLDS,
# high value is less accurate, but has faster training
thresh = 1e-3,
# again lower number of iterations for faster training
maxit = 1e3) plot(glmnet_classifier)
验证集效果
最后验证测试集效果,AUC为0.9185.除了以上的基础分析外,还可以对数据进行标准化、TF-IDF或hash化来提高执行的效率和模型准确性。
it_test = test$review %>%
prep_fun %>%
tok_fun %>%
itoken(ids = test$id,
# turn off progressbar because it won't look nice in rmd
progressbar = FALSE)
dtm_test = create_dtm(it_test, vectorizer)
preds = predict(glmnet_classifier, dtm_test, type = 'response')[,1]
glmnet:::auc(test$sentiment, preds)
## [1] 0.9185611
二、Glove词嵌入
在Tomas Mikolov等提出word2vec后,关于词向量表示的文献就层出不穷。斯坦福大学提出GloVe: [Global Vectors for Word Representation](http://nlp.stanford.edu/projects/glove/),主要是在词语共现矩阵下因式分解。经过代码优化GloVe性能提高了2-3倍,是通过单精度浮点运算[4]。
现在我们就通过text2vec实现一个word2vec。
读取数据
给定一个语言规则的例子:“paris”之于 “france” 等于 “germany” 之于——,我们期望这个结果为“berlin”。我们以Wikipedia数据为语料进行demo。
text8_file = "./text8"
if (!file.exists(text8_file)) {
download.file("http://mattmahoney.net/dc/text8.zip", "./text8.zip")
unzip ("./text8.zip", files = "text8", exdir = "./")}
wiki = readLines(text8_file, n = 1, warn = FALSE)
创建词汇表
通过首先tokens迭代,运用流处理API节省内存使用(运用text2vec操作raw数据的首要动作)。并设置prune_vocabulary参数进行清洗最小词频。最终留下71290个terms。创建共现矩阵tcm。
# Create iterator over tokens
tokens <- space_tokenizer(wiki)
# Create vocabulary. Terms will be unigrams (simple words).
it = itoken(tokens, progressbar = FALSE)
vocab <- create_vocabulary(it)
vocab <- prune_vocabulary(vocab, term_count_min = 5L)
# Use our filtered vocabulary
vectorizer <- vocab_vectorizer(vocab,
# don't vectorize input
grow_dtm = FALSE,
# use window of 5 for context words
skip_grams_window = 5L)
tcm <- create_tcm(it, vectorizer)
运用GloVe对TCM进行因子分解
text2vec使用GloVe算法进行并行化随机梯度下降,默认情况下将使用计算机的所有核并行运算,当然也可以指定threads。
#RcppParallel::setThreadOptions(numThreads = 4)
glove = GlobalVectors$new(word_vectors_size = 50, vocabulary = vocab, x_max = 10)glove$fit(tcm, n_iter = 20)
注意:text2vec为S6类型对象,因此可以用fit或fit_transform进行S3操作。
#glove = GlobalVectors$new(word_vectors_size = 50, vocabulary = vocab, x_max = 10)
# `glove` object will be modified by `fit()` call !
fit(tcm, glove, n_iter = 20)
#now we get the word vectors:
word_vectors <- glove$get_word_vectors()
查找最近的词向量:paris – france + germany
可以看到概率最大的词向量为“berlin”。
berlin <- word_vectors["paris", , drop = FALSE] -
word_vectors["france", , drop = FALSE] +
word_vectors["germany", , drop = FALSE]
cos_sim = sim2(x = word_vectors, y = berlin, method = "cosine", norm = "l2")head(sort(cos_sim[,1], decreasing = TRUE), 5)
# berlin paris munich leipzig germany
# 0.8015347 0.7623165 0.7013252 0.6616945 0.6540700
三、主题模型LDA(Latent Dirichlet Allocation)
构建dtm与前面步骤“词向量操作”一致,后面运用LDA函数构建主题模型。
tokens = movie_review$review %>%
tolower %>%
word_tokenizer
# turn off progressbar because it won't look nice in rmd
it = itoken(tokens, ids = movie_review$id, progressbar = FALSE)
v = create_vocabulary(it) %>%
prune_vocabulary(term_count_min = 10, doc_proportion_max = 0.2)
vectorizer = vocab_vectorizer(v)
dtm = create_dtm(it, vectorizer, type = "lda_c")
#前面步骤与词向量操作一致,后面运用LDA函数构建主题模型
lda_model =
LDA$new(n_topics = 10, vocabulary = v,
doc_topic_prior = 0.1, topic_word_prior = 0.01)
doc_topic_distr =
lda_model$fit_transform(dtm, n_iter = 1000, convergence_tol = 0.01,
check_convergence_every_n = 10)
四、相似性度量
text2vec提供了2套函数集测量变量距离/相似性。他们是:
sim2(x, y, method):分别计算x*y个相似性;
psim2(x, x, method):平行地求数据的相似性,x个相似性;
dist2(x, y, method):跟sim2相反,分别计算x*y个距离;
4. pdist2(x, x, method),平行地求数据的距离,x个距离。
注意到的是,sim2与psim2一个是生成了x*y个数值,一个是生成了x个数值,区别显而易见[5]。主要有4种距离的度量方法:Jaccard距离、Cosine距离、Euclidean距离和RWMD(Relaxed Word Mover’s Distance).
举例
还是拿影评数据为例,计算文档间的相似性。
library(stringr)
library(text2vec)
data("movie_review")
# select 500 rows for faster running times
movie_review = movie_review[1:500, ]prep_fun = function(x) {
x %>%
# make text lower case
str_to_lower %>%
# remove non-alphanumeric symbols
str_replace_all("[^[:alnum:]]", " ") %>%
# collapse multiple spaces
str_replace_all("\\s+", " ")}
movie_review$review_clean = prep_fun(movie_review$review)
创建两个文档集,计算两者相似性
doc_set_1 = movie_review[1:300, ]
it1 = itoken(doc_set_1$review_clean, progressbar = FALSE)
# specially take different number of docs in second set
doc_set_2 = movie_review[301:500, ]
it2 = itoken(doc_set_2$review_clean, progressbar = FALSE)
由于需要在同一个向量空间比较文档的相似性,因此需要定义一个相同的空间和项目文档集。
it = itoken(movie_review$review_clean, progressbar = FALSE)
v = create_vocabulary(it) %>%
prune_vocabulary(doc_proportion_max = 0.1, term_count_min = 5)
vectorizer = vocab_vectorizer(v)
4.1 Jaccard similarity
# they will be in the same space because we use same vectorizer# hash_vectorizer will also work fine
dtm1 = create_dtm(it1, vectorizer)
dim(dtm1)
dtm2 = create_dtm(it2, vectorizer)
dim(dtm2)
d1_d2_jac_sim = sim2(dtm1, dtm2, method = "jaccard", norm = "none")
4.2 Cosine similarity
d1_d2_cos_sim = sim2(dtm1, dtm2, method = "cosine", norm = "l2")
4.3 Euclidean distance
x = dtm_tfidf_lsa[1:300, ]
y = dtm_tfidf_lsa[1:200, ]
m1 = dist2(x, y, method = "euclidean")
4.4 RWMD
data("movie_review")
tokens = movie_review$review %>%
tolower %>%
word_tokenizer v = create_vocabulary(itoken(tokens)) %>%
prune_vocabulary(term_count_min = 5, doc_proportion_max = 0.5)
corpus = create_corpus(itoken(tokens), vocab_vectorizer(v, skip_grams_window = 5))
dtm = get_dtm(corpus)
tcm = get_tcm(corpus)
glove_model = GloVe$new(word_vectors_size = 50, vocabulary = v, x_max = 10)
wv = glove_model$fit(tcm, n_iter = 10)
rwmd_model = RWMD(wv)
rwmd_dist = dist2(dtm[1:10, ], dtm[1:100, ], method = rwmd_model, norm = 'none')
参考文献
[1] Deep Learning 实战之word2vec http://wenku.baidu.com/link?url=GIaePrya8VtcNJIFrC91LNqlekzsX07K8dA-kRppUITgF5eyofvvz2wgmZ32DTJKa3HY-78s0Gk64Z7GlQklXvI-UEdhkWj6IIzgU6Gmr7q
[2]斯坦福大学深度学习与自然语言处理第二讲——词向量
[3]R+NLP:text2vec包——New 文本分析生态系统 No.1(一,简介)http://blog.csdn.net/sinat_26917383/article/details/53161863
[4]R+NLP︱text2vec包——BOW词袋模型做监督式情感标注案例(二,情感标注)http://blog.csdn.net/sinat_26917383/article/details/53260117
[5]R+NLP︱text2vec包——四类文本挖掘相似性指标 RWMD、cosine、Jaccard 、Euclidean (三,相似距离)http://blog.csdn.net/sinat_26917383/article/details/53286009
[6]text2vec http://text2vec.org/glove.html
[7]GloVe: Global Vectors for Word Representationhttp://nlp.stanford.edu/projects/glove/
[8]GloVe vs word2vec revisitedhttp://dsnotes.com/post/glove-enwiki/
[9]text2vec package help
要想获取分析代码,可查看原文,进入本人的GitHubhttps://github.com/Alven8816查看下载,或通过本人邮箱yuwenhuajiayou@sina.cn与本人联系
”乐享数据“个人公众号,不代表任何团体利益,亦无任何商业目的。任何形式的转载、演绎必须经过公众号联系原作者获得授权,保留一切权力。欢迎关注“乐享数据”。