首页 > 技术文章 > scikit-learn 4.2 Feature extraction特征提取

xmd-home 2018-08-28 09:45 原文

4.2 特征提取

sklearn.feature_extraction 模块可以被用来从包含文本或者特片的数据集中提取出适用于机器学习算法的特征。

注意:特征提取和特征选择是极不相同的:前者由任意数据组成,比如文本或者图片,转换为适用于机器学习的数字。后者是应用于这些特征的机器学习方法。

4.2.1 从字典中加载特征

类DictVectorizer可以将由python标准的列表dict对象所表示的特征转换为由scikit-learn中常用的NumPy/SciPy所表示的对象。

虽然dict处理起来不是特别快,但是其优势在于方便的使用,创建系数矩阵(缺失值不会被存储)以及将特征名字存储与值上。

DictVectorizer可以创建我们 称之为one-of-K或者"one-hot"编码用于分类特征(又名aka nominal,discrete)特征。分类特征是"属性-值"对,值限制为一系列的无序离散变量(比如,主题标识、对象类型、标记、名字。。。)

在下面的例子中,"城市"是一个分类属性,"温度"是另一个数值特征:

>>> measurements = [

... {'city': 'Dubai', 'temperature': 33.},

... {'city': 'London', 'temperature': 12.},

... {'city': 'San Francisco', 'temperature': 18.},

... ]

>>>from sklearn.feature_extration import DictVectorizer

>>>vec = DictVectorizer() #声明一个字典到向量的变换

 

>>>vec.fit_transform(measurements).toarray() #fit_transform将上述的字典转换为一个稀疏矩阵toarray()将稀疏矩阵转换为矩阵的形式。

array([[ 1., 0., 0., 33.],

[ 0., 1., 0., 12.],

[ 0., 0., 1., 18.]])

 

>>>vec.fit_transform(measurements)

(0, 0) 1.0

(0, 3) 33.0

(1, 1) 1.0

(1, 3) 12.0

(2, 2) 1.0

(2, 3) 18.0

>>> vec.get_feature_names()  #输出特征的名字
								

['city=Dubai', 'city=London', 'city=San Francisco', 'temperature']

DictVectorizer对于自然语言处理模型来说也是一个有用的变换,可以提提取特定的单词的特征。

举例来说,假设我们有一个算法可以提取出部分演讲的标签(PoS),我们想要使用这些标签作为补充的标签来训练一个序列分类器(比如,一个chunker)。下面的字典可以作为这样的特征窗口从句子"The cat sat on the mat"中提取出单词"sat"。

>>> pos_window = [

...     {

...         'word-2': 'the',

...         'pos-2': 'DT',

...         'word-1': 'cat',

...         'pos-1': 'NN',

...         'word+1': 'on',

...         'pos+1': 'PP',

...     },

...     # in a real application one would extract many such dictionaries

... ]

该描述可以被向量化一个适用于分类器(可能会被用于text.TfidfTransformer用于正则化)的稀疏的二维矩阵。

>>> vec = DictVectorizer()    #创建一个变换声明
								

>>> pos_vectorized = vec.fit_transform(pos_window)  #将pos_window变换为稀疏矩阵表示的形式。

>>> pos_vectorized                

<1x6 sparse matrix of type '<... 'numpy.float64'>'

    with 6 stored elements in Compressed Sparse ... format>

>>> pos_vectorized.toarray()   #输出矩阵形式

array([[ 1.,  1.,  1.,  1.,  1.,  1.]]) 

>>> vec.get_feature_names()    #得到特征名字

['pos+1=PP', 'pos-1=NN', 'pos-2=DT', 'word+1=on', 'word-1=cat', 'word-2=the']

你可以想象,如果一个人这样提取在一篇文档语料库中的个体单词的上下语义,最终结果中的矩阵一定是非常大的(许多one-hot-features),大多数时间里,他们的值都为0。为了使结果数据结构更好的适合内存,DictVectorizer类使用一个scipy.sparse矩阵,默认代替numpy.ndarray

4.2.2 特征散列

FeatrueHasher是一个高速,低内存向量化程序,其使用一个名为feature hashing的技术,或者"散列方法"。不是对每个参与的特征都建立散列表,像vectorizers那样,FeatureHasher应用一个散列功能于特征上,直接确定他们的列索引。这样做的结果就是提高了速度,降低了内存使用,以被检查为代价。哈希方法不会记住输入特性的样子,因此也没有使用inverse_transform方法。

