1. 首页
  2. 数据分析

机器学习系列:(四)从线性回归到逻辑回归—续篇

欢迎小伙伴们回来继续学习,本篇内容是连着上一篇

“机器学习系列:(四)从线性回归到逻辑回归”文章。

多类分类

现实中有很多问题不只是分成两类,许多问题都需要分成多个类,成为多类分类问题(Multi-class classification)。比如听到一首歌的样曲之后,可以将其归入某一种音乐风格。这类风格就有许多种。scikit-learn用one-vs.-all或one-vs.-the-rest方法实现多类分类,就是把多类中的每个类都作为二元分类处理。分类器预测样本不同类型,将具有最大置信水平的类型作为样本类型。LogisticRegression()通过one-vs.-all策略支持多类分类。下面,我们用它来分析一个多类分类问题。

假设你想看电影,而你又非常讨厌看太次的电影。所以有品位的你可以在看每部电影之前都会看一堆影评,不过你更讨厌看影评。那么下面我们就用好影评来给电影分类。

本例中,我们利用烂番茄(Rotten Tomatoes)网站影评短语数据对电影进行评价。每个影评可以归入下面5个类项:不给力(negative),不太给力(somewhat negative),中等(neutral),有点给力(somewhat positive), 给力(positive)。解释变量不会总是直白的语言,因为影评内容千差万别,有讽刺的,否定的,以及其他语义的表述,语义并不直白,这些都会让分类充满挑战。数据集可以从kaggle上下载。首先,我们还是用Pandas简单探索一下:

import zipfile# 压缩节省空间z = zipfile.ZipFile('mlslpic/train.zip')df = pd.read_csv(z.open(z.namelist()[0]), header=0, delimiter='t')
df.head()

Out[30]:

PhraseId SentenceId Phrase Sentiment
0 1 1 A series of escapades demonstrating the adage … 1
1 2 1 A series of escapades demonstrating the adage … 2
2 3 1 A series 2
3 4 1 A 2
4 5 1 series 2
df.count()

Out[31]:

PhraseId      156060 SentenceId    156060 Phrase        156060 Sentiment     156060 dtype: int64

Sentiment是响应变量,0是不给力(negative),4是给力(positive),其他以此类推。Phrase列是影评的内容。影评中每句话都被分割成一行。我们不需要考虑PhraseId列和SentenceId列。

df.Phrase.head(10)

Out[32]:

0    A series of escapades demonstrating the adage ... 1    A series of escapades demonstrating the adage ... 2                                             A series 3                                                    A 4                                               series 5    of escapades demonstrating the adage that what... 6                                                   of 7    escapades demonstrating the adage that what is... 8                                            escapades 9    demonstrating the adage that what is good for ... Name: Phrase, dtype: object
df.Sentiment.describe()

Out[33]:

count    156060.000000 mean          2.063578 std           0.893832 min           0.000000 25%           2.000000 50%           2.000000 75%           3.000000 max           4.000000 Name: Sentiment, dtype: float64
df.Sentiment.value_counts()

Out[34]:

2    79582 3    32927 1    27273 4     9206 0     7072 dtype: int64
df.Sentiment.value_counts()/df.Sentiment.count()

Out[35]:

2    0.509945 3    0.210989 1    0.174760 4    0.058990 0    0.045316 dtype: float64

可以看出,近51%都是评价为2中等(neutral)的电影。可见,在这个问题里,准确率不是一个有信息量的评价指标,因为即使很烂的分类器预测出中等水平的结果,其准确率也是51%。3有点给力(somewhat positive)的电影占21%,4给力(positive)的电影占6%,共占27%。剩下的21%就是不给力(negative),不太给力(somewhat negative)的电影。用scikit-learn来训练分类器:

import pandas as pdfrom sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.linear_model.logistic import LogisticRegressionfrom sklearn.cross_validation import train_test_splitfrom sklearn.metrics import classification_report, accuracy_score, confusion_matrixfrom sklearn.pipeline import Pipelinefrom sklearn.grid_search import GridSearchCVimport zipfilepipeline = Pipeline([     ('vect', TfidfVectorizer(stop_words='english')),     ('clf', LogisticRegression())])parameters = {     'vect__max_df': (0.25, 0.5),     'vect__ngram_range': ((1, 1), (1, 2)),     'vect__use_idf': (True, False),     'clf__C': (0.1, 1, 10),}z = zipfile.ZipFile('mlslpic/train.zip')df = pd.read_csv(z.open(z.namelist()[0]), header=0, delimiter='t')X, y = df['Phrase'], df['Sentiment'].as_matrix()X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5)grid_search = GridSearchCV(pipeline, parameters, n_jobs=3, verbose=1, scoring='accuracy')grid_search.fit(X_train, y_train)print('最佳效果:%0.3f' % grid_search.best_score_)print('最优参数组合:')best_parameters = grid_search.best_estimator_.get_params()for param_name in sorted(parameters.keys()):     print('t%s: %r' % (param_name, best_parameters[param_name]))
[Parallel(n_jobs=3)]: Done   1 jobs       | elapsed:    4.6s [Parallel(n_jobs=3)]: Done  50 jobs       | elapsed:  1.8min [Parallel(n_jobs=3)]: Done  68 out of  72 | elapsed:  3.0min remaining:   10.6s [Parallel(n_jobs=3)]: Done  72 out of  72 | elapsed:  3.3min finished
Fitting 3 folds for each of 24 candidates, totalling 72 fits 最佳效果:0.620 最优参数组合: 	clf__C: 10 	vect__max_df: 0.25 	vect__ngram_range: (1, 2) 	vect__use_idf: False

多类分类效果评估

