從 保齡球得分計算方法 淺析 深度學習

原由

週六被小夥伴拖去游泳,美名其曰:鍛鍊身體。其實某人就是去泡澡的,哈哈。說正題吧,游完泳在體育場裏閒逛,裏面很大,轉着轉着看到一個保齡球館,懷着對未知事物的好奇,決定和某人去嘗試一下。我和S同窗一人買了一局,按照說明,每一局分爲10次,每一次有兩次機會扔球。最後的比分就不說了,反正玩的很爽,最後也在邊上一個厲害的大叔指點下,學會了基本的扔球姿式。 html

看到這你覺得這是一篇敘事文?那就錯了,原由是從這裏開始的,咱們的次數用完後,留在裏面打檯球(這裏也有檯球桌),看到不斷有穿着隊服一類東西的人進來,應該是來比賽的,同時又看到了賽道上面的牌子,有一個寫着:289分。那分數是怎麼計算的呢,懷着好奇心搜索起保齡球的積分規則來。在瞭解以後,我就在想一個問題:__若是是讓我開發一個保齡球的遊戲,那麼計分程序要怎麼寫呢?__今天咱們就從這裏提及。。。python

規則

先簡述一下保齡球的規則,這裏引用百度知道的別人的回答,每一局比賽有10格,每格有兩次擊球機會,咱們這裏關注它的得分狀況,這裏分爲兩種狀況:apache

  • 1-9格擊球
    每一格有3種可能:api

    1. 第一次擊球所有擊倒:這種狀況得分就是擊倒的瓶數(10)+後兩次擊球擊倒的總數
    2. 兩次擊球所有擊倒:這樣得分爲擊倒的瓶數(10)+後一次擊球擊倒的總數
    3. 兩次擊球沒有所有擊倒:得分爲兩次擊倒總瓶數
  • 第10格擊球
    這一格有兩種可能:數組

    1. 前兩次未能將瓶所有擊倒:得分爲擊倒總瓶數+第9格的得分
    2. 前兩次將瓶所有擊倒,得到一次追加機會:得分爲兩次擊倒總數(10)+追加時擊倒的總瓶數+第9格分數

程序

規則也瞭解了,下面就到了寫代碼的時候了,爲了方便,這裏選擇Python,版本爲3.6
考慮到直觀性,這裏沒有用交互式的程序,而是直接將擊中狀況抽象成矩陣(數組),算出最後總分。
輸入的數據大概是這個樣子:網絡

[[0, 3], [2, 6], [3, 6], [0, 3], [3, 0], [9, 1], [6, 3], [6, 2], [4, 6], [4, 2]]框架

10x2的數組,表明前10格每格的擊倒瓶數,若是一格內不須要第二次擊球,也算做0。這裏先寫一個簡單的數據生成函數。dom

import random
def top_10():
    for i in range(10):
        for j in range(2):
            if j == 0 :
                a[i][j] = random.randint(0,10)
            else :
                a[i][j] = random.randint(0,10-a[i][j-1]) 
    return a

同時,咱們注意到了,這個生成函數還少了點什麼,沒錯,就是第十格的追加擊球數。因此,這裏再定義一個追加球生成函數
這裏爲了後面計算方便,也定義爲[[x,y]]這種格式函數

def addto_num(a):
    return [[random.randint(0,10),0]] if sum(a[9]) == 10 else [[0,0]]

原始數據的生成咱們完成了,接下來要定義計算函數了,計算總分數學習

def calc_total(top):
    sums = 0
    index = 0
    for x in top:
        if x[0] == 10:
            sums += 10
            if top[index+1][0] == 10:
                sums += 10 + top[index+2][0]
            else:
                sums += sum(top[index+1])
        elif sum(x) == 10:
            sums += 10 + top[index+1][0]
        else:
            sums += sum(x)
        index+=1
        if index == 9:
            break
    sums += sum(top[8]+top[9]+top[10])
    return sums

代碼寫的不是很好看,你們請諒解啊,不過整個完整的功能是作完了,咱們能夠寫個方法測試下

tmp1 = top_10()
add1 = addto_num(tmp1)
c = calc_total(tmp1+add1)
print(c)
78

神經網絡版

