标量、向量、空间
单个数字特征也称为标量。标量的有序列表称为向量。向量位于向量空间中。在绝大多数机器学习应用中, 对模型的输入通常表示为数字向量。向量可以被可视化为空间中的一个点。(有时人们从原点到那一点画一条线和一个箭头。在这本书中,我们将主要使用这一点。例如,假设我们有一个二维向量𝑣=[1,−1]。也就是说,向量包含两个数,在第一方向𝑑1中,向量具有1的值,并且在第二方向𝑑2中,它具有−1的值。我们可以在二维图中绘制𝑣。
在数据世界中, 抽象向量及其特征维度具有实际意义。例如, 它可以代表一个人对歌曲的偏爱。每首歌都是一个特征, 其中1的值相当于大拇指向上,−1个拇指向下。假设向量𝑣表示一个听众 Bob 的喜好。Bob喜欢 Bob Dylan 的 “Blowin’ in the Wind” 和 Lady Gaga 的 "Poker Face"。其他人可能有不同的喜好。总的来说, 数据集合可以在特征空间中可视化为点云. 相反,一首歌可以由一组人的个人喜好来表示。假设只有两个听众,Alice 和 Bob。Alice 喜欢 Leonard Cohen 的 “Poker Face”, “Blowin’ in the Wind” 和 “Hallelujah”,但讨厌 Katy Perry 的 “Roar” 和 Radiohead 的 “Creep”。Bob 喜欢 “Roar", “Hallelujah”和“Blowin’ in the Wind”,但讨厌 “Poker Face” 和 “Creep” 。在听众的空间里,每一首歌都是一个点。就像我们可以在特征空间中可视化数据一样,我们可以在数据空间中可视化特征。图2-2显示了这个例子。
处理计数
在大数据时代,计数可以快速积累而不受约束。用户可以将歌曲或电影放在无限播放中,或者使用脚本反复检查流行节目的门票可用性,这会导致播放次数或网站访问计数迅速上升。当数据可以以高的体积和速度产生时,它们很可能包含一些极值。这是一个好主意,检查他们的规模,并确定是否保持它们作为原始数字,将它们转换成二进制变量,以指示存在,或将它们放入粗粒度。
二值化
Million Song 数据集中的用户品味画像包含了一百万个用户在 Echo Nest 的完整音乐聆听历史。下面是有关数据集的一些相关统计数据。
Echo Nest 品味画像数据集的统计
•有超过4800万个用户ID、音乐ID和监听计数三元组。
•完整的数据集包含1019318个独特用户和384546首独特歌曲。
•引文:Echo Nest 品味画像的数据子集,官方的 Million Song 数据集的用户数据集,可从这里获得:http://labrosa.ee.columbia.edu/millionsong/tasteprofile
假设任务是建立一个推荐器向用户推荐歌曲。推荐器的一个组件可以预测用户将对一首特别的歌曲会有多少喜欢。由于数据包含实际的听歌次数,这应该是预测的目标吗?如果一个大的听计数意味着用户真的喜欢这首歌,反之亦然,那是正确的。然而,数据表明,虽然99%的听计数是24或更低,也有一些听计数数以千计,最大为9667。(如图2-3所示,直方图最接近于0的bin中的峰值。但是超过10000个三元组的计数更大,几千个则有几个。这些值异常大;如果我们试图预测实际的听计数,那么模型将被这些大的值拉离。
在 Million Song 数据集中,原始监听计数不是用户口味的可靠度量。(在统计术语中,健壮性意味着该方法在各种各样的条件下工作。)用户有不同的听力习惯。有些人可能把他们最喜欢的歌曲放在无限的循环中,而其他人可能只在特殊的场合品尝它们。很难说听一首歌20次的人一定喜欢听10次的人的两倍。
用户偏好的更健壮表示是使计数二元化和修剪所有大于1的计数为1。换句话说,如果用户至少听过一首歌,那么我们将其视为用户喜欢歌曲。这样,模型不需要花费周期来预测原始计数之间的微小差异。二进制目标是用户偏好的简单而稳健的度量。
例2-1:使 Million Song 数据集中听歌计数二进制化
import pandas as pd listen_count = pd.read_csv( 'data/train_triplets.txt.zip', header=None, delimiter='\t') # The table contains user-song-count triplets. Only non-zero counts are # included. Hence to binarize the count, we just need to set the entire # count column to 1. listen_count[2] = 1
这是我们设计模型目标变量的一个例子。严格地说, 目标不是一个特征, 因为它不是输入。但有时我们确实需要修改目标以解决正确的问题。
量化或装箱
对于本练习, 我们从第 6 轮 Yelp 数据集挑战中采集数据, 并创建一个更小的分类数据集。Yelp 数据集包含用户对来自北美和欧洲十个城市的企业的评论。每个商户都标记为零个或多个类别。以下是有关数据集的相关统计信息。
关于第 6 轮 Yelp 数据集的统计
•有782种商户类别。
•完整的数据集包含 1569264 个(约1.6M)评论和 61184 个(61K)商户。
•“餐厅”(990627个评论)和“夜生活”(210028个评论)是最流行的类别,评论计数是明智的。
•没有一个商户同时属于餐厅和夜生活分类。因此,这两组评论之间没有重叠。
每个商户都有一个评论计数。假设我们的任务是使用协同过滤来预测用户可能给企业的评级。评论计数可能是一个有用的输入特征,因为通常在流行和良好的评级之间有很强的相关性。现在的问题是,我们应该使用原始评论计数或进一步处理它吗?图2-4显示了所有商户评论计数的直方图。我们看到和音乐听歌计数一样的模式。大部分的统计数字都很小,但一些企业有成千上万的评论。
例2-2:在YELP数据集中可视化商户评论计数
import pandas as pd import json import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline ### Load the data about businesses biz_file = open('data/yelp_academic_dataset_business.json') biz_df = pd.DataFrame([json.loads(x) for x in biz_file.readlines()]) biz_file.close() ### Plot the histogram of the review counts sns.set_style('whitegrid') fig, ax = plt.subplots() biz_df['review_count'].hist(ax=ax, bins=100) ax.set_yscale('log') ax.tick_params(labelsize=14) ax.set_xlabel('Review Count', fontsize=14) ax.set_ylabel('Occurrence', fontsize=14)
输出:Text(0,0.5,'Occurrence')
对于许多模型来说,跨越数个数量级的原始计数是有问题的。在线性模型中,相同的线性系数必须对计数的所有可能值工作。大量的计数也可能破坏无监督学习方法,如k-均值聚类,它使用相似性函数来测量数据点之间的相似性。k-均值使用数据点之间的欧几里得距离。数据向量的一个元素中的大计数将超过所有其他元素中的相似性,这可能会丢弃整个相似性度量。
一种解决方案是通过量化计数来包含标量。换句话说,我们将计数分组到容器中,并且去掉实际的计数值。量化将连续数映射成离散数。我们可以把离散化的数字看作是代表强度度量的容器的有序的序列。
为了量化数据,我们必须决定每一个箱子应该有多宽。解决方案分为固定宽度或自适应两种类型。我们将给出每个类型的例子。
固定宽度装箱
对于固定宽度装箱, 每个 bin 都包含一个特定的数值范围。范围可以是定制设计或自动分割, 它们可以线性缩放或指数缩放。例如, 我们可以将一个人的年龄分组为十年: 0-9 岁归纳到bin 1, 10-19 年归纳到 bin 2 等。要从计数映射到 bin, 只需除以 bin 的宽度并取整部分。
也经常看到定制设计的年龄范围更适合于生活的阶段:
•0-12 岁
•12-17 岁
•18-24 岁
•25-34 岁
•35-44 岁
•45-54 岁
•55-64 岁
•65-74 岁
•75 岁以上
当数字跨越多个数量级时,最好用10个幂(或任何常数的幂)来分组:0-9、10-99、100-999、100-9999等。容器宽度呈指数增长,从O(10)、O(100)到O(1000)和以上。要从计数映射到bin,取计数的log值。指数宽度的划分与对数变换非常相关,我们在“对数变换”中讨论。
例2-3:用固定宽度的箱进行量化计数
import numpy as np ### Generate 20 random integers uniformly between 0 and 99 small_counts = np.random.randint(0, 100, 20) small_counts 输出:array([84, 45, 51, 18, 50, 78, 40, 25, 75, 17, 39, 44, 69, 53, 35, 5, 8, 51, 63, 34]) np.floor_divide(small_counts, 10) 输出:array([8, 4, 5, 1, 5, 7, 4, 2, 7, 1, 3, 4, 6, 5, 3, 0, 0, 5, 6, 3], dtype=int32) ### An array of counts that span several magnitudes large_counts = [ 296, 8286, 64011, 80, 3, 725, 867, 2215, 7689, 11495, 91897, 44, 28, 7971, 926, 122, 22222 ] ### Map to exponential-width bins via the log function np.floor(np.log10(large_counts)) 输出:array([2., 3., 4., 1., 0., 2., 2., 3., 3., 4., 4., 1., 1., 3., 2., 2., 4.])
分位数装箱
固定宽度装箱很容易计算。但是如果计数有很大的差距, 那么将会有许多空的垃圾箱没有数据。该问题可以通过基于数据分布的垃圾箱自适应定位来解决。这可以使用分发的分位数来完成。
分位数是将数据划分为相等部分的值。例如, 中位数将数据分成一半;一半的数据是较小的, 一半大于中位数。分位数把数据分成几个部分, 十分位数把数据划分成十份。示例2-4 演示如何计算 Yelp 商户评论数的十等分, 图2-5 覆盖直方图上的十等分。这就更清楚地说明了对更小的计数的歪斜。
例 2-4:计算 Yelp 商户评论数的十分位数
deciles = biz_df['review_count'].quantile([.1, .2, .3, .4, .5, .6, .7, .8, .9]) print(deciles) 输出:0.1 3.0 0.2 3.0 0.3 4.0 0.4 5.0 0.5 6.0 0.6 8.0 0.7 12.0 0.8 23.0 0.9 50.0 Name: review_count, dtype: float64 ### Visualize the deciles on the histogram sns.set_style('whitegrid') fig, ax = plt.subplots() biz_df['review_count'].hist(ax=ax, bins=100) for pos in deciles: handle = plt.axvline(pos, color='r') ax.legend([handle], ['deciles'], fontsize=14) ax.set_yscale('log') ax.set_xscale('log') ax.tick_params(labelsize=14) ax.set_xlabel('Review Count', fontsize=14) ax.set_ylabel('Occurrence', fontsize=14) 输出:Text(0,0.5,'Occurrence')
为了计算分位数和映射数据到分位数箱,我们可以使用 Pandas 库。pandas.DataFrame.quantile和 pandas.Series.quantile 用于计算分位数。pandas.qcut将数据映射到所需数量的分位数。
例2-5:按分位数分箱计数
### Continue example Example 2-3 with large_counts import pandas as pd ### Map the counts to quartiles pd.qcut(large_counts, 4, labels=False) 输出:array([1, 2, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 2, 1, 0, 3], dtype=int64) ### Compute the quantiles themselves large_counts_series = pd.Series(large_counts) large_counts_series.quantile([0.25, 0.5, 0.75]) 输出:0.25 122.0 0.50 926.0 0.75 8286.0 dtype: float64
对数转换
在“量化或装箱”中,我们简要地介绍了把计数的对数映射到指数宽度箱的概念。让我们现在再看一看。 对数函数是指数函数的逆。它定义为log𝑎(𝑎𝑥)=𝑋。其中 𝑎为正常数, 𝑥可以是任何正数。由于𝑎0=1,我们有log𝑎(1)=0。这意味着对数函数将小范围的数字 (0、1) 映射到负数的整个范围(−∞,0)。函数log𝑎(10)=0。将[1、10]映射到[0、1]、将[10、100]映射到 [1、2]等等。换言之, 对数函数压缩大数的范围, 并扩展小数的范围。越大的 𝑥, 𝑙𝑜𝑔(𝑥)的增量越慢。通过查看 𝑙𝑜𝑔函数的图像, 可以更容易地消化这一点。
对数变换是处理具有重尾分布的正数的有力工具。(重尾分布在尾部范围内的概率比高斯分布的概率大)。它将分布在高端的长尾压缩成较短的尾部,并将低端扩展成较长的头部。图2-7比较d对数转换之前和之后的YELP商户评论计数的直方图。Y轴现在都在正常(线性)尺度上。在(0.5,1) (0.5,1)。范围内的底部图中增加的仓间隔是由于在1和10之间只有10个可能的整数计数。请注意,原始审查计数非常集中在低计数区域,离群值在4000以上。对数变换后,直方图不集中在低端,更分散在X轴上。
例2-6:可视化对数变换前后评论数分布
fig, (ax1, ax2) = plt.subplots(2,1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) biz_df['review_count'].hist(ax=ax1, bins=100) ax1.tick_params(labelsize=14) ax1.set_xlabel('review_count', fontsize=14) ax1.set_ylabel('Occurrence', fontsize=14) biz_df['log_review_count'] = np.log(biz_df['review_count'] + 1) biz_df['log_review_count'].hist(ax=ax2, bins=100) ax2.tick_params(labelsize=14) ax2.set_xlabel('log10(review_count))', fontsize=14) ax2.set_ylabel('Occurrence', fontsize=14) 输出:Text(23.625,0.5,'Occurrence')
另一个例子是来自 UC Irvine 机器学习库的在线新闻流行数据集。以下是有关数据集的相关统计信息。
在线新闻流行数据集的统计
•该数据集包括 MasHabor 在两年的时间内出版的 39797 个新闻文章的60个特征。
•引证: K. Fernandes, P. Vinagre 和 P. Cortez . 一种用于预测在线新闻的流行程度的主动智能决策支持系统。2015 第十七届 EPIA 活动, 葡萄牙人工智能会议论文集, 9月, 葡萄牙科英布拉。
目的是利用这些特征来预测文章在社交媒体上的用分享数量表示的流行度。在本例中, 我们将只关注一个特征——文章中的单词数。图2-8 显示了对数转换前后特征的直方图。请注意, 在对数转换后, 分布看起来更高斯, 除了长度为零的文章 (无内容) 的断裂。
df = pd.read_csv('data/OnlineNewsPopularity.csv', delimiter=', ') df.head() df['log_n_tokens_content'] = np.log10(df['n_tokens_content'] + 1)
例2-7:可视化在有对数变换和没有对数变换时新闻文章流行度的分布
fig, (ax1, ax2) = plt.subplots(2,1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) df['n_tokens_content'].hist(ax=ax1, bins=100) ax1.tick_params(labelsize=14) ax1.set_xlabel('Number of Words in Article', fontsize=14) ax1.set_ylabel('Number of Articles', fontsize=14) df['log_n_tokens_content'].hist(ax=ax2, bins=100) ax2.tick_params(labelsize=14) ax2.set_xlabel('Log of Number of Words', fontsize=14) ax2.set_ylabel('Number of Articles', fontsize=14) 输出:Text(23.625,0.5,'Number of Articles')
参考文献
[1]Feature Engineering for Machine Learning Models. Alice Zheng[美]