应用有监督的机器学习算法时,需要将数据集切分成训练数据集和测试数据集两部分。在《Handson ML》一书中,使用了numpy.random.permutation
( https://numpy.org/doc/stable/reference/random/generated/numpy.random.permutation.html#numpy-random-permutation)对数据集进行了切分。其思路是:利用permutation生成shuffle后记录索引(打乱顺序的索引集合),然后按比例提取训练数据集的索引子集和测试数据集的测试数据集,最后通过DataFrame的iloc方法按索引完成了数据集的切割,操作示例代码如下:
import numpy as np
# to make this notebook's output identical at every run
np.random.seed(42)
# For illustration only. Sklearn has train_test_split()
def split_train_test(data, test_ratio):
# permutation就是生成随机数组,参数是n就生成从0到n-1的n个元素组成的数组,但是顺序是打乱是,不是0,1,2...这样的
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
# 根据切分好的索引,分别提取对应记录,形成训练集和测试集
return data.iloc[train_indices], data.iloc[test_indices]
该切分方法有一个问题:当多次执行时,每次切分出的数据集都是不固定的,这不是算法期望的行为,有两种方法可以修正这一问题:
对于第二种方式,可以使用如下代码进行验证:
import numpy as np
# 设置seed,可以保证每次生成的随机数组是固定的!这对于切分数据集来说是必要的
np.random.seed(42)
shuffled_indices = np.random.permutation(5)
print(shuffled_indices)
但是,当数据集发生变更后,上述基于固定索引的切分方法就不再可靠了(原始记录的条数和位置都有可能发生变化),较为稳妥的切分方法应该是基于数据自身特征进行切分,这样无论这条记录出现在什么位置上,都可以被精准划拨到指定的数据集上。
最简单的做法是基于记录的ID进行切分,我们可以对ID使用crc32循环冗余编码,然后将求出的hash值和按期望切分比例算出的hash值进行比较,以决定数据是进入训练集还是测试集,以下是参考代码:
from zlib import crc32
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32
def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
# 针对每一个id计算crc32并判定是否落在测试数据集区间中,是则放入in_test_set
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]
但是,并不是所有的记录都有ID,此时就需要根据一些相对稳定的属性列来构建ID,例如,在《Handson ML》一书使用的房地产数据中,根据经纬度可以构建中相对稳定的ID值:ID = 经度*1000 + 维度
鉴于切分数据集是如些基础和必要,Scikit-Learn也就直接内置了现成的函数,其功能和做法与最开始时介绍的手动切分方法是一致的:
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
参数的参数都可以直接猜出其作用,test_size=0.2表示期望测试数据集占20%,random_state=42就是此前说的设置固定的seed:np.random.seed(42)
,以确保每次执行时切分的记录是一致的。