政安晨:【详细解析】【用TensorFlow从头实现】一个机器学习的神经网络小示例【解构演绎】

准备工作

咱们将通过这篇文章反复咀嚼我原来文章里提到的那篇《神经网络小实例》,大家可以先看看,比如做些环境准备等等(这是我的这篇文章的链接):

政安晨的机器学习笔记——基于Anaconda安装TensorFlow并尝试一个神经网络小实例icon-default.png?t=N7T8https://blog.csdn.net/snowdenkeke/article/details/135841281上面这个实例里是基于Windows系统的,如果是Ubuntu系统,可以参考我的下面这篇文章里面的Ubuntu系统的环境搭建部分:

政安晨的机器学习笔记——跟着演练快速理解TensorFlow(适合新手入门)icon-default.png?t=N7T8https://blog.csdn.net/snowdenkeke/article/details/135950931

待大家准备好环境后,咱们就开始。


先使用Keras回顾一下

咱们在这一部分先回顾一下我之前的文章,使用Keras API实现一下这个神经网络小实例,为后面仅用TensorFlow从头开始重新实现这个例子做好准备。

咱们先导入:

import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow import keras
from tensorflow.keras import layers

准备数据:

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

现在,输入图像已经保存在float32类型的NumPy张量中,其形状分别为(60000, 784)(训练数据)和(10000, 784)(测试数据):

       

构建模型:

model = keras.Sequential([layers.Dense(512, activation="relu"),layers.Dense(10, activation="softmax")
])

这个模型里包含两个链接在一起的Dense层,每层都对是输入数据做一些简单的张量运算。每个层的属性里,都是权重张量,这里面保存了模型所学到的信息(知识)

编译模型:

model.compile(optimizer="rmsprop",loss="sparse_categorical_crossentropy",metrics=["accuracy"])

训练循环:

model.fit(train_images, train_labels, epochs=5, batch_size=128)

模型开始在训练数据上进行迭代(每个小批量包含128个样本),共迭代5轮(在所有训练数据上迭代一次叫作一轮(epoch))。

对于每批数据,模型会计算损失相对于权重的梯度(利用反向传播算法,这一算法源自微积分的链式法则),并将权重沿着减小该批量对应损失值的方向移动。5轮之后,模型共执行2345次梯度更新(每轮469次),模型损失值将变得足够小,使得模型能够以很高的精度对手写数字进行分类。

测试预测:

咱们从刚才导入的测试图片中选取一张:test_images[7]

这张图片数字是:test_labels[7]

咱们在Jupyter Notebook中看到这个数字是 9

接下来,咱们用这张图片:test_images[7] 来测试咱们刚才训练好的模型

test_digits = test_images[0:10]
predictions = model.predict(test_digits)
predictions[7]
predictions[7].argmax()

我的执行如下:

这个模型已经预测到了正确的数字图像:9。

正式开始

现在咱们开始用TensorFlow从头重新实现上面这个小示例。

如何完全理解神经网络呢,让咱们从头开始重新实现整个过程。

当然,咱们不会重新实现基本的张量运算,也不会手动实现反向传播,咱们只是几乎不会用到Keras的功能。

实现一个简单的Dense类

Dense层实现了下列输入变换,其中W和b是模型参数,activation是一个逐元素的函数。

(比如relu,但最后一层是softmax)。

output = activation(dot(W, input) + b)

现在咱们来实现一个简单的Python类NaiveDense,它创建了两个TensorFlow变量W和b,并定义了一个__call__()方法供外部调用,以实现上述变换

class NaiveDense:def __init__(self, input_size, output_size, activation):self.activation = activation# 创建一个形状为(input_size, output_size)的矩阵W,并将其随机初始化w_shape = (input_size, output_size)w_initial_value = tf.random.uniform(w_shape, minval=0, maxval=1e-1)self.W = tf.Variable(w_initial_value)# 创建一个形状为(output_size,)的零向量bb_shape = (output_size,)b_initial_value = tf.zeros(b_shape)self.b = tf.Variable(b_initial_value)# 前向传播def __call__(self, inputs):  return self.activation(tf.matmul(inputs, self.W) + self.b)@property # 获取该层权重的便捷方法def weights(self):return [self.W, self.b]

实现一个简单的Sequential类

接下来,咱们创建一个NaiveSequential类,将这些层链接起来,它封装了一个层列表,并定义了一个__call__()方法供外部调用,这个方法将按顺序调用输入的层,它还有一个weights属性,用于记录该层的参数。

