白手起家学习数据科学 ——Naive Bayes之“背后的思想”(十)

来源:互联网 发布:游戏网站源码 编辑:程序博客网 时间:2024/05/21 07:14

如果人们不能互相沟通,那么社会网络就不是一个好的网络。基于此,DataSciencester网站有个大众的特点,允许用户发送消息给其他的用户。大多数用户是有责任的公民,他们只发送受欢迎的“最近好么”(how is it going?)消息,其他一些用户发送极端的垃圾邮件,关于致富方案、无需开处方的药物等,你的用户开始抱怨,所以Messaging部门的副总要求你使用数据科学找到一个方法过滤这些垃圾邮件。

无声的垃圾邮件过滤系统

S是一个”消息是垃圾邮件”事件,V是一个”包含单词viagra的消息”的事件。贝叶斯原理告诉我们,在”包含单词viagra“条件下”消息是垃圾邮件”的概率:

P(S|V)=[P(V|S)P(S)]/[P(V|S)P(S)+P(V|¬S)P(¬S)]

分子是”消息是垃圾邮件”同时”包含单词viagra消息”的概率,然而分母是”包含单词viagra消息”的概率。因此你可以简单的把这个公式看成在”包含单词viagra邮件”集合里,垃圾邮件所占的比重。

如果我们有大量知道是垃圾邮件的消息,以及大量知道是非垃圾邮件的消息,那么我们能简单的评估P(V|S)P(V|¬S)。如果我们假设任何消息都是等价的可能是垃圾邮件也可能是非垃圾邮件(所以P(S)=P( ¬S)=0.5),那么:

P(S|V)=P(V|S)/[P(V|S)+P(V|¬S)]

例如,如果垃圾邮件50%有单词viagra,但是只有1%的非垃圾邮件有单词viagra,那么在包含单词viagra邮件的前提下,是垃圾邮件的概率是:

0.5/(0.5+0.01)=98

更加复杂的垃圾邮件过滤系统

现在设想一下我们有许多单词w1,...,wn组成的词汇表,为了把这个词汇表移动真实的概率理论中,我们记Xi为事件”包含单词wi的信息”;提出评估P(Xi|S)概率,表示已知是垃圾邮件,包含第i个单词的概率;相似的评估P(Xi|¬S)概率,表示已知是非垃圾消息,包含第i个单词的概率。

朴素贝叶斯(Naive Bayes)最关键的假设是:基于已知消息是垃圾邮件或者不是的前提下,每个单词跟另外的单词是独立的。直观上,这个假设的意思是知道某个垃圾消息包含单词viagra没有给你任何信息是否同样的消息包含单词rolex,数学上表示:

P(X1=x1,...,Xn=xn|S)=P(X1=x1|S)...P(Xn=xn|S)

这是一个极端的假设(这就是”naive”的由来)。设想一下我们的词汇表只包含单词viagrarolex,所有垃圾消息一半是”cheap viagra”,另外一半是”authentic rolex”。这这个案例中,朴素贝叶斯评估包含”viagra”和”rolex”两者的垃圾消息是:

P(X1=1X2=1|S)=P(X1=1|S)P(X2=1|S)=.5.5=.25

由于我们假定的知识”viagra”和”rolex”从来不一起出现,虽然这个假设不现实,但是这个模型常常执行的 很好并被使用在真实的垃圾过滤系统中。

同理可以推出Bayes理论用于”只有viagra”垃圾邮件过滤,我们能计算一个消息是垃圾邮件的概率:

P(S|X=x)=P(X=x|S)/[P(X=x|S)+P(X=x|¬S)]

朴素贝叶斯允许我们把计算出来的概率(单词表里每个单词)简单的一起相乘。

实际中,你通常想要避免多个概率相乘,为了避免一个叫做下溢(underflow)的问题,因为计算机不能处理一个接近于零的浮点型数据(意味着小数点后面有很多位)。回顾一下线性代数log(ab)=loga+logb以及exp(logx) = x,我们通常计算p1...pn如下式:

exp(log(p1)+...log(pn))

最后的挑战就是评估P(Xi|S)和P(Xi|¬S),即在已知垃圾消息(或非垃圾消息)下包含单词wi的概率。如果我们有相当一部分”训练”消息带着标签”spam”和”nonspam”,很显然简单评估P(Xi|S)看成”spam”消息集中单词wi占的比例。

