文章目录

  • 1. 常规切分思路
  • 2. Scikit-Learn内置的切分函数


1. 常规切分思路

应用有监督的机器学习算法时,需要将数据集切分成训练数据集和测试数据集两部分。在《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]

该切分方法有一个问题:当多次执行时,每次切分出的数据集都是不固定的,这不是算法期望的行为,有两种方法可以修正这一问题:

  • 将切分好的索引集合保存下来,这样每次提取的就是固定的数据集了
  • 在调用permutation之前,显式地设置随机数发生器的seed,这样就能保证每次生成的随机索引数组都是一致的

对于第二种方式,可以使用如下代码进行验证:

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 + 维度

2. Scikit-Learn内置的切分函数

鉴于切分数据集是如些基础和必要,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),以确保每次执行时切分的记录是一致的。