質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

SVMによる多クラス分類で予測すると、すべて同じクラスに分類される

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,202

pura

score 5

 発生している問題・エラーメッセージ

当方、農作物の研究をしております。
過去十数年にわたり国内約100か所で気象データや土壌の状態に関する指標約80種を調査した結果がexcelinputシートに、
また同年の収穫時期(1:9月上旬、2:9月中旬・・・6:月下旬として、多クラス分類されてます)を調査した結果が
同じブック内のanswerシートにそれぞれ約2000件入力されています。
これら80種の指標と収穫時期の関係をSVMで学習して当年の収穫時期の予測に活用できないかを試しており、以下を記述したところ、
得られるtest_dataの回答がすべて同じクラスになってしまいます。

 該当のソースコード

python3

import pandas as pd
import numpy as np
from sklearn import svm, metrics, cross_validation
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from mlxtend.plotting import plot_decision_regions

filename = "gloth.xlsx"
input_sheet_name = "input"
answer_sheet_name = "answer"

input_book = pd.read_excel(filename, sheet_name=input_sheet_name)
answer_book = pd.read_excel(filename, sheet_name=answer_sheet_name)

new_input_book = input_book.drop(0, axis=0) #inputシートの0行目はデータ収集中のため、除外していいます。
new_answer_book = answer_book.drop(0, axis=0) #answerシートの0行目はデータ未調査で空欄のため、除外しています。

xlsx_data = new_input_book[["MAX_TEM","MIN_TEM"・・・,"SEIIKU_1","SEIIKU_2"]] #指標はinputシートに80種程度入力しています
xlsx_label = new_answer_book[["Group"]] #収穫時期は多クラス分類してanswerシートに入力しています

train_data, test_data, train_label, test_label = cross_validation.train_test_split(xlsx_data, xlsx_label,test_size=0.2)

clf = svm.SVC(kernel='rbf', gamma=1/80, C=10.0)
clf.fit(train_data, train_label.values.ravel())

pre = clf.predict(test_data)

ac_score = metrics.accuracy_score(test_label, pre)
print("正解率=", ac_score)

 試したこと

print(pre)
でtest_dataの予測結果を確認したところ、いずれのデータの予測もすべて「1」として分類されているため、
正解率は16~20%と大変低くなってしまいます。

 補足情報(FW/ツールのバージョンなど)

プログラムは、jupyter notebookで記述しています。
機械学習の勉強を始めて1か月程度の素人です。
不足している情報がありましたら
お伝えしたいと思いますので、よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

もう解決していますが…

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

sklearnのSVCはOne-vs-Oneで実装されていませんか?


簡単なIrisに対してのコード。

SVCをもうすでに使っているのならマルチクラスを分類できない理由は特にないように思いましたが…

import numpy as np

from sklearn.datasets import load_iris

from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import accuracy_score
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import RandomizedSearchCV

from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier, OneVsOneClassifier
from sklearn.multioutput import MultiOutputClassifier

def load(fonehot=False):
    data = load_iris()

    x = data['data']
    y = data['target']
    hs = y

    if fonehot:
        en = OneHotEncoder()
        y = en.fit_transform(y.reshape(-1, 1)).toarray()

    return x, y, hs

def get_SVC():
    clf = SVC()
    param_grid = {'kernel': ['rbf', 'linear'],
                  'C': np.logspace(-10, 1, 1000),
                  'gamma': np.logspace(-10, 1, 1000)}
    clf = RandomizedSearchCV(clf, param_grid, cv=5, n_iter=100, random_state=2018)
    return clf

def get_OvR():
    clf = SVC()
    clf = OneVsRestClassifier(clf)
    param_grid = {'estimator__kernel': ['rbf', 'linear'],
                  'estimator__C': np.logspace(-10, 1, 1000),
                  'estimator__gamma': np.logspace(-10, 1, 1000)}
    clf = RandomizedSearchCV(clf, param_grid, cv=5, n_iter=100, random_state=2018)
    return  clf

def get_OvO():
    clf = SVC()
    clf = OneVsOneClassifier(clf)
    param_grid = {'estimator__kernel': ['rbf', 'linear'],
                  'estimator__C': np.logspace(-10, 1, 1000),
                  'estimator__gamma': np.logspace(-10, 1, 1000)}
    clf = RandomizedSearchCV(clf, param_grid, cv=5, n_iter=100, random_state=2018)
    return  clf

