0%

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

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

1.准备数据

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

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

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

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

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

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

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

前几天,室友换工作了,之前的他是做客户端上位机的,换工作了之后接触到了集群、负载均衡、分布式等概念,但是不是很清楚,百度上的答案也是鸡同鸭讲,于是在群里和我讨论了这个问题。我想起我刚做服务器开发的时候,关于这些概念也不是很清晰,现在想想,应该可以用大白话描述出来。下面举个例子。

1.什么是集群

首先,我们假设这么个例子,你现在是一个刺激战场的服务器开发工程师,从0开始开发。你先开发了一个服务器A,你开发的服务器A性能峰值能容纳5000人在线玩游戏,然后部署使用。因为是新开发的游戏,玩的人不是很多。过了一段时间后,发现玩游戏的人越来越多了,现在已经有8000人要玩了,一台服务器已经撑不住了,你又开发了服务器B。让服务器A、服务器B一起运行,这样你的游戏最多就能容纳10000人游戏了。服务器A、服务器B就叫做你的游戏服务器集群。同理,你扩展到10台服务器,只要是你刺激战场游戏的服务器,都称作你的服务器集群

2.什么是负载均衡

你现在有了2台服务器,你发现你的服务器运行是这样的,8000人进行游戏,服务器A人数达到5000人了,剩下的3000人才会进入的是服务器B。这样会造成一个问题,有软件常识的人都知道,数据量越大,处理数据的时间就越长、或者空间就越大。服务器A有5000人进行游戏,已经是达到了性能峰值,玩家偶尔会感觉到服务器卡顿,例如:点了开局,过一会儿才会跳入下一个界面。就像你电脑同时开启了很多东西,虽然都能处理,但是偶尔会卡顿。你觉得这样不合理,明明有两台服务器,只要每台服务器分别处理4000个玩家,这样两个服务器都没达到峰值,8000玩家都能有良好的体验。于是乎,你就写了一些处理方法,让服务器A和服务器B处理的玩家是相同的。例如,有6000玩家进行游戏的时候,服务器A、服务器B分别处理3000玩家。这就是负载均衡

3.什么是分布式

经过一段时间的运营,你的刺激战场游戏已经有5W玩家进行游戏,峰值的时候有4W玩家同时在线。你按照上面的方法,你开发了10台服务器(服务器A、B、C….),游戏运营的都很正常。忽然有一天,有4W人同时在线时,忽然有有三台服务器挂了,连接这三台服务器的玩家全部被断开连接,然后重新连接,但是你7台服务器的性能巅峰只能容纳3.5W人。意味着有5000玩家直接玩不了,运气不好,5000玩家里刚好有几个大土豪,刚好再逛商城,准备消费,现在消费不聊了,经过一投诉,你被公司痛批了一顿,你被痛批后,开始觉得你的设计不合理。虽然4W人同时在线,但是只有3W人是再游戏的,1W人是不进入游戏的,只是再逛大厅(商城),而且你原来把所有的逻辑都写再一个服务器(登录、大厅、游戏)导致一个服务器性能巅峰只能容纳5000人。于是乎,你重新分析了需求:1.同时登陆的玩家最多有1W人,2同时在线的玩家一般有1/4是至逛大厅和商城的,剩下的3/4是进行游戏的。然后你重新设计了你的服务器,把服务器分成3类,每一类只处理一种业务,分成了登陆服务器、大厅服务器、游戏服务器。经过你这么一拆分,由于一台服务器只处理一种逻辑,服务器性能得到了提高,最高可以处理1W人的逻辑,这就是分布式。现在你重新分配服务器,10台服务器,分成2台登陆服务器,2台大厅服务器,6台游戏服务器,现在游戏在同时有4W人在线的时候,按照1/4在大厅(1W人),3/4在游戏(3W人)。这个时候,如果有3台服务器挂了,都不会出现像上次那样玩家无法连接的情况。

以上的只是举个例子帮助理解这几个概念,实际情况会更复杂,但是原理都是这样的。

开始接触卷积神经网络已经有快三个星期了。觉得还是有一些概念,要刚开始知道会比较容易理解这个东西。

1.什么是卷积神经网络

** **其实这个东西应该是这样读的,卷积 神经网络。卷积是用来提取特征的,神经网络是用来分类的。合起来就是,卷积神经网络用来提取特征然后分类的。下面继续解释

2.为什么卷积可以提取特征

** 在使用卷积神经网络时,经常看到一个概念叫做卷积核(滤波器),这个东西为什么可以提取特征呢。举个可能不恰当的例子,例如,你现在有一堆混合物(大米+红豆+大豆),要对它们进行分类,这个使用你使用一个筛子,这个筛子就类似于上面说的卷积核(滤波器)**,你通过改变它,既可以提取特征。就像你改变你的筛子大小,最小的时候,你只能筛出大米,别的东西无法通过筛子,你的卷积核就提取了大米的特征。

3.什么是神经网络

** **如上图所示,这就是一个神经网络的结构,简单来说,就是输入层(两个神经元,红色)—>隐藏层(三个神经元,绿色)—>输出层(两个神经元,黄色)。即,给定了输入,最后得到输出。为什么可以分类,我也不太清楚,可以自行百度。