因为哈希功能可能会造成(不相关)特征之间的冲突,使用有符号哈希函数,哈希值的符号决定了存储在特性输出矩阵中的值的符号。

这样的话,冲突可能会抵消而不是累积误差,并且任何输出特性的期望平均值都是零。这种机制是默认使用的alternate_sign=True,对于小的哈希表大小特别有用(n_features < 10000)。对于大的哈希表,可能不适用,允许输出传递给类似于estimators的成员类似于sklearn.naive_bayes.MultinomialNBsklearn.feature_sele

ction.chi2期望非负输入的特性选择器。

FeatureHasher接受要么是映射(dict以及他在collection模块中的变体),(feature, value)对,或者是字符串,取决于构建参数的类型input_type。映射被当作是(feature, value)对,单一的字符串,被默认为值为1,比如,['feat1', 'feat2', 'feat3']被解释为[('feat1', 1), ('feat2', 1), ('feat3', 1)]。如果一个单一的字符串在同一个样本中出现了多次,其对应的值将被累加,如('feat', 2) and ('feat', 3.5) become ('feat', 5.5))FeatureHasher的输出经常使用CSR格式的scipy.sparse矩阵。

特征散列可以被应用于文档分类,但是不像text.CountVectorizerFeatureHasher不会切分单词或者其他任何的预处理,处理将unicode编码转换为utf-8编码;参见用hashing trick将大型的预料库向量化,下面是一个混合的tokenizer/hasher

作为一个例子,考虑到单词水平的自然语言处理任务,需要从(token,part_of_speech)中提出特征。可以使用python生成器lai 提取特征。

def token_features(token, part_of_speech):

if token.isdigit(): #isdigit()检验字符串是否只由数字构成

yield "numeric"

else:

yield "token={}".format(token.lower())#对字符串进行格式化

yield "token,pos={},{}".format(token, part_of_speech)

if token[0].isupper(): #检验是否为大写字母

yield "uppercase_initial"

if token.isupper():

yield "all_uppercase"

yield "pos={}".format(part_of_speech) 

之后, raw_X使用FeatureHasher.transform进行创建:

raw_X = (token_features(tok, pos_tagger(tok)) for tok in corpus)

并以hasher表示为:

hasher = FeatureHasher(input_type='string')

X = hasher.transform(raw_X) 

得到scipy.sparse矩阵X

4.2.2.1 实现细节

    FeatureHasher使用32位的变量。结果就是该特征支持的最大数字为2^31-1

    由于使用简单的模将哈希函数转换为列索引,因此建议使用2的幂作为n_features参数;否则,特性将不会均匀地映射到列。

4.2.3文本特征提取

4.2.3.1词袋表示

文本分析是机器学习算法的一个主要应用领域。然而,原始数据、符号序列不能直接输入到算法本身中,因为大多数算法期望的是固定大小的数字特征向量,而不是长度不一的原始文本文档。

为了解决这个问题,scikit-learn为从文本内容中提取数字特性的最常见方法提供了实用工具,即:

  • tokenizing strings and giving an integer id for each possible token, for instance by using white-spaces and punctuation as token separators.
  • counting the occurrences of tokens in each document.
  • normalizing and weighting with diminishing importance tokens that occur in the majority of samples / documents.
  • tokenizing 文档中的字符串单位,通常使用空格和标点分割。
  • counting 统计每个文档中token的个数
  • normalizing 正则化

在该方案中,特征和样本定义如下:

  • 每个标记(token)出现频率(无论其规范化与否)都被视为一个特征。
  • 给定文档的所有token频率的向量被认为是一个多变量样本。

因此,一个文档语料库可以用一个矩阵表示,每个文档有一行,每个标记(例如单词)有一列出现在语料库中。

我们将向量化称为将文本文档集合转换为数字特征向量的一般过程。该具体策略(标记化、计数和规范化)被称为"字袋"或"n-g袋"表示。文档是通过单词出现来描述的,而完全忽略了文档中单词的相对位置信息。

4.2.3.2 稀疏性

由于大多数文档通常使用语料库中使用的单词的一个非常小的子集,因此生成的矩阵将具有许多0的特征值(通常超过99%)。

