机器学习笔记七——特征工程之特征处理

在实际应用中,数据的类型多种多样,比如文本、音频、图像、视频等。而很多机器学习算法要求输入的样本特征是数学上可计算的,因此在机器学习之前需要将不同类型的数据转换为向量表示即数据的特征表示。
特征分类:

对特征进行分类,对于不同的特征应该有不同的处理方法。

根据不同的分类方法,可以将特征分为

(1)Low level特征和High level特征。

  • Low level特征——较低级别的特征,主要是原始特征,不需要或者需要非常少的人工处理和干预。
    例如文本特征中的词向量特征,图像特征中的像素点,用户id,商品id等。
    Low level特征一般维度比较高,不能用过于复杂的模型。

  • High level特征——经过较复杂的处理,结合部分业务逻辑或者规则、模型得到的特征。
    例如人工打分,模型打分等特征,可以用于较复杂的非线性模型。

  • Low level 比较针对性,覆盖面小。长尾样本的预测值主要受high level特征影响。 高频样本的预测值主要受low level特征影响。

(2)稳定特征与动态特征。

  • 稳定特征——变化频率(更新频率)较少的特征
    例如评价平均分,团购单价格等,在较长的时间段内都不会发生变化。

  • 动态特征——更新变化比较频繁的特征,有些甚至是实时计算得到的特征
    例如距离特征,2小时销量等特征。或者叫做实时特征和非实时特征。

  • 针对两类特征的不同可以针对性地设计特征存储和更新方式
    例如
    对于稳定特征,可以建入索引,较长时间更新一次,如果做缓存的话,缓存的时间可以较长。
    对于动态特征,需要实时计算或者准实时地更新数据,如果做缓存的话,缓存过期时间需要设置的较短。

(3)二值特征、连续型特征、离散型特征、枚举特征。

  • 二值特征——主要是0/1特征,即特征只取两种值:0或者1
    例如用户id特征:目前的id是否是某个特定的id,词向量特征:某个特定的词是否在文章中出现等等。

  • 连续型特征——取值为连续实数的特征。比如,身高175.4cm。特征取值为是0~正无穷。

  • 离散性特征——取值为离散实数的特征。离散型特征又可以分为类别型和序列型

    • 类别型特征:取离散值,表示没有比较关系的类型。比如,血型有 A 型、B 型、AB 型和 O 型 4 种,它们各自为一个独立类型。
    • 序列型特征:取离散值,表示有比较关系的类型。比如,收入划分为为 “高”、“中”、“低”3 种类型,有比较关系。
  • 枚举值特征——主要是特征有固定个数个可能值,例如今天周几,只有7个可能值:周1,周2,…,周日。

模型输入的特征通常需要数值型的,所以需要将非数值型特征转换为数值特征。 如性别、职业、收入水平、国家、汽车使用品牌等。
机器学习模型需要的数据是数字型的,因为只有数字类型才能进行计算,而我么你平时处理到的一些数据是很多是符号的,或者是中文的。所以编码是必要的,对于各种各样的特征值去编码实际上就是一个量化的过程

1、类别型特征的处理—特征编码

类别特征,见名思义,就是用来表达一种类别或标签

分类变量的类别通常不是数字,需要使用编码方法将这些非数字类别变为数字。即特征编码

特征编码

1.1 处理少量特征

1、One-hot 编码
又称独热编码

每个特征取值对应一维特征,从而得到稀疏的特征矩阵。
一个绝对的具有k个可能类别的变量被编码为长度为k的特征
向量。
如表1即为对三种水果进行编码

e1e2e3
apple100
banna010
grape001

独热编码e1,e2,e3限制条件:
e1 + e2 + e3 = 1

df = pd.DataFrame({
    'fruit':
    ['apple', 'apple', 'banna', 'banna', 'grape'],
    'Rent': [10, 10, 15, 15, 20]
})
one_hot_df = pd.get_dummies(df, prefix=['fruit'])

优点:

  • 能够处理非数值属性。比如血型、性别等

  • 一定程度上扩充了特征。

  • 编码后的向量是稀疏向量,只有一位是 1,其他都是 0,可以利用向量的稀疏来节省存储空间。

  • 能够处理缺失值。当所有位都是 0,表示发生了缺失。此时可以采用处理缺失值提到的高维映射方法,用第 N+1 位来表示缺失值。

缺点:

1.高维度特征会带来以下几个方面问题:

  • KNN 算法中,高维空间下两点之间的距离很难得到有效的衡量;

  • 逻辑回归模型中,参数的数量会随着维度的增高而增加,导致模型复杂,出现过拟合问题;

  • 通常只有部分维度是对分类、预测有帮助,需要借助特征选择来降低维度。

