C#编程实现

这篇文章做了什么

  朴素贝叶斯算法是机器学习中非常重要的分类算法,用途十分广泛,如垃圾邮件处理等。而情感分析(Sentiment Analysis)是自然语言处理(Natural Language Progressing)中的重要问题,用以对文本进行正负面的判断,以及情感度评分和意见挖掘。本文借助朴素贝叶斯算法,针对文本正负面进行判别,并且利用C#进行编程实现。

不先介绍点基础?

朴素贝叶斯,真的很朴素

  朴素贝叶斯分类算法,是一种有监督学习算法,通过对训练集的学习,基于先验概率与贝叶斯公式,计算出特定条件下样本属于某一类别的概率(条件概率),从而达到分类的目的。为什么说朴素贝叶斯朴素,其实这儿的朴素是英文翻译过来的,朴素贝叶斯叫Naive Bayesian Classification,这里的"Naive"被译为了朴素,其实翻译为天真更贴切一点,因为它假设了所有的样本都是独立的,这确实"Too Naive"。因此我们就有了半朴素贝叶斯和贝叶斯网络,这些将在后面为大家呈现。

情感分析是啥

  自然语言处理(NLP)是人工智能之中非常重要的学科,与机器学习交叉甚广。而情感分析优势NLP中最为重要的内容之一,目前主要分为两大流派,一派是语言学派(推荐《自然语言处理综论》),他们从语法、句子结构、词性等角度出发,通过解析一个句子的结构来分析情感正负面或是中性;另一派是统计学派(推荐《统计自然语言处理》),通过高质量的语料库与训练集,借助统计理论与算法,完成句子的情感分析。

朴素贝叶斯如何实现情感分析

1.前期准备:

  • 做好正负面标记的文本作为训练集

  • 正负面词库

2.针对文本实现:

  • 分词,推荐使用Jieba分词引擎,无论是python, C#, 还是R语言上都有非常优秀的第三方库,而且可以导入词库

  • 获取正负面词,此时需要用到正负面词库,即通过正负面词库的匹配,筛选出文本中能够反映情感的词。

  • 进入训练集中匹配,获取每个正面或者负面的词在两个训练集的词频,如“好吃”这个词在正面训练集里出现频率为0.1%,而在负面训练集里频率为0.001%。

  • 通过贝叶斯公式计算出在文本出现“好吃”这个词的条件下位正面或者为负面的概率,例如:

  p(正面|"好吃")=p(正面)p("好吃"|正面)p(正面)p("好吃"|正面)+p(负面)p("好吃"|负面)p(正面|"好吃")=p(正面)p("好吃"|正面)p(正面)p("好吃"|正面)+p(负面)p("好吃"|负面)

  得到的概率即是在好吃这个词出现的条件下,该句子为正面的概率。

  • 然而一句话可能有多个表示情感的词,比如"不好看"、"开心",我们最终要求得在这些词出现的条件下该文本为正面的条件概率:p(正面|"好吃","不好看","开心")$,具体计算过程见如下推导,令上述三个词分别为a,b,c,正面为pos,负面为neg,具体计算过程见如下推导,令上述三个词分别为a,b,c,正面为pos,负面为neg:
p(pos|a,b,c)==p(pos)p(a,b,c|pos)p(pos)p(a,b,c|pos)+p(neg)p(a,b,c|neg)p(pos)p(a|pos)p(b|pos)p(c|pos)p(pos)p(a|pos)p(b|pos)p(c|pos)+p(neg)p(a|neg)p(b|neg)p(c|neg)(1)(2)(1)p(pos|a,b,c)=p(pos)p(a,b,c|pos)p(pos)p(a,b,c|pos)+p(neg)p(a,b,c|neg)(2)=p(pos)p(a|pos)p(b|pos)p(c|pos)p(pos)p(a|pos)p(b|pos)p(c|pos)+p(neg)p(a|neg)p(b|neg)p(c|neg)

  假设一句话为正面或是负面的先验概率均为0.5,则p(pos)=p(neg)=0.5p(pos)=p(neg)=0.5,那么(2)式等于:

