第二章 端到端的机器学习项目
创建测试集
方法一:随机抽样
import numpy as np
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))#洗(牌/数据)函数
test_set_size = int(len(data) * test_ratio)#测试集大小
test_indices = shuffled_indices[:test_set_size]#把0-test_set_size个数据给测试集
train_indices = shuffled_indices[test_set_size:]#把test_set_size-len个数据给训练集
return data.iloc[train_indices], data.iloc[test_indices]#返回训练集索引及值和测试集索引及值
第一种解决方案:在首次运行时储存此测试集以便在后续运行时加载
第二种解决方案:在调用np.random.permutation()
之前设置随机数生成器的种子,保证每次运行时的随机数相同
前两种方案的缺点:当数据集进行更新(增加或减少)时,都会改变测试集
第三种解决方案:使用每个样本的标识符来决定它是否应该加入测试集(假设样本的标识符具有唯一性和不变性),具体做法:计算每个样本标识符的哈希值,若哈希值小于或等于最大哈希值的20%,则将此样本放入到测试集中。
from zlib import crc32
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32
#crc32(data[, value]):计算data的CRC(循环冗余校验)值,计算的结果是一个32位的整数。参数value是校验时的起始值,其默认值为0。借助参数value可为分段的输入计算校验值。此算法没有加密强度,不应用于身份验证和数字签名。此算法的目的仅为验证数据的正确性,不适合作为通用散列算法在所有的Python版本和平台上获得相同的值
def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))#测试在不在测试集里
return data.loc[~in_test_set], data.loc[in_test_set]#返回不在和在测试集里的索引值
但是housing
数据集没有标识符列,解决办法:为每一个样本添加行索引
housing_with_id = housing.reset_index() # adds an `index` column将索引值传输到数据框的列中并设置一个简单的整数索引
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
新的问题:
若使用行索引作为唯一标识符,则前提是如果有新数据进入,则必须被追加到数据集的末尾且期间不会删除任何行。如果做不到的话,就使用最稳定的特性来构建一个唯一的标识符。举个例子,一个地区的纬度和经度可以保证在几百万年内保持稳定,因此可以将它们合成为一个ID,如下所示:
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")
方法四:Scikit-Learn包中的train_test_split
函数,功能同上述split_train_test
函数相同,但增加了几个特性参数:random_state
设置随机数种子,其次可以在相同的索引上分割行数相同的多个数据集,例如:
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
上述抽样方法的使用前提是在数据集足够大的情况下,当数据集不够大时使用分层抽样方法
方法二:分层抽样
假设收入中位数是预测房价中位数的一个关键因素,则需要创建一个属性为收入类别
,我们可以看到收入直方图中数据主要集中在1.5到6之间,但有少数人的收入已经远远超过6,在创建收入类别时尤其要注意这一点:保证每个层的样本数量足够大且层数尽可能的少。举个例子,将收入分为5个类别:0到1.5,1.5到3,… ,以此类推,如下所示:
#对每一个数据增加income_cat属性,即1-5中的对应数字标签
housing["income_cat"] = pd.cut(housing["median_income"],
bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
labels=[1, 2, 3, 4, 5])
从sklearn里导入StratifiedShuffleSplit
(分层抽样)函数:
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
对比
可以看到分层抽样更接近整体的比例
英文
It's highly prone to do sth.
容易做某事
stumble upon
意外发现
generalization error
泛化误差
data snooping bias
数据透视偏差,指基于先前得到的实证经验后对历史数据进行分析后所得到的偏差