Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

sklearnを用いた多クラス分類試してみた

前回、ロジスティック回帰にてポケモンのステータスからタイプを推定してみました。
elsammit-beginnerblg.hatenablog.com

しかし、2クラス分類しか出来ていなかったので2種類のタイプ中、どちらのタイプが合致するかを推定することしか出来ませんでした。
そこで、今回は多クラス分類で3タイプ以上の分類を行いたいと思います!!
f:id:Elsammit:20200927222549p:plain

。。。と考えていたのですが、、、
3クラスで微妙な結果となってしまったので、今回はそこまでをまとめたいと思います😢

■ロジスティック回帰での多クラス分類

ロジスティック回帰での多クラス分類を行う上で、one vs all(one vs rest)を用います。
one vs allとは、複数クラスを「注目するクラス」と「その他のクラス」に分け、この2クラスに対してロジスティック回帰を行う手法となります!!

まずは3クラス分類するために目的変数Yを追加します。
目的変数Yを用意する関数は下記としました。

def type_to_num(p_type,test,typ):
    if p_type == test:
        return 0
    elif p_type == typ:
        return 2
    else:
        return 1

poke1_type1 = df[df['タイプ1'] == ctype1]
poket1_ype2 = df[df['タイプ2'] == ctype1]
poke1_type = pd.concat([poke1_type1, poket1_ype2])

poke2_type1 = df[df['タイプ1'] == ctype2]
poke2_type2 = df[df['タイプ2'] == ctype2]
poke2_type = pd.concat([poke2_type1,poke2_type2])

poke3_type1 = df[df['タイプ1'] == ctype3]
poke3_type2 = df[df['タイプ2'] == ctype3]
poke3_type = pd.concat([poke3_type1,poke3_type2])

pokemon_types = pd.concat([poke1_type, poke2_type, poke3_type], ignore_index=True)
type1 = pokemon_types["タイプ1"].apply(type_to_num,test=ctype1,typ=ctype3)
type2 = pokemon_types["タイプ2"].apply(type_to_num,test=ctype1,typ=ctype3)
pokemon_types["type_num"] = type1*type2
pokemon_types.head()

※ctype1~ctype3には各タイプ名が入ります。

前回の関数から1種類タイプを追加してtype_numを0~2の3種類が入るようにしました。

そして、3クラスのロジスティック回帰を実行します。
実行するコードはこちら。

X = pokemon_types.iloc[:, 7:13].values
y = pokemon_types["type_num"].values

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)
lr = LogisticRegression(C=1.0, max_iter=200)
lr.fit(X_train, y_train)

3種類分のポケモンを分類することになり、繰り返し回数が増大したため、max_iterを200に設定しました。


こちらで試しに、「はがね」、「ノーマル」、「みず」の組み合わせで実行したところ、、、

-------------------------------------------------
trainデータに対するscore: 0.618
testデータに対するscore: 0.627
-------------------------------------------------

となりました!!
低い!! 60%台って。。。流石に。。。きつい。。。

一番判別できる組み合わせを確認してみました!!
結果、
「はがね」、「ノーマル」、「みず」の組み合わせが一番高い結果となりました。
ですが、

Highest Score is ...
========================================================
type1 is でんき type2 is ノーマル type3 is はがね
result score is 0.829
result score(train) is 0.795
========================================================

という結果であり、80%台👌
まぁ、及第点かな?

次に前回と同様にポケモン剣盾で出現するポケモンのみに限定して先ほど学習させたモデルで予測判定を実施してみました!!
検証時のコードはこちら。

poketype1 = "でんき"
poketype2 = "ノーマル"
poketype3 = "はがね"

    with codecs.open("/kaggle/input/pokemon/pokemon.csv", "r", "utf-8", "ignore") as file:
        check = pd.read_table(file, delimiter=",")  
    
    metal11 = check[check['type1'] == poketype1]
    metal22 = check[check['type2'] == poketype1]
    metal = pd.concat([metal11, metal22])

    elec1 = check[check['type1'] == poketype2]
    elec2 = check[check['type2'] == poketype2]
    elec = pd.concat([elec1,elec2])
    
    water1 = check[check['type1'] == poketype3]
    water2 = check[check['type2'] == poketype3]
    water = pd.concat([water1,water1])

    pokemon_check = pd.concat([metal,elec,water], ignore_index=True)
    type11 = pokemon_check["type1"].apply(type_to_num,test=poketype1,typ=poketype3)
    type22 = pokemon_check["type2"].apply(type_to_num,test=poketype1,typ=poketype3)
    pokemon_check["type_num"] = type11*type22
    pokemon_check.head()

    X = pokemon_check.iloc[:, 1:7].values
    y = pokemon_check["type_num"].values

    i = 0
    error1 = 0
    success1 = 0
    error2 = 0
    success2 = 0
    error3 = 0
    success3 = 0
    print("[判断したポケモン結果]")
    print("----------------------------------------")
    print("")

    while i < len(pokemon_check):
        y_pred = lr.predict(X[i].reshape(1, -1))
        if y_pred == 0:
            if pokemon_check.loc[i, ["type_num"]].values == 0:
                success1 += 1
            else:
                error1 += 1
                print(pokemon_check.loc[i, "name"])
                print(str(poketype1)+"タイプではないです")
                print("")
        elif y_pred == 1:
            if pokemon_check.loc[i, ["type_num"]].values == 0:
                error2 += 1
                print(pokemon_check.loc[i, "name"])
                print(str(poketype1)+"タイプです")
                print("")
            elif pokemon_check.loc[i, ["type_num"]].values == 2:
                error2 += 1
                print(pokemon_check.loc[i, "name"])
                print(str(poketype3)+"タイプです")
                print("")             
            elif pokemon_check.loc[i, ["type_num"]].values == 1:
                success2 += 1
        elif y_pred == 2:
            if pokemon_check.loc[i, ["type_num"]].values == 2:
                success3 += 1
            else:
                error3 += 1
                print(pokemon_check.loc[i, "name"])
                print(str(poketype3)+"タイプではないです")
                print("")

        else:
            print("意味不エラー")
        i += 1
    print("----------------------------------------")
    print("正しく" + str(poketype1) + "タイプと判断したポケモンの数: %d匹" % success1)
    print("正しく" + str(poketype3) + "タイプと判断したポケモンの数: %d匹" % success3)
    print("正しく" + str(poketype1) + "タイプではないと判断ポケモンの数: %d匹" % success2)
    print("誤って" + str(poketype1) + "タイプと判断したポケモンの数: %d匹" % error1)
    print("誤って" + str(poketype3) + "タイプと判断したポケモンの数: %d匹" % error2)
    print("誤ってタイプ判定したポケモンの数: %d匹" % error2)
    print("検査合計結果:%d匹" % (error1+error2+error3+success1+success2+success3))
    print("識別率:%.3f%%" % ((success1+success2+success3)/(error1+error2+error3+success1+success2+success3)*100))