但是这引起了一个大的问题,设想一下,在我们的训练集上词汇表中”data”单词只出现在非垃圾消息中,那么我们评估P("data"|S)=0,结果是我们的朴素贝叶斯分类器总是把垃圾消息概率为0赋值给任何包含单词”data”的消息中,甚至像”data on cheap viagra and authentic rolex watches.”这样的消息,为了避免这个问题,我们通常使用某些过滤。

尤其,我们将选择pseudocount—k,评估在spam条件下第i个单词的概率:

P(Xi|S)=(k+numberofspamscontainingwi)/(2k+numberofspams)

P(Xi|¬S)也类似。我们假设另外加k个额外的spams包含这个单词以及另外加k个额外的spams不包含这个单词。

例如,在spam条件下,如果”data”发生了0/98(98个spam,有0个包含单词”data”),并且如果k为1,我们评估P("data"|S)1/100=0.01,允许我们的分类器仍然把一些非零spam概率赋值给包含单词”data”的消息中。

实施(Implementation)

现在我们有了所有技术,我们需要建立我们的分类器。首先,让我们创建一个简单的函数,把所有消息切分成不同的单词。把每个消息转换成小写,然后利用re.findall()抽出单词,这些单词由字母、数字以及省略号组成,最后使用set()得到不同的单词:

def tokenize(message):    message = message.lower() # convert to lowercase    all_words = re.findall("[a-z0-9']+", message) # extract the words    return set(all_words) # remove duplicates

我们的第二个函数对带有标签的训练集进行计数,它会返回一个字典,其key为单词,value为一个带有2个元素的list [spam_count, non_spam_count],对应在spam和nonspam消息中key出现的次数:

def count_words(training_set):    """training set consists of pairs (message, is_spam)"""    counts = defaultdict(lambda: [0, 0])    for message, is_spam in training_set:        for word in tokenize(message):            counts[word][0 if is_spam else 1] += 1    return counts

接下来的步骤是把这些计数转换成概率,这些概率利用我们之前描述的平滑操作,我们的函数会返回一个list,其每行是包含3个元素的tuple,这3个元素分别为单词;spam消息下的单词概率;nonspam消息下的单词概率:

def word_probabilities(counts, total_spams, total_non_spams, k=0.5):    """turn the word_counts into a list of triplets    w, p(w | spam) and p(w | ~spam)"""    return [(w,            (spam + k) / (total_spams + 2 * k),            (non_spam + k) / (total_non_spams + 2 * k))            for w, (spam, non_spam) in counts.iteritems()]

最后一步是使用这些单词概率(以及我们的朴素贝叶斯假设)为这些消息计算概率:

def spam_probability(word_probs, message):    message_words = tokenize(message)    log_prob_if_spam = log_prob_if_not_spam = 0.0    # iterate through each word in our vocabulary    for word, prob_if_spam, prob_if_not_spam in word_probs:        # if *word* appears in the message,        # add the log probability of seeing it        if word in message_words:            log_prob_if_spam += math.log(prob_if_spam)            log_prob_if_not_spam += math.log(prob_if_not_spam)        # if *word* doesn't appear in the message        # add the log probability of _not_ seeing it        # which is log(1 - probability of seeing it)        else:            log_prob_if_spam += math.log(1.0 - prob_if_spam)            log_prob_if_not_spam += math.log(1.0 - prob_if_not_spam)    prob_if_spam = math.exp(log_prob_if_spam)    prob_if_not_spam = math.exp(log_prob_if_not_spam)    return prob_if_spam / (prob_if_spam + prob_if_not_spam)

我们把这些连接起来组成Naive Bayes Classifier

class NaiveBayesClassifier:    def __init__(self, k=0.5):        self.k = k        self.word_probs = []    def train(self, training_set):        # count spam and non-spam messages        num_spams = len([is_spam                        for message, is_spam in training_set                        if is_spam])        num_non_spams = len(training_set) - num_spams        # run training data through our "pipeline"        word_counts = count_words(training_set)        self.word_probs = word_probabilities(word_counts,                                            num_spams,                                            num_non_spams,                                            self.k)    def classify(self, message):    return spam_probability(self.word_probs, message)

下一章节我们将要测试模型。

0 0