特征工程之类别特征

类别特征

一个类别特征,见名思义,就是用来表达一种类别或标签。比如,一个类别特征能够表达世界上的主要城市,一年四季,或者说一个公司的产品(石油、路程、技术)。在真实世界的数据集中,类别值的数量总是无限的。同时这些值一般可以用数值来表示。但是,与其他数值变量不一样的是,类别特征的数值变量无法与其他数值变量进行比较大小。(作为行业类型,石油与旅行无法进行比较)它们被称之为非序的。

一个简单的问题可以作为测试是否应该是一个分类变量的试金石测试:“两个价值有多么不同,或者只是它们不同?”500美元的股票价格比100美元的价格高5倍。 所以股票价格应该用一个连续的数字变量表示。 另一方面,公司的产业(石油,旅游,技术等)应该无法被比较的,也就是类别特征。

大的分类变量在交易记录中特别常见。 对于实例中,许多Web服务使用id作为分类变量来跟踪用户具有数百至数百万的值,取决于唯一的数量服务的用户。 互联网交易的IP地址是另一个例子一个很大的分类变量。 它们是分类变量,因为即使用户ID和IP地址是数字,它们的大小通常与任务无关在眼前。 例如,在进行欺诈检测时,IP地址可能是相关的个人交易。 某些IP地址或子网可能会产生更多欺骗性交易比其他人。 但是164.203.x.x的子网本质上并不多欺诈性比164.202.x.x; 子网的数值无关紧要。

文档语料库的词汇可以被解释为一个大的分类变量,类别是唯一的单词。 它可能在计算上很昂贵代表如此多的不同类别。 如果一个类别(例如,单词)出现多个数据点(文档)中的时间,然后我们可以将它表示为一个计数并表示所有的类别通过他们的统计数字。 这被称为bin-counting。 我们用分类变量的共同表示开始讨论,并且最终蜿蜒曲折地讨论了大范围的bin-counting问题变量,这在现代数据集中非常普遍。

对类别特征进行编码

分类变量的类别通常不是数字。例如,眼睛的颜色可以是“黑色”,“蓝色”,“棕色”等。因此,需要使用编码方法将这些非数字类别变为数字。 简单地将一个整数(比如1到k)分配给k个可能的类别中的每一个都是诱人的。 但是,由此产生的价值观可以互相授权,这在类别中不应该被允许。

One-hot 编码

将类别特征进行表示一个最好的办法就是使用一组比特位来表达。每一位代表一个可能的类别。 如果该变量不能一次成为多个类别,那么该组中只有一位可以是1。 这被称为独热编码,它在Scikit Learn中实现sklearn.preprocessing.OneHotEncoder。 每个位都是一个特征。 因此是一个绝对的具有k个可能类别的变量被编码为长度为k的特征向量。

表5-1 对3个城市的类别进行独热编码

Citye1e2e3San Francisco100New York010Seattle001

独热编码非常易于理解。 但它使用的是比严格必要的更多的一点。 如果我们看到k-1位是零,那么最后一位必须是1,因为变量必须具有k个值中的一个。 在数学上,可以写下这个约束条件为“所有位的和必须等于1”。

等式 5-1. 独热编码e1,e2,e3限制条件。

e1+e2+e3+...+ek=1 \\

因此,我们有一个线性的依赖性。 线性相关特征,就像我们一样在tfidf中发现,有点烦人,因为它意味着训练线性模型不会是唯一的。 特征的不同线性组合可以做出同样的预测,所以我们需要跳过额外条件的来理解特征对预测的影响。

dummy编码

独热编码的问题是它允许k个自由度,其中变量本身只需要k-1。 虚拟编码通过仅使用表示中的k-1个特征来消除额外的自由度。

公共汽车下面有一个特征,由全零向量表示。 这被称为参考类别。 虚拟编码和独热编码都是在Pandas中以pandas.get_dummies的形式实现的。

表5-2 对3个城市的类别进行dummy编码

Citye1e2San Francisco10New York01Seattle00

使用虚拟编码进行建模的结果比单编码更易解释。这很容易在简单的线性回归问题中看到。 假设我们有一些数据关于三个城市的公寓租赁价格:旧金山,纽约和西雅图。(见表5-3)

表5-3 三个不同城市的公寓价格数据集

idcityRent0SF39991SF40002SF40013NYC34994NYC35005NYC35016Seattle24997Seattle25008Seattle2501

图5-1 公寓租金价格在one-hot编码中的向量空间表示。点的大小表达了数据集中租金不同价格的平均数。

我们这时能够仅仅依靠城市这一个变量来建立线性回归来预测租金的价格。

线性回归模型可以这样写

y=w_1x_1+w_2x_2+w_3x_3+...+w_nx_n \\

