scikit-learn 笔记 - 数据集转换

特征缩放 (Feature Scaling)

在使用机器学习算法前,经常需要进行特征缩放(feature scaling),将所有特征都缩放到相同的尺度 ([0,1]的范围)。

下面是进行特征缩放的一种方式:

1
2
3
4
def featureScaling(arr):
minval = min(arr)
maxval = max(arr)
return [1.0 * (val - minval) / (maxval - minval) for val in arr]

使用sklearn:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy
from sklearn.preprocessing import MinMaxScaler

train_data = numpy.array([[-1, 2], [-0.5, 6], [0, 10], [1, 18]])
test_data = numpy.array([[2, 2]])

# create the scaler
scaler = MinMaxScaler()

# compute the minimum and maximum to be used for later scaling.
scaler.fit(train_data)

# scaling features
print(scaler.transform(train_data))
print(scaler.transform(test_data))

对许多机器学习算法,特征缩放将影响最终的效果,如SVM和K-Means等(影响计算距离时各特征的比重)。

此外,特征缩放通常可以加速(大部分)机器学习算法的训练过程。

文本特征

文本分析是机器学习算法的一个重要应用领域,然而原始的文本数据很难直接作为大部分算法的输入,为此 scikit-learn 提供了许多方法从文本中提取特征向量。

词袋 (Bag of Words)

词袋(Bag-of-Words, BOW)模型是文本向量化的一种方式,主要关注文档中单词的出现频率,舍弃了单词中的所有顺序信息。

任何文档都可以编码成一个固定长度的向量,该向量表示词汇表中每个单词在文档中的计数或频率,长度为从所有文档中抽取的单词构成的词汇表的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from sklearn.feature_extraction.text import CountVectorizer

# CountVectorizer implements both tokenization and occurrence counting in a single class
vectorizer = CountVectorizer()

# tokenizes a string
analyze = vectorizer.build_analyzer()
analyze("This is a text document to analyze.") == (
['this', 'is', 'text', 'document', 'to', 'analyze'])

# tokenize and count the word occurrences of a minimalistic corpus of text documents
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)
print vectorizer.get_feature_names() == (
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this'])
print X.toarray()

# Transform a new document to document-term matrix.
print vectorizer.transform(['Something completely new.']).toarray()

# To preserve some of the local ordering information,
# we can extract 2-grams of words in addition to the 1-grams (individual words).
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
token_pattern=r'\b\w+\b', min_df=1)
analyzer = bigram_vectorizer.build_analyzer()
print analyze('Bi-grams are cool!') == (
['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool'])
print bigram_vectorizer.fit_transform(corpus).toarray()

StopWords

并非所有文字都拥有相同的信息量,在文本分析前,我们通常需要进行预处理,去除停用词 (StopWords, 通常指那些低信息量高频率的词) ,从而简化语料库 (corpus) 。

NLTK 中获取停止词:

1
2
import nltk
nltk.download("stopwords")

1
2
from nltk.corpus import stopwords
sw = stopwords.words("english")

词干提取

可能有许多单词都表示同一个意思,在得到词袋之前,可以先将这些词打包进行词干分析,即可得到合并后的词汇,从而简化语料库。

可以使用 NLTK 提供的词干提取器 (Stemmer) 进行词干化。

1
2
3
4
5
6
from nltk.stem.snowball import SnowballStemmer

stemmer = SnowballStemmer("english")
print stemmer.stem("responsivity") == "respons"
print stemmer.stem("responsiveness") == "respons"
print stemmer.stem("unresponsive") == "unrespons"

应该在创建词袋之前先进行词干化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sklearn.feature_extraction.text import CountVectorizer

# split the text string into individual words
document = "Text Analysis is a major application field for machine learning algorithms."
vectorizer = CountVectorizer()
analyze = vectorizer.build_analyzer()
wordlist = analyze(document)

# stem each word
from nltk.stem.snowball import SnowballStemmer
stemmer = SnowballStemmer("english")
stemedlist = [stemmer.stem(word) for word in wordlist]

# join the stemmed word to words
words = " ".join(stemedlist)
print words

Tf–idf

单词的出现频率不能完全代表单词的权重,替代的方法是统计单词权重,其中流行的方法是 TF-IDF (Term Frequency–Inverse Document, 词频 - 逆文档频率),代表一个词对于一个文档的重要程度。

TF-IDF 在统计单词权重时会综合考虑(加权)词频和逆文档频率。

词频 (Term Frequency) : 给定的某一个词在一篇文档中出现的次数,词频越高 TF 值越高。

逆文档频率 (Inverse Document Frequency) : 单词在所有文档中出现的频率越高,IDF 值则下降,越罕见的词可能有更高的 IDF 值。

TF-IDF 会把文档中更有代表意义的单词标注出来,例如仅在某篇文档中出现频率较高,而不是所有文档中都频繁出现的词。

