遊戲AI--決策(1)

機器學習的引入

機器學習最近由於AlphaGo數據挖掘的大熱而變得流行起來,然而我們可以讓它變得更加cool,在接下來的模塊中,我將講述我對遊戲中應用機器學習的理解以及我的現有成果。

應用機器學習的意義

遊戲進行中,尤其是網絡遊戲,會積累大量的數據,數據賦予了計算機行爲意識,而我們如果能“告訴”計算機數據歸納總結的方法,那計算機可能會產生驚人的思維能力,而機器學習就是這樣的一種神奇的工具。

前景

目前的遊戲由於同質化嚴重,所以一個高度智能的遊戲體驗會帶給玩家難以想象的快感,而且筆者以爲,很有可能在某一天,我們能在遊戲中建立真正的人工智能,因爲遊戲世界高度抽象化,數據的提取難度大大降低,當然,這有點跑題了。

應用機器學習的決策邏輯

遊戲的進行,決策和事件無疑是最重要的兩個條件,傳統的遊戲中,我們使用大量的條件判斷來進行決策,不僅在邏輯上很繁瑣,而且有一個重大的問題,它是“死的”,比如玩家經過一個怪物,怪物必定會攻擊玩家,如果要改善這一決策,那需要添加許多條件,比如針對怪物的“攻擊性”,重寫判斷邏輯。
機器學習則不然,我們可以根據以前的記錄進行訓練,每個怪物都是獨特的,即使他們隸屬於同一個類,“個性”將使AI十分豐富。

目標–魚類的遺傳

由於機器學習的數學模型非常複雜(對筆者來說是這樣的,花了很多功夫纔看懂),我們先講一個應用,然後我們根據其需求來應用機器學習。

在之前的遊戲底層邏輯中,筆者曾說過想要實現一款海洋模擬的遊戲,之前的隻言片語可能已經能描述大概的框架了,不知道也沒關係,此處我們有一個問題:捕食行爲
捕食行爲基本上是動物最重要的行爲了,此處有三個需要決策的問題,在進入捕食狀態前的判斷中,我們需要知道:
1、是否需要捕食
2、捕食什麼魚類(前提是在食物鏈上的)
3、哪個地方適合捕食

是否捕食

在傳統遊戲中,設定一個飢餓值,這個值隨着時間遞減,減少到一定程度就去捕食。
而這種狀態是不合理的:

  • 首先,我們喫東西並不一定是飢餓,其他的因素都有可能使其變化。
  • 其次,這種if(1)else(0)的變化在空間上表現出來的是跳躍性的曲線,不符合我們越飢餓越想喫東西這樣的特性。
  • 然後,我們的決策沒有隨機性,很容易預測,很“無聊”。

解決方案

我們設定一個合理的初始函數:

H(x)=x1θ1+x2θ2+...+xnθn

矩陣(向量)表達:
H(x)=XTθ

其實就是多元一次方程,H函數得到一個值,然後我們根據值進行決策。

此處提及幾個概念:
θ 我們稱其爲權值,意思是該變量對決策產生影響的重要程度。
x 此處稱爲特性。

根據此函數,我們輸入一系列x,就能得出決策結果。然後你們可能發現了一個問題,θ 怎麼解決,我們先根據經驗定義一個值,然後進行訓練,最終得出一個權值。

遺傳

所謂遺傳是怎麼一回事呢,此處我們每個魚類種羣共享一個H函數,魚在生產下一代的時候,將完全繼承(clone)或者部分繼承(block copy)該函數,此處我們降維處理,將其直接複製。在這種機制下,擁有好的H函數的魚羣將存活下來,並且繁衍生息,而擁有差的H函數的魚羣就被淘汰了。

針對H函數進行訓練,LogisticRegression

此處不會講解關於機器學習的基本知識,太多而且筆者水平有限,講解不清楚。
下面的模塊講解LogisticRegression(以下簡稱LR)的基本理解,如果不感興趣可以跳過直接看公式。

我們已經知道了H函數,此處稱爲數學模型。

H(x)=x1θ1+x2θ2+...+xnθn

現在我們有很多的已知的數據:
x1 x2 x3 H(x),或者叫做y
1 2 3 4
2 3 4 5

諸如此類的,此處的數據最好是數值型,如果是標稱型(ex.顏色:紅,黃,藍),有比較複雜的轉換過程,推薦使用其他的學習算法諸如樹迴歸一類的。
誤差函數:H函數根據我們現在已知的權值會得出一個結果,但是它和上表中的H(x)是不同的,我們把現在的權值代入每一組數據求結果,然後用真實結果(表中的H(x),用y表示)做差,這就是誤差函數。