class NaiveSequential:def __init__(self, layers):self.layers = layersdef __call__(self, inputs):x = inputsfor layer in self.layers:x = layer(x)return x@propertydef weights(self):weights = []for layer in self.layers:weights += layer.weightsreturn weights

利用这个NaiveDense类和NaiveSequential类,我们可以创建一个与Keras类似的模型。

model = NaiveSequential([NaiveDense(input_size=28 * 28, output_size=512, activation=tf.nn.relu),NaiveDense(input_size=512, output_size=10, activation=tf.nn.softmax)
])
assert len(model.weights) == 4

批量生成器

现在,咱们需要对MNIST数据进行小批量迭代:

import mathclass BatchGenerator:def __init__(self, images, labels, batch_size=128):assert len(images) == len(labels)self.index = 0self.images = imagesself.labels = labelsself.batch_size = batch_sizeself.num_batches = math.ceil(len(images) / batch_size)def next(self):images = self.images[self.index : self.index + self.batch_size]labels = self.labels[self.index : self.index + self.batch_size]self.index += self.batch_sizereturn images, labels

完成一次训练步骤

接下来一步就是“训练步骤”,即在一批数据上运行模型后更新模型权重。

咱们需要做到以下几点:

(1)计算模型对图像批量的预测值。

(2)根据实际标签,计算这些预测值的损失值。

(3)计算损失相对于模型权重的梯度。

(4)将权重沿着梯度的反方向移动一小步。

现在,咱们准备计算梯度:

def one_training_step(model, images_batch, labels_batch):# (本行及以下4行)运行前向传播,即在GradientTape作用域内计算模型预测值with tf.GradientTape() as tape: predictions = model(images_batch)per_sample_losses = tf.keras.losses.sparse_categorical_crossentropy(labels_batch, predictions)average_loss = tf.reduce_mean(per_sample_losses)# 计算损失相对于权重的梯度,输出gradients是一个列表,每个元素对应model.weights列表中的权重gradients = tape.gradient(average_loss, model.weights)# 利用梯度来更新权重(稍后给出这个函数的定义)update_weights(gradients, model.weights)return average_loss

“更新权重”这一步(由update_weights函数实现)的目的,就是将权重沿着减小批量损失值的方向移动“一小步”。

移动幅度由学习率决定,它通常是一个很小的数,要实现这个update_weights函数,最简单的方法就是从每个权重中减去gradient * learning_rate。

learning_rate = 1e-3def update_weights(gradients, weights):for g, w in zip(gradients, weights):# assign_sub相当于TensorFlow变量的-=w.assign_sub(g * learning_rate)

在实践中,你几乎不会像这样手动实现权重更新,而是会使用Keras的Optimizer实例,如下:

from tensorflow.keras import optimizers

optimizer = optimizers.SGD(learning_rate=1e-3)

def update_weights(gradients, weights):
    optimizer.apply_gradients(zip(gradients, weights))

最后这一段代码您不必在Python环境中输入

训练循环

咱们现在开始完整的训练循环。

说明一轮训练就是对训练数据的每个批量都重复上述训练步骤,而完整的训练循环就是重复多轮训练。

先定义一下这个拟合函数:

def fit(model, images, labels, epochs, batch_size=128):for epoch_counter in range(epochs):print(f"Epoch {epoch_counter}")batch_generator = BatchGenerator(images, labels)for batch_counter in range(batch_generator.num_batches):images_batch, labels_batch = batch_generator.next()loss = one_training_step(model, images_batch, labels_batch)if batch_counter % 100 == 0:print(f"loss at batch {batch_counter}: {loss:.2f}")

现在咱们开始运行一下

依旧使用上面咱们用到的图像训练的例子(如果您已经导入了图像数据,则下述代码无需执行):

from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

开始训练:

fit(model, train_images, train_labels, epochs=10, batch_size=128)

训练过程如下:

评估模型

咱们可以评估模型,方法是对模型在测试图像上的预测值取argmax,并将其与预期标签进行比较。

import numpy as nppredictions = model(test_images)# 对TensorFlow张量调用.numpy(),可将其转换为NumPy张量
predictions = predictions.numpy()predicted_labels = np.argmax(predictions, axis=1)
matches = predicted_labels == test_labels
print(f"accuracy: {matches.mean():.2f}")

