文本挖掘:情感分析详细步骤(基础+源码)

Posted 大数据挖掘DT数据分析

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文本挖掘:情感分析详细步骤(基础+源码)相关的知识,希望对你有一定的参考价值。


词典型情感分析大致有以下几个步骤:

训练数据集、neg/pos情感词典、分词+数据清洗清洗(一、二、三级清洗步骤)、计算情感得分、模型评价


(1)在分析过程中,难免会产生很多中间变量,它们会占用大量内存。书中提到通常会将所有的临时中间变量命名为temp,只需要保证下一个temp出现之前,临时变量不会再延用就可以了。


(2)毫无疑问,如果不追求高大上的算法的话,词典法不失为一种好方法,其实有时候我们使用了很多方法,结果发现并没有什么质变,也浪费了大量时间;


比如在优化词典的时候,我希望使用高大上的算法解决问题,自动分辨出情感词,结果浪费了大量的时间,尝试了卡方统计量、各种分类器等等,结果可想而知,最后还是使用人工的方法将词典优化了一遍,是的,是用肉眼。
其实有时候看起来最笨的方法也许是现阶段最有效最合适最省事的方法,只是它看起来很low,这也许就是笨方法的高深之处,“聪明人”是不屑于使用这些方法的。


(3)仅仅使用词汇并不能非常准确的识别一条文本所表达的情感倾向。一些修辞手法例如反讽、欲扬先抑等等也会给基于词典的情感分析造成困难。



一、训练数据集


文本作为非结构化数据,在构造训练集的时候,很少会发给你完整的数据集,可能需要批量读取txt字符。


批量读取txt字符文件

如何导入?


如何用函数批量导入文本,并且能够留在R的环境之中?循环用read.table,怎么解决每个文本文件命名问题?

list函数能够有效的读入,并且存放非结构化数据。

  1. reviewpath <- "F:/R语言/train2"  

  2. completepath <- list.files(reviewpath, pattern = "*.txt$", full.names = TRUE)  


代码解读:reviewpath为文件夹的目录名字,completepath为读取文件夹中所有的文件,生成字符串(character)格式。


如何读取单文本内容?

前面文档导入,相当于是给每个文档定了位,现在需要读入单个文档内的文本信息。

文本文档读取的时候会出现很多问题,比如分隔符、制表符等,而出现乱码,需要逐行读取。


[plain]   

  1. ######批量读入文本  

  2. read.txt <- function(x) {  

  3.   des <- readLines(x)                   #每行读取  

  4.   return(paste(des, collapse = ""))     #没有return则返回最后一个函数对象  

  5. }  

  6. review <- lapply(completepath, read.txt)  

  7. #如果程序警告,这里可能是部分文件最后一行没有换行导致,不用担心。  

代码解读:read.txt是一个简单的逐行读取的函数,readLines函数,是将一段文字分成以下的形式,需要粘贴起来;

[plain]   

  1. [1] ""                                                                                                                                                              

  2. [2] "刚买的这款电脑,在自提点打开的,就发现键盘已经坏了,有个按键都快掉了,自提点不管,让去联系退换货部门,退换货部门说键盘坏了不管退换,让去惠普自己更换新键盘。"  

  3. [3] ""                                                                                                                                                              

  4. [4] "在京东刚买的东西出现问题就要四处跑去修理,他们把责任推的一干二净,现在除非你出具惠普的质检报告,他们才给受理。"                                                

  5. [5] ""      


return(paste)函数将每一行粘贴在一起,最后返回完整的文本内容;


lapply表示逐文本读取。

加入文档名字

读取了每个文档到list之中,怎么跟每个文档名字匹配在一起?


  1. docname <- list.files(reviewpath, pattern = "*.txt$")  

  2. reviewdf <- as.data.frame(cbind(docname, unlist(review)),   

  3.                           stringsAsFactors = F)   

  4. colnames(reviewdf) <- c("id", "msg")   #列名  


代码解读:list.files中,full.names=F代表返回文档名字(默认),full.names=T则定位文档;


利用as.data.frame成为一个数据框,并且不变成因子型,stringsAsFactors是因为文档名字列,很容易变成字符因子型,需要关闭这功能;

colnames修改列名,还有names也可以达到同样的效果

文本挖掘:情感分析详细步骤(基础+源码)

图 1


`read.csv`函数读取文件时,可能报警:“EOF within quoted string”,一般为数据中不正常的符号所致,常见的方法是将`quote = ""`设置为空,这样做虽然避免了警告,但是仍然解决不了问题,有时数据会对不上号,所以最好从符号上着手将一些特殊符号去除