例如,包含10,000个短文本文档(如电子邮件)的集合将使用一个词汇表,其大小总计为100,000个唯一单词,而每个文档单独使用100到1000个唯一的单词。

为了能够在内存中存储这样一个矩阵,同时也为了加快代数运算矩阵/向量,实现通常使用稀疏表示,例如scipy中可用的实现。稀疏的包。

4.2.3.3 常见的向量化使用

CountVectorizer以一个简单的类实现了标记化和频率计数:

>>> from sklearn.feature_extraction.text import CountVectorizer 

这个模型有很多参数,但是默认值是相当合理的(详情请看参考文档):

>>> vectorizer = CountVectorizer() #创建一个词向量

>>> vectorizer

CountVectorizer(analyzer=...'word', binary=False, decode_error=...'strict',

dtype=<... '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)

使用CountVectorizer()统计语料中的单词:

>>> corpus = [

... 'This is the first document.',

... 'This is the second second document.',

... 'And the third one.',

... 'Is this the first document?',

... ]

>>> X = vectorizer.fit_transform(corpus) #转换成稀疏矩阵的表示形式

>>> X

<4x9 sparse matrix of type '<... 'numpy.int64'>'

with 19 stored elements in Compressed Sparse ... format>

默认配置通过提取至少两个字母的单词来标记字符串。可以显式地请求执行此步骤的特定函数:

>>> analyze = vectorizer.build_analyzer()

>>> analyze("This is a text document to analyze.") == (

... ['this', 'is', 'text', 'document', 'to', 'analyze'])

True #a是一个字母,因此默认忽略

在此过程中,矩阵的每一列都相当于一个索引。列的解释可以按照下面的方式进行检索:

>>> vectorizer.get_feature_names() == (

... ['and', 'document', 'first', 'is', 'one',

... 'second', 'the', 'third', 'this'])

True

#获取特征的名字(token的名字)

 

>>> X.toarray()

array([[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]]...)

#每一列都是一个索引,按照上面的特征名字进行排序

从特性名称到列索引的逆向映射存储在vectorizer的vocabulary ary_属性中:

>>> vectorizer.vocabulary_.get('document')

1

因此,在以后的调用中,将完全忽略在训练语料库中没有看到的单词:

>>> vectorizer.transform(['Something completely new.']).toarray()

...

array([[0, 0, 0, 0, 0, 0, 0, 0, 0]]...)

指的注意的是,在前面的语料库中,第一个和最后一个文档的单词完全相同,因此以相同的向量编码。特别是我们失去了最后一份文件是疑问形式的信息。为了保存一些本地排序信息,除了1-g(单个单词)外,我们还可以提取2-g的单词。

>>> 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

因此,该向量可以提取出更大的词汇量,从而解决本地位置模式编码歧义的问题:

>>> X_2 = bigram_vectorizer.fit_transform(corpus).toarray()

>>> X_2

...

array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],

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

[1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],

[0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1]]...) 

尤其是疑问句"Is this"只出现在最后一份文件中:

>>> feature_index = bigram_vectorizer.vocabulary_.get('is this')

>>> X_2[:, feature_index]

array([0, 0, 0, 1]...)

4.2.3.4 Tf-idf加权

在一个大的文本语料库中,有些单词非常常见(例如英语中的"the"、"a"、"is"),因此,对于文档的实际内容几乎没有什么有意义的信息。如果我们将直接计数数据直接提供给一个分类器,这些高频词将会遮蔽更稀有的、关键的术语的频率。

为了将计数特性重新加权为适合于分类器使用的浮点值,常见的手段是使用tf-idf转换。

Tf表示词频率,Tf - idf表示词频乘以该词的逆文档频率。

使用TfidfTransformer的默认设置。

TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)

词语频率(词语在给定文档中出现的次数)与idf相乘,idf组件计算为:

where is the total number of documents,(文档的总数量) and is the number of documents that contain term (包含词语t的文档的数量). The resulting tf-idf vectors are then normalized by the Euclidean norm:

这最初是一种用于信息检索(作为搜索引擎结果的排序函数)的词语加权方案,在文档分类和聚类中也得到了很好的应用。