我的执行如下

好啦,聪明的您已经可以看到咱们对刚才“手工实现的模型的评估值啦

发现了吧,用几行Keras代码就能完成的工作,手动实现起来还是挺费劲的。

但手动实现一遍之后,你现在应该已经清楚地了解在调用fit()时神经网络内部都发生了什么

拥有这种对代码底层原理的思维模型,可以让你更好地使用Keras API的高级功能。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/467877.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像处理之《神经网络模型的通用隐写框架》论文阅读

一、文章摘要 在本文中,我们提出了一个通用的隐写框架,用于神经网络实现隐蔽通信。首先,我们设计了一种基线隐写方法,在网络训练过程中将秘密数据嵌入到给定神经网络(封面网络)的卷积层中。对于包含秘密数据的网络(隐写网络)&…

python 基础知识点(蓝桥杯python科目个人复习计划40)

今日复习内容:矩阵乘法,高斯消元 哈哈,我来干回老本行,复习点儿数学类专业学的东西 因为电脑上制作费时间,所以我直接用我的《高等代数》和《数值分析》笔记。 一.矩阵乘法 例题1:矩阵相乘 题目描述&am…

ElasticSearch级查询Query DSL上

目录 ES高级查询Query DSL match_all 返回源数据_source 返回指定条数size 分页查询from&size 指定字段排序sort 术语级别查询 Term query术语查询 Terms Query多术语查询 exists query ids query range query范围查询 prefix query前缀查询 wildcard query通…

蓝桥杯嵌入式第10届真题(完成) STM32G431

蓝桥杯嵌入式第10届真题(完成) STM32G431 题目 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body********************************…

家政小程序系统源码开发:引领智能生活新篇章

随着科技的飞速发展,小程序作为一种便捷的应用形态,已经深入到我们生活的方方面面。尤其在家庭服务领域,家政小程序的出现为人们带来了前所未有的便利。它不仅简化了家政服务的流程,提升了服务质量,还为家政服务行业注…

Linux_线程

线程与进程 多级页表 线程控制 线程互斥 线程同步 生产者消费者模型 常见概念 下面选取32位系统举例。 一.线程与进程 上图是曾经我们认为进程所占用的资源的集合。 1.1 线程概念 线程是一个执行分支,执行粒度比进程细,调度成本比进程低线程是cpu…

题目:1.可凑成的最大花束数(蓝桥OJ 3344)

问题描述: 解题思路: 官方: 总结:使用二分枚举符合条件的x,不能用贪心(又大到小依次枚举,会导致超时,因为数据太大(1e9以上,超过规定的1e8)&#…

MYSQL笔记:简单的SQL操作和select查询

MYSQL笔记:简单的SQL操作和select查询 文章目录 MYSQL笔记:简单的SQL操作和select查询结构化查询语句SQL库操作表操作CRUD操作单表查询select 查询例子 分页查询与limitlimit 只是对结果条数有限制还是会提高查询效率? order bygroup by多表连…

java之jvm详解

JVM内存结构 程序计数器 Program Counter Register程序计数器(寄存器) 程序计数器在物理层上是通过寄存器实现的 作用:记住下一条jvm指令的执行地址特点 是线程私有的(每个线程都有属于自己的程序计数器)不会存在内存溢出 虚拟机栈(默认大小为1024kb) 每个线…

Rust入门:如何在windows + vscode中关闭程序codelldb.exe

在windows中用vscode单步调试rust程序的时候,发现无论是按下stop键,还是运行完程序,调试器codelldb.exe一直霸占着主程序不退出,如果此时对代码进行修改,后续就没法再编译调试了。 目前我也不知道要怎么处理这个事&am…

python-分享篇-GUI界面开发-PyQt5-弹出不同种类的消息提示框

代码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file messagebox.ui # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! 弹出不同种类的消息提示框from PyQt5 import QtCore, QtGui,…

剪辑视频衔接怎么操作 剪辑视频衔接过渡自然方法 剪辑视频教程新手入门 抖音剪辑短视频 会声会影视频制作教程

视频剪辑在现代社交媒体和数字媒体时代中变得越来越重要。它广泛应用于各种领域,包括电影制作、广告宣传、教育培训、社交媒体内容创作等。 一、剪辑视频衔接怎么操作 会声会影是一款功能强大、易于使用的视频编辑软件。接下来我们拿会声会影为例讲解剪辑视频如何…