J(θ)=12i=0nH(xi)yi)2

梯度下降
此處我們要克服誤差,所以要沿着誤差函數變化最快的方向改善我們的θ ,該方向叫做梯度方向,二維數學表達式爲:

f(x,y)=(f(x,y)x,f(x,y)y)

多維時相同,也就是對誤差函數求偏導。
結果如下:
i=0n(yiH(xi))xi

隨機梯度下降
爲了減少計算複雜度,此處我們使用隨機梯度下降,將誤差函數的比較範圍由整個數據集合變爲其中一次(此處直接爲新添加的一次):
(yiH(xi))xi

然後我們的沿着這個方向一點點的改善,的到訓練的迭代公式:

好了,公式就是這個,以上是推導過程:

θ:=θαf(xT)

LR代碼部分:

首先引入c++矩陣庫:Eigen,這個庫一般般,用起來不是很順手,由於字符對齊問題,用VS編譯的同學,該庫的矩陣類型請自行使用引用&。

#pragma once

#include<Eigen/Dense>
#include"cocos2d.h"
#include<vector>
using namespace Eigen;

template<class ElementType, int ElementNum>
class LogisticRegression//the number of sample elements is best less than 16 
{
public:
    LogisticRegression(std::string owner)
    {
        assert(ElementNum < 16 &&
            "TOO LONG!!!!");
        this->getDataFromFile();
        _owner = owner;
    }
    ~LogisticRegression()  {}

    virtual bool initWithData(Matrix<ElementType, ElementNum, 1 >& weightMatrix,double pace)
    {
        _pace = pace;
        try
        {
            _weightMatrix = weightMatrix;
        }
        catch (ElementType)
        {
            return false;
        }
        return true;
    }
public:

    virtual bool getDataFromFile()
    {
        for (int i = 0; i < _weightMatrix.rows(); i++)
        {
            _weightMatrix(i, 0) = (ElementType)cocos2d::UserDefault::getInstance()->getDoubleForKey(
                cocos2d::StringUtils::format("Fish_%s_weight_%d,", _owner, i).c_str()
                );
        }

    }

    void training(Matrix<ElementType, ElementNum + 1, 1>& data)//a group of data
    {
        Matrix<ElementType, ElementNum, 1> xi;
        for (int i = 0; i < ElementNum; i++)
        {
            xi(i, 0) = data(i, 0);
        }

        _weightMatrix = _weightMatrix + _pace*(data(ElementNum, 0) - H_func(xi))*xi;
    }

    void training(Matrix<ElementType, ElementNum + 1, Eigen::Dynamic>& datas, bool overwide)//more than one
    {
        for (int i = 0; i < datas.cols(); i++)
        {
            Matrix<ElementType, ElementNum + 1, 1> data = data.col(i);
            training(data);
        }
    }

    ElementType resultBeforeSigmoid(Matrix<ElementType, ElementNum, 1>& data)
    {
        return data.dot(_weightMatrix);
    }

    bool result(Matrix<ElementType, ElementNum, 1>& data)//we use boolean to describe the result
    {
        return sigmoid(resultBeforeSigmoid(data));
    }

    virtual bool record()
    {
        for (int i = 0; i < _weightMatrix.rows(); i++)
        {
            cocos2d::UserDefault::getInstance()->setDoubleForKey(
                cocos2d::StringUtils::format("Fish_%s_weight_%d,", _owner, i).c_str(),
                (double)_weightMatrix(i, 0)
                );
        }
    }

protected:

    bool sigmoid(ElementType num)
    {
        ElementType  result = 1 / (1 + exp(-1 * num));
        return result > 0.5;
    }

    double H_func(Matrix<ElementType, ElementNum, 1>& data)
    {
        return data.dot(_weightMatrix);
    }

private:
    Matrix<ElementType, ElementNum, 1> _weightMatrix;
    double _pace;//the speed of convergence
    std::string _owner;
};

此處並沒有對大規模訓練做優化,讀取數據也需要童鞋們自己動手,一個基本的類,我們講完了所有的決策再來談談怎麼在實例中應用這些模塊。


準備寫一個有關遊戲底層算法,物理算法,以及AI(重點是機器學習在遊戲中的應用)的長篇博客,歡迎大家指正交流╰( ̄▽ ̄)╯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章