目的
上海というスマホゲームにはログインボーナスがあり、8個並んだ配列から1個選ぶと、ダイヤかハートがもらえる。(細かくは記載しないが)ダイヤのほうが価値があるので、ダイヤを当てたい。
⇒TouchToStart⇒
⇒SelectOne⇒
⇒OK⇒
これまでの経験上、どうも、右下の端が、よくダイヤ(x15)がでるようなので、選択位置と取得できるマークには、偏りがあると考えた。そこで、選択位置と取得できるマークには関連があるか、独立性の検定で確かめてみたい。
なお、マーク(と数)については、目視確認ではなく、機械学習により分類・認識する。
ゲーム「上海」とは、「積み上げられた麻雀牌の山から、ある一定のルールに従って牌を取り除いていく、「Mahjong solitaire」とも呼ばれるソリティアの一種」Wikipediaより引用、ただし、ゲーム内容とこの記事は全く関係がない。
独立性の検定とは、「2つの変数に対する2つの観察(2x2分割表で表される)が互いに独立かどうかを検定する。例えば、「別の地域の人々について、選挙である候補を支持する頻度が違う」かどうかを検定する方法」Wikipediaより引用。
今回は、選択位置と取得できるマーク数という2変数の独立性を検定する。
補足
検定する上で、適合度の検定なのか、独立性の検定なのか、判断に迷いました。マークの出方が同様に確からしいかを検定するので、適合度の検定があっているように思いましたが、選択した位置という変数もあるので、独立性の検定にしました。参考:適合度検定Wikipediaより
データの取得
右下の端が、よくダイヤ(x15)がでるということも確かめてみたい(ダイヤを当てたい)ので、まずは、右下の端を選択し続けること50回。そのスクリーンショットを保存し、データを収集した。
ログインボーナスは1日1回なので、毎日できたとしても50日かかったことになる。データ分析は、データ収集に時間と手間がかかることをあらためて実感できた。
画像認識
50個であれば、1個1個手作業で集計することもそれほど大変ではないが、技術的興味から、画像認識で選択位置毎のマークを特定したい。マーク(ダイヤ、ハート)だけでなく、その数もあるので、数字認識はCNNで認識する。マークは簡単そうなので、ニューラルネットワークは使用せず、決定木(DecisionTree) で分類する。
教師データの切り出し
教師データを切り出す必要があるので、マスターファイルに1ファイルずつ、選択位置とマークを目視確認して記載する。それを読み込むと、以下のようになる。
- 画像左から右にx0,x1,x2,x3、上から下にy0,y1とし(例:右下はy1_x3)、そのラベルを記載する。
- ラベルはダイヤをD、ハートをH、それに続けて数字とした。
- 選択した位置からは牌が消えてブランクとなるので、BLというラベルとした。
- 10画像分のラベルを作成したので、10枚x8個=80個の教師データが作成される。
# 教師データのラベリングしたマスターファイルを作成し読み込む
import pandas as pd
df_master = pd.read_csv('./data/master.csv')
df_master
filename y0_x0 y0_x1 y0_x2 y0_x3 y1_x0 y1_x1 y1_x2 y1_x3
0 IMG_2465.PNG D10 D15 H2 H3 D5 H1 H1 BL
1 IMG_2467.PNG H3 H1 D15 H2 H1 H1 D10 BL
2 IMG_2475.PNG D5 H1 H1 H2 D10 H1 H3 BL
3 IMG_2482.PNG H3 H1 D10 H1 H1 D5 H2 BL
4 IMG_3295.PNG H3 D15 H2 H1 D5 D10 H1 BL
5 IMG_3299.PNG H1 H1 D15 H2 H3 D10 H1 BL
6 IMG_3308.PNG D10 H1 H1 H3 D5 D15 H1 BL
7 IMG_3311.PNG D10 D5 H1 H3 H1 H2 D15 BL
8 IMG_3316.PNG H1 H1 H1 H2 H3 D5 D15 BL
9 IMG_3320.PNG D10 H1 H1 H2 H1 D5 D15 BL
選択位置の座標については、マークと数字でそれぞれ設定し、扱いやすいようにデータフレーム化する。8か所の位置についてそれぞれ、X方向とY方向の開始座標と終了座標が取得できるようになった。
# 切り出す座標、サイズを設定(マーク)
mark_x_pos = [164,284,404,524] # X方向開始座標(4個)
mark_y_pos = [234,394] # Y方向開始座標(2個)
mark_x_size = 56 # Xサイズ
mark_y_size = 56 # Yサイズ
# 切り出す座標、サイズを設定(数字)
number_x_pos = [198,318,438,558] # X方向開始座標(4個)
number_y_pos = [285,445] # Y方向開始座標(2個)
number_x_size = 32 # Xサイズ
number_y_size = 32 # Yサイズ
# このままでは扱いにくいので、8個の矩形をデータフレーム化する
def get_pos_list(x_pos,y_pos,x_size,y_size):
pos_list = []
for y in range(0,2):
for x in range(0,4):
start_x = x_pos[x]
end_x = start_x+x_size
start_y = y_pos[y]
end_y = start_y+y_size
pos_list.append([x,y,start_x,end_x,start_y,end_y])
df_pos_list = pd.DataFrame(pos_list,columns=['x','y','start_x','end_x','start_y','end_y'])
df_pos_list.set_index(['x','y'],inplace=True)
return df_pos_list
mark_pos_list = get_pos_list(mark_x_pos,mark_y_pos,mark_x_size,mark_y_size)
number_pos_list = get_pos_list(number_x_pos,number_y_pos,number_x_size,number_y_size)
print(mark_pos_list)
start_x end_x start_y end_y
x y
0 0 164 220 234 290
1 0 284 340 234 290
2 0 404 460 234 290
3 0 524 580 234 290
0 1 164 220 394 450
1 1 284 340 394 450
2 1 404 460 394 450
3 1 524 580 394 450
正しく設定できるか確認するために、選択位置矩形を表示してみる。(マークは赤枠、数字は青枠)
import numpy as np
from PIL import Image, ImageDraw
from matplotlib import pyplot as plt
# 1枚読み込んで、表示してみる
filename_png = 'data/' + df_master.iloc[0,0]
img = Image.open(filename_png)
draw = ImageDraw.Draw(img)
# マーク(赤枠)
for index, pos in mark_pos_list.iterrows():
draw.rectangle((pos.start_x, pos.start_y, pos.end_x, pos.end_y), outline=(255, 0, 0), width =10)
# 数字(青枠)
for index, pos in number_pos_list.iterrows():
draw.rectangle((pos.start_x, pos.start_y, pos.end_x, pos.end_y), outline=(0, 0, 255), width =10)
np_img = np.asarray(img)
plt.imshow(np_img)
plt.axis('off')
plt.show()
教師データのラベリング
全体(マーク+数字)のラベル付け、及び、マークと数字をそれぞれ認識するので、それぞれのラベル付けを行う。
# 全体のラベル
label_list = {'D15':0,'D10':1,'D5':2,'H3':3,'H2':4,'H1':5,'BL':6}
# マークのラベル
mark_label_list = {'D':0,'H':1,'B':2}
# 数字のラベル
number_label_list = {'15':0, '10':1, '5':2, '3':3, '2':4, '1':5,'0':6}
# キーから値を取得
def get_keys_from_value(val,list=label_list):
return [k for k, v in list.items() if v == val]
# キーのリストを取得
def get_key_list(list):
return [key for key in list]
# 全体ラベルからマークラベルの取得
def get_mark_label(label):
return label[0] # ラベルの1文字目(D/H/B)
# 全体ラベルから数字ラベルの取得
def get_number_label(label):
if label=='BL':
return '0'
return label[1:]
前処理
設定した選択位置の座標毎に教師データを切り出す。画像認識するので、[0,1]に正規化し、RGBの順番を入れ替えて、numpy array として戻すようにする。
import os
# マーク/数字部分の切り出し
def cut_image(img,x,y,pos,label=None):
img_cut = img.crop((pos.start_x, pos.start_y, pos.end_x, pos.end_y))
# ファイル保存(デバッグ用)
if label:
dirname_out = 'cut/' + label
if not os.path.isdir(dirname_out):
os.mkdir(dirname_out)
filename_out = dirname_out + '/y' + str(y) + '_x' + str(x) + '_' + filename
img_cut.save(filename_out, quality=100)
# np array に変換し、[0,255]諧調 を [0,1]諧調に変換
np_img = np.array(img_cut,dtype=float)
np_img /= 255
# 横,縦,RGB を RGB,横,縦 に変換
size_x = np_img.shape[0]
size_y = np_img.shape[1]
image_r = np_img[:,:,0]
image_r = image_r.reshape( 1, size_x, size_y)
image_g = np_img[:,:,1]
image_g = image_g.reshape( 1, size_x, size_y)
image_b = np_img[:,:,2]
image_b = image_b.reshape( 1, size_x, size_y)
return np.vstack([image_r,image_g,image_b])
切り出した結果の例を図示する。
【マーク】左からダイヤ、ハート、BL
【数字】左からx15、x10、x5、x3、x2、x1、BL
同じマークは全く同じ画像(バイナリレベルで)と考えていたが、よく見ると、キラキラした部分がそれぞれ異なり、バイナリレベルでは同じ画像ではない。
教師データの作成
全ファイルを読み込み、教師データを切り出しリスト化する。
# 教師データのリスト(マーク)
mark_train_img_list = []
mark_y_train = []
# 教師データのリスト(数字)
number_train_img_list = []
number_y_train = []
for index, label_info in df_master.iterrows():
filename = label_info[0]
filename_png = 'data/' + filename
img = Image.open(filename_png)
# マーク
for (x,y), pos in mark_pos_list.iterrows():
label = label_info[1+x+y*4]
mark_label = get_mark_label(label)
mark_cut_image = cut_image(img,x,y,pos,mark_label)
mark_train_img_list.append(mark_cut_image)
mark_y_train.append(mark_label_list[mark_label])
# 数字
for (x,y), pos in number_pos_list.iterrows():
label = label_info[1+x+y*4]
number_label = get_number_label(label)
number_cut_image = cut_image(img,x,y,pos,number_label)
number_train_img_list.append(number_cut_image)
number_y_train.append(number_label_list[number_label])
mark_train_img_list = np.array(mark_train_img_list)
print(mark_train_img_list.shape)
number_train_img_list = np.array(number_train_img_list)
print(number_train_img_list.shape)
(80, 3, 56, 56)
(80, 3, 32, 32)
教師データは、画像数(10)x8個が教師データとなり合計80個、RGBの3色毎に、マークサイズが56x56、数字サイズが32x32で、ということがわかる。
マーク認識
分類の特徴量
ハートは赤、ダイヤは青、BLは黒なので、全く同じ画像ではないものの、簡単に分類できそうである。
RGB値の平均で分類できそうか、RGBの平均を箱ひげ図で可視化する。
# マークのイメージリストから、説明変数xを取得する
def get_mark_x(img_list):
# rgbの平均をとる
mark_mean = [rgb.mean() for img in img_list for rgb in img]
# numpy array に変換し、画像数xRGBに変形
np_mark_mean = np.array(mark_mean)
np_mark_mean = np_mark_mean.reshape(-1,3)
# 平均
mark_x_colums=['r_mean','g_mean','b_mean']
return pd.DataFrame( np_mark_mean, columns=mark_x_colums)
mark_X_train = get_mark_x(mark_train_img_list)
# 各マーク毎に、箱ひげ図を表示
fig = plt.figure(figsize=(15, 3))
index = 1
for i, colums in enumerate(mark_X_train.columns):
plot_list = list()
for key, valule in mark_label_list.items():
mark_X_train_temp = mark_X_train.copy()
mark_X_train_temp['y'] = mark_y_train
mark_X_train_temp = mark_X_train_temp[mark_X_train_temp['y']==valule]
mark_X_train_temp = mark_X_train_temp.iloc[:,i]
plot_list.append(mark_X_train_temp)
ax = fig.add_subplot(1, 3, index)
ax.boxplot(plot_list,labels=mark_label_list.keys())
ax.set_title(colums)
index += 1
左から赤(R)平均、緑(G)平均、青(B)平均のグラフである。
BLは全体的に暗めで簡単に分類可能そうだが、ハートは赤いのでRでダイヤと区別できると思いきや、ダイヤのRも高くRでは区別できない。それでも、GかBでは区別できそうである。
学習用と評価用に分割
from sklearn.model_selection import train_test_split
mark_X_tra, mark_X_val, mark_y_tra, mark_y_val = train_test_split(mark_X_train, mark_y_train,train_size=0.7, random_state=0)
print(f'train_size={mark_X_tra.shape[0]},test_size={mark_X_val.shape[0]}')
train_size=56,test_size=24
80個の教師データを、学習データ70%、評価データ30%に分割したので、それぞれ56個、24個に分割できた。
決定木による分類
決定木で学習と予測し、認識したマーク数を集計すると、うまく分類できている。(認識率100%)
# 決定木
from sklearn.tree import DecisionTreeClassifier
model_dt = DecisionTreeClassifier(max_depth=3,random_state=0)
model_dt.fit(mark_X_tra, mark_y_tra)
# 予測
y_pred_dt = model_dt.predict(mark_X_val)
# 認識率
result = y_pred_dt==mark_y_val
print(f'認識率={result.mean()*100:.3f}%')
# 認識結果まとめ(マーク)
mark_np_result = np.zeros([3,3],dtype=int)
for predict, label in zip(y_pred_dt,mark_y_val):
mark_np_result[label][predict] += 1
mark_df_result = pd.DataFrame(mark_np_result,index=mark_label_list.keys(),columns=mark_label_list.keys())
print('認識したマーク(縦:正解、横:予測)')
print(mark_df_result)
認識率=100.000%
認識したマーク(縦:正解、横:予測)
D H B
D 8 0 0
H 0 12 0
B 0 0 4
【参考】学習済モデルの可視化
学習済モデルを可視化する。graphviz でシンプルな分類木を描くを参考とさせていただきました。
import graphviz
data = tree.export_graphviz(model_dt,
feature_names = mark_X_tra.columns,
impurity = True,
class_names = get_key_list(mark_label_list),
filled=False, rounded=True
)
graph = graphviz.Source(data)
graph
まず、Greenの平均で、ダイヤを分類し、Blueの平均で、BLとハートに分類していることがわかる。
数字認識
RGBの平均では数字は認識できないので、数字はCNNで認識する。
学習用と評価用に分割
ここはマークの認識と全く同様。
# 訓練データ、評価データに分割する。
number_X_tra, number_X_val, number_y_tra, number_y_val = train_test_split(number_train_img_list, number_y_train,train_size=0.7, random_state=0)
print(f'train_size={number_X_tra.shape[0]},test_size={number_X_val.shape[0]}')
train_size=56,test_size=24
CNNによる分類
CNNについては、PyTorchでCNNを徹底解説を参考とさせて頂きました。
ベースは同じですが、ハイパーパラメータを弄ったり、ドロップアウトを追加したり、いろいろと試して、細かい点は変更しました。
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt #グラフ出力用module
BATCH_SIZE = 256
WEIGHT_DECAY = 0.005
LEARNING_RATE = 0.1
EPOCH = 100
X_train_torch = torch.tensor(number_X_tra, dtype=torch.float)
y_train_torch = torch.tensor(number_y_tra, dtype=torch.long)
trainset = torch.utils.data.TensorDataset(X_train_torch, y_train_torch)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=BATCH_SIZE)
X_test_torch = torch.tensor(number_X_val, dtype=torch.float)
y_test_torch = torch.tensor(number_y_val, dtype=torch.long)
testset = torch.utils.data.TensorDataset(X_test_torch, y_test_torch)
testloader = torch.utils.data.DataLoader(testset, batch_size=BATCH_SIZE)
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.relu = nn.ReLU()
self.pool = nn.MaxPool2d(2)
self.conv1 = nn.Conv2d(3, 16, (5, 5))
self.conv2 = nn.Conv2d(16, 32, (5, 5))
self.fc1 = nn.Linear(800, 256)
self.fc2 = nn.Linear(256, 7)
self.flatten = nn.Flatten()
self.drop = nn.Dropout(p=0.5)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.drop(x)
x = self.pool(x)
x = self.conv2(x)
x = self.relu(x)
x = self.drop(x)
x = self.pool(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.relu(x)
x = self.drop(x)
x = self.fc2(x)
return x
device = torch.device("cpu")
net = Net()
net = net.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=LEARNING_RATE)
train_loss_value=[] #trainingのlossを保持するlist
train_acc_value=[] #trainingのaccuracyを保持するlist
test_loss_value=[] #testのlossを保持するlist
test_acc_value=[] #testのaccuracyを保持するlist
for epoch in range(EPOCH):
print('epoch', epoch+1) #epoch数の出力
for (inputs, labels) in trainloader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
sum_loss = 0.0 #lossの合計
sum_correct = 0 #正解率の合計
sum_total = 0 #dataの数の合計
#train dataを使ってテストをする(パラメータ更新がないようになっている)
for (inputs, labels) in trainloader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
sum_loss += loss.item() #lossを足していく
_, predicted = outputs.max(1) #出力の最大値の添字(予想位置)を取得
sum_total += labels.size(0) #labelの数を足していくことでデータの総和を取る
sum_correct += (predicted == labels).sum().item() #予想位置と実際の正解を比べ,正解している数だけ足す
print("train mean loss={}, accuracy={}"
.format(sum_loss*BATCH_SIZE/len(trainloader.dataset), float(sum_correct/sum_total))) #lossとaccuracy出力
train_loss_value.append(sum_loss*BATCH_SIZE/len(trainloader.dataset)) #traindataのlossをグラフ描画のためにlistに保持
train_acc_value.append(float(sum_correct/sum_total)) #traindataのaccuracyをグラフ描画のためにlistに保持
sum_loss = 0.0
sum_correct = 0
sum_total = 0
net.eval()
#test dataを使ってテストをする
for (inputs, labels) in testloader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
sum_loss += loss.item()
_, predicted = outputs.max(1)
sum_total += labels.size(0)
sum_correct += (predicted == labels).sum().item()
print("test mean loss={}, accuracy={}"
.format(sum_loss*BATCH_SIZE/len(testloader.dataset), float(sum_correct/sum_total)))
test_loss_value.append(sum_loss*BATCH_SIZE/len(testloader.dataset))
test_acc_value.append(float(sum_correct/sum_total))
net.train()
#以下グラフ描画
plt.plot(range(EPOCH), train_loss_value)
plt.plot(range(EPOCH), test_loss_value, c='#00ff00')
plt.xlim(0, EPOCH)
plt.xlabel('EPOCH')
plt.ylabel('LOSS')
plt.legend(['train loss', 'test loss'])
plt.title('loss')
plt.show()
plt.clf()
plt.plot(range(EPOCH), train_acc_value)
plt.plot(range(EPOCH), test_acc_value, c='#00ff00')
plt.xlim(0, EPOCH)
plt.xlabel('EPOCH')
plt.ylabel('ACCURACY')
plt.legend(['train acc', 'test acc'])
plt.title('accuracy')
plt.show()
epoch 1
train mean loss=8.184068407331194, accuracy=0.375
test mean loss=20.446175893147785, accuracy=0.25
epoch 2
train mean loss=8.101869310651507, accuracy=0.375
test mean loss=20.058897654215496, accuracy=0.25
;(後略)
順調に誤差が減少し、100エポックの学習を終えると、正解率もほぼ1(100%)となっていることがわかる。
それでは、この学習モデルを使って、評価用データを予測してみる。
# 予測
def predict_number(X):
X_torch = torch.tensor(X, dtype=torch.float)
y_pred = net(X_torch)
_, predicted = torch.max(y_pred.data, 1)
# numpy array に変換
normal_list = []
for i in predicted:
normal_list.append(int(i))
return np.array(normal_list)
# 予測
predicted = predict_number(number_X_val)
result = predicted==number_y_val
print(f'認識率={result.mean()*100:.3f}%')
# 認識結果まとめ(数字)
number_np_result = np.zeros([7,7],dtype=int)
for predict, label in zip(predicted,number_y_val):
number_np_result[label][predict] += 1
number_df_result = pd.DataFrame(number_np_result,index=number_label_list.keys(),columns=number_label_list.keys())
print('認識した数字(縦:正解、横:予測)')
print(number_df_result)
認識率=100.000%
認識した数字(縦:正解、横:予測)
15 10 5 3 2 1 0
15 1 0 0 0 0 0 0
10 0 4 0 0 0 0 0
5 0 0 3 0 0 0 0
3 0 0 0 3 0 0 0
2 0 0 0 0 3 0 0
1 0 0 0 0 0 6 0
0 0 0 0 0 0 0 4
認識した数字を集計すると、うまく分類できている。(認識率100%)
全体(マーク+数字)の分類
1画面(1ファイル)のマークと数字の認識結果を用いて、以下のように処理する。
- 8個の位置について、それぞれ、マークと数字を認識し、結果を結合する。
- 1つはBLなので、他の7個を認識した結果以外のマークと数字をBLにあてがう。
- 具体的には、
- label_one_image というリストに、8個の取得できるラベルを定義しておき、
- そのリストから、BL以外で認識したマークのラベルを削除していく。
- 残ったラベルのマークが、BLの位置のマークである。
- と判断する。
- もし、BL以外の7個の認識を間違えていた場合、エラーとしエラーとなった画像ファイル名を出力し、除外する。
- 可能性としては、2個をそれぞれ反対に認識する(例:H1をH3と誤認識し、H3をH1と誤認識する)とつじつまがあうが、これまでの認識率の高さから、可能性は低いとして、このようなケースは考えない。
from PIL import Image
import numpy as np
# 1ファイルの処理
# 戻り値は、予測したラベルのリスト
# 0 filename, #1 y0_x0, #2 y0_x1, #3 y0_x2, #4 y0_x3, #5 y1_x0, #6 y1_x1, #7 y1_x2, #8 y1_x3, #9 選択index(1-8)
def predict_1file(filename):
basename = os.path.basename(filename)
# 読み込み
img = Image.open(filename)
# 予測したラベル
predict_label = []
predict_label.append(basename)
# 1毎画像のラベル
label_one_image = ['D15','D10','D5','H3','H2','H1','H1','H1']
# テストデータのリスト
mark_test_img_list = []
number_test_img_list = []
# マークの切り出し、認識
for (x,y), pos in mark_pos_list.iterrows():
mark_cut_image = cut_image(img,x,y,pos)
mark_test_img_list.append(mark_cut_image)
mark_test_img_list = np.array(mark_test_img_list)
mark_X_test = get_mark_x(mark_test_img_list)
mark_y_pred_test = model_dt.predict(mark_X_test)
mark_label = [get_keys_from_value(y,mark_label_list)[0] for y in mark_y_pred_test]
# 数字の切り出し、認識
for (x,y), pos in number_pos_list.iterrows():
number_cut_image = cut_image(img,x,y,pos)
number_test_img_list.append(number_cut_image)
number_test_img_list = np.array(number_test_img_list)
number_X_test = number_test_img_list
number_y_pred_test = predict_number(number_X_test)
number_label = [get_keys_from_value(y,number_label_list)[0] for y in number_y_pred_test]
# ラベル(マーク+数字)
for mark, number in zip(mark_label,number_label):
label = mark+number
predict_label.append(label)
if label in label_one_image:
del label_one_image[label_one_image.index(label)]
# 1画像でうまく認識できた
if len(label_one_image)==1:
bk_index = predict_label.index('B0')
predict_label[bk_index] = label_one_image[0]
predict_label.append(bk_index)
return predict_label
else:
print('error'+basename)
print(predict_label)
return None
上記1ファイルの処理関数を使って、取得した全画像50枚について処理する。
import glob
# 予測したラベルのリストを取得
def get_predict_label_list(dir):
predict_label_list = []
# ファイルをループ
files = glob.glob( dir + '/*.PNG' )
for file in files:
# 1ファイルの処理
predict_label = predict_1file(file)
if predict_label!=None:
predict_label_list.append(predict_label)
return predict_label_list
# 予測したラベルのリストを取得
predict_label_list = get_predict_label_list('./Data')
エラーが出力されないので、前述の通り、うまく認識できた(100%)と判断する。
位置毎に取得できたマークの集計
位置毎に取得できたマークの集計するが、その際、H1は、1画面から3個取得できるので、1/3として、取得できる確率を全て1/8として合わせておく。
また、選択した(取得した)数と合計数も集計しておく。
# 位置毎のマークの集計
def get_mark_list(predict_label_list):
#0 filename #1 y0_x0 #2 y0_x1 #3 y0_x2 #4 y0_x3 #5 y1_x0 #6 y1_x1 #7 y1_x2 #8 y1_x3 #9 選択index(1-8)
mark_list = np.zeros(9*(len(label_list)-1),dtype=float)
mark_list = mark_list.reshape( 9 , len(label_list)-1)
for predict_label in predict_label_list:
# 位置毎のマーク
for index in range(1,9):
mark = predict_label[index]
mark_list[index-1, label_list[mark]] += 1
# Getしたマーク(BLの場所)
bk_index = predict_label[9]
mark = predict_label[bk_index]
mark_list[8,label_list[mark]] += 1
# H1は、3枚出るので、1/3しておく
mark_list[:,-1] /= 3
# 合計を計算
sum_list = mark_list[:-1,:].sum(axis=0)
mark_list = np.append(mark_list,mark_list[:-1,:].sum(axis=0))
mark_list = mark_list.reshape(10,6)
# データフレーム化
column_list = [key for key in label_list][:-1]
index_list = ['y'+str(y)+'_x'+str(x) for y in range(0,2)for x in range(0,4)]
index_list.append('Get')
index_list.append('Sum')
df_mark_list = pd.DataFrame(mark_list,index=index_list,columns=column_list)
return df_mark_list
# 位置毎のマークの集計
df_mark_list = get_mark_list(predict_label_list)
df_mark_list
D15 D10 D5 H3 H2 H1
y0_x0 3.0 9.0 8.0 8.0 6.0 5.333333
y0_x1 5.0 1.0 3.0 3.0 4.0 11.333333
y0_x2 7.0 7.0 5.0 3.0 9.0 6.333333
y0_x3 2.0 3.0 2.0 12.0 11.0 6.666667
y1_x0 4.0 2.0 16.0 7.0 2.0 6.333333
y1_x1 2.0 19.0 10.0 4.0 3.0 4.000000
y1_x2 18.0 4.0 1.0 5.0 8.0 4.666667
y1_x3 9.0 5.0 5.0 8.0 7.0 5.333333
Get 9.0 5.0 5.0 8.0 7.0 5.333333
Sum 50.0 50.0 50.0 50.0 50.0 50.000000
縦方向はマーク位置と、取得した個数(Get)、合計(Sum)、横軸はマークである。
合計50回のうち、同様に確からしい場合1/8の確率となるので、期待値は6.25となる。
ダイヤ(x15)は、9個もらえたので、やはり多いようだ。
しかしながら、y1_x2では、その倍の18個にもなり、かなり偏っているように見える。
独立性の検定をするまでもなく、関連がありそうな・・・
独立性の検定
選択位置と取得できるマークには関連があるかを検定したい。同様に確からしい場合どの位置でも1/8の確率となるところ、いかさまくじのようなインチキをしている場合には関連がある、という結果となる。
帰無仮説:「選択位置と取得できるマークには関連がない」
対立仮説:「選択位置と取得できるマークには関連がある」
として、独立性の検定をした。
PythonとRで独立性の検定(カイ2乗検定・フィッシャーの直接確率検定)を参考とさせて頂きました。
import scipy.stats as st
# 独立性の検定
def test_of_independence(df_mark_list,alpha = 0.05):
# 取得と合計を削除
df_mark_list_wo_GetSum = df_mark_list[:-2]
print(df_mark_list_wo_GetSum)
x2, p, dof, e = st.chi2_contingency(df_mark_list_wo_GetSum,correction=False)
print(f'p値 = {p}')
print(f'カイ2乗値 = {x2:.2f}')
print(f'自由度 = {dof}')
print(f'よって、帰無仮説:「選択位置と取得できるマークには関連がない」は、有意水準{alpha}において、',end='')
if p < alpha:
print('棄却される')
else:
print('採択される')
# 独立性の検定
test_of_independence(df_mark_list)
D15 D10 D5 H3 H2 H1
y0_x0 3.0 9.0 8.0 8.0 6.0 5.333333
y0_x1 5.0 1.0 3.0 3.0 4.0 11.333333
y0_x2 7.0 7.0 5.0 3.0 9.0 6.333333
y0_x3 2.0 3.0 2.0 12.0 11.0 6.666667
y1_x0 4.0 2.0 16.0 7.0 2.0 6.333333
y1_x1 2.0 19.0 10.0 4.0 3.0 4.000000
y1_x2 18.0 4.0 1.0 5.0 8.0 4.666667
y1_x3 9.0 5.0 5.0 8.0 7.0 5.333333
p値 = 5.5391302763822504e-11
カイ2乗値 = 118.37
自由度 = 35
よって、帰無仮説:「選択位置と取得できるマークには関連がない」は、有意水準0.05において、棄却される
p値がマイナス11乗なので、極めて稀で起こる確率である。
有意水準を5%と設定しておいたが、まさにけた違いであり得ない確率である。
ダイヤ(x15)は、9個もらえたので、いかさまというわけではないが、かなり偏った出方であることがわかった。
選択位置を変えてもう一度、検定
右下(y1_x3)を選択し続けた結果、その隣(y1_x2)でダイヤ(x15)が多いので、y1_x2を選択することを50回試してみた。
その結果に対して、同じ処理をしてみるみると・・・
# 予測したラベルのリストを取得
predict_label_list2 = get_predict_label_list('./Data2')
# 位置毎のマークの集計
df_mark_list2 = get_mark_list(predict_label_list2)
# 独立性の検定
test_of_independence(df_mark_list2)
D15 D10 D5 H3 H2 H1
y0_x0 5.0 4.0 8.0 12.0 7.0 4.666667
y0_x1 5.0 2.0 2.0 5.0 5.0 10.333333
y0_x2 1.0 5.0 5.0 0.0 9.0 10.000000
y0_x3 7.0 4.0 5.0 13.0 10.0 3.666667
y1_x0 4.0 5.0 18.0 7.0 3.0 4.333333
y1_x1 2.0 22.0 1.0 3.0 4.0 6.000000
y1_x2 6.0 2.0 10.0 8.0 7.0 5.666667
y1_x3 20.0 6.0 1.0 2.0 5.0 5.333333
p値 = 4.118824762977543e-17
カイ2乗値 = 155.41
自由度 = 35
よって、帰無仮説:「選択位置と取得できるマークには関連がない」は、有意水準0.05において、棄却される
今度は、y1_x2の位置ではダイヤ(x15)が6個と平均以下となり、y1_x3の位置でダイヤ(x15)が19個と突出している。当然、独立性の検定では、帰無仮説は棄却される。
結論
選択した位置により、マークの出る確率が変わっている。
予測モデルをうまく作れば、より多くのダイヤ(x15)がもらえそうなので、検討してみようかなとおもっていますが、良い結果がまとまればまた報告します。