def get_OneHot():
    clf = SVC()
    clf = MultiOutputClassifier(clf)
    param_grid = {'estimator__kernel': ['rbf', 'linear'],
                  'estimator__C': np.logspace(-10, 1, 1000),
                  'estimator__gamma': np.logspace(-10, 1, 1000)}
    clf = RandomizedSearchCV(clf, param_grid, cv=5, n_iter=100, random_state=2018)
    return  clf

def CV(get_clf, x, y, hs, n_splits=3):
    kf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=2018)
    s_s = []
    pss = []
    for tr, ts in kf.split(x, hs):
        x_ = x[tr]
        y_ = y[tr]
        px = x[ts]
        py = y[ts]
        clf = get_clf()
        clf.fit(x_, y_)
        s_ = accuracy_score(y_, clf.predict(x_))
        ps = accuracy_score(py, clf.predict(px))
        s_s.append(s_)
        pss.append(ps)
    print('train: {0:7.4f} {1:7.4f}'.format(np.mean(s_s), np.std(s_s)))
    print('test:  {0:7.4f} {1:7.4f}'.format(np.mean(pss), np.std(pss)))

if __name__ == '__main__':
    print('SVC(Implemented with One-vs-one)')
    CV(get_SVC, *load())
    print('OneVsRest')
    CV(get_OvR, *load())
    print('OneVsOne')
    CV(get_OvO, *load())
    print('OneHot')
    CV(get_OneHot, *load(fonehot=True))

追記

IrisのXに2変数を使って平面上に射影したときの境界線。

広がったガウシアンの場合(gamma=0.000001)
広がったガウシアン

局在したガウシアンの場合(gamma=10000.)
局在したガウシアン

CVしたガウシアンの場合
最適化したガウシアン

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/24 09:29

    mkgreiさん
    別に気にする必要はありません。
    私自身、svmでは多クラス識別はムリと教わっていて、そこから先は自力で1対他をコードにしていたので、むしろ勉強になりました。

    svcで多クラス識別が難しい原因ですが、カーネルの問題ということはないでしょうか?リニアなら線を重ね合わせることで識別できることはロジスティック回帰の多クラス識別の原理から理解できますが、rbfだとムリなのかなぁと感覚的に思っていました。この辺りは真面目にsvmの原理を探求していないので、解説付きで可能とする理由を教えていただけると助かります

    キャンセル

  • 2018/03/26 21:11

    rbfの場合、近傍法に近い状況になるので、最寄りの教師データに分類されるのではないでしょうか?
    その際にガウシアンの減衰速度によって境界線の形が変化します。
    排他的経済水域みたいになります。

    図を追記します。

    キャンセル

  • 2018/03/26 22:08

    いろいろとありがとうございます。
    やはりマジメに勉強しないとなぜそうなるのかについてイメージがつかめそうにない気がしてきました。

    キャンセル

+1

svmは多クラス識別ができないと記憶していて、その結果ではないかと思います。
svmを使った多クラス識別をするには1対多分類法と呼ばれるあるクラスとそれ以外のクラスという2値分類を複数回実施するものがいいかと思います。具体的にはクラス1とそれ以外の分類を実施します。次にクラス1以外と識別されたデータを対象にクラス2とそれ以外の分類を実施します。以後同様の処理を全ての分類が終わるまで実施します。
もしかすると、svmで多クラス識別する関数が既に実装されているのであれば、それを使ったほうがいいと思いますが、とりあえず、上記を試すことを検討してはいかがでしょうか

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/22 10:11

    早速のご教示をいただき、ありがとうございます。
    調べてみたところ、おっしゃるとおりSVMでは多クラス識別を行えないことがわかりました。
    幸い、SVMで多クラス識別する方法(One versus the OneまたはOne versus the Rest)について
    解説されているサイトもありましたので、いただいたご回答とそちらのサイトを参考として
    試行錯誤してみようと思います。

    今週月曜から3日間、ずっとあれこれいじってもうまくいかず悩んでおりましたが、
    そもそも元のSVMが多クラス分類に対応していないとは、調べ方に問題ありと痛感しており、
    お恥ずかしい限りです。重ね重ね、感謝申し上げます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る