第五章:進(jìn)一步探索分類(lèi)

訓(xùn)練集和測(cè)試集

通常我們將數(shù)據(jù)集分為兩個(gè)部分,第一部分用來(lái)構(gòu)造分類(lèi)器,因此稱為訓(xùn)練集;另一部分用來(lái)評(píng)估分類(lèi)器的結(jié)果,因此稱為測(cè)試集。

訓(xùn)練集和測(cè)試集在數(shù)據(jù)挖掘中很常用。

數(shù)據(jù)挖掘工程師不會(huì)用同一個(gè)數(shù)據(jù)集去訓(xùn)練和測(cè)試程序,因?yàn)槿绻褂糜?xùn)練集去測(cè)試分類(lèi)器,得到的結(jié)果肯定是百分之百準(zhǔn)確,所以這種做法不可取。

將數(shù)據(jù)集拆分成一大一小兩個(gè)部分的做法就產(chǎn)生了,前者用來(lái)訓(xùn)練,否則用來(lái)測(cè)試。不過(guò)這種做法也有問(wèn)題:如果分割的時(shí)候不湊巧,就會(huì)引發(fā)異常。

解決方法之一就是將數(shù)據(jù)集按不同的方式拆分,測(cè)試多次,取結(jié)果的平均值。比如我們將數(shù)據(jù)結(jié)構(gòu)拆分為均等的兩份:

我們可以先用第一部分做訓(xùn)練集,第二部分做測(cè)試集,然后再反過(guò)來(lái),取兩次測(cè)試的平均結(jié)果。我們還可以將數(shù)據(jù)集分成三份,用兩個(gè)部分來(lái)做訓(xùn)練集,一個(gè)部分來(lái)做測(cè)試集,迭代三次:

  • 1.使用 Part 1 和 Part 2 訓(xùn)練,使用 Part 3 測(cè)試;

  • 2.使用 Part 1 和 Part 3 訓(xùn)練,使用 Part 2 測(cè)試;

  • 3.使用 Part 2 和 Part 3 訓(xùn)練,使用 Part 1 測(cè)試;

最后取三次測(cè)試的平均結(jié)果。

在數(shù)據(jù)挖掘中,通常的做法是將數(shù)據(jù)集拆分成十份,并按上述方式進(jìn)行迭代測(cè)試。因此這種方式也成為——十折交叉驗(yàn)證。

十折交叉驗(yàn)證

第一步:將數(shù)據(jù)分成10份

第二步:重復(fù)以下步驟10次

    1. 每次迭代我們保留一個(gè)桶,比如第一次迭代保留木桶 1,第二次保留木桶 2。
    1. 我們使用剩余的 9 個(gè)桶來(lái)訓(xùn)練分類(lèi)器,比如第一次迭代使用木桶 2 至 10 來(lái)訓(xùn)練。
    1. 我們用剛才保留的一個(gè)桶來(lái)進(jìn)行測(cè)試,并記錄結(jié)果,比如:35 個(gè)籃球運(yùn)動(dòng)員分類(lèi)正確, 29 個(gè)普通人分類(lèi)正確。

第三步:合并結(jié)果

留一法

在數(shù)據(jù)挖掘領(lǐng)域,N折交叉驗(yàn)證又稱為留一法。

優(yōu)點(diǎn):

(1)我們用幾乎所有的數(shù)據(jù)進(jìn)行訓(xùn)練,然后用一個(gè)數(shù)據(jù)進(jìn)行測(cè)試。

(2)確定性

缺點(diǎn):

(1)計(jì)算時(shí)間很長(zhǎng)

(2)分層問(wèn)題

結(jié)論:留一法對(duì)小數(shù)據(jù)集是合適的,但大多數(shù)情況下我們會(huì)選擇十折交叉驗(yàn)證。

混淆矩陣

混淆矩陣的對(duì)角線(綠色字體)表示正確的人數(shù),因此求的準(zhǔn)確率是:

代碼實(shí)現(xiàn)

# -*- coding:utf-8 -*-

# 將數(shù)據(jù)等分成十份的示例代碼

'''
Created on 2018年11月27日

@author: KingSley
'''

import random

