经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 大数据/云/AI » 人工智能基础 » 查看文章
使用人工神经网络训练手写数字识别模型
来源:cnblogs  作者:zylyehuo  时间:2023/10/11 16:12:21  对本文有异议

博客地址:https://www.cnblogs.com/zylyehuo/

效果展示

下载项目

https://github.com/zylyehuo/ANN-main

下载数据集(共四个)(上一步中已包含数据集,按需自己下载)

http://yann.lecun.com/exdb/mnist/

目录结构

完整过程流程图

模型训练过程流程图

手写数字预测过程流程图

dataloader.py

  1. import numpy as np
  2. import struct
  3. import matplotlib.pyplot as plt
  4. import os
  5. # download the mnist dataset from http://yann.lecun.com/exdb/mnist/
  6. # 读取要训练的数据集
  7. def read_train_data(path=''):
  8. with open(os.path.join(path, 'train-images.idx3-ubyte'), 'rb') as f1:
  9. buf1 = f1.read()
  10. with open(os.path.join(path, 'train-labels.idx1-ubyte'), 'rb') as f2:
  11. buf2 = f2.read()
  12. return buf1, buf2
  13. # 读取要测试的数据集
  14. def read_test_data(path=''):
  15. with open(os.path.join(path, 't10k-images.idx3-ubyte'), 'rb') as f1:
  16. buf1 = f1.read()
  17. with open(os.path.join(path, 't10k-labels.idx1-ubyte'), 'rb') as f2:
  18. buf2 = f2.read()
  19. return buf1, buf2
  20. # 得到图片数据
  21. def get_image(buf1):
  22. image_index = 0
  23. '''
  24. struct.calcsize('>IIII') 是Python中用于计算二进制数据格式字符串的大小的函数调用
  25. 它描述了一个包含四个大端字节序的无符号整数的数据结构的大小
  26. '''
  27. image_index += struct.calcsize('>IIII')
  28. img_num = int((len(buf1) - 16) / 784)
  29. im = []
  30. for i in range(img_num):
  31. temp = list(struct.unpack_from('>784B', buf1, image_index)) # '>784B'的意思就是用大端法读取784个unsigned byte
  32. im.append(temp)
  33. image_index += struct.calcsize('>784B') # 每次增加784B
  34. im = np.array(im, dtype=np.float32) # 将图片作为数据源传入numpy数组
  35. return im
  36. # 得到标签数据
  37. def get_label(buf2):
  38. label_index = 0
  39. '''
  40. 用于计算二进制数据格式字符串的大小的函数
  41. '>II' 是格式字符串,它描述了要打包或解包的数据结构。
  42. 在这个格式字符串中,> 表示使用大端字节序(big-endian),
  43. I 表示一个无符号整数(unsigned int)。
  44. '''
  45. label_index += struct.calcsize('>II')
  46. idx_num = int(len(buf2) - 8)
  47. labels = []
  48. for i in range(idx_num):
  49. temp = list(struct.unpack_from('>1B', buf2, label_index))
  50. labels.append(temp)
  51. label_index += 1
  52. labels = np.array(labels, dtype=int) # 将标签作为数据源传入numpy数组
  53. return labels
  54. # 加载训练数据
  55. def load_train_data(path=''):
  56. img_buf, label_buf = read_train_data(path)
  57. imgs = get_image(img_buf)
  58. labels = get_label(label_buf)
  59. return imgs, labels
  60. # 加载测试数据
  61. def load_test_data(path=''):
  62. img_buf, label_buf = read_test_data(path)
  63. imgs = get_image(img_buf) # 得到图片数据
  64. labels = get_label(label_buf) # 得到标签数据
  65. return imgs, labels
  66. if __name__ == "__main__":
  67. # 测试查看数据集数据使用
  68. """
  69. from keras.datasets import mnist
  70. (train_images, train_labels), (test_images, test_labels) = mnist.load_data() # 挂梯子
  71. print(f"train_images.shape = {train_images.shape}\ntest_labels.shape = {test_images.shape}")
  72. print("train_labels", train_labels)
  73. print("teat_labels", test_labels)
  74. # print(train_images[0])
  75. print(train_labels[-1])
  76. plt.imshow(train_images[-1])
  77. plt.show()
  78. """
  79. imgs, labels = load_test_data()
  80. for i in range(9):
  81. # 将整个图像窗口分为3行3列
  82. plt.subplot(3, 3, i + 1)
  83. # 设置窗口的标题
  84. title = u"标签对应为:" + str(labels[i])
  85. # 显示窗口的标题
  86. plt.title(title, fontproperties='SimHei')
  87. # 将读取的图片数据源改成28x28的大小
  88. img = np.array(imgs[i]).reshape((28, 28))
  89. # cmap参数: 为调整显示颜色 gray为黑白色,加_r取反为白黑色
  90. plt.imshow(img, cmap='gray')
  91. plt.show()

