精华内容
下载资源
问答
  • 由于目前游戏的ai最后两手牌...要一个斗地主AI,我觉得是很难的,技术和数学都要有一定水平。 2.老夫就一个刚工作2年的程序员,而且还是非AI专业,让我萌生了就是觉得很难很难得想法。 3.老夫就拿这点工资...

           由于目前游戏的ai最后两手牌打的不尽人意,而手头上的工作又暂时弄完了。就被安排去学习AI,最终目标是重新训练/优化现有的AI而这只是我的记录,仅仅是偏向我的应用,并不偏向研究。虽然只是这样,但是其实听到这个任务的时候,内心是拒绝的。

           原因有以下几点:

           1.要写出一个斗地主AI,我觉得是很难的,技术和数学都要有一定水平。

           2.老夫就一个刚工作2年的程序员,而且还是非AI专业,让我萌生了就是觉得很难很难得想法。

           3.老夫就拿这点工资(说来惭愧,哎)。。。我如果真能写,我才值这点钱么,我写出来的敢用么。

           虽然内心不愿意,但是身体却很诚实,毕竟做的都是出卖自己的工作。就算叫我去38度的天下晒太阳,叫我去厕所吃快餐,我也没什么好拒绝的吧,毕竟花钱买我时间的。将自己的路程尽量全部记录,给自己看,也分享给和我相似情况下想学写游戏AI的人,如果有同僚看到我说的有什么不对,请指出。这里要感谢我的经理潇爷,我的天资比较愚钝,他带我了解并写下第一个关于0-9黑白图片的试别ai。下面开始进入正文。

           

         1.我们游戏中指的AI是什么

            其实AI没有想象的那么难,AI也是分369等的,比如游戏时的NPC,能和你进行对话,能给你任务,这样的就比较简单不需要用到机器学习;更进一步,mmo游戏中的怪物,boss,知道主动攻击你,知道放技能,知道躲技能(这个可能也不需要用到机器学习,或者用了我不知道,因为我目前只做过棋牌游戏);再进一步,你以为你玩的棋牌游戏匹配到的都是真人么,或者进一步说,你以为你别的游戏匹配到的队友都是人么,也许它们只是AI而已(这个就需要机器学习了,这个东西已经拥有了类似人类的想法来陪你玩游戏)。

          2.什么是训练一个AI

            就是训练出一个模型,这个模型再你给它一个输入的时候,它能够预测出最大概率的输出是什么(类似一个函数,你给一个输入,它返回输出)。例如图像试别AI,给定的输入是一张图像,它用输出告诉你最大概率这个图像是什么。注意,这里是告诉你最大概率输出是什么,并不是告诉你输出是什么(关于这个之后会继续聊)。

          3.如何训练一个AI

            其实很多事情,前人已经做好了,就像我们要训练AI,并不是所有的东西都要自己写,现在有很多深度学习的框架,就我而已,我只是应用,我只要找到合适的框架、然后使用框架训练出自己想要的AI即可(类似于有很多库函数,只需要找到自己想用的库函数,然后调用它,获取结果即可)。

          4.我的学习路程

            1.补充了一些知识

            2.经理又给我补充了一些知识,然后在他的带领下,写了一个 0-9的黑白图像试别AI

            3.经过以上的路程后,开始自己继续探索,训练一个斗地主AI

     

            注意:训练 == 写,训练一个AI == 写一个AI,有些东西是术语来着,我也不是专业的,但是又接触了,所以有的时候会语无伦次。

            第一篇大概写这么多,后面会接着更新

           

    展开全文
  • 接上回书,那么如何写一个入门的简单AI训练(0-9)数字图片试别AI。本文的程序,配合我训练的模型试别准确率只有98.8%,不过也是算是给我开辟了新的知识面。 1.为什么使用卷积神经网络 原因有二:1.直观上,使用...

           接上回书,那么如何写一个入门的简单AI训练(0-9)数字图片试别AI。本文的程序,配合我训练的模型试别准确率只有98.8%,不过也是算是给我开辟了新的知识面。

        1.为什么使用卷积神经网络

            原因有二:1.直观上,使用卷积比全连接网络少很多参数(百度上说的);2.我经理直接叫我用这个。哈哈哈哈 = =,我只是大概知道,可以用来处理图像。如果有同僚看到这篇文章,有别的见解欢迎指出。

        2.在写这个训练AI的代码之前,需要准备什么

            使用卷积训练AI,也就是教会AI试别各种数字的是需要大量的数据进行训练了。因为是入门,所以我的训练图片是32*32的黑白图像,也就是用画图工具画出来的。0-9每个数字画了十张。然后写了个python脚本,用每个数字的10张样本,经过简单平移或者旋转生成1000张图。0-9总共是10000张图作为所有的输入数据。设计好输入和输入,安装好相应的库,例如tensorflow,numpy

        3.代码主要分为4个部分:读取图片,读取训练集和校验集,训练,使用模型

          下面开始进入代码部分,代码部分有足够的注释:

           a.读取图片:

    def read_sample(file_path):
        ret = numpy.zeros((32, 32))                 #使用numpy构建32*32的数组
        im = Image.open(file_path)
        rgb_im = im.convert('RGB')                  #将打开的image指定真彩色模式 
        for i in range(32):
            for j in range(32):
                r, g, b = rgb_im.getpixel((i, j))   #读取每个点的r,g,b
                average = (r + b + g)/3             
                if average >= 127:                  #因为是黑白图像,非黑即白rgb三原色综合是255,所以可以使用127来区分
                    ret[i, j] =  0
                else:
                    ret[i, j] = 1
    
        return ret
    
    # 可以使用以下代码输入一下看看图形转化为数字输入的结果
    # ret = read_sample("C:/Users/bfs/Desktop/learning_ai/0/0_1.png")  
    # for j in range(32):
    #     for i in range(32):
    #         print(int(ret[i, j]), end=' ')
    #     print('')      
    # 

             这里随便使用一张图0的图打印结果如下

            可以清晰的看到,转换成数字输入的结果,的确是一个0。

             b.读取训练集和校验集

             这里补充一下,在所有的数据里面,需要把数据分为训练和校验。校验用来验证训练的结果。校验满意后,再生成模型。之后就是直接使用模型了。

    def read_trainset_and_validateset():
        trainset_samples = []               #定义训练集
        validateset_samples = []            #定义校验集
    
        for i in range(10):
            for root, dirs, files in os.walk("out/"+str(i), topdown=False):
                for name in files:
                    pic_path = os.path.join(root, name)
                    rand_num = random.random()
                    if rand_num > 0.9:                                            #90%为训练集,剩下10%为校验集
                        trainset_samples.append( (read_sample(pic_path), i) )     #插入训练集,内容为 (图像内容,数字)
                    else:
                        validateset_samples.append( (read_sample(pic_path), i) )  #插入校验集,内容为 (图像内容,数字)
    
        return (trainset_samples, validateset_samples)

           c.训练

    def train():
        print("loading....")
        trainset_samples, valideteset_samples = read_trainset_and_validateset()    #获取训练集和校验集
        x = numpy.zeros((len(trainset_samples), 32, 32, 1))                        #构建训练集, 10个32*32的1通道神经元输入
        y = numpy.zeros((len(trainset_samples), 10))                               #构建训练集, 二维0-9的神经元输出
        validate_x = numpy.zeros((len(valideteset_samples), 32, 32, 1))            #构建校验集, 10个32*32的1通道神经元输入
        validate_y = numpy.zeros((len(valideteset_samples), 10))                   #构建校验集, 二维0-9的神经元输出
        print("conver......")
    
        for i in range(len(trainset_samples)):
            sample, sample_out = trainset_samples[i]            #sample对应读取的图像结果, sample_out对应结果为哪个数字
            for xi in range(32):    
                for yi in range(32):
                    x[i, xi, yi, 0] = sample[xi, yi]            #训练集输入, i对应目前是哪个神经元的输入, sample为读取图像的结果
    
            y[i, sample_out] = 1                                #训练集输出, 意思为这个神经元的输出结果为1, 即当以上神经元输入时,对应的这个神经元的sample_out的预测结果为1(100%)
    
        for i in range(len(valideteset_samples)):
            sample, sample_out = valideteset_samples[i]         #sample对应读取的图像结果, sample_out对应结果为哪个数字
            for xi in range(32):
                for yi in range(32):
                    validate_x[i, xi, yi, 0] = sample[xi, yi]   #校验集输入, i对应目前是哪个神经元的输入, sample为读取图像的结果
    
            validate_y[i, sample_out] = 1                       #校验集输出, 意思为这个神经元的输出结果为1, 即当以上神经元输入时,对应的这个神经元的sample_out的预测结果为1(100%)
    
        input = keras.Input(shape=(32,32,1))                                                    #输入层,32*32个神经元输入,一个输入通道,因为只是一个二维的黑白图像,一个输入通道即可。复杂的情况则需要多个输入通道,可以自己看看别的例子
        layer = keras.layers.Conv2D(filters=32, kernel_size=(5,5), activation='relu')(input)    #卷积层,5*5个神经元感受视野,32个卷积核,激励函数relu做非线性映射
        layer = keras.layers.Conv2D(filters=8, kernel_size=(5,5), activation='relu')(layer)     #卷积层,5*5个神经元感受视野,8个卷积核,激励函数relu做非线性映射
        layer = keras.layers.MaxPool2D(pool_size=(2, 2))(layer)                                 #池化层,maxpool取“池化视野”矩阵中的最大值,当输入经过卷积层时,得到的feature map (特征图)还是比较大,可以通过池化层来对每一个 feature map 进行降维操作
        layer = keras.layers.Dropout(rate=0.4)(layer)                                           #该层的作用相当于对参数进行正则化来防止模型过拟合
        layer = keras.layers.Flatten()(layer)                                                   #将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。
        layer = keras.layers.Dense(128, activation='relu')(layer)                               #全连接层,有128个神经元,激活函数采用‘relu’
        layer = keras.layers.Dense(10, activation='softmax')(layer)                             #输出层,有10个神经元,每个神经元对应一个类别,输出值表示样本属于该类别的概率大小。
    
        model = keras.Model(input, layer)                                                       #创建模型
        model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  #指定一些参数
        print("training.....")
        model.fit(x=x, y=y, batch_size=200, epochs=40, validation_data=(validate_x, validate_y)) #训练模型
    
        # model.save('C:/Users/bfs/Desktop/learning_ai/pictrue_model.h5')  #训练满意的话则保存
    
        return model

           训练结果为

           可以看到校验集的校验结果精确度达到98.8%左右

          d.使用模型

    def verify_modle():
        m_model = keras.models.load_model('C:/Users/bfs/Desktop/learning_ai/pictrue_model'+'.h5')
        verify_sample = read_sample('C:/Users/bfs/Desktop/learning_ai/3.png')  #我这里随便使用一张图片来进行实际预测
        test_x = numpy.zeros((1, 32, 32, 1))
        for xi in range(32):
                for yi in range(32):
                    test_x[0, xi, yi, 0] = verify_sample[xi, yi]
        predict_result = m_model.predict(x=test_x)               #导入实际使用时的图片数据
    
        ret = 0
        max_prob = max(predict_result[0])                        #返回最大的概率。训练结果是一个二维数组,相当于一列0-9的概率。最大的概率则为预测结果
        for x in range(10):
            if max_prob == predict_result[0, x]:
                ret = x
        
        print('解析图片结果结果为', ret)

          以下为完整代码:

    #coding:utf-8
    from PIL import Image, ImageChops
    import os
    import numpy
    import random
    import numpy as np
    import tensorflow as tf
    from tensorflow import keras
    
    def read_sample(file_path):
        ret = numpy.zeros((32, 32))                 #使用numpy构建32*32的数组
        im = Image.open(file_path)
        rgb_im = im.convert('RGB')                  #将打开的image指定真彩色模式 
        for i in range(32):
            for j in range(32):
                r, g, b = rgb_im.getpixel((i, j))   #读取每个点的r,g,b
                average = (r + b + g)/3             
                if average >= 127:                  #因为是黑白图像,非黑即白rgb三原色综合是255,所以可以使用127来区分
                    ret[i, j] =  0
                else:
                    ret[i, j] = 1
    
        return ret
    
    def read_trainset_and_validateset():
        trainset_samples = []               #定义训练集
        validateset_samples = []            #定义校验集
    
        for i in range(10):
            for root, dirs, files in os.walk("out/"+str(i), topdown=False):
                for name in files:
                    pic_path = os.path.join(root, name)
                    rand_num = random.random()
                    if rand_num > 0.9:                                            #90%为训练集,剩下10%为校验集
                        trainset_samples.append( (read_sample(pic_path), i) )     #插入训练集,内容为 (图像内容,数字)
                    else:
                        validateset_samples.append( (read_sample(pic_path), i) )  #插入校验集,内容为 (图像内容,数字)
    
        return (trainset_samples, validateset_samples)
    
    
    # ret = read_sample("C:/Users/bfs/Desktop/learning_ai/0/0_1.png")  
    # for j in range(32):
    #     for i in range(32):
    #         print(int(ret[i, j]), end=' ')
    #     print('')      
    # 
     
    def train():
        print("loading....")
        trainset_samples, valideteset_samples = read_trainset_and_validateset()    #获取训练集和校验集
        x = numpy.zeros((len(trainset_samples), 32, 32, 1))                        #构建训练集, 10个32*32的1通道神经元输入
        y = numpy.zeros((len(trainset_samples), 10))                               #构建训练集, 二维0-9的神经元输出
        validate_x = numpy.zeros((len(valideteset_samples), 32, 32, 1))            #构建校验集, 10个32*32的1通道神经元输入
        validate_y = numpy.zeros((len(valideteset_samples), 10))                   #构建校验集, 二维0-9的神经元输出
        print("conver......")
    
        for i in range(len(trainset_samples)):
            sample, sample_out = trainset_samples[i]            #sample对应读取的图像结果, sample_out对应结果为哪个数字
            for xi in range(32):    
                for yi in range(32):
                    x[i, xi, yi, 0] = sample[xi, yi]            #训练集输入, i对应目前是哪个神经元的输入, sample为读取图像的结果
    
            y[i, sample_out] = 1                                #训练集输出, 意思为这个神经元的输出结果为1, 即当以上神经元输入时,对应的这个神经元的sample_out的预测结果为1(100%)
    
        for i in range(len(valideteset_samples)):
            sample, sample_out = valideteset_samples[i]         #sample对应读取的图像结果, sample_out对应结果为哪个数字
            for xi in range(32):
                for yi in range(32):
                    validate_x[i, xi, yi, 0] = sample[xi, yi]   #校验集输入, i对应目前是哪个神经元的输入, sample为读取图像的结果
    
            validate_y[i, sample_out] = 1                       #校验集输出, 意思为这个神经元的输出结果为1, 即当以上神经元输入时,对应的这个神经元的sample_out的预测结果为1(100%)
    
        input = keras.Input(shape=(32,32,1))                                                    #输入层,32*32个神经元输入,一个输入通道,因为只是一个二维的黑白图像,一个输入通道即可。复杂的情况则需要多个输入通道,可以自己看看别的例子
        layer = keras.layers.Conv2D(filters=32, kernel_size=(5,5), activation='relu')(input)    #卷积层,5*5个神经元感受视野,32个卷积核,激励函数relu做非线性映射
        layer = keras.layers.Conv2D(filters=8, kernel_size=(5,5), activation='relu')(layer)     #卷积层,5*5个神经元感受视野,8个卷积核,激励函数relu做非线性映射
        layer = keras.layers.MaxPool2D(pool_size=(2, 2))(layer)                                 #池化层,maxpool取“池化视野”矩阵中的最大值,当输入经过卷积层时,得到的feature map (特征图)还是比较大,可以通过池化层来对每一个 feature map 进行降维操作
        layer = keras.layers.Dropout(rate=0.4)(layer)                                           #该层的作用相当于对参数进行正则化来防止模型过拟合
        layer = keras.layers.Flatten()(layer)                                                   #将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。
        layer = keras.layers.Dense(128, activation='relu')(layer)                               #全连接层,有128个神经元,激活函数采用‘relu’
        layer = keras.layers.Dense(10, activation='softmax')(layer)                             #输出层,有10个神经元,每个神经元对应一个类别,输出值表示样本属于该类别的概率大小。
    
        model = keras.Model(input, layer)                                                       #创建模型
        model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  #指定一些参数
        print("training.....")
        model.fit(x=x, y=y, batch_size=200, epochs=40, validation_data=(validate_x, validate_y)) #训练模型
    
        model.save('C:/Users/bfs/Desktop/learning_ai/pictrue_model.h5')
    
        return model
    
    def verify_modle():
        m_model = keras.models.load_model('C:/Users/bfs/Desktop/learning_ai/pictrue_model'+'.h5')
        verify_sample = read_sample('C:/Users/bfs/Desktop/learning_ai/3.png')  #我这里随便使用一张图片来进行实际预测
        test_x = numpy.zeros((1, 32, 32, 1))
        for xi in range(32):
                for yi in range(32):
                    test_x[0, xi, yi, 0] = verify_sample[xi, yi]
        predict_result = m_model.predict(x=test_x)               #导入实际使用时的图片数据
    
        ret = 0
        max_prob = max(predict_result[0])                        #返回最大的概率。训练结果是一个二维数组,相当于一列0-9的概率。最大的概率则为预测结果
        for x in range(10):
            if max_prob == predict_result[0, x]:
                ret = x
        
        print('解析图片结果结果为', ret)
    
    if __name__ == "__main__":
        # train()
        verify_modle()
    
    

              使用图像3导入模型结果为:

            最后有几点需要注意。

            1.我这里写的是很简单的0-9手写数字试别,所以再构建输入的时候只有一个输入通道,如果是复杂的问题,可能需要多个输入通道,比如彩色的图像,可能需要R,G,B三个输入通道。

            2.训练AI是这么个过程,给定输入,给定输入,让AI像人一样去学习。就像一个人要学习一样,告诉自己,这个图像3输入的结果是3。是一定要给出输出的。

            3.AI模型就像我们以前写的函数,给了输入,它就会给输出。像这个0-9的输入是为数字0-9的概率。而不是直接告诉你是什么数字,取出最大的概率对应的数字,就是AI觉得这张图是个什么数字.

            4.我也是初学者,写的注释什么的都是自己的理解,可能不对。如果有发现错误的/有歧义的地方。欢迎指出

            5.上面的训练就是不断利用卷积神经网络提取各种图像的特征,最后当给定一副新的图,就去匹配其相应的特征,给出结果。

            增加一些知识补充连接:

            1.知乎<如何通俗易懂的理解卷积> https://www.zhihu.com/question/22298352/answer/228543288
            2.卷积神经网络详解 - 卷积层逻辑篇  https://blog.csdn.net/tjlakewalker/article/details/83275322

    展开全文
  • 经过3个星期得努力,终于初步达成效果,由于训练数据少(因为我在公司使用的电脑只能使用CPU训练,所以数据不能太多,... 最后,学会使用卷积神经网络去训练一个AI,按照应用来说,其实是不算难的,难点在于设计模型。

           经过3个星期得努力,终于初步达成效果,由于训练数据少(因为我在公司使用的电脑只能使用CPU训练,所以数据不能太多,否则就要很长时间)训练出了个傻子。之后我便没有在继续这个工作了。原因有二:1.我身后的同事和我一起再搞,他的进度比我快多了,而且也拿到了AI训练的机器的使用权。2.的确我也是来了新的需求。所以AI工作先告一段落。

           下面分享训练AI的思路。至于代码,就不能分享了。因为是工程代码。

         1.准备数据

            经过前面几篇关于使用卷积神经网络的blog,已经说明了为什么要准备数据。因为我现在的公司拥有大量的牌局数据,所以对我来说这个也不是什么问题。如果没有数据的话,就难搞了。

          2.重中之重,设计输入输出

            卷积神经网络比较有名的应用,就是图像识别,也适合做图像识别。那我们拿到斗地主的牌局数据又有什么用呢?其实只要打开了思维,把牌局数据转化成矩阵,输入到设计的模型里,再规定输出即可。

            而训练出来最终的模型是这样的,给定你设计的输入,AI会告诉你输出的牌型概率。比如,你给定了你的手牌作为输入,然后AI模型会给出概率出什么牌。至于概率对应的出牌,也是你设计的输出。下面给出一张图,可以作为思路参考。至于怎么做,怎么设计,还是在于自己。

            这篇文章的地址为:https://gameinstitute.qq.com/course/detail/10132#,腾讯大牛的文章。

            至于输出,我觉得都是要枚举出所有的出牌情况,把输出对应好,最后使用模型时看概率出什么牌。

            最后,学会使用卷积神经网络去训练一个AI,按照应用来说,其实是不算难的,难点在于设计模型。

    展开全文
  • 前言 这两天在网上看到一张让人涨姿势的图片,图片中展示的是贪吃蛇游戏, 估计大部分人都玩过...作为一个CSer, 第一个想到的是,这东西是程序实现的(因为,一般人干不出这事。 果断是要让程序来干的)第二个想到的

    前言

    这两天在网上看到一张让人涨姿势的图片,图片中展示的是贪吃蛇游戏, 估计大部分人都玩过。但如果仅仅是贪吃蛇游戏,那么它就没有什么让人涨姿势的地方了。 问题的关键在于,图片中的贪吃蛇真的很贪吃XD,它把矩形中出现的食物吃了个遍, 然后华丽丽地把整个矩形填满,真心是看得赏心悦目。作为一个CSer, 第一个想到的是,这东西是写程序实现的(因为,一般人干不出这事。 果断是要让程序来干的)第二个想到的是,写程序该如何实现,该用什么算法? 既然开始想了,就开始做。因为Talk is cheap,要show me the code才行。 (从耗子叔那学来的)

    开始之前,让我们再欣赏一下那只让人涨姿势的贪吃蛇吧:( 如果下面的动态图片浏览效果不佳的话,可以右键保存下来查看)

    语言选择

    Life is short, use python! 所以,根本就没多想,直接上python。

    最初版本

    先让你的程序跑起来

    首先,我们第一件要做的就是先不要去分析这个问题。 你好歹先写个能运行起来的贪吃蛇游戏,然后再去想AI部分。这个应该很简单, c\c++也就百来行代码(如果我没记错的话。不弄复杂界面,直接在控制台下跑), python就更简单了,去掉注释和空行,5、60行代码就搞定了。而且,最最关键的, 这个东西网上肯定写滥了,你没有必要重复造轮子, 去弄一份来按照你的意愿改造一下就行了。

    简单版本

    我觉得直接写perfect版本不是什么好路子。因为perfect版本往往要考虑很多东西, 直接上来就写这个一般是bug百出的。所以, 一开始我的目标仅仅是让程序去控制贪吃蛇运动,让它去吃食物,仅此而已。 现在让我们来陈述一下最初的问题:

    在一个矩形中,每一时刻有一个食物,贪吃蛇要在不撞到自己的条件下,
    找到一条路(未必要最优),然后沿着这条路运行,去享用它的美食
    

    我们先不去想蛇会越来越长这个事实,问题基本就是,给你一个起点(蛇头)和一个终点( 食物),要避开障碍物(蛇身),从起点找到一条可行路到达终点。 我们可以用的方法有:

    • BFS
    • DFS
    • A*

    只要有选择,就先选择最简单的方案,我们现在的目标是要让程序先跑起来, 优化是后话。so,从BFS开始。我们最初将蛇头位置放入队列,然后只要队列非空, 就将队头位置出队,然后把它四领域内的4个点放入队列,不断地循环操作, 直到到达食物的位置。这个过程中,我们需要注意几点:1.访问过的点不再访问。 2.保存每个点的父结点(即每个位置是从哪个位置走到它的, 这样我们才能把可行路径找出来)。3.蛇身所在位置和四面墙不可访问。

    通过BFS找到食物后,只需要让蛇沿着可行路径运动即可。这个简单版本写完后, 贪吃蛇就可以很欢快地运行一段时间了。看图吧:(不流畅的感觉来自录屏软件@_@)

    为了尽量保持简单,我用的是curses模块,直接在终端进行绘图。 从上面的动态图片可以看出,每次都单纯地使用BFS,最终有一天, 贪吃蛇会因为这种不顾后果的短视行为而陷入困境。 而且,即使到了那个时候,它也只会BFS一种策略, 导致因为当前看不到目标(食物),认为自己这辈子就这样了,破罐子破摔, 最终停在它人生中的某一个点,不再前进。(我好爱讲哲理XD)

    BFS+Wander

    上一节的简单版本跑起来后,我们认识到,只教贪吃蛇一种策略是不行的。 它这么笨一条蛇,你不多教它一点,它分分钟就会挂掉的。 所以,我写了个Wander函数,顾名思义,当贪吃蛇陷入困境后, 就别让它再BFS了,而是让它随便四处走走,散散心,思考一下人生什么的。 这个就好比你困惑迷茫的时候还去工作,效率不佳不说,还可能阻碍你走出困境; 相反,这时候你如果放下手中的工作,停下来,出去旅个游什么的。回来时, 说不定就豁然开朗,土地平旷,屋舍俨然了。

    Wander函数怎么写都行,但是肯定有优劣之分。我写了两个版本,一个是在可行的范围内, 朝随机方向走随机步。也就是说,蛇每次运动的方向是随机出来的, 总共运动的步数也是随机的。Wander完之后,再去BFS一下,看能否吃到食物, 如果可以那就皆大欢喜了。如果不行,说明思考人生的时间还不够,再Wander一下。 这样过程不断地循环进行。可是就像“随机过程随机过”一样,你“随机Wander就随机挂”。 会Wander的蛇确实能多走好多步。可是有一天,它就会把自己给随机到一条死路上了。 陷入困境还可以Wander,进入死胡同,那可没有回滚机制。所以, 第二个版本的Wander函数,我就让贪吃蛇贪到底。在BFS无解后, 告诉蛇一个步数step(随机产生step),让它在空白区域以S形运动step步。 这回运动方向就不随机了,而是有组织有纪律地运动。先看图,然后再说说它的问题:

    没错,最终还是挂掉了。S形运动也是无法让贪吃蛇避免死亡的命运。 贪吃蛇可以靠S形运动多存活一段时间,可是由于它的策略是:

    while 没有按下ESC键:
        if 蛇与食物间有路径:
            走起,吃食物去
        else:
            Wander一段时间
    

    问题就出在蛇发现它自己和食物间有路径,就二话不说跑去吃食物了。 它没有考虑到,你这一去把食物给吃了后形成的局势(蛇身布局), 完全就可能让你挂掉。(比如进入了一个自己蛇身围起来的封闭小空间)

    so,为了能让蛇活得久一些,它还要更高瞻远瞩才行。

    高瞻远瞩版本

    我们现在已经有了一个比较低端的版本,而且对问题的认识也稍微深入了一些。 现在可以进行一些比较慎密和严谨的分析了。首先,让我们罗列一些问题: (像头脑风暴那样,想到什么就写下来即可)

    • 蛇和食物间有路径直接就去吃,不可取。那该怎么办?
    • 如果蛇去吃食物后,布局是安全的,是否就直接去吃?(这样最优吗?)
    • 怎样定义布局是否安全?
    • 蛇和食物之间如果没有路径,怎么办?
    • 最短路径是否最优?(这个明显不是了)
    • 那么,如果布局安全的情况下,最短路径是否最优?
    • 除了最短路径,我们还可以怎么走?S形?最长?
    • 怎么应对蛇身越来越长这个问题?
    • 食物是随机出现的,有没可能出现无解的布局?
    • 暴力法(brute force)能否得到最优序列?(让贪吃蛇尽可能地多吃食物)

    只要去想,问题还挺多的。这时让我们以面向过程的思想,带着上面的问题, 把思路理一理。一开始,蛇很短(初始化长度为1),它看到了一个食物, 使用BFS得到矩形中每个位置到达食物的最短路径长度。在没有蛇身阻挡下, 就是曼哈顿距离。然后,我要先判断一下,贪吃蛇这一去是否安全。 所以我需要一条虚拟的蛇,它每次负责去探路。如果安全,才让真正的蛇去跑。 当然,虚拟的蛇是不会绘制出来的,它只负责模拟探路。那么, 怎么定义一个布局是安全的呢? 如果你把文章开头那张动态图片中蛇的销魂走位好好的看一下, 会发现即使到最后蛇身已经很长了,它仍然没事一般地走出了一条路。而且, 是跟着蛇尾走的!嗯,这个其实不难解释,蛇在运动的过程中,消耗蛇身, 蛇尾后面总是不断地出现新的空间。蛇短的时候还无所谓,当蛇一长, 就会发现,要想活下来,基本就只能追着蛇尾跑了。在追着蛇尾跑的过程中, 再去考虑能否安全地吃到食物。(下图是某次BFS后,得到的一个布局, 0代表食物,数字代表该位置到达食物的距离,+号代表蛇头,*号代表蛇身, -号代表蛇尾,#号代表空格,外面的一圈#号代表围墙)

    # # # # # # # 
    # 0 1 2 3 4 # 
    # 1 2 3 # 5 # 
    # 2 3 4 - 6 # 
    # 3 + * * 7 # 
    # 4 5 6 7 8 # 
    # # # # # # # 
    

    经过上面的分析,我们可以将布局是否安全定义为蛇是否可以跟着蛇尾运动, 也就是蛇吃完食物后,蛇头和蛇尾间是否存在路径,如果存在,我就认为是安全的。

    OK,继续。真蛇派出虚拟蛇去探路后,发现吃完食物后的布局是安全的。那么, 真蛇就直奔食物了。等等,这样的策略好吗?未必。因为蛇每运动一步, 布局就变化一次。布局一变就意味着可能存在更优解。比如因为蛇尾的消耗, 原本需要绕路才能吃到的食物,突然就出现在蛇眼前了。所以,真蛇走一步后, 更好的做法是,重新做BFS。然后和上面一样进行安全判断,然后再走。

    接下来我们来考虑一下,如果蛇和食物之间不存在路径怎么办? 上文其实已经提到了做法了,跟着蛇尾走。只要蛇和食物间不存在路径, 蛇就一直跟着蛇尾走。同样的,由于每走一步布局就会改变, 所以每走一步就重新做BFS得到最新布局。

    好了,问题又来了。如果蛇和食物间不存在路径且蛇和蛇尾间也不存在路径, 怎么办?这个我是没办法了,选一步可行的路径来走就是了。还是一个道理, 每次只走一步,更新布局,然后再判断蛇和食物间是否有安全路径; 没有的话,蛇头和蛇尾间是否存在路径;还没有,再挑一步可行的来走。

    上面列的好几个问题里都涉及到蛇的行走策略,一般而言, 我们会让蛇每次都走最短路径。这是针对蛇去吃食物的时候, 可是蛇在追自己的尾巴的时候就不能这么考虑了。我们希望的是蛇头在追蛇尾的过程中, 尽可能地慢。这样蛇头和蛇尾间才能腾出更多的空间,空间多才有得发展。 所以蛇的行走策略主要分为两种:

    1. 目标是食物时,走最短路径
    2. 目标是蛇尾时,走最长路径
    

    那第三种情况呢?与食物和蛇尾都没路径存在的情况下, 这个时候本来就只是挑一步可行的步子来走,最短最长关系都不大了。 至于人为地让蛇走S形,我觉得这不是什么好策略,最初版本中已经分析过它的问题了。 (当然,除非你想使用最最无懈可击的那个版本,就是完全不管食物, 让蛇一直走S,然后在墙边留下一条过道即可。这样一来, 蛇总是可以完美地把所有食物吃完,然后占满整个空间,可是就很boring了。 没有任何的意思)

    上面还提到一个问题:因为食物是随机出现的,有没可能出现无解的局面? 答案是:有。我运行了程序,然后把每一次布局都输出到log,发现会有这样的情况:

    # # # # # # # 
    # * * * * * # 
    # * * - 0 * # 
    # * * # + * # 
    # * * * * * # 
    # * * * * * # 
    # # # # # # # 
    

    其中,+号是蛇头,-号是蛇尾,*号是蛇身,0是食物,#号代表空格,外面一圈# 号代表墙。这个布局上,食物已经在蛇头面前了,可是它能吃吗?不能! 因为它吃完食物后,长度加1,蛇头就会把0的位置填上,布局就变成:

    # # # # # # # 
    # * * * * * # 
    # * * - + * # 
    # * * # * * # 
    # * * * * * # 
    # * * * * * # 
    # # # # # # # 
    

    此时,由于蛇的长度加1,蛇尾没有动,而蛇头被自己围着,挂掉了。可是, 我们却还有一个空白的格子#没有填充。按照我们之前教给蛇的策略, 面对这种情况,蛇头就只会一直追着蛇尾跑,每当它和食物有路径时, 它让虚拟的蛇跑一遍发现,得到的新布局是不安全的,所以不会去吃食物, 而是选择继续追着蛇尾跑。然后它就这样一直跑,一直跑。死循环, 直到你按ESC键为止。

    由于食物是随机出现的,所以有可能出现上面这种无解的布局。当然了, 你也可以得到完满的结局,贪吃蛇把整个矩形都填充满。

    上面的最后一个问题,暴力法是否能得到最优序列。从上面的分析看来, 可以得到,但不能保证一定得到。

    最后,看看高瞻远瞩的蛇是怎么跑的吧:

    矩形大小10*20,除去外面的边框,也就是8*18。Linux下录完屏再转成GIF格式的图片, 优化前40多M,真心是没法和Windows的比。用下面的命令优化时, 有一种系统在用生命做优化的感觉:

    convert output.gif -fuzz 10% -layers Optimize optimised.gif
    

    最后还是拿到Windows下用AE,三下五除二用图片序列合成的动态图片 (记得要在format options里选looping,不然图片是不会循环播放的)

    Last but not least

    如果对源代码感兴趣,请戳以下的链接: Code goes here

    另外,本文的贪吃蛇程序使用了curses模块, 类Unix系统都默认安装的,使用Windows的童鞋需要安装一下这个模块, 送上地址: 需要curses请戳我

    以上的代码仍然可以继续改进(现在加注释不到300行,优化一下可以更少), 也可用pygame或是pyglet库把界面做得更加漂亮,Enjoy!

    展开全文
  • Unity 如何写一个足球运动员AI(一)

    千次阅读 多人点赞 2018-05-14 22:08:17
    球员类里写一个枚举,列出各种状态,再Update里写一个Swith Case,不同状态对应不同方法,如果是射门传球这种只执行一帧的状态,则只调一次方法。 3. 把各种球员方法定义出来 下面是球员类源码: using System....
  • 前言这两天在网上看到一张让人涨姿势的图片,图片中展示的是贪吃蛇游戏, 估计大部分人都玩...作为一个CSer, 第一个想到的是,这东西是程序实现的(因为,一般人干不出这事。 果断是要让程序来干的)第二个想到的是,
  • 开始接触卷积神经网络已经有快三个星期了。... 在使用卷积神经网络时,经常看到一个概念叫做卷积核(滤波器),这个东西为什么可以提取特征呢。举个可能不恰当的例子,例如,你现在有一堆混合物(...
  • 用Python写一个贪吃蛇AI

    千次阅读 2016-06-28 23:42:51
    如何用Python写一个贪吃蛇AI 作者:Hawstein 出处:http://hawstein.com/posts/snake-ai.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载...
  • ZEGO教程:如何从零实现一个AI老师

    千次阅读 2019-04-22 11:00:32
    上周,即构推出了AI智能课堂解决方案。方案采用即构独有的“切换推流视频无缝衔接”技术,解决了AI教师视频无延迟无卡顿实时切流播放、...今天,我们将AI智能课堂教师端技术实现的方法出来,和大家分享如何从零开...
  • 集智导读:本文会为大家展示机器学习专家 Mike Shi 如何用 50 行 Python 代码创建一个 AI,使用增强学习技术,玩耍一个保持杆子平衡的小游戏。所用环境为标准的 OpenAI Gym,只使用 Numpy 来创建 agent。各位看官好...
  • 最近有一门课结束了,需要做一个井字棋的游戏,我用JavaScript一个。首先界面应该问题不大,用html稍微一下就可以。主要是人机对弈时的ai算法,如何使电脑方聪明起来,是值得思考一下的。开始游戏后,由玩家...
  • 如何写一个标准的fragment

    千次阅读 2017-08-09 01:18:00
    Q204Ai禄勾富植硬劝http://music.hao123.com/songlist/495886945?ctn=tdq 4QO0eE着疟济雷绰安http://music.hao123.com/songlist/495867926 S8eok8蓖吭詹较刑倜http://music.hao123.com/songlist/495942943?wzb=wgf...
  • 来源:机器之心本文约30000字,建议阅读10分钟。这是篇真正针对初学者的 AI 教程,不只讲概念,还讲概念的底层原理。David Code 有多身份:他是旅行作家,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 606
精华内容 242
关键字:

如何写一个ai