機械学習の勉強では、アヤメの仕分けプログラムの開発が主流ですが、オーソドックス過ぎてつまらないので、艦これの艦種をパラメーターから推測するプログラムを作ってみました。

結果から言うと、この艦種推測の機械学習プログラムは正解率が低く、不完全です。(理由は後述します)

とはいえ、艦これが好きで、これから機械学習を勉強したいと考えている方にはおすすめできます。

プログラムの概要

概要

この記事で紹介するプログラムは、データをスクレイピングしてCSVに整形するプログラムと、データから学習して結果を推測する機械学習のプログラムの二本立てになっています。

まず、艦ごとのパラメーターを抜き取るために、艦これwikiからウェブスクレイピングを行い、CSVファイルにデータをまとめます。

そのCSVファイルを読み込んで、scikit-learnに教師データを読み込ませ、テストデータの結果を予想する仕組みです。

フローチャート図

プログラムが2つあるので、それぞれのフローチャート図を紹介します。

スクレイピングのフローチャート図

スクレイピングフローチャート図

機械学習のフローチャート図

機械学習フローチャート図

ソースコード

スクレイピングのソースコード

#! /usr/bin/python3
import sys,bs4,requests,csv,os

URL     = "https://wikiwiki.jp/kancolle/%E7%B0%A1%E6%98%93%E6%9C%80%E7%B5%82%E5%80%A4"
TIMEOUT = 10
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0"}

CSV_PATH    = "./kancolle_data.csv"
LABEL       = [[ "No", 
                "名前",
                "改造Lv",
                "火力",
                "雷装",
                "対空",
                "装甲",
                "耐久",
                "回避",
                "運",
                "運上限",
                "索敵",
                "対潜",
                "スロ",
                "搭載",
                "速力",
                "射程",
                "燃料",
                "弾薬"]]

def get_table():
    
    ship_data       = []
    csv_ship_data   = []

    site_data   = requests.get(URL,timeout=TIMEOUT,headers=HEADERS)
    site_soup   = bs4.BeautifulSoup(site_data.content,"lxml")

    row_data        = site_soup.select(".style_table > tbody > tr")

    for i in range(len(row_data)):
        cell_soup   = bs4.BeautifulSoup(str(row_data[i]),"lxml")
        cell_data   = cell_soup.select("td")

        for j in range(len(cell_data)):
            string = str(cell_data[j])
            if "gainsboro" not in string and "#EBF1DE" not in string:
                ship_data.append(cell_data[j].getText())

        if ship_data != []:
            csv_ship_data.append(ship_data)
            ship_data = []

    return csv_ship_data

def write_csv(csv_data):

    #存在する場合の処理
    if os.path.isfile(CSV_PATH):
        print("CSVファイルが既に存在します。")
        return False

    #存在しない場合の処理(ラベルも記述する)
    else:

        with open(CSV_PATH,"a",newline="",encoding='UTF-8',errors='replace') as output_file:
            output_writer = csv.writer(output_file)

            for i in range(len(LABEL)):
                try:
                    output_writer.writerow(LABEL[i])
                except:
                    print("write:error{}".format(i))

            for i in range(len(csv_data)):
                try:
                    output_writer.writerow(csv_data[i])
                except:
                    print("write:error{}".format(i))
            output_file.close()

            return True

if __name__ == "__main__":
    try:
        csv_data = get_table()
        print(write_csv(csv_data))


    except KeyboardInterrupt:
        print("\nprogram was ended.\n")
        sys.exit()

機械学習のソースコード

#! /usr/bin/python3
from sklearn import svm,metrics
from sklearn.model_selection import train_test_split
import pandas as pd

import sys,random,re,csv

CSV_PATH    = "./kancolle_data_edited.csv"

def learn_and_test():

    csv         = pd.read_csv(CSV_PATH)
    csv_data    = csv[["火力","雷装","対空","装甲","耐久","回避","対潜","搭載","燃料","弾薬"]]
    csv_label   = csv["艦種"]

    train_data,test_data,train_label,test_label = train_test_split(csv_data,csv_label,test_size=0.05)

    print(train_data)
    print(train_label)
    print(test_data)
    print(test_label)

    clf = svm.SVC()
    clf.fit(train_data,train_label)
    pre = clf.predict(test_data)

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

if __name__ == "__main__":
    try:
        learn_and_test()

    except KeyboardInterrupt:
        print("\nprogram was ended.\n")
        sys.exit()

このプログラムの解説

スクレイピングの解説

スクレイピングでは、指定したURLのテーブルタグを、行ごとにリストに代入していきます。

