当一局游戏结束,总有一些疑问:为什么我的评分这么低,为什么不是我的MVP?虽然菜是原罪,但游戏具体的评分仅仅是根据KDA(KILL,DEATH,ASSIST)计算出来或是还有其它参数,另外具体的算法又是如何?因为之前有看过一个老师使用梯度下降算法来分析Pokemon的CP(combat power)值是如何进化的,于是也想借用该算法来分析王者荣耀的评分机制。

      为了模拟游戏的计算机制,我们需要给出一个计算模型。从游戏的结果中可以直观看到,最终的评分S与自己的KDA是相关的,并且可能还与其他的数据相关。这里我们先假设只与KDA有关,并且近似于一种线性关系。于是我们建立一种线性模型如下:

model1

      同时为了说明,不同的a、b、c的好与坏,需要定义一个损失函数L(Loss function)。该函数类似于统计学中计算标准差的方法,定义函数L(a,b,c)(如下图)。当函数L的值越小,说明越接近于我们想要得到的最佳模型。

Lossf1

      接下来就需要通过梯度下降算法,求出一个最佳的a、b、c,使得L(a,b,c)的值最小。若对于一个一元函数(如下图),当点a0处的导数值小于零,则可以判断出函数极小值点在a0点的右侧;相反的,若导数大于零,则函数极小值点在a0点的左侧;

graph1

      当函数有三个参数时,我们可以通过分别求各个参数的偏导数来判断极小值点的位置(即高等数学中的梯度)。因此具体的梯度下降算法的步骤如下:

algorithm1

      根据以上算法可以通过C语言实现(如附录中完整的C语言程序)。当我们使用最终计算的模型模拟游戏的评分机制时,会发现结果有很大偏差。主要原因是,一方面所统计的样本太少(即training data数量太少);另一方面,真正的评分机制,不只涉及到KDA,而且应该还考虑了对英雄的伤害值、承受的伤害值、总的输出值等因素。当然,我们可以将所有可能会用到的数据考虑在内,重新定义一个n维参数的线性模型,然后用梯度下降算法计算。至于具体的实现方式以及梯度下降算法可能出现的一些问题会在之后的文章中分析。

result1


源代码:

#include <stdio.h>

const int TA = 12;        // 样本个数
float step = 0.000001;    // learning rate
//training data,K/D/A/S(score)

float K[12] = {   7,    4,    6,   3,    4,     9,   4,     4,    2,   0,   2,    4};
float D[12] = {   5,    1,    1,   4,    3,     1,   3,     7,    2,   8,   3,    6};
float A[12] = {   9,    9,    5,   11,   6,     3,   7,     1,    4,   3,   4,    1};
float S[12] = { 9.3, 11.6, 10.8, 11.5, 10.3, 12.3, 9.2,   6.5,  9.0, 5.4, 9.2,  7.1};

float gradienta(float a, float b, float c)
{
    int i = 0;
    float sum = 0;
    for ( ; i < TA; ++i) {
        float tmp = S[i] - a*K[i] - b*D[i] - c*A[i];
        sum += -2 * K[i] * tmp;
    }
    return sum;
}
float gradientb(float a, float b, float c)
{
    int i = 0;
    float sum = 0;
    for ( ; i < TA; ++i) {
        float tmp = S[i] - a*K[i] - b*D[i] - c*A[i];
        sum += -2 * D[i] * tmp;
    }
    return sum;
}
float gradientc(float a, float b, float c)
{
    int i = 0;
    float sum = 0;
    for ( ; i < TA; ++i) {
        float tmp = S[i] - a*K[i] - b*D[i] - c*A[i];
        sum += -2 * A[i] * tmp;
    }
    return sum;
}

void outputKDAS(float a, float b, float c)
{
    int i = 0;
    for ( ; i < TA; ++i) {
        float tmp =  a*K[i] + b*D[i] + c*A[i];
        printf("%4.1f: %4.0f, %4.0f, %4.0f, %4.1f\n", tmp, K[i], D[i], A[i], S[i]);
    }
}


float loss(float a, float b, float c)
{
    int i = 0;
    float sum = 0;
    for ( ; i < TA; ++i) {
        float tmp = S[i] - a*K[i] - b*D[i] - c*A[i];
        sum += (tmp * tmp);
    }
    return sum;
}



int main()
{
    float a = 1;
    float b = -1;
    float c = 1;
    float ga = 0;
    float gb = 0;
    float gc = 0;

    int t = 100000;
    int i = 0;
    for ( ; i < t; ++i) {

        ga = gradienta(a, b, c);
        gb = gradientb(a, b, c);
        gc = gradientc(a, b, c);
        if ((ga <= 0.0000001 && ga >= -0.0000001)
                || (gb <= 0.0000001 && gb >= -0.0000001)
                || (gc <= 0.0000001 && gc >= -0.0000001) )
            break;
        a = a - step * ga;
        b = b - step * gb;
        c = c - step * gc;

        printf("%d: s = %fK + %fD + %fA\n", i, a, b, c);
    };
    printf("result : s = %fK + %fD + %fA; loss = %f\n", a, b, c, loss(a,b,c));
    outputKDAS(a, b, c);

    return 0;
}