训练与测试

我们的最近邻分类器有多好?为了回答这个问题,我们需要了解我们的分类正确的频率。如果患者患有慢性肾脏疾病,我们的分类器检测到它的可能性有多大?

如果患者在训练集中,我们可以立即知道。我们已经知道该患者的类别。因此,我们可以直接比较我们的预测和患者的真实类别。

但分类器的目的是对不在训练集中的“新”患者进行预测。我们不知道这些患者的类别,但我们可以根据分类器做出预测。如何判断预测是否正确?

一种方法是等待对患者进行进一步的医学检测,然后检查我们的预测是否与检测结果一致。但按照这种方法,当我们能够说出预测的准确概率时,它对于帮助患者已经不再有用了。

相反,我们将在一些已知真实类别的患者上尝试我们的分类器。然后,我们将计算分类器正确的时间比例。这个比例将作为分类器能够准确预测其类别的所有新患者比例的估计值。这被称为“测试”。

[In ]:
import matplotlib
#matplotlib.use('Agg')
path_data = '../../../assets/data/'
from datascience import *
%matplotlib inline
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import math
import scipy.stats as stats
plt.style.use('fivethirtyeight')
[In ]:
def standard_units(x):
    return (x - np.mean(x))/np.std(x)
[In ]:
def distance(pt1, pt2):
    return np.sqrt(np.sum((pt1 - pt2)**2))

def all_dists(training, p):
    attributes = training.drop('Class')
    def dist_point_row(row):
        return distance(np.array(row), p)
    return attributes.apply(dist_point_row)

def table_with_distances(training, p):
    return training.with_column('Distance', all_dists(training, p))

def closest(training, p, k):
    with_dists = table_with_distances(training, p)
    sorted_by_dist = with_dists.sort('Distance')
    topk = sorted_by_dist.take(np.arange(k))
    return topk

def majority(topkclasses):
    ones = topkclasses.where('Class', are.equal_to(1)).num_rows
    zeros = topkclasses.where('Class', are.equal_to(0)).num_rows
    if ones > zeros:
        return 1
    else:
        return 0

def classify(training, p, k):
    closestk = closest(training, p, k)
    topkclasses = closestk.select('Class')
    return majority(topkclasses)
[In ]:
def classify_grid(training, test, k):
    c = make_array()
    for i in range(test.num_rows):
        # Run the classifier on the ith patient in the test set
        c = np.append(c, classify(training, make_array(test.row(i)), k))   
    return c
[In ]:
ckd = Table.read_table(path_data + 'ckd.csv').relabeled('Blood Glucose Random', 'Glucose')
ckd = Table().with_columns(
    'Hemoglobin', standard_units(ckd.column('Hemoglobin')),
    'Glucose', standard_units(ckd.column('Glucose')),
    'White Blood Cell Count', standard_units(ckd.column('White Blood Cell Count')),
    'Class', ckd.column('Class')
)
color_table = Table().with_columns(
    'Class', make_array(1, 0),
    'Color', make_array('darkblue', 'gold')
)
ckd = ckd.join('Class', color_table)

过度乐观的“测试”

训练集提供了一组非常有诱惑力的患者来测试我们的分类器,因为我们知道训练集中每个患者的类别。

但让我们小心……如果走这条路,前面会有陷阱。一个例子会告诉我们为什么。

假设我们使用一个 1-最近邻分类器,基于血糖和白细胞计数来预测患者是否患有慢性肾脏疾病。

[In ]:
ckd.scatter('White Blood Cell Count', 'Glucose', group='Color')
A scatterplot with 'White Blood Cell Count' on the x-axis and 'Glucose' on the y-axis. There are gold data points in the bottom left corner of the graph, and dark blue data points all over. The gold and dark blue data points do overlap in this graph.

之前,我们说我们预期会有一些错误分类,因为左下角有一些蓝色和金色点的混合。

但训练集中的点呢,也就是已经在散点图上的点?我们会错误分类它们吗?

答案是不会。记住,1-最近邻分类会寻找训练集中离被分类点最近的点。嗯,如果被分类的点已经在训练集中,那么它在训练集中的最近邻就是它自己!因此,它将被分类为它自己的颜色,这将是正确的,因为训练集中的每个点已经被正确着色。

换句话说,如果我们使用训练集来“测试”我们的 1-最近邻分类器,分类器将 100% 通过测试。

任务完成。多么伟大的分类器!

