python编程导论_第十五课

学习安排(8月24日-8月26日)
1.主要学习视频Week8
链接(http://www.xuetangx.com/courses/MITx/6_00_2x/2014_T2/courseware/d39541ec36564a88af34d319a2f16bd7/
2.参考书第21、24章

chapter24 分类方法

最常见的监督式学习应用就是建立分类模型。分类模型也称为分类器,用于对样本进行标注,标明这个样本属于一个有限的类别集合中的哪个类。

在单分类学习中,训练集中的数据仅来自一个类别,目标是学习一个模型以预测某个样本是否属于这个类别。当难以找到不属于这个类别的训练数据时,单分类学习是比较有用的,它通常用于建立异常检测机制,例如在计算机网络中检测未知攻击。

在二分类学习(常称为二元分类)中,训练集中的样本全部来自两个类别(通常称为阳性和阴性) ,目标是找到一个可以区分两个类别的边界。 多分类学习的目标则是找到可以将多个类别区分开来的边界。

本章将介绍两种广泛使用的监督式学习方法来解决分类问题: K最近邻方法和回归方法。介
绍这些方法之前,我们先要解决一个问题:如何评价由这些方法产生的分类器?

分类器评价

对于二分类问题,可将样例根据其真是类别与分类器预测类别的组合划分为真阳性、假阳性、真阴性、假阴性四种情况,可用混淆矩阵表示模型的分类结果。
每种分类器在训练数据上的准确度可以计算如下:
准确度=\frac{真阳性+真阴性}{真阳性+假阳性+真阴性+假阴性}
当两个类的大小差不多时,用准确度评价分类器是非常合适的。存在严重的类别不平衡时,用准确度评价分类器会得到非常糟糕的结果。想像一下,如果你负责评价这样一种分类器,它用来预测某个人是否患有某种潜在的致命疾病,这种疾病的发病率大约是0.1%。这时,准确度就不是一个合适的统计量,因为只要简单地宣布所有患者都没有病,就可以得到99.9%的准确度。这种分类器对于那些要为治疗付钱的人来说真是太好了(因为没有人需要治疗!),但对于那些对自己可能患病忧心忡忡的人来说,就太不公平了。

类别不平衡时,可用如下统计量评价分类器:灵敏度=\frac{真阳性}{真阳性+假阴性}
特异度=\frac{真阴性}{真阴性+假阳性}
阳性预测值=\frac{真阳性}{真阳性+假阳性}
阴性预测值=\frac{真阴性}{真阴性+假阴性}

预测跑步者的性别

我们的任务是通过跑步者的年龄和完成时间来预测跑步者的性别。
函数getBMData从一个文件读出数据并建立一个样本集合。每个样本都是Runner类的一个实例(instance)。每名跑步者都有一个标签(性别)和一个特征向量(年龄和完成时间)。 Runner类中的方法featureDist返回两名跑步者特征向量之间的欧氏距离。下一步就是,将样本划分为一个训练集和一个先保留不用的测试集。最常用的做法是,使用80%的数据训练模型,使用剩余的20%数据对模型进行测试。函数divide80_20可以完成这一任务。请注意,训练数据是随机选取的。

class Runner(object):
    def __init__(self, gender, age, time):
        self.featureVec = (age, time)
        self.label = gender

    def featureDist(self, other):
        dist = 0.0
        for i in range(len(self.featureVec)):
            dist += abs(self.featureVec[i] - other.featureVec[i]) ** 2
        return dist ** 0.5

    def getTime(self):
        return self.featureVec[1]
    def getAge(self):
        return self.featureVec[0]
    def getLabel(self):
        return self.label
    def getFeatures(self):
        return self.featureVec

    def __str__(self):
        return str(self.getAge()) + ', ' + str(self.getTime()) \
               + ', ' + self.label

def buildMarathonExamples(fileName):
    data = getBMData(fileName)
    examples = []
    for i in range(len(data['age'])):
        a = Runner(data['gender'][i], data['age'][i],
                   data['time'][i])
        examples.append(a)
    return examples

def divide80_20(examples):
    sampleIndices = random.sample(range(len(examples)),
                                  len(examples) // 5)
    trainingSet, testSet = [], []
    for i in range(len(examples)):
        if i in sampleIndices:
           testSet.append(examples[i])
        else:
          trainingSet.append(examples[i])
    return trainingSet, testSet

现在我们已经做好了准备,可以通过各种不同的方法使用训练集来建立分类器,以预测跑步者的性别。通过对数据的检查,我们知道训练集中有58%的跑步者是男性。所以,如果我们总是猜测跑步者是男性,将会得到58%的准确度。

K最近邻方法

K最近邻方法可能是最简单的分类算法。通过这种方法“学习”的模型就是训练集本身。对新样本进行标注时,就是根据它们与训练集样本的相似度而进行的。

如下实现了一个K最近邻分类器,可以基于跑步者的年龄和完成时间对其性别进行预测。这个实现其实是一种暴力算法。函数 findKNearest 的复杂度与 exampleSet 中的样本数量 成 线 性 关系 , 因 为 它要 计 算example 与 exampleSet 中 每 个元 素 之 间 的特 征 距 离 。函 数 KNearestClassify 使用简单的多数票胜出原则来进行分类,它的复杂度是 O(len(training) * len(testSet)),因为它要对函数 findNearest 进行总共 len(testSet) 次调用。

def findKNearest(example, exampleSet, k):
    kNearest, distances = [], []
    # 建立一个列表,包含最初7个样本和它们的距离
    for i in range(k):
        kNearest.append(exampleSet[i])
        distances.append(example.featureDist(exampleSet[i]))
    maxDist = max(distances)  # 找出最大距离
    # 检查其余样本
    for e in exampleSet[k:]:
        dist = example.featureDist(e)
        if dist < maxDist:
            # 替换距离更远的邻居
            maxIndex = distances.index(maxDist)
            kNearest[maxIndex] = e
            distances[maxIndex] = dist
            maxDist = max(distances)
    return kNearest, distances


def KNearestClassify(training, testSet, label, k):
    """假设training和testSet是两个样本列表, k是整数
    使用K最近邻分类器预测testSet中的每个样本是否具有给定的标签
    whether each example in testSet has the given label
    返回真阳性、假阳性、真阴性和假阴性的数量"""
    truePos, falsePos, trueNeg, falseNeg = 0, 0, 0, 0
    for e in testSet:
        nearest, distances = findKNearest(e, training, k)
        # 进行投票
        numMatch = 0
        for i in range(len(nearest)):
            if nearest[i].getLabel() == label:
                numMatch += 1
            if numMatch > k // 2:  # 具有标签
                if e.getLabel() == label:
                    truePos += 1
                else:
                    falsePos += 1
            else:  # 不具有标签
                if e.getLabel() != label:
                    trueNeg += 1
                else:
                    falseNeg += 1
    return truePos, falsePos, trueNeg, falseNeg

基于回归的分类器

下面使用线性回归,根据训练集数据为男性和女性分别建模。


男性和女性的线性回归模型

如下代码使用LogisticRegression类为波士顿马拉松数据建立了一个模型,并进行了测试。实现applyModel时,代码首先使用列表推导式(参见5.3.2节)建立一个列表,列表中的元素是testSet中样本的特征向量。然后,代码调用model.predict_proba方法得到一个数组,数组的元素是一个值对,对应每个特征变量的预测值。最后,代码将预测值与具有该特征向量的样本的标签进行比较,记录并返回真阳性、假阳性、真阴性和假阴性结果的数量。

def applyModel(model, testSet, label, prob = 0.5):
    #为所有测试样本创建一个包含特征向量的向量
    testFeatureVecs = [e.getFeatures() for e in testSet]
    probs = model.predict_proba(testFeatureVecs)
    truePos, falsePos, trueNeg, falseNeg = 0, 0, 0, 0
    for i in range(len(probs)):
        if probs[i][1] > prob:
            if testSet[i].getLabel() == label:
                truePos += 1
            else:
                falsePos += 1
        else:
            if testSet[i].getLabel() != label:
                trueNeg += 1
            else:
                falseNeg += 1
    return truePos, falsePos, trueNeg, falseNeg

examples = buildMarathonExamples('bm_results2012.txt')
training, test = divide80_20(examples)

featureVecs, labels = [], []
for e in training:
    featureVecs.append([e.getAge(), e.getTime()])
    labels.append(e.getLabel())
model = sklearn.linear_model.LogisticRegression().fit(featureVecs, labels)
print('Feature weights for label M:',
    'age =', str(round(model.coef_[0][0], 3)) + ',',
    'time =', round(model.coef_[0][1], 3))
truePos, falsePos, trueNeg, falseNeg = \
    applyModel(model, test, 'M', 0.5)
getStats(truePos, falsePos, trueNeg, falseNeg)

我们可以在applyModel函数中调整概率阈值,使灵敏度与KNN方法的灵敏度近似相等。要找到这个阈值概率,我们可以对prob的值进行遍历,直到得到一个与KNN方法非常接近的灵敏度。
如果使用prob = 0.578——而不是0.5——调用applyModel,会得到如下结果:
准确度=0.659
灵敏度=0.714
特异度=0.586
阳性预测值=0.695
可以看出,这两个模型的性能几乎是一样的。

对于线性回归模型,知道改变决策阈值所带来的影响非常容易。因此,人们通常使用受试者工作特征曲线,或称ROC曲线,来形象地表示灵敏度和特异度之间的折衷关系。这种曲线可以绘制出多个决策阈值的真阳性率(灵敏度)和假阳性率(1 – 特异度)之间的关系。

通过计算曲线下面积,可以在多个ROC曲线之间进行比较。这个面积实际上是个概率,对于一个随机选择的阳性样本,一个好的模型将其标注为阳性的概率应该高于将一个随机选择的阴性样本标注为阳性的概率。这就是人们所说的模型的判别能力。请一定注意,判别能力和准确度是不同的,它也常被称为对概率的校准。例如,我们可以将所有估计出的概率都除以2,这时不会
改变模型的判别能力,但显然改变了模型估计的准确度。

def buildROC(model, testSet, label, title, plot = True):
    xVals, yVals = [], []
    p = 0.0
    while p <= 1.0:
        truePos, falsePos, trueNeg, falseNeg =\
            applyModel(model, testSet, label, p)
        xVals.append(1.0 - specificity(trueNeg, falsePos))
        yVals.append(sensitivity(truePos, falseNeg))
        p += 0.01
    auroc = sklearn.metrics.auc(xVals, yVals, True)
    if plot:
        pylab.plot(xVals, yVals)
        pylab.plot([0,1], [0,1,], '--')
        pylab.title(title + ' (AUROC = '+ str(round(auroc, 3)) + ')')
        pylab.xlabel('1 - Specificity')
        pylab.ylabel('Sensitivity')
    return auroc

buildROC(model, test, 'M', 'ROC for Predicting Gender')
ROC曲线和AUROC
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • https://developers.google.com/machine-learning/crash-cour...
    iOSDevLog阅读 2,648评论 1 11
  • 之前介绍过Apache Spark的基本概念以及环境准备,本篇以分类算法为入口,主要熟悉下Spark的Python...
    EnjoyTheLife阅读 5,117评论 0 4
  • 机器学习术语表 本术语表中列出了一般的机器学习术语和 TensorFlow 专用术语的定义。 A A/B 测试 (...
    yalesaleng阅读 1,958评论 0 11
  • 注:题中所指的『机器学习』不包括『深度学习』。本篇文章以理论推导为主,不涉及代码实现。 前些日子定下了未来三年左右...
    我偏笑_NSNirvana阅读 39,902评论 12 145
  • 我愿幻成手中的剪刀 亲吻你的指甲 我愿幻成精致的发卡 紧束你的长发 我愿幻成十二月的雪花 抚触你的脸颊 我愿化作刻...
    珩左先生阅读 189评论 3 3