前言:

本文属于学习笔记性质。为了让自己更深入地理解卷积神经网络,我只用numpy、pandas等几个库手搓了一个识别MNIST数字的CNN。500张图单次训练,准确率70-80%。

注意:

1.代码并非原创,主要参考了下面的文章,我按自己的思路进行了一些改动。

(29条消息) python神经网络案例——CNN卷积神经网络实现mnist手写体识别_python cnn_腾讯数据架构师的博客-CSDN博客

2.可能有一些错误,欢迎批评指正。

3.有些地方非常话痨,还请见谅。

本网络的架构:

输入28*28分辨率的图像,

卷积层1包含8个5*5的卷积核,输出8张24*24的图像,

池化层1进行2*2最大池化,输出8张12*12的图像。

卷积层2包含16组,每组8个5*5的卷积核,输出16张8*8的图像,

池化层2进行2*2最大池化,输出16张4*4的图像。

全连接层将池化层2的结果拉直形成1*256的向量,输出1*10的one-hot编码。

0.所调用的库

import pandas as pd
import numpy as np

为了方便观察loss和特征图,可以再加一个:

import matplotlib.pyplot as plt

1.激活函数

常见激活函数主要包括relu、sigmoid、tanh、softmax等, 此处使用relu(卷积层)和softmax(最后的全连接层)。

relu函数写的非常冗长,也可以用np.nditer()简化。

dreluy:

输入:反向传播时传到relu激活层的张量。

输出:对该张量进行relu求导操作后的张量。dsoftmaxy类似。

(注:导数既可以写成是x的函数也可以写成是y的函数,例如y = x^2, dy/dx = 2x = 2*sqrt(y),因为求导操作是反向传播时才需要的,所以这里把导函数写作y的函数)

def relu(ip_array):op_array=np.zeros((ip_array.shape))if ip_array.ndim == 2:    for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):op_array[i][j] = max(0,ip_array[i][j])elif ip_array.ndim == 3:   for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):for k in range(ip_array.shape[2]):op_array[i][j][k] = max(0,ip_array[i][j][k])elif ip_array.ndim == 4: for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):for k in range(ip_array.shape[2]):for l in range(ip_array.shape[3]):op_array[i][j][k][l] = max(0,ip_array[i][j][k][l])return op_arraydef dreluy(ip_array):op_array=np.zeros((ip_array.shape))if ip_array.ndim == 2:    for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):if ip_array[i][j] >0:op_array[i][j] = 1else:op_array[i][j] = 0elif ip_array.ndim == 3:for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):for k in range(ip_array.shape[2]):if ip_array[i][j][k] >0:op_array[i][j][k] = 1else:op_array[i][j][k] = 0elif ip_array.ndim == 4:for i in range(ip_array.shape[0]):for j in range(ip_array.shape[1]):for k in range(ip_array.shape[2]):for l in range(ip_array.shape[3]):if ip_array[i][j][k][l] >0:op_array[i][j][k][l] = 1else:op_array[i][j][k][l] = 0return op_arraydef softmax(x):#softmax只允许输入一个列向量,输出一个列向量y=np.zeros(x.shape)sum1 = np.sum(np.exp(x)) for i in range(len(x)):y[i]=np.exp(x[i])/sum1return ydef dsoftmaxy(y):dy = np.eye(len(y))-yfor i in range(len(dy)):dy[i] = dy[i]*y[i]return dy

2.下面是卷积和池化操作需要的一些工具函数:

2.1. 截取待操作区域的函数(进行卷积和池化操作时用):

vertex_i和vertex_j是待操作区域的顶角坐标,输出的是以此为顶角,尺寸与卷积核或池化层尺寸相同的的一片区域。

def area(ip_array,k,i,j,filter_size,stride):'''ip_array:第k层i*j图片k:深度,如果为1,本函数返回的是顶角为i,j的待卷积/池化的单片区域,否则是第k层的区域'''vertex_i = stride*ivertex_j = stride*jif ip_array.ndim == 2:return ip_array[vertex_i: vertex_i + filter_size,vertex_j: vertex_j + filter_size]if ip_array.ndim == 3:return ip_array[k,vertex_i: vertex_i + filter_size,vertex_j: vertex_j + filter_size]

2.2. 获取一片区域中最大元素所在的位置(池化层的反向传播用):

分情况,如果由多个相同的最大元素,则只输出第一个元素的坐标

