Python机器学习库SKLearn:数据集转换之特征提取
特征提取:http://scikit-learn.org/stable/modules/feature_extraction.html#feature-extraction
sklearn.feature_extraction模块可以用于从由诸如文本和图像的格式组成的数据集中提取机器学习算法支持的格式的特征。
注意:特征提取与特征选择非常不同:前者包括将任意数据(如文本或图像)转换为可用于机器学习的数值特征。 后者是应用于这些特征的机器学习技术。
4.2.1 从词典中加载特征
类DictVectorizer可用于将表示为标准Python dict对象列表的要素数组转换为scikit-learn估计量使用的NumPy / SciPy表示。虽然处理不是特别快,但是Python的dict具有使用方便,除了值的优势外还有稀疏(没有功能不需要存储)和存储特征名称。
DictVectorizer为分类(也称为标称,离散)特征实现所谓的one-of-K或“one-hot(独热)”编码。 分类特征是“属性值”对,其中值被限制到无序的离散可能性的列表(例如主题标识符,对象的类型,标签,名称...)。
在下面,“city”是分类属性,而“temperature”是传统的数字特征:
measurements = [ {"city": "Dubai", "temperature": 33.}, {"city": "London", "temperature": 12.}, {"city": "San Fransisco", "temperature": 18.}, ] from sklearn.feature_extraction import DictVectorizer vec = DictVectorizer() vec.fit_transform(measurements).toarray() """ 输出: array([[ 1., 0., 0., 33.], [ 0., 1., 0., 12.], [ 0., 0., 1., 18.]]) """ vec.get_feature_names() """ 输出: ["city=Dubai", "city=London", "city=San Fransisco", "temperature"] """
DictVectorizer也是自然语言处理模型中的训练序列分类器的有用的表示变换,其通常通过提取围绕感兴趣的特定词的特征窗口来工作。例如,假设我们具有提取我们想要用作训练序列分类器(例如块)的互补标签的部分语音(PoS)标签的第一算法。 以下dict可以是在"猫坐在席子上"的句子“sat”周围提取的这样一个窗口。
pos_window = [ { "word-2": "the", "pos-2": "DT", "word-1": "cat", "pos-1": "NN", "word+1": "on", "pos+1": "PP", }, # 在实际应用中,将提取许多这样的字典 ]
该描述可以被矢量化为适于馈送到分类器中的稀疏二维矩阵(可能在被管道化到文本之后。用于归一化的TfidfTransformer):
vec = DictVectorizer() pos_vectorized = vec.fit_transform(pos_window) pos_vectorized """ 输出: <1x6 sparse matrix of type "<class "numpy.float64">" with 6 stored elements in Compressed Sparse Row 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"] """你可以想象,如果一个文档的每个单词提取这样的上下文,所得到的矩阵将非常宽(许多个独热特征),大多数的零需要花费很多的内存和时间。 为了使得到的数据结构能够适应内存,DictVectorizer类默认使用scipy.sparse矩阵,而不是numpy.ndarray。
4.2.2 特征散列
FeatureHasher类是一个高速,低内存的向量化器,使用一种称为特征散列或“散列法”的技术。代替在训练中构建训练中遇到的特征的哈希表,如向量化所做的那样,FeatureHasher的实例将哈希函数应用于特征以直接确定其在样本矩阵中的列索引。结果是以牺牲可检查性为代价,提高速度和减少存储器使用;哈希不记得输入要素的样子,没有inverse_transform方法。
由于散列函数可能导致(不相关的)特征之间的冲突,使用带符号的散列函数,并且散列值的符号确定存储在特征的输出矩阵中的值的符号。这样,冲突可能抵消而不是累积误差,并且任何输出特征的值的预期平均值为零。如果non_negative = True传递给构造函数,则采用绝对值。这撤消了一些冲突处理,但允许输出传递到估计器,如sklearn.naive_bayes.MultinomialNB或sklearn.feature_selection.chi2特征选择器,期望非负输入。
FeatureHasher接受映射(如Python的dict及其在collections模块中的变体),(feature,value)对或字符串,具体取决于构造函数参数input_type。 映射被视为(feature,value)对的列表,而单个字符串的隐式值为1,因此["feat1","feat2","feat3"]被解释为[("feat1",1), "feat2",1),("feat3",1)]。 如果单个特征在样本中出现多次,则相关值将被求和(因此("feat",2)和("feat",3.5)变为("feat",5.5))。
FeatureHasher的输出总是CSR格式中的scipy.sparse矩阵。
在文档分类中可以使用特征散列,但是与text.CountVectorizer不同,FeatureHasher不执行字分割或除Unicode到UTF-8编码之外的任何其他预处理; 对于组合的tokenizer / hasher,请参阅下面的散列技巧的大文本语料库的矢量化。
作为示例,考虑需要从(token,part_of_speech)对提取的特征的词级自然语言处理任务。 可以使用Python生成器函数来提取特征:
def token_features(token, part_of_speech): if token.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) #然后,要馈送到FeatureHasher.transform的raw_X可以使用以下来构造: raw_X = (token_features(tok, pos_tagger(tok)) for tok in corpus) #并且馈送到具有以下的散列: hasher = FeatureHasher(input_type="string") X = hasher.transform(raw_X)
得到一个scipy.sparse矩阵X.
注意使用生成器理解,其将惰性引入到特征提取中:令牌仅在来自哈希的请求时处理。
4.2.2.1 实现细节
FeatureHasher使用MurmurHash3的带符号32位变体。 结果(并且由于scipy.sparse的限制),支持的特征的最大数目当前是2 ^ {31} - 1。Weinberger等人的散列技巧的原始公式 使用两个单独的散列函数h和 xi来分别确定特征的列索引和符号。 本实现在假定MurmurHash3的符号位与其它位无关的假设下工作。由于使用简单模数将散列函数转换为列索引,因此建议使用2的幂作为n_features参数;
否则这些要素将不会均匀地映射到列。
4.2.3文本特征提取
4.2.3.1 词袋表示(词袋模型)
文本分析是机器学习算法的主要应用领域。然而,原始数据,符号序列不能直接馈送到算法本身,因为大多数人期望具有固定大小的数字特征向量而不是具有可变长度的原始文本文档。为了解决这个问题,scikit-learn提供了用于从文本内容中提取数字特征的最常用方法的实用程序,即:
令牌(tokenizing)化字符串并为每个可能的令牌给出整数id,例如通过使用空格和标点符号作为令牌分隔符。//单词分割
计数(counting)每个文档中标记的出现次数。//单词计数
使用大多数样本/文档中出现的减少的重要性标记进行归一化和加权(normalizing and
weighting)。//归一化/标准化
在该方案中,特征和样本定义如下:
每个单独的标记出现频率(标准化或不标准)被视为特征。
给定文档的所有令牌频率的向量被认为是多元样本。
因此,文档语料库可以由每个文档具有一行和在语料库中出现每个令牌(例如,词)的列的矩阵表示。
我们将向量化称为将文本文档的集合转换为数字特征向量的一般过程。这种具体的策略(标记化,计数和归一化)被称为词袋或“n-gram袋”表示。文档由单词出现来描述,同时完全忽略文档中的单词的相对位置信息。
4.2.3.2 稀疏性
由于大多数文档通常使用语料库中非常小的子集的单词,所以得到的矩阵将具有许多为零(通常大于99%)的特征值。例如,10,000个短文本文档(例如电子邮件)的集合将使用具有总共100,000个唯一字词的大小的词汇表,而每个文档将单独使用100到1000个唯一字词。为了能够在存储器中存储这样的矩阵,但是为了加速代数运算矩阵/向量,实现方式通常使用稀疏表示,例如在scipy.sparse包中可用的实现。
4.2.3.3。常用Vectorizer的使用
CountVectorizer在单个类中实现标记化和计数:
from sklearn.feature_extraction.text import CountVectorizer此模型有很多参数,但是默认值是相当合理的(有关详细信息,请参阅参考文档):
vectorizer = CountVectorizer(min_df=1) 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)让我们使用它来标记和计数文本文档的简约语料库的单词出现:
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>默认配置通过提取至少2个字母的单词对字符串进行标记化。 可以明确请求执行此步骤的特定函数:
analyze = vectorizer.build_analyzer() analyze("This is a text document to analyze.") == ( ... ["this", "is", "text", "document", "to", "analyze"]) 输出:True在拟合期间由分析器发现的每个项被分配与所得矩阵中的列对应的唯一整数索引。 这些列的解释可以如下检索:
vectorizer.get_feature_names() == ( ["and", "document", "first", "is", "one", "second", "the", "third", "this"]) 输出:True 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]]...)从特征名到列索引的逆转换映射存储在向量化器的vocabulary_属性中:
vectorizer.vocabulary_.get("document") 输出:1因此,在未来调用变换方法时,在训练语料库中看不到的词将被完全忽略:
vectorizer.transform(["Something completely new."]).toarray() 输出:array([[0, 0, 0, 0, 0, 0, 0, 0, 0]]...)注意,在先前的语料库中,第一个和最后一个文档具有完全相同的字,因此被编码在相等的向量中。 特别是我们失去的信息,最后一个文件是一个疑问形式。 为了保留一些本地排序信息,我们可以提取除了1克(单个词)之外的2克词:
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),token_pattern=r"w+", 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]]...)特别是,“这是”的疑问形式只出现在最后一份文件中:
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分量, 其计算为
,
其中n_d是文档的总数,并且 text {df}(d,t)是包含项t的文档的数目。 然后通过欧几里得范数标准化所得的tf-idf载体:
.
这最初是为信息检索开发的术语加权方案(作为搜索引擎结果的排名函数),其也已经在文档分类和聚类中被良好使用。
以下部分包含进一步的说明和示例,说明如何精确计算tf-idfs,以及如何在scikit-learn的TfidfTransformer和TfidfVectorizer中计算的tf-idfs与标准教科书符号略有不同,定义为:
在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]])
每行被归一化为具有单位欧几里德范数:
例如,我们可以在计数数组中计算第一个文档中的第一项的tf-idf,如下所示:
现在,如果我们对文档中的剩余2项重复这个计算,我们得到:
raw tf-idfs的向量:
然后,应用欧几里德(L2)范数,我们获得以下文档1的tf-idfs:
此外,默认参数smooth_idf = True为分子和分母添加“1”,就好像看到一个额外的文档包含集合中的每个术语一次,这样可以防止零分割:
使用该修改,文档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]])由拟合方法调用计算的每个要素的权重存储在模型属性中:
>>> transformer.idf_ array([ 1. ..., 2.25..., 1.84...])
由于tf-idf通常用于文本特性,因此还有另一个类TfidfVectorizer将单个模型中的CountVectorizer和TfidfTransformer的所有选项组合在一起:
>>> from sklearn.feature_extraction.text import TfidfVectorizer >>> vectorizer = TfidfVectorizer(min_df=1) >>> vectorizer.fit_transform(corpus) ... <4x9 sparse matrix of type "<... "numpy.float64">" with 19 stored elements in Compressed Sparse ... format>
虽然tf-idf标准化通常非常有用,但是可能存在二进制出现标记可能提供更好特征的情况。 这可以通过使用CountVectorizer的二进制参数来实现。 特别地,一些估计器例如伯努利朴素贝叶斯显式地建模离散布尔随机变量。 此外,非常短的文本可能具有噪声tf-idf值,而二进制出现信息更稳定。
像往常一样,调整特征提取参数的最佳方式是使用交叉验证的网格搜索。
4.2.3.5 解码文本文件
文本由字符组成,但文件由字节组成。 这些字节表示根据某种编码的字符。 要在Python中使用文本文件,它们的字节必须解码为一个称为Unicode的字符集。 常见的编码是ASCII,Latin-1(西欧),KOI8-R(俄罗斯)和通用编码UTF-8和UTF-16。 许多其他存在。
如果你加载的文本实际上没有使用UTF-8编码,那么你会得到一个UnicodeDecodeError。通过将decode_error参数设置为“忽略”或“替换”,可以告诉矢量化器无关于解码错误。有关更多详细信息,请参阅Python函数bytes.decode的文档(在Python提示符下键入help(bytes.decode))。
如果您在解码文本时遇到问题,请尝试以下操作:
找出文本的实际编码是什么。该文件可能带有一个标题或README,告诉您编码,或者可能有一些标准编码,您可以根据文本来自哪里。
您可以使用UNIX命令文件找出一般使用何种编码。 Python chardet模块有一个名为chardetect.py的脚本,它将猜测特定的编码,虽然你不能依赖它的猜测是正确的。
你可以尝试UTF-8并忽略错误。您可以使用bytes.decode(errors ="replace")解码字节字符串,用无意义的字符替换所有解码错误,或在矢量化程序中设置decode_error ="replace"。这可能会损坏您的功能的有用性。
真实文本可以来自各种源,这些源可能已经使用了不同的编码,或者甚至以与其编码的编码不同的编码进行粗略解码。这在从Web检索的文本中很常见。 Python包ftfy可以自动分类出一些类的解码错误,因此您可以尝试将未知文本解码为latin-1,然后使用ftfy修复错误。
如果文本是编码的杂乱,这很难排序(这是20新闻组数据集的情况),你可以回到一个简单的单字节编码,如拉丁-1。某些文本可能显示不正确,但至少相同的字节序列将始终表示相同的功能。
例如,以下片段使用chardet(不随scikit-learn一起提供,必须单独安装)来计算三个文本的编码。 然后将文本向量化并打印学习的词汇。 此处不显示输出。
>>> import chardet >>> text1 = b"Sei mir gegrxc3xbcxc3x9ft mein Sauerkraut" >>> text2 = b"holdselig sind deine Gerxfcche" >>> text3 = b"xffxfeAx00ux00fx00 x00Fx00lx00xfcx00gx00ex00lx00nx00 x00dx00ex00sx00 x00Gx00ex00sx00ax00nx00gx00ex00sx00,x00 x00Hx00ex00rx00zx00lx00ix00ex00bx00cx00hx00ex00nx00,x00 x00tx00rx00ax00gx00 x00ix00cx00hx00 x00dx00ix00cx00hx00 x00fx00ox00rx00tx00" >>> decoded = [x.decode(chardet.detect(x)["encoding"]) ... for x in (text1, text2, text3)] >>> v = CountVectorizer().fit(decoded).vocabulary_ >>> for term in v: print(v)
4.2.3.6 应用和示例
词袋表示是相当简单,但在实践中令人惊讶的有用。
特别是在监督设置中,它可以成功地与快速和可扩展的线性模型组合以训练文档分类器,例如:
使用稀疏特征的文本文档的分类
在无监督设置中,它可以用于通过应用诸如K均值的聚类算法将类似文档分组在一起:
使用k-means聚类文本文档
最后,通过放松聚类的硬分配约束,例如通过使用非负矩阵分解(NMF或NNMF),可以发现语料库的主要主题:
使用非负矩阵因子分解和潜在狄利克雷分配的主题提取
4.2.3.7 词袋表示的限制
4.2.4 图像特征提取
4.2.4.1 补丁提取
extract_patches_2d函数从存储为二维阵列的图像或沿着第三轴的具有颜色信息的三维提取片段。 要从所有其修补程序重建图像,请使用reconstruct_from_patches_2d。 例如,让使用生成具有3个颜色通道的4×4像素图片(例如,以RGB格式):
>>> 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)补丁提取器类的工作方式与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 图像的连接图
scikit-learn中的几个估计器可以使用特征或样本之间的连接信息。 例如,Ward聚类(分层聚类)可以仅聚集图像的相邻像素,从而形成连续的区块:
为此,估计器使用“连通性”矩阵,给出哪些样本被连接。
函数img_to_graph从2D或3D图像返回这样的矩阵。 类似地,grid_to_graph为给定这些图像的形状的图像构建连接矩阵。
这些矩阵可用于在使用连通性信息(例如Ward聚类(Hierarchical clustering))的估计器中强加连接性,但也建立预先计算的内核或相似性矩阵。
- 上一篇: Java中数字签名RSASignature 算法的使用
- 下一篇:没有了