二次元リストになっており、write_csv関数にてCSVに記録しています。

機械学習の解説

機械学習では、CSVファイルを読み込み、一部のパラメーターを抽出して、対応する艦種を推測するようにしています。

スクレイピングでは艦種までCSVに記録していないので、手動で入力しました。

工夫したところ

テーブルタグのスクレイピングについて

テーブルタグのスクレイピングには課題が多いです。

特に、rowspanとcolspanの属性指定がスクレイピングを困難にしています。

そこで今回は、rowspanとcolspanが指定されているクラスや属性の部分を事前に除外して、セルの数が一致しなくても必ず行単位でテーブルタグを抽出できるようにしました。

それがスクレイピングプログラムのget_table関数の二重ループの箇所。一行単位でセルをパースしていく仕組みなので、セルの数が一致しなくても問題はありません。崩れることなくデータを抽出できます。

教師データとテストデータの割合指定について

scikit-learnには自動的に教師データとテストデータを仕分けしてくれる関数が用意されています。それが、train_test_splitです。

そのままだと値は0.25になり、教師データとテストデータの割合が75:25になります。この割合を変更可能な形にするために、test_sizeを指定しています。

今回のプログラムでは0.05に指定しているので、教師データとテストデータの割合は95:5です。

比較するパラメーターについて

艦種を特定するためには、比較するパラメーターに注意する必要があります。

例えば、雷装だけで比較しようとすると、雷装値が0の戦艦や空母、海防艦の区別がつきません。

同じように火力だけで比較しようとすると、100を超えている戦艦は特定しやすいですが、30前後の駆逐艦や海防艦の区別がつかないのです。

だから、比較するパラメーターについては複数指定する必要があります。特に火力、雷装、対空、装甲の基本のパラメーターの他に、搭載機数や燃費などでも比較をしています。

動作環境

  • Python3.6以上
  • BeautifulSoup、requests、scikit-learn、pandasがインストール済み

実際に動かしてみる

スクレイピングプログラムを動かして艦のパラメーターをCSVに格納する

まずは、データである艦ごとのパラメーターをCSVに格納してみます。スクレイピングプログラムを実行させましょう。

スクレイピング完了

無事、CSVファイルにデータを格納できました。

機械学習プログラムを動かして、艦種を予想する

CSVファイルの末尾の列に、艦種を入力します。表計算ソフトを使うなり、vimの矩形選択を使うなりご自由にどうぞ。

CSVファイルのファイル名をkancolle_data_edited.csvに書き換えてプログラムと同じディレクトリに格納します。

実行するとこうなりました。

正解率が50パーセントしか無い

正解率が50パーセントにしか到達できていません。あまりにも低すぎます。

なぜ正解率がこんなにも低いのか?

以下、あくまでも推測ではありますが、自分なりに考えてみたことをまとめてみました。

比較するパラメーターが少ない

比較するパラメーターの種類が少ないのではないでしょうか?

例えば、現在は「火力」「雷装」「対空」「装甲」「耐久」「回避」「対潜」「搭載」「燃料」「弾薬」の10個のパラメーターで比較していますが、中にはパラメーターが似ているだけで艦種が全く異なっている場合もあります。

とはいえ、比較するパラメーターが多すぎてしまうと、今度は過剰適合になってしまう可能性も考えられます。このバランスを考えつつ、高い正解率を叩き出せるようにしなければなりません。

艦種ごとのデータが少なすぎる

艦種の数に対して、データであるパラメーターが少なすぎると思います。

特に、揚陸艦や工作艦、水上機母艦などは実装されている艦が少ないので、正しい答えを推測することは難しいです。重雷装巡洋艦や航空巡洋艦等も同様です。

これらのデータが少なすぎる艦種については集約することで対処すれば良いでしょう。例えば、軽巡と重巡を、1つの巡洋艦という艦種に集約すれば予測もしやすくなります。

艦種を集約、比較するパラメーターの数を増やして再試行してみた

まず、艦種を集約します。以下のようにCSVの艦種を書き換えました。

変更前 変更後
海防艦 海防艦
駆逐艦 駆逐艦
軽巡洋艦、重巡洋艦、航空巡洋艦、練習巡洋艦、重雷装巡洋艦 巡洋艦
装甲空母、航空母艦、軽空母 空母
戦艦、航空戦艦 戦艦
潜水空母、潜水艦 潜水艦
水上機母艦、補給艦、揚陸艦、潜水母艦、工作艦 他艦

これで、艦種は7種類になりました。艦種ごとのデータも増えて予測しやすくなったのではないでしょうか?

