1. はじめに
この記事では、機械学習の手法を用いて人事データを分析し、分類を行いました。
- 教師なし学習の k-means法(エルボー法、シルエット分析) を使ったクラスタリング
- 教師なし学習の 混合ガウス分布の情報量基準 を使ったクラスタリング
- 教師あり学習の 決定木 を使った分類
- 教師あり学習の ランダムフォレスト を使った分類
- 教師あり学習の ニューラルネットワーク を使った分類
分析するデータは、コーエー・テクモ社の超有名タイトル「三國志XIII」の武将データです。公開されているサイトからスクレイピングで取得しています。
題して「三国志に学ぶ適材適所の分析」です。ご興味ある方はご一読いただきますと幸いです。
2. 分析の背景
2.1. 動機
私はしがないサラリーマンで、会社では人事関係の部署に長く務めています。主に人事情報が集積されたシステムを導入時から扱っており、なんとなく人事データに触れる機会が多いです。
最近、従業員満足度アンケート、適性検査、ストレス耐性などいろいろな従業員分析のサービスが飛躍的な進化を遂げています。
これはおそらく、このようなサービスを実施している会社が、機械学習の手法を分析に取り入れた成果でしょう!アンケート回答の文脈からPOSI/NEGA分析するなんて、昔はなかったですもん。
私も機械学習の手法を身につけたく、分析にトライしました。
2.2. 開発環境
ハード(自作PC)
項目 | 内容 |
---|---|
OS | Ubuntu 22.04 |
CPU | Ryzen5600X |
GPU | GeforceGTX1660Super |
ソフト
名称 | バージョン |
---|---|
Python | 3.10.12 |
cuda | 12.2 |
cuDNN | 8.9 |
ライブラリ
名称 | バージョン |
---|---|
numpy | 1.26.4 |
Tensorflow | 2.15.0 |
keras | 2.15.0 |
scikit-lean | 1.5.2 |
pandas | 2.2.3 |
Matplotlib | 3.9.1 |
seaborn | 0.13.2 |
2.3. 分析に使用するデータ
仕事で人事データ触っているといっても、それを使うわけにはいかないし、困ったなーと思っていたら、いいサイトを見つけました!!!
中学生の頃、受験勉強そっちのけで、ファミコンで没頭していたのが KOEI の「三國志」。
今は14版まで出ているロングセラーのシミュレーションゲームです。
その13版のWIKIサイト(https://sangokushi13wiki.wiki.fc2.com/)
にて公開いただいている「武将データ」を今回の学習データにします。
2.4. 分析の流れ
以下の流れで進みます。
(1) データ集め
(2) データの確認
(3) データのラベルづけ
(4) 教師なし学習(クラスタリング)を用いた分類
クラスタリングを用いてデータの散らばり方から分類を行ってみます。
(5) 教師あり学習を用いた分類
決定木、ランダムフォレスト、ニューラルネットワークを用いて予測モデルを作成します。
3. 分析準備
3.1. データ集め
まずは、三国志13攻略wikiサイト(https://sangokushi13wiki.wiki.fc2.com/wiki/%E6%AD%A6%E5%B0%86%E4%B8%80%E8%A6%A7)
から、武将データをDataFrameに落としてきます。
ここで師匠より伝授された、秘術?スクレイピングを披露します。
import pandas as pd
url = 'https://sangokushi13wiki.wiki.fc2.com/wiki/%E6%AD%A6%E5%B0%86%E4%B8%80%E8%A6%A7'
dfs = pd.read_html(url)
print(dfs[1].head())
# html内には14個の<table があり。
# うち1つ目dfs[0]は、アカサタナ... dfs[13]はいいね シェアする
output_df = pd.concat(dfs[1:])
output_df.reset_index(drop=True,inplace=True)
output_df.to_csv("/home/j/SangokushiVocationalAptitudeAnalysis/SangokushiData.csv",index=None)
read_htmlがミソで、DataFramesにWEBサイト上の tables を格納することができます。取得したデータは一旦 CSV(SangokushiData.csv)に格納します。
なお、話が前後して申し訳ありませんが、師匠よりスクレイピングする前には2つの事に気をつけるように言われています。
- ルートにある、robots.txt の記述を確認すること
- 利用規約の確認
今回参照したサイトは上記の確認の結果、スクレイピングを禁止していないので、スクレイピングを実施しました。
3.2. データの確認
まず、データの確認をします。
一旦CSVに落としていますが、pandas の DataFrame に展開します。
import pandas as pd
import pandas as pd
df_origin = pd.read_csv('/home/j/SangokushiVocationalAptitudeAnalysis/SangokushiData.csv')
df_origin.head()
相性 | 名前 | 読み | 性別 | 生年 | 登場 | 没年 | 統率 | 武力 | 知力 | 政治 | 槍兵 | 騎兵 | 弓兵 | 伝授特技 | 重臣特性 | 戦法 | 理想威名 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
62 | 阿会喃 | アカイナン | 男 | 190 | 217 | 225 | 65 | 74 | 26 | 33 | C | B | C | 訓練 | - | 騎兵攻撃陣 | 万夫不当 |
131 | 韋昭 | イショウ | 男 | 204 | 223 | 273 | 16 | 19 | 65 | 73 | C | C | C | 商業 | - | 消沈工作 | 宰相 |
77 | 伊籍 | イセキ | 男 | 162 | 189 | 226 | 29 | 24 | 80 | 86 | C | C | C | 商業 | 商業重視 | 足止 | 高論卓説 |
72 | 尹賞 | インショウ | 男 | 194 | 213 | 260 | 51 | 44 | 62 | 66 | C | B | B | 交渉 | - | 単体挑発 | 宰相 |
38 | 尹大目 | インダイモク | 男 | 211 | 230 | 270 | 4 | 9 | 33 | 51 | C | C | C | 農業 | - | 槍攻強化 | 宰相 |
df_origin.describe()
説明変数として使おうと思っている特徴量「統率」「武力」「知力」「政治」の平均は55〜60台というところでしょうか。
なんじゃ?知力1って?
各能力値のグラフも描いてみます。
import matplotlib.pyplot as plt
import numpy as np
import japanize_matplotlib
df_origin.hist(figsize=(20,15),bins=30)
3.3. データのラベリング
学習データを「武将データ」にする!と張飛のように意気込んでいますが、張飛(≒私)は大事な事を忘れています。
# 張飛は、三国志の主要キャラクターで、血気盛ん・突撃前進する傾向があります。突撃前進した結果、後方の大切な拠点を奪われて、パーティが危機に陥る話があります。
そうです。ラベルがありません! ラベルがなかったら教師あり学習できないじゃん!!
落ち込んでいても仕方がないので、「こういうところ(肝心なところ)が抜けているのが私の良いところ。」と勝手に思い込み、気持ちの切り替えをします。
ロピアで買ってきたティラミス(消費税抜 888円)をヨメはんと食します。美味なり!ヨメはん狂喜!!オススメです。
そして出た結論。
「どういう基準でラベルをつけるか」
まずは、ラベル付けの基本的な考え方を整理します。
(1) なんといっても、人を動かす力が第一。それが理路整然と説得して動かすも良し。力任せで人を率いるのも良し。
(2) 統率力が低くても、頭がいいか、手際がいい人物は役に立つ。実際、組織もある程度以上の規模になると、規律と手続きが必要になり、そのような人物は重宝されるので。
本当はこのような考え方は、昭和企業っぽくてキライなんですけど。。。
基本的な考え方に則り、次のような基準でラベル付けを行いました。
(1) 統率 77 以上、もしくは武力 80 以上を「名将」とする。
(2) 統率 64 以上を「将軍」とする。
(3) 知力 80 以上、もしくは政治 80 以上を「名参謀」とする。
(4) 知力 70 以上、もしくは政治 75 以上を「参謀」とする。
(5) 残りのうち、統率・武力より知力・政治が相対的に高い場合を「文官」とする。
(6) 残りを「武官」とする。
(7) (1)〜(6)の基準を基本とするが、能力値だけでは表せない分類も存在し、若干名、フィーリングでラベル付けしたものもある。
データにラベル「分類」「分類CD」を手作業で付加し、SangokushiData3.csvを作りました。
import pandas as pd
df_labeled = pd.read_csv('/home/j/SangokushiVocationalAptitudeAnalysis/SangokushiData3.csv')
counts = df_labeled['分類CD'].value_counts()
print(counts)
4. 分析
4.1. 教師なし学習(クラスタリング)
まずは、ラベルなしで、特徴量だけで分類するクラスタリングを実施します。
分類しやすさを考慮し、説明変数を「統率」「知力」「武力」「政治」の4つを候補としますが、説明変数間の相関を見てみます。
subdf = df_origin[['統率','知力','武力','政治']]
subdf.corr()
「統率」と「武力」、「知力」と「政治」の間に相関関係が認められますね。
よって、以後は説明変数2つ「統率」「知力」に絞って話を進めます。
X2_np = df_origin[['統率','知力']].to_numpy()
4.1.1. k-means法
クラスタリングの代表的な手法「k-means法」で分類を行います。
(1) エルボー法によるクラスター数の決定
先人に知恵(参考とした記事 https://laid-back-scientist.com/k-means#toc5 )をお借りして、エルボー(Elbow)法にてクラスター数を算出します。
エルボー(Elbow)法について、上記記事より説明を引用します。
記事を参考にクラスター数を求めます。
max_cluster = 15
sum_of_squared_errors = []
for i in range(1, max_cluster):
model = KMeans(n_clusters=i, random_state=0, init='random')
model.fit(X2_np)
sum_of_squared_errors.append(model.inertia_) # 損失関数の値を保存
plt.plot(range(1, max_cluster), sum_of_squared_errors, marker='o')
plt.xlabel('number of clusters')
plt.ylabel('sum of squared errors')
plt.show()
エルボーって、「肘」のことだったんですね。カクっと曲がっている。
いわゆる「エルボー」は、4か5あたりであると思われます。今回は5で進めます。
from sklearn.cluster import KMeans
model = KMeans(n_clusters=5,random_state=0)
model.fit(X2_np)
df_origin['クラスター結果'] = model.labels_
df_origin.head()
データフレームdf_originにクラスター結果付加しました。
以下、可視化します。
# model.cluster_centers_ でクラスター重心の座標を取得できる
df_origin_cluster_centers = pd.DataFrame(model.cluster_centers_)
df_origin_cluster_centers.columns = ['統率', '知力']
# 散布図と重心をプロット
import pandas as pd
sns.scatterplot(data=df_origin, x='統率', y='知力', hue='クラスター結果')
sns.scatterplot(data=df_origin_cluster_centers, x='統率', y='知力', s=200, marker='*', color='gold', linewidth=0.5)
(2) シルエット分析によるクラスタリング
同じk-means法を用いるクラスタリングの他の手法「シルエット分析」を実施してみます。
参考とした記事は、https://laid-back-scientist.com/k-means#toc6 です。
「シルエット分析」についての説明を上記記事より引用します。
シルエット分析では、下記の指標を基にクラスタリングの性能を評価します。
クラスター内のデータ点は密になっているほど良い
各クラスターは離れているほど良い
具体的には、以下の手順で定義されるシルエット係数(silhouette coefficient)でクラスタリングの性能を評価します。
シルエット係数はその定義から、
[−1,1]の区間に収まります。また、全データでシルエット係数を求め平均値をとった際、1に近いほどクラスタリングの性能が良いといえます。シルエット分析の可視化では以下のルールに則ります。
所属クラスタ番号でソート
同じクラスタ内ではシルエット係数の値でソート
横軸をシルエット係数、縦軸をクラスター番号としてプロットすることでシルエット分析の可視化をおこないます。
記事で紹介されているコードにて、クラスタ数ごとにグラフ化します。
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
import japanize_matplotlib
from sklearn.metrics import silhouette_samples
max_cluster = 10
def show_silhouette(fitted_model):
cluster_labels = np.unique(fitted_model.labels_)
num_clusters = cluster_labels.shape[0]
silhouette_vals = silhouette_samples(X2_np, fitted_model.labels_) # シルエット係数の計算
# 可視化
y_ax_lower, y_ax_upper = 0, 0
y_ticks = []
for idx, cls in enumerate(cluster_labels):
cls_silhouette_vals = silhouette_vals[fitted_model.labels_==cls]
cls_silhouette_vals.sort()
y_ax_upper += len(cls_silhouette_vals)
cmap = cm.get_cmap("Spectral")
rgba = list(cmap(idx/num_clusters)) # rgbaの配列
rgba[-1] = 0.7 # alpha値を0.7にする
plt.barh(
y=range(y_ax_lower, y_ax_upper),
width=cls_silhouette_vals,
height=1.0,
edgecolor='none',
color=rgba)
y_ticks.append((y_ax_lower + y_ax_upper) / 2.0)
y_ax_lower += len(cls_silhouette_vals)
silhouette_avg = np.mean(silhouette_vals)
plt.axvline(silhouette_avg, color='orangered', linestyle='--')
plt.xlabel('silhouette coefficient')
plt.ylabel('cluster')
plt.yticks(y_ticks, cluster_labels + 1)
plt.show()
print(silhouette_avg)
for i in range(2,max_cluster):
model = KMeans(n_clusters=i, random_state=0, init='random')
model.fit(X2_np)
show_silhouette(model)
[クラスタ数2]
[クラスタ数3]
[クラスタ数4]
[クラスタ数5]
[クラスタ数6]
クラスター数2〜10を試してみました。
【結果】
シルエット係数の平均値(グラフ中の赤点線)は
クラスター数 = 3(0.4713608314478208)が一番高くなりました。
クラスター数 = 3 で散布図を描いてみます。
model = KMeans(n_clusters=3, random_state=0, init='random')
model.fit(X2_np)
df_origin['クラスター結果'] = model.labels_
# model.cluster_centers_ でクラスター重心の座標を取得できる
df_origin_cluster_centers = pd.DataFrame(model.cluster_centers_)
df_origin_cluster_centers.columns = ['統率', '知力']
print(df_origin_cluster_centers)
# 散布図と重心をプロット
import pandas as pd
import seaborn as sns
import japanize_matplotlib
sns.scatterplot(data=df_origin, x='統率', y='知力', hue='クラスター結果')
sns.scatterplot(data=df_origin_cluster_centers, x='統率', y='知力', s=200, marker='*', color='gold', linewidth=0.5)
4.1.2. 混合ガウス分布
(1) 情報量基準
k-means法の他に、混合ガウス分布の情報量基準を使ったクラスタリング手法があります。
先人の知恵をお借りして、クラスタ数を求めます。
参考とした記事は https://emotionexplorer.blog.fc2.com/blog-entry-391.html です。
Scikit-Learn ガウス混合モデル(GaussianMixture)では、ベイズ情報量基準(BIC)と赤池情報量基準(AIC)をサポートしています。
それぞれは下記式であらわされます。式
BIC = log(m)p−2log(L)
AIC=2p−2log(L)m : インスタンス数
p : モデルの学習パラメータ
L : モデルの尤度関数詳細については触れませんが、この情報量基準を最小化する方向でクラスタ数を求めていけばよいわけです。
ちなみに赤池情報量基準の赤池という日本語が気になったので調べたら、元統計数理研究所所長の赤池弘次さんが1971年に考案した方法なのだそうですね。
つまり、2つの情報量基準が最小となるクラスタ数を求めればよいのです。
上記記事で紹介されているコードを用いて、本件のクラスタ数を求めます。
from matplotlib import pyplot as plt
import japanize_matplotlib
import numpy as np
from sklearn.mixture import GaussianMixture
# クラスタ数 1~10をフィティングして試す
rangeary = np.arange(1, 11)
gms_per_k = []
for k in rangeary:
gm = GaussianMixture(n_components=k, covariance_type='full')
gm.fit(X2)
gms_per_k.append(gm)
bics = [model.bic(X2) for model in gms_per_k] # 入力Xの現在モデルのベイズ情報量基準
aics = [model.aic(X2) for model in gms_per_k] # 入力Xの現在モデルの赤池情報量基準
# コンソール表示
for k, b, a in zip(rangeary, bics, aics):
print('{:2d}: {:.2f} {:.2f}'.format(k, b, a))
# シルエットスコアグラフ
plt.title('クラスタ数の選択 情報量基準グラフ')
plt.plot(rangeary, bics, "bo-", color="blue", label="BIC")
plt.plot(rangeary, aics, "go--", color="red", label="AIC")
plt.xlabel("k", fontsize=14)
plt.ylabel("情報量基準", fontsize=14)
plt.axis([1, 9.5, np.min(aics) - 50, np.max(aics) + 50])
plt.annotate('最小', #
xy=(4, bics[3]),
xytext=(0.35, 0.6),
textcoords='figure fraction',
fontsize=14,
arrowprops=dict(facecolor='black', shrink=0.1))
plt.legend()
plt.show()
クラスタ数=4 でBICが最小値を取りました。AICも最小値と言ってよいと思われます。
クラスター数 = 4 で散布図を描いてみます。
X2 = df_origin[['統率','知力']]
from sklearn.mixture import GaussianMixture as gm
model = gm(n_components=4,covariance_type='full')
model.fit(X2)
X2["cluster"] = model.predict(X2)
「統率」をx軸、「知力」をy軸として可視化してみます。
import seaborn as sns
import japanize_matplotlib
sns.lmplot(x= "統率", y= "知力", hue='cluster', data=X2, fit_reg=False)
4.2. 教師あり学習
「統率」「武力」「知力」「政治」の4特徴量を説明変数として分析します。
(1) 決定木(Decision Tree)
X4 = df[['統率','武力','知力','政治']]
y = df[['分類CD']].to_numpy()
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X4,y,test_size=0.25,random_state=0)
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(max_depth=6,max_leaf_nodes=10,min_samples_leaf=10,random_state=0)
model.fit(X_train,y_train)
y_pred = model.predict(X_test)
from sklearn.metrics import classification_report
print(classification_report(y_test,y_pred))
accuracy = 0.94
なかなかの予測モデルができたのではないでしょうか。
散布図を描いてみます。説明変数4つなので、そのままでは散布図を描けないので、PCAを利用して、主成分を2つ(PCA1、PCA2)に絞り描いてみます。
# 予測結果を日本語変換
henkan = {1:"名将",2:"将軍",3:"名参謀",4:"参謀",5:"文官",6:"武官"}
y_pred_name = []
for i in range(len(y_pred)):
y_pred_name.append(henkan[y_pred[i]])
# PCAを利用して2軸抽出
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_test)
pca_transformed = pca.transform(X_test)
# データマージ
X_test["PCA1"] = pca_transformed[:, 0]
X_test["PCA2"] = pca_transformed[:, 1]
X_test["cluster"] = y_pred
X_test["bunrui"] = y_pred_name
import seaborn as sns
sns.lmplot(x= "PCA1", y= "PCA2", hue='bunrui', data=X_test, fit_reg=False)
PCA1は「武力」がプラス方向に大きく作用し、「知力」「政治」がマイナス方向に大きく作用する指標のようです。
PCA2は「統率」がプラス方向に大きく作用し、他の特徴量はプラス方向に中程度作用する指標のようです。
(2) ランダムフォレスト(Random Forest)
次に、同じように特徴量4つで Random Forest を使い予測モデルを作成します。
X4 = df_labeled[['統率','武力','知力','政治']]
y = df_labeled[['分類CD']].to_numpy()
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X4,y,test_size=0.25,random_state=0)
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators= 10, max_depth=6, max_features= 6, random_state=0)
model.fit(X_train,y_train)
y_pred = model.predict(X_test)
from sklearn.metrics import classification_report
print(classification_report(y_test,y_pred))
accuracy=0.96
好成績だと思います。
可視化します。
henkan = {1:"名将",2:"将軍",3:"名参謀",4:"参謀",5:"文官",6:"武官"}
y_pred_name = []
for i in range(len(y_pred)):
y_pred_name.append(henkan[y_pred[i]])
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_test)
pca_transformed = pca.transform(X_test)
# データマージ
X_test["PCA1"] = pca_transformed[:, 0]
X_test["PCA2"] = pca_transformed[:, 1]
X_test["cluster"] = y_pred
X_test["bunrui"] = y_pred_name
X_test.head()
from matplotlib import pyplot as plt
import japanize_matplotlib
import seaborn as sns
sns.lmplot(x= "PCA1", y= "PCA2", hue='bunrui', data=X_test, fit_reg=False)
(3) ニューラルネットワーク(Neaural Network)
ご紹介するまでもなく、今や世界を席巻するAI(Artificial Interigence 人工知能)の発展にブレークスルーを起こした ニューラルネットワークも分類に使えるとのことで、https://free.kikagaku.ai/tutorial/basic_of_deep_learning/learn/tensorflow_classification
を参考に予測モデルを作成します。
まずは簡単にニューラルネットワークを紹介します。
ニューラルネットワークとは、人間の神経細胞の構造を模倣して作られたもので、神経細胞に見立てた「ノード」と、神経同士のつながりを示す「エッジ」で構成されています。
出典
https://aws.amazon.com/jp/what-is/neural-network/#:~:text=%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AF%E3%80%81%E4%BA%BA%E9%96%93%E3%81%AE,%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%AD%E3%83%B3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%BE%E3%81%99%E3%80%82
まずはライブラリを読み込みます。
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from keras.utils import to_categorical
データを用意します。
X4 = df_labeled[['統率','武力','知力','政治']]
y = df_labeled[['分類CD']]
y_hot = to_categorical(y)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X4, y_hot, test_size=0.25,random_state=0, stratify=y_hot)
標準化しておかないと学習がうまく進みません。
from sklearn.preprocessing import StandardScaler
ms = StandardScaler()
X_train_std = ms.fit_transform(X_train)
X_test_std = ms.transform(X_test)
import os, random
def reset_seed(seed=0):
os.environ['PYTHONHASHSEED'] = '0'
random.seed(seed) # random関数のシードを固定
np.random.seed(seed) # numpyのシードを固定
tf.random.set_seed(seed) # tensorflowのシードを固定
from tensorflow.keras import models, layers
reset_seed(0)
model = models.Sequential([
layers.Dense(units=32, activation='relu', input_shape=(4,)),
layers.Dense(units=16, activation='relu'),
layers.Dense(units=16, activation='relu'),
layers.Dense(units=7, activation='softmax')
])
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(X_train_std, y_train,
batch_size=4,
epochs=42,
validation_data=(X_test_std, y_test))
result = pd.DataFrame(history.history)
y_pred = model.predict(X_test_std)
y_pred_int = np.argmax(y_pred,axis=1)
henkan = {1:"名将",2:"将軍",3:"名参謀",4:"参謀",5:"文官",6:"武官"}
y_pred_name = []
for i in range(len(y_pred_int)):
y_pred_name.append(henkan[y_pred_int[i]])
y_test_int = np.argmax(y_test,axis=1)
from sklearn.metrics import classification_report
print(classification_report(y_test_int,y_pred_int))
accuracy= 0.91 となりました。
可視化します。
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_test)
pca_transformed = pca.transform(X_test)
X_test["PCA1"] = pca_transformed[:, 0]
X_test["PCA2"] = pca_transformed[:, 1]
X_test["y_pred"] = y_pred_int
X_test["y_pred_name"] = y_pred_name
from matplotlib import pyplot as plt
import japanize_matplotlib
import seaborn as sns
sns.lmplot(x= "PCA1", y= "PCA2", hue='y_pred_name', data=X_test, fit_reg=False)
5. 結論
5.1. 結果と考察
5.1.1. 教師なし学習
(1) k-means法(エルボー法)
【考察】
クラスタの特徴を書き出し、「分類名」を付けてみます。
クラスタ | 特徴 | 代表人物 | 分類名 |
---|---|---|---|
0 | 「統率」も「知力」も中位 | 楽進、関興、曹休 | 万能キャラ |
1 | 「統率」も「知力」も低い | 蔡和、蔡中、韓玄 | モブキャラ |
2 | 「統率」が低く「知力」が高い | 王允、孔融、張昭 | 頭脳派キャラ |
3 | 「統率」も「知力」も高い | 関羽、司馬懿、曹操 | ヒーローキャラ |
4 | 「統率」が高く「知力」が低い | 顔良、張飛、呂布 | 武闘派キャラ |
万能キャラ ・・・優秀だけど、一歩引いてサポートするのが得意なタイプ
モブキャラ ・・・他のキャラの引き立て役を演ずるタイプ
頭脳派キャラ ・・・知力で勝負するのが得意なタイプ
ヒーローキャラ・・・能力が高く、チームの中心的なタイプ
武闘派キャラ ・・・武力で勝負するのが得意なタイプ
(2) k-means法(シルエット分析)
【結果】
クラスター数は、3と出ました
【考察】
クラスタの特徴を書き出し、「分類名」を付けてみます。
クラスタ | 特徴 | 代表人物 | 分類名 |
---|---|---|---|
0 | 「統率」が低く「知力」が高い | 王允、孔融、張昭 | 頭脳派キャラ |
1 | 「統率」も「知力」も高い | 関羽、司馬懿、曹操 | ヒーローキャラ |
2 | 「知力」が低い | 蔡和、韓玄、張飛 | モブキャラ |
「モブキャラ」は「知力」が低い人物で構成されているが、一部、卓越した「統率」力を持っている人もいます。
卓越した「統率力」を持っている人と、モブキャラを混ぜるのはちょっと分類が雑すぎる気がしますねぇ。
(3) 混合ガウス分布の情報量基準
【考察】
クラスタの特徴を書き出し、「分類名」を付けてみます。
クラスタ | 特徴 | 代表人物 | 分類名 |
---|---|---|---|
0 | 「知力」が低い | 蔡和、韓玄、張飛 | モブキャラ |
1 | 「統率」が平均より少し高い | 楽進、荀彧、曹休 | 万能キャラ |
2 | 「統率」も「知力」も高い | 関羽、司馬懿、曹操 | ヒーローキャラ |
3 | 「統率」が低く「知力」が高い | 王允、孔融、張昭 | 頭脳派キャラ |
「万能キャラ」に魏国の名参謀、知力96の「荀彧」が分類されています。
「万能キャラ」には「知力」が高い人、「モブキャラ」には「統率」が高い人が混じっています。先程のシルエット分析同様、ちょっと分類が粗い気がしますねぇ。
教師なし学習3つを行ったうえでの結論
教師なし学習を行った上での結論としては、クラスタ数=5 がベストと考えます。
5.1.2. 教師あり学習
正解ラベルデータの散布図
まずは、正解ラベルデータの散布図を記載します。
(1) 決定木(Decision Tree)
- 正答率(accuracy)は 0.94 でした
ラベル | precision | recall | f1-score | support |
---|---|---|---|---|
1 | 0.96 | 0.89 | 0.93 | 28 |
2 | 1.00 | 1.00 | 1.00 | 56 |
3 | 0.89 | 0.97 | 0.93 | 34 |
4 | 0.96 | 0.84 | 0.90 | 32 |
5 | 0.89 | 0.97 | 0.93 | 33 |
6 | 0.91 | 0.91 | 0.91 | 32 |
accuracy | 0.94 | 215 | ||
macro avg | 0.94 | 0.93 | 0.93 | 215 |
weighted avg | 0.94 | 0.94 | 0.94 | 215 |
【考察】
正答率が非常に高いので、テストデータの散布図と、決定木による分類の散布図の差を比べようとしても、分かりづらいのですが、例えば、一番上左端の一点(赤点線で囲んでおります)の分類が、正解ラベル⇒名参謀、予測データ⇒名将 となっており、異なっています。
このデータは、あの天才軍師 諸葛孔明 様です。
氏名 | 統率 | 武力 | 知力 | 政治 | 予測データ | 正解ラベル |
---|---|---|---|---|---|---|
諸葛孔明 | 98 | 38 | 100 | 95 | 1: 名将 | 3: 名参謀 |
私の決定木モデルは、孔明 様を何故、「名将」と予測してしまったのだろう?
まずは「名将の平均値」、「名参謀の平均値」と「諸葛孔明のパラメータ値」を比較するレーダーチャートを描いてみます。
⇒ 諸葛孔明様のチャート(緑線)と、他の2つのチャートを比べてみても形状から類似性を見つけるのは難しそうです。
そこで、諸葛孔明様のデータと、「名参謀の平均値」を比べてみます。
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
諸葛孔明 | 98 | 38 | 100 | 95 |
「名参謀」の平均値 | 57.747475 | 36.676768 | 83.454545 | 83.232323 |
差 | 40.252525 | 1.323232 | 16.545455 | 11.767677 |
⇒ 諸葛孔明様は、「統率」の平均値の差が、40超もあり、「名参謀」と判断しずらい状況です。
所謂、外れ値です。
(2) ランダムフォレスト(Random Forest)
- 正答率(accuracy)は 0.96 でした
ラベル | precision | recall | f1-score | support |
---|---|---|---|---|
1 | 1.00 | 0.89 | 0.94 | 28 |
2 | 1.00 | 1.00 | 1.00 | 56 |
3 | 0.92 | 1.00 | 0.96 | 34 |
4 | 0.97 | 0.94 | 0.95 | 32 |
5 | 0.91 | 0.97 | 0.94 | 33 |
6 | 0.97 | 0.94 | 0.95 | 32 |
accuracy | 0.96 | 215 | ||
macro avg | 0.96 | 0.96 | 0.96 | 215 |
weighted avg | 0.96 | 0.96 | 0.96 | 215 |
【考察】
さきほどの、諸葛孔明 様が、正解ラベルと同じ、「名参謀」と予測されています。
ラベル3のprecisionが0.92に上昇しています。
また、他のラベルのprecision、recallも上昇しており、f1-scoreも0.95近傍に上昇しています。
RandomForestは今回の分析データとすごく相性がいいようですね。
ちなみに、エラーとなった8テストデータのうち、2つは次のとおりです。
氏名 | 統率 | 武力 | 知力 | 政治 | 予測データ | 正解ラベル |
---|---|---|---|---|---|---|
満寵 | 85 | 64 | 82 | 84 | 3: 名参謀 | 1: 名将 |
陳宮 | 83 | 55 | 89 | 83 | 3: 名参謀 | 1: 名将 |
「統率」「知力」がともに高いケースです。
先程の諸葛孔明同様、名将の平均値、名参謀の平均値との乖離を見てみます。
【満寵】
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
満寵の能力値(A) | 85 | 64 | 82 | 84 |
「名将」の平均値(B) | 85.858586 | 78.89899 | 69.575758 | 58.414141 |
差(A-B) | -0.858586 | -14.89899 | 12.424242 | 25.585859 |
「名参謀」の平均値(C) | 57.747475 | 36.676768 | 83.454545 | 83.232323 |
差(A-C) | 27.252525 | 27.323232 | -1.454545 | 0.767677 |
「統率」は名将の集団寄りですが、「知力」「政治」が名参謀の集団に極めて近く、名参謀と予測されたようです。
【陳宮】
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
陳宮の能力値(A) | 83 | 55 | 89 | 83 |
「名将」の平均値(B) | 85.858586 | 78.89899 | 69.575758 | 58.414141 |
差(A-B) | -2.858586 | -23.89899 | 19.424242 | 24.585859 |
「名参謀」の平均値(C) | 57.747475 | 36.676768 | 83.454545 | 83.232323 |
差(A-C) | 25.252525 | 18.323232 | 5.545455 | -0.232323 |
「統率」は名将の集団寄りですが、「知力」「政治」が名参謀の集団寄りということで、名参謀と予測されたようです。
先程の諸葛孔明同様、「統率」「知力」がともに高いときに予測が難しいことがわかってきました。
(3) ニューラルネットワーク(Neaural Network)
【結果】
- 正答率(accuracy)は 0.91 でした
ラベル | precision | recall | f1-score | support |
---|---|---|---|---|
1 | 0.90 | 0.88 | 0.89 | 32 |
2 | 0.91 | 1.00 | 0.96 | 43 |
3 | 0.86 | 0.94 | 0.90 | 33 |
4 | 0.86 | 0.88 | 0.87 | 42 |
5 | 1.00 | 0.92 | 0.96 | 37 |
6 | 0.96 | 0.82 | 0.88 | 28 |
accuracy | 0.91 | 215 | ||
macro avg | 0.92 | 0.91 | 0.91 | 215 |
weighted avg | 0.91 | 0.91 | 0.91 | 215 |
【考察】
相当、チューニングしましたが、このaccuracyが限界でした。
ラベル1〜ラベル4のprecisionが低い。ラベル1とラベル4のrecallが低い。
エラーデータ(19件)について分析します。
正解ラベル | |||||||
---|---|---|---|---|---|---|---|
名 将 | 将 軍 | 名参謀 | 参 謀 | 文 官 | 武 官 | ||
予測ラベル | 名 将 | 0 | 0 | 1 ※1 | 2 | 0 | 0 |
将 軍 | 2 ※2 | 0 | 0 | 0 | 2 | 0 | |
名参謀 | 2 ※1 | 0 | 0 | 3 ※2 | 0 | 0 | |
参 謀 | 0 | 0 | 1 ※2 | 0 | 0 | 5 | |
文 官 | 0 | 0 | 0 | 0 | 0 | 0 | |
武 官 | 0 | 0 | 0 | 0 | 1 | 0 |
【予測ラベルの散布図】
エラー原因として、次の2つを考えました。
①※1のパターン (青の点線で図示)
対象者は以下のとおりで、「統率」「知力」がともに高いです。
統率 | 武力 | 知力 | 政治 | 予測データ | 正解ラベル | |
---|---|---|---|---|---|---|
蘇秦 | 90 | 47 | 97 | 90 | 3:名参謀 | 1:名将 |
張角 | 87 | 25 | 86 | 80 | 3:名参謀 | 1:名将 |
徐庶 | 87 | 64 | 93 | 80 | 1:名将 | 3:名参謀 |
ひとりずつ見てみます。
【蘇秦】
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
蘇秦の能力値(A) | 90 | 47 | 97 | 90 |
「名将」の平均値(B) | 85.858586 | 78.89899 | 69.575758 | 58.414141 |
差(A-B) | 4.141414 | -31.89899 | 27.424242 | 31.585859 |
「名将」の平均値と、「知力」「政治」が大きく乖離しているので、「名将」と予測されなかったようです。
【張角】
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
張角の能力値(A) | 87 | 25 | 86 | 80 |
「名将」の平均値(B) | 85.858586 | 78.89899 | 69.575758 | 58.414141 |
差(A-B) | 1.141414 | -53.89899 | 16.424242 | 21.585859 |
「名将」の平均値と、「知力」「政治」が大きく乖離しているので、「名将」と予測されなかったようです。
【徐庶】
統率 | 武力 | 知力 | 政治 | |
---|---|---|---|---|
徐庶の能力値(A) | 87 | 64 | 93 | 80 |
「名参謀」の平均値(B) | 57.747475 | 36.676768 | 83.454545 | 83.232323 |
差(A-B) | 29.252525 | 27.323232 | 9.545455 | -3.232323 |
「名参謀」の平均値と、「統率」「武力」が大きく乖離しているので、「名参謀」と予測されなかったようです。
3人とも先出の諸葛孔明同様、外れ値として扱ったほうがいいようです。
②※2のパターン (赤の実線で図示)
「名将」と「将軍」、「名参謀」と「参謀」といった似た性質を持った集団の境界でエラーが起こっています。
散布図を見比べて見ると、これらの線引は難しいと思われます。
むしろ、ラベルの統合(「名将」+「将軍」⇒「将軍」、「名参謀」+「参謀」⇒「参謀」)を行ったほうが良いと思われます。
教師あり学習3つを行ったうえでの結論
RandomForestが一番性能に優れました。
しかしながら、ニューラルネットワークは改善すべき課題も見え、より精度向上が見込めると考えています。
5.2. 課題
前述のとおり、ニューラルネットワークの精度向上を図り、より良いモデルの構築を目指します。
【対策1】 外れ値(「統率」も「知力」も高い)を除外する。
【対策2】 ラベルの整理統合を行う。
また、各ラベルの疑似データを作成し、データのかさ増しを行うと、モデルの精度向上に有効であると思われます。
5.3. 謝辞
最初に、この世界に機械学習という人類史に残る大きな財産を生み出し、発展させ、しかも誰でも志ざせばそれを学ぶことができるインフラを提供してくださっている数多の英才たちに感謝します。
そして、「三国志」という、壮大な物語をこの世に残してくださった、古代中国の数多の文人たちに感謝します。
次に、その壮大な物語の息吹を現代へと蘇らせ、そして沢山の人々を魅了するゲームを生み出した株式会社コーエーテクモゲームスに感謝します。
そして師匠の木地山航輝氏に最大の謝意を贈りたいです。
師匠の助けなくば、かなり怪しい分析記事になってしまうところでした。本当にありがとうございます。
5.4. 参考文献
武将一覧
https://sangokushi13wiki.wiki.fc2.com/wiki/%E6%AD%A6%E5%B0%86%E4%B8%80%E8%A6%A7
WEBテーブルのスクレイピング
https://note.nkmk.me/python-pandas-web-html-table-scraping/
pandasでcsvファイルの書き込み
https://note.nkmk.me/python-pandas-to-csv/
pandasで散布図行列(Scatter matrix)を描く
https://qiita.com/i_makoto1002/items/c94d1028c19cc2eddb48
エルボー法について
https://laid-back-scientist.com/k-means#toc5
シルエット分析について
https://laid-back-scientist.com/k-means#toc6
混合ガウス分布を使ったクラスタ数の決定について
https://emotionexplorer.blog.fc2.com/blog-entry-391.html
ニューラルネットワークを使用した分類について
https://free.kikagaku.ai/tutorial/basic_of_deep_learning/learn/tensorflow_classification