def array_argmax(ip_array):coord = np.where(ip_array == np.max(ip_array))if len(coord[0]) != 1 :return coord[0][0],coord[0][1]else:return coord[0],coord[1]

2.3. 补零(卷积层的反向传播用):

def zp(ip_array,p):  '''输入:4*4*3的图片,需要补零的层数(例如1层)输出:补零后的5*5*3的图片,最外圈为零'''  if p==0:return ip_arrayelse:if ip_array.ndim == 2:op_array = np.zeros((ip_array.shape[1] + 2*p,ip_array.shape[2] + 2*p))for k in range(1):for i in range(p,ip_array.shape[1]-p):for j in range(p,ip_array.shape[2]-p):op_array[i][j] = ip_array[i-p][j-p]return op_arrayelif ip_array.ndim == 3:op_array = np.zeros((ip_array.shape[0],ip_array.shape[1] + 2*p,ip_array.shape[2] + 2*p))for k in range(ip_array.shape[0]):for i in range(p,op_array.shape[1]-p):for j in range(p,op_array.shape[2]-p):op_array[k][i][j] = ip_array[k][i-p][j-p]return op_array

2.4. 卷积:

def conv(ip_array,kernel,b,stride=1):'''输入:无深度:4*4的图片,2*2的核,步长有深度:4*4*2的图片,一个2*2*2的核,步长输出:一张卷积后的2*2图片,深度被积分掉'''       if ip_array.ndim == 2:op_rows = int((ip_array.shape[0] - kernel.shape[1])/stride + 1)op_cols = int((ip_array.shape[1] - kernel.shape[1])/stride + 1)op_array = np.zeros((op_rows,op_cols))for k in range(1):for i in range(op_rows):for j in range(op_cols):op_array[i][j] = (area(ip_array, k, i, j, kernel.shape[1],stride=1)*kernel).sum() + b    if ip_array.ndim == 3:op_rows = int((ip_array.shape[1] - kernel.shape[2])/stride + 1)op_cols = int((ip_array.shape[2] - kernel.shape[2])/stride + 1)op_array = np.zeros((ip_array.shape[0],op_rows,op_cols))for k in range(ip_array.shape[0]):for i in range(op_rows):for j in range(op_cols):op_array[k][i][j] = (area(ip_array, k, i, j, kernel.shape[2],stride=1)*kernel[k]).sum() + bop_array = np.sum(op_array,axis=0)return op_array

2.5.生成卷积核:

生成的卷积核是一个四维张量,一共有num组,depth个size*size的方形卷积核。

num就是当前卷积层一共要用多少组卷积核进行卷积。depth就是原图有几个通道,或者上一个卷积层输出了几张图像。

class Kernel:'''输入:核的size、depth和数量get_W:返回5*5的权重矩阵W,深度3get_b:返回一个偏置b,深度3update:得到微分矩阵后更新W和b'''def __init__(self, size, depth, nums):self.nums = numsself.depth = depth       self.val = 1/np.sqrt(size**2*depth+size**2*nums)self.W = np.random.uniform(-self.val,self.val,(nums, depth, size, size))self.b = np.zeros(self.nums)def get_W(self):return self.Wdef get_b(self):return self.b

3.以下是网络的主体:

3.1.卷积层

