NLP——自然语言处理(三)text2vec包

原创 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包进行文本分析也大同小易,主要分三步:
  1. 构建一个文档-词频矩阵(document-term matrix,DTM)或者词频共现矩阵( term-co-occurrence matrix,TCM);

  2. 在DTM基础上拟合模型,包括文本(情感)分类、主题模型、相似性度量等。并进行模型的调试和验证;

  3. 最终在新的数据上运用拟合好的模型。

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套函数集测量变量距离/相似性。他们是:
  1. sim2(x, y, method):分别计算x*y个相似性;

  2. psim2(x, x, method):平行地求数据的相似性,x个相似性;

  3. 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') 

参考文献

要想获取分析代码,可查看原文,进入本人的GitHubhttps://github.com/Alven8816查看下载,或通过本人邮箱yuwenhuajiayou@sina.cn与本人联系

”乐享数据“个人公众号,不代表任何团体利益,亦无任何商业目的。任何形式的转载、演绎必须经过公众号联系原作者获得授权,保留一切权力。欢迎关注“乐享数据”。

    原文作者:Alven
    原文地址: https://zhuanlan.zhihu.com/p/25474709
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