翻开西瓜书的第二章,迎面而来的便是各类测试评估方法,在这一节我将会通过全面分析其中具有代表性的ROC与lost曲线的理论及代码来帮助大家有效掌握本章知识。
首先要明确一个概念,在机器学习领域,无论我们使用什么模型和算法,都需要训练数据集和测试数据集,理想状态下二者应该是互斥的。只有这样,在模型完成训练后,测试数据集才能够准确得出特定模型的性能表现。
但在这里我们遇到了一个问题:对于一个给定的数据集,我们需要同时从中提取训练数据和测试数据。提取的训练数据越多,训练效果越好,但留下的测试数据越少,测试结果的可信度越差;反之,测试效果可信度高而训练效果会更差,模型性能无法全面表现。二者显然是矛盾的,因此如何科学合理地划分已有数据集是我们测试模型性能地前提。
这里有三种主流方法:
1.留出法(hold out)
直接将数据集划分为两个互斥的集合,一般来说训练数据会占比2/3-4/5,这样可以保证较好的测试效果
上代码:
from sklearn import datasets
from sklearn.model_selection import train_test_split
iris = datasets.load_iris()
#分别导入数据和标签
X = iris.data
y = iris.target
#train_test_split(X,y,test_size,random_state) X为待划分数据集,y为待划分标签集,test_size为测试集占总整个数据集的百分比,random_state为随机种子
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.25,random_state=1)
【深入解析train_test_split函数】
变量:
test_size:三种类型。float,int,None,可选参数。
float:0.0-1.0。测试数据集占总数据集的比例。
int:测试数据集具体的样本数量。
None:设置为训练数据集的补。
default:默认为0.25,(train_size没有设置的时候)
train_size:三种类型。float,int,None。
float:0.0-1.0,训练数据集占总数据集的百分比。
int:训练数据集样本数量。
None:设为test_size的补。
default:默认None。
random_state:三种类型。int,randomstate instance,None。
int:是随机数生成器的种子。每次分配的数据相同。
randomstate:可以自己定义随机。
None:使用了np.random的randomstate。
shuffle:布尔值。默认None。在划分数据之前先打乱数据。若shuffle=FALSE,则stratify须为None。
stratify:array-like或None,默认为None。否则将会利用数据的标签值将数据分层划分(也就是说x_train 和 x_test的内部数据构成比例受y标签的比例的影响)。
若为None时,划分出来的测试集或训练集中,其类标签的比例也是随机的。
若不为None时,划分出来的测试集或训练集的类标签的比例同输入的数组中类标签的比例相同,可以用于处理不均衡的数据集。
再来上一段该函数的代码核心部分:
if shuffle is False:
if stratify is not None:
raise ValueError(
"Stratified train/test split is not implemented for "
"shuffle=False")
n_samples = _num_samples(arrays[0])
n_train, n_test = _validate_shuffle_split(n_samples, test_size,
train_size)
train = np.arange(n_train)
test = np.arange(n_train, n_train + n_test)
else:
if stratify is not None:
CVClass = StratifiedShuffleSplit
else:
CVClass = ShuffleSplit
cv = CVClass(test_size=test_size,
train_size=train_size,
random_state=random_state)
train, test = next(cv.split(X=arrays[0], y=stratify))
return list(chain.from_iterable((safe_indexing(a, train),
safe_indexing(a, test)) for a in arrays))
可以看到,是否进行随机化,是否分层,又封装了不同的函数:
如果shuffle 为 false : _validate_shuffle_split() + np.arange()
否则若stratify赋值进行分层:StratifiedShuffleSplit()
若不分层(stratify=none):ShuffleSplit()
【_validate_shuffle_split()代码】该函数根据数据集大小和要求参数返回测试集和训练集的大小值
def _validate_shuffle_split(n_samples, test_size, train_size):
"""
Validation helper to check if the test/test sizes are meaningful wrt to the
size of the data (n_samples)
"""
if (test_size is not None and
np.asarray(test_size).dtype.kind == 'i' and
test_size >= n_samples):
raise ValueError('test_size=%d should be smaller than the number of '
'samples %d' % (test_size, n_samples))
#测试集过大报错,dtype.kind==''i'的意思是有符号整数signed integer
if (train_size is not None and
np.asarray(train_size).dtype.kind == 'i' and
train_size >= n_samples):
raise ValueError("train_size=%d should be smaller than the number of"
" samples %d" % (train_size, n_samples))
#训练集过大报错,dtype.kind==''i'的意思是有符号整数signed integer
if test_size == "default":
test_size = 0.1
# 默认test_size=0.1
if np.asarray(test_size).dtype.kind == 'f':
n_test = ceil(test_size * n_samples)
elif np.asarray(test_size).dtype.kind == 'i':
n_test = float(test_size)
#如果是浮点数(dtype.kind=='f'),test_size*n_samples后向正无穷大方向取整(ceil()),如果是整数形式,化为浮点数格式
if train_size is None:
n_train = n_samples - n_test
elif np.asarray(train_size).dtype.kind == 'f':
n_train = floor(train_size * n_samples)
else:
n_train = float(train_size)
if test_size is None:
n_test = n_samples - n_train
if n_train + n_test > n_samples:
raise ValueError('The sum of train_size and test_size = %d, '
'should be smaller than the number of '
'samples %d. Reduce test_size and/or '
'train_size.' % (n_train + n_test, n_samples))
return int(n_train), int(n_test)
【StratifiedShuffleSplit()】
【ShuffleSplit()】
对应之后说明,不细说了。
2.交叉验证法/k折交叉验证(cross validation/k-fold cross validation)
由于留出法对于训练数据和测试数据的特点无法广泛表达,我们尝试将原数据集分为k份,将k-1份数据集构成训练集,剩下的1份为测试集,随后不断依次替换这k份数据集的位置,进行k次循环,最终将每一次的测试结果进行平均得出模型的性能。这种方法被称之为交叉验证法,也叫k折验证法。
可以想到,对于一个有m个元素的数据及而言,k折验证法的极端便是k=m的情况,这时我们称之为留一发(leave-one-out,注意区分留一法),这时测试结果被认为比较准确,当时对于庞大的测试集这会带来m次循环遍历的高额计算代价,因此k折验证多取10次,此时称之为10折交叉验证,如下一图胜千言。
上代码:有两种K-fold函数(这里只讨论单次模式,实际应用中多使用p次k-fold)
import numpy as np
from sklearn.model_selection import KFold,StratifiedKFold
X=np.array([
[1,2,3,4],
[11,12,13,14],
[21,22,23,24],
[31,32,33,34],
[41,42,43,44],
[51,52,53,54],
[61,62,63,64],
[71,72,73,74]
])
y=np.array([1,1,0,0,1,1,0,0])
#n_folds这个参数没有,引入的包不同,
floder = KFold(n_splits=4,random_state=0,shuffle=False)
#KFold函数在分割数据集时会考虑y中标签类的构成,使得每组结果都保持相同的标签分布比例
for train, test in sfolder.split(X,y): #.split()返回的是索引值
print('Train: %s | test: %s' % (train, test))
print(" ")
Train: [1 3 4 5 6 7] | test: [0 2] Train: [0 2 4 5 6 7] | test: [1 3] Train: [0 1 2 3 5 7] | test: [4 6] Train: [0 1 2 3 4 6] | test: [5 7]
sfolder = StratifiedKFold(n_splits=4,random_state=0,shuffle=False) #StratifiedKFold函数不考虑标签集只按照顺序对数据集进行逐条分层.
for train, test in floder.split(X,y):
print('Train: %s | test: %s' % (train, test))
print(" ")
Train: [2 3 4 5 6 7] | test: [0 1] Train: [0 1 4 5 6 7] | test: [2 3] Train: [0 1 2 3 6 7] | test: [4 5] Train: [0 1 2 3 4 5] | test: [6 7]
这里给出sklearn里面很有用的split数据分割模块k-fold相关类的查询表,我们可以看到hold-out也调用了其中的函数,其可视为特殊情况下的单次k-fold
KFold (n_splits, shuffle, random_state) | StratifiedKFold (n_iter, test_size, train_size, random_state) | |
---|---|---|
分为K份,K-1用于训练,余下的用作测试 | 与K-Fold相同,但是每个组都保持了类别分布 | |
ShuffleSplit (n_iter, test_size, train_size, random_state) | StratifiedShuffleSplit | |
随机产生训练/测试序列 | 与Shuffle分割相同,但是在每次迭代中保持了类别分布S | |
LeaveOneGroupOut () | LeavePGroupsOut (p) | |
剔除一个组 | 剔除P个组 | |
LeavePOut (p) | PredefinedSplit | |
剔除P个采样 | 基于预定义分割产生训练/测试序列 |
GroupKFold (n_splits, shuffle, random_state) | ||
---|---|---|
确保同组数据不会同时出现在训练集和测试集中 | ||
GroupShuffleSplit | ||
确保同组数据不会同时出现在训练集和测试集中 | ||
LeaveOneOut () | ||
剔除1个采样 | ||
3自助法(bootstraping)
k-fold方法对于较大的数据有良好的效果,但对于较小的数据集仍会有样本缺失的问题。因此对于较小的样本还有一种自助法可以选取,bootstraping狮子对于原来有m个元素的样本集进行m次随机采样,从而生成一个和原来样本集一样大的训练集,可知原集合里未被抽到的元素概率可用p=(1-1/m)^m 表示,根据求极限原理当m趋近无穷大,p=1/e (0.368)。也就是说通过自主法我们可以获得一个和原集合一样大的训练集(由于随机抽样我们认为这个训练集的元素特征与原集合相似)和一个三分之一大小与之完全互斥的测试集。对于小数据集这提高了了训练和测试的性能,但如果数据集过大反而会造成较大的数据遗漏。
这个方法主要用于集成学习里面的决策树算法,到后面讲我们再详细说,调用bootstraping通常是用sklearn.ensemble.bagging里面的_generate_indices和_generate_bagging_indices,二者区别是是否返回标签索引。
from sklearn.ensemble import bagging
import numpy as np
x = bagging._generate_indices(random_state=r,n_population=20,n_samples=20,bootstrap=True)
r = np.random.RandomState(1)
print(x)
#[ 5 11 12 8 9 11 5 15 0 16 1 12 7 13 6 18 5 18 11 10]
上源码:
def _generate_indices(random_state, bootstrap, n_population, n_samples):
"""Draw randomly sampled indices."""
# Draw sample indices
indices = random_state.randint(0, n_population, n_samples)
if bootstrap:
else:
random_state=random_state)
indices = sample_without_replacement(n_population, n_samples,
return indices
bootstrap_samples, n_features, n_samples,
def _generate_bagging_indices(random_state, bootstrap_features,
max_features, max_samples):
feature_indices = _generate_indices(random_state, bootstrap_features,
"""Randomly draw feature and sample indices."""
# Get valid random state
random_state = check_random_state(random_state)
# Draw indices
n_samples, max_samples)
n_features, max_features)
sample_indices = _generate_indices(random_state, bootstrap_samples,
return feature_indices, sample_indices
参考资料:
https://blog.csdn.net/liuxiao214/article/details/79019901
https://jizhi.im/blog/post/sklearntutorial0203