训练集和测试集
通常我们将数据集分为两个部分,第一部分用来构造分类器,因此称为训练集;另一部分用来评估分类器的结果,因此称为测试集。
训练集和测试集在数据挖掘中很常用。
数据挖掘工程师不会用同一个数据集去训练和测试程序,因为如果使用训练集去测试分类器,得到的结果肯定是百分之百准确,所以这种做法不可取。
将数据集拆分成一大一小两个部分的做法就产生了,前者用来训练,否则用来测试。不过这种做法也有问题:如果分割的时候不凑巧,就会引发异常。
解决方法之一就是将数据集按不同的方式拆分,测试多次,取结果的平均值。比如我们将数据结构拆分为均等的两份:
我们可以先用第一部分做训练集,第二部分做测试集,然后再反过来,取两次测试的平均结果。我们还可以将数据集分成三份,用两个部分来做训练集,一个部分来做测试集,迭代三次:
1.使用 Part 1 和 Part 2 训练,使用 Part 3 测试;
2.使用 Part 1 和 Part 3 训练,使用 Part 2 测试;
3.使用 Part 2 和 Part 3 训练,使用 Part 1 测试;
最后取三次测试的平均结果。
在数据挖掘中,通常的做法是将数据集拆分成十份,并按上述方式进行迭代测试。因此这种方式也成为——十折交叉验证。
十折交叉验证
第一步:将数据分成10份
第二步:重复以下步骤10次
- 每次迭代我们保留一个桶,比如第一次迭代保留木桶 1,第二次保留木桶 2。
- 我们使用剩余的 9 个桶来训练分类器,比如第一次迭代使用木桶 2 至 10 来训练。
- 我们用刚才保留的一个桶来进行测试,并记录结果,比如:35 个篮球运动员分类正确, 29 个普通人分类正确。
第三步:合并结果
留一法
在数据挖掘领域,N折交叉验证又称为留一法。
优点:
(1)我们用几乎所有的数据进行训练,然后用一个数据进行测试。
(2)确定性
缺点:
(1)计算时间很长
(2)分层问题
结论:留一法对小数据集是合适的,但大多数情况下我们会选择十折交叉验证。
混淆矩阵
混淆矩阵的对角线(绿色字体)表示正确的人数,因此求的准确率是:
代码实现
# -*- coding:utf-8 -*-
# 将数据等分成十份的示例代码
'''
Created on 2018年11月27日
@author: KingSley
'''
import random
def buckets(filename, bucketName, separator, classColumn):
"""
filename 源文件名
bucketName 十个目标文件的前缀名
separator 分隔符,如制表符、逗号等
classColumn 表示数据所属分类的那一列的序号
"""
# 将数据分为 10 份
numberOfBuckets = 10
data = {}
# 读取数据,并按分类放置
with open(filename) as f:
lines = f.readlines()
for line in lines:
if separator != '\t':
line = line.replace(separator, '\t')
# 获取分类
category = line.split()[classColumn]
data.setdefault(category, [])
data[category].append(line)
# 初始化分桶
buckets = []
for i in range(numberOfBuckets):
buckets.append([])
# 将各个类别的数据均匀地放置到桶中
for k in data.keys():
# 打乱分类顺序
random.shuffle(data[k])
bNum = 0
# 分桶
for item in data[k]:
buckets[bNum].append(item)
bNum = (bNum + 1) % numberOfBuckets
# 写入文件
for bNum in range(numberOfBuckets):
f = open("%s-%02i" % (bucketName, bNum + 1), 'w')
for item in buckets[bNum]:
f.write(item)
f.close()
# 调用示例
buckets("pimaSmall.txt", 'pimaSmall',',',8)
Kappa指标
将对角线相加(35 + 88 + 28 = 151)除以合计(200)就可以了,结果是0.755。
首先,我们将上表中的数据抹去一部分,只留下合计:
真实的体操运动员一共有60人,随机分类器会将其中的20%(12人)分类为体操,50%(30人)分类为篮球,30%(18人)分类为马拉松,填入表格:
继续用这种方法填充空白。
100个真实的篮球运动员,20%(20人)分到体操,50%(50人)分到篮球,30%(30人)分到马拉松。
从而得到随机分类器的准确率是:
Kappa指标可以用来衡量我们之前构造的分类器和随机分类器的差异,公式为:
# -*- coding:utf-8 -*-
# 十折交叉验证
'''
Created on 2018年11月27日
@author: KingSley
'''
from dataclasses import fields
class Classifier:
def __init__(self, bucketPrefix, testBucketNumber, dataFormat):
"""该分类器程序将从 bucketPrefix 指定的一系列文件中读取数据,
并留出 testBucketNumber 指定的桶来做测试集,其余的做训练集。
dataFormat 用来表示数据的格式,如:
"class num num num num num comment"
"""
self.medianAndDeviation = []
# 从文件中读取文件
self.format = dataFormat.strip().split('\t')
self.data = []
# 用 1-10 来标记桶
for i in range(1, 11):
# 判断该桶时候包含在训练集中
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)
# 获取特征向量的长度
self.vlen = len(self.data[0][1])
# 标准化数据
for i in range(self.vlen):
self.normalizeColumn(i)
def getMedian(self, alist):
"""返回中位数"""
if alist == []:
return []
blist = sorted(alist)
length = len(alist)
if length % 2 == 1:
# 列表有奇数个元素,返回中间元素
return blist[int(((length + 1) / 2) - 1)]
else:
# 列表有偶数个元素,返回总量两个元素的均值
v1 = blist[int(length / 2)]
v2 = blist[(int(length / 2) - 1)]
return (v1 + v2) / 2.0
def getAbsoluteStandardDeviation(self, alist, median):
"""计算绝对偏差"""
sum = 0
for item in alist:
sum += abs(item - median)
return sum / len(alist)
def normalizeColumn(self, columnNumber):
"""标准化 self.data 中的 columnNumber 列"""
# 将该列所有值提取到一个列表中
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):
"""对每列的中位数和绝对偏差,计算标准化向量 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 所指定的文件作为测试集"""
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):
"""计算曼哈顿距离"""
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):
"""预测 itemVector 的分类"""
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
# 输出结果
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