2017-09-05 14:25:48 dangyalingengjia 阅读数 2036
  • AI时代,机器学习该如何入门?

    机器学习入门视频教程,该课程会告诉大家如何入门机器学习,掌握机器学习必要的基础知识,了解机器学习路径。对于机器学习,很多人的观点是:机器学习技术是今后所有技术人员都绕不过的一个门槛。 那么,普通程序员该学习机器学作为一名对机器学习心有向往的程序员,我该以什么样的姿势开始呢?不妨看看该课程。

    7197 人正在学习 去看看 CSDN讲师

学习了一点机器学习的内容:如何进行垃圾邮件分类。自己总结了一下。
试想一下我们人脑的思考方式,我们是如何判断一封邮件是垃圾邮件的:如果邮件中包含某些词,而且这些词出现的频率较高时(比如一封很短的邮件中只有几个Html标签,其中还包含“中奖”等词汇),我们把它归为“奇怪邮件”一类。当我们看了很多这样的“奇怪邮件”之后,我们判定这类邮件是我们所不需要的“垃圾邮件”,好在现在的邮箱都有垃圾箱可以帮我们分类。但是某一天我们收到腾讯客服的电话,我们真的中奖了,电话中说他们已经发了一封简短的邮件通知我,这是才焕然大悟,是不是那封邮件被当作垃圾邮件了,我们需要重新审视我们的分类标准。
那我们来试着模拟人脑的这个过程:
1,首先提取分类特征
首先需要获得一个垃圾邮件样本,然后统计样本中所有邮件中每一个单词出现的频率,并进行排序。从而得到分类特征词。
2,根据特征值计算新邮件是垃圾邮件的可能性。
新邮件中出现了某些“垃圾邮件特征词”,如何断定它是不是垃圾邮件呢。我们可以通过计算它是垃圾邮件的概率来判断。即计算出现特征词w1,…wn时,该邮件是垃圾邮件的条件概率。
介绍一下贝叶斯定理:
下面的图片截取自:http://www.chepoo.com/naive-bayesian-text-classification-algorithm-to-learn.html
贝叶斯定理
最终得出:
P(是垃圾邮件 | 含特征词w1,…wn) = P(含特征词w1 | 是垃圾邮件)*…P(wn | 是垃圾邮件) * P(是垃圾邮件) / (P(w1) * … P(wn))
如果此概率大于50%,我们认为是垃圾邮件。
注意:
这里如果某个P(含特征词w1 | 是垃圾邮件)为0的话,会使得最终结果为0,这被称为数据稀疏问题。为了解决这个问题,我们让每个特征词最少出现一次。
3,解决误杀和漏杀的问题。
如果确定有误杀及漏杀的情况,如何改进此模型呢?
这里猜测一下,是否可以通过调整P(w1 | 是垃圾邮件),来调整w1的判定权重呢?待以后学习后更新。

2017-09-06 11:15:01 gyh_420 阅读数 352
  • AI时代,机器学习该如何入门?

    机器学习入门视频教程,该课程会告诉大家如何入门机器学习,掌握机器学习必要的基础知识,了解机器学习路径。对于机器学习,很多人的观点是:机器学习技术是今后所有技术人员都绕不过的一个门槛。 那么,普通程序员该学习机器学作为一名对机器学习心有向往的程序员,我该以什么样的姿势开始呢?不妨看看该课程。

    7197 人正在学习 去看看 CSDN讲师

本次实验是使用生成学习算法来处理数据(筛选垃圾邮件)。
判别学习算法(discriminative learning algorithm):直接学习p(y|x)(比如说logistic回归)或者说是从输入直接映射到{0,1}.
生成学习算法(generative learning algorithm):对p(x|y)(和p(y))进行建模,比如高斯判别法(GDA)和朴素贝叶斯法,前者是用来处理连续数据的,后者是用来处理离散数据的。
简单的来说,判别学习算法的模型是通过一条分隔线把两种类别区分开,而生成学习算法是对两种可能的结果分别进行建模,然后分别和输入进行比对,计算出相应的概率。
比如说良性肿瘤和恶性肿瘤的问题,对良性肿瘤建立model1(y=0),对恶性肿瘤建立model2(y=1),p(x|y=0)表示是良性肿瘤的概率,p(x|y=1)表示是恶性肿瘤的概率,然后根据贝叶斯公式(Bayes rule)推导出恶性肿瘤的概率:p(y=1|x),贝叶斯公式如下:
这里写图片描述
本次实验主要是使用朴素贝叶斯法处理离散数据,高斯判别法类似,只是参数的计算方法不同。
题目如下:
这里写图片描述
数据链接:
http://openclassroom.stanford.edu/MainFolder/courses/MachineLearning/exercises/ex6materials/ex6DataPrepared.zip
原题链接:
http://openclassroom.stanford.edu/MainFolder/DocumentPage.php?course=MachineLearning&doc=exercises/ex6/ex6.html
原理:
这里就不给出详细的概念和推导了,想要了解的可以查阅其他资料,这里直接给出计算朴素贝叶斯参数的公式并做个解释:
这里写图片描述
这里1{…}表达式的意思:1{true}=1 , 1{false}=0
m代表有m个特征值(x),φj表示第j个特征向量xj的概率,^表示and的意思,所以我们在使用的时候就是算出xj的个数,除以分母即是φj|y=1和φj|y=0的值。
然后根据公式:
这里写图片描述
可以得到n个特征量对应的概率,注意这里公式上面的x是向量x,因为我们对朴素贝叶斯的假设是各特征量之间是独立的,所以计算概率可以进行乘法计算。
因为在甄别过程中还可能碰到没有加入过的特征量,但是如果按照之前的公式就会计算出0概率,而实际上这是不合理,所以需要引入拉普拉斯平滑
这里写图片描述
最后给出我们实验中用到的公式:
这里写图片描述
这里写图片描述
m代表有m个文本,本试验中有700的文本用例,k代表的是对应的特征词,ni表示第i个文本中有ni个特征词,V代表的是特征数量。
最后转换成对数进行计算:
这里写图片描述
训练部分的代码:

% train.m
% Exercise 6: Naive Bayes text classifier

clear all; close all; clc

% store the number of training examples
numTrainDocs = 700;

% store the dictionary size
numTokens = 2500;

% read the features matrix
M = dlmread('train-features.txt', ' ');
spmatrix = sparse(M(:,1), M(:,2), M(:,3), numTrainDocs, numTokens);
train_matrix = full(spmatrix);

% train_matrix now contains information about the words within the emails
% the i-th row of train_matrix represents the i-th training email
% for a particular email, the entry in the j-th column tells
% you how many times the j-th dictionary word appears in that email



% read the training labels
train_labels = dlmread('train-labels.txt');
% the i-th entry of train_labels now indicates whether document i is spam


% Find the indices for the spam and nonspam labels
spam_indices = find(train_labels);
nonspam_indices = find(train_labels == 0);

% Calculate probability of spam
prob_spam = length(spam_indices) / numTrainDocs;

% Sum the number of words in each email by summing along each row of
% train_matrix
email_lengths = sum(train_matrix, 2);%得到每个邮件中的特征词的个数,ni个
% Now find the total word counts of all the spam emails and nonspam emails
spam_wc = sum(email_lengths(spam_indices));%代表∑1{y(i)=1}ni 
nonspam_wc = sum(email_lengths(nonspam_indices));%代表∑1{y(i)=0}ni 