=p(a|pos)p(b|pos)p(c|pos)p(a|pos)p(b|pos)p(c|pos)+p(a|neg)p(b|neg)p(c|neg)(3)(3)=p(a|pos)p(b|pos)p(c|pos)p(a|pos)p(b|pos)p(c|pos)+p(a|neg)p(b|neg)p(c|neg)
  • 拉普拉斯平滑:为了避免某个词在训练集中出现概率为0导致结果有偏,拉普拉斯平滑法讲所有词出现次数加一,在样本量很大时,每个分量加一几乎不会对最终估计结果造成太大影响,但这样的好处是很好的解决了词频为0导致的有偏问题。

  • 其他变式

    贝叶斯推断及其互联网应用(二):过滤垃圾邮件这篇文章中做了如下变形,将结果以单个词下正面或是负面的条件概率表示,但是文章并未提及拉普拉斯校准,而是用拍脑袋想出来的1%作为校准概率。由于:

p(a|pos)====p(a|pos)p(a)p(a)p(a|pos)p(a)p(a|pos)p(pos)+p(a|neg)p(neg) (全概率公式)p(a|pos)p(pos)p(a|pos)p(pos)+p(a|neg)p(neg)p(a)/p(pos)p(pos|a)p(a)p(pos)(4)(5)(6)(7)(4)p(a|pos)=p(a|pos)p(a)p(a)(5)=p(a|pos)p(a)p(a|pos)p(pos)+p(a|neg)p(neg) (全概率公式)(6)=p(a|pos)p(pos)p(a|pos)p(pos)+p(a|neg)p(neg)p(a)/p(pos)(7)=p(pos|a)p(a)p(pos)

  所以有(3)式等于:

(3)式===p(pos|a)p(pos|b)p(pos|c)p(a)p(b)p(c)p(pos)3p(pos|a)p(pos|b)p(pos|c)p(a)p(b)p(c)p(pos)3+p(neg|a)p(neg|b)p(neg|c)p(a)p(b)p(c)p(neg)3p(pos|a)p(pos|b)p(pos|c)p(pos|a)p(pos|b)p(pos|c)+p(neg|a)p(neg|b)p(neg|c)p(pos|a)p(pos|b)p(pos|c)p(pos|a)p(pos|b)p(pos|c)+(1−p(pos|a))(1−p(pos|b))(1−p(pos|c))(8)(9)(10)(8)(3)式=p(pos|a)p(pos|b)p(pos|c)p(a)p(b)p(c)p(pos)3p(pos|a)p(pos|b)p(pos|c)p(a)p(b)p(c)p(pos)3+p(neg|a)p(neg|b)p(neg|c)p(a)p(b)p(c)p(neg)3(9)=p(pos|a)p(pos|b)p(pos|c)p(pos|a)p(pos|b)p(pos|c)+p(neg|a)p(neg|b)p(neg|c)(10)=p(pos|a)p(pos|b)p(pos|c)p(pos|a)p(pos|b)p(pos|c)+(1−p(pos|a))(1−p(pos|b))(1−p(pos|c))

  以上公式均为自己推导,作者原文中并未详细写出,如涉及侵权问题请通知我删除此部分。如有问题也欢迎大家指出,谢谢~。

3.聊一聊其他的

  python号称最强大的中文自然语言处理库SnowNLP,其中的情感分析事实上非常粗糙,首先它并没有一个正负面的词库,而是把一个句子分出的所有词都参与计算,包括各种各样的无意义词、符号等等,而事实上这些词并不能反映任何情感。作者可能先验的认为正负面训练集中这些无情感词频率可以认为是近似的,但事实并非如此,所以这些词将导致计算结果有偏。

缺点:

  • 朴素贝叶斯假设每个词是独立的,但是事实并非如此,尤其是针对长篇文章,或者是出现多个复杂语气词情况,独立性假设失效。
  • 无法处理句子中比较的情况,无法落在句子中某一个产品或者属性上,难以做意见挖掘。
  • 依赖于高质量的语料库

优点:

  • 无需对句子成分进行分解,无需分析语法语义
  • 针对无法判断正负面的词时有效(“呵呵”,“卧槽”等)
  • 正负面词混杂时,依旧可以根据训练集做出较为准确的判断

说了这么多,我该如何实现呢?

在此小弟附上c#代码,如果觉得不清晰,您可以移步至我的GitHub