class Conv:def __init__(self,ip_rows,ip_cols,depth,ker_size,ker_num,act_name="relu",lr=0.005,stride=1):self.ip_rows = ip_rowsself.ip_cols = ip_colsself.depth = depthself.ker_size = ker_sizeself.stride = strideself.actor = act_nameself.ker_num = ker_numself.kernel_groups = Kernel(self.ker_size,self.depth,self.ker_num)self.op_rows = int((self.ip_rows - ker_size)/stride + 1)self.op_cols = int((self.ip_cols - ker_size)/stride + 1)self.op_array = np.zeros((self.ker_num,self.op_rows,self.op_cols))self.lr=lrdef act(self,x):if self.actor == "relu":return relu(x)elif self.actor == "sigmoid":return sigm(x)def dact(self,y):if self.actor == "relu":return dreluy(y)elif self.actor == "sigmoid":return dsigmy(y)def forward(self,ip_array):           for l in range(self.ker_num):self.op_array[l] = conv(ip_array,self.kernel_groups.get_W()[l],self.kernel_groups.get_b()[l],self.stride)self.op_array = self.act(self.op_array)return self.op_arraydef backward(self,ip_array,grad_array,first_layer="N"):#grad_array是上一层传到本层的梯度,一共是ker_num张self.delta_array = np.zeros(grad_array.shape)for num in range(self.ker_num):self.delta_array[num] = self.dact(self.forward(ip_array))[num]*grad_array[num]self.dW = np.zeros((self.ker_num,self.depth,self.ker_size,self.ker_size))self.db = np.zeros(self.ker_num)for num in range(self.ker_num):self.db[num] = np.sum(self.delta_array[num])   self.kernel_groups.get_b()[num] += -self.lr*self.db[num]for dep in range(self.depth):if ip_array.ndim == 2:self.dW[num][dep] = conv(ip_array,self.delta_array[num],0,self.stride)if ip_array.ndim == 3:self.dW[num][dep] = conv(ip_array[dep],self.delta_array[num],0,self.stride)self.kernel_groups.get_W()[num][dep] += -self.lr*self.dW[num][dep]   self.padded_grad = zp(self.delta_array,self.ker_size-1) # ker_num个        self.next_grad = np.zeros(ip_array.shape)  # depth个if first_layer == "N":for depth in range(ip_array.shape[0]):for num1 in range(self.ker_num):self.next_grad[depth] += conv(self.padded_grad[num1],np.rot90(self.kernel_groups.get_W()[num1][depth],2),0)return self.next_gradelse:pass

这里主要说一下反向传播。

假设某卷积层的输入(ip_array)为2通道4*4的图,用3组2通道2*2的卷积核进行卷积,则输出(op_array)是3张3*3的图。则反向传播时,传到本层的梯度(grad_array)应该是,具体而言是3张3*3的array。不妨记第j个array为

接下来要求的是:; ;和要传播的梯度

根据正向传播过程:

可以推导出反向传播的公式(这里的星号有的表示点积,有的表示卷积,懒得区分了):

对b_j,公式1输出的是一张3*3的array,而b_j是一个数,如何对b进行更新呢?

实际上,正向传播时,op_j中的每个元素都是ip1与W_1j、ip_2与W_2j的分别卷积并加和,然后再加上b_j得到的。因此,最终L的对b_j的导数是上面的公式输出的array中所有元素的加和。

如果不好理解,不妨看下面的例子:

y1 = W1∗x1+b, y2 = W2∗x2 + b, z = f(y1) + g(y2),

因此

dz/db = dz/dy1*dy1/db + dz/dy2*dy2/db = f'(y1) + g'(y2)

对W_ij,从以下例子可以发现,A在op_array的四个元素中系数依次是1,2,4,5,也就是说op_array每个元素对A的导数应该分别是1,2,4,5;即A的导数是op1'*1+op2'*2+op3'*4+op4'*5,这正是梯度矩阵与ip_i卷积的结果。因此,公式2的最后一个偏微分是第i张ip_array,即ip_i,最后一个星号对应的是卷积操作。

类似地,对ip_i,由于op_j是ip_i和W_ij卷积得到,因此公式3的最后一个偏微分似乎应该是W_ij,最后一个星号是卷积操作。但实际操作比较复杂:首先需要把进行补零,需满足补零后的array与W_ij卷积时,卷积结果与ip_i的尺寸一致。然后让补零后的矩阵与翻转了180°的W_ij进行卷积。这操作类似普通神经网络反向传播时要对权重矩阵W进行转置。我也不知道怎么很直观的理解这件事,但你按上面那张图进行类似地分析就知道这个操作得到的结果是正确的。

小结,因为卷积操作比点乘复杂,而且卷积核参数过多(组数*每组通道数*行*列),所以卷积层的反向传播有点麻烦。我的心得是,只要记住传播到某层的梯度肯定与你要更新的东西具有相同的形状,就不会被绕晕或者忘记某个操作。

3.2.池化层