% Calculate the probability of the tokens in spam emails
%对应于∑∑1{xj^i=K and y(i)=1}+1 
prob_tokens_spam = (sum(train_matrix(spam_indices, :)) + 1) ./ ...
    (spam_wc + numTokens);
% Now the k-th entry of prob_tokens_spam represents phi_(k|y=1)

% Calculate the probability of the tokens in non-spam emails
prob_tokens_nonspam = (sum(train_matrix(nonspam_indices, :)) + 1)./ ...
    (nonspam_wc + numTokens);
% Now the k-th entry of prob_tokens_nonspam represents phi_(k|y=0)

分类测试部分的代码:

% test.m
% Exercise 6: Naive Bayes text classifier

% read the test matrix in the same way we read the training matrix
N = dlmread('test-features.txt', ' ');
spmatrix = sparse(N(:,1), N(:,2), N(:,3));
test_matrix = full(spmatrix);

% Store the number of test documents and the size of the dictionary
numTestDocs = size(test_matrix, 1);
numTokens = size(test_matrix, 2);


% The output vector is a vector that will store the spam/nonspam prediction
% for the documents in our test set.
output = zeros(numTestDocs, 1);

% Calculate log p(x|y=1) + log p(y=1)
% and log p(x|y=0) + log p(y=0)
% for every document
% make your prediction based on what value is higher
% (note that this is a vectorized implementation and there are other
%  ways to calculate the prediction)
log_a = test_matrix*(log(prob_tokens_spam))' + log(prob_spam);
log_b = test_matrix*(log(prob_tokens_nonspam))'+ log(1 - prob_spam);  
output = log_a > log_b;


% Read the correct labels of the test set
test_labels = dlmread('test-labels.txt');

% Compute the error on the test set
% A document is misclassified if it's predicted label is different from
% the actual label, so count the number of 1's from an exclusive "or"
numdocs_wrong = sum(xor(output, test_labels))

%Print out error statistics on the test set
fraction_wrong = numdocs_wrong/numTestDocs


这里写图片描述
注意这个地方的test_matrix为我们测试用的数据,代表了xk,那么我们需要通过φk|y=1 ^num(xk)=P(x|y=1)来换算得到,注意这里的x是向量,num表示k特征值出现的次数,因为是独立的,所以是连乘换算得到。

最后把结果和人工甄别的结果做个对比
这里写图片描述
误检率:1.9%

2018-08-01 19:59:39 imHery 阅读数 302
  • AI时代,机器学习该如何入门?

    机器学习入门视频教程,该课程会告诉大家如何入门机器学习,掌握机器学习必要的基础知识,了解机器学习路径。对于机器学习,很多人的观点是:机器学习技术是今后所有技术人员都绕不过的一个门槛。 那么,普通程序员该学习机器学作为一名对机器学习心有向往的程序员,我该以什么样的姿势开始呢?不妨看看该课程。

    7197 人正在学习 去看看 CSDN讲师

目前为止,我们将每个词出现与否作为一个特征,叫做词集模型

def setOfWords2Vec(vocabList, inputSet):#输入:词表向量+待测文本向量
    returnVec = [0]*len(vocabList)
    for eachWord in inputSet:
        if eachWord in vocabList:
            returnVec[vocabList.index(eachWord)] = 1
        else: print "the word: %s is not in my Vocabulary!" % eachWord
    return returnVec#返回词表0/1向量,反映那些词汇在文档中是否出现

但是如果一个词在文档中出现不止一次,我们需要词袋模型

 

def bagOfWords2VecMN(vocabList, inputSet):#反映词表中单词在文档中出现次数
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec#返回数字向量

其实函数基本是一样的,只不过词袋模型返回的是每个词在文档中出现的次数

def textParse(bigString):    #input is big string, #output is word list
    import re
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2] 

这里用到了正则表达式和列表解析的知识,有兴趣的可以去看。总之,函数功能是 :

返回字符串列表,去掉少于2个字符的字符串,并全部转为小写

现在,电子邮件分别位于spam和ham两个文件夹中,一个是垃圾邮件,一个是正常邮件,各25个

现在使用朴素贝叶斯进行交叉验证

意思说从中一部分用来训练,一部分用来测试,下面取40个训练,10个测试

def spamTest():
    docList=[]
    classList = []
    fullText =[]
    
    for i in range(1,26):
        #读取垃圾邮件
        wordList = textParse(open('c://Users//Hery//email//spam//%d.txt' % i).read())
        docList.append(wordList)  #list-list
        fullText.extend(wordList) #list
        classList.append(1)
        #读取正常邮件
        wordList = textParse(open('c://Users//Hery//email//ham//%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    
    vocabList = createVocabList(docList)#词表向量

    trainingSet = range(50)#50个号码
    testSet=[]             #创建测试集
    for i in range(10):    #随机抽取10个号码
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])  
    trainMat=[]
    trainClasses = []
    for docIndex in trainingSet:#range(50)剩下的40个号码
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    
    errorCount = 0
    for docIndex in testSet:    #10个随机号码
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
            print "classification error\n",docList[docIndex]
            print "classify value:",classifyNB(array(wordVector),p0V,p1V,pSpam)
            print "real value:",classList[docIndex]
            print "***********************************************************"
    print 'the error rate is: ',float(errorCount)/len(testSet)
    #return vocabList,fullText

测试结果每次都会不一样,因为训练集和测试集不确定

测试之后,当然可以再写个函数,把全部50个作为训练集,自己输入一个文档,让分类器进行分类。

2019-03-30 18:38:27 qq_29317617 阅读数 338
  • AI时代,机器学习该如何入门?

    机器学习入门视频教程,该课程会告诉大家如何入门机器学习,掌握机器学习必要的基础知识,了解机器学习路径。对于机器学习,很多人的观点是:机器学习技术是今后所有技术人员都绕不过的一个门槛。 那么,普通程序员该学习机器学作为一名对机器学习心有向往的程序员,我该以什么样的姿势开始呢?不妨看看该课程。

    7197 人正在学习 去看看 CSDN讲师

我们的任务

垃圾邮件检测是机器学习在现今互联网领域的主要应用之一。几乎所有大型电子邮箱服务提供商都内置了垃圾邮件检测系统,能够自动将此类邮件分类为“垃圾邮件”。

在此项目中,我们将使用朴素贝叶斯算法创建一个模型,该模型会通过我们对模型的训练将信息数据集分类为垃圾信息或非垃圾信息。对垃圾文本信息进行大致了解十分重要。通常它们都包含“免费”、“赢取”、“获奖者”、“现金”、“奖品”等字眼,因为这些它们专门用来吸引你的注意力,诱惑你打开信息。此外,垃圾信息的文字一般都使用大写形式和大量感叹号。收信人能轻易辨认垃圾信息,而我们的目标是训练模型帮助我们识别垃圾信息!

能够识别垃圾信息是一种二元分类问题,因为此处信息只有“垃圾信息”或“非垃圾信息”这两种分类。此外,这是一种监督式学习问题,因为我们会向模型中提供带标签数据集,模型能够从中学习规律并在日后做出预测。

第 0 步:朴素贝叶斯定理简介

贝叶斯定理是最早的概率推理算法之一,由 Reverend Bayes 提出(他用来推理上帝是否存在),该定理在某些用例中依然很有用。

