1、实现一个简单的Dense类,就是实现图中层的定义
这是一个类,这个层主要实现数据变换的操作,即输入一个tensor,先与权重矩阵W相乘,然后加上b,最后经过激活函数activation运算,输出一个新的张量。为了实现这个操作,我们将这个任务划分成几个子任务:
(1)定义需要输入的属性,用于支持数据变换这一核心操作。这些属性包括W、b、activation。
(2)实现数据变换的操作。
(3)为了更方便的在后续的操作中获取权重的更新,希望定义一个新方法返回W和b。
以下是具体的实现:
import tensorflow as tf
class NaiveDense:
def __init__(self,activation,input_size,output_size):
#定义属性activation激活函数
self.activation=activation
#定义属性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)#前两步都是为这个属性的设置起作用
#定义属性b
b_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
#@property是一个装饰器,用于将一个方法转换为属性访问。使用@property可以让你以属性的方式访问方法,而不需要显式调用它。
#在你的代码中,weights方法被装饰为属性,这意味着你可以像访问属性一样访问它,而无需使用括号。例如:weights = layer.weights # 直接访问,不需要 weights()
def weights(self):
return [self.W,self.b]
值得注意的是,带有双下划线(__
)的函数通常表示这是一个特殊方法(也称为魔法方法),它们在特定情况下被自动调用,通常用于实现类的某些特定行为,比如对象的创建、销毁、属性访问等。
这里对__init__和__call__函数不理解的可以参考:
函数中self的理解-CSDN博客文章浏览阅读43次。self是Python类中一个非常重要的概念,它使得对象能够在其方法中引用自身的属性和其他方法。通过self访问和修改实例属性区分实例属性和局部变量实现方法链调用在类的内部方法之间传递实例。https://blog.csdn.net/weixin_65259109/article/details/144879964?spm=1001.2014.3001.5502
__call__函数的理解-CSDN博客当然,layer = MyLayer()的出现是因为这个类中没有初始化函数__init__,所以需要先激活一下,表明layer这个变量是在MyLayer这个类中进行游走。函数是一个特殊的方法,它允许类的实例像函数一样被调用。也就是说假如我有一个类名为Example,那我可以在后面的代码中直接使用这样的格式调用Example类中的。在 Python 中,https://blog.csdn.net/weixin_65259109/article/details/144883835?sharetype=blogdetail&sharerId=144883835&sharerefer=PC&sharesource=weixin_65259109&spm=1011.2480.3001.81182、简单的Sequential类实现,实现图中层到层的箭头
这个类在作用是将每个层连接起来。它用于封装一个层列表,也就是传入要是一个列表,这个列表里面是一个个层。这个类要将上一个层进行前向传播后的输出传递给下一个层。这样描述的操作的实现过程一般是先定义一个变量,用for循环不断对这个变量进行更新。这个实现在__call__函数中。此外,我还希望有一个函数记录某一层的参数。这个操作在weights函数当中实现。
以下是具体实现:
class NaiveSequential:
def __init__(self,layers):
self.layers = layers#这里的layer我设定是一个列表的输入这个列表里面的每一个元素的数据结构是NaiveDense
def __call__(self,inputs):
x=inputs#先定义一个变量用于存储,由于后面调用的layer,是self.layers传入的是layers属于NaiveDense类,所以调用的NaiveDense类中的__call__方法,返回的是计算过后的权重
#所以x是self.activation(tf.matmul(inputs,self.W)+self.b),其数据结构是张量
for layer in self.layers:#self.layers我想传入的是每一个元素的数据结构是NaiveDense的列表,故layer的数据结构是NaiveDense
x=layer(x) #layer的数据结构是NaiveDense,layer()则自动调用NaiveDense类中的__call__方法
#不断的循环使得上一层的权重不断的传递给下一层
return x
@property
def weights(self):
weights = []
for layer in self.layers:
weights += layer.weights #layer.weights返回的是对应该层的权重列表[W,b]
#是将 layer.weights 列表中的元素添加到 weights 列表中。具体来说,+= 运算符在这里用于列表的拼接,也就是将 layer.weights 中的所有元素添加到 weights 列表的末尾
return weights
3、将Dense和Sequential组合起来,实现图中的层到层
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)
])
4、实现批量生成器用于训练模型(准备工作),是图中输入X的优化
就是将一大坨数据分成坨1、坨2、坨3……然后按照顺序依次输入模型进行训练。
我使用的数据集是MINIST,是手写数据集。可以在tensorflow.keras.datasets直接引入。
import math
class BatchGenerator:
def __init__(self,images,labels,batch_size=128):
assert len(images)==len(labels)
self.index=0#后面这个index可以进行更新
self.images=images#导入的images是列表,里面的元素是数组
self.labels=labels
self.batch_size=batch_size#一批取多少个
self.num_batches=math.ceil(len(images)/batch_size)#取上整
def next(self):#用于更新数值
images = self.images[self.index : self.index+batch_size]
labels = self.labels[self.index : self.index+batch_size]
self.index += self.batch_size
return images,labels
5、完成一次训练步骤,其实就是图中剩下的所有步骤
主要包括以下步骤:
(1)收集从层中出来的预测值
(2)计算与真实值的差别。由于是多个样本,需要获取每个样本的预测值与真实值的差别然后取平均,作为平均差距。
(3)这个平均差距对权重求梯度
(4)更新权重,将权重沿着梯度上升的方向反向移动一小步为了能够减少差距
def one_training_step():
#GradientTape()是一个作用域,用于计算模型的预测值、每个样本差、平均样本差
with tf.GradientTape() as tape:
prediction = model(images_batch)#我是一批一批数据输入进model,我的任务是10分类任务,在NaiveSequential最后的softmax后输出的是每个样本在10类上的类别概率,应该是形状为(images_batch,10)
per_sample_losses = tf.keras.losses.sparse_categorical_crossentropy(labels_batch,predictions)#计算每个样本的预测值与真实值的差距,labels_batch是真实值,predictions是预测值,计算差距的方法是sparse_categorical_crossentropy
average_loss = tf.reduce_mean(per_sample_losses)#计算平均差
gradients = tape.gradient(average_loss,model.weights)#平均差是y,模型权重是x,y对x求梯度得到上升最快的方向,是一个方向
update_weights(gradients,model.weights)#更新模型权重,这个函数在后面,实现了权重沿着上升最快的方向的反方向(也就是下降最快的方向)走一小步(即每个权重减去gradients*learning_rate)
return average_loss
#对每个权重减去gra'dient*learning_rate
learning_rate = 1e-3#设置走一小步的步长
def update_weights(gradients,weights):
for g,w in zip(gradients,weights):
w.assign_sub(g*learning_rate)#相当于每个权重weights在对应的梯度升高最快方向gradients的反方向减去一小步g*learning_rate
6、实现完整的训练循环
实现每一轮的训练
每一轮实现每一批的训练
将数据输入串通起来
def fit(epochs,batch_size=128):
for epoch_counter in range(epochs):#设置训练几个循环,这个是图中的整个流程走几遍
print(f"Epoch {epoch_counter}")
batch_generator = BatchGenerator(images,labels)#初始化BatchGenerator,将这个类中的属性和方法加载进内存
for batch_counter in range(batch_generator.num_batches):#将一批数据分成小批输入,一批一批输入因为没有办法一下子输入如此大量的数据
#对于一整个函数而言:
#轮1:批1-->批2-->批3.....-->批num_batches
#然后结果到轮2继续训练:批1-->批2-->批3.....-->批num_batches
#轮3
#轮4
#……
images_batch,labels_batch = batch_generator.next()#提前准备好下一轮输入的数据
loss = one_training_step(model,images_batch,labels_batch)#记录当前批的loss值
if batch_counter % 100 == 0:#批次被100整除时打印loss值看看训练过程
print(f"loss at batch {batch_counter}:{loss:.2f}")
7、运行一下
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)
8、评估模型
import numpy as np
preditions = model(test_images)
preditions = preditions.numpy()
predited_labels = np.argmax(preditions,axis=1)
matches = predited_labels==test_labels
print(f"accuracy:{matches.mean():.2f}")
1.
np.argmax(preditions, axis=1)
功能:
np.argmax
函数用于返回数组中最大值的索引。在这里,preditions
是一个二维数组(通常是一个形状为(num_samples, num_classes)
的数组),其中每一行代表一个样本的预测结果,每一列代表一个类别的预测分数。参数:
axis=1
表示沿着行的方向进行操作,也就是说,对于每一行(每个样本),np.argmax
会找到最大值的索引。数据结构变化:
- 输入:
preditions
是一个形状为(num_samples, num_classes)
的二维数组。- 输出:
predited_labels
是一个一维数组,形状为(num_samples,)
,其中每个元素是对应样本预测的类别索引。
2.
matches = predited_labels == test_labels
功能:这一行代码用于比较预测的标签
predited_labels
和真实的标签test_labels
之间的相等性。它会生成一个布尔数组,表示每个样本的预测是否正确。数据结构变化:
predited_labels
是一个一维数组,包含每个样本的预测类别索引。test_labels
也是一个一维数组,包含每个样本的真实类别索引。matches
将是一个布尔数组,形状为(num_samples,)
,其中每个元素的值为True
(表示预测正确)或False
(表示预测错误)。
标签:__,keras,self,labels,batch,神经网络,weights,images,tensorflow From: https://blog.csdn.net/weixin_65259109/article/details/144879311
matches.mean()
的作用是计算布尔数组matches
中True
值的比例,从而得出模型的准确率。具体来说,以下是这个操作的详细解释:1.
matches
数组
matches
是一个布尔数组,形状为(num_samples,)
,其中每个元素的值为True
或False
。True
表示对应样本的预测标签与真实标签相同(预测正确)。False
表示对应样本的预测标签与真实标签不同(预测错误)。2. 布尔数组的数值表示
在 NumPy 中,布尔值可以被转换为数值:
True
被视为1
False
被视为0
因此,
matches
数组中的True
和False
可以被视为1
和0
。3.
mean()
方法
mean()
方法计算数组中所有元素的平均值。对于布尔数组,
mean()
计算的是True
值的比例,即:accuracy=number of Truetotal number of samplesaccuracy=total number of samplesnumber of True
4. 结果
matches.mean()
的结果是一个浮点数,表示模型在测试集上的准确率,范围在0.0
到1.0
之间。- 如果结果为
0.85
,则表示模型的预测正确率为85%
。