以下部分包含进一步的解释和示例,说明tf-idfs是如何精确计算的,以及scikit-learn的TfidfTransformer和TfidfVectorizer中tf-idfs是如何计算的,与定义idf的标准教科书符号略有不同

在TfidfTransformer和TfidfVectorizer的smooth_idf=False中,"1"计数被添加到idf中,而不是idf的分母:

TfidfTransformer类实现了这种标准化:

>>> from sklearn.feature_extraction.text import TfidfTransformer

>>> transformer = TfidfTransformer(smooth_idf=False) #构建一个分类器

>>> transformer

TfidfTransformer(norm=...'l2', smooth_idf=False, sublinear_tf=False,use_idf=True) 

让我们以下面的计数为例。第一项是100%出现的,所以不是很令人关注。另外两个在不到50%的情况下出现的特征才可能更能代表文档的内容:

>>> counts = [[3, 0, 1],

... [2, 0, 0],

... [3, 0, 0],

... [4, 0, 0],

... [3, 2, 0],

... [3, 0, 2]]

...

>>> tfidf = transformer.fit_transform(counts)

>>> tfidf

<6x3 sparse matrix of type '<... 'numpy.float64'>'

with 9 stored elements in Compressed Sparse ... format>

 

>>> tfidf.toarray()

array([[ 0.81940995, 0. , 0.57320793],

[ 1. , 0. , 0. ],

[ 1. , 0. , 0. ],

[ 1. , 0. , 0. ],

[ 0.47330339, 0.88089948, 0. ],

[ 0.58149261, 0. , 0.81355169]])

每一行都归一化得到单位欧几里德范数:

例如,我们可以计算count数组中第一个文档中第一项的tf-idf,如下所示:

#一共有6项,即有6个document

#包含第一项的文档有6个

#计算其逆文档频率

#计算其词频*逆文档频率

现在,如果我们对文档中剩下的两项重复这个计算,我们得到

原始tf-idfs向量:

然后,应用欧几里德(L2)范数,我们得到了文档1的tf-idfs:

此外,默认参数smooth_idf=True在分子和分母上增加了"1",分母就像每个文档都包含额外的词一样,这是为了防止0除的出现:

使用此修改,文档1第三项tf-idf更改为1.8473:

l2归一化tf-idf变为

>>> transformer = TfidfTransformer()

>>> transformer.fit_transform(counts).toarray()

array([[ 0.85151335, 0. , 0.52433293],

[ 1. , 0. , 0. ],

[ 1. , 0. , 0. ],

[ 1. , 0. , 0. ],

[ 0.55422893, 0.83236428, 0. ],

[ 0.63035731, 0. , 0.77630514]])

通过fit方法调用计算的每个特征的权重都存储在模型属性中:

>>> transformer.idf_

array([ 1. ..., 2.25..., 1.84...])#这里指的就是单词的逆文档频率

虽然tf-idf经常用于文本特性,还有另一个类TfidfVectorizer,它将CountVectorizer和TfidfTransformer的所有选项组合在一个模型中:

>>> from sklearn.feature_extraction.text import TfidfVectorizer

>>> vectorizer = TfidfVectorizer()

>>> vectorizer.fit_transform(corpus)

...

<4x9 sparse matrix of type '<... 'numpy.float64'>'

with 19 stored elements in Compressed Sparse ... format>

虽然tf-idf规范化通常非常有用,但在某些情况下,二进制标记可能提供更好的特性。这可以通过使用CountVectorizer的二进制参数来实现。特别地,一些估计,如伯努利朴素贝叶斯显式模型离散布尔随机变量。此外,非常短的文本很可能有噪声tf-idf值,而二进制信息更稳定。

通常,调整特征提取参数的最佳方法是使用交叉验证的网格搜索,例如使用分类器流水线化特征提取器:

Sample pipeline for text feature extraction and evaluation

4.2.3.5. 文本文件解码

文本由字符组成,而文件由字节组成。这些字节根据某种编码表示字符。要使用Python中的文本文件,它们的字节必须被解码为一个称为Unicode的字符集。常见的编码有ASCII、Latin-1(西欧)、KOI8-R(俄语)和通用编码UTF-8和UTF-16。

注意:编码也可以称为"字符集",但这个术语不太准确:单个字符集可以存在多个编码。