习惯上我们还添加一个常量来,这样的话当x全部为0,y不会为0.

例5-1.在独热编码上的线性回归

import pandas as pd
from sklearn import linear_model
df = pd.DataFrame({
    'City':
    ['SF', 'SF', 'SF', 'NYC', 'NYC', 'NYC', 'Seattle', 'Seattle', 'Seattle'],
    'Rent': [3999, 4000, 4001, 3499, 3500, 3501, 2499, 2500, 2501]
})
df['Rent'].mean()

输出3333.3333333333335

one_hot_df = pd.get_dummies(df, prefix=['city'])
one_hot_df

输出:


model = linear_model.LinearRegression()
model.fit(one_hot_df[['city_NYC', 'city_SF', 'city_Seattle']],
          one_hot_df[['Rent']])
model.coef_

输出array([[ 166.66666667, 666.66666667, -833.33333333]])

使用dummy code进行回归

dummy_df = pd.get_dummies(df, prefix=['city'], drop_first=True)
dummy_df

输出:


model.fit(dummy_df[['city_SF', 'city_Seattle']], dummy_df['Rent'])

输出:LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

print(model.coef_)
print(model.intercept_)

输出:array([ 500., -1000.]) 3500.0 通过独热编码,截距项表示目标变量的全局均值租金价格,并且每个线性系数表示该城市的平均租金与全局平均值的差异。

通过虚拟编码,偏差系数代表响应的平均值参考类别的变量y,在这个例子中是纽约市。该第i个特征的系数等于平均响应之间的差异第i类别的值和参考类别的平均值。

表5-4:线性回归学得的系数

idx1x2x3bone-hot166.67666.67-833.333333.33dummy coding0500-10003500

Effect编码

分类变量编码的另一种变体称为Effect编码。 Effect编码与虚拟编码非常相似,区别在于参考类别现在由所有-1的向量表示。

表5-5: Effect编码表示3个城市

Citye1e2San Francisco10New York01Seattle-1-1

Effect编码与虚拟编码非常相似,但是在线性回归中更容易被拟合。例子5-2表达了运行机理。截距项表示目标的全球平均值变量,单个系数表示各个类别的平均值与全球平均值有多少差异。 (这被称为类别或级别的主要效果,因此名称为“效果编码”。)独热编码实际上具有相同的截距和系数,但在这种情况下,每个城市都有线性系数。 在效果编码中,没有单一特征代表参考类别。 因此,参考类别的影响需要分别计算为所有其他类别的系数的负和。(查看what is effect coding?)

例子5-2 Effect编码的线性回归

effect_df = dummy_df.copy()
effect_df.loc[3:5, ['city_SF', 'city_Seattle']] = -1.0
effect_df

输出:


model.fit(effect_df[['city_SF', 'city_Seattle']], effect_df['Rent'])

输出:LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

print(model.coef_)
print(model.intercept_)

输出:array([ 666.66666667, -833.33333333]) 3333.3333333333335

类别变量的优点和缺点

独热,虚拟和效果编码非常相似。 他们每个人都有优点和缺点。 独热编码是多余的,它允许多个有效模型一样的问题。 非唯一性有时候对解释有问题。该优点是每个特征都明显对应于一个类别。 此外,失踪数据可以编码为全零矢量,输出应该是整体目标变量的平均值。

虚拟编码和效果编码不是多余的。 他们产生独特和可解释的模型。 虚拟编码的缺点是它不能轻易处理缺少数据,因为全零矢量已经映射到参考类别。它还编码每个类别相对于参考类别的影响,其中看起来很奇怪。 效果编码通过使用不同的代码来避免此问题参考类别。 但是,所有-1的矢量都是一个密集的矢量,对于存储和计算来说都很昂贵。 因此,Pandas和Scikit Learn等流行的ML软件包选择了虚拟编码或独热编码,而不是效应编码。当类别数量变得非常多时,所有三种编码技术都会失效大。 需要不同的策略来处理非常大的分类变量。

处理大量的类别特征

互联网上的自动数据收集可以生成大量的分类变量。这在诸如定向广告和欺诈检测等应用中很常见。 在有针对性的广告中,任务是根据用户的搜索查询或当前页面将用户与一组广告进行匹配。 功能包括用户ID,广告的网站域,搜索查询,当前页面以及这些功能的所有可能的成对连词。 (查询是一个文本字符串,可以切分成常用的文本特征,但查询通常很短,通常由短语组成,因此在这种情况下最好的行为通常是保持完整,或 通过哈希函数来简化存储和比较,我们将在下面更详细地讨论哈希。)其中每一个都是一个非常大的分类变量。 我们面临的挑战是如何找到一个能够提高内存效率的优秀特征表示,并生成训练速度快的准确模型。