理解该定理的最佳方式是通过一个例子来讲解。假设你是一名特勤人员,你接到任务,需要在共和党总统候选人的某次竞选演说中保护他/她的安全。这场竞选演说是所有人都可以参加的公开活动,你的任务并不简单,需要时刻注意危险是否存在。一种方式是对每个人都设定一个威胁因子,根据人的特征(例如年龄、性别,是否随身带包以及紧张程度等等),你可以判断此人是否存在威胁。

如果某人符合所有这些特征,已经超出了你内心中的疑虑阈值,你可以采取措施并将此人带离活动现场。贝叶斯定理的原理也是如此,我们将根据某些相关事件(某人的年龄、性别、是否带包了、紧张程度等)的发生概率计算某个事件(某人存在威胁)的概率。

你还需要考虑这些特征之间的独立性。例如,如果在活动现场,有个孩子看起来很紧张,那么与紧张的成人相比,孩子存在威胁的可能性会更低。为了深入讲解这一点,看看下面两个特征:年龄和紧张程度。假设我们单独研究这些特征,我们可以设计一个将所有紧张的人视作潜在威胁人士的模型。但是,很有可能会有很多假正例,因为现场的未成年人很有可能会紧张。因此同时考虑年龄和“紧张程度”特征肯定会更准确地反映哪些人存在威胁。

这就是该定理的“朴素”一词的含义,该定理会认为每个特征相互之间都保持独立,但实际上并非始终是这样,因此会影响到最终的结论。

简而言之,贝叶斯定理根据某些其他事件(在此例中是信息被分类为垃圾信息)的联合概率分布计算某个事件(在此例中是信息为垃圾信息)的发生概率。稍后我们将深入了解贝叶斯定理的原理,但首先了解下我们将处理的数据。

第 1.1 步:了解我们的数据集

我们将使用来自 UCI 机器学习资源库中的数据集,该资源库有大量供实验性研究的精彩数据集。这是直接数据链接。

** 下面是该数据的预览:**
ALT
数据集中的列目前没有命名,可以看出有 2 列。

第一列有两个值:“ham”,表示信息不是垃圾信息,以及“spam”,表示信息是垃圾信息。

第二列是被分类的信息的文本内容。

** 说明:**

  • 使用 read_table 方法可以将数据集导入 pandas 数据帧。因为这是一个用制表符分隔的数据集,因此我们将使用“\t”作为“sep”参数的值,表示这种分隔格式。
  • 此外,通过为 read_table() 的“names”参数指定列表 [‘label’, ‘sms_message’],重命名列。
  • 用新的列名输出数据帧的前五个值。
'''
Solution
'''
import pandas as pd
# Dataset from - https://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection
df = pd.read_table('smsspamcollection/SMSSpamCollection', sep='\t', names=['label', 'sms_message'])

# Output printing out first 5 columns
df.head()
label sms_message
0 ham Go until jurong point, crazy.. Available only ...
1 ham Ok lar... Joking wif u oni...
2 spam Free entry in 2 a wkly comp to win FA Cup fina...
3 ham U dun say so early hor... U c already then say...
4 ham Nah I don't think he goes to usf, he lives aro...

第 1.2 步:数据预处理

我们已经大概了解数据集的结构,现在将标签转换为二元变量,0 表示“ham”(即非垃圾信息),1表示“spam”,这样比较方便计算。

你可能会疑问,为何要执行这一步?答案在于 scikit-learn 处理输入的方式。Scikit-learn 只处理数字值,因此如果标签值保留为字符串,scikit-learn 会自己进行转换(更确切地说,字符串标签将转型为未知浮点值)。

如果标签保留为字符串,模型依然能够做出预测,但是稍后计算效果指标(例如计算精确率和召回率分数)时可能会遇到问题。因此,为了避免稍后出现意外的陷阱,最好将分类值转换为整数,再传入模型中。

说明:

  • 使用映射方法将“标签”列中的值转换为数字值,如下所示:
    {‘ham’:0, ‘spam’:1} 这样会将“ham”值映射为 0,将“spam”值映射为 1。
  • 此外,为了知道我们正在处理的数据集有多大,使用“shape”输出行数和列数
'''
Solution
'''
print(df.shape)
df['label'] = df.label.map({'ham':0, 'spam':1})
(5572, 2)
df['label'].head()
0    0
1    0
2    1
3    0
4    0
Name: label, dtype: int64

第 2.1 步:Bag of words

我们的数据集中有大量文本数据(5,572 行数据)。大多数机器学习算法都要求传入的输入是数字数据,而电子邮件/信息通常都是文本。

现在我们要介绍 Bag of Words (BoW) 这个概念,它用来表示要处理的问题具有“大量单词”或很多文本数据。BoW 的基本概念是拿出一段文本,计算该文本中单词的出现频率。注意:BoW 平等地对待每个单词,单词的出现顺序并不重要。

利用我们将介绍的流程,我们可以将文档集合转换成矩阵,每个文档是一行,每个单词(令牌)是一列,对应的(行,列)值是每个单词或令牌在此文档中出现的频率。

例如:

假设有四个如下所示的文档:

['Hello, how are you!', 'Win money, win from home.', 'Call me now', 'Hello, Call you tomorrow?']

我们的目标是将这组文本转换为频率分布矩阵,如下所示:
在这里插入图片描述
从图中可以看出,文档在行中进行了编号,每个单词是一个列名称,相应的值是该单词在文档中出现的频率。

我们详细讲解下,看看如何使用一小组文档进行转换。

要处理这一步,我们将使用 sklearns
count vectorizer 方法,该方法的作用如下所示:

  • 它会令牌化字符串(将字符串划分为单个单词)并为每个令牌设定一个整型 ID。
  • 它会计算每个令牌的出现次数。

** 请注意:**

  • CountVectorizer 方法会自动将所有令牌化单词转换为小写形式,避免区分“He”和“he”等单词。为此,它会使用参数 lowercase,该参数默认设为 True
  • 它还会忽略所有标点符号,避免区分后面有标点的单词(例如“hello!”)和前后没有标点的同一单词(例如“hello”)。为此,它会使用参数 token_pattern,该参数使用默认正则表达式选择具有 2 个或多个字母数字字符的令牌。
  • 要注意的第三个参数是 stop_words。停用词是指某个语言中最常用的字词,包括“am”、“an”、“and”、“the”等。 通过将此参数值设为 english,CountVectorizer 将自动忽略(输入文本中)出现在 scikit-learn 中的内置英语停用词列表中的所有单词。这非常有用,因为当我们尝试查找表明是垃圾内容的某些单词时,停用词会使我们的结论出现偏差。

我们将在之后的步骤中深入讲解在模型中应用每种预处理技巧的效果,暂时先知道在处理文本数据时,有这些预处理技巧可采用。

第 2.2 步:从头实现 Bag of Words

在深入了解帮助我们处理繁重工作的 scikit-learn 的 Bag of Words(BoW) 库之前,首先我们自己实现该步骤,以便了解该库的背后原理。

** 第 1 步:将所有字符串转换成小写形式。**

假设有一个文档集合:

documents = ['Hello, how are you!',
             'Win money, win from home.',
             'Call me now.',
             'Hello, Call hello you tomorrow?']

