LoginSignup
6
4

More than 3 years have passed since last update.

【Python】scikit-learnのSVMで文字列データを使用する

Last updated at Posted at 2021-01-11

はじめに

前記事の「【R】"Need numeric dependent variable for regression."の原因と対応」でR言語でサポートベクターマシンを使用した分類問題が動作させることが出来た。
今度はこれを R言語からPythonに移植しようとした際に、R言語では文字列データ(正確には因子型)で出来たが、Pythonでは数値型に変換しないと下記のエラーになってしまう。

model.fit(sze.iloc[train, 1:], sze.iloc[train, 0]) 

ValueError: could not convert string to float: 'G'

それだと不便だし、自分が知らないだけで本当はPythonでも出来るのではないかと疑い調べました。

環境

  • Google Colaboratory
  • Python 3.6.9
  • sklearn 0.22.2

調査

「scikit-learn SVM 文字列」で検索してもズバリの回答が得られない、日本語の検索は無理と判断して英語に変更し「scikit-learn SVM String」や「scikit-learn SVM Non-Integer」で検索して下記サイトを見つけた。
Non-Integer Class Labels Scikit-Learn - stackoverflow

これを実行してもエラーにはならない。

from sklearn.svm import SVC
clf = SVC()
x = [[1,2,3], [4,5,6]]
y = ['dog', 'cat']
clf.fit(x,y)

yhat = clf.predict([[1,2,5]])
print yhat[0]

それではと使用したいのはジャンケンデータなのでGとCとPの文字列を使用して、学習データと正解ラベルに文字列を用いてみた。しかし、こうすると同じエラーが出てしまう。

from sklearn.svm import SVC
clf = SVC()
x = [['G',2,3],['C',5,6]]
y = ['G', 'C']
clf.fit(x,y)

yhat = clf.predict([['P',2,5]])
print(yhat[0])

ValueError: could not convert string to float: 'G'

サンプル同様に正解ラベルのみ文字列にした場合、エラーは出なかった。

from sklearn.svm import SVC
clf = SVC()
x = [['1',2,3],['4',5,6]]
y = ['G', 'C']
clf.fit(x,y)

yhat = clf.predict([['1',2,5]])
print(yhat[0])

SVM(サポートベクターマシン)は線形二値分類のアルゴリズムなので、学習データにURLのような文字データが入ってきても分類できないと思われます。
機械学習ValueErrorでURL文字列を読み込めない。

学習データに関しては、文字列ではなく数値型を使用する必要がある。

文字列型から数値型にする

文字列型から数値型にするには、カテゴリーエンコーディングを使用する。
カテゴリーエンコーディングには、ラベルエンコーディングやOne-Hotエンコーディングなど多くの種類がある。
機械学習のための前処理(scikit-learn.preprocessing)として提供されている

LabelEncoder

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
le.fit(['C', 'P', 'G', 'C', 'C'])

print(le.classes_)
print(le.transform(['G', 'C', 'P', 'C'])) 
print(le.inverse_transform([1, 0, 2, 0]))

# 結果
['C' 'G' 'P']
[1 0 2 0]
['G' 'C' 'P' 'C']

le.fit() の部分で変換したいデータを選択します。
le.classes_では割り振りの値を確認できます。アルファベットの昇順に数値が割り振られています。
le.transform() で数値へ変換します。
le.inverse_transform()で文字列データに戻せます。

OneHotEncoder

One-hotベクトル(ワンホット表現)の意味とメリット・デメリット

LabelEncoderのように1次元配列で渡すと、次のエラー「'Expected 2D array, got 1D array instead'」になるので2次元配列で渡します。1次元配列で変換したい場合、後述のラベルバイナライザーを使用します。
pandasのDataFrameを使用する上では2次元配列の方が都合がいい。

from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer

ohe = OneHotEncoder()
ohe.fit([['C'], ['P'], ['G'], ['C'], ['C']])
ct = ColumnTransformer([("category", ohe, [0])], remainder="passthrough")

print(ohe.categories_)
print(ct.fit_transform([['G'], ['C'], ['P'], ['C']])) 
print(ohe.inverse_transform([[0,1,0],[1,0,0],[0,0,1],[1,0,0]]))

# 結果
[array(['C', 'G', 'P'], dtype=object)]
[[0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 1.]
 [1. 0. 0.]]
[['G']
 ['C']
 ['P']
 ['C']]

ohe.fit() の部分で変換したいデータを選択します。
ohe.classes_では割り振りの値を確認できます。アルファベットの昇順に数値が割り振られています。
ct.fit_transform() で数値へ変換します。
ohe.inverse_transform()で文字列データに戻せます。

ColumnTransformerを使うと複数の列の変換も可能、引数の意味合いなどは下記サイトを参考

OneHotEncoderのサンプルでみる'categorical_features'キーワードですが、バージョン0.20で非推奨、0.22で削除となり、代わりに'ColumnTransformer'を使用するように提示されます。
scikit-learnのColumnTransformerを使ってみる - 静かなる名辞

pandasには'get_dummies'を使用してOne-Hotエンコーディングができます。用途に合わせて使いましょう。
pandasのget_dummiesを使う - 静かなる名辞

本来の使い方としては、下記のように複数の組み合わせでOne-Hot表現したい場合となります。
OneHotEncoder - Taustation

