目录
一、背景与总概
二、研究对象
三、数据表示与结构
四、选择的模型
五、效果展示
六、画板部分
七、直接抄,直接方向跑!
所有的工程文件百度云连接,包含模型!
链接:https://pan.baidu.com/s/1RlS0aVPOFwmNBPBHmcF0SQ
提取码:MATR
--来自百度网盘超级会员V3的分享
注意:人工智能预测的闪退问题是在main文件同目录下没有image_rgzn的文件夹,添加就好。
一、背景与总概
利用Python语言编写和调试一个识别手写数字图像的三层深度前馈网络,包括数据预处理,网络模型构建,模型参数初始化和正向推理,反向梯度下降参数寻优,最后模型预测的功能。目的是学会基本的深度网络模型建立、训练和推理过程,理解深度网络的实现原理。
通过自己学习人工智能之后,发现了三个的重要经验和总结,第一个是你对你研究事物本质的理解;第二个是,将你研究事物进行数据化,找到一个合理的数据表示以及数据的结构;第三个是,寻找与这个数据表示的形式和结构合适的“模型”(即模型算法等)。依据上面数据到模型的这个思路,对下文进行一个描写
首先,介绍一下MNIST手写数据集,这就是我们研究的事物了。该数据集包含60,000个用于训练的示例和10,000个用于测试的示例。数据集包含了0-9共10类手写数字图片,每张图片都做了尺寸归一化,都是28x28大小的灰度图。
训练集图像:train-images-idx3-ubyte.gz(9.9MB,包含60000个样本)
训练集标签:train-labels-idx1-ubyte.gz(29KB,包含60000个标签)
测试集图像:t10k-images-idx3-ubyte.gz(1.6MB,包含10000个样本)
测试集标签:t10k-labels-idx1-ubyte.gz(5KB,包含10000个标签)
下图展示一些具体例子:
其次是手写板程序,在PyQt5实例 画板小程序_pyqt5画图板_CreatorGG的博客-CSDN博客的程序基础之上添加了,1人工智能预测,2预测结果展示的文本框。
传统机器学习的问题与缺陷随着深度学习的发展被得到解决,深度学习也可以说是神经网络的重命名,他是建立在多层非线性的神经网络结构之上,对数据表示进行抽象的一系列机器学习。深度学习的出现使得图像,语言得到突破性的发展。本此处理的数据为图像,所以最后我本次选用的模型是人工智能深度神经网络(一般的神经网络)。你也可以使用卷积神经网络模型,卷积神经网络是对将局部的特征十分的敏感,正确率会更高。
二、研究对象
在你的PycharmIDE里创建一个function.py的文件,在里面定义如下函数:
1:jiexi_image(path),此函数需要一个字符串对象的输入,是两个训练集和测试集的图像文件地址,返回的对象是一个numpy.array的对象。
def jiexi_image(path): # 用二进制读取 data = open(path, 'rb').read() offset = 0 fmt_header = '>iiii' magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, data, offset) print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols)) image_size = num_rows * num_cols offset += struct.calcsize(fmt_header) fmt_image = '>' + str(image_size) + 'B' images = np.empty((num_images, num_rows, num_cols)) for i in range(num_images): if (i + 1) % 10000 == 0: print('已解析 %d' % (i + 1) + '张') images[i] = np.array(struct.unpack_from(fmt_image, data, offset)).reshape((num_rows, num_cols)) offset += struct.calcsize(fmt_image) return images
2: jiexi_label(path) ,传入参数是训练集和测试集的两个label标签文件地址,是一个字符串对象,返回的也是一个numpy.array的对象。
def jiexi_label(path): data = open(path, 'rb').read() offset = 0 fmt_header = '>ii' magic_number, num_images = struct.unpack_from(fmt_header, data, offset) print('魔数:%d, 图片数量: %d张' % (magic_number, num_images)) # 解析数据集 offset += struct.calcsize(fmt_header) fmt_image = '>B' labels = np.empty(num_images) for i in range(num_images): if (i + 1) % 10000 == 0: print('已解析 %d' % (i + 1) + '张') labels[i] = struct.unpack_from(fmt_image, data, offset)[0] offset += struct.calcsize(fmt_image) return labels
3:plot_data(images,labels,n,issave = False),传入图像,image是一个numpy.array对象;传入的标签,labels是一个numpy.array对象;传入的issave是一个判断逻辑值,如果是真就保存图片,但是一般是不保存的。
def plot_data(images,labels,n,issave = False): for i in range(n): print(labels[i]) plt.imshow(images[i], cmap='gray') plt.show() # if(issave == True): # plt.savefig(fname = "save"+str(datetime.datetime.now())+".jpg") print('done')
接下来在你的工程文件夹下建立一个train.py 文件,在里面利用function.py里你设定的函数来解析训练集图像和测试集图像,训练集标签和测试集标签,然后利用plot_data函数打印数据,查看是否对应。
import function#start1 = time.time()train_image_path = './MNIST/train-images-idx3-ubyte/train-images.idx3-ubyte'train_lable_path = './MNIST/train-labels-idx1-ubyte/train-labels.idx1-ubyte'teat_image_path = './MNIST/t10k-images-idx3-ubyte/t10k-images.idx3-ubyte'teat_lable_path = './MNIST/t10k-labels-idx1-ubyte/t10k-labels.idx1-ubyte'# #加载数据train_image = function.jiexi_image(train_image_path)train_lable = function.jiexi_label(train_lable_path)teat_image = function.jiexi_image(teat_image_path)test_lable = function.jiexi_label(teat_lable_path)# print(train_image.shape)function.plot_data(train_image, train_lable, 10, True)
最终效果:
可以看出是正确的。
三、数据表示与结构
既然train_image, train_lable, teat_image , test_lable 是numpy.array对象,那么我们就可以对他进行操作,对他进行打印输出,print(train_image):
什么也看不出来,那我们打印一下他的维度来看看, ,是一个三维数组。
打印train_image[0]来看:
然后可以推知,我们要处理的对象结构是如下图所示。
四、选择的模型
传统机器学习的问题与缺陷随着深度学习的发展被得到解决,深度学习也可以说是神经网络的重命名,他是建立在多层非线性的神经网络结构之上,对数据表示进行抽象的一系列机器学习。深度学习的出现使得图像,语言得到突破性的发展。本此处理的数据为图像,所以最后我本次选用的模型是人工智能深度神经网络(一般的神经网络)。你也可以使用卷积神经网络模型,卷积神经网络是对将局部的特征十分的敏感,正确率会更高。
在你的工程文件下创建一个DeepNET.py的文件,里面是深度神经网络所需要的各种函数。从零开始,从理论到代码实现无论是在研究和学习都是十分有帮助的,希望我和各位读者都保持住这个习惯。
本次,假设你已经有一定的知识储备了,如梯度下降法的本质,神经网络结构基本清楚,如果不清楚就十分推荐,deeplearning的吴大师的视频教程 [双语字幕]吴恩达深度学习deeplearning.ai_哔哩哔哩_bilibili 教的非常细致。
第一步,导入库,在DeepNET.py的文件里完善搭建深度神经网络所需要的函数。
深度神经网络概述,Deep Neural Networks, 深度神经网络,以下简称DNN。DNN里最基本的单元是神经元模型。每个神经元与其他神经元相连,当他“兴奋”时,就会向相连的神经元发送物质,改变神经元的电位。如果某个神经元的电位超过了一个阀值,那么它就会被激活。结果抽象可以得到沿用至今的M_P神经元模型。
线性部分,是简单的相乘相加,激活部分是利用激活函数处理得到输出。常见的激活函数有sigmoid,relu等,本次采用的激活函数是relu函数。
由神经元组成的多层神经网络,如图所示。有输入层,输出层以及中间隐含层。每一个输入线性求合,通过激活函数,传到下一个神经元,我们大可不必一个个的去算,我们可以使用向量化来使得我们的程序更加简洁。
梯度下降法:
(下面为了简单我以输入的对象是28*28规格图片,第一层隐含单元有200个,第二层隐含单元是100个,输出层为10个的网络结构阐述。)
一般的构造网络的流程:
初始化超参数(包括启动深度神经网络的权值w,偏执b)--》向前传播(线性部分+激活函数)--》 计算代价 --》 反向传播 (激活函数反向,线性部分反向)--》更新超参数.
基础部分和总概 网络结构为 [28*28 200 100 10]
学习人工智能,应该理论应用于实践,应该多动手进行数学演算,将演算用代码实现,最后进行总结于改进。
(1)对这个网络的结构要有一个清楚的认识
在草稿本上进行矩阵维数的测试
下面是对于想要了解代码一个个看清楚流程的去看,如果想直接跑通,代码在最后!
1、导入库,是导入一些必要的库
import matplotlib.pyplot as pltimport numpy as npimport datetimeimport osimport sysimport h5py
2、定义所需要的softmax函数
def sigmoid(Z): A = 1/(1+np.exp(-Z)) cache = Z return A, cachedef sigmoid_backward(dA, cache): Z = cache s = 1/(1+np.exp(-Z)) dZ = dA * s * (1-s) return dZ# relu函数 和反向求导def relu(Z): A = np.maximum(0,Z) cache = Z return A, cachedef relu_backward(dA, cache): Z = cache dZ = np.array(dA, copy=True) dZ[Z <= 0] = 0 return dZ# Softmaxdef softmax(Z): A = np.exp(Z)/np.sum(np.exp(Z),axis=0) cache = Z return A, cache
在此我把relu和sigmoid函数也给出,方便读者后续的使用,可以利用这个去做逻辑回归啊什么什么的,增加泛用性。
3、初始化参数
使用梯度下降法的首要步骤就是初始化参数,这个点是随机的。
输入的是 layers_dims是一个向量,是你的网络结构。返回的是一个字典对象parameters,里面有超参数w和b。、
恭喜你,走出了第一步,完成了初始化参数的步骤。
def init_W(layers_dims): np.random.seed(3) parameters = {} L = len(layers_dims) for l in range(1, L): parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) / np.sqrt(layers_dims[l - 1]) parameters["b" + str(l)] = np.zeros((layers_dims[l], 1)) return parameters
4.1、向前传播之线性部分
,也就是输入x与权值相乘与偏执相加。是一层的线性部分。
%20def%20L_forword_sum(W,A,b):%20%20%20%20Z%20=%20np.dot(W,A)+b%20%20%20%20cache%20=%20(A,W,b)%20%20%20%20return%20Z,cache
%204.2、向前传播之激活函数向前
%20%20 %20 %20 %20隐含层使用的是relu函数,输出层使用的是softmax函数,参考(311条消息)%20入门级都能看懂的softmax详解_bitcarmanlee的博客-CSDN博客。
%20(311条消息)%20ReLU函数简介_潇湘_AQ的博客-CSDN博客
%20本函数是基于线性部分建立的一个输入是A_prev是上一层的输出,W,b是本层的网络参数,activation是激活函数的名字,用于选择用哪一个函数。返回值是一个元组,包含A隐含层输出的激活值,和用于反向传播时的重要数据。
%20def%20L_activate_forworld(A_prev,W,b,activation):%20%20%20%20if%20activation%20==%20"relu":%20%20%20%20%20%20%20%20Z%20,linear_cache%20=%20%20L_forword_sum(W,A_prev,b)%20%20%20%20%20%20%20%20A,%20activation_cache%20=%20relu(Z)%20%20%20%20elif%20activation%20==%20"sigmoid":%20%20%20%20%20%20%20%20Z,%20linear_cache%20=%20L_forword_sum(W,%20A_prev,%20b)%20%20%20%20%20%20%20%20A,%20activation_cache%20=%20sigmoid(Z)%20%20%20%20elif%20activation%20==%20"softmax":%20%20%20%20%20%20%20%20Z,%20linear_cache%20=%20L_forword_sum(W,%20A_prev,%20b)%20%20%20%20%20%20%20%20A,%20activation_cache%20=%20softmax(Z)%20%20%20%20cache%20=%20(linear_cache,%20activation_cache)%20%20%20%20return%20A,cache
%204.3、向前传播函数接口
%20%20 %20 %20 %20线性部分+激活函数组成一次向前传播,线性部分的输出输入到激活函数最后得到的最终值。
%20%20 %20 %20 %20函数输入x是初始的输入值即图片的大小,parameters是超参数字典。经过一个for循环可以完成整个向前传播。
%20%20 %20 %20 %20恭喜你,你的网络利用这个函数就可以进行向前传播了。
%20def%20L_forword(X,%20parameters):%20%20%20%20caches%20=%20[]%20%20%20%20A%20=%20X%20%20%20%20L%20=%20len(parameters)%20//%202%20%20%20%20for%20l%20in%20range(1,%20L):%20%20%20%20%20%20%20%20A_prev%20=%20A%20%20%20%20%20%20%20%20A,%20cache%20=%20L_activate_forworld(A_prev,%20parameters['W'%20+%20str(l)],%20parameters['b'%20+%20str(l)],%20"relu")%20%20%20%20%20%20%20%20caches.append(cache)%20%20%20%20#%20最后一层使用softmax%20%20%20%20AL,%20cache%20%20=%20L_activate_forworld(A,%20parameters['W'%20+%20str(L)],%20parameters['b'%20+%20str(L)],%20"softmax")%20%20%20%20caches.append(cache)%20%20%20%20return%20AL,%20caches
%205、计算代价之交叉熵代价函数
%20(311条消息)%20交叉熵损失函数(Cross%20Entropy%20Loss)_SongGu1996的博客-CSDN博客
%20%20 %20 %20 %20你可以计算你的网络的代价了,每个网络都希望做到收敛快,代价小。
%20def%20cost(Y_out,Y):%20%20%20%20cost%20=%20-np.sum(np.multiply(np.log(Y_out),%20Y))%20/%20Y_out.shape[1]%20%20%20%20cost%20=%20np.squeeze(cost)%20%20%20%20return%20cost
%205.1、反向传播之线性部分的反向
%20%20 %20 %20 %20感兴趣的可以到我的草稿上看。
%20def%20linear_backward(dZ,%20cache):%20%20%20%20A_prev,%20W,%20b%20=%20cache%20%20%20%20m%20=%20A_prev.shape[1]%20%20%20%20dW%20=%20np.dot(dZ,%20A_prev.T)%20/%20m%20%20%20%20db%20=%20np.sum(dZ,%20axis=1,%20keepdims=True)%20/%20m%20%20%20%20dA_prev%20=%20np.dot(W.T,%20dZ)%20%20%20%20return%20dA_prev,%20dW,%20db
%205.2、反向传播之激活函数的反向
%20感兴趣的可以到我的草稿上看。
%20def%20linear_activation_backward(dA,%20cache,%20Y,activation="relu"):%20%20%20%20linear_cache,%20activation_cache%20=%20cache%20%20%20%20if%20activation%20==%20"relu":%20%20%20%20%20%20%20%20dZ%20=%20relu_backward(dA,%20activation_cache)%20%20%20%20%20%20%20%20dA_prev,%20dW,%20db%20=%20linear_backward(dZ,%20linear_cache)%20%20%20%20elif%20activation%20==%20"sigmoid":%20%20%20%20%20%20%20%20dZ%20=%20sigmoid_backward(dA,%20activation_cache)%20%20%20%20%20%20%20%20dA_prev,%20dW,%20db%20=%20linear_backward(dZ,%20linear_cache)%20%20%20%20elif%20activation%20==%20"softmax":%20%20%20%20%20%20%20%20dZ%20=%20dA%20-%20Y%20%20%20%20%20%20%20%20dA_prev,%20dW,%20db%20=%20linear_backward(dZ,%20linear_cache)%20%20%20%20return%20dA_prev,%20dW,%20db
%205.3、%20反向传播函数接口
%20%20 %20 %20 %20将激活函数的反向和线性部分的反向组织好后,封装为一个反向函数接口。
%20%20 %20 %20 %20恭喜你,你已经做好了反向传播了。
%20def%20L_model_backward(AL,%20Y,%20caches,case):%20%20%20%20grads%20=%20{}%20%20%20%20L%20=%20len(caches)%20%20%20%20m%20=%20AL.shape[1]%20%20%20%20Y%20=%20Y.reshape(AL.shape)%20%20%20%20dAL%20=%20-%20(np.divide(Y,%20AL)%20-%20np.divide(1%20-%20Y,%201%20-%20AL))%20%20%20%20if%20case%20==%20"softmax":%20%20%20%20%20%20%20%20current_cache%20=%20caches[L%20-%201]%20%20%20%20%20%20%20%20grads["dA"%20+%20str(L)],%20grads["dW"%20+%20str(L)],%20grads["db"%20+%20str(L)]%20=%20linear_activation_backward(AL,%20current_cache,Y,"softmax")%20%20%20%20elif%20case%20%20==%20"sigmoid":%20%20%20%20%20%20%20%20current_cache%20=%20caches[L%20-%201]%20%20%20%20%20%20%20%20grads["dA"%20+%20str(L)],%20grads["dW"%20+%20str(L)],%20grads["db"%20+%20str(L)]%20=%20linear_activation_backward(AL,%20current_cache,Y,%20"sigmoid")%20%20%20%20for%20l%20in%20reversed(range(L%20-%201)):%20%20%20%20%20%20%20%20current_cache%20=%20caches[l]%20%20%20%20%20%20%20%20dA_prev_temp,%20dW_temp,%20db_temp%20=%20linear_activation_backward(grads["dA"%20+%20str(l%20+%202)],%20current_cache,%20Y%20,"relu")%20%20%20%20%20%20%20%20grads["dA"%20+%20str(l%20+%201)]%20=%20dA_prev_temp%20%20%20%20%20%20%20%20grads["dW"%20+%20str(l%20+%201)]%20=%20dW_temp%20%20%20%20%20%20%20%20grads["db"%20+%20str(l%20+%201)]%20=%20db_temp%20%20%20%20return%20grads
%206、利用梯度下降法%20更新参数
%20%20 %20 %20 %20 梯度下降法是一个十分好的优化算法,他的目的更改参数使代价优化到最小。
%20def%20update_parameters(parameters,%20grads,%20learning_rate):%20%20%20%20L%20=%20len(parameters)%20//%202%20%20#%20整除%20%20%20%20for%20l%20in%20range(L):%20%20%20%20%20%20%20%20parameters["W"%20+%20str(l%20+%201)]%20=%20parameters["W"%20+%20str(l%20+%201)]%20-%20learning_rate%20*%20grads["dW"%20+%20str(l%20+%201)]%20%20%20%20%20%20%20%20parameters["b"%20+%20str(l%20+%201)]%20=%20parameters["b"%20+%20str(l%20+%201)]%20-%20learning_rate%20*%20grads["db"%20+%20str(l%20+%201)]%20%20%20%20return%20parameters
%207、神经网络搭建
%20%20 %20 %20 依据下面的过程,将你的函数,像拼拼图一样做出来。
%20初始化超参数(包括启动深度神经网络的权值w,偏执b)--》向前传播(线性部分+激活函数)--》%20计算代价%20--》%20反向传播%20(激活函数反向,线性部分反向)--》更新超参数
%20%20 %20 %20 %20for循环是来进行重复训练的。.
%20X,%20输入的样本集;Y,label集;
%20net_layers,网络结构;learning_rate=0.0075,%20学习率;num_iterations=3000,迭代次数;step%20=1,%20步长;print_cost=False,%20是否打印代价;isPlot=True,是否绘制代价—迭代次数图;
%20def%20deepnet(X,%20Y,net_layers,learning_rate=0.0075,%20num_iterations=3000,step%20=1,%20print_cost=False,%20isPlot=True):%20%20%20%20np.random.seed(1)%20#设计种子%20%20%20%20costs%20=%20[]%20#用于画图%20%20%20%20parameters%20=%20init_W(net_layers)%20%20%20%20for%20i%20in%20range(0,%20num_iterations):%20%20%20%20%20%20%20%20#%20迭代%20%20%20%20%20%20%20%20AL,%20caches%20=%20L_forword(X,%20parameters)%20%20%20%20%20%20%20%20costi%20=%20cost(AL,%20Y)%20#这里的Y是标准化的Y%20%20%20%20%20%20%20%20grads%20=%20L_model_backward(AL,%20Y,%20caches,"softmax")%20%20%20%20%20%20%20%20parameters%20=%20update_parameters(parameters,%20grads,%20learning_rate)%20%20%20%20%20%20%20%20if%20i%20%%20step%20==%200:%20%20%20%20%20%20%20%20%20%20%20%20#%20记录成本%20%20%20%20%20%20%20%20%20%20%20%20costs.append(costi)%20%20%20%20%20%20%20%20%20%20%20%20#%20是否打印成本值%20%20%20%20%20%20%20%20%20%20%20%20if%20print_cost:%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print("第",%20i,%20"次迭代,成本值为:",%20np.squeeze(costi))%20%20%20%20if%20isPlot:%20%20%20%20%20%20%20%20plt.plot(np.squeeze(costs))%20%20%20%20%20%20%20%20plt.ylabel('cost')%20%20%20%20%20%20%20%20plt.xlabel('iterations%20(per%20tens)')%20%20%20%20%20%20%20%20plt.title("Learning%20rate%20="%20+%20str(learning_rate))%20%20%20%20%20%20%20%20plt.show()%20%20%20%20%20%20%20%20#%20plt.savefig(fnme%20=%20"cast"+str(datetime.datetime.now())+".jig")%20%20%20%20return%20parameters
%208、测试集合测试
%20%20 %20 %20 %20将得到的模型,用于测试集看准确率。
%20def%20predict(X,%20y,%20parameters,Y_org):%20%20%20%20m%20=%20X.shape[1]%20%20%20%20n%20=%20len(parameters)%20//%202%20%20#%20神经网络的层数%20%20%20%20p%20=%20np.zeros((1,%20m))%20%20%20%20#%20根据参数前向传播%20%20%20%20probas,%20caches%20=%20L_forword(X,%20parameters)%20%20%20%20p%20=%20np.argmax(probas,axis=0)%20%20%20%20zql%20=%20float(np.sum((p%20==%20Y_org))%20/%20m)%20%20%20%20print("准确度为:%20"%20+%20str(float(np.sum((p%20==%20Y_org))%20/%20m)))%20%20%20%20error_list%20=%20[]%20%20%20%20for%20i%20in%20range(m):%20%20%20%20%20%20%20%20if%20p[i]%20!=%20Y_org[i]:%20%20%20%20%20%20%20%20%20%20%20%20error_list.append(i)%20%20%20%20return%20p,error_list,zql
%209、保存模型
%20%20 %20 %20 %20你所训练好的网络不仅仅只是训练好就完了,你要应用它,就得保存下来,我这里有两个保存函数,一个是保存为txt文本,一个是保存为h5文件,建议使用h5文件,也可以像vvg19那样使用.mat文件数据,只要是数据,你就可以打开,打开的接口可以自己写,如果自己写的话一般是学习其结构,转化为二进制在转化为你想要的数据,不过,基本有大佬写出来了,不要闭门造车。
%20def%20save_model(parameters):%20%20%20%20np.set_printoptions(threshold=sys.maxsize)%20%20%20%20model_number%20=%200%20%20%20%20f%20=%20open("model/model"%20+%20str(model_number)%20+%20".txt",%20"a+")%20%20%20%20f.write(str(datetime.datetime.now())%20+%20"\n")%20%20%20%20f.write("model_number%20"%20+%20str(model_number)%20+%20"\n")%20%20%20%20for%20i,%20j%20in%20parameters.items():%20%20%20%20%20%20%20%20f.write(str(i)%20+%20"\n")%20%20%20%20%20%20%20%20f.write(str(j)%20+%20"\n")%20%20%20%20f.close()%20%20%20%20return%200#保存为h5数据格式def%20save_h5(data,layers,zql):%20%20%20%20str1%20=%20"./model/model"+str(datetime.datetime.now().strftime("%Y%m%d%H%M%S"))+".h5"%20%20%20%20f%20=%20h5py.File(str1,%20"w")%20%20%20%20ID%20=%20["model%20layer%20"]%20%20%20%20f.create_dataset("layers",data%20=%20layers)%20%20%20%20i%20=%20len(data)%20//%202%20%20%20%20for%20j%20in%20range(i):%20%20%20%20%20%20%20%20f.create_dataset("W"+str(j+1),data%20=%20data["W"+str(j+1)])%20%20%20%20%20%20%20%20f.create_dataset("b"+str(j+1),data%20=%20data["b"+str(j+1)])%20%20%20%20f.create_dataset("accuracy",data%20=%20zql)%20%20%20%20f.close()
%2010、利用模型预测和读取h5数据格式的模型
%20%20 %20 %20 %20保存好了数据,就是如何读取我们的网络参数,进行运用人工智能深度神经网络。利用这两个函数就可以。
%20def%20predict1(X,%20parameters):%20%20%20%20#%20根据参数前向传播%20%20%20%20probas,%20caches%20=%20L_forword(X,%20parameters)%20%20%20%20p%20=%20np.argmax(probas,axis=0)%20%20%20%20return%20pdef%20read_ccs(path):%20%20%20%20w%20=%20h5py.File(path,%20"r")%20%20%20%20layers%20=%20w["layers"][:]%20%20%20%20l%20=%20len(layers)%20%20%20%20p%20=%20{}%20%20%20%20#%20print(l)%20%20%20%20for%20i%20in%20range(1,%20l):%20%20%20%20%20%20%20%20p["W"%20+%20str(i)]%20=%20w["W"%20+%20str(i)][:]%20%20%20%20%20%20%20%20p["b"%20+%20str(i)]%20=%20w["b"%20+%20str(i)][:]%20%20%20%20return%20p,%20layers
%20第二步,有了相应功能的函数,我们就可以进行训练。train.py里的具体内容如下。
%20import%20DeepNETimport%20timeimport%20functionif%20__name__%20==%20'__main__':%20%20%20%20#%20计时开始%20%20%20%20start1%20=%20time.time()%20%20%20%20train_image_path%20=%20'./MNIST/train-images-idx3-ubyte/train-images.idx3-ubyte'%20%20%20%20train_lable_path%20=%20'./MNIST/train-labels-idx1-ubyte/train-labels.idx1-ubyte'%20%20%20%20teat_image_path%20=%20'./MNIST/t10k-images-idx3-ubyte/t10k-images.idx3-ubyte'%20%20%20%20teat_lable_path%20=%20'./MNIST/t10k-labels-idx1-ubyte/t10k-labels.idx1-ubyte'%20%20%20%20#%20#加载数据%20%20%20%20train_image%20=%20function.jiexi_image(train_image_path)%20%20%20%20train_lable%20=%20function.jiexi_label(train_lable_path)%20%20%20%20teat_image%20=%20function.jiexi_image(teat_image_path)%20%20%20%20test_lable%20=%20function.jiexi_label(teat_lable_path)%20%20%20%20#%20print(train_image.shape)%20%20%20%20function.plot_data(train_image,train_lable,10,True)%20%20%20%20train_image%20=%20train_image.reshape(train_image.shape[0],%20-1).T%20/%20255%20%20%20%20teat_image%20=%20teat_image.reshape(teat_image.shape[0],%20-1).T%20/%20255%20%20%20%20train_lable1%20=%20function.label_init(train_lable)%20%20%20%20test_lable1%20=%20%20function.label_init(test_lable)%20%20%20%20print(train_image.shape)%20%20%20%20end1%20=%20time.time()%20%20%20%20start2%20=%20time.time()%20%20%20%20layers%20=%20[784,%20200,%20150,%2010]%20%20%20%20parameters%20=%20DeepNET.deepnet(train_image,%20train_lable1,layers%20,%20learning_rate=0.0075,%20num_iterations=3000,%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20step=100,%20print_cost=True,%20isPlot=True)%20%20%20%20end2%20=%20time.time()%20%20%20%20p%20,error_list_train,zql1%20=%20DeepNET.predict(train_image,%20train_lable1,%20parameters,%20train_lable)%20%20%20%20p0%20,error_list_test%20,zql2%20=%20DeepNET.predict(teat_image,test_lable1,parameters,test_lable)%20%20%20%20zql%20=%20[[zql1],[zql2]]%20%20%20%20print("数据加载时间:",end1-start1,"%20秒")%20%20%20%20print("模型训练时间:",end2-start2,"%20秒")%20%20%20%20DeepNET.save_h5(parameters,layers,zql)
%20五、效果展示
%20(1)训练情况
%20
(2)模型保存情况 h5数据情况
最终效果
综合来说,没有加优化正确率也是不错的,感兴趣的可以对网络进行优化。
六、画板部分
这里参考的是(311条消息) PyQt5实例 画板小程序_pyqt5画图板_CreatorGG的博客-CSDN博客
对qtpy5感兴趣的可以去学习一下。
整个手绘板加训练的程序在最后给出。
七、直接抄,直接方向跑!
训练网络,运行train.py。
训练出自己的网络后,使用main.py运行利用使用你的模型。
文件结构和目录:
创建我红线的文件。
(1)function.py代码:
#此文件是一些函数 有加载数据模块import datetimeimport structimport numpy as npimport matplotlib.pyplot as pltfrom PIL import Imageimport osdef jiexi_image(path): # 用二进制读取 data = open(path, 'rb').read() offset = 0 fmt_header = '>iiii' magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, data, offset) print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols)) image_size = num_rows * num_cols offset += struct.calcsize(fmt_header) fmt_image = '>' + str(image_size) + 'B' images = np.empty((num_images, num_rows, num_cols)) for i in range(num_images): if (i + 1) % 10000 == 0: print('已解析 %d' % (i + 1) + '张') images[i] = np.array(struct.unpack_from(fmt_image, data, offset)).reshape((num_rows, num_cols)) offset += struct.calcsize(fmt_image) return imagesdef jiexi_label(path): data = open(path, 'rb').read() offset = 0 fmt_header = '>ii' magic_number, num_images = struct.unpack_from(fmt_header, data, offset) print('魔数:%d, 图片数量: %d张' % (magic_number, num_images)) # 解析数据集 offset += struct.calcsize(fmt_header) fmt_image = '>B' labels = np.empty(num_images) for i in range(num_images): if (i + 1) % 10000 == 0: print('已解析 %d' % (i + 1) + '张') labels[i] = struct.unpack_from(fmt_image, data, offset)[0] offset += struct.calcsize(fmt_image) return labelsdef plot_data(images,labels,n,issave = False): for i in range(n): print(labels[i]) plt.imshow(images[i], cmap='gray') plt.show() # if(issave == True): # plt.savefig(fname = "save"+str(datetime.datetime.now())+".jpg") print('done')## 说明:输入原始图像路径和新建图像文件夹名称 默认修改出长度宽度为64*64def stdimage(pathorg, name, pathnew=None, width=64, length=64): # 检查文件是否建立 if pathnew == None: # 如果没有手动创建 tage = os.path.exists(os.getcwd() + '\\' + name) # 检查一下是否属实 if not tage: # 没有整个新文件夹 os.mkdir(os.getcwd() + "\\" + name) # 创建文件夹,name image_path = os.getcwd() + "\\" + name + "\\" else: # 已经手动创建 tage = os.path.exists(pathnew + "\\" + name) if not tage: path = os.getcwd() os.mkdir(path + "\\" + name) image_path = path + "\\" + name + "\\" ## 开始处理 i = 1 # 从一开始 list_name = os.listdir(pathorg) # 获取图片名称列表 for item in list_name: # 检查是否有图片 tage = os.path.exists(pathorg + str(i) + '.png') if not tage: image = Image.open(pathorg + '\\' + item) std = image.resize((width, length), Image.ANTIALIAS) ## 模式为RGB if not std.mode == "RGB": std = std.convert('RGB') std.save(image_path + str(i) + '.png') i += 1def label_init(lable): n = lable.shape[0] label_Y = np.zeros([10, n]) res = lable.astype(int) for i in range(0, label_Y.shape[1]): label_Y[res[i], i] = 1 return label_Ydef get_X(path): im_name_list = os.listdir(path) all_data = [] for item in im_name_list: try: all_data.append(plt.imread(path + '\\' + item).tolist()) except: print(item + " open error ") return all_data
(2)DeepNet.py
import matplotlib.pyplot as pltimport numpy as npimport datetimeimport osimport sysimport h5py# sigmoiddef sigmoid(Z): A = 1/(1+np.exp(-Z)) cache = Z return A, cachedef sigmoid_backward(dA, cache): Z = cache s = 1/(1+np.exp(-Z)) dZ = dA * s * (1-s) return dZ# relu函数 和反向求导def relu(Z): A = np.maximum(0,Z) cache = Z return A, cachedef relu_backward(dA, cache): Z = cache dZ = np.array(dA, copy=True) dZ[Z <= 0] = 0 return dZ# Softmaxdef softmax(Z): A = np.exp(Z)/np.sum(np.exp(Z),axis=0) cache = Z return A, cache# 初始化w# def INIT_W(n_x,n_h1,n_h2,n_y):# W1 = np.random.randn(n_h1, n_x ) * 0.01# b1 = np.zeros((n_h1, 1))# W2 = np.random.randn(n_h2,n_h1)*0.01# b2 = np.zeros((n_h2,1))# W3 = np.random.randn(n_y, n_h2) * 0.01# b3 = np.zeros((n_y, 1))# INIT = {# "W1" : W1,# "b1" : b1,# "W2" : W2,# "b2" : b2,# "W3" : W3,# "b3" : b3# }# return INITdef init_W(layers_dims): np.random.seed(3) parameters = {} L = len(layers_dims) for l in range(1, L): parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) / np.sqrt(layers_dims[l - 1]) parameters["b" + str(l)] = np.zeros((layers_dims[l], 1)) return parameters# 向前def L_forword_sum(W,A,b): Z = np.dot(W,A)+b cache = (A,W,b) return Z,cachedef L_activate_forworld(A_prev,W,b,activation): if activation == "relu": Z ,linear_cache = L_forword_sum(W,A_prev,b) A, activation_cache = relu(Z) elif activation == "sigmoid": Z, linear_cache = L_forword_sum(W, A_prev, b) A, activation_cache = sigmoid(Z) elif activation == "softmax": Z, linear_cache = L_forword_sum(W, A_prev, b) A, activation_cache = softmax(Z) cache = (linear_cache, activation_cache) return A,cachedef L_forword(X, parameters): caches = [] A = X L = len(parameters) // 2 for l in range(1, L): A_prev = A A, cache = L_activate_forworld(A_prev, parameters['W' + str(l)], parameters['b' + str(l)], "relu") caches.append(cache) # 最后一层使用softmax AL, cache = L_activate_forworld(A, parameters['W' + str(L)], parameters['b' + str(L)], "softmax") caches.append(cache) return AL, caches#计算代价def cost(Y_out,Y): cost = -np.sum(np.multiply(np.log(Y_out), Y)) / Y_out.shape[1] cost = np.squeeze(cost) return cost#线性返回def linear_backward(dZ, cache): A_prev, W, b = cache m = A_prev.shape[1] dW = np.dot(dZ, A_prev.T) / m db = np.sum(dZ, axis=1, keepdims=True) / m dA_prev = np.dot(W.T, dZ) return dA_prev, dW, dbdef linear_activation_backward(dA, cache, Y,activation="relu"): linear_cache, activation_cache = cache if activation == "relu": dZ = relu_backward(dA, activation_cache) dA_prev, dW, db = linear_backward(dZ, linear_cache) elif activation == "sigmoid": dZ = sigmoid_backward(dA, activation_cache) dA_prev, dW, db = linear_backward(dZ, linear_cache) elif activation == "softmax": dZ = dA - Y dA_prev, dW, db = linear_backward(dZ, linear_cache) return dA_prev, dW, dbdef L_model_backward(AL, Y, caches,case): grads = {} L = len(caches) m = AL.shape[1] Y = Y.reshape(AL.shape) dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL)) if case == "softmax": current_cache = caches[L - 1] grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(AL, current_cache,Y,"softmax") elif case == "sigmoid": current_cache = caches[L - 1] grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(AL, current_cache,Y, "sigmoid") for l in reversed(range(L - 1)): current_cache = caches[l] dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l + 2)], current_cache, Y ,"relu") grads["dA" + str(l + 1)] = dA_prev_temp grads["dW" + str(l + 1)] = dW_temp grads["db" + str(l + 1)] = db_temp return gradsdef update_parameters(parameters, grads, learning_rate): L = len(parameters) // 2 # 整除 for l in range(L): parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads["dW" + str(l + 1)] parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)] return parametersdef deepnet(X, Y,net_layers,learning_rate=0.0075, num_iterations=3000,step =1, print_cost=False, isPlot=True): np.random.seed(1) #设计种子 costs = [] #用于画图 parameters = init_W(net_layers) for i in range(0, num_iterations): # 迭代 AL, caches = L_forword(X, parameters) costi = cost(AL, Y) #这里的Y是标准化的Y grads = L_model_backward(AL, Y, caches,"softmax") parameters = update_parameters(parameters, grads, learning_rate) if i % step == 0: # 记录成本 costs.append(costi) # 是否打印成本值 if print_cost: print("第", i, "次迭代,成本值为:", np.squeeze(costi)) if isPlot: plt.plot(np.squeeze(costs)) plt.ylabel('cost') plt.xlabel('iterations (per tens)') plt.title("Learning rate =" + str(learning_rate)) plt.show() # plt.savefig(fnme = "cast"+str(datetime.datetime.now())+".jig") return parametersdef predict(X, y, parameters,Y_org): m = X.shape[1] n = len(parameters) // 2 # 神经网络的层数 p = np.zeros((1, m)) # 根据参数前向传播 probas, caches = L_forword(X, parameters) p = np.argmax(probas,axis=0) zql = float(np.sum((p == Y_org)) / m) print("准确度为: " + str(float(np.sum((p == Y_org)) / m))) error_list = [] for i in range(m): if p[i] != Y_org[i]: error_list.append(i) return p,error_list,zqldef save_model(parameters): np.set_printoptions(threshold=sys.maxsize) model_number = 0 f = open("model/model" + str(model_number) + ".txt", "a+") f.write(str(datetime.datetime.now()) + "\n") f.write("model_number " + str(model_number) + "\n") for i, j in parameters.items(): f.write(str(i) + "\n") f.write(str(j) + "\n") f.close() return 0#保存为h5数据格式def save_h5(data,layers,zql): str1 = "./model/model"+str(datetime.datetime.now().strftime("%Y%m%d%H%M%S"))+".h5" f = h5py.File(str1, "w") ID = ["model layer "] f.create_dataset("layers",data = layers) i = len(data) // 2 for j in range(i): f.create_dataset("W"+str(j+1),data = data["W"+str(j+1)]) f.create_dataset("b"+str(j+1),data = data["b"+str(j+1)]) f.create_dataset("accuracy",data = zql) f.close()def predict1(X, parameters): # 根据参数前向传播 probas, caches = L_forword(X, parameters) p = np.argmax(probas,axis=0) return pdef read_ccs(path): w = h5py.File(path, "r") layers = w["layers"][:] l = len(layers) p = {} # print(l) for i in range(1, l): p["W" + str(i)] = w["W" + str(i)][:] p["b" + str(i)] = w["b" + str(i)][:] return p, layers
(3)train.py
import DeepNETimport timeimport functionif __name__ == '__main__': # 计时开始 start1 = time.time() train_image_path = './MNIST/train-images-idx3-ubyte/train-images.idx3-ubyte' train_lable_path = './MNIST/train-labels-idx1-ubyte/train-labels.idx1-ubyte' teat_image_path = './MNIST/t10k-images-idx3-ubyte/t10k-images.idx3-ubyte' teat_lable_path = './MNIST/t10k-labels-idx1-ubyte/t10k-labels.idx1-ubyte' # #加载数据 train_image = function.jiexi_image(train_image_path) train_lable = function.jiexi_label(train_lable_path) teat_image = function.jiexi_image(teat_image_path) test_lable = function.jiexi_label(teat_lable_path) # print(train_image.shape) function.plot_data(train_image,train_lable,10,True) train_image = train_image.reshape(train_image.shape[0], -1).T / 255 teat_image = teat_image.reshape(teat_image.shape[0], -1).T / 255 train_lable1 = function.label_init(train_lable) test_lable1 = function.label_init(test_lable) print(train_image.shape) end1 = time.time() start2 = time.time() layers = [784, 200, 150, 10] parameters = DeepNET.deepnet(train_image, train_lable1,layers , learning_rate=0.0075, num_iterations=3000, step=100, print_cost=True, isPlot=True) end2 = time.time() p ,error_list_train,zql1 = DeepNET.predict(train_image, train_lable1, parameters, train_lable) p0 ,error_list_test ,zql2 = DeepNET.predict(teat_image,test_lable1,parameters,test_lable) zql = [[zql1],[zql2]] print("数据加载时间:",end1-start1," 秒") print("模型训练时间:",end2-start2," 秒") DeepNET.save_h5(parameters,layers,zql)
(4) main.py
# 加载库from MainWidget import MainWidgetfrom PyQt5.QtWidgets import QApplicationimport sysdef main(): app = QApplication(sys.argv) mainWidget = MainWidget() # 新建一个主界面 mainWidget.show() # 显示主界面 exit(app.exec_()) # 进入消息循环if __name__ == '__main__': main()
(5)mainwidget.py
使用画板程序之前得跟该你的模型路径名字。就是红色部分。
def yuce(self):
# #标准化图片 获取Y
savePath = "./image_rgzn/test.png"
image = self.__paintBoard.GetContentAsQImage()
image.save(savePath)
img = Image.open(savePath)
img = img.convert("I")
img = img.resize((28, 28))
x = np.array(img)
train_image = x.reshape(1, -1).T / 255
w,layer = DeepNET.read_ccs("./model/model20221119225104.h5")
p = DeepNET.predict1(train_image,w)
self.__text_out.setText(str(p[0]))
print(p)
# print("hello")
# res = QMessageBox.information(self,"人工智能判断为:",str(p),QMessageBox.Yes|QMessageBox.No)
# res.exec_()
# 读取数据权重
# 预测并输出
'''Created on 2018年8月8日@author: Freedom'''from PyQt5.Qt import QWidget, QColor, QPixmap, QIcon, QSize, QCheckBoxfrom PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QPushButton, QSplitter, \ QComboBox, QLabel, QSpinBox, QFileDialog,QTextEditfrom PaintBoard import PaintBoardimport numpy as npfrom PIL import Imageimport DeepNETclass MainWidget(QWidget): def __init__(self, Parent=None): ''' Constructor ''' super().__init__(Parent) self.__InitData() # 先初始化数据,再初始化界面 self.__InitView() def __InitData(self): ''' 初始化成员变量 ''' self.__paintBoard = PaintBoard(self) # 获取颜色列表(字符串类型) self.__colorList = QColor.colorNames() def __InitView(self): ''' 初始化界面 ''' self.setFixedSize(640, 480) self.setWindowTitle("PaintBoard Example PyQt5") # 新建一个水平布局作为本窗体的主布局 main_layout = QHBoxLayout(self) # 设置主布局内边距以及控件间距为10px main_layout.setSpacing(10) # 在主界面左侧放置画板 main_layout.addWidget(self.__paintBoard) # 新建垂直子布局用于放置按键 sub_layout = QVBoxLayout() # 设置此子布局和内部控件的间距为10px sub_layout.setContentsMargins(10, 10, 10, 10) self.__btn_Clear = QPushButton("清空画板") self.__btn_Clear.setParent(self) # 设置父对象为本界面 # 将按键按下信号与画板清空函数相关联 self.__btn_Clear.clicked.connect(self.__paintBoard.Clear) sub_layout.addWidget(self.__btn_Clear) self.__btn_yuce = QPushButton("人工智能预测") self.__btn_yuce.setParent(self) # 设置父对象为本界面 self.__btn_yuce.clicked.connect(lambda:self.yuce()) sub_layout.addWidget(self.__btn_yuce) self.__text_out = QTextEdit(self) self.__text_out.setParent(self) self.__text_out.setObjectName("预测结果为:") sub_layout.addWidget(self.__text_out) self.__btn_Quit = QPushButton("退出") self.__btn_Quit.setParent(self) # 设置父对象为本界面 self.__btn_Quit.clicked.connect(self.Quit) sub_layout.addWidget(self.__btn_Quit) self.__btn_Save = QPushButton("保存作品") self.__btn_Save.setParent(self) self.__btn_Save.clicked.connect(self.on_btn_Save_Clicked) sub_layout.addWidget(self.__btn_Save) self.__cbtn_Eraser = QCheckBox(" 使用橡皮擦") self.__cbtn_Eraser.setParent(self) self.__cbtn_Eraser.clicked.connect(self.on_cbtn_Eraser_clicked) sub_layout.addWidget(self.__cbtn_Eraser) splitter = QSplitter(self) # 占位符 sub_layout.addWidget(splitter) self.__label_penThickness = QLabel(self) self.__label_penThickness.setText("画笔粗细") self.__label_penThickness.setFixedHeight(20) sub_layout.addWidget(self.__label_penThickness) self.__spinBox_penThickness = QSpinBox(self) self.__spinBox_penThickness.setMaximum(40) self.__spinBox_penThickness.setMinimum(2) self.__spinBox_penThickness.setValue(20) # 默认粗细为10 self.__spinBox_penThickness.setSingleStep(2) # 最小变化值为2 self.__spinBox_penThickness.valueChanged.connect( self.on_PenThicknessChange) # 关联spinBox值变化信号和函数on_PenThicknessChange sub_layout.addWidget(self.__spinBox_penThickness) self.__label_penColor = QLabel(self) self.__label_penColor.setText("画笔颜色") self.__label_penColor.setFixedHeight(20) sub_layout.addWidget(self.__label_penColor) self.__comboBox_penColor = QComboBox(self) self.__fillColorList(self.__comboBox_penColor) # 用各种颜色填充下拉列表 self.__comboBox_penColor.currentIndexChanged.connect( self.on_PenColorChange) # 关联下拉列表的当前索引变更信号与函数on_PenColorChange sub_layout.addWidget(self.__comboBox_penColor) main_layout.addLayout(sub_layout) # 将子布局加入主布局 def __fillColorList(self, comboBox): index_black = 0 index = 0 for color in self.__colorList: if color == "black": index_black = index index += 1 pix = QPixmap(70, 20) pix.fill(QColor(color)) comboBox.addItem(QIcon(pix), None) comboBox.setIconSize(QSize(70, 20)) comboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents) comboBox.setCurrentIndex(index_black) def on_PenColorChange(self): color_index = self.__comboBox_penColor.currentIndex() color_str = self.__colorList[color_index] self.__paintBoard.ChangePenColor(color_str) def on_PenThicknessChange(self): penThickness = self.__spinBox_penThickness.value() self.__paintBoard.ChangePenThickness(penThickness) def on_btn_Save_Clicked(self): savePath = QFileDialog.getSaveFileName(self, 'Save Your Paint', '.\\', '*.png') print(savePath) if savePath[0] == "": print("Save cancel") return image = self.__paintBoard.GetContentAsQImage() image.save(savePath[0]) def on_cbtn_Eraser_clicked(self): if self.__cbtn_Eraser.isChecked(): self.__paintBoard.EraserMode = True # 进入橡皮擦模式 else: self.__paintBoard.EraserMode = False # 退出橡皮擦模式 def Quit(self): self.close() def yuce(self): # #标准化图片 获取Y savePath = "./image_rgzn/test.png" image = self.__paintBoard.GetContentAsQImage() image.save(savePath) img = Image.open(savePath) img = img.convert("I") img = img.resize((28, 28)) x = np.array(img) train_image = x.reshape(1, -1).T / 255 w,layer = DeepNET.read_ccs("./model/model20221119225104.h5") p = DeepNET.predict1(train_image,w) self.__text_out.setText(str(p[0])) print(p) # print("hello") # res = QMessageBox.information(self,"人工智能判断为:",str(p),QMessageBox.Yes|QMessageBox.No) # res.exec_() # 读取数据权重 # 预测并输出
(6)paintboard.py
'''Created on 2018年8月9日@author: Freedom'''from PyQt5.QtWidgets import QWidgetfrom PyQt5.Qt import QPixmap, QPainter, QPoint, QPaintEvent, QMouseEvent, QPen, \ QColor, QSizefrom PyQt5.QtCore import Qtclass PaintBoard(QWidget): def __init__(self, Parent=None): ''' Constructor ''' super().__init__(Parent) self.__InitData() # 先初始化数据,再初始化界面 self.__InitView() def __InitData(self): self.__size = QSize(280, 280) # 新建QPixmap作为画板,尺寸为__size self.__board = QPixmap(self.__size) self.__board.fill(Qt.black) # 用白色填充画板 self.__IsEmpty = True # 默认为空画板 self.EraserMode = False # 默认为禁用橡皮擦模式 self.__lastPos = QPoint(0, 0) # 上一次鼠标位置 self.__currentPos = QPoint(0, 0) # 当前的鼠标位置 self.__painter = QPainter() # 新建绘图工具 self.__thickness = 10 # 默认画笔粗细为10px self.__penColor = QColor("white") # 设置默认画笔颜色为黑色 self.__colorList = QColor.colorNames() # 获取颜色列表 def __InitView(self): # 设置界面的尺寸为__size self.setFixedSize(self.__size) def Clear(self): # 清空画板 self.__board.fill(Qt.black) self.update() self.__IsEmpty = True def ChangePenColor(self, color="black"): # 改变画笔颜色 self.__penColor = QColor(color) def ChangePenThickness(self, thickness=10): # 改变画笔粗细 self.__thickness = thickness def IsEmpty(self): # 返回画板是否为空 return self.__IsEmpty def GetContentAsQImage(self): # 获取画板内容(返回QImage) image = self.__board.toImage() return image def paintEvent(self, paintEvent): # 绘图事件 # 绘图时必须使用QPainter的实例,此处为__painter # 绘图在begin()函数与end()函数间进行 # begin(param)的参数要指定绘图设备,即把图画在哪里 # drawPixmap用于绘制QPixmap类型的对象 self.__painter.begin(self) # 0,0为绘图的左上角起点的坐标,__board即要绘制的图 self.__painter.drawPixmap(0, 0, self.__board) self.__painter.end() def mousePressEvent(self, mouseEvent): # 鼠标按下时,获取鼠标的当前位置保存为上一次位置 self.__currentPos = mouseEvent.pos() self.__lastPos = self.__currentPos def mouseMoveEvent(self, mouseEvent): # 鼠标移动时,更新当前位置,并在上一个位置和当前位置间画线 self.__currentPos = mouseEvent.pos() self.__painter.begin(self.__board) if self.EraserMode == False: # 非橡皮擦模式 self.__painter.setPen(QPen(self.__penColor, self.__thickness)) # 设置画笔颜色,粗细 else: # 橡皮擦模式下画笔为纯白色,粗细为10 self.__painter.setPen(QPen(Qt.white, 10)) # 画线 self.__painter.drawLine(self.__lastPos, self.__currentPos) self.__painter.end() self.__lastPos = self.__currentPos self.update() # 更新显示 def mouseReleaseEvent(self, mouseEvent): self.__IsEmpty = False # 画板不再为空
感谢看完的读者,希望你们都可以对编程产生热爱!