scikit中的文本特征提取器知道如何解码文本文件,但前提是你告诉他们文件的编码是什么。CountVectorizer为此接受一个编码参数。对于许多文本文件,正确的编码可能是UTF-8,因此它是默认的(编码=" UTF-8 ")。

但是,如果正在加载的文本实际上不是用UTF-8编码的,那么将得到一个UnicodeDecodeError。通过将decode_error参数设置为"ignore"或"replace",可以让向量化工具对解码错误不会报错。有关Python函数bytes.decode的详细信息请参阅文档(在Python提示符处输入help(bytes.decode))。

如果你在解码文本时有困难,可以做如下尝试:

  • 找出文本的实际编码是什么。文件可能带有一个头或README,告诉您编码,或者可能有一些标准编码,您可以根据文本的来源假设。
  • 可以尝试UTF-8并忽略错误。使用bytes.decode(errors='replace')对字节字符串进行解码,替换所有解码错误,或者在vectorizer中设置decode_error='replace'。这可能会破坏你的特征的有用性。
  • 真正的文本可能来自各种不同的来源,这些来源可能使用不同的编码,甚至可能被草率地解码为不同的编码,而不是它所使用的编码。这在从Web检索的文本中很常见。Python包ftfy可以自动整理某些类型的解码错误,因此您可以尝试将未知文本解码为latin-1,然后使用ftfy来修复错误。
  • 如果文本是混合编码的,很难进行分类(这是20个新闻组数据集的情况),那么可以使用简单的单字节编码,比如latin-1。某些文本可能显示不正确,但至少相同的字节序列始终表示相同的特性。

 

4.2.3.6. Applications and examples

词袋表示非常简单,但在实践中非常有用。

特别是在有监督的设置中,它可以成功地与快速和可伸缩的线性模型相结合,以训练文档分类器;

在无监督的情况下,它可以通过应用K-means等聚类算法将类似的文档分组在一起;

最后,可以通过放松聚类的硬性分配约束来发现语料库的主要主题,例如使用非负矩阵分解(NMF或NNMF)。

4.2.3.7. Limitations of the Bag of Words representation(词袋模型的限制)

一组unigram(就是一袋单词)不能捕捉短语多词表达,忽略了词序依赖。此外,单词袋模型不考虑潜在的拼写错误或单词派生

N-gram可以克服这点!与其构建一个简单的unigrams集合(n=1),不如选择一个bigrams集合(n=2),在这里统计连续单词成对出现的次数。

可以考虑使用字符n-g的集合,这是一种抵抗拼写错误和派生的表示。

例如,假设我们正在处理一个由两个文档组成的语料库:['words', 'wprds']。第二个文档包含单词"words"的拼写错误。一个简单的词汇表示可以将这两个文档看作是非常不同的文档,它们在两个可能的特性上都有所不同。然而,一个2-gram

>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(2, 2))

>>> counts = ngram_vectorizer.fit_transform(['words', 'wprds'])

>>> ngram_vectorizer.get_feature_names() == (

... [' w', 'ds', 'or', 'pr', 'rd', 's ', 'wo', 'wp'])

True

>>> counts.toarray().astype(int)

array([[1, 1, 1, 0, 1, 1, 1, 0],

[1, 1, 0, 1, 1, 1, 0, 1]])

表示法可以在8个特征中找到4个匹配的文档,这可以帮助首选分类器更好地决定:

具有边界感知功能的变体char_wb对于使用空白分隔词的语言来说特别有趣,因为在这种情况下,它生成的嘈杂特性要比原始char变体少得多。对于这类语言,它可以提高使用这类特性训练的分类器的预测精度和收敛速度,同时在拼写和词源方面保持鲁棒性。

而一些局部定位信息可以通过提取n-g而不是单个单词来保存,一袋一袋的单词和一袋一袋的n-g破坏了文档的大部分内部结构,从而破坏了内部结构所承载的大部分意义。

因此,为了解决更广泛的自然语言理解任务,应该考虑句子和段落的局部结构。因此,许多这样的模型将被定义为"结构化输出"问题,而这些问题目前不在scikitlearn的范围之内。

4.2.3.8. Vectorizing a large text corpus with the hashing trick

(使用哈希技巧向量化大型文本语料库)