** 说明:**

  • 将文档集合中的所有字符串转换成小写形式。将它们保存到叫做“lower_case_documents”的列表中。你可以使用 lower() 方法在 python 中将字符串转换成小写形式。
'''
Solution:
'''
documents = ['Hello, how are you!',
             'Win money, win from home.',
             'Call me now.',
             'Hello, Call hello you tomorrow?']

lower_case_documents = []
for i in documents:
    i = i.lower()
    lower_case_documents.append(i)
print(lower_case_documents)
['hello, how are you!', 'win money, win from home.', 'call me now.', 'hello, call hello you tomorrow?']

** 第 2 步:删除所有标点符号 **

说明:
删除文档集合中的字符串中的所有标点。将它们保存在叫做“sans_punctuation_documents”的列表中。

'''
Solution:
'''
sans_punctuation_documents = []
import string

for i in lower_case_documents:
    # TODO
    i = i.translate(str.maketrans('', '', string.punctuation))
    sans_punctuation_documents.append(i)
    
print(sans_punctuation_documents)
['hello how are you', 'win money win from home', 'call me now', 'hello call hello you tomorrow']

** 第 3 步:令牌化 **

令牌化文档集合中的句子是指使用分隔符将句子拆分成单个单词。分隔符指定了我们将使用哪个字符来表示单词的开始和结束位置(例如,我们可以使用一个空格作为我们的文档集合的单词分隔符。)

说明:
使用 split() 方法令牌化“sans_punctuation_documents”中存储的字符串,并将最终文档集合存储在叫做“preprocessed_documents”的列表中。

'''
Solution:
'''
preprocessed_documents = []
for i in sans_punctuation_documents:
    i = i.split(' ')
    preprocessed_documents.append(i)
print(preprocessed_documents)
[['hello', 'how', 'are', 'you'], ['win', 'money', 'win', 'from', 'home'], ['call', 'me', 'now'], ['hello', 'call', 'hello', 'you', 'tomorrow']]

** 第 4 步:计算频率 **

我们已经获得所需格式的文档集合,现在可以数出每个单词在文档集合的每个文档中出现的次数了。为此,我们将使用 Python collections 库中的 Counter 方法。

Counter 会数出列表中每项的出现次数,并返回一个字典,键是被数的项目,相应的值是该项目在列表中的计数。

说明:
使用 Counter() 方法和作为输入的 preprocessed_documents 创建一个字典,键是每个文档中的每个单词,相应的值是该单词的出现频率。将每个 Counter 字典当做项目另存到一个叫做“frequency_list”的列表中。

'''
Solution
'''
frequency_list = []
import pprint
from collections import Counter

for i in preprocessed_documents:
    #TODO
    frequency_list.append(Counter(i))
    
pprint.pprint(frequency_list)
[Counter({'hello': 1, 'how': 1, 'are': 1, 'you': 1}),
 Counter({'win': 2, 'money': 1, 'from': 1, 'home': 1}),
 Counter({'call': 1, 'me': 1, 'now': 1}),
 Counter({'hello': 2, 'call': 1, 'you': 1, 'tomorrow': 1})]

恭喜!你从头实现了 Bag of Words 流程!正如在上一个输出中看到的,我们有一个频率分布字典,清晰地显示了我们正在处理的文本。

我们现在应该充分理解 scikit-learn 中的 sklearn.feature_extraction.text.CountVectorizer 方法的背后原理了。

我们将在下一步实现 sklearn.feature_extraction.text.CountVectorizer 方法。

第 2.3 步:在 scikit-learn 中实现 Bag of Words

我们已经从头实现了 BoW 概念,并使用 scikit-learn 以简洁的方式实现这一流程。我们将使用在上一步用到的相同文档集合。

'''
Here we will look to create a frequency matrix on a smaller document set to make sure we understand how the 
document-term matrix generation happens. We have created a sample document set 'documents'.
'''
documents = ['Hello, how are you!',
                'Win money, win from home.',
                'Call me now.',
                'Hello, Call hello you tomorrow?']

说明:
导入 sklearn.feature_extraction.text.CountVectorizer 方法并创建一个实例,命名为 ‘count_vector’。

'''
Solution
'''
from sklearn.feature_extraction.text import CountVectorizer
count_vector = CountVectorizer()

** 使用 CountVectorizer() 预处理数据 **

在第 2.2 步,我们从头实现了可以首先清理数据的 CountVectorizer() 方法。清理过程包括将所有数据转换为小写形式,并删除所有标点符号。CountVectorizer() 具有某些可以帮助我们完成这些步骤的参数,这些参数包括:

  • lowercase = True

lowercase 参数的默认值为 True,它会将所有文本都转换为小写形式。

  • token_pattern = (?u)\\b\\w\\w+\\b

token_pattern 参数具有默认正则表达式值 (?u)\\b\\w\\w+\\b,它会忽略所有标点符号并将它们当做分隔符,并将长度大于等于 2 的字母数字字符串当做单个令牌或单词。

  • stop_words

stop_words 参数如果设为 english,将从文档集合中删除与 scikit-learn 中定义的英语停用词列表匹配的所有单词。考虑到我们的数据集规模不大,并且我们处理的是信息,并不是电子邮件这样的更庞大文本来源,因此我们将不设置此参数值。

你可以通过如下所示输出 count_vector 对象,查看该对象的所有参数值:

'''
Practice node:
Print the 'count_vector' object which is an instance of 'CountVectorizer()'
'''
print(count_vector)
CountVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=<class 'numpy.int64'>, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), preprocessor=None, stop_words=None,
        strip_accents=None, token_pattern='(?u)\\b\\w\\w+\\b',
        tokenizer=None, vocabulary=None)

说明:
使用 fit() 将你的文档数据集与 CountVectorizer 对象进行拟合,并使用 get_feature_names() 方法获得被归类为特征的单词列表。

'''
Solution:
'''
count_vector.fit(documents)
count_vector.get_feature_names()
['are',
 'call',
 'from',
 'hello',
 'home',
 'how',
 'me',
 'money',
 'now',
 'tomorrow',
 'win',
 'you']

get_feature_names() 方法会返回此数据集的特征名称,即组成 ‘documents’ 词汇表的单词集合。

**
说明:**
创建一个矩阵,行是 4 个文档中每个文档的行,列是每个单词。对应的值(行,列)是该单词(在列中)在特定文档(在行中)中出现的频率。为此,你可以使用 transform() 方法并传入文档数据集作为参数。transform() 方法会返回一个 numpy 整数矩阵,你可以使用 toarray() 将其转换为数组,称之为 ‘doc_array’