neural_network.py

  1. import numpy as np
  2. import os
  3. import dataloader as dl # 导入数据加载器模块
  4. import random
  5. import argparse # 导入命令行参数解析模块
  6. import matplotlib.pyplot as plt
  7. # 创建解析器
  8. parser = argparse.ArgumentParser()
  9. # 添加参数
  10. # --data_path 的默认参数为 ’./‘ 即当前路径
  11. parser.add_argument('--data_path', type=str, default='./')
  12. # 解析参数
  13. args = parser.parse_args()
  14. # 加载参数
  15. data_path = args.data_path
  16. # 激活函数
  17. class Sigmoid(object):
  18. def __init__(self):
  19. self.gradient = []
  20. def forward(self, x):
  21. """
  22. Sigmoid激活函数的前向传播函数。
  23. Args:
  24. x (numpy.ndarray): 输入数据。
  25. Returns:
  26. numpy.ndarray: 经过Sigmoid激活后的数据。
  27. """
  28. self.gradient = x * (1.0 - x)
  29. return 1.0 / (1.0 + np.exp(-x))
  30. def backward(self):
  31. """
  32. Sigmoid激活函数的反向传播函数。
  33. Returns:
  34. numpy.ndarray: 梯度。
  35. """
  36. return self.gradient
  37. # ReLU激活函数
  38. class ReLU(object):
  39. def __init__(self):
  40. self.gradient = []
  41. def forward(self, input_data):
  42. """
  43. ReLU激活函数的前向传播函数。
  44. Args:
  45. input_data (numpy.ndarray): 输入数据。
  46. Returns:
  47. numpy.ndarray: 经过ReLU激活后的数据。
  48. """
  49. extend_input = input_data
  50. self.gradient = np.where(input_data >= 0, 1, 0.001)
  51. self.gradient = self.gradient[:, None]
  52. input_data[input_data < 0] = 0.01 * input_data[input_data < 0]
  53. return input_data
  54. def backward(self):
  55. """
  56. ReLU激活函数的反向传播函数。
  57. Returns:
  58. numpy.ndarray: 梯度。
  59. """
  60. return self.gradient
  61. def softmax(input_data):
  62. """
  63. softmax函数用于将输入数据转化为概率分布。
  64. Args:
  65. input_data (numpy.ndarray): 输入数据。
  66. Returns:
  67. numpy.ndarray: 经过Softmax处理后的概率分布。
  68. """
  69. # 减去最大值防止softmax上下溢出
  70. input_max = np.max(input_data)
  71. input_data -= input_max
  72. input_data = np.exp(input_data)
  73. exp_sum = np.sum(input_data)
  74. input_data /= exp_sum
  75. return input_data
  76. # 全连接
  77. class FullyConnectedLayer(object):
  78. def __init__(self, input_size, output_size, learning_rate=0.01):
  79. self._w = np.random.randn(input_size * output_size) / np.sqrt(input_size * output_size)
  80. self._w = np.reshape(self._w, (input_size, output_size))
  81. b = np.zeros((1, output_size), dtype=np.float32)
  82. self._w = np.concatenate((self._w, b), axis=0)
  83. self._w = self._w.astype(np.float32)
  84. # self._w = np.ones((input_size + 1, output_size), dtype=np.float32)
  85. self.lr = learning_rate
  86. self.gradient = np.zeros((input_size + 1, output_size), dtype=np.float32)
  87. self.w_gradient = []
  88. self.input = []
  89. def forward(self, input_data):
  90. """
  91. 全连接层的前向传播函数。
  92. Args:
  93. input_data (numpy.ndarray): 输入数据。
  94. Returns:
  95. numpy.ndarray: 前向传播结果。
  96. """
  97. # 将b加入w矩阵
  98. input_data = np.append(input_data, [1.0], axis=0)
  99. input_data = input_data.astype(np.float32)
  100. # 计算线性乘积
  101. output_data = np.dot(input_data.T, self._w)
  102. # 保存输入数据以计算梯度
  103. self.input = input_data
  104. # 更新梯度
  105. self.gradient = self._w
  106. self.w_gradient = input_data
  107. return output_data
  108. def backward(self):
  109. """
  110. 全连接层的反向传播函数。
  111. Returns:
  112. numpy.ndarray: 梯度。
  113. """
  114. return self._w[:-1, :]
  115. def update(self, delta_grad):
  116. """
  117. 更新权重参数的函数。
  118. Args:
  119. delta_grad (numpy.ndarray): 更新的梯度。
  120. """
  121. self.input = self.input[:, None]
  122. self._w -= self.lr * np.matmul(self.input, delta_grad)
  123. def get_w(self):
  124. """
  125. 获取权重参数的函数。
  126. Returns:
  127. numpy.ndarray: 权重参数。
  128. """
  129. return self._w
  130. def set_w(self, w):
  131. """
  132. 设置权重参数的函数。
  133. Args:
  134. w (numpy.ndarray): 要设置的权重参数。
  135. """
  136. self._w = w
  137. # 交叉熵损失函数
  138. class CrossEntropyWithLogit(object):
  139. def __init__(self):
  140. self.gradient = []
  141. def calculate_loss(self, input_data, y_gt):
  142. """
  143. 计算交叉熵损失函数。
  144. Args:
  145. input_data (numpy.ndarray): 输入数据。
  146. y_gt (numpy.ndarray): 真实标签。
  147. Returns:
  148. float: 损失值。
  149. """
  150. input_data = softmax(input_data)
  151. # 交叉熵公式 -sum(yi*logP(i))
  152. loss = -np.sum(y_gt * np.log(input_data + 1e-5))
  153. # 计算梯度
  154. self.gradient = input_data - y_gt
  155. return loss
  156. def predict(self, input_data):
  157. """
  158. 预测函数,返回预测的类别。
  159. Args:
  160. input_data (numpy.ndarray): 输入数据。
  161. Returns:
  162. int: 预测的类别。
  163. """
  164. # input_data = softmax(input_data)
  165. return np.argmax(input_data)
  166. def backward(self):
  167. """
  168. 交叉熵损失函数的反向传播函数。
  169. Returns:
  170. numpy.ndarray: 梯度。
  171. """
  172. return self.gradient.T
  173. # MNIST神经网络模型
  174. class MNISTNet(object):
  175. def __init__(self):
  176. self.linear_layer1 = FullyConnectedLayer(28 * 28, 128)
  177. self.linear_layer2 = FullyConnectedLayer(128, 10)
  178. self.relu1 = ReLU()
  179. self.relu2 = ReLU()
  180. self.loss = CrossEntropyWithLogit()
  181. def train(self, x, y):
  182. """
  183. 训练函数,包括前向传播和反向传播。
  184. Args:
  185. x (numpy.ndarray): 输入数据。
  186. y (numpy.ndarray): 真实标签。
  187. Returns:
  188. float: 损失值。
  189. """
  190. # 前向传播
  191. x = self.linear_layer1.forward(x)
  192. x = self.relu1.forward(x)
  193. x = self.linear_layer2.forward(x)
  194. x = self.relu2.forward(x)
  195. loss = self.loss.calculate_loss(x, y)
  196. # print("loss:{}".format(loss))
  197. # 反向传播
  198. loss_grad = self.loss.backward()
  199. relu2_grad = self.relu2.backward()
  200. layer2_grad = self.linear_layer2.backward()
  201. grads = np.multiply(loss_grad, relu2_grad)
  202. self.linear_layer2.update(grads.T)
  203. grads = layer2_grad.dot(grads)
  204. relu1_grad = self.relu1.backward()
  205. grads = np.multiply(relu1_grad, grads)
  206. self.linear_layer1.update(grads.T)
  207. return loss
  208. def predict(self, x):
  209. """
  210. 预测函数,返回预测的类别。
  211. Args:
  212. x (numpy.ndarray): 输入数据。
  213. Returns:
  214. int: 预测的类别。
  215. """
  216. # 前向传播
  217. x = self.linear_layer1.forward(x)
  218. x = self.relu1.forward(x)
  219. x = self.linear_layer2.forward(x)
  220. x = self.relu2.forward(x)
  221. number_index = self.loss.predict(x)
  222. return number_index
  223. def save(self, path='.', w1_name='w1', w2_name='w2'):
  224. """
  225. 保存权重参数到文件。
  226. Args:
  227. path (str): 保存路径。
  228. w1_name (str): 第一个权重参数文件名。
  229. w2_name (str): 第二个权重参数文件名。
  230. """
  231. w1 = self.linear_layer1.get_w()
  232. w2 = self.linear_layer2.get_w()
  233. np.save(os.path.join(path, w1_name), w1)
  234. np.save(os.path.join(path, w2_name), w2)
  235. def evaluate(self, x, y):
  236. """
  237. 评估函数,判断预测是否正确。
  238. Args:
  239. x (numpy.ndarray): 输入数据。
  240. y (numpy.ndarray): 真实标签。
  241. Returns:
  242. bool: 预测是否正确。
  243. """
  244. if y == self.predict(x):
  245. return True
  246. else:
  247. return False
  248. def load_param(self, path=""):
  249. """
  250. 加载权重参数。
  251. Args:
  252. path (str): 参数文件路径。
  253. """
  254. w1 = np.load(os.path.join(path, 'w1.npy'))
  255. w2 = np.load(os.path.join(path, 'w2.npy'))
  256. self.linear_layer1.set_w(w1)
  257. self.linear_layer2.set_w(w2)
  258. def one_hot_encoding(y):
  259. one_hot_y = np.eye(10)[y]
  260. return one_hot_y
  261. def train_net(data_path=''):
  262. """
  263. 训练神经网络模型。
  264. Args:
  265. data_path (str): 数据路径,默认为当前路径。
  266. """
  267. m_net = MNISTNet()
  268. x_train, y_train = dl.load_train_data(data_path)
  269. '''
  270. 这行代码执行了以下两个步骤:
  271. 1、归一化:将输入数据中的每个像素值除以255。这是因为MNIST数据集中的像素值范围是0到255,通过将它们除以255,
  272. 将它们缩放到了0到1之间。这种归一化有助于神经网络的训练,因为它将输入数据的值范围映射到了一个更小的区间,有助于加速训练过程。
  273. 2、中心化:从每个像素值中减去0.5。这意味着将所有像素值的平均值平移到0附近。
  274. 这个步骤有助于训练过程中的数值稳定性,通常在输入数据的均值不为0时进行中心化。
  275. 综合起来,这行代码的目的是将原始的像素值转换为一个均值接近0、范围在-0.5到0.5之间的归一化值,
  276. 以更好地满足神经网络的训练需求。这是神经网络中常见的数据预处理步骤之一。
  277. '''
  278. x_train = x_train / 255 - 0.5
  279. '''
  280. 将目标标签(或类别)进行独热编码(One-Hot Encoding)的操作。这是为了适应多类别分类问题的标签表示方式。
  281. 在机器学习中,特别是在多类别分类任务中,通常会使用独热编码来表示目标标签。独热编码是一种二进制编码方式,
  282. 其中每个类别都由一个长度等于类别总数的二进制向量表示。在这个向量中,只有一个元素是1,其他元素都是0,用来表示样本所属的类别。
  283. 这种编码方式有助于模型理解和处理多类别分类问题。
  284. 在这里,one_hot_encoding 函数将原始的目标标签 y_train 转换为独热编码的形式。
  285. '''
  286. y_train = one_hot_encoding(y_train)
  287. epoch = 200 # 定义训练的轮数,也就是训练过程将遍历整个训练数据集的次数
  288. # num_examples = iter * batch_size
  289. for i in range(epoch):
  290. average_loss = 0 # 用于记录每个epoch中的平均损失
  291. '''
  292. x_train.shape[0] 是 NumPy 数组 x_train 的属性,表示该数组的第一维度(也就是行数)。
  293. 在这个上下文中,x_train 应该是一个包含训练数据的 NumPy 数组,通常是一个二维数组,其中每一行代表一个训练样本,而每一列代表该样本的特征。
  294. 所以,x_train.shape[0] 返回的值就是训练数据集中样本的数量,也就是数据集的行数。
  295. 在上述代码中,它用于计算平均损失,以便将累积的损失除以数据集中的样本数量,从而得到每个 epoch 的平均损失。
  296. 例如,如果 x_train 的形状是 (60000, 784),那么 x_train.shape[0] 就是 60000,表示数据集中有 60000 个训练样本。
  297. '''
  298. for j in range(x_train.shape[0]):
  299. # 遍历训练数据集中的每个样本
  300. # 计算并累加损失,同时更新模型参数
  301. average_loss += m_net.train(x_train[j], y_train[j])
  302. # 打印每2000个样本的训练损失,以便实时监视模型的训练进度。
  303. if j % 2000 == 0:
  304. print('train set loss(epo:{}): {}'.format(i, average_loss / (j + 1)))
  305. # 打印每个epoch的平均损失
  306. print('train set average loss: {}'.format(average_loss / x_train.shape[0]))
  307. # 保存模型参数,通常在每个epoch结束后保存,以便稍后使用
  308. m_net.save()
  309. def eval_net(path=""):
  310. """
  311. 评估神经网络模型。
  312. 计算在测试数据集上的分类准确度(precision)或召回率(recall)
  313. 加载训练好的神经网络模型,在测试数据集上进行分类预测,并计算模型的分类准确度,以评估模型在新数据上的性能。
  314. Args:
  315. path (str): 参数文件路径。
  316. """
  317. x_test, y_test = dl.load_test_data(path)
  318. '''
  319. 这行代码执行了以下两个步骤:
  320. 1、归一化:将输入数据中的每个像素值除以255。这是因为MNIST数据集中的像素值范围是0到255,通过将它们除以255,
  321. 将它们缩放到了0到1之间。这种归一化有助于神经网络的训练,因为它将输入数据的值范围映射到了一个更小的区间,有助于加速训练过程。
  322. 2、中心化:从每个像素值中减去0.5。这意味着将所有像素值的平均值平移到0附近。
  323. 这个步骤有助于训练过程中的数值稳定性,通常在输入数据的均值不为0时进行中心化。
  324. 综合起来,这行代码的目的是将原始的像素值转换为一个均值接近0、范围在-0.5到0.5之间的归一化值,
  325. 以更好地满足神经网络的训练需求。这是神经网络中常见的数据预处理步骤之一。
  326. '''
  327. x_test = x_test / 255.0 - 0.5
  328. precision = 0 # 初始化一个变量 precision 为0,用于计算分类准确度。
  329. m_net = MNISTNet() # 创建一个新的神经网络模型 m_net,这个模型是用于在测试数据集上进行分类预测的。
  330. m_net.load_param() # 加载之前训练好的神经网络模型的参数。这些参数包含了模型在训练数据上学到的权重和偏差。
  331. for i in range(x_test.shape[0]): # 遍历测试数据集中的每个样本。
  332. '''
  333. 对当前测试样本进行分类预测,并与真实标签 y_test[i] 进行比较。
  334. 如果模型的预测结果与真实标签一致,表示分类正确,执行下面的代码块。
  335. '''
  336. if m_net.evaluate(x_test[i], y_test[i]):
  337. precision += 1 # 如果分类正确,将 precision 值加1,用于统计正确分类的样本数量。
  338. precision /= len(x_test) # 计算分类准确度。它将正确分类的样本数量除以测试数据集的总样本数量,从而得到分类准确度。
  339. # 通常,分类准确度是一个在0到1之间的值,表示模型在测试数据上的性能。
  340. print('precision of test data set is {}'.format(precision))
  341. def visualize(path):
  342. """
  343. 可视化函数,用于展示模型预测结果。
  344. Args:
  345. path (str): 参数文件路径。
  346. """
  347. # 加载测试数据
  348. x, y_gt = dl.load_test_data(path)
  349. # 数据预处理:将像素值归一化到[-0.5, 0.5]范围
  350. x_imput = x / 255.0 - 0.5
  351. # 创建一个新的神经网络模型
  352. m_net = MNISTNet()
  353. # 加载之前训练好的模型参数
  354. m_net.load_param()
  355. # 随机选择一个测试样本的索引
  356. visualize_idx = random.randint(0, x.shape[0] - 1)
  357. # 使用模型对选定的测试样本进行预测
  358. y_pred = m_net.predict(x_imput[visualize_idx])
  359. # 创建一个绘图区域,并显示图像和标签信息
  360. plt.subplot(111)
  361. title = "真值标签为:{},""预测标签为:{}".format(y_gt[visualize_idx], y_pred)
  362. plt.title(title, fontproperties='SimHei')
  363. # 将测试样本的图像转换成可视化的形式,并显示在图中
  364. img = np.array(x[visualize_idx]).reshape((28, 28))
  365. # cmap参数: 为调整显示颜色 gray为黑白色,加_r取反为白黑色
  366. plt.imshow(img, cmap='gray')
  367. # 显示图像
  368. plt.show()
  369. """
  370. 这三个函数一起构成了神经网络的训练、评估和可视化过程,能够训练模型、评估其性能并可视化模型的预测结果。
  371. """
  372. if __name__ == '__main__':
  373. '''
  374. train_net() 函数的作用是训练神经网络模型。
  375. 它通过多次迭代训练数据集中的样本,使用反向传播算法来更新神经网络的权重和参数,以最小化损失函数。
  376. 在训练过程中,模型逐渐调整自己以提高对训练数据的拟合。训练完成后,模型的参数将保存在文件中,以备将来使用。
  377. '''
  378. train_net()
  379. '''
  380. eval_net() 函数的作用是评估训练好的神经网络模型在测试数据集上的性能。
  381. 它会加载之前训练好的模型参数,并使用这些参数来进行测试数据集上的预测。
  382. 然后,它会计算模型在测试数据集上的精度或其他性能指标,并打印出来,以衡量模型的性能。
  383. '''
  384. eval_net()
  385. '''
  386. visualize(args.data_path) 函数的作用是可视化神经网络模型的预测结果。
  387. 它会加载训练好的模型参数,并随机选择一个测试样本,然后使用模型对该样本进行预测。
  388. 最后,它会显示该样本的真实标签和模型的预测标签,以及样本的图像,以帮助可视化模型的性能。
  389. '''
  390. visualize(args.data_path)

原文链接:https://www.cnblogs.com/zylyehuo/p/17753007.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号