from sklearn.preprocessing import OneHotEncoder

X = [
    ['Tokyo', 'Male'],
    ['Tokyo', 'Female'],
    ['Osaka', 'Male'],
    ['Kyoto', 'Female'],
    ['Osaka', 'Female'],
    ['Osaka', 'Male']
]

ohe = OneHotEncoder(sparse=False)
ohe.fit(X)

print(ohe.categories_)
print(ohe.transform(X)) 

# 結果
[array(['Kyoto', 'Osaka', 'Tokyo'], dtype=object), array(['Female', 'Male'], dtype=object)]
[[0. 0. 1. 0. 1.]
 [0. 0. 1. 1. 0.]
 [0. 1. 0. 0. 1.]
 [1. 0. 0. 1. 0.]
 [0. 1. 0. 1. 0.]
 [0. 1. 0. 0. 1.]]

LabelBinarizer

One-Hotエンコーディングでは2次元配列で渡していましたが、ラベルバイナライザーは1次元配列でOne-Hot表現に変換できます。

import numpy as np
from sklearn.preprocessing import LabelBinarizer

lb = LabelBinarizer()
lb.fit(['C', 'P', 'G', 'C', 'C'])

print(lb.classes_)
print(lb.transform(['G', 'C', 'P', 'C'])) 
print(lb.inverse_transform(np.array([[0,1,0],[1,0,0],[0,0,1],[1,0,0]])))

# 結果
['C' 'G' 'P']
[[0 1 0]
 [1 0 0]
 [0 0 1]
 [1 0 0]]
['G' 'C' 'P' 'C']

lb.fit() の部分で変換したいデータを選択します。
lb.classes_では割り振りの値を確認できます。アルファベットの昇順に数値が割り振られています。
lb.transform() で数値へ変換します。
lb.inverse_transform()で文字列データに戻せます。※np.arrayが必要

Scikit-learn's LabelBinarizer vs. OneHotEncoder - stackoverflow

OrdinalEncoder

LabelEncoderと同じようにアルファベットの昇順に数値が割り振られます。違いとしてはOneHotEncoderと同様に2次元配列で渡します。
pandasのDataFrameを使用する上では2次元配列の方が都合がいい。

from sklearn.preprocessing import OrdinalEncoder

oe = OrdinalEncoder()
oe.fit([['C'], ['P'], ['G'], ['C'], ['C']])

print(oe.categories_)
print(oe.fit_transform([['G'], ['C'], ['P'], ['C']])) 
print(oe.inverse_transform([[1],[0],[2],[0]]))

# 結果
[array(['C', 'G', 'P'], dtype=object)]
[[1.]
 [0.]
 [2.]
 [0.]]
[['G']
 ['C']
 ['P']
 ['C']]

oe.fit() の部分で変換したいデータを選択します。
oe.classes_では割り振りの値を確認できます。アルファベットの昇順に数値が割り振られています。
oe.fit_transform() で数値へ変換します。
oe.inverse_transform()で文字列データに戻せます。

OrdinalEncoderは複数の組み合わせで使用できるので、LabelEncoderが正解ラベル側として使用するのに対し、学習データ側はOrdinalEncoderを使用するイメージです。

from sklearn.preprocessing import OrdinalEncoder

X = [
    ['Tokyo', 'Male'],
    ['Tokyo', 'Female'],
    ['Osaka', 'Male'],
    ['Kyoto', 'Female'],
    ['Osaka', 'Female'],
    ['Osaka', 'Male']
]

oe = OrdinalEncoder()
oe.fit(X)

print(oe.categories_)
print(oe.transform(X)) 

# 結果
[array(['Kyoto', 'Osaka', 'Tokyo'], dtype=object), array(['Female', 'Male'], dtype=object)]
[[2. 1.]
 [2. 0.]
 [1. 1.]
 [0. 0.]
 [1. 0.]
 [1. 1.]]

CustomerRating

サザエさんのジャンケンでは、統計情報からG>C>Pの強さになっているので、LabelEncoderOrdinalEncoderのようにアルファベットの昇順に数値が割り振られると、結果に違いが出てしまう。CをGとPの間にある別のアルファベットに変更すれば、LabelEncoderOrdinalEncoderでもそのまま使用できるが、記号を維持したまま数値を割り当てたい。

mapを使用して割り当てたい数値に変換している。

import pandas as pd

rating = {'G' : 1, 'C' : 2, 'P' : 3}
df = pd.DataFrame(['C', 'P', 'G', 'C', 'C'])
print(df.applymap(lambda x : rating[x]))

# 結果
0  2
1  3
2  1
3  2
4  2

最後に

同じ列に異なる数値があるとモデルのデータによってはある種の順序(0 < 1 < 2)であると誤解する。この問題を克服するために、One Hot表現を使用する。逆にある種の順序(G > C > P)の優先度を考慮するならば、同じ列に異なる数値を用いてもいい。

One Hot表現を使用する意味がやっと分かった気がする。
入力(説明変数、独立変数)と出力(目的変数、従属変数)があって、説明変数側は回帰でも分類でも基本的 One Hot表現を使用すればいいわけだ、目的変数側は文字列であっても問題ないよってことだね。

【scikit-learn】Label EncoderとOne Hot Encoderの使い分けについて - teratail

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4