想必你們也瞭解,當下最火的就是AI,而做爲實現AI的其中一種手段,深度學習必不可少。最近也在學習這方面的知識(ps:給沐神瘋狂打call,強烈推薦他的深度學習課程,連接你們本身去搜,就不作廣告了),雖說本身連入門都算不上,但仍是想實現一下本身版本的。

因而就有了這個:

深度學習版本的保齡球得分計算方法

這裏咱們用到了mxnet這個深度學習框架,最基礎的部分的兩個庫ndarrayautograd

首先,咱們是基於線性迴歸這個最簡單也是最基礎的神經網絡實現的,模型看起來就像這樣
$$\boldsymbol{\hat{y}} = X \boldsymbol{w} + b$$
同時定義它的損失函數,也就是計算預測值和實際值的差距,這裏用兩個的平方偏差來計算,模型是這樣
$$\sum_{i=1}^n (\hat{y}_i-y_i)^2.$$

首先,咱們要__建立數據集__
由於咱們以前定義的是Python的list,因此在這裏要轉換成mxnet的內置數組ndarray

不過在此以前咱們要先改進下咱們的生成函數,以前是由兩個函數組成,如今爲了方便,咱們合成一個。同時,計算方法改形成ndarray版本的。

from mxnet import ndarray as nd
from mxnet import autograd

def init_data():
    for i in range(0,10):
        for j in range(0,2):
            if j == 0 :
                a[i][j] = random.randint(0, 10)
            else :
                a[i][j] = random.randint(0,10-a[i][j-1]) 
    return a+[[random.randint(0,10),0]] if sum(a[9]) == 10 else a+[[0,0]]
def calc_total_nd(top):
    sums = 0
    index = 0
    for x in top:
        if x[0].asscalar() == 10:
            sums += 10
            if top[index+1][0].asscalar() == 10:
                sums += 10 + top[index+2][0].asscalar()
            else:
                sums += nd.sum(top[index+1]).asscalar()
        elif nd.sum(x).asscalar() == 10:
            sums += 10 + top[index+1][0].asscalar()
        else:
            sums += nd.sum(x).asscalar()
        index+=1
        if index == 9:
            break
    sums += nd.sum(top[8]+top[9]+top[10]).asscalar()
    return sums   

num_inputs = 22
num_examples = 1000
X = nd.zeros(shape=(num_examples,11,2))
for i in X:
    i[:] = nd.array(init_data())
y = nd.array([calc_total_nd(i) for i in X])

而後是定義 數據讀取方法
目的是在後面訓練時隨機遍歷咱們的數據集,這裏參考了沐神教程裏的方法。

import random
batch_size = 10
def data_iter():
    # 產生一個隨機索引
    idx = list(range(num_examples))
    random.shuffle(idx)
    for i in range(0, num_examples, batch_size):
        j = nd.array(idx[i:min(i+batch_size,num_examples)])
        yield nd.take(X, j), nd.take(y, j)

嘗試着讀取一個

for data, label in data_iter():
    print(data, label)
    break