def buckets(filename, bucketName, separator, classColumn):
    """
    filename 源文件名
    bucketName 十個(gè)目標(biāo)文件的前綴名
    separator 分隔符,如制表符、逗號(hào)等
    classColumn 表示數(shù)據(jù)所屬分類(lèi)的那一列的序號(hào)
    """
    
    # 將數(shù)據(jù)分為 10 份
    numberOfBuckets = 10
    data = {}
    # 讀取數(shù)據(jù),并按分類(lèi)放置
    with open(filename) as f:
        lines = f.readlines()
    for line in lines:
        if separator != '\t':
            line = line.replace(separator, '\t')
        # 獲取分類(lèi)
        category = line.split()[classColumn]
        data.setdefault(category, [])
        data[category].append(line)       
    # 初始化分桶
    buckets = []
    for i in range(numberOfBuckets):
        buckets.append([])       
    # 將各個(gè)類(lèi)別的數(shù)據(jù)均勻地放置到桶中
    for k in data.keys():
        # 打亂分類(lèi)順序
        random.shuffle(data[k])
        bNum = 0
        # 分桶
        for item in data[k]:
            buckets[bNum].append(item)
            bNum = (bNum + 1) % numberOfBuckets
            
    # 寫(xiě)入文件
    for bNum in range(numberOfBuckets):
        f = open("%s-%02i" % (bucketName, bNum + 1), 'w')
        for item in buckets[bNum]:
            f.write(item)
        f.close()
  
# 調(diào)用示例      
buckets("pimaSmall.txt", 'pimaSmall',',',8)

Kappa指標(biāo)

將對(duì)角線相加(35 + 88 + 28 = 151)除以合計(jì)(200)就可以了,結(jié)果是0.755。

首先,我們將上表中的數(shù)據(jù)抹去一部分,只留下合計(jì):

真實(shí)的體操運(yùn)動(dòng)員一共有60人,隨機(jī)分類(lèi)器會(huì)將其中的20%(12人)分類(lèi)為體操,50%(30人)分類(lèi)為籃球,30%(18人)分類(lèi)為馬拉松,填入表格:

繼續(xù)用這種方法填充空白。

100個(gè)真實(shí)的籃球運(yùn)動(dòng)員,20%(20人)分到體操,50%(50人)分到籃球,30%(30人)分到馬拉松。

從而得到隨機(jī)分類(lèi)器的準(zhǔn)確率是:

Kappa指標(biāo)可以用來(lái)衡量我們之前構(gòu)造的分類(lèi)器和隨機(jī)分類(lèi)器的差異,公式為:


# -*- coding:utf-8 -*-

# 十折交叉驗(yàn)證

'''
Created on 2018年11月27日

@author: KingSley
'''

from dataclasses import fields