不,并非如此。左下方的一个新点可能很容易被错误分类,正如我们之前指出的。“100% 准确率”曾是一个美好的梦想。

这个例子的教训是:不要使用训练集来测试基于该训练集的分类器。

生成测试集

在之前的章节中,我们看到随机抽样可用于估计总体中满足某个条件的个体比例。不幸的是,我们刚刚看到,训练集在一个重要方面并不像来自所有患者总体的随机样本:我们的分类器对训练集中个体的正确猜测比例高于对总体中个体的正确猜测比例。

当我们计算数值参数的置信区间时,我们希望从总体中获得许多新的随机样本,但我们只能访问一个样本。我们通过对样本进行自助法重抽样来解决这个问题。

我们将使用一个类似的想法来测试我们的分类器。我们将从原始训练集中创建两个样本,将其中一个样本用作训练集,另一个用于测试。

因此,我们将有三组个体: - 一个训练集,我们可以在其上任意探索以构建分类器; - 一个独立的测试集,用于测试我们的分类器,看看它正确分类的比例; - 底层的个体总体,我们不知道其真实类别;希望我们的分类器对这些个体的表现与对测试集的表现大致相当。

如何生成训练集和测试集?你已经猜到了——我们将随机选择。

ckd 中有 158 个个体。让我们随机使用其中一半进行训练,另一半进行测试。为此,我们将打乱所有行,取前 79 行作为训练集,剩下的 79 行用于测试。

[In ]:
shuffled_ckd = ckd.sample(with_replacement=False)
training = shuffled_ckd.take(np.arange(79))
testing = shuffled_ckd.take(np.arange(79, 158))

现在让我们基于训练样本中的点构建分类器:

[In ]:
training.scatter('White Blood Cell Count', 'Glucose', group='Color')
plt.xlim(-2, 6)
plt.ylim(-2, 6);
The previous scatterplot is reproduced here with a slightly larger range for the y-axis, but none of the data has changed. Previous description: A scatterplot with 'White Blood Cell Count' on the x-axis and 'Glucose' on the y-axis. There are gold data points in the bottom left corner of the graph, and dark blue data points all over. The gold and dark blue data points do overlap in this graph.

我们得到以下分类区域和决策边界:

[In ]:
x_array = make_array()
y_array = make_array()
for x in np.arange(-2, 6.1, 0.25):
    for y in np.arange(-2, 6.1, 0.25):
        x_array = np.append(x_array, x)
        y_array = np.append(y_array, y)
        
test_grid = Table().with_columns(
    'Glucose', x_array,
    'White Blood Cell Count', y_array
)
[In ]:
c = classify_grid(training.drop('Hemoglobin', 'Color'), test_grid, 1)
[In ]:
test_grid = test_grid.with_column('Class', c).join('Class', color_table)
test_grid.scatter('White Blood Cell Count', 'Glucose', group='Color', alpha=0.4, s=30)

plt.xlim(-2, 6)
plt.ylim(-2, 6);
A scatterplot with 'White Blood Cell Count' on the x-axis and 'Glucose' on the y-axis. Points are arranged in a grid and colored either a partially transparent blue or gold. The majority of the grid is blue, but the bottom left corner, with a few eceptions is gold.

测试数据放在这个图上,你可以立即看到,虽然分类器几乎将所有点都分对了,但仍有一些错误。例如,测试集的一些蓝点落在分类器的金色区域中。

[In ]:
test_grid = test_grid.with_column('Class', c).join('Class', color_table)
test_grid.scatter('White Blood Cell Count', 'Glucose', group='Color', alpha=0.4, s=30)

plt.scatter(testing.column('White Blood Cell Count'), testing.column('Glucose'), c=testing.column('Color'), edgecolor='k')

plt.xlim(-2, 6)
plt.ylim(-2, 6);
The previous scatterplot is reproduced here with the addition of the actual data points with gold data points in the bottom left and blue throughout the whole of the graph. The previous description is: A scatterplot with 'White Blood Cell Count' on the x-axis and 'Glucose' on the y-axis. Points are arranged in a grid and colored either a partially transparent blue or gold. The majority of the grid is blue, but the bottom left corner, with a few eceptions is gold.

尽管存在一些错误,但分类器在测试集上表现得相当好。假设原始样本是从底层总体中随机抽取的,那么希望分类器在整个总体上的表现具有相似的准确率,因为测试集是从原始样本中随机选择的。