如果已经用 CountVectorizer 得到了向量,可以使用 TfidfTransformer 转换为 TF-IDF 表示。

1
2
3
4
5
6
7
from sklearn.feature_extraction.text import TfidfTransformer

# use TfidfTransformer to compute the tf-idfs
# X is generated by a CountVectorizer
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X)
print tfidf.toarray()

也可以直接使用 TfidfVectorizer 对文档进行向量化。

1
2
3
4
5
from sklearn.feature_extraction.text import TfidfVectorizer

# TfidfVectorizer combines all the options of CountVectorizer and TfidfTransformer in a single model
vectorizer = TfidfVectorizer(stop_words="english") # set parameters to remove stop words
X = vectorizer.fit_transform(corpus)

HashingVectorizer

当语料库很庞大时,将需要巨大的向量来编码文档,对内存要求很高,而且会减慢算法的速度。

HashingVectorizer 结合了特征哈希和 CountVectorizer ,可以用来解决上面的问题。

由于使用了单向的哈希方法,哈希化后的编码将无法转回单词的表达。

1
2
3
4
5
from sklearn.feature_extraction.text import HashingVectorizer

# Vectorizing a large text corpus with the hashing trick.
hv = HashingVectorizer(n_features=10)
vec = hv.transform(corpus)

特征选择 (Feature Selection)

选择合适的特征是机器学习算法中很关键的一环。通常,我们需要用尽量少的特征来表达尽量多的数据中包含的信息。以下是选择特征时常见的两个操作:

  • 欠拟合时需要加入新的特征来表示某些数据中重要的模式,添加新特征很多时候依赖于直觉与经验。
  • 找出最合适的特征,去除多余的特征。需要去除的特征有些是含有噪音,不利于模型训练,还有些可能与其他特征高度相关,导致重复的信息等等。此外,过多的特征也可能导致过拟合,还会造成训练速度缓慢。

注意:警惕有BUG的特征,有时候某个特征对准确率提升特别明显,此时需要警惕是真的找到了关键特征还是引入了不合理的特征(如特征很多样本很少,此时本该过拟合反而准确率很高)。sklearn 中某些分类器如 DecisionTreeClassifier 可以通过属性查看各特征的重要性, 可以仔细筛查重要性非常高的特征是否存在问题:

1
2
3
4
5
from sklearn.tree import DecisionTreeClassifier

clf = DecisionTreeClassifier()
clf.fit(features_train, labels_train)
print clf.feature_importances_

sklearn 中有多种辅助方法帮助自动选择特征。多数方法都属于单变量特征选择的范畴,即独立对待每个特征并询问其在分类或回归中的能力。

sklearn 中有两大单变量特征选择工具:SelectPercentileSelectKBest。 两者之间的区别从名字就可以看出:SelectPercentile 选择最有用的 x% 的特征,而 SelectKBest 选择 k 个最有用的特征。

1
2
3
4
5
6
7
from sklearn.feature_selection import SelectPercentile, f_classif

# feature selection
selector = SelectPercentile(f_classif, percentile=10)
selector.fit(features_train, labels_train)
features_train = selector.transform(features_train).toarray()
features_test = selector.transform(features_test).toarray()

此外,使用 TfidfVectorizer 进行文本向量化时, 也有控制参数可以进行特征简化,下例中,max_df=0.5 表示如果单词出现在超过50%的文档中,将不加入词汇表:

1
2
3
4
# max_df - When building the vocabulary 
# ignore terms that have a document frequency
# strictly higher than the given threshold
vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5, stop_words='english')

套索回归 (Lasso Regression)

Lasso算法是一种同时进行特征选择和正则化的回归分析方法。

其优化的目标函数如下:

使用 sklearn 中的 Lasso 算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sklearn.linear_model import Lasso

X_train = [[0, 0], [1, 1]]
y_train = [0, 1]
X_test = [[1, 1]]

# create the Lasso model and fit on the training data
reg = Lasso(alpha = 0.1)
reg.fit(X_train, y_train)

# The coefficients
print('Coefficients: ', reg.coef_)

# predict labels for the test data
pred = reg.predict(X_test)
print pred

参考资料

http://scikit-learn.org/stable/modules/preprocessing.html#scaling-features-to-a-range

http://scikit-learn.org/stable/modules/feature_extraction.html#text-feature-extraction

http://scikit-learn.org/stable/modules/feature_extraction.html#tfidf-term-weighting

http://scikit-learn.org/stable/modules/feature_extraction.html#vectorizing-a-large-text-corpus-with-the-hashing-trick

http://scikit-learn.org/stable/modules/feature_extraction.html#feature-hashing

http://scikit-learn.org/stable/modules/feature_selection.html

http://scikit-learn.org/stable/modules/linear_model.html#lasso