2.决策树模型不推荐对离散特征进行独热编码,有以下两个主要原因:

  • 产生样本切分不平衡问题,此时切分增益会非常小。

比如对血型做独热编码操作,那么对每个特征是否 A 型、是否 B 型、是否 AB 型、是否 O 型,会有少量样本是 1 ,大量样本是 0。

这种划分的增益非常小,因为拆分之后:

  • 较小的那个拆分样本集,它占总样本的比例太小。无论增益多大,乘以该比例之后几乎可以忽略。

  • 较大的那个拆分样本集,它几乎就是原始的样本集,增益几乎为零。

  • 影响决策树的学习。

决策树依赖的是数据的统计信息。而独热码编码会把数据切分到零散的小空间上。在这些零散的小空间上,统计信息是不准确的,学习效果变差。

本质是因为独热编码之后的特征的表达能力较差。该特征的预测能力被人为的拆分成多份,每一份与其他特征竞争最优划分点都失败。最终该特征得到的重要性会比实际值低。

2、dummy 编码
又称虚拟编码
一个绝对的具有k个可能类别的变量被编码为长度为k-1的特征
向量。
全零向量表示参考类别.

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

3、Effect 编码
一个绝对的具有k个可能类别的变量被编码为长度为k-1的特征
向量。
全负一向量表示参考类别.

e1e2
apple10
banna01
grape-1-1

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

优缺点对比
在这里插入图片描述

4、序号编码

序号编码一般用于处理类别间具有大小关系的数据即序列型特征。

比如成绩,可以分为高、中、低三个档次,并且存在“高>中>低”的大小关系,那么序号编码可以对这三个档次进行如下编码:高表示为 3,中表示为 2,低表示为 1,这样转换后依然保留了大小关系。
分层 编码
比如邮编、身份证号等,取不同的位数进行分层,然后按层次进行自然数编码。
散列编码
对于取值特别多的,可以先散列后独热。可能导致特征取值冲突。
计数编码
这种方法对异常值比较敏感,也可能导致特征取值冲突。
计数排名编码
对异常点不敏感,不会导致特征取值冲突。
目标编码
有监督的编码方法,需结合交叉验证。(如CTR任务中,可对广告主ID结合过去固定时间段内的点击率进行编码)
交叉组合编码
交叉组合(类别+类别):笛卡儿积操作、基于分组统计。

交叉组合(类别+数值):如统计产品在某个区域的销量、价格、平均价差等。

1.2处理大量的类别特征

1.对编码不做任何事情。使用便宜的训练简单模型。在许多机器上将独热编码引入线性模型(逻辑回归或线性支持向量机)。

2.压缩编码,有两种方式

  • 对特征进行哈希–在线性回归中特别常见
  • bin-counting–在线性回归中与树模型都常见

特征哈希
特征哈希
bin-counting
相关介绍

2、文本特征的处理——文本形式转换为向量形式

文本特征提取有两个非常重要的模型:

词集模型: 单词构成的集合,集合中每个元素都只有一个,也即词集中的每个单词都只有一个。

词袋模型: 在词集的基础上如果一个单词在文档中出现不止一次,统计其出现的次数(频数)。

两者本质上的区别:词袋是在词集的基础上增加了频率的维度,词集只关注有和没有,词袋还要关注有几个。

假设我们要对一篇文章进行特征化,最常见的方式就是词袋模型。

2.1 词集模型

词集模型(Set of Words,简称SoW): 单词构成的集合,每个单词只出现一次。和词袋模型唯一的不同是它仅仅考虑词是否在文本中出现,而不考虑词频。也就是一个词在文本在文本中出现1次和多次特征处理是一样的。在大多数时候,我们使用词袋模型,后面的讨论也是以词袋模型为主。

2.2 词袋(Bag-of-Words,Bow)模型

词袋模型(Bag of Words,简称BoW), 即将所有词语装进一个袋子里,不考虑其词法和语序的问题,即每个词语都是独立的,把每一个单词都进行统计,同时计算每个单词出现的次数。也就是说,词袋模型不考虑文本中词与词之间的上下文关系,仅仅只考虑所有词的权重,而权重与词在文本中出现的频率有关。

词袋模型的三部曲:分词(tokenizing),统计修订词特征值(counting)与向量化

具体步骤:
1、分词:首先将训练样本中所有不重复的词放到袋子中构成一个词表(字典);

2、统计每个词在训练样本中出现的次数