对于这种类别特征处理的方案有:

  1. 对编码不做任何事情。 使用便宜的训练简单模型。 在许多机器上将独热编码引入线性模型(逻辑回归或线性支持向量机)。
  2. 压缩编码,有两种方式
  • a. 对特征进行哈希--在线性回归中特别常见
  • b. bin-counting--在线性回归中与树模型都常见

使用one-hot编码是可行的。在微软搜索广告研究中,Graepel等人 [2010]报告在贝叶斯概率回归模型中使用这种二值特征,可以使用简单更新在线进行培训。 与此同时,其他组织则争论压缩方法。 来自雅虎的研究人员 通过特征散列方式[Weinberger et al.2009年]。 尽管McMahan等人[2013]在谷歌的广告引擎上尝试了功能哈希,并没有找到显着的改进。 然而,微软的其他人则被认为是计数[Bilenko,2015]。

我们将会看到,所有这些想法都有利有弊。 我们将首先描述解决方案本身,然后讨论他们的权衡。

特征哈希

散列函数是一个确定性函数,它映射一个潜在的无界整数到有限整数范围[1,m]。 由于输入域可能大于输出范围,多个数字可能会映射到相同的输出。 这被称为a碰撞。 统一的散列函数可确保大致相同数量的数字被映射到每个m箱。 在视觉上,我们可以将散列函数视为一台机器可以吸入编号的球并将它们传送到一个m箱。 球与相同的号码将始终被路由到同一个bin。

散列函数可以为任何可以用数字表示的对象构造(对于可以存储在计算机上的任何数据都是如此):数字,字符串,复杂的结构等。

当有很多特征时,存储特征向量可能占用很多空间。 特征散列将原始特征向量压缩为m维通过对特征ID应用散列函数来创建矢量。 例如,如果原件特征是文档中的单词,那么散列版本将具有固定的词汇大小为m,无论输入中有多少独特词汇。

例5-3 对单词的特征哈希

def hash_features(word_list, m):
    output = [0] * m

    for word in word_list:
        index = hash_fcn(word) % m
        output[index] += 1

    return output

功能散列的另一个变体添加了一个符号组件,因此计数也是从哈希箱中增加或减少。 这确保了内部产品之间散列特征与原始特征的期望值相同。

def hash_features(word_list, m):
    output = [0] * m
    for word in word_list:
        index = hash_fcn(word) % m
        sign_bit = sign_hash(word) % 2
        if (sign_bit == 0):
            output[index] -= 1
        else:
            output[index] += 1
    return output

哈希后内积的值在时间复杂度在O(1/(m**0.5)).所以哈希表m的大小可以根据可接受的错误来选择。在实践中,选择合适的m可能需要一些试验和错误。特征哈希可以用于涉及特征内积的模型矢量和系数,例如线性模型和核心方法。 它一直证明在垃圾邮件过滤任务中取得成功[Weinberger等,2009]。在有针对性的广告案例中,McMahan et al. [2013年]报告不能将预测误差降低到可接受的水平,除非m的数量级为数十亿。散列特征的一个缺点是散列特征是聚合的原始特征,不再可解释。

在这个例子中,我们将使用Yelp评论数据集来演示存储和,解释性使用的为sklearn的库FeatureHasher

import pandas as pd
import json
js = []
#data文件夹下的数据集需要自己下载
with open('data/yelp_academic_dataset_review.json') as f:
    for i in range(10000):
        js.append(json.loads(f.readline()))

review_df = pd.DataFrame(js)

m = len(review_df.business_id.unique())
from sklearn.feature_extraction import FeatureHasher
h = FeatureHasher(n_features=m, input_type='string')
f = h.transform(review_df['business_id'])
review_df['business_id'].unique().tolist()[0:5]

输出:['9yKzy9PApeiPPOUJEtnvkg', 'ZRJwVLyzEJq1VAihDhYiow', '6oRAC4uyJCsJl1X0WZpVSA', '_1QQZuf4zZOyFCvXc0o6Vg', '6ozycU1RpktNG2-1BroVtw']

print(f.toarray())

输出:array([[0., 0., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.], ..., [0., 0., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.]])

我们看看特征的存储

from sys import getsizeof
print('Our pandas Series, in bytes: ', getsizeof(review_df['business_id']))
print('Our hashed numpy array, in bytes: ', getsizeof(f))

输出:Our pandas Series, in bytes: 790104 Our hashed numpy array, in bytes: 56 我们可以清楚地看到如何使用特征散列会以计算方式使我们受益,牺牲直接的用户解释能力。 这是一个容易的权衡来接受何时从数据探索和可视化发展到机器学习管道对于大型数据集。

发布于 2020-05-01 22:45