本文通过特征提取、特征转换、特征选择三个过程介绍数据预处理方法,特征提取将原始数据转换为适合建模的特征,特征转换将数据进行变换以提高算法的准确性,特征选择用来删除无用的特征。
知识点
- 特征提取
- 特征转换
- 特征选择
本次实验的一些示例将使用 Renthop 公司的数据集。首先载入数据集。
# 下载数据并解压
!wget -nc "https://labfile.oss.aliyuncs.com/courses/1283/renthop_train.json.gz"
!gunzip "renthop_train.json.gz"
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
df = pd.read_json('renthop_train.json')
df.head()
特征提取
在实践中,很少有数据是以矩阵形式保存、可以直接使用的,这就是为什么需要对数据进行特征提取。首先看看在一些常见的数据类型中特征提取是如何进行的。
文本数据
文本的处理方法非常多,本次实验介绍最流行的一种处理过程:
- 在处理文本之前,必须对文本进行切分词(tokenzie)操作,也就是将文本切分为单元(token)。在最简单的情形下,一个 token 就是一个单词。但直接按单词切分可能会损失一些信息,比如「Santa Barbara」应该是一个整体,却被切分为 2 个 token。现成的分词器(tokenizer)会考虑语言的特性,但也会出错,特别是当你处理特定来源的文本时(报纸、俚语、误拼、笔误)。
- 接下来需要正则化数据。对文本而言,这涉及词干提取(stemming)和词形还原(lemmatization)方法。这两个方法是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别。两者的区别可以参考《Introduction to Information Retrieval》一书的 Stemming and lemmatization 一节。
- 当文档被转换为单词序列之后,就可以用向量表示它。最简单的方法是词袋(Bag of Words)模型:创建一个长度等于字典的向量,计算每个单词出现在文本中的次数,然后将次数放入向量对应的位置中。
下面用代码来表述词袋模型。
import numpy as np
import pandas as pd
texts = ['i have a cat',
'you have a dog',
'you and i have a cat and a dog']
vocabulary = list(enumerate(set([word for sentence
in texts for word in sentence.split()])))
print('Vocabulary:', vocabulary)
def vectorize(text):
vector = np.zeros(len(vocabulary))
for i, word in vocabulary:
num = 0
for w in text:
if w == word:
num += 1
if num:
vector[i] = num
return vector
print('Vectors:')
for sentence in texts:
print(vectorize(sentence.split()))
下图是一个简化的词袋模型实现过程,在实际应用中,还需要考虑停止词,字典的最大长度等问题。
当使用词袋模型时,文本中的单词顺序信息会丢失,这意味着向量化之后,“i have no cows”(我没有牛)和“no, i have cows”(没,我有牛)会变得一样,尽管事实上它们的意思截然相反。
为了避免这个问题,可以转而使用 N-Gram 模型。下面载入相关库,建立 N-Gram 模型。
from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(ngram_range=(1, 1))
vect.fit_transform(['no i have cows', 'i have no cows']).toarray()
PS:这个toarray()把稀疏矩阵转换为密集矩阵了
-
稀疏矩阵
- 仅记录非零元素的位置(行、列)和数值;绝大部分零不需显式保存。
- 例如 CSR(Compressed Sparse Row)格式只保留若干数组,分别存储非零元素及其在行列上的索引。
-
密集矩阵
- 用常规二维数组方式存储:从第一行第一个元素到最后一行最后一个元素,每个位置都在内存中占一个位置,无论是 0 还是非 0。
- 如果矩阵非常大且零很多,这会浪费大量内存。
比如说这个CSR(Compressed Sparse Row):
- 对矩阵的每一行,记录非零元素的“列索引”和“数值”,并用一个额外的数组来标记各行在存储数组中的起始位置。
- 核心思路:依次扫描每行,遇到非零元素就把“列索引”、“数值”存下来,行与行之间的边界用一个专门数组记下。
- 结果:可以想象把整个矩阵“行顺序”排成一条线;对每行非零部分做顺序存储。
打印结果
vect.vocabulary_
改变参数,再次建立 N-Gram 模型。
vect = CountVectorizer(ngram_range=(1, 2))
vect.fit_transform(['no i have cows', 'i have no cows']).toarray()
打印结果。
vect.vocabulary_
除了基于单词生成 N-Gram 模型,在某些情形下,可以基于字符生成 N-Gram 模型,以考虑相关单词的相似性或笔误,下面基于字符生成 N-Gram 模型,并查看它们之间的欧氏距离。
from scipy.spatial.distance import euclidean
from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(ngram_range=(3, 3), analyzer='char_wb')
n1, n2, n3, n4 = vect.fit_transform(
['andersen', 'petersen', 'petrov', 'smith']).toarray()
euclidean(n1, n2), euclidean(n2, n3), euclidean(n3, n4)
有时候,在语料库(数据集的全部文档)中罕见但在当前文本中出现的专业词汇可能会非常重要。因此,通过增加专业词汇的权重把它们和常用词区分开,是很合理的,这一方法称为 TF-IDF(词频 - 逆向文档频率),其默认选项为:
&