//首先需要加载Jieba.Net,可以通过NuGet自行安装,然后using
//建立jieba类,用以处理训练集和文本
class Jieba
{
public string doc{get;set;}
public string PosWords {get;set;}
public string NegWords {get;set;}
public string stopwords {get;set;}
public List<string> JiebaCut()
{
JiebaSegmenter jiebaseg = new JiebaSegmenter();
var segment = jiebaseg.Cut(doc);
List<string> cutresult = new List<string>();
foreach (var i in segment)
{
if (!stopwords.Contains(i))
cutresult.Add(i);
}
return cutresult;
}
public List<string> handle_sentiment(bool pos = true)
{
var words = JiebaCut();
string PosOrNegWords = pos ? PosWords:NegWords;
List<string> handle_result = new List<string>();
foreach (var word in words)
{
if (PosOrNegWords.Contains(word))
handle_result.Add(word);
}
return handle_result;
}
public List<string> handle_words()
{
var words = JiebaCut();
List<string> handle_result = new List<string>();
foreach (var word in words)
{
if ((PosWords + NegWords).Contains(word))
handle_result.Add(word);
}
return handle_result;
}
}

  然后,我们需要定义一个处理概率的类,以及派生类

    class BaseProb
{
public Dictionary<string, double> d = new Dictionary<string, double>();
public double total = 0.0;
public int none = 0;
public bool exists(string key) { return d.Keys.Contains(key); }
public double getsum() { return total; }
public void get(string key, out bool para1, out double para2)
{
if (!exists(key))
{
para1 = false; para2 = none;
}
else
{
para1 = true;para2 = d[key];
}
}
public double freq(string key)
{
bool para1;
double para2;
get(key, out para1, out para2);
return para2 / total;
}
//def samples(self):
// return self.d.keys()
}
class AddOneProb : BaseProb
{
//public Dictionary<string, double> d = new Dictionary<string, double>();
//public double total = 0.0;
//public int none = 1;
public void add(string key, int value)
{
total += value;
if (!exists(key))
{
d.Add(key, 1);
total += 1;
}
d[key] += value;
} public void DPrint()
{
Console.WriteLine(d.Count);
Console.ReadKey();
foreach (var key in d.Keys)
{
Console.WriteLine(key+" : "+d[key].ToString());
}
}
}

  接下来需要将原始训练集序列化为Json格式,方便以后调用,我使用的是LitJson,转为["pos":{"我是一个词":10},"neg":{"我也是一个词":20}]的形式,以后直接读取词频而非整个句子再进行分词与统计。我们通过以下代码读取序列化后的数据:

        public static Dictionary<string, AddOneProb> Load(string filepath, out double total)
{
string json;
using (var sr = new StreamReader(filepath, Encoding.Default))
json = sr.ReadToEnd(); JsonData jd = JsonMapper.ToObject(json);
Dictionary<string, AddOneProb> d = new Dictionary<string, AddOneProb>() { { "neg", new AddOneProb() }, { "pos", new AddOneProb() } }; foreach (var i in jd["d"]["neg"]["d"])
{
var arr = i.ToString().Replace("[","").Replace("]","").Split(',');
int count = 0;
if (int.TryParse(arr[1],out count ))
d["neg"].add(arr[0], count);
}
foreach (var i in jd["d"]["pos"]["d"])
{
var arr = i.ToString().Replace("[", "").Replace("]", "").Split(',');
int count = 0;
if (int.TryParse(arr[1], out count))
d["pos"].add(arr[0], count);
}
total = Convert.ToDouble (jd["total"].ToString());
return d;
}

  当然如果你不想进行序列化,也行,可以通过以下代码训练样本

        public static void Train_data(string negFilePath, string negWords, string posFilePath, string posWords,
ref Dictionary<string, AddOneProb> d, ref double total, string stopwordFilepath)
{
//d = new Dictionary<string, AddOneProb>() { { "pos", new AddOneProb() }, { "neg", new AddOneProb() } };
string negfile = "", posfile = "";
using (var sr1 = new StreamReader(negFilePath, Encoding.Default))
negfile = sr1.ReadToEnd();
using (var sr2 = new StreamReader(posFilePath, Encoding.Default))
posfile = sr2.ReadToEnd();
string stopwords = ReadTxtToEnd(stopwordFilepath);
List<Tuple<List<string>, string>> data = new List<Tuple<List<string>, string>>();
var sent_cut = new Jieba();
sent_cut.NegWords = negWords;
sent_cut.PosWords = posWords;
foreach (var sent in posfile.Replace("\r", "").Split('\n'))
{
sent_cut.doc = sent;
sent_cut.stopwords = stopwords;
data.Add(new Tuple<List<string>, string>(sent_cut.handle_sentiment(), "pos"));
}
Console.WriteLine("正面词库导入完毕");
foreach (var sent in negfile.Replace("\r", "").Split('\n'))
{
sent_cut.doc = sent;
sent_cut.stopwords = stopwords;
data.Add(new Tuple<List<string>, string>(sent_cut.handle_sentiment(false), "neg"));
}
Console.WriteLine("负面词库导入完毕"); foreach (var d_ in data)
{
var c = d_.Item2.ToString();
if (d_.Item1 == null)
continue;
else
{
foreach (var word in d_.Item1)
d[c].add(word, 1);
}
}
total = 0;
foreach (var value in d.Values)
{
total += value.total;
}
}

  接下来就可以写分类函数了,是这样的:

protected static Tuple<string, double> Classify(List<string> x,
Dictionary<string, AddOneProb> d, double total)
{
Dictionary<string, double> temp = new Dictionary<string, double>();
foreach (var k in d.Keys)
{
temp.Add(k, Math.Log(0.5));
foreach (var word in x)
{
Console.WriteLine(k+" : "+word + " : " + d[k].freq(word));
temp[k] += Math.Log(d[k].freq(word));
}
Console.ReadKey();
}
string ret = "";
double prob = 0;
double now;
foreach (var k in d.Keys)
{
now = 0;
foreach (var otherk in d.Keys)
{
try
{
now += Math.Exp(temp[otherk] - temp[k]);
}
catch (Exception)
{
now = 10000;
}
}
now = 1 / now;
if (now > prob)
{
ret = k;
prob = now;
}
}
return new Tuple<string, double>(ret, prob);
}
public static double classify_(string sent, Dictionary<string, AddOneProb> d,
double total, string stopwordFilepath)
{
Jieba jiebaword = new Jieba();
jiebaword.doc = sent;
jiebaword.stopwords = ReadTxtToEnd(stopwordFilepath);
var retprob = Classify(jiebaword.JiebaCut(), d, total);
if (retprob.Item1 == "pos")
return retprob.Item2;
else
return 1 - retprob.Item2;
}

  如何使用:

        static void Main(string[] args)
{
double total = 0;
var d = Train.Load(SentimentFilepath + "sentiment_json.txt",out total);
var poswords = Train.ReadTxtToEnd(SentimentFilepath + "pos.csv");
var negwords = Train.ReadTxtToEnd(SentimentFilepath + "neg.csv");
Train.Train_data(SentimentFilepath + "neg_train.csv", negwords, SentimentFilepath + "pos_train.csv", poswords,ref d, ref total, SentimentFilepath + "stopwords.csv");
string testsentence = "很忽悠,不好";
var sent = Train.classify_(testsentence, d, total, SentimentFilepath + "stopwords.csv");
Console.WriteLine(sent);
Console.ReadKey();
}

