上篇回归分析中用到的数据都是数值型的,但是机器学习中遇到的很多问题可能是分类变量、文字甚至图像,所以需要对这些对象进行转化,将其序列化,即特征提取。

sklearn中特征提取主要是应用feature_extraction子模块,而该子模块主要分为from text 和from images 两种形式:

(1)文本特征提取

1.1 CountVectoizer

from sklearn.feature_extraction.text import CountVectorizer
text = ["this is a test",
       "test sklearn model's feature_extraction"]
vectorizer = CountVectorizer()
vectorizer
print('文集的特征向量为:\n%s' % vectorizer.fit_transform(text).todense())
#todense为稀疏矩阵,也可以用toarray()转化为array格式
print('文集的词汇表为:\n%s' % vectorizer.vocabulary_)

输出结果为:

CountVectorizer(analyzer='word', binary=False, decode_error='strict', dtype=<class 'numpy.int64'>, encoding='utf-8', input='content', lowercase=True, max_df=1.0, max_features=None, min_df=1, ngram_range=(1, 1), preprocessor=None, stop_words=None, strip_accents=None, token_pattern='(?u)\b\w\w+\b', tokenizer=None, vocabulary=None) 文集的特征向量为: [[0 1 0 0 1 1] [1 0 1 1 1 0]] 文集的词汇表为: {'this': 5, 'sklearn': 3, 'model': 2, 'test': 4, 'is': 1, 'feature_extraction': 0} 从vectorizer的输出结果看,CountVectorizer类通过正则表达式用空格分割句子,抽取长度大于等于2的字母序列,所以词汇表中没有'a',因为长度小于2;text称为文集,''内的句子称为文档,我们用词汇表中每个单词的特征向量来表示每个文档,用一个词典来表示词汇表与特征向量索引的对应关系,特征向量的每一个元素是用二进制表示单词是否在文档中;

另外从vectorizer的输出结果中看到,stop_words(停用词)的默认值为空,可以设置stop_words将is,the,be,wil这些常用词过滤掉,这样可以将文档的特征向量降维:

vectorizer_s = CountVectorizer(stop_words='english')
print(vectorizer_s.fit_transform(text).todense())
print(vectorizer_s.vocabulary_)
'''
输出结果为:
[[0 0 0 1]  [1 1 1 1]]
{'feature_extraction': 0, 'sklearn': 2, 'test': 3, 'model': 1}
'''

1.2 TfidfVectorizer

前面说的CountVectorizer创建的特征向量与单词的语法、顺序、频率无关,但是一个单词出现的频率对文档有重要作用,多次出现的单词比只出现一次的单词更能体现文档的意思,可以将CountVectorizer中的binary参数设置为False,返回词汇表的词频而不是二进制结果:

corpus = [
   'The dog ate a sandwich and I ate a sandwich',
'The wizard transfigured a sandwich'
]
vectorizer = CountVectorizer(binary=False, stop_words='english')
print(vectorizer.fit_transform(corpus).todense())

结果为:

[[2 1 2 0 0] [0 0 1 1 1]] 可以看出返回的是文集中每个文档中单词出现的次数; 但是可能出现的情况是很多单词在每个文档中出现的频率一样,但是两个文档的长度差别很大, 或者是一个高频词在文集中其他文档中出现的频率也很大,这些单词并没有突出代表单个文档, 所以引用用tf和idf这两个指标,调用TfidfVectorizer类来解决这两个问题;

vectorizer = TfidfVectorizer(stop_words='english')
print(vectorizer.fit_transform(corpus).todense())
print(vectorizer.vocabulary_)

[[ 0.75458397 0.37729199 0.53689271 0. 0. ] [ 0. 0. 0.44943642 0.6316672 0.6316672 ]] {'sandwich': 2, 'transfigured': 3, 'ate': 0, 'dog': 1, 'wizard': 4}

1.3 HashingVectorizer 前面是用包含文集所有词块的词典来完成文档词块与特征向量的映射的;但是这么做文集必须被 调用两次,第一次是创建词典,第二次是创建文档的特征向量;而且词典必须保存在内存中,如 果文集特别大的话就特别消耗内存;这些问题可以通过哈希表有效解决,可以将词块用哈希函数 来确定它在特征向量的索引位置,不创建词典,这称谓哈希技巧;哈希技巧是无固定状态的,它 将任意的数据块映射到固定数目的位置,并保证相同的输入一定产生相同的输出,不同的输入尽 可能产生不同不同的输出;但是哈希技巧的一个不足是模型结果更难察看,因为哈希函数不能显示 哪个词块映射到特征向量的哪个位置了:

from sklearn.feature_extraction.text import HashingVectorizer
vectorizer = HashingVectorizer(n_features=6)
#n_features是特征向量的维度,默认值是2^20
print(vectorizer.transform(corpus).todense())

[[-0.37796447 0. 0.75592895 0.37796447 0. -0.37796447] [-0.5 0. 0.5 0.5 0. 0.5 ]]

(2)图片特征提取 2.1 通过像素值提取特征 一张图片可以看成一个元素都是颜色值的矩阵,表示图像基本特征就是将矩阵每行连起来变成 一个行向量:

from sklearn import datasets
import matplotlib.pyplot as plt
digits = datasets.load_digits()
plt.figure()
plt.axis('off')
plt.imshow(digits.images[1])
plt.show()

2.2 对感兴趣的点进行特征提取 前面创建的特征矢量包含了图像的每个像素,既包含了图像特征的有用信息,也包含了一堆噪声。 但是有些像素是没用的,我们可以仅借助有用的信息来识别图像,这些有用的信息的属性就被称为兴趣点;边缘(edges) 和角点(corners)是两种常用的兴趣点类型。可以用scikit-image来抽取兴趣点:

import numpy as np
from skimage.feature import corner_harris, corner_peaks
from skimage.color import rgb2gray
import matplotlib.pyplot as plt
import skimage.io as io
from skimage.exposure import equalize_hist

def show_corners(corners, image):
    fig = plt.figure()
    plt.gray()
    plt.imshow(image)
    y_corner, x_corner = zip(*corners)
    plt.plot(x_corner, y_corner, 'or')
    plt.xlim(0, image.shape[1])
    plt.ylim(image.shape[0], 0)
    plt.show()

mandrill = io.imread('test.jpg')
mandrill = equalize_hist(rgb2gray(mandrill))
corners = corner_peaks(corner_harris(mandrill), min_distance=2)
show_corners(corners, mandrill)