3、向量化:以这个词表为标准来遍历每一个样本,如果词表中对应位置的词出现在了样本中,那么对应位置就用1来表示,没有出现就用0来表示;最后,对于每个样本来说都将其向量化成了一个和词表长度一样的0-1向量。
在这里插入图片描述
词袋模型的局限性

  • 词汇表的构建:词汇表的建立和维护都值得考量,不合理的词汇表将导致文档表示向量的稀疏问题显著。
  • 稀疏问题:大部分的文本都只会使用词汇表中的很少一部分的词,因此词向量中会有大量的0。也就是说词向量是稀疏的。
  • 语义:因为词袋模型没有考虑到语序,但是往往语序又蕴含着不同的语义信息。比如“this is interesting” vs “is this interesting”,不同的语序代表的语义是不同的。

例子:
对下面四个文本进行词袋化

This is the first document.
This is the second second document.
And the third one.
Is this the first document?

词袋模型CountVectorizer实现
直接用scikit-learn的CountVectorizer类来完成,这个类可以帮我们完成文本的词频统计与向量化。

CountVectorize函数比较重要的几个参数为:

  • decode_error,处理解码失败的方式,分为‘strict’、‘ignore’、‘replace’三种方式。

  • strip_accents,在预处理步骤中移除重音的方式。默认为None,可设为ascii或unicode,将使用ascii或unicode编码在预处理步骤去除raw document中的重音符号。

  • max_features,词袋特征个数的最大值。

  • stop_words,设置停用词,设为english将使用内置的英语停用词,设为一个list可自定义停用词,设为None不使用停用词,设为None且max_df∈[0.7, 1.0)将自动根据当前的语料库建立停用词表。

  • max_df,可以设置为范围在[0.0 1.0]的float,也可以设置为没有范围限制的int,默认为1.0。这个参数的作用是作为一个阈值,当构造语料库的关键词集的时候,如果某个词的document frequence大于max_df,这个词不会被当作关键词。如果这个参数是float,则表示词出现的次数与语料库文档数的百分比,如果是int,则表示词出现的次数。如果参数中已经给定了vocabulary,则这个参数无效。

  • min_df, 类似于max_df,参数min_df=n表示词必须要在至少n个文档中出现过,否则就不考虑。

  • binary,默认为False,当与TF-IDF结合使用时需要设置为True。

CountVectorizer是通过fit_transform()函数将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在第i个文本下的词频。即各个词语出现的次数,通过get_feature_names()可看到所有文本的关键字,通过toarray()可看到词频矩阵的结果。

【代码实现】

1、分词、获取词频

from sklearn.feature_extraction.text import CountVectorizer
 
# 实例化分词对象
vec = CountVectorizer(min_df=1)
 
# 将文本进行词袋处理
corpus = ['This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?']
 
X = vec.fit_transform(corpus)#词频矩阵
print(X)

输出

  (0, 8)	1
  (0, 3)	1
  (0, 6)	1
  (0, 2)	1
  (0, 1)	1
  (1, 8)	1
  (1, 3)	1
  (1, 6)	1
  (1, 1)	1
  (1, 5)	2
  (2, 6)	1
  (2, 0)	1
  (2, 7)	1
  (2, 4)	1
  (3, 8)	1
  (3, 3)	1
  (3, 6)	1
  (3, 2)	1
  (3, 1)	1

可以看出4个文本的词频已经统计出,输出中,左边的括号中的第一个数字是文本的序号,第2个数字是词的序号,注意词的序号是基于所有的文档的。第三个数字就是我们的词频。

2、获取对应的特征名称,即各个特征代表的词:

fnames = vec.get_feature_names()# 获得文本的关键字
print(fnames)

输出:

['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']

3、获取词袋数据,即每个文本的词向量特征:

arr = X.toarray()# 向量化结果即每个文本的词向量特征
print(arr)

输出:

[[0 1 1 1 0 0 1 0 1]
 [0 1 0 1 0 2 1 0 1]
 [1 0 0 0 1 0 1 1 0]
 [0 1 1 1 0 0 1 0 1]]

至此,完成了词袋化

4、也可以获取词频矩阵与关键字的列联表

import pandas as pd
pd.DataFrame(arr, columns=fnames)

输出:

在这里插入图片描述

完整代码:

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
 
# 实例化分词对象
vec = CountVectorizer(min_df=1)
 
# 将文本进行词袋处理
corpus = ['This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?']
 
X = vec.fit_transform(corpus)#词频矩阵
print(X)
fnames = vec.get_feature_names()# 获得文本的关键字
print(fnames)
arr = X.toarray()# 向量化结果即每个文本的词向量特征
print(arr)
pd.DataFrame(arr, columns=fnames)# 词频矩阵与关键字的列联表