'''
Solution
'''
doc_array = count_vector.transform(documents).toarray()
doc_array
array([[1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1],
       [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 2, 0],
       [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
       [0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 1]])

现在,对于单词在文档中的出现频率,我们已经获得了整洁的文档表示形式。为了方便理解,下一步我们会将此数组转换为数据帧,并相应地为列命名。

说明:
将我们获得并加载到 ‘doc_array’ 中的数组转换为数据帧,并将列名设为单词名称(你之前使用 get_feature_names() 计算了名称)。将该数据帧命名为 ‘frequency_matrix’。

'''
Solution
'''
frequency_matrix = pd.DataFrame(doc_array, columns = count_vector.get_feature_names())
frequency_matrix
are call from hello home how me money now tomorrow win you
0 1 0 0 1 0 1 0 0 0 0 0 1
1 0 0 1 0 1 0 0 1 0 0 2 0
2 0 1 0 0 0 0 1 0 1 0 0 0
3 0 1 0 2 0 0 0 0 0 1 0 1

恭喜!你为我们创建的文档数据集成功地实现了 Bag of Words 问题。

直接使用该方法的一个潜在问题是如果我们的文本数据集非常庞大(假设有一大批新闻文章或电子邮件数据),由于语言本身的原因,肯定有某些值比其他值更常见。例如“is”、“the”、“an”等单词、代词、语法结构等会使矩阵出现偏斜并影响到分析结果。

有几种方式可以减轻这种情况。一种方式是使用 stop_words 参数并将其值设为 english。这样会自动忽略 scikit-learn 中的内置英语停用词列表中出现的所有单词(来自输入文本)。

另一种方式是使用 tfidf 方法。该方法已经超出了这门课程的讲解范畴。

第 3.1 步:训练集和测试集

我们已经知道如何处理 Bag of Words 问题,现在回到我们的数据集并继续我们的分析工作。第一步是将数据集拆分为训练集和测试集,以便稍后测试我们的模型。

说明:
通过在 sklearn 中使用 train_test_split 方法,将数据集拆分为训练集和测试集。使用以下变量拆分数据:

  • X_train 是 ‘sms_message’ 列的训练数据。
  • y_train 是 ‘label’ 列的训练数据
  • X_test 是 ‘sms_message’ 列的测试数据。
  • y_test 是 ‘label’ 列的测试数据。
    输出每个训练数据和测试数据的行数。
'''
Solution

NOTE: sklearn.cross_validation will be deprecated soon to sklearn.model_selection 
'''
# split into training and testing sets
# USE from sklearn.model_selection import train_test_split to avoid seeing deprecation warning.
from sklearn.cross_validation import train_test_split

X_train, X_test, y_train, y_test = train_test_split(df['sms_message'], 
                                                    df['label'], 
                                                    random_state=1)

print('Number of rows in the total set: {}'.format(df.shape[0]))
print('Number of rows in the training set: {}'.format(X_train.shape[0]))
print('Number of rows in the test set: {}'.format(X_test.shape[0]))
Number of rows in the total set: 5572
Number of rows in the training set: 4179
Number of rows in the test set: 1393

第 3.2 步:对数据集应用 Bag of Words 流程。

我们已经拆分了数据,下个目标是按照第 2 步:Bag of words 中的步骤操作,并将数据转换为期望的矩阵格式。为此,我们将像之前一样使用 CountVectorizer()。我们需要完成两步:

  • 首先,我们需要对 CountVectorizer()拟合训练数据 (X_train) 并返回矩阵。
  • 其次,我们需要转换测试数据 (X_test) 以返回矩阵。

注意:X_train 是数据集中 ‘sms_message’ 列的训练数据,我们将使用此数据训练模型。

X_test 是 ‘sms_message’ 列的测试数据,我们将使用该数据(转换为矩阵后)进行预测。然后在后面的步骤中将这些预测与 y_test 进行比较。

我们暂时为你提供了进行矩阵转换的代码!

'''
[Practice Node]

The code for this segment is in 2 parts. Firstly, we are learning a vocabulary dictionary for the training data 
and then transforming the data into a document-term matrix; secondly, for the testing data we are only 
transforming the data into a document-term matrix.

This is similar to the process we followed in Step 2.3

We will provide the transformed data to students in the variables 'training_data' and 'testing_data'.
'''
"\n[Practice Node]\n\nThe code for this segment is in 2 parts. Firstly, we are learning a vocabulary dictionary for the training data \nand then transforming the data into a document-term matrix; secondly, for the testing data we are only \ntransforming the data into a document-term matrix.\n\nThis is similar to the process we followed in Step 2.3\n\nWe will provide the transformed data to students in the variables 'training_data' and 'testing_data'.\n"
'''
Solution
'''
# Instantiate the CountVectorizer method
count_vector = CountVectorizer()

# Fit the training data and then return the matrix
training_data = count_vector.fit_transform(X_train)

# Transform testing data and return the matrix. Note we are not fitting the testing data into the CountVectorizer()
testing_data = count_vector.transform(X_test)

第 4.1 步:从头实现贝叶斯定理

我们的数据集已经是我们希望的格式,现在可以进行任务的下一步了,即研究用来做出预测并将信息分类为垃圾信息或非垃圾信息的算法。记得在该项目的开头,我们简要介绍了贝叶斯定理,现在我们将深入讲解该定理。通俗地说,贝叶斯定理根据与相关事件有关的其他事件的概率计算该事件的发生概率。它由先验概率(我们知道的概率或提供给我们的概率)和后验概率(我们希望用先验部分计算的概率)组成。

我们用一个简单的示例从头实现贝叶斯定理。假设我们要根据某人接受糖尿病检测后获得阳性结果计算此人有糖尿病的概率。
在医学领域,此类概率非常重要,因为它们涉及的是生死情况。

我们假设:

P(D) 是某人患有糖尿病的概率。值为 0.01,换句话说,普通人群中有 1% 的人患有糖尿病(免责声明:这些值只是假设,并非任何医学研究的结论)。

P(Pos):是获得阳性测试结果的概率。

P(Neg):是获得阴性测试结果的概率。

P(Pos|D):是本身有糖尿病并且获得阳性测试结果的概率,值为 0.9,换句话说,该测试在 90% 的情况下是正确的。亦称为敏感性或真正例率。

P(Neg|~D):是本身没有糖尿病并且获得阴性测试结果的概率,值也为 0.9 ,因此在 90% 的情况下是正确的。亦称为特异性或真负例率。

贝叶斯公式如下所示:

ALT

  • P(A):A 独立发生的先验概率。在我们的示例中为 P(D),该值已经提供给我们了 。

  • P(B):B 独立发生的先验概率。在我们的示例中为 P(Pos)

  • P(A|B):在给定 B 的情况下 A 发生的后验概率,在我们的示例中为 P(D|Pos),即某人的测试结果为阳性时患有糖尿病的概率。这是我们要计算的值。

  • P(B|A):在给定 A 的情况下 B 可能发生的概率。在我们的示例中为 P(Pos|D),该值已经提供给我们了 。

将这些值代入贝叶斯定理公式中:

P(D|Pos) = P(D) * P(Pos|D) / P(Pos)

获得阳性测试结果 P(Pos) 的概率可以使用敏感性和特异性来计算,如下所示:

P(Pos) = [P(D) * Sensitivity] + [P(~D) * (1-Specificity))]

'''
Instructions:
Calculate probability of getting a positive test result, P(Pos)
'''
'''
Solution (skeleton code will be provided)
'''
# P(D)
p_diabetes = 0.01

# P(~D)
p_no_diabetes = 0.99

# Sensitivity or P(Pos|D)
p_pos_diabetes = 0.9

# Specificity or P(Neg|~D)
p_neg_no_diabetes = 0.9

# P(Pos)
p_pos = p_pos_diabetes*p_diabetes + (1 - p_neg_no_diabetes)*p_no_diabetes
print('The probability of getting a positive test result P(Pos) is: {}'.format(p_pos))
The probability of getting a positive test result P(Pos) is: 0.10799999999999998

** 我们可以利用所有这些信息计算后验概率,如下所示:**

某人测试结果为阳性时患有糖尿病的概率为:

P(D|Pos) = (P(D) * Sensitivity)) / P(Pos)

某人测试结果为阳性时没有糖尿病的概率为:

P(~D|Pos) = (P(~D) * (1-Specificity)) / P(Pos)

后验概率的和将始终为 1

'''
Instructions:
Compute the probability of an individual having diabetes, given that, that individual got a positive test result.
In other words, compute P(D|Pos).

The formula is: P(D|Pos) = (P(D) * P(Pos|D) / P(Pos)
'''
'''
Solution
'''
# P(D|Pos)
p_diabetes_pos = (p_pos_diabetes*p_diabetes)/p_pos
print('Probability of an individual having diabetes, given that that individual got a positive test result is:\
',format(p_diabetes_pos)) 
Probability of an individual having diabetes, given that that individual got a positive test result is: 0.08333333333333336
'''
Instructions:
Compute the probability of an individual not having diabetes, given that, that individual got a positive test result.
In other words, compute P(~D|Pos).

The formula is: P(~D|Pos) = P(~D) * P(Pos|~D) / P(Pos)

Note that P(Pos|~D) can be computed as 1 - P(Neg|~D). 

Therefore:
P(Pos|~D) = p_pos_no_diabetes = 1 - 0.9 = 0.1
'''
'''
Solution
'''
# P(Pos|~D)
p_pos_no_diabetes = 0.1

# P(~D|Pos)
p_no_diabetes_pos = (p_pos_no_diabetes*p_no_diabetes) / p_pos
print('Probability of an individual not having diabetes, given that that individual got a positive test result is:'\
,p_no_diabetes_pos)
Probability of an individual not having diabetes, given that that individual got a positive test result is: 0.9166666666666669

恭喜!你从头实现了贝叶斯定理。你的分析表明即使某人的测试结果为阳性,他/她也有 8.3% 的概率实际上患有糖尿病,以及 91.67% 的概率没有糖尿病。当然前提是全球只有 1% 的人群患有糖尿病,这只是个假设。

** “朴素贝叶斯”中的“朴素”一词是什么意思?**

朴素贝叶斯中的“朴素”一词实际上是指,算法在进行预测时使用的特征相互之间是独立的,但实际上并非始终这样。在我们的糖尿病示例中,我们只考虑了一个特征,即测试结果。假设我们添加了另一个特征“锻炼”。假设此特征具有二元值 01,0 表示某人一周的锻炼时间不超过 2 天,1 表示某人一周的锻炼时间超过 2 天。如果我们要同时使用这两个特征(即测试结果和“锻炼”特征的值)计算最终概率,贝叶斯定理将不可行。朴素贝叶斯是贝叶斯定理的一种延伸,假设所有特征相互之间是独立的。

第 4.2 步:从头实现朴素贝叶斯

你已经知道贝叶斯定理的详细原理,现在我们将用它来考虑有多个特征的情况。

假设有两个政党的候选人,“Jill Stein”是绿党候选人,“Gary Johnson”是自由党的候选人,两位候选人在演讲中提到“自由”、“移民”和“环境”这些字眼的概率为:

  • Jill Stein 提到“自由”的概率:0.1 ---------> P(F|J)

  • Jill Stein 提到“移民”的概率:0.1 -----> P(I|J)

  • Jill Stein 提到“环境”的概率:0.8 -----> P(E|J)

  • Gary Johnson 提到“自由”的概率:0.7 -------> P(F|G)

  • Gary Johnson 提到“移民”的概率:0.2 —> P(I|G)

  • Gary Johnson 提到“环境”的概率:0.1 —> P(E|G)

假设 Jill Stein 发表演讲的概率 P(J)0.5,Gary Johnson 也是 P(G) = 0.5

了解这些信息后,如果我们要计算 Jill Stein 提到“自由”和“移民”的概率,该怎么做呢?这时候朴素贝叶斯定理就派上用场了,我们将考虑两个特征:“自由”和“移民”。

现在我们可以定义朴素贝叶斯定理的公式:

ALT

在该公式中,y 是分类变量,即候选人的姓名,x1xn 是特征向量,即单个单词。该定理假设每个特征向量或单词 (xi) 相互之间是独立的。

为了详细讲解该公式,我们需要计算以下后验概率:

  • P(J|F,I):Jill Stein 提到“自由”和“移民”的概率。

根据上述公式和贝叶斯定理,我们可以进行以下计算:P(J|F,I) = (P(J) * P(F|J) * P(I|J)) / P(F,I)。在此等式中,P(F,I) 是在研究中提到“自由”和“移民”的概率。

  • P(G|F,I):Gary Johnson 提到“自由”和“移民”的概率。

根据上述公式,我们可以进行以下计算:P(G|F,I) = (P(G) * P(F|G) * P(I|G)) / P(F,I)

'''
Instructions: Compute the probability of the words 'freedom' and 'immigration' being said in a speech, or
P(F,I).

The first step is multiplying the probabilities of Jill Stein giving a speech with her individual 
probabilities of saying the words 'freedom' and 'immigration'. Store this in a variable called p_j_text

The second step is multiplying the probabilities of Gary Johnson giving a speech with his individual 
probabilities of saying the words 'freedom' and 'immigration'. Store this in a variable called p_g_text

The third step is to add both of these probabilities and you will get P(F,I).
'''
'''
Solution: Step 1
'''
# P(J)
p_j = 0.5

# P(F/J)
p_j_f = 0.1

# P(I/J)
p_j_i = 0.1

p_j_text = p_j * p_j_f * p_j_i 
print(p_j_text)
0.005000000000000001
'''
Solution: Step 2
'''
# P(G)
p_g = 0.5

# P(F/G)
p_g_f = 0.7

# P(I/G)
p_g_i = 0.2

p_g_text = p_g * p_g_f * p_g_i
print(p_g_text)
0.06999999999999999
'''
Solution: Step 3: Compute P(F,I) and store in p_f_i
'''
p_f_i = p_j_text + p_g_text
print('Probability of words freedom and immigration being said are: ', format(p_f_i))
Probability of words freedom and immigration being said are:  0.075

现在可以计算 P(J|F,I) 的概率,即 Jill Stein 提到“自由”和“移民”的概率,以及 P(G|F,I),即 Gary Johnson 提到“自由”和“移民”的概率。

'''
Instructions:
Compute P(J|F,I) using the formula P(J|F,I) = (P(J) * P(F|J) * P(I|J)) / P(F,I) and store it in a variable p_j_fi
'''
'''
Solution
'''
p_j_fi = p_j_text / p_f_i
print('The probability of Jill Stein saying the words Freedom and Immigration: ', format(p_j_fi))
The probability of Jill Stein saying the words Freedom and Immigration:  0.06666666666666668
'''
Instructions:
Compute P(G|F,I) using the formula P(G|F,I) = (P(G) * P(F|G) * P(I|G)) / P(F,I) and store it in a variable p_g_fi
'''
'''
Solution
'''
p_g_fi = p_g_text / p_f_i
print('The probability of Gary Johnson saying the words Freedom and Immigration: ', format(p_g_fi))
The probability of Gary Johnson saying the words Freedom and Immigration:  0.9333333333333332

可以看出,和贝叶斯定理一样,后验概率之和等于 1。恭喜!你从头实现了朴素贝叶斯定理。分析表明,绿党的 Jill Stein 在演讲中提到“自由”和“移民”的概率只有 6.6%,而自由党的 Gary Johnson 有 93.3% 的可能性会提到这两个词。