上面的矢量化方案很简单,但是它在内存中保存了从字符串token到整数特征索引(词汇表_属性)的映射,这在处理大型数据集时造成了一些问题:

  • 语料库越大,词汇量就越大,因此使用的内存也就越多;
  • 拟合需要分配与原始数据集大小成比例的中间数据结构。
  • 构建单词映射需要对数据集进行完整的传递,因此不可能以严格的在线方式对文本分类器进行匹配。
  • 具有较大词汇表的pickling和未pickling矢量化程序可能非常慢(通常比pickling /未pickling平面数据结构(如大小相同的NumPy数组)慢得多)
  • 不能简单的将向量化工作分割成子任务

通过结合sklearn.feature_extraction实现的"哈希技巧"(特性哈希)、文本预处理及CountVectorizer的tokenization特征可以克服这些限制。

这个组合是在HashingVectorizer中实现的,一个经常结合CountVectorizer的转换类。HashingVectorizer是无状态的,这意味着不必调用fit:

>>> from sklearn.feature_extraction.text import HashingVectorizer

>>> hv = HashingVectorizer(n_features=10)

>>> hv.transform(corpus)

...

<4x10 sparse matrix of type '<... 'numpy.float64'>'

with 16 stored elements in Compressed Sparse ... format>

    可以看到向量输出16个非零的tokens,比CountVectorizer的19个非零的tokens要少这种差异来自于哈希函数冲突,因为n_features参数的值很低。

在实际设置中,n_features参数可以保留为其默认值2 ** 20(大约100万个可能的特性)。如果内存或下游模型大小是一个问题,那么选择一个较低的值(比如2 ** 18)可能会有帮助,而不会在典型的文本分类任务中引入太多额外的冲突。

让我们用默认设置再试一次:

>>> hv = HashingVectorizer()

>>> hv.transform(corpus)

...

<4x1048576 sparse matrix of type '<... 'numpy.float64'>'

with 19 stored elements in Compressed Sparse ... format>

我们不再得到冲突,但这是以输出空间更大维度为代价的。当然,这里使用的其他术语可能仍然会相互冲突。

HashingVectorizer也有以下限制:

  • 由于执行映射的散列函数是单向的,因此不可能反转模型(没有inverse_transform方法),也不可能访问特性的原始字符串表示形式。
  • 它不提供IDF权重,因为这会在模型中引入状态性。如果需要,可以将TfidfTransformer追加到管道中。

4.2.3.9. Performing out-of-core scaling with HashingVectorizer

使用HashingVectorizer的一个有趣的开发是执行内核外扩展的能力。这意味着我们可以从不适合计算机主存的数据中学习。

实现核外扩展的一个策略是将数据以小批量的形式流到评估器。使用HashingVectorizer对每个迷你批处理进行矢量化,以保证估计器的输入空间始终具有相同的维数。因此,在任何时候使用的内存数量都受一个迷你批处理大小的限制。虽然使用这种方法可以摄取的数据量没有限制,但从实际的角度来看,学习时间通常受希望在任务上花费的CPU时间的限制。

有关文本分类任务中的核心外扩展的完整示例,请参阅文本文档的核心外分类。

4.2.3.10. Customizing the vectorizer classes(定制向量类)

可以通过向vectorizer构造函数传递一个可调用的函数来定制行为:

>>> def my_tokenizer(s):

... return s.split()

...

>>> vectorizer = CountVectorizer(tokenizer=my_tokenizer)

>>> vectorizer.build_analyzer()(u"Some... punctuation!") == (

... ['some...', 'punctuation!'])

True

特别地,我们做如下命名:

  • 预处理器:可调用的,它将整个文档作为输入(作为单个字符串),并返回可能经过转换的文档版本,仍然是一个完整的字符串。这可以用来删除HTML标签,小写整个文档,等等。
  • 记号赋予器:一个可调用的,它从预处理器获取输出并将其分割成标记,然后返回这些标记的列表。
  • 分析器:一个可调用的,替换预处理器和标记器。默认的分析器都调用预处理器和标记器,但是自定义分析器将跳过这一步。N-gram提取和停止字过滤发生在分析器级别,因此自定义分析器可能必须重现这些步骤。

 

要使预处理器、标记器和分析器知道模型参数,可以从类派生并覆盖build_预处理器build_tokenizer'和build_analyzer方法,而不是传递定制函数。

 