class Maxpooling:'''输入:输入图片的行数、列数、深度,池化尺寸和步长。forward功能:输入4*4*k的待处理图片,输出2*2*k的池化结果backward功能(上池化):以k=1为例。正向传播时,输入4*4的图片A,输出2*2的结果。反向传播时,先创建一个与A同形状的零矩阵D。导数矩阵del(2,2)中的格点[m][n]对应D中的一个2*2区域:D[m*stride : m*stride + pool_size][n*stride : n*stride + pool_size],D在该区域中仅有一个非零元素x,其位置就是A在该区域中最大元素所处的位置,其数值就是del[m][n]返回D。'''       def __init__(self,ip_rows,ip_cols,depth,pool_size,stride=2):self.ip_rows = ip_rowsself.ip_cols = ip_colsself.depth = depthself.pool_size = pool_sizeself.stride = strideself.op_rows = int((self.ip_rows - pool_size)/stride + 1)self.op_cols = int((self.ip_cols - pool_size)/stride + 1)def forward(self,ip_array):self.op_array = np.zeros((self.depth,self.op_rows,self.op_cols))for k in range(self.depth):  #池化在卷积后面,因此输入的图片一定是多层的for i in range(self.op_rows):for j in range(self.op_cols):self.op_array[k][i][j] = np.max(area(ip_array[k],k,i,j,self.pool_size,self.stride))return self.op_arraydef backward(self,ip_array,sens_array):self.back_array = np.zeros((ip_array.shape))for k in range(self.depth):for m in range(self.op_rows):for n in range(self.op_cols):    i,j = array_argmax(area(ip_array[k],k,m,n,self.pool_size,self.stride))self.back_array[k][m*self.stride+i,n*self.stride+j] = sens_array[k][m][n].copy() return self.back_array

池化层的反向传播就容易多了,已经在注释里说明了。

3.3.全连接层

class NN:    '''单层全连接BP神经网络。初始化:输入维度、神经元个数(即输出维度)、激活函数为的名字(sigmoid,relu或softmax)foward(X):对input进行一次正向传播act/dact:根据输入的激活函数名字指定激活函数backward(grad):设本层输出为z2,则应输入∂L/∂z2,该函数据此更新W2和b2,并给出∂L/∂z1'''def __init__(self,ipsize,opsize,act_name,lr=0.002):self.ipsize = ipsizeself.opsize = opsizeself.actor = act_nameself.val = 1/np.sqrt(self.ipsize + self.opsize)self.W = np.random.uniform(-self.val,self.val,(self.opsize,self.ipsize))self.b = np.zeros(self.opsize)self.z = np.zeros(self.opsize)self.lr = lrdef act(self,x):if self.actor == "relu":return relu(x)elif self.actor == "sigmoid":return sigm(x)elif self.actor == "softmax":return softmax(x)def dact(self,y):if self.actor == "relu":return dreluy(y)elif self.actor == "sigmoid":return dsigmy(y)elif self.actor == "softmax":return dsoftmaxy(y)    def forward(self,X):self.X = Xself.h = np.dot(self.W,self.X) + self.bself.y = self.act(self.h)return self.ydef backward(self,grad):if self.actor == "softmax":self.del2 = np.dot(self.dact(self.y),grad)else:self.del2 = self.dact(self.y)*grad self.del1 = np.dot(self.W.T,self.del2)self.W += -self.lr*np.outer(self.del2,self.X.T)  self.b += -self.lr*self.del2return self.del1class CNN:  def __init__(self):self.conv1 = Conv(28,28,1,5,8,lr=0.01)self.pool1 = Maxpooling(24,24,8,2)self.conv2 = Conv(12,12,8,5,16,lr=0.005)self.pool2 = Maxpooling(8,8,16,2)self.fc1 = NN(256,10,"softmax",lr=0.002)def forward(self,one_pic):self.conved1 = self.conv1.forward(one_pic)self.pooled1 = self.pool1.forward(self.conved1)self.conved2 = self.conv2.forward(self.pooled1)self.pooled2 = self.pool2.forward(self.conved2)self.res = self.fc1.forward(self.pooled2.flatten().reshape(-1))return self.resdef backward(self,one_pic,Y):self.forward(one_pic)self.grad0 = -Y/self.resself.gradfc1 = self.fc1.backward(self.grad0)self.gradfc1 = self.gradfc1.reshape(16,4,4)self.gradp2 = self.pool2.backward(self.conved2,self.gradfc1)self.gradc2 = self.conv2.backward(self.pooled1,self.gradp2)self.gradp1 = self.pool1.backward(self.conved1,self.gradc2)self.conv1.backward(one_pic,self.gradp1,first_layer="Y")

4.整合

损失函数为交叉熵