另一个比较常见的朴素贝叶斯定理应用示例是在搜索引擎中搜索“萨克拉门托国王”。为了使我们能够获得与萨克拉门托国王队 NBA 篮球队相关的结果,搜索引擎需要将这两个单词关联到一起,而不是单独处理它们,否则就会获得标有“萨克拉门托”的图片(例如风光图片)以及关于“国王”的图片(可能是历史上的国王),而实际上我们想要搜索的是关于篮球队的图片。这是一种搜索引擎将单词当做非独立个体(因此采用的是“朴素”方式)的经典示例。

将此方法应用到我们的垃圾信息分类问题上,朴素贝叶斯算法会查看每个单词,而不是将它们当做有任何联系的关联体。对于垃圾内容检测器来说,这么做通常都可行,因为有些禁用词几乎肯定会被分类为垃圾内容,例如包含“伟哥”的电子邮件通常都被归类为垃圾邮件。

第 5 步:使用 scikit-learn 实现朴素贝叶斯

幸运的是,sklearn 具有多个朴素贝叶斯实现,这样我们就不用从头进行计算。我们将使用 sklearns 的 sklearn.naive_bayes 方法对我们的数据集做出预测。

具体而言,我们将使用多项式朴素贝叶斯实现。这个分类器适合分类离散特征(例如我们的单词计数文本分类)。它会将整数单词计数作为输入。另一方面,高斯朴素贝叶斯更适合连续数据,因为它假设输入数据是高斯(正态)分布。

'''
Instructions:

We have loaded the training data into the variable 'training_data' and the testing data into the 
variable 'testing_data'.

Import the MultinomialNB classifier and fit the training data into the classifier using fit(). Name your classifier
'naive_bayes'. You will be training the classifier using 'training_data' and y_train' from our split earlier. 
'''
'''
Solution
'''
from sklearn.naive_bayes import MultinomialNB
naive_bayes = MultinomialNB()
naive_bayes.fit(training_data, y_train)
MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)
'''
Instructions:
Now that our algorithm has been trained using the training data set we can now make some predictions on the test data
stored in 'testing_data' using predict(). Save your predictions into the 'predictions' variable.
'''
'''
Solution
'''
predictions = naive_bayes.predict(testing_data)

我们已经对测试集进行预测,现在需要检查预测的准确率了。

第 6 步:评估模型

我们已经对测试集进行了预测,下一个目标是评估模型的效果。我们可以采用各种衡量指标,但首先快速总结下这些指标。

** 准确率 **衡量的是分类器做出正确预测的概率,即正确预测的数量与预测总数(测试数据点的数量)之比。

** 精确率 **指的是分类为垃圾信息的信息实际上是垃圾信息的概率,即真正例(分类为垃圾内容并且实际上是垃圾内容的单词)与所有正例(所有分类为垃圾内容的单词,无论是否分类正确)之比,换句话说,是以下公式的比值结果:

[True Positives/(True Positives + False Positives)]

** 召回率(敏感性)**表示实际上为垃圾信息并且被分类为垃圾信息的信息所占比例,即真正例(分类为垃圾内容并且实际上是垃圾内容的单词)与所有为垃圾内容的单词之比,换句话说,是以下公式的比值结果:

[True Positives/(True Positives + False Negatives)]

对于偏态分类分布问题(我们的数据集就属于偏态分类),例如如果有 100 条信息,只有 2 条是垃圾信息,剩下的 98 条不是,则准确率本身并不是很好的指标。我们将 90 条信息分类为非垃圾信息(包括 2 条垃圾信息,但是我们将其分类为非垃圾信息,因此它们属于假负例),并将 10 条信息分类为垃圾信息(所有 10 条都是假正例),依然会获得比较高的准确率分数。对于此类情形,精确率和召回率非常实用。可以通过这两个指标获得 F1 分数,即精确率和召回率分数的加权平均值。该分数的范围是 0 到 1,1 表示最佳潜在 F1 分数。

我们将使用所有四个指标确保我们的模型效果很好。这四个指标的值范围都在 0 到 1 之间,分数尽量接近 1 可以很好地表示模型的效果如何。

'''
Instructions:
Compute the accuracy, precision, recall and F1 scores of your model using your test data 'y_test' and the predictions
you made earlier stored in the 'predictions' variable.
'''
'''
Solution
'''
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
print('Accuracy score: ', format(accuracy_score(y_test, predictions)))
print('Precision score: ', format(precision_score(y_test, predictions)))
print('Recall score: ', format(recall_score(y_test, predictions)))
print('F1 score: ', format(f1_score(y_test, predictions)))
Accuracy score:  0.9885139985642498
Precision score:  0.9720670391061452
Recall score:  0.9405405405405406
F1 score:  0.9560439560439562

第 7 步:总结

和其他分类算法相比,朴素贝叶斯具有的一大主要优势是能够处理大量特征。在我们的示例中,有数千个不同的单词,每个单词都被当做一个特征。此外,即使存在不相关的特征也有很好的效果,不容易受到这种特征的影响。另一个主要优势是相对比较简单。朴素贝叶斯完全可以直接使用,很少需要调整参数,除非通常分布数据已知的情况需要调整。
它很少会过拟合数据。另一个重要优势是相对于它能处理的数据量来说,训练和预测速度很快。总之,朴素贝叶斯是非常实用的算法!

恭喜!你成功地设计了一个可以有效地预测信息是否为垃圾信息的模型!

感谢你与我们一起学习这些知识!

2019-04-05 18:26:49 weixin_44750583 阅读数 408
  • AI时代,机器学习该如何入门?

    机器学习入门视频教程,该课程会告诉大家如何入门机器学习,掌握机器学习必要的基础知识,了解机器学习路径。对于机器学习,很多人的观点是:机器学习技术是今后所有技术人员都绕不过的一个门槛。 那么,普通程序员该学习机器学作为一名对机器学习心有向往的程序员,我该以什么样的姿势开始呢?不妨看看该课程。

    7197 人正在学习 去看看 CSDN讲师

6.1.SVM建立垃圾邮件分类器

1)题目:

如今,许多电子邮件服务提供垃圾邮件过滤器,能够将电子邮件精确地分类为垃圾邮件和非垃圾邮件。在本部分练习中,您将使用SVM构建自己的垃圾邮件过滤器。
您将训练一个分类器来分类给定的电子邮件x是垃圾邮件(y = 1)还是非垃圾邮件(y = 0)。特别地,你需要将每封电子邮件转换成一个特征向量xRnx\in R^n
本练习中包含的数据集是基于SpamAssassin Public Corpus(http://spamassassin.apache.org/old/publiccorpus/ )的一个子集,对于本练习,您将只使用电子邮件正文(不包括邮件抬头)。
数据集链接: https://pan.baidu.com/s/1cEgQIvehUcLxZ0WVhxcPuQ 提取码: xejn

2)大致步骤:

  • 邮件预处理。首先读取样例邮件查看下。然后进行预处理:
    1.把整封邮件转化为小写
    2.移除所有HTML标签(超文本标记语言)
    3.将所有的URL替换为’httpaddr’
    4.将所有的地址替换为’emailaddr’
    5.将所有数字替换为’number’
    6.将所有美元符号($)替换为’dollar’
    7.将所有单词还原为词根。例如,“discount”, “discounts”, “discounted” and “discounting”都替换为“discount”
    8.移除所有非文字类型,所有的空格(tabs, newlines, spaces)调整为一个空格
    然后再对照单词表得到样例对应的序号列。
  • 提取特征。利用序号列得到邮件的一个特征向量,xRnx\in R^n,这里是一个1899维的特征向量。
  • 训练SVM。取C=0.1,核函数为线性核,用训练集训练出模型,训练精度为99.8%,再在测试集上测试,精度为98.9%。
  • 打印权重最高的前15个词,邮件中出现这些词更容易是垃圾邮件
  • 用训练好的模型预测已给的四封邮件