4.使用卷积神经网络的实际例子

用大白话举例过程,原理是这样的,但是实际操作会有一些别的不通。比如用卷积神经网络训练了一个可以识别猫、狗图像的AI。

1.把海量的猫图片、狗图片作为输入

2.当输入猫图片时,让训练模型输出为A

3.当输入狗图片时,让训练模型输出为B

4.当训练的时候,海量的训练数据就会反复调整卷积核,不停的改变参数(也就是学习),最后拿到最优的模型

5.最后,使用时当你给定一张猫的图片的时候,猫图片输入模型,模型会预测出这张图最大概率结果是A。如果输入的是狗的图片,则这张图最大概率是B

最后有两点要说一说

** 1.其实使用卷积神经网络训练AI并没有想象的那么可怕,会自己思考,什么的七七八八。你必须事先告诉它结果。例如,你给他一巴掌作为输入时,输出为AI给你100元。当训练完之后,你给它一巴掌,它只会给你100元,并不会反过来打你一巴掌。**

** 2.要想使用卷积神经网络训练AI,必须要规定相应的输入和输出作为训练,这个输入和输出是我们程序员自己定的。**

一直是在框架上进行开发,而skynet的优点之一也是可以用lua来写逻辑,所以C调用lua这一块一直没有自己实际尝试,现在正在训练ai,刚好有时间搞搞这个。

先补充概念:

** 1.C是需要自己管理内存的,申请、释放**

** 2.lua是自动管理内存的,没有引用的变量会定时被gc**

** **那如果,c在调用lua时,如果lua变量被释放了,就悲剧了。所以需要用一个东西一直来引用lua的变量。C与lua交互就是使用虚拟栈来交互(个人理解)。

下面上代码,这些C使用lua的api可以自己去查具体的用处。

附上lua 5.3中文参考手册地址:https://www.runoob.com/manual/lua53doc/contents.html#contents

** 1.以下为C代码**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>

extern "C"
{
#include "lua.h"
#include "lauxlib.h" //这里需要真tm的注意,不是luaxlib
#include "lualib.h"
}

//调用lua中的add函数
int call_lua_add(lua_State *L)
{
lua_getglobal(L, "add"); //把虚拟机中全局变量 add 压入虚拟机L的栈,这里注意,一定要是全局变量
lua_pushnumber(L, 123); //第一个参数入栈
lua_pushnumber(L, 456); //第二个参数入栈
lua_call(L, 2, 1); //调用栈中的add函数,2个参数,1个返回值
int sum = (int)lua_tonumber(L, -1); //获取栈顶元素(上一步的返回值)
lua_pop(L, 1); //栈顶元素出战
return sum;
}

int main()
{
lua_State *L = luaL_newstate(); //新建lua虚拟机
luaL_openlibs(L); //在虚拟机中载入lua所有函数库
luaL_dofile(L, "Test.lua"); //加载 并 运行指定的文件
lua_settop(L, 0); //重新设置栈底,这个过程,是为了确认栈底是空的,以便后面的操作是按照顺序入栈的且从1号栈位开始
int ret = call_lua_add(L);
printf("调用lua文件结果为 %d\n", ret);
lua_close(L); //一定记得关闭虚拟机

return 0;
}

2.以下为lua代码

1
2
3
4
5
function add( x, y )
return x+y
end

print("你终于动手了")

3.编译c文件,然后发现报错了,原因时找不到头文件的,也就是环境没指定好

4.简单查找一下

4.然后编译,-I参数是用来指定头文件目录 (是i),然后发现报错了

这个报错是说没找到这些东西,因为这些都在lualib库里,查找一下,指定一下即可。

5.我是直接sudo cp /usr/lib/x86_64-linux-gnu/liblua5.3.so.0 . 将库拷贝到本目录,然后改名为liblua5.3.so。然后编译,-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名 (l),没报错。运行。结果如下

因为使用lua也有一段时间了,以前一直感觉C与lua交互是不难的,但是感觉是感觉,只有自己实际操作了,才有权力去说难不难。今天尝试了,的确不难。还是需要尽量都是自己去尝试了,才下结论。“有剑不用,和没有剑,不是用一个概念”

接上回书,那么如何写一个入门的简单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.读取图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.读取训练集和校验集**

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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.使用模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

以下为完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#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

由于目前游戏的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,有些东西是术语来着,我也不是专业的,但是又接触了,所以有的时候会语无伦次。**

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

写博客还是习惯先写一下,写下这篇博客的原因。前几天撸的博客《使用redis做排行榜相同积分情况下,如何使用到达时间来排序》,我使用了这个方法修改了原来写的排行榜,之前的排行榜我是没有这样做。修改之后,需要运营先暂停排行榜,将之前排行榜的分数记录,发奖之后再开启新的排行榜。但是策划最后说我忘记了,周四更新之后,导致旧方法记录的分数存在,同时新方法也在记录分数,排行榜就乱了,只能临时暂停排行榜,等我修复后再开启。