二元分类里,混淆矩阵可以用来可视化不同分类错误的数据。每种类型的精确率,召回率和综合评价指标(F1 score)可以计算,所有预测的准确率也可以计算。

predictions = grid_search.predict(X_test)print('准确率:', accuracy_score(y_test, predictions))print('混淆矩阵:', confusion_matrix(y_test, predictions))print('分类报告:', classification_report(y_test, predictions))
准确率: 0.635024990388 混淆矩阵: [[ 1178  1701   616    71     4]  [  990  5993  6030   563    30]  [  227  3231 32668  3520   143]  [   37   401  6642  8089  1305]  [    7    30   534  2397  1623]] 分类报告:              precision    recall  f1-score   support            0       0.48      0.33      0.39      3570           1       0.53      0.44      0.48     13606           2       0.70      0.82      0.76     39789           3       0.55      0.49      0.52     16474           4       0.52      0.35      0.42      4591  avg / total       0.62      0.64      0.62     78030

我们通过网格搜索获得了最佳参数组合,最终的分类器是通过对开始的分类器不断优化得到的。

多标签分类和问题转换

前面我们讨论了二元分类,多类分类,还有一种分类问题是多标签分类(multi-label classification)。每个样本可以拥有全部类型的一部分类型。这样的例子太普遍了,比如统计班上同学一周7天里哪天有空。每个同学都会在周一到周日这7天里,根据自己的情况分别打勾。再比如常见的博客文章分类标签,一篇文章一般都有好几个标签等等。多标签分类问题一般有两种解决方法。

问题转化方法(Problem transformation)可以将多标签问题转化成单标签问题。 第一种转换方法是训练集里面每个样本通过幂运算转换成单标签。比如下面数据里面每篇文章都带有若干标签。

机器学习系列:(四)从线性回归到逻辑回归---续篇

转换方法就是用幂运算将多个类合并成一个类,比如样本1有LocalUS类,新建一个标签为Local^US类,这样多标签就变成单标签了。

机器学习系列:(四)从线性回归到逻辑回归---续篇

这样原来5个标签现在变成了7个标签。这种幂运算虽然直观,但是并不实用,因为这样做多出来的标签只有一小部分样本会用到。而且,这些标签只能在训练集里面学习这些类似,在测试集中依然无法使用。

另外一种问题转换方法就是每个标签都用二元分类处理。每个标签的分类器都预测样本是否属于该标签。我们的例子中需要5个二元分类器,第一个分类器预测样本是否应该被归入Local类,第二个分类器预测样本是否应该被归入US类,以此类推。预测最后一步就是把这些分类结果求并集,如下图所示。这个问题确保了单标签问题和多标签问题有同样的训练集,只是忽略了标签之间的关联关系。

机器学习系列:(四)从线性回归到逻辑回归---续篇

多标签分类效果评估

多标签分类效果评估与多标签分类效果评估方式不同。最常用的手段是汉明损失函数(Hamming loss)和杰卡德相似度(Jaccard similarity)。汉明损失函数表示错误标签的平均比例,是一个函数,当预测全部正确,即没有错误标签时,值为0。杰卡德相似度或杰卡德相指数(Jaccard index),是预测标签和真实标签的交集数量除以预测标签和真实标签的并集数量。其值在{0,1}之间,公式如下:

机器学习系列:(四)从线性回归到逻辑回归---续篇

import numpy as npfrom sklearn.metrics import hamming_loss, jaccard_similarity_scoreprint(hamming_loss(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[0.0, 1.0], [1.0, 1.0]])))print(hamming_loss(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[1.0, 1.0], [1.0, 1.0]])))print(hamming_loss(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[1.0, 1.0], [0.0, 1.0]])))print(jaccard_similarity_score(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[0.0, 1.0], [1.0, 1.0]])))print(jaccard_similarity_score(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[1.0, 1.0], [1.0, 1.0]])))print(jaccard_similarity_score(np.array([[0.0, 1.0], [1.0, 1.0]]), np.array([[1.0, 1.0], [0.0, 1.0]])))
0.0 0.25 0.5 1.0 0.75 0.5

总结

本章我们介绍了广义线性模型,是对普通线性回归中解释变量非正态分布情况的扩展。广义线性回归模型通过联接方程将解释变量和响应变量联接起来,和普通线性回归不同,这个方程可能是非线性的。我们重点介绍了逻辑联接方程,其图象是一种S曲线,对任意实数的返回值都在在{0,1}之间,如群体生长曲线。

之后,我们介绍了逻辑回归,一种通过逻辑联接方程联接解释变量与呈伯努力分布的响应变量的关系。逻辑回归可用于解决二元分类问题,我们用它研究了典型的垃圾短信分类问题。紧接着我们介绍了多类分类问题,其类型空间超过两个,每个样本都有且仅有一种类型,我们用one-vs.-all策略研究了通过影评对电影分类的问题。最后,我们介绍了多标签分类,其类型空间超过两个,每个样本都有至少一种标签。介绍完广义线性模型的回归和分类问题,下一章我们就来介绍非线性模型的回归和分类问题——决策树。

博主简介:风雪夜归子(英文名: Allen),机器学习算法攻城狮,喜爱钻研Machine Learning的黑科技,对Deep Learning和Artificial Intelligence充满兴趣,经常关注kaggle数据挖掘竞赛平台,对数据、Machine Learning和Artificial Intelligence有兴趣的各位童鞋可以一起探讨哦

原文始发于微信公众号(PPV课数据科学社区):机器学习系列:(四)从线性回归到逻辑回归—续篇

原创文章,作者:ppvke,如若转载,请注明出处:http://www.ppvke.com/archives/13705

联系我们

4000-51-9191

在线咨询:点击这里给我发消息

工作时间:周一至周五,9:30-18:30,节假日休息