本文中导入的数据集是清华大学李军标注的近24000个酒店评论文本和谭松波整理的12000个来自京东、携程、当当网的跨行业评论文本。并给出了每个文本数据的评分。李军老师的数据是众多的txt文件的评论文本+用rlabelclass文件来存放文本标签,可以用read.table来调用。中科院自动化所的中英文新闻语料库 http://www.datatang.com/data/13484
中文新闻分类语料库从凤凰、新浪、网易、腾讯等版面搜集。英语新闻分类语料库为Reuters-21578的ModApte版本。


  1. train<- read.csv("./train.csv",quote = "",sep = """, header = T, stringsAsFactors = F)  

  2. #没有quote,会出现Warning message:EOF within quoted string  

  3. #读入csv格式的时候,出现所有字符变成双引号,需要sep = """,来划分开,字符串分隔符的问题?  


会出现的问题:

(1)EOF within quoted string

解决方法:quote="";


(2)CSV格式被读入R内存中时,所有字符、变量内容都被加了双引号?

解决方案:需要调整,需要sep = """,来划分开。除了英文逗号可能引起`read.csv`函数读取csv文件报错以外,
#还有英文单引号(')、英文双引号(")、波浪号(~),都会引起读取时发生警告,带来csv文件或txt文件读取不完整的后果


二、正向、逆向情感词典


1、词典导入与处理


市面上关于情感词典,有多家研究机构进行了分析,并且公布了结果,比如大连理工、汉语情感词极值表、台湾大学情感NTUSD、知网Hownet情感词、中文褒贬义词典v1.0(清华大学李军)等,有些词典分为正向、逆向单词两个部分;有些放在一起,然后有单独的标签,可以cbind合并在一起。本文引用的是谭松波老师的正向、逆向情感词典。


  1. #1、情感正向词,词组+打“+1”-label  

  2. pos <- read.csv("./pos.csv", header = T, sep = ",", stringsAsFactors = F)  

  3. weight <- rep(1, length(pos[,1]))  

  4. pos <- cbind(pos, weight)  

  5.   

  6. #2、情感负向词,词组+打“-1”-label  

  7. neg <- read.csv("./neg.csv", header = T, sep = ",", stringsAsFactors = F)  

  8. weight <- rep(-1, length(neg[,1]))  

  9. neg <- cbind(neg, weight)  

代码解读:weight是标签,主动贴在正向、逆向词典上。然后进行正向、逆向词典的合并。


  1. #3、正、负向词组合并  

  2. posneg <- rbind(pos, neg)  #正负词典合并  

  3. names(posneg) <- c("term", "weight")  

  4. posneg <- posneg[!duplicated(posneg$term), ]#`duplicated`函数的作用和`unique`函数比较相似,它返回重复项的位置编号  


各个词典对情感词的倾向定义可能矛盾,出现同一个词具有情感正向和负向两种倾向的情况,尽管这种情况更加符合现实,但是违背了基于词典的情感分析的原假设,所以要将这些词去重,我们的方法是一个词如果同时属于正向和负向,仅保留正向分类。用duplicated语句,保留重复的第一个词语,详细可见博客: 


文本挖掘:情感分析详细步骤(基础+源码)

图1 


2、词典读入词库


另外既然整合了大量的词典,就要尽量保证分词器能够把这些情感词汇分出来,所以需要将情感词典添加到分词器的词典中去,虽然这种方法在特殊情况下并不一定凑效。

已知了词典,需要把情感词放到词库里面,以备后续的匹配、分词。在这分词选用Rwordseg包来进行分词。


  1. dict <- posneg[, "term"]  

  2. #library(Rwordseg)  

  3. #listDict()  #查看已有词库  

  4. #uninstallDict() #删除安装的词典    

  5. insertWords(dict)  


关于Rwordseg包,如果已经存放了词库,应该先删除原有的词库。


listDict函数是查看词库,uninstallDict函数是删除词库,insertWords是把单词加入词库。加入的词库,应该是单词,所以需要posneg[,"term"]项。


三、数据清洗+分词


1、一、二级清洗


文本挖掘中,对文本的清洗工作尤为重要,会出现比如:英文逗号、波浪线、英文单引号、英文双引号、分隔符等。一级清洗去掉一些特殊符号,二级清洗去掉一些内容较少、空缺值。


  1. sentence <- as.vector(train.test$msg) #文本内容转化为向量sentence  

  2. sentence <- gsub("[[:digit:]]*", "", sentence) #清除数字[a-zA-Z]  

  3. sentence <- gsub("[a-zA-Z]", "", sentence)   #清除英文字符  

  4. sentence <- gsub("\.", "", sentence)      #清除全英文的dot符号  

  5. train.test <- train.test[!is.na(sentence), ]          #清除一些空值文本(文本名)  

  6. sentence <- sentence[!is.na(sentence)]   #清除对应sentence里面的空值(文本内容),要先执行文本名  

  7. train.test <- train.test[!nchar(sentence) < 2, ]  #筛选字符数小于2的文本  

  8. sentence <- sentence[!nchar(sentence) < 2] #`nchar`函数对字符计数,英文叹号为R语言里的“非”函数  



2、分词


每次可能耗费时间较长的过程,都要使用少量数据预估一下时间,这是一个优秀的习惯



  1. system.time(x <- segmentCN(strwords = sentence))   



分词之后需要分出来的词语,把ID、label加上,如图2所示。参考 第四节



  1. temp <- lapply(x, length)                       #每一个元素的长度,即文本分出多少个词  

  2. temp <- unlist(temp)                            #lapply返回的是一个list,所以3行unlist  

  3.   

  4. id <- rep(train.test[, "id"], temp)             #将每一个对应的id复制相应的次数,就可以和词汇对应了  

  5.   

  6. label <- rep(train.test[, "label"], temp)       #id对应的情感倾向标签复制相同的次数  

  7. term <- unlist(x)                               #6行将list解散为向量  

  8.   

  9. testterm <- as.data.frame(cbind(id, term, label), stringsAsFactors = F) #生成一个单词-文档-数据框  


3、三级清洗——去停用词


虽然算法已经足够简单,没有必要去除停用词,但是为了显示诚意,文本分析里每一个环节都不能少,这里还是认真的去除停用词,真的不是走过场哦。


  1. stopword <- read.csv("./stopword.csv", header = T, sep = ",", stringsAsFactors = F)  

  2. stopword <- stopword[!stopword$term %in% posneg$term,]#函数`%in%`在posneg$term中查找stopword的元素,如果查到了就返回真值,没查到就返回假  

  3. testterm <- testterm[!testterm$term %in% stopword,]#去除停用词  


最后生成了图2中的前三列,weght是下面关联情感权重的结果。


图2


四、情感得分


1、关联情感权重

已经获得了训练集的分词,而且也有了情感词典+情感词权重,那么如何把情感词典中的情感权重,加入到训练集的数据集中呢?

这时候需要进行词库之间的匹配,用plyr包中的join函数就可以匹配、并合并。


  1. library(plyr)  

  2. testterm <- join(testterm, posneg)  

  3. testterm <- testterm[!is.na(testterm$weight), ]  

  4. head(testterm)  



2、计算情感得分

关联了情感权重,那么每个文档的得分自然而然可以求得,以weight为例,进行分组汇总即可,用aggregate函数。


  1. #2、计算情感指数  

  2. dictresult <- aggregate(weight ~ id, data = testterm, sum)  

  3. dictlabel <- rep(-1, length(dictresult[, 1]))  

  4. dictlabel[dictresult$weight > 0] <- 1          #很有技巧地把情感词语正负赋值到情感得分表中  

  5. dictresult <- as.data.frame(cbind(dictresult, dictlabel), stringsAsFactors = F)  



图3

得到了如图3中weight的数列,为了与原来的文本分类进行比较,需要简单知道每个文本的情感偏向,得分>0则偏向为1,得分<0,偏向为-1,这时候引入了一个辅助列,dictlabel来进行这样的操作。
dictlabel[dictresult$weight > 0] <- 1是辅助列运算的精华语句。


3、模型评价

  1. ###模型评价  

  2. temp <- unique(testterm[, c("id", "label")])  

  3. dictresult <- join(dictresult, temp)  

  4. evalue <- table(dictresult$dictlabel, dictresult$label)  


最后可以和原先的分类进行混淆矩阵评价。从结果查看,并不是很精确。
从执行的过程中我们也发现,很多不具有情感色彩的词被定义为了情感词,例如的、了、还、在、我、都、把、上等字词,这些字词都是高频字词,而我们的计算方法按照出现频次重复计算,所以导致上面的结果偏差很大。
暂时的改进办法:修改优化词典,去除这类词汇,或者更改为去重计算,即一条评论中某词无论出现多少次都只计算一次权重。

via:http://blog.csdn.net/sinat--26917383/article/details/51313336

以上是关于文本挖掘:情感分析详细步骤(基础+源码)的主要内容,如果未能解决你的问题,请参考以下文章

Python做文本挖掘的情感极性分析

snownlp情感分析源码解析

应用Python做文本挖掘的情感极性分析

文本挖掘-避孕药主题情感分析

iDST-文本挖掘算法专家-情感分析&文本反垃圾-杭州

短文本情感分析