2.3 Bag-of-N-gram-Feature(N元特征词袋模型)

词袋模型将文本看作词的集合,不考虑词序信息,不能精确地表示文本信息。一种改进方式是在词袋模型的基础上引入N元特征(N-gram-Feature) 即Bag-of-N-gram-Feature。

Bag-of-N-gram-Feature是 BOW 的自然延伸。

具体步骤:

1、分词:将训练样本中所有不重复的词以及每N个连续词构成的基本词放到袋子中构成一个词表(字典);

2、统计每个词在训练样本中出现的次数

3、向量化:以这个词表为标准来遍历每一个样本,如果词表中对应位置的词出现在了样本中,那么对应位置就用1来表示,没有出现就用0来表示;最后,对于每个样本来说都将其向量化成了特征数量较大的0-1向量。

以最简单的二元特征为例:
文本‘Bi-grams are cool!’,引入二元特征以后,共有’bi’, ‘grams’, ‘are’, ‘cool’, ‘bi grams’, ‘grams are’, 'are cool‘七个特征单元.
验证:

bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
                                    token_pattern=r'\b\w+\b', min_df=1)
analyze = bigram_vectorizer.build_analyzer()
analyze('Bi-grams are cool!') == (
    ['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool'])

输出:
True
Bag-of-N-gram-Feature的缺点:

N-gram 保留了文本的更多原始序列结构,故 bag-of-ngram可以提供更多信息。但是,这是有代价的。理论上,用
k 个独特的词,可能有 k 个独立的 2-gram(也称为 bigram)。在实践中,并不是那么多,因为不是每个单词后都可以跟一个单词。尽管如此,通常有更多不同的 n-gram(n > 1)比单词更
多。这意味着词袋会更大并且有稀疏的特征空间。这也意味着 n-gram 计算,存储和建模的成本会变高。n 越大,信息越丰富,成本越高

例子:对下面四个文本进行二元特征词袋化

from sklearn.feature_extraction.text import CountVectorizer
#ngram_range参数(1,2)表示单独词,每两个连续词构成基本单元
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
                                    token_pattern=r'\b\w+\b', min_df=1)
# 将文本进行二元特征词袋处理
corpus = ['This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?']
X = bigram_vectorizer.fit_transform(corpus)#词频矩阵
print(X)
fnames = bigram_vectorizer.get_feature_names()# 获得文本的关键字
print(fnames)
len(fnames)#关键字长度
X_2 = bigram_vectorizer.fit_transform(corpus).toarray()#向量化结果即每个文本的词向量特征
X_2

关CountVectorizer的更多用法见官方文档sklearn.feature_extraction.text.CountVectorizer

在这里插入图片描述

3、数字特征处理

数值类型的数据具有实际测量意义,分为连续型(身高体重等)和离散型(计数等),常用的处理方法:
1.二值化
非一即二
适应于一类占主要一大部分,而多种类别共占一小部分(如99%的人喜欢喝水,1%的人分别喜欢喝茶,喝饮料等。此时,可以将类别分为喜欢喝水,不喜欢喝水)
2.量化与装箱
对于许多模型来说,跨越数个数量级的原始计数是有问题的。数据向量的一个元素中的大计数将超过所有其他元素中的相似性,这可能会丢弃整个相似性度量。

一种解决方案是通过量化计数来包含标量。换句话说,我们将计数分组到容器中,并且去掉实际的计数值。量化将连续数映射成离散数。我们可以把离散化的数字看作是代表强度度量的容器的有序的序列。

装箱

即分组(分桶)
1.固定宽度装箱:均匀或者以10的幂装箱
用固定宽度的箱进行量化计数

import numpy as np
small_counts = np.random.randint(0, 100, 20)#随机产生0到100之间的数
np.floor_divide(small_counts, 10)#以10为间隔进行装箱
np.floor(np.log10(small_counts))#以10的幂进行装箱

2.分位数装箱:
固定宽度装箱很容易计算。但是如果计数有很大的差距, 那么将会有许多空的箱没有数据。该问题可以通过基于数据分布的垃圾箱自适应定位来解决。这可以使用分发的分位数来完成。

分位数是将数据划分为相等部分的值。例如, 中位数将数据分成一半;一半的数据是较小的, 一半大于中位数。分位数把数据分成几个部分, 十分位数把数据划分成十份。

import pandas as pd
large_counts = [
    296, 8286, 64011, 80, 3, 725, 867, 2215, 7689, 11495, 91897, 44, 28, 7971,
    926, 122, 22222
]
pd.qcut(large_counts, 4, labels=False)