一些提示与技巧:

  • 如果文档是由外部包预先标记的,那么将它们存储在文件(或字符串)中,标记用空格隔开, analyzer=str.split
  • 一些的标记级别的分析,如词干提取、词根提取、复合分裂、基于词性的过滤等,并不包括在scikit-learn代码基中,但是可以通过定制标记器或分析器来添加。这时使用一个带有NLTK分词器和词还原器的CountVectorizer实现:
  • >>> from nltk import word_tokenize
  • >>> from nltk.stem import WordNetLemmatizer
  • >>> class LemmaTokenizer(object):
  • ... def __init__(self):
  • ... self.wnl = WordNetLemmatizer()
  • ... def __call__(self, doc):
  • ... return [self.wnl.lemmatize(t) for t in word_tokenize(doc)]
  • ...
  • >>> vect = CountVectorizer(tokenizer=LemmaTokenizer())
  • (注意,这里没有滤掉标点符号。)

例如,下面的示例将把一些英式拼写转换为美式拼写:

>>> import re

>>> def to_british(tokens):

... for t in tokens:

... t = re.sub(r"(...)our$", r"\1or", t)

... t = re.sub(r"([bt])re$", r"\1er", t)

... t = re.sub(r"([iy])s(e$|ing|ation)", r"\1z\2", t)

... t = re.sub(r"ogue$", "og", t)

... yield t

...

>>> class CustomVectorizer(CountVectorizer):

... def build_tokenizer(self):

... tokenize = super(CustomVectorizer, self).build_tokenizer()

... return lambda doc: list(to_british(tokenize(doc)))

...

>>> print(CustomVectorizer().build_analyzer()(u"color colour"))

[...'color', ...'color']

在处理不使用显式分隔符(如空格)的亚洲语言时,定制向量器也很有用。

4.2.4. Image feature extraction(图像特征提取)

4.2.4.1. Patch extraction(补丁提取)

extract_patches_2d函数从存储为二维数组的图像中提取补丁,或者沿着第三轴提取具有颜色信息的三维图像。要从所有补丁中重新构建图像,请使用reconstruct_from_patches_2d。样例生成带有3个颜色通道(例如RGB格式)的4x4像素图像。

>>> import numpy as np

>>> from sklearn.feature_extraction import image

 

>>> one_image = np.arange(4 * 4 * 3).reshape((4, 4, 3))

>>> one_image[:, :, 0] # R channel of a fake RGB picture

array([[ 0, 3, 6, 9],

[12, 15, 18, 21],

[24, 27, 30, 33],

[36, 39, 42, 45]])

 

>>> patches = image.extract_patches_2d(one_image, (2, 2), max_patches=2,

... random_state=0)

>>> patches.shape

(2, 2, 2, 3)

>>> patches[:, :, :, 0]

array([[[ 0, 3],

[12, 15]],

 

[[15, 18],

[27, 30]]])

>>> patches = image.extract_patches_2d(one_image, (2, 2))

>>> patches.shape

(9, 2, 2, 3)

>>> patches[4, :, :, 0]

array([[15, 18],

[27, 30]])

现在让我们通过对重叠区域的平均来尝试从这些斑块中重建原始图像:

>>> reconstructed = image.reconstruct_from_patches_2d(patches, (4, 4, 3))

>>> np.testing.assert_array_equal(one_image, reconstructed)

PatchExtractor类的工作方式与extract_patches_2d相同,只是它支持多个图像作为输入。它作为一个估计器实现,因此可以在管道中使用。

>>> five_images = np.arange(5 * 4 * 4 * 3).reshape(5, 4, 4, 3)

>>> patches = image.PatchExtractor((2, 2)).transform(five_images)

>>> patches.shape

(45, 2, 2, 3)

4.2.4.2. Connectivity graph of an image

scikit-learn中的几个评估器可以使用特性或示例之间的连接信息。例如,Ward聚类(分层聚类)只能将图像的相邻像素聚在一起,从而形成相邻的斑块:

为此,估计器使用一个"连通"矩阵,给出的样例就是连通的。

函数img_to_graph从2D或3D图像返回这样一个矩阵。类似地,grid_to_graph为给定这些图像形状的图像构建一个连接矩阵。

这些矩阵可用于在使用连通性信息(如Ward聚类(分层聚类))的估计器中强制连接,也可用于构建预先计算的内核或相似矩阵。

推荐阅读