クロの制作日記

クロの制作日記

田舎の大学生がUnityとか機械学習関連の制作物をひたすらアップします。ブログで紹介したコード一覧https://github.com/kuroshum/blog_code

機械学習のニューラルネットワークを用いてワインの質を分類

はじめに

大学の機械学習に関する演習で、ニューラルネットワークを用いて好きな分類問題を解けといった課題が出されました。

以下の記事でも言及しています。
kurora-shumpei.hatenablog.com

今回はその課題で作成したニューラルネットワークの解説をしていきます。


ちなみに、分類問題はワインの質を10段階で分類するというWine Quality Data Setを選択しました。
archive.ics.uci.edu




モデル

ニューラルネットワークのモデルの金型は演習を担当した先生のホームページに載っています。基礎から説明していてわかりやすいので、ニューラルネットワークの仕組みを理解したい人は1から読むことをおすすめします。

hirotaka-hachiya.hatenablog.com

私のモデルもこのサイトを元に演習で作成しました。プログラムを確認したらわかりますが、tensorflowやchainerといった機械学習のライブラリを用いずにpythonだけ(勿論numpyとかは使ってますが)でニューラルネットワークを実装しています。なので、pythonさえあれば動くので、環境の構築は気にしなくても大丈夫です。

プログラム

実装したプログラムはGitHubにあげています。
github.com


コメント見れば大体わかるかと思いますが、一応上記のプログラムの解説をしていきます。

ただ、先生のサイトで解説されているニューラルネットワークの基本的な部分の解説は省略します。

その部分は上記の先生のサイトで確認してください。

全体の流れ

流れは大体こんな感じになっています。

f:id:kurora-shumpei:20190218180816p:plain
フロー
では一つ一つ確認していきましょう。

データ読み込み

winequality-white.csvデータを読み込みます。265~277行目の箇所です。

f:id:kurora-shumpei:20190218181103p:plain
データの読み込み

このワインの質データは白ワインと赤ワインのデータが存在しています。なのでプログラムではコメントアウトしていますが「winequality-red.csv」を読み込むこともできるようにしています。

ここでは、

  • csvファイルを読み込む
  • pandasを用いて説明変数(quality以外のワインの成分データ)と目的変数(quality)を分割して変数に保存

を行っています。

Holdout法

何か英語で書いてあるし凄そうと思うかもしれませんが、やっていることは単純です。

以下の画像のように説明変数を訓練データと評価データに一定の割合で分割しているだけです。

f:id:kurora-shumpei:20190218182249p:plain
Holdout法

プログラムとしては171~197行目の箇所です。

#------------------------------------------------
# Holdout法
# 入力されたデータを訓練データ・テストデータに分割
# x    : 説明変数
# t    : 目的変数
# rate : データ分割の割合
def Holdout(x, t, rate):
    # 全データ数
    data_num = len(x)
    # 訓練データ数(全データ数のrate割)
    train_num = int(data_num*rate)

    np.random.seed(1)

    # データ数
    randInd = np.random.permutation(data_num)

    # 訓練データ( 0 ~ train_num )
    xTrain = x[randInd[:train_num]]
    tTrain = t[randInd[:train_num]]

    # テストデータ( train_num ~ data_num)
    xTest = x[randInd[train_num:]]
    tTest = t[randInd[train_num:]]

    return xTrain, tTrain, xTest, tTest
#------------------------------------------------

データの前処理

これの説明は過去に記事として公開していますのでそちらを確認してください。
kurora-shumpei.hatenablog.com

プログラムとしては250~262行目の箇所です。

def Standardization(xData, Flag=True):
    xData_cent_list = []
    if Flag == True:
        for i in range(xData.shape[1]):
            mean_vals = np.mean(xData[:,i], axis=0)
            std_val = np.std(xData[:,i])
            xData_cent = ((xData[:,0] - mean_vals) / std_val)
            xData_cent_list.append(xData_cent)
        xData_cent_list = np.array(xData_cent_list).reshape([xData.shape[0],xData.shape[1]])
    else:
        xData_cent = xData

    return xData_cent_list

データを分類

ここからはニューラルネットワークの説明となるので省きます。

ただ、一つ問題がおきました。

実行自体は問題なく進んだのですが、実行結果が以下の画像のようになりました。

f:id:kurora-shumpei:20190218183656p:plain
結果が悪い

まじか...、と落胆してプログラムの方で何かミスをしているのか?と思って調べたのですが特に問題はありません。

なんで良い結果がでないんだ!?この野郎!!と色々原因を探った結果、

そもそも今回作成した3層のニューラルネットワークでは11段階分類は難しいんじゃないか、と結論付けしました。

最近流行りのディープニューラルネットワーク(4層以上のニューラルネットワーク)なら正確に分類できるのかもしれませんが、今回のような浅いニューラルネットワークでは11段階の分類は無理があるのだと思います。

この問題の解決方法としては、

  • 層を増やす
  • データを増やす
  • 分類の段階を減らす

の3つが上げられます。他にもあったらごめんなさい。

一番目の層を増やすのは技術的に難しいのと演習の趣旨から外れる気がしたので除外しました。

二番目のデータを増やす、恐らくこれが一番良いアプローチだと思います。

ただ、データは提供されているものなので正攻法ではデータを増やすことができません。なので、元のデータを色々いじってデータを水増しする方法があります。

以下のサイトに詳しく載っているので興味があれば確認してみてください。
products.sint.co.jp

本当はこれを実装したかったのですが、時間がなかったので諦めました。

ということで今回は三番目の分類の段階を減らすを選択しました。

これはこれで問題を回避しているだけなので良いのか?と思うでしょうが、所詮大学の演習なので良しとしてください。

今回は、二段階と五段階に減らせるように実装しました。

プログラムとしては290~293行目と223~248行目の箇所です。

# 分類するクラス数を 10 から 2 に変更(0:美味しくない or 1:美味しい)
    # Standard : 基準値
    standard = 6
    tData = SetClassNum(tData, standard, classNum=2)
#------------------------------------------------
# クラス数を10から、2 or 5に変更する
def SetClassNum(tData, standard, classNum=2):
    if classNum == 5:
        index = 0
        for i in tData:
            if 10 > i and i > 7:
                tData[index] = 4
            elif 8 > i and i > 5:
                tData[index] = 3
            elif 6 > i and i > 3:
                tData[index] = 2
            elif 4 > i and i > 1:
                tData[index] = 1
            else:
                tData[index] = 0
            index += 1

    elif classNum == 2:
        tData = np.array([1 if i > standard else 0 for i in tData])[np.newaxis].T
    
    # 目的変数をone-hot表現に
    tData = np.eye(classNum)[tData[:,0]]
    
    return tData
#------------------------------------------------

ということで実行すると、以下の画像のような結果がでました。

f:id:kurora-shumpei:20190218190502p:plain
終結

まあまあの結果が出たので良しとしましょう。




最後に

今回はニューラルネットワークを用いたワインの質の分類を解説しました。

機械学習のライブラリを使わずにニューラルネットワークを実装するのは良い経験になったと思います。

先生のサイトでは強化学習についての記事も書いてあるので興味があれば、また確認してみてください。