特征提取是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。用 Python 进行特征提取的方法有很多,这里我使用 sklearn.feature_extraction.DictVectorizer 这个类来进行特征提取,毕竟新版本的 scikit-learn 在使用这个类的时候会遇到一些问题,在讲怎么用它进行特征提取的同时顺便把这些问题解决了。
检查版本
首先需要检查 scikit-learn 的版本,我的版本是 0.21.3,如图所示。
检查完版本之后就是讲解怎么使用 DictVectorizer 进行特征提取。
用 DictVectorizer 进行特征提取
虽然在开头我解释了特征提取主要用于提取图像数据的特征,但是提取其他类型数据的特征也是时常会有的。今天讲的 DictVectorizer 主要是用来提取字典数据的特征,当然也可以提取 DataFrame 格式的数据的特征(老版本 scikit-learn 里面的 DictVectorizer 应该或许可以直接用来提取 DataFrame 格式的数据的特征,毕竟我没用过老版本的这个类,但是我敢确定的是新版本需要做一些变换)。
首先跟着老版本的模式先来一波,代码如下:
代码语言:javascript复制from random import random
from pandas import DataFrame
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
df = DataFrame({'X1': [random()for _ in range(100)], 'X2': [random()for _ in range(100)]})
X_train, X_test = train_test_split(df, random_state=0)
dv = DictVectorizer().fit_transform(X_train)
print(dv)
在这里首先我是构造了一个随机生成 100 条数据的数据集,其中每个数据点有两个特征 X1 和 X2,没有目标值,毕竟特征提取和数据转换属于无监督学习的范畴。然后必然是拆分训练集与测试集,接着用 DictVectorizer 对象的 fit_transform 方法对训练集进行训练并转换,最后把转换后的东西做一个输出,这段代码逻辑就是如此,并没有特别复杂。知道了代码逻辑之后就要运行代码了,运行结果如图所示。
发现报错,而且这个错误非常莫名其妙,光看报错完全不知道问题出在哪里。看不出错误没关系,我们可以去看看 scikit-learn 的文档,或许是新版本的 scikit-learn 把 DictVectorizer 这个类的使用方法给改掉了,在文档中我们可以发现这么一个使用 DictVectorizer 的小例子,如图所示。
我们发现 fit_transform 方法里面传入的是一个字典列表格式的数据,而不是其他格式的数据。这个字典列表格式的数据看起来很简单,就是一个列表,其中的每个元素是一个字典,字典键对应着特征名,字典值对应着特征值。DataFrame 格式的数据是一个表格,表格中每一行对应着一条数据,有多少行就有多少条数据,每一列对应着一个特征,有多少列就有多少个特征。知道了这些把 DataFrame 格式的数据转换成字典列表格式的数据就是轻而易举的事情了,直接上代码,如下所示:
代码语言:javascript复制from random import random
from pandas import DataFrame
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
df = DataFrame({'X1': [random()for _ in range(100)], 'X2': [random()for _ in range(100)]})
X_train, X_test = train_test_split(df, random_state=0)
X_train = [{'X1': X_train['X1'][i], 'X2': X_train['X2'][i]}for i in range(75)]
dv = DictVectorizer().fit_transform(X_train)
print(dv)
一行代码就完成了数据格式的转换,这行代码没什么难的,就是一个列表推导式。在这里重点解释一下 75 这个数字,75 意味着 X_train 里面有 75 条数据(同时也暗示了 X_test 里面有 25 条数据),至于为什么是 75 只要记得是 train_test_split 这个函数里面默认规定的就行了。接下来还是运行一下看看,运行结果如图所示。
还是报错,更加莫名其妙,同样也是看不出错在了哪里,我们把那个列表推导式写完整一些,每次循环的时候顺便打印循环变量 i 的值,代码如下:
代码语言:javascript复制from random import random
from pandas import DataFrame
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
df = DataFrame({'X1': [random()for _ in range(100)], 'X2': [random()for _ in range(100)]})
X_train, X_test = train_test_split(df, random_state=0)
li = []
for i in range(75):
print(i)
li.append({'X1': X_train['X1'][i], 'X2': X_train['X2'][i]})
dv = DictVectorizer().fit_transform(X_train)
print(dv)
运行结果如图所示。
确实发现循环变量 i 一旦变成 2 就会出错,我目前敢肯定我的方向是对的,就是数据格式需要做转换,但是这里肯定有一些细节我没注意。我首先猜测问题出在 X_train,先打印一下 X_train 看看,代码如下:
代码语言:javascript复制from random import random
from pandas import DataFrame
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
df = DataFrame({'X1': [random()for _ in range(100)], 'X2': [random()for _ in range(100)]})
X_train, X_test = train_test_split(df, random_state=0)
print(X_train)
li = []
for i in range(75):
print(i)
li.append({'X1': X_train['X1'][i], 'X2': X_train['X2'][i]})
dv = DictVectorizer().fit_transform(X_train)
print(dv)
运行结果如图所示。
我们可以发现 X_train 最左边有一列是一列无序的整数,这一列是索引列,索引无序并且有大于 75 的数,这说明了在 train_test_split 里面进行训练集测试集分离的过程中是带着原来的索引进行分离,分离之后并不会对索引进行更新,既然如此只需要对索引进行迭代就行了,代码如下:
代码语言:javascript复制from random import random
from pandas import DataFrame
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
df = DataFrame({'X1': [random()for _ in range(100)], 'X2': [random()for _ in range(100)]})
X_train, X_test = train_test_split(df, random_state=0)
X_train = [{'X1': X_train['X1'][i], 'X2': X_train['X2'][i]}for i in X_train.index]
dv = DictVectorizer().fit_transform(X_train)
print(dv)
运行结果如图所示。
确实没有报错了,输出结果看看就好,毕竟我瞎构造的数据没有一点实际意义