3.对数转换
对数变换是处理具有重尾分布的正数的有力工具。(重尾分布在尾部范围内的概率比高斯分布的概率大)。它将分布在高端的长尾压缩成较短的尾部,并将低端扩展成较长的头部。

4.特征缩放
如果你的模型对输入特征的数值范围敏感, 则特征缩放可能会有所帮助。

Min-max缩放
设x是一个单独的特征值(即,在某些数据点中的一个特征值),以及min(x) 和 max(x) ,分别是整个数据集上该特征的最小值和最大值。Min-max缩放压缩(或拉伸)所有特征值到[0,1]的范围内。最小最大尺度的公式是
x 1 = x − m i n ( x ) m a x ( x ) − m i n ( x ) x_1=\frac{x-min(x)}{max(x)-min(x)} x1=max(x)min(x)xmin(x)
标准化(方差缩放)
缩放后的特征的平均值为0, 方差为1。
公式为:
x 2 = x − m e a n ( x ) v a r ( x ) x_2=\frac{x-mean(x)}{var(x)} x2=var(x)xmean(x)
5.缺失值处理
补均值、补中位数、用模型预测缺失值、将缺失作为一种信息进行编码喂给模型让其学习。
6.特征交叉
两个数值变量的加减乘除、FM和FFM模型。

4、特征缩放

如果你的模型对输入特征的数值范围敏感, 则特征缩放可能会有所帮助。

特征缩放主要分为两种方法,归一化正则化。

4.1 归一化

归一化(Normalization),也称为标准化, 这里不仅仅是对特征,实际上对于原始数据也可以进行归一化处理,它是将特征(或者数据)都缩放到一个指定的大致相同的数值区间内。

归一化的两个原因:

  • 某些算法要求样本数据或特征的数值具有零均值和单位方差;

-为了消除样本数据或者特征之间的量纲影响,即消除数量级的影响。如下图所示是包含两个属性的目标函数的等高线

在这里插入图片描述
常用的两种归一化方法:
在这里插入图片描述
归一化不是万能的,实际应用中,通过梯度下降法求解的模型是需要归一化的,这包括线性回归、逻辑回归、支持向量机、神经网络等模型。但决策树模型不需要,以 C4.5 算法为例,决策树在分裂结点时候主要依据数据集 D 关于特征 x 的信息增益比,而信息增益比和特征是否经过归一化是无关的,归一化不会改变样本在特征 x 上的信息增益。

4.2 正则化

正则化是将样本或者特征的某个范数(如 L1、L2 范数)缩放到单位 1。

在这里插入图片描述

  • 正则化的过程是针对单个样本的,对每个样本将它缩放到单位范数。

  • 归一化是针对单个属性的,需要用到所有样本在该属性上的值。

  • 一般如果使用二次型(如点积)或者其他核方法计算两个样本之间的相似性时,该方法会很有用。

参考资料:
1、《百面机器学习》
2、https://blog.csdn.net/laolu1573/article/details/79410187

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习水果识别是一种利用机器学习算法和图像处理技术对水果进行自动识别的方法。其中,使用Python中的OpenCV库实现物体特征提取是一种常见的实现方式。 OpenCV是一个强大的开源计算机视觉库,提供了许多用于图像处理和分析的函数和工具。它可以辅助我们实现水果识别所需要的特征提取步骤。 首先,我们需要准备水果图像数据集。这些图像可以是不同种类的水果,每个水果都有多个不同视角的图像。接下来,我们使用OpenCV库中的函数加载和处理这些图像。 在特征提取中,我们可以使用很多不同的技术。其中,最常用的方法是使用图像的颜色和纹理特征。在处理图像时,我们可以使用OpenCV中的函数计算这些特征。 例如,我们可以使用OpenCV中的函数提取图像的颜色直方图。这可以帮助我们了解图像中不同颜色的比例和分布情况。在水果识别中,不同水果的颜色特征往往是不同的。 此外,我们还可以使用OpenCV中的纹理特征提取方法,比如局部二值模式(Local Binary Patterns)。这可以帮助我们分析图像中的纹理信息,如图像的细节和纹理变化。这些纹理特征在识别不同类型的水果时也是有用的。 最后,我们可以使用机器学习算法,如支持向量机(SVM)或卷积神经网络(CNN),来训练一个分类模型。这个模型可以根据提取的特征来判断输入图像是否为某种水果。 总之,使用Python中的OpenCV库实现水果识别中的物体特征提取是一种非常有效的方法。通过提取图像的颜色和纹理特征,并使用机器学习算法进行分类,我们可以实现一个准确和效的水果识别系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值