上記を実行した結果、

----------------------------------------
合計ポケモン数:92匹
正しくでんきタイプと判断したポケモンの数: 22匹
正しくはがねタイプと判断したポケモンの数: 20匹
正しくでんきタイプではないと判断ポケモンの数: 29匹
誤ってでんきタイプと判断したポケモンの数: 4匹
誤ってはがねタイプと判断したポケモンの数: 15匹
誤ってタイプ判定したポケモンの数: 15匹
検査合計結果:92匹
識別率:77.174%

70%台😂 あまりうまく判定出来ていないですね泣。

■k近傍法で分類してみた

あまりうまく行かなったので、今度はk近傍法で分類を試してみました。


クラス判別法の1つ。knnと呼ばれております。
学習データをベクトル空間上にプロットしておき、未知のデータが得られたら、そこから距離が近い順に任意の個数を取得し、
多数決でデータが属するクラスを推定する。

例えば下記の場合、
k=3の範囲では、ClassBに分類
k=6の範囲では、ClassAに分類
されることになります。
f:id:Elsammit:20200927214041p:plain

knnはこちらの要領で実行可能です。

from sklearn.neighbors import KNeighborsClassifier
from sklearn.cross_validation import train_test_split

lr = KNeighborsClassifier(n_neighbors = 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)
lr.fit(X_train, y_train)

ロジスティック回帰と異なる点は、

from sklearn.neighbors import KNeighborsClassifier

を定義することと、
モデルを

lr = KNeighborsClassifier(n_neighbors = 8)

に置き換えることです。
n_neighborsにはk値が入ります。

knnで実行してみた結果、

-------------------------------------------------
trainデータに対するscore: 0.769
testデータに対するscore: 0.821
-------------------------------------------------

となり、80%台👌

先ほどと同様に、ポケモン剣盾で出現するポケモンのみに限定して先ほど学習させたモデルで予測判定を実施!!

----------------------------------------
合計ポケモン数:92匹
正しくでんきタイプと判断したポケモンの数: 23匹
正しくはがねタイプと判断したポケモンの数: 22匹
正しくでんきタイプではないと判断ポケモンの数: 27匹
誤ってでんきタイプと判断したポケモンの数: 3匹
誤ってはがねタイプと判断したポケモンの数: 14匹
誤ってタイプ判定したポケモンの数: 14匹
検査合計結果:92匹
識別率:78.261%

70%台😂 先ほどのロジスティック回帰と比較すれば若干よく判定出来ていますが、、、あまりうまく判定出来ていないですね泣。

ソースコードはこちらに公開しております。
https://github.com/Elsammit/PokemonAI.git

■今後の方針

2クラス分類と比較してとても微妙な結果となりました。。。
k近傍法(knn)、ロジスティック回帰どちらもほぼ同じ結果。。。
ただ、パラメータを変化させていないので、こちらを最適化させることでもう少し精度を上げることができそうな気がします。
引き続き調査してみたいと思います!!

■最後に

クラスが1つ追加するだけで難易度がグッと上がった気がします😢
後、色々ネットを見ながら進めてきましたが、結構苦しいですね。
やはり本などでベースを作らなければならないですね。。。
G検定合格したので各アルゴリズムの概要は知っているのですが、いざ使うとなると。。。ですね

深層学習教科書 ディープラーニング G検定(ジェネラリスト) 公式テキスト

新品価格
¥2,772から
(2020/9/27 22:22時点)

以前から気になっていたこちらの本でも買って読もうかな??

ゼロから作るDeep Learning ?Pythonで学ぶディープラーニングの理論と実装

新品価格
¥3,445から
(2020/9/27 22:20時点)

■参考
http://www.tsjshg.info/udemy/Lec80-81.html
K近傍法(多クラス分類) - Qiita