C#编程实现朴素贝叶斯算法下的情感分析的更多相关文章

  1. 朴素贝叶斯算法下的情感分析——C#编程实现

    这篇文章做了什么 朴素贝叶斯算法是机器学习中非常重要的分类算法,用途十分广泛,如垃圾邮件处理等.而情感分析(Sentiment Analysis)是自然语言处理(Natural Language Pr ...

  2. 朴素贝叶斯算法 & 应用实例

    转载请注明出处:http://www.cnblogs.com/marc01in/p/4775440.html 引 和师弟师妹聊天时经常提及,若有志于从事数据挖掘.机器学习方面的工作,在大学阶段就要把基 ...

  3. 朴素贝叶斯算法的python实现

    朴素贝叶斯 算法优缺点 优点:在数据较少的情况下依然有效,可以处理多类别问题 缺点:对输入数据的准备方式敏感 适用数据类型:标称型数据 算法思想: 朴素贝叶斯比如我们想判断一个邮件是不是垃圾邮件,那么 ...

  4. [ML学习笔记] 朴素贝叶斯算法(Naive Bayesian)

    [ML学习笔记] 朴素贝叶斯算法(Naive Bayesian) 贝叶斯公式 \[P(A\mid B) = \frac{P(B\mid A)P(A)}{P(B)}\] 我们把P(A)称为"先 ...

  5. 【十大算法实现之naive bayes】朴素贝叶斯算法之文本分类算法的理解与实现

    关于bayes的基础知识,请参考: 基于朴素贝叶斯分类器的文本聚类算法 (上) http://www.cnblogs.com/phinecos/archive/2008/10/21/1315948.h ...

  6. 朴素贝叶斯算法原理及Spark MLlib实例(Scala/Java/Python)

    朴素贝叶斯 算法介绍: 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法. 朴素贝叶斯的思想基础是这样的:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,在没有其它可用信息下,我 ...

  7. Python机器学习笔记:朴素贝叶斯算法

    朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法.对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同.比如决策树,KNN,逻辑回归,支持向 ...

  8. Python机器学习算法 — 朴素贝叶斯算法(Naive Bayes)

    朴素贝叶斯算法 -- 简介 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法.最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Baye ...

  9. 机器学习---用python实现朴素贝叶斯算法(Machine Learning Naive Bayes Algorithm Application)

    在<机器学习---朴素贝叶斯分类器(Machine Learning Naive Bayes Classifier)>一文中,我们介绍了朴素贝叶斯分类器的原理.现在,让我们来实践一下. 在 ...

随机推荐

  1. Java学习笔记--Java8 Lambda表达式

    Java Lambda表达式入门:http://blog.csdn.net/renfufei/article/details/24600507 lambda内容的介绍:http://swiftlet. ...

  2. 性能测试 - some

    1.日常业务中测试过的最大并发数,其QPS为多少? 答: 从服务端开发处得知线上某台机器单接口访问量为63万 因该接口为视频类访问接口,大多数接口集中于晚间时段.可计算QPS = 63万/8/3600 ...

  3. java中把字节数组转换为16进制字符串

    把字符串数组转换为16进制字符串 import java.security.MessageDigest; public class StringUtil { public StringUtil() { ...

  4. 二叉树的Morris遍历

    二叉树的遍历,除了上篇文章中的传统递归和使用的栈结构的非递归方式,还有如下这种Morris遍历方式,该算法的构思非常巧妙:利用前驱空闲的rightChild指针指向当前节点,形成一个环.时间复杂度和前 ...

  5. BZOJ1638: [Usaco2007 Mar]Cow Traffic 奶牛交通

    1638: [Usaco2007 Mar]Cow Traffic 奶牛交通 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 571  Solved: 199 ...

  6. 黑马程序员_Java基本数据类型对象包装类

    基本数据类型对象包装类 byte Byte short Short int Integer long Long boolean Boolean float Float double Double ch ...

  7. 【剑指offer】面试题39扩展:平衡二叉树

    题目: 输入一棵二叉树,判断该二叉树是否是平衡二叉树. 思路: 直观的思路是,判断根结点的左子树.右子树高度差是否小于1. 为避免多次访问同一结点,应该用后序遍历的方式访问. 注意:加号优先级高于条件 ...

  8. 修改xcode代码风格设置

    1.找到文件:/Applications/Xcode.app/Contents/PlugIns/IDECodeSnippetLibrary.ideplugin/Contents/Resources/S ...

  9. 【hihoCoder第十四周】无间道之并查集

    就是基础的并查集.0代表合并操作,1代表查询操作.一开始以为会卡路径压缩,忐忑的交了一版裸并查集,结果AC了.数据还是很水的. 以后坚持做hiho,当额外的练习啦~ #include <bits ...

  10. Ext.window的close的问题

    以前每次都是用的hide,关闭后隐藏窗体,下一次点击再打开,这种方法在我的随笔里面有,可是现在遇到一个问题,我的窗体里面有个formpanel,formpanel每一项都有一个默认值,意思就是修改的时 ...