class Classifier:
    def __init__(self, bucketPrefix, testBucketNumber, dataFormat):
        """該分類(lèi)器程序?qū)?bucketPrefix 指定的一系列文件中讀取數(shù)據(jù),
        并留出 testBucketNumber 指定的桶來(lái)做測(cè)試集,其余的做訓(xùn)練集。
        dataFormat 用來(lái)表示數(shù)據(jù)的格式,如:
        "class num num num num num comment"
        """
        
        self.medianAndDeviation = []
        
        # 從文件中讀取文件

        self.format = dataFormat.strip().split('\t')
        self.data = []
        # 用 1-10 來(lái)標(biāo)記桶
        for i in range(1, 11):
            # 判斷該桶時(shí)候包含在訓(xùn)練集中
            if i != testBucketNumber:
                filename = "%s-%02i" % (bucketPrefix, i)
                f = open(filename)
                lines = f.readlines()
                f.close()
                for line in lines[1:]:
                    fields = line.strip().split('\t')
                    ignore = []
                    vector = []
                    for i in range(len(fields)):
                        if self.format[i] == 'num':
                            vector.append(float(fields[i]))
                        elif self.format[i] == 'comment':
                            ignore.append(fields[i])
                        elif self.format[i] == 'class':
                            classification = fields[i]
                    self.data.append((classification, vector, ignore))
        self.rawData = list(self.data)
        # 獲取特征向量的長(zhǎng)度
        self.vlen = len(self.data[0][1])
        # 標(biāo)準(zhǔn)化數(shù)據(jù)
        for i in range(self.vlen):
            self.normalizeColumn(i)
            
    def getMedian(self, alist):
        """返回中位數(shù)"""
        if alist == []:
            return []
        blist = sorted(alist)
        length = len(alist)
        if length % 2 == 1:
            # 列表有奇數(shù)個(gè)元素,返回中間元素
            return blist[int(((length + 1) / 2) -  1)]
        else:
            # 列表有偶數(shù)個(gè)元素,返回總量?jī)蓚€(gè)元素的均值
            v1 = blist[int(length / 2)]
            v2 = blist[(int(length / 2) - 1)]
            return (v1 + v2) / 2.0
        
    def getAbsoluteStandardDeviation(self, alist, median):
        """計(jì)算絕對(duì)偏差"""
        sum = 0
        for item in alist:
            sum += abs(item - median)
        return sum / len(alist)
    
    def normalizeColumn(self, columnNumber):
        """標(biāo)準(zhǔn)化 self.data 中的 columnNumber 列"""
        # 將該列所有值提取到一個(gè)列表中
        col = [v[1][columnNumber] for v in self.data]
        median = self.getMedian(col)
        asd = self.getAbsoluteStandardDeviation(col, median)
        #print("Median: %f   ASD = %f" % (median, asd))
        self.medianAndDeviation.append((median, asd))
        for v in self.data:
            v[1][columnNumber] = (v[1][columnNumber] - median) / asd

    def normalizeVector(self, v):
        """對(duì)每列的中位數(shù)和絕對(duì)偏差,計(jì)算標(biāo)準(zhǔn)化向量 v"""
        vector = list(v)
        for i in range(len(vector)):
            (median, asd) = self.medianAndDeviation[i]
            vector[i] = (vector[i] - median) / asd
        return vector
    
    def testBucket(self, bucketPrefix, bucketNumber):
        """讀取 bucketPrefix - bucketNumber 所指定的文件作為測(cè)試集"""
        
        filename = "%s-%02i" % (bucketPrefix, bucketNumber)
        f = open(filename)
        lines = f.readlines()
        totals = {}
        f.close()
        for line in lines:
            data = line.strip().split('\t')
            vector = []
            classInColumn = -1
            for i in range(len(self.format)):
                if self.format[i] == 'num':
                    vector.append(float(data[i]))
                elif self.format[i] == 'class':
                    classInColumn = i
            theRealClass = data[classInColumn]
            classifiedAs = self.classify(vector)
            totals.setdefault(theRealClass, {})
            totals[theRealClass].setdefault(classifiedAs, 0)
            totals[theRealClass][classifiedAs] += 1
        return totals
        
    def manhattan(self, vector1, vector2):
        """計(jì)算曼哈頓距離"""
        return sum(map(lambda v1, v2: abs(v1 - v2), vector1, vector2))
    
    def nearestNeighbor(self, itemVector):
        """返回 itemVector 的鄰近"""
        return min([(self.manhattan(itemVector, item[1]), item) for item in self.data])
    
    def classify(self, itemVector):
        """預(yù)測(cè) itemVector 的分類(lèi)"""
        return self.nearestNeighbor(self.normalizeVector(itemVector))[1][0]
    

def tenfold(bucketPrefix, dataFormat):
    results = {}
    for i in range(1, 11):
        c = Classifier(bucketPrefix, i, dataFormat)
        t = c.testBucket(bucketPrefix, i)
        for (key, value) in t.items():
            results.setdefault(key, {})
            for (ckey, cvalue) in value.items():
                results[key].setdefault(ckey, 0)
                results[key][ckey] += cvalue
            
    # 輸出結(jié)果
    categories = list(results.keys())
    categories.sort()
    print(   "\n       Classified as: ")
    header =    "        "
    subheader = "      +"
    for category in categories:
        header += category + "   "
        subheader += "----+"
    print (header)
    print (subheader)
    total = 0.0
    correct = 0.0
    for category in categories:
        row = category + "    |"
        for c2 in categories:
            if c2 in results[category]:
                count = results[category][c2]
            else:
                count = 0
            row += " %2i |" % count
            total += count
            if c2 == category:
                correct += count
        print(row)
    print(subheader)
    print("\n%5.3f percent correct" %((correct * 100) / total))
    print("total of %i instances" % total)
    
tenfold("mpgData\mpgData", "class\tnum\tnum\tnum\tnum\tnum\tcomment")

參考原文作者:Ron Zacharski CC BY-NC 3.0] https://github.com/egrcc/guidetodatamining

參考原文原文 http://guidetodatamining.com/

參考譯文來(lái)自 @egrcchttps://github.com/egrcc/guidetodatamining

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,306評(píng)論 2 89
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,063評(píng)論 25 709
  • 昨天晚上在群里看到有討論信仰的問(wèn)題,我就回了一句有信仰是好事,起碼有敬畏之心。后續(xù)大家就信仰和敬畏似乎展開(kāi)了討論,...
    潔_寞碎閱讀 229評(píng)論 0 0
  • 無(wú)力吐槽,昨晚兩把,隊(duì)友實(shí)在不給力啊,逆天也坑的很。不玩了,這游戲不適合我。
    柒灬月流火閱讀 216評(píng)論 0 0

友情鏈接更多精彩內(nèi)容