class CNN:  def __init__(self):self.conv1 = Conv(28,28,1,5,8,lr=0.01)self.pool1 = Maxpooling(24,24,8,2)self.conv2 = Conv(12,12,8,5,16,lr=0.005)self.pool2 = Maxpooling(8,8,16,2)self.fc1 = NN(256,10,"softmax",lr=0.002)def forward(self,one_pic):self.conved1 = self.conv1.forward(one_pic)self.pooled1 = self.pool1.forward(self.conved1)self.conved2 = self.conv2.forward(self.pooled1)self.pooled2 = self.pool2.forward(self.conved2)self.res = self.fc1.forward(self.pooled2.flatten().reshape(-1))return self.resdef backward(self,one_pic,Y):self.forward(one_pic)self.grad0 = -Y/self.resself.gradfc1 = self.fc1.backward(self.grad0)self.gradfc1 = self.gradfc1.reshape(16,4,4)self.gradp2 = self.pool2.backward(self.conved2,self.gradfc1)self.gradc2 = self.conv2.backward(self.pooled1,self.gradp2)self.gradp1 = self.pool1.backward(self.conved1,self.gradc2)self.conv1.backward(one_pic,self.gradp1,first_layer="Y")

5.训练测试部分

注:MINST_DATA和MINST_TRAIN是把MINST的数据库转化为了CSV格式。该格式中,第一列为实际数字,第2-785列为展开后的28*28像素。

定义一个用来看loss的函数:

def plot_curve(data):fig=plt.figure()plt.plot(range(len(data)),data,color="red")plt.legend(["loss"],loc="upper right")plt.xlabel("step")plt.ylabel("loss")plt.show()
if __name__ == '__main__':#数据预处理,将图片进行Z-score标准化,标签进行one-hot转码num1 = 600iter = 1CNN1 = CNN()data_csv=pd.read_csv("C://mnist_train.csv")X=data_csv.iloc[:num1,1:].values/255for i in range(X.shape[0]):miu=np.mean(X[i])sigma=np.std(X[i])for j in range(X.shape[1]):X[i][j]=(X[i][j]-miu)/sigma.copy()   X=X.reshape(num1,28,28)Y=data_csv.iloc[:num1,0].valuesY_oh=np.zeros((len(Y),10))for i in range(len(Y)):Y_oh[i][Y[i]]=1     #随便看一张图plt.matshow(X[0])    loss=[]    for epoch in range(iter):y=np.zeros(len(Y))right=0for i in range(X.shape[0]):CNN1.backward(X[i],Y_oh[i])if np.argmax(CNN1.forward(X[i])) == Y[i]:right += 1if i%10==0:print(np.argmax(CNN1.forward(X[i])),Y[i])loss.append(np.sum(-Y_oh[i]*np.log(CNN1.forward(X[i]))))if i%100==0:plot_curve(loss)print("epoch no.%s"%epoch,"accurancy:",right/len(Y))#看一下训练完的卷积核长啥样for i in range(4):plt.matshow(CNN1.conv1.kernel_groups.get_W()[i])for i in range(2):for j in range(2):plt.matshow(CNN1.conv2.kernel_groups.get_W()[i][j])#开始测试    print("\ntest begins:\n")num2 = 100test_csv=pd.read_csv("C://mnist_train.csv")X1=test_csv.iloc[:num2,1:].values/255for i in range(X1.shape[0]):miu=np.mean(X1[i])sigma=np.std(X1[i])for j in range(X1.shape[1]):X1[i][j]=(X1[i][j]-miu)/sigma.copy()   X1=X1.reshape(num2,28,28)Y1=test_csv.iloc[:num2,0].valuesright1 = 0for i in range(X1.shape[0]):if np.argmax(CNN1.forward(X1[i])) == Y1[i]:right1 += 1if i%4==0:print(np.argmax(CNN1.forward(X1[i])),Y1[i])#看一下特征图长啥样for i in range(8):plt.matshow(CNN1.conv1.forward(X1[i]))print("test accurancy:",right1/len(Y1))plt.show()

训练结果:

打印结果:

epoch no.0 accurancy: 0.7816666666666666

test begins:

test accurancy: 0.8

第一个卷积层的前四个卷积核:

后两个核看起来比较明显,是检测斜线特征的,看一下对应的特征图:

手搓卷积神经网络(CNN)进行手写数字识别(python)相关推荐

  1. 深度学习(DL)与卷积神经网络(CNN)学习笔记随笔-04-基于Python的LeNet之MLP

    原文地址可以查看更多信息 本文主要参考于:Multilayer Perceptron  python源代码(github下载 CSDN免费下载) 本文主要介绍含有单隐层的MLP的建模及实现.建议在阅读 ...

  2. 深度学习(DL)与卷积神经网络(CNN)学习笔记随笔-03-基于Python的LeNet之LR

    原地址可以查看更多信息 本文主要参考于:Classifying MNIST digits using Logistic Regression  python源代码(GitHub下载 CSDN免费下载) ...

  3. 卷积神经网络(三):卷积神经网络CNN的简单实现(部分Python源码)

    转载自: 卷积神经网络(三):卷积神经网络CNN的简单实现(部分Python源码) - xuanyuansen的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/xu ...

  4. 深蓝学院第三章:基于卷积神经网络(CNN)的手写数字识别实践

    参看之前篇章的用全连接神经网络去做手写识别:https://blog.csdn.net/m0_37957160/article/details/114105389?spm=1001.2014.3001 ...

  5. BP神经网络实现手写数字识别Python实现,带GUI手写画板

    BP神经网络实现手写数字识别 BP神经网络模型 用tkinter编写用于手写输入的画板 程序运行的效果截图 在B站看了一个机器学习基础的视频( 链接)后,发现到资料里面有一个用BP神经网络对手写数字进 ...

  6. lenet5实现手写数字识别---python

    ​​​​​​lenet5实现手写数字识别 一. 导入数据集 下载数据集 查看第一张图片的尺寸 输出数据集规模 可知图片尺寸为28*28像素,通道数为1.训练集包含55000张图片,验证集包含5000张 ...

  7. KNN实现手写数字识别Python

    本文章总结了KNN算法字手写数字识别的应用,比较系统的对传统KNN和各种改进的KNN算法进行实现,由于完整的代码量较大(部分代码参考了机器学习实战),不适合全部贴出,但是博主乐于分享,有需要的话可以直 ...

  8. python opencv数字识别_基于模板匹配的手写数字识别(python+opencv)

    智能计算课第一周的实验是做基于模板匹配的手写数字识别,光听见就很感兴趣,于是决定认真做做这个实验,本实验基于python3+opencv的python版本,所用到的知识都比较简单,基本上边学边做,技术 ...

  9. 基于cnn的人脸识别_基于卷积神经网络(CNN)的人脸在线识别系统

    微信搜索"AI大道理",选择"置顶"公众号 重磅干货,深入讲解AI大道理 ------ 本设计研究人脸识别技术,基于卷积神经网络构建了一套人脸在线检测识别系统, ...

最新文章

  1. lpk.dll是什么
  2. python反转链表和成对反转
  3. 企业组网为组织带来什么便利性和实用性?—Vecloud微云
  4. 【c++】5.函数传指针与传值特容易混淆的点
  5. linux 用户态 spinlock,spinlock作用
  6. [Python学习笔记]h5py入门讲解
  7. 当 JS 工程师遇到了 TypeScript 会发生什么?
  8. c理c利用计算机怎么弹,通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的...
  9. ajax获取301,PHP获取301重定向页面跳转后真实URL地址
  10. 计算机基础【面试遇到】
  11. js 用指定字符分割字符串
  12. 我开源了一个私人云盘——蓝眼云盘
  13. ionic刷新html页面,Ionic实现页面下拉刷新(ion-refresher)功能代码
  14. 喜茶门店总数超过650家,深圳单城市突破100家店
  15. 阿里云服务器ECS windows server已开放端口但连不上的问题
  16. 门限签名技术火了,它的硬核之处绝不仅仅是秘密共享……
  17. 计算机与机器人论文参考文献,机器人论文参考文献(2016年10月21日)
  18. 三跨考生准备考研复试(机试)之路(日记版)
  19. CodeM资格赛D 送外卖 题解
  20. 【人工智能哲学01/2】人工智能前世今生

热门文章

  1. python应用在人工智能_python应用篇-大数据和人工智能时代下,python牛在哪?
  2. 安卓 Handler 消息机制(总)
  3. Entering emergency mode
  4. catkin_make命令
  5. 2014年2月15日 19:04:39
  6. 读书笔记-哈佛大学极简经济学1.
  7. php+手办,不做成HUB真可惜了 桌面新摆件诞生——ROG官方手办
  8. python人工智能编程教程入门_Python人工智能教程让你从入门到践
  9. 群晖 python_利用群晖Docker安装ubuntu16.04搭建python网站服务器(安装篇)
  10. Python计算合理避税后收入增加情况