3)关于Python:

  • .lower( )可以转化为小写。

  • 邮件预处理时需要用到re模块,正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为”元字符”)组成的文字模式。re模块中的函数让你检查一个特定的字符串是否匹配给定的正则表达式(或给定的正则表达式是否匹配特定的字符串,这可归结为同一件事)。
    使用re.sub(pattern, repl, string, count=0, flags=0)
    pattern是需要被替换的部分,repl为替换之后的内容,string为查找的范围,count表示模式匹配后替换的最大次数,默认0表示替换所有的匹配。

  • re.split( ) 用来分割字符串。注意在分割时候不能直接用吴恩达给出的MATLAB的代码,因为有一些需要转义。

  • nltk的全称是natural languagetoolkit,是一套基于python的自然语言处理工具集。这里主要使用了词干提取的Porter提取算法。nltk.stem.PorterStemmer( )

  • try和except语句块可以用来运行可能会有问题的代码。

  • 在循环中不想要某些条件执行后面的代码可以用continue跳过本次循环直接进行下一次循环。

  • sorted函数直接对数组进行排序,np.argsort是返回排序后的坐标。

  • 关于正则表达式:
    字符”r“的意思是表示忽略后面的转义字符,这样简化了后面正则表达式里每遇到一个转义字符还得挨个转义的麻烦;[ ]里面可以用来填要匹配的字符集;\是转义字符,让它后面的字符还原它原有的含义;*匹配前面的子表达式零次或多次;+匹配前面的子表达式一次或多次;$表示只在字符末尾进行匹配。。。这里引用第一个链接里的一些表格以备后用~
    更多可参考 http://www.runoob.com/regexp/regexp-syntax.htmlhttps://blog.csdn.net/u010254900/article/details/22038741

字符 描述
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用\( 和 \)。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用\ +。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用\ ?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。
| 指明两项之间的一个选择。要匹配 |,请使用 \|。

4) 代码与结果:


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.io as scio
from sklearn import svm 
import re #处理正则表达式的模块
import nltk #自然语言处理工具包

'''============================part1 邮件预处理========================='''
#查看样例邮件
f = open('emailSample1.txt', 'r').read()
print(f)

def processEmail(email):
    email = email.lower() #转化为小写
    email = re.sub('<[^<>]+>', ' ', email) #移除所有HTML标签
    email = re.sub('(http|https)://[^\s]*', 'httpaddr', email) #将所有的URL替换为'httpaddr'
    email = re.sub('[^\s]+@[^\s]+', 'emailaddr', email) #将所有的地址替换为'emailaddr'
    email = re.sub('\d+', 'number', email) #将所有数字替换为'number'
    email = re.sub('[$]+', 'dollar', email) #将所有美元符号($)替换为'dollar'
    
    #将所有单词还原为词根//移除所有非文字类型,空格调整
    stemmer = nltk.stem.PorterStemmer() #使用Porter算法
    tokens = re.split('[ @$/#.-:&*+=\[\]?!()\{\},\'\">_<;%]', email) #把邮件分割成单个的字符串,[]里面为各种分隔符
    tokenlist = []
    for token in tokens:
        token = re.sub('[^a-zA-Z0-9]', '', token) #去掉任何非字母数字字符
        try: #porterStemmer有时会出现问题,因此用try
            token = stemmer.stem(token) #词根
        except:
            token = ''
        if len(token) < 1: 
            continue #字符串长度小于1的不添加到tokenlist里
        tokenlist.append(token)
    
    return tokenlist

#查看处理后的样例
processed_f = processEmail(f)
for i in processed_f:
    print(i, end=' ')

#得到单词表,序号为索引号+1
vocab_list = np.loadtxt('vocab.txt', dtype='str', usecols=1)
#得到词汇表中的序号
def word_indices(processed_f, vocab_list):
    indices = []
    for i in range(len(processed_f)):
        for j in range(len(vocab_list)):
            if processed_f[i]!=vocab_list[j]:
                continue
            indices.append(j+1)
    return indices

#查看样例序号
f_indices = word_indices(processed_f, vocab_list)
for i in f_indices:
    print(i, end=' ')


'''============================part2 提取特征========================='''
def emailFeatures(indices):
    features = np.zeros((1899))
    for each in indices:
        features[each-1] = 1 #若indices在对应单词表的位置上词语存在则记为1
    return features
        
sum(emailFeatures(f_indices)) #45


'''============================part3 训练SVM========================='''
#训练模型
train = scio.loadmat('spamTrain.mat')
train_x = train['X']
train_y = train['y']

clf = svm.SVC(C=0.1, kernel='linear')
clf.fit(train_x, train_y)

#精度
def accuracy(clf, x, y):
    predict_y = clf.predict(x)
    m = y.size
    count = 0
    for i in range(m):
        count = count + np.abs(int(predict_y[i])-int(y[i])) #避免溢出错误得到225
    return 1-float(count/m) 

accuracy(clf, train_x, train_y) #0.99825

#测试模型
test = scio.loadmat('spamTest.mat')
accuracy(clf, test['Xtest'], test['ytest']) #0.989


'''============================part4 高权重词========================='''
#打印权重最高的前15个词,邮件中出现这些词更容易是垃圾邮件
i = (clf.coef_).size-1
while i >1883:
    #返回从小到大排序的索引,然后再打印
    print(vocab_list[np.argsort(clf.coef_).flatten()[i]], end=' ')
    i = i-1

'''============================part5 预测邮件========================='''

t = open('spamSample2.txt', 'r').read()
#预处理
processed_f = processEmail(t) 
f_indices = word_indices(processed_f, vocab_list)
#特征提取
x = np.reshape(emailFeatures(f_indices), (1,1899))
#预测
clf.predict(x)

查看样例邮件
在这里插入图片描述

预处理后的样例(吴恩达给出的作业中的样例不对,visitor you re 他给分为visitor your了,这是由于在分割的时候是否把’ 作为分割,这样会导致后面特征提取后少一个非零项,即只有44项)
在这里插入图片描述

样例对应的词汇序号
在这里插入图片描述

训练精度和测试精度
在这里插入图片描述

15个权重最高的词,和作业中有些微差别
在这里插入图片描述

测试了给出的四封邮件,都正确分类,下面是spamSample2的结果,分类器把它分为垃圾邮件
在这里插入图片描述

本次作业没有给出知识点概括,因为上次作业6.0已经大概描述清楚了SVM的原理和模型选择的一些内容,这次作业其实主要是对练习怎么从文本中提取特征得到一个n维的特征向量,再进行模型训练。
文本提取和处理也是很难啊~ 继续学吧!

没有更多推荐了,返回首页