唯物是真 @Scaled_Wurm

プログラミング(主にPython2.7)とか機械学習とか

特徴量(素性)を作るときのメモ + scikit-learnにちょっと触る

機械学習のデータとして特徴量を作るときの注意点や悩むことなどをメモっておきました。

間違いなどが含まれているかもしれません。
基本的な内容ですので調べればもっと適切なやり方があると思います。

カテゴリカル・データ

カテゴリカル・データというのは、いくつかの限られた種類の値をとり、その大小関係に意味が無いものです。
質的データとか名義尺度とか呼ばれることもあります。
例えば都道府県のデータを考えた時に、北海道と沖縄は違う値ですが、その大小関係は定義できません。
(もちろん北海道と沖縄に面積的な大小関係などはありますが、欲しい情報ではないとします)

カテゴリカル・データを特徴量にするときにはカテゴリーごとにその特徴であるかどうかの二値にするとよいと言われています
以下に例を示します。それぞれの列がデータごとの特徴量を表していると考えてください

北海道:1 沖縄:0 東京:0
北海道:0 沖縄:1 東京:0
北海道:0 沖縄:0 東京:1

ちなみに最近Python機械学習ライブラリであるscikit-learnを使っているのですがDictVectorizerというのを使うと二値化もやってくれるみたいです。

from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer()
print vec.fit_transform([{'住所': '北海道'}, {'住所': '沖縄'}, {'住所': '東京'}]).toarray()

とすると以下のように出力されます

[[ 1.  0.  0.]
 [ 0.  0.  1.]
 [ 0.  1.  0.]]


他の特徴量の与え方としてはカテゴリーを数値として扱うというのも考えられるのですが、無意味な擬似的な大小関係が生じるため多くの手法では悪影響があります。
北海道を1、沖縄を2、東京を3とすると以下のようになります。

住所:1
住所:2
住所:3

頻度を使うそれとも?

データ内に複数回出現する要素(単語など)についても様々な特徴量の作り方がされています。
頻度そのものではなく割合にするとよくなったりすることがあります。

  • 含まれるかどうかの二値
  • 頻度そのもの
  • 頻度を全体の頻度で割ったりして重み付けしたもの(tf-idf - Wikipediaなど)

scikit-learnにもTf-idfの変換用のクラス(TfidfTransformer)があります。

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer()
>>> transformer
TfidfTransformer(norm='l2', smooth_idf=True, sublinear_tf=False, use_idf=True)

>>> 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 '<type 'numpy.float64'>'
    with 9 stored elements in Compressed Sparse Row format>

>>> tfidf.toarray()                        
array([[ 0.85...,  0.  ...,  0.52...],
       [ 1.  ...,  0.  ...,  0.  ...],
       [ 1.  ...,  0.  ...,  0.  ...],
       [ 1.  ...,  0.  ...,  0.  ...],
       [ 0.55...,  0.83...,  0.  ...],
       [ 0.63...,  0.  ...,  0.77...]])
http://scikit-learn.org/stable/modules/feature_extraction.html#tfidf-term-weighting

連続な特徴量の離散化

「時間」などの連続な値を持つ特徴量をどのように入れるかというのは大きな曖昧性があります。

大小関係が特徴量で捉えたい傾向に合致しているとは限らないため、カテゴリカル・データでやったように特徴量をいくつかの区分に分けて、その区分に含まれるかどうかで考えるという離散化がよく行われます。
この区分のやり方にもいろいろあって、単純な方法としては以下のようなものがあります。

  • 等しい幅、例えば4時間ごとに区切る(1-4時, 5-8時, 9-12時, 13-16時, 17-20時, 21-24時)
  • 区分ごとの頻度が等しくなるように(データを見て頻度が等しくなるように区分します)

あるいは、この区分以下ならこの特徴量が1になるという入れ方も考えられます。
5分毎に分けて、5分以下, 10分以下, 15分以下の区分があるときにデータが7分なら、10分以下かつ15分以下なので、2つの特徴量が1になります。

scikit-learnにはこの機能がありません。
使ったことはないですが、同じくPythonのライブラリのorangeには離散化の機能があるみたいです。

スケーリング

取りうる値の大小が著しく異なる特徴量を入れると結果が悪くなることがあります。
こういうときは平均を引いて、標準偏差で割るとよくなることがあります。場合によっては悪くなることもあります。
scikit-learnにはpreprocessing.scale関数があって簡単にスケーリングできます。

>>> from sklearn import preprocessing
>>> import numpy as np
>>> X = np.array([[ 1., -1.,  2.],
...               [ 2.,  0.,  0.],
...               [ 0.,  1., -1.]])
>>> X_scaled = preprocessing.scale(X)

>>> X_scaled                                          
array([[ 0.  ..., -1.22...,  1.33...],
       [ 1.22...,  0.  ..., -0.26...],
       [-1.22...,  1.22..., -1.06...]])
http://scikit-learn.org/stable/modules/preprocessing.html#standardization-or-mean-removal-and-variance-scaling

平滑化

時系列データなら前後の時間、画像データなら周りの画素などで平均を取ると結果がよくなることがあります。