在传统的软件工程中,核心是代码,然而,在机器学习项目中,重点则是特征——也就是说,开发人员优化模型的方法之一是增加和改进其输入特征。很多时候,优化特征比优化模型带来的增益要大得多。
笔者曾经参与过一个“商品推荐”的项目,在项目中,笔者发现商品的类目(划分类别的标记,如纸品家清、米面粮油、数码产品等)虽然有 100 多个,但是运营人员经验不足,将超过 70% 的商品采用了默认的类目(精品好货)标记。如此一来,原本可以用来标记商品类型的关键特征(类目)的实际作用大打折扣。于是,我们重新整理了这个特征,将所有商品重新标记,在未改变算法模型的前提下,推荐效果(uvctr)提升了8%+。
目录
1.将原始数据映射到特征
1.1 映射数值
1.2 映射分类值
1.3 稀疏表示
2.寻找优秀的特征
2.1 避免很少使用的离散特征值
2.2 喜欢清晰、明显的含义
2.3 不要将“魔法”值与实际数据混合在一起
2.4 考虑上游不稳定因素
3.数据清洗
3.1 缩放特征值
3.2 处理极端异常值
3.3 分档
3.4 清洗
3.5 了解数据
4.参考文献
1.将原始数据映射到特征
图 1 左侧展示了来自输入数据源的原始数据;右侧展示了一个特征向量,它是包含数据集中示例的浮点值集。 特征工程意味着将原始数据转换为特征向量。在实践中,会花费大量时间实施特征工程。许多机器学习模型必须将特征表示为实数向量,因为特征值必须乘以模型权重。
图 1 特征工程将原始数据映射到 ML 特征
1.1 映射数值
整数和浮点数据不需要特殊编码,因为它们可以乘以数字权重。如图 2 所示,将原始整数值 6 转换为特征值 6.0 很简单:
图 2 将整数值映射到浮点值
1.2 映射分类值
分类特征具有一组离散的可能值。例如,可能有一个名为 street_name(
街道名称)的特征
包含以下选项:
{'Charleston Road', 'North Shoreline Boulevard', 'Shorebird Way', 'Rengstorff Avenue'}
由于模型无法将字符串乘以学习到的权重,因此我们使用特征工程将字符串转换为数值。我们可以通过定义从特征值(我们将其称为可能值的词汇表)到整数的映射来实现此目的。由于并非世界上的每条街道都会出现在我们的数据集中,因此我们可以将所有其他街道分组为一个包罗万象的 “其他”类别,称为OOV(词汇外)桶。
使用这种方法,我们可以将街道名称映射到数字:
- 将 “Charleston Road” 映射到 0
- 将 “North Shoreline Boulevard” 映射到 1
- 将 “Shorebird Way” 映射到 2
- 将 “Rengstorff Avenue” 映射到 3
- 将其他所有内容 (OOV) 映射到 4
但是,如果我们将这些索引号直接合并到我们的模型中,则会出现问题:
-
我们将学习适用于所有街道的单一权重。例如,如果我们学习的权重为 6,那么对于 Charleston Road,我们将其乘以 0,对于 North Shoreline Boulevard,我们将其乘以 1,对于 Shorebird Way,我们将其乘以 2,依此类推。
street_name
考虑一个使用特征来预测房价的模型。价格不太可能根据街道名称进行线性调整,而且这会假设您根据街道的平均房价对街道进行排序。我们的模型需要灵活地学习每条街道的不同权重,并将其添加到使用其他功能估计的价格中。 -
我们没有考虑
street_name
可能采用多个值的情况。例如,许多房屋位于两条街道的拐角处,如果值包含单个索引,则无法在值中编码该信息。
为了消除上述问题,我们可以为模型中的每个分类特征创建一个二元向量来表示值,如下所示:
- 对于适用于该示例的值,将相应的向量元素设置为
1
。 - 将所有其他元素设置为
0
。
该向量的长度等于词汇表中元素的数量。当单个值为 1 时,这种表示称为 one-hot 编码;当多个值为 1 时,这种表示称为 multi-hot 编码。
图 3 说明了特定街道的 one-hot 编码:Shorebird Way。Shorebird Way 的二元向量中的元素值为 1
,而所有其他街道的元素值为 0
。
图 3 通过 one-hot 编码映射街道地址
上述方法有效地为每个特征值(例如,街道名称)创建一个布尔变量。此处,如果房屋位于 Shorebird Way,则仅对于 Shorebird Way,二进制值为 1。因此,该模型仅使用 Shorebird Way 的权重。同样,如果房屋位于两条街道的拐角处,则两个二进制值设置为 1,并且模型使用它们各自的权重。
One-hot 编码还可以扩展到其他场景,比如不能直接乘以权重的数字数据,例如邮政编码。
1.3 稀疏表示
假设数据集中有 1,000,000 个不同的街道名称。在处理这些向量时,显式创建一个包含 1,000,000 个元素的二进制向量,其中只有 1 或 2 个元素为真,在存储和计算时间方面都是非常低效的表示形式。在这种情况下,常见的方法是使用 稀疏表示,其中仅存储非零值。在稀疏表示中,仍然为每个特征值学习独立的模型权重,如上所述。
2.寻找优秀的特征
在上面,我们已经探索了将原始数据映射到合适的特征向量的方法,但这只是工作的一部分。我们还必须探索哪些类型的值可以在这些特征向量中产生良好的表现。
2.1 避免很少使用的离散特征值
好的特征值应该在数据集中出现超过 5 次左右。这样做可以使模型了解该特征值与标签的关系。也就是说,拥有相同离散值的许多示例使模型有机会在不同设置中查看该特征,进而确定何时它是标签的良好预测变量。例如,名为 house_type(房屋类型)
的特征可能在很多示例中出现。
相反,如果某个特征的值仅出现一次或很少出现,则模型无法根据该特征进行预测。例如,unique_house_id(房屋唯一编码)
是一个不好的特征,因为每个值只能使用一次,因此模型无法从中学习任何内容。
2.2 喜欢清晰、明显的含义
每个特征对于项目中的任何人都应该具有清晰且明显的含义。例如,名称明确的特征,并且值相对于名称有意义:
house_age_years: 27
相反,不好的特征,除了创建它的工程师之外,任何人都无法理解以下特征值的含义:
house_age: 851472000
在某些情况下,噪声数据(而不是糟糕的工程选择)会导致值不明确。例如,以下 user_age_years ,如果不做前置过滤,这样的 “不恰当的特征数值” 将会混入。
user_age_years: 277
2.3 不要将“魔法”值与实际数据混合在一起
良好的浮点数特征不应包含特殊的超出范围的“魔法”值。例如,假设某个特征包含 0 到 1 之间的浮点值。因此,如下所示的值就可以了:
quality_rating: 0.82
quality_rating: 0.37
但是,如果用户没有输入 quality_rating
,则数据集可能会用如下所示的 “魔法” 值来表示其不存在:
quality_rating: -1
要显式标记 “魔法值”,请创建一个布尔功能来指示是否提供了 quality_rating 这一特征,并
为这个布尔特征命名,例如 is_quality_rating_defined
。
在原来的特征中,将 “魔法值” 替换如下:
- 对于采用有限值集(离散变量)的变量,向该集中添加一个新值并用它来表示特征值缺失。
- 对于连续变量,通过使用特征数据的平均值来确保缺失值不会影响模型。
2.4 考虑上游不稳定因素
特征的定义不应随时间而改变。例如,城市名称一般不会更改。(需要说明的是,在实际应用中,我们需要将 “br/sao_paulo” 这样的字符串转换为 one-hot 向量。)
city_id: "br/sao_paulo"
3.数据清洗
苹果树结出美味的果实和虫害的混合物。然而,高端杂货店里的苹果却是 100% 完美的水果。在果园和杂货店之间,有人花费大量时间去除坏苹果或在可回收的苹果上撒一点蜡。作为一名 ML 工程师,您将花费大量时间排出不好的示例并清理可挽救的示例。即使是几个“坏苹果”也会破坏一个大的数据集。
3.1 缩放特征值
缩放意味着将浮点特征值从其自然范围(例如 100 到 900)转换为标准范围(例如 0 到 1 或 -1 到 +1)。如果一个特征集仅包含一个特征,那么缩放几乎不会带来任何实际好处。然而,如果一个特征集由多个特征组成,那么特征缩放会带来以下好处:
- 帮助梯度下降更快地收敛。
- 有助于避免“NaN 陷阱”,即模型中的一个数字变成 NaN (例如,当训练期间某个值超过浮点精度限制时),并且由于数学运算,模型中的所有其他数字最终也会变成 NaN。
- 帮助模型学习每个特征的适当权重。如果没有特征缩放,模型将过多关注范围更广的特征。
我们不必为每个浮点特征提供完全相同的比例。如果特征 A 从 -1 缩放到 +1,而特征 B 从 -3 缩放到 +3,则不会发生什么可怕的事情。但是,如果特征 B 从 5000 缩放到 100000,模型反应会很差。
3.2 处理极端异常值
下图表示 roomsPerPerson
从加州住房数据集调用的一个特征。的值 roomsPerPerson
是通过将某个区域的房间总数除以该区域的人口来计算的。该图显示,加利福尼亚州的绝大多数地区每人拥有一到两个房间。但沿着 x 轴看一下。
图 4 一条非常长的尾巴
我们如何才能最大限度地减少这些极端异常值的影响?好吧,一种方法是取每个值的对数:
图 5 对数缩放仍然留下尾巴
对数缩放的效果稍好一些,但仍然存在明显的异常值尾部。让我们选择另一种方法。如果我们简单地将 的最大值“限制”或“限制”roomsPerPerson
为任意值(例如 4.0)会怎么样?
图 6 在 4.0 处剪切特征值
将特征值裁剪为 4.0 并不意味着我们忽略所有大于 4.0 的值。相反,它意味着所有大于 4.0 的值现在都变为 4.0。这解释了 4.0 处的有趣山丘。尽管有这座山,但缩放后的特征集现在比原始数据更有用。
3.3 分档
下图显示了加利福尼亚州不同纬度房屋的相对流行率。请注意聚类情况 - 洛杉矶大约位于纬度 34 处,旧金山大约位于纬度 38 处。
图 7 每个纬度的房屋
数据集中,latitude
是一个浮点值。然而,在我们的模型中表示 latitude
为浮点特征是没有意义的。这是因为纬度和住房价值之间不存在线性关系。例如,北纬 35 度的房子就不是 3534 比纬度 34 的房屋更贵(或更便宜)。然而,各个纬度可能是房屋价值的一个很好的预测指标。
为了使纬度成为有用的预测器,我们将纬度划分为“区间”,如下图所示:
图 8 分箱值
我们现在拥有 11 个不同的布尔特征(LatitudeBin1
, LatitudeBin2
, ..., LatitudeBin11
),而不是只有一个浮点特征。拥有 11 个独立的特征并不好,因此让我们将它们合并成一个 11 元素向量。这样做将使我们能够将纬度 37.4 表示如下:
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
基于分箱,我们的模型现在可以学习每个纬度完全不同的权重。
3.4 清洗
到目前为止,我们假设用于训练和测试的所有数据都是值得信赖的。在现实生活中,由于以下一个或多个原因,数据集中的许多示例都是不可靠的:
- 省略的值。例如,某人忘记输入房屋的年龄值。
- 重复的例子。 例如,服务器错误地将相同的日志上传了两次。
- 不好的标签。 例如,有人将橡树的图片错误地标记为枫树。
- 不良特征值。例如,有人输入了额外的数字。
一旦检测到,通常需要通过从数据集中删除它们来 “修复” 不良示例。要检测遗漏的值或重复的示例,您可以编写一个简单的程序。在实践中,检测不良特征值或标签可能要棘手得多。
除了检测不良的个别示例之外,还必须检测聚合中的不良数据。直方图是可视化聚合数据的绝佳机制。此外,获取如下统计数据也会有所帮助:
- 最大值和最小值
- 平均值和中位数
- 标准差
考虑生成离散特征最常见值的列表。例如,示例的数量是否与 country:uk
您期望的数量相匹配。language:jp
是数据集中最常用的语言吗?
3.5 了解数据
请遵循以下规则:
- 请记住数据应该是什么样子。
- 验证数据是否满足这些期望(或者你可以解释为什么不满足)。
- 仔细检查训练数据是否与其他来源一致。
像对待任何关键任务代码一样小心地对待你的数据。好的机器学习依赖于好的数据。
4.参考文献
本文部分内容翻译自:英文资料(链接-https://developers.google.cn/machine-learning/crash-course/representation/feature-engineering)