混乱的原因是redis中记录的分数,旧分数是实际分数,新分数为 score<<26 + time。这样新进排行榜的玩家就算是只得1分,也会比之前第一名的玩家分数高,这样旧乱套了。修复的方法就是修改redis中旧分数,用旧分数<<26 + time,写个lua脚本直接让redis运行即可。

redis执行lua有两种方法:

** 1.连接redis后,在命令行输入一串字符串,这串字符串是按照一定规则连接起来的lua字符串,这种方法我没有深入研究,大概如下:**

** 2.将要执行的lua脚本写成一个文件,直接运行,我用的是第二种方法,也比较推荐第二种方法,第二种方法自己写的清楚,看的清楚,而且执行方便。**

** ** lua_2_redis.lua如下

** **

执行方法如下:

** redis-cli -h 127.0.0.1 -p 36001 -a xxxxxxx –eval lua_2_redis.lua >> ret.txt**

在脚本中写return,则会把自己想要的结果返回,我这里使用了 >>ret.txt是做了重定向,把输出保存到ret.txt文件。执行结果可以看到redis中对应的分数已经被改变:

** **输出的文件ret.txt:

注意:因为redis中支持lua只是辅助,所以lua中有些方法是不支持的,例如os.time()、也不支持位操作。所以我上面时间直接写死了个计算后的时间。位操作用乘法代替。不支持的方法需要自己另外想办法解决了。

最后有个感悟,在项目中碰到问题不要怕,代码都是人写的,总会有解决方法。

在用redis写排行榜的时候,相同积分的情况要按照到达时间排序,用到了Lua的位操作,网上很少这方面的资料,写一下做简单笔记。

lua位操作其实和c语言一样,例子如下:

结果为:

实际应用可以看看我的另一篇博客,《使用redis做排行榜相同积分情况下,如何使用到达时间来排序》

好久没更新博客了,杂事多,其实还是就是自己懒惰了吧。哈哈哈哈。言归正传。

最近需求撸了个排行榜,是用redis实现的,依稀记得,前几个月去面试的时候,面试官老喜欢问我做过排行榜么,做过排行榜么,那时候还没做过,也知道是redis做的,以为会有点难度,但是其实并不难。技术就是这样的,不知道就难,知道了就简单,消除恐惧的最好方法就是面对恐惧,奥里给!

** redis的有序集合(sorted set),会元素都会关联一个double类型的分数。然后通过分数来为集合中的成员进行从小到大的排序。简直就是天生为排行榜设计的,但是有序集合(sorted set)在元素积分相同的情况下居然是字典排序**(就是同分数的情况下,使用我们存贮的元素进行排序),这就不符合我们理想中的排行榜了。

改进如下:

我们只需要在玩家的积分上关联上时间即可,例如:存入的数据为{uid,score}

1.我们需要拿到一个时间来做标准,可以是排行榜的结束时间或者是任何别的时间。我使用的是结束时间的时间戳(end_time)。

2.对score进行处理,**new_score = score << 26 + (end_time - now)**。进行位操作之后再加上时间戳。这样相同积分的情况下,越后面到达的存入的,处理之后的new_score越小。

3.存入redis的数据为{uid, new_socre}。

4.因为是使用了位操作,当需要显示积分的时候,new_score >>26即会剔除掉时间戳,显示积分。

因为是使用了位操作并且关联了时间戳,在分数上相同的概率就会很小很小了。但是这样做也是有缺陷的,即相同时间到达相同积分的,还是会按照元素进行字典排序,不过无关紧要,这种概率很低。我的需求在这种情况下相同的话,是可以按照uid排序的,所以也没有再进一步优化。如果哪位同行看到了,有更好的方法也欢迎分享。

最近项目使用到表的排序,我使用table.sort通过表的value排序,使用key进行排序暂未研究。之前一直不是很清楚table.sort这个方法,刚好有机会记录一下。

方法原型:table.sort (table, funtion(a,b))

两个入参:1.table———–需要排序的table

2.function——-排序方法,可自定义。如果不填,则按默认排序。形式是固定的,入参a,b为排序table中的value1、value2….(这个具体不知道怎么描述)

1.不传排序方法,采用自定义:

结果:

可以看到,table.sort默认是使用table中存贮的 value进行从小到大的排序,有一点需要注意的是,如果table中含有number和string,使用默认的方法进行比较会报 number和string比较的错误。也有一些别的坑,使用的时候需要谨慎(默认的我用的比较少)

2.传入自定义的排序方法:

例如传入的table是 :

传入的方法的格式是固定的,也就是一个

匿名函数function(a,b)

……..(你的实现)

end

匿名函数中的a,b是传入的table的2个value,table.sort每次会返回两个value传入比较函数。上例是t中的value。

比较函数是这样的:

结果为:

自定义的比较函数功能十分强大,但是也有一些坑。

注意:

1.table.sort并不稳定,当条件的两个元素相等时,它们在排序后的相对位置可能会改变(据说,我自己暂未出现,可能用的少)

2.要求需要排序table中间元素不能有nil,否则会报错

3.当比较的两个元素相等的时候,比较函数一定要返回false,返回true会报错,table.sort会根据你返回的bool来判断两个value是否保持原来的顺序