[[[  2.   0.]
  [  7.   0.]
  [  1.   7.]
  [  2.   2.]
  [  6.   2.]
  [  0.   5.]
  [  0.   5.]
  [  7.   1.]
  [  6.   4.]
  [  3.   0.]
  [  0.   0.]]

 [[  6.   3.]
  [  4.   2.]
  [  2.   4.]
  [  8.   2.]
  [  4.   6.]
  [  6.   3.]
  [  2.   6.]
  [  6.   3.]
  [  2.   3.]
  [  8.   2.]
  [  7.   0.]]

 [[ 10.   0.]
  [  8.   0.]
  [  2.   2.]
  [  8.   2.]
  [  0.   3.]
  [ 10.   0.]
  [ 10.   0.]
  [  6.   3.]
  [ 10.   0.]
  [  1.   7.]
  [  0.   0.]]

 [[  5.   1.]
  [  6.   2.]
  [ 10.   0.]
  [  3.   6.]
  [  8.   2.]
  [ 10.   0.]
  [  4.   4.]
  [  2.   4.]
  [  2.   0.]
  [  7.   3.]
  [ 10.   0.]]

 [[  6.   2.]
  [  8.   0.]
  [  0.   0.]
  [  9.   0.]
  [  6.   4.]
  [  5.   3.]
  [  5.   0.]
  [  1.   6.]
  [  0.   1.]
  [  4.   4.]
  [  0.   0.]]

 [[  5.   5.]
  [  6.   3.]
  [  0.   7.]
  [  2.   8.]
  [ 10.   0.]
  [  4.   0.]
  [  1.   5.]
  [  1.   2.]
  [  1.   2.]
  [  0.   2.]
  [  0.   0.]]

 [[ 10.   0.]
  [  0.   3.]
  [  3.   7.]
  [  3.   1.]
  [  8.   1.]
  [  4.   2.]
  [  8.   1.]
  [  6.   4.]
  [ 10.   0.]
  [  5.   0.]
  [  0.   0.]]

 [[  8.   2.]
  [ 10.   0.]
  [  6.   0.]
  [ 10.   0.]
  [  1.   4.]
  [  2.   6.]
  [  9.   0.]
  [  5.   5.]
  [  7.   1.]
  [  5.   1.]
  [  0.   0.]]

 [[  9.   1.]
  [  7.   1.]
  [  6.   3.]
  [  0.   5.]
  [  7.   3.]
  [  7.   1.]
  [  6.   3.]
  [  3.   1.]
  [  3.   3.]
  [ 10.   0.]
  [  6.   0.]]

 [[  0.  10.]
  [  4.   3.]
  [  2.   6.]
  [  2.   6.]
  [  4.   1.]
  [  8.   1.]
  [  5.   4.]
  [  3.   6.]
  [  6.   4.]
  [  4.   2.]
  [  0.   0.]]]
<NDArray 10x11x2 @cpu(0)> 
[  73.  104.  133.  118.   70.   87.  107.  118.  105.   99.]
<NDArray 10 @cpu(0)>

數據準備好了,如今要定義一個__初始化的模型參數__
這裏隨機生成一個就行了,後面咱們會經過訓練,慢慢學習完善這個參數,這也是深度學習的目的

w = nd.random_normal(shape=(num_inputs, ))
b = nd.random_normal(shape=(1,))
params = [w, b]
print(params)
[
[ 0.50869578 -0.16038011  0.91511744  0.84187603 -0.49177799 -1.00553632
 -1.55609238  3.13221502 -0.15748753 -0.4358989  -0.52664566 -0.49295077
 -0.17884982  1.43718672  0.43164727 -0.31814137  0.46760127 -0.16282491
  0.17287086  0.6836102   0.76158988  1.61066961]
<NDArray 22 @cpu(0)>, 
[  9.91063134e-05]
<NDArray 1 @cpu(0)>]

而後附上梯度,也就是讓後面autograde能夠對這個函數求導

for param in params:
    param.attach_grad()

定義模型和損失函數

這裏要注意的是:咱們的維度不是1,因此要把數組的維度reshape一下變成一維數組

def net(X):
    return nd.dot(X.reshape((-1,num_inputs)), w) + b
def square_loss(yhat, y):
    return (yhat - y.reshape(yhat.shape)) ** 2

而後是優化方法,也就是學習方法,讓函數去學習參數

def SGD(params, lr):
    for param in params:
        param[:] = param - lr * param.grad

最後就是__訓練__了

epochs = 5
learning_rate = .0001
for e in range(epochs):
    total_loss = 0
    for data, label in data_iter():
        with autograd.record():
            output = net(data)
            loss = square_loss(output, label)
        loss.backward()
        SGD(params, learning_rate/batch_size)
        total_loss += nd.sum(loss).asscalar()
    print("Epoch %d, average loss: %f" % (e, total_loss/num_examples))
Epoch 0, average loss: 82.049488
Epoch 1, average loss: 82.009441
Epoch 2, average loss: 81.810044
Epoch 3, average loss: 82.243776
Epoch 4, average loss: 82.023799

最後來驗證下咱們的預測結果

for data, label in data_iter():
        print("實際分數")
        print(label)
        print("預測分數")
        print(net(data))
        break
實際分數

[ 108.   77.  102.  115.   85.  110.   76.  124.   78.   87.]
<NDArray 10 @cpu(0)>
預測分數

[ 107.43678284   86.52748871  101.92710114  116.50645447   90.5655899
  115.31760406   80.10424805  118.94145203   84.49520111   95.17882538]
<NDArray 10 @cpu(0)>

參考:
動手學深度學習

相關文章
相關標籤/搜索