続いて、比較するパラメーターを増やします。「火力」「雷装」「対空」「装甲」「耐久」「回避」「対潜」「搭載」「燃料」「弾薬」に加え、「運」、「索敵」、「スロ」を増やします。「速力」と「射程」は文字列なので今回は除外します。

この状態で比較してみると、こうなりました。

正解率75パーセントが出た

どうやら、艦種を集約したことがうまく機能したようです。とはいえ、試行回数を重ねると正解率の低いものも出てきており、最低で30パーセントしか正解しなかったものもありました。

正解率33パーセント

過剰適合の可能性もあるので、比較するパラメーターを減らしてみます。「回避」「運」「索敵」を除外してみました。

正解率91パーセント

正解率が90パーセントを超えました。とはいえ、テストデータの殆どが、データ量の多い駆逐艦を引いたので正解率が高くなるのも不思議ではないでしょう。

特にデータ量の少ない潜水艦や他艦などの推測には誤りが多く見られました。いずれにしてもデータの量の絶対値が少ないことが推測されます。

メリットとデメリット

メリット

  • 汎用性が高く、別の分野でも応用できる
  • 教師データとして複数の列を指定可能
  • 教師データとテストデータの割合を指定可能
  • CSVファイルを使用しているので、加工も容易

汎用性が高く、別の分野でも応用できる

この機械学習プログラムは、あくまでもCSVの列名を指定して結果を推測しているだけなので、汎用性は高いです。

CSVのデータが顧客の個人情報であればマーケティングなどにも利用できるでしょう。

教師データとして複数の列を指定可能

実際に動かしてみるで実践したとおり、複数の列を指定可能です。

比較するパラメーターも手動で変更できるので、正解率の高い組み合わせを特定できるでしょう。

後は、この正解率の高い組み合わせを、forループを活用して全自動で導き出すことができれば理想的ですね。

わざわざ手動でパラメーターをいじっていくのは面倒ですし。

教師データとテストデータの割合を指定可能

train_test_splitを使用して、教師データとテストデータの割合を指定しています。数値をいじればいいだけなので、簡単です。

後は、この数値を、正解率にムラが出ないように最適な割合になるように、自動で導き出せれば良いでしょう。

CSVファイルを使用しているので、加工も容易

今回は、数が極端に少ない艦種を集約することで対策しました。CSVファイルにデータを格納しているので、すぐに編集できますし、後から追加することもできるでしょう。

デメリット

  • ユーザーから値を受け取って推測することはできない
  • テーブル取得の際にrowspanとcolspanには対応していない

ユーザーから値を受け取って推測することはできない

ユーザーから値を受け取って、その値から艦種を推測することはできません。最も、比較するパラメーターが多いので、入力に手間がかかりそうですが。

テーブル取得の際にrowspanとcolspanには対応していない

厳密に言うと、テーブル取得の際にrowspanとcolspanには対応できていません。

rowspanとcolspanを使用しているタグのクラスを特定して除外しているだけなので、クラスが無ければ対応できないでしょう。

とはいえ、テーブルのスクレイピングはかなり複雑ですし、それぞれに対応させていると時間がかかるので、今回は無視しました。

結論

おそらく、これで機械学習の基礎部分は把握できるのではないでしょうか?

アヤメの種類を判定するデモプログラムでは、検索すれば前例が大量にあるのでいくらでも対策できるでしょう。試行錯誤をしなくても正解率が高いので面白みがありませんし、勉強にもならないと思います。

一方で、データ取得から正解率の向上などを含めて、自分で試行錯誤していくことができれば、今後の開発にも役立つでしょう。機械学習プログラムは組んで終わりではなく、高い正解率を叩き出さなければ意味はありませんし。

そのためにも、まずはデータ収集を徹底することから始めていく必要があると思います。

関連記事

BeautifulSoupを使用したスクレイピング

【内部リンク】cronで動作させ、テキストを連続で抽出して保存するウェブスクレイピングをPythonで作ってみた

BeautifulSoupを使用したウェブスクレイピングプログラムです。cronでの定時処理が可能で、機械学習に有効なプログラムであると言えるでしょう。

もちろん、改造すれば画像のダウンロードにも利用できます。

動的サイトのスクレイピングはSeleniumでOK

【内部リンク】Amazonのほしい物リストから値下げされた商品だけをメールで通知するPythonプログラム

Amazonのスクレイピング処理はBeautifulSoupとrequestsモジュールだけでは不可能です。動的サイトのスクレイピングをするためにはSeleniumを使いましょう。

Seleniumがあればブラウザ作業の自動化もできて、とても便利です。