首页 > 其他分享 >只使用tensorflow而不使用keras实现一个简单的神经网络

只使用tensorflow而不使用keras实现一个简单的神经网络

时间:2025-01-04 14:32:17浏览次数:3  
标签:__ keras self labels batch 神经网络 weights images tensorflow

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(表示预测错误)。

matches.mean() 的作用是计算布尔数组 matchesTrue 值的比例,从而得出模型的准确率。具体来说,以下是这个操作的详细解释:

1. matches 数组

  • matches 是一个布尔数组,形状为 (num_samples,),其中每个元素的值为 TrueFalse
  • True 表示对应样本的预测标签与真实标签相同(预测正确)。
  • False 表示对应样本的预测标签与真实标签不同(预测错误)。

2. 布尔数组的数值表示

在 NumPy 中,布尔值可以被转换为数值:

  • True 被视为 1
  • False 被视为 0

因此,matches 数组中的 TrueFalse 可以被视为 10

3. mean() 方法

  • mean() 方法计算数组中所有元素的平均值。

  • 对于布尔数组,mean() 计算的是 True 值的比例,即:

    accuracy=number of Truetotal number of samplesaccuracy=total number of samplesnumber of True​

4. 结果

  • matches.mean() 的结果是一个浮点数,表示模型在测试集上的准确率,范围在 0.01.0 之间。
  • 如果结果为 0.85,则表示模型的预测正确率为 85%

标签:__,keras,self,labels,batch,神经网络,weights,images,tensorflow
From: https://blog.csdn.net/weixin_65259109/article/details/144879311

相关文章

  • Tensorflow张量的创建与修改和张量的运算
    构建一些tensorflow代码来实现一些训练神经网络的概念。其结构如下:低阶张量操作,以下可转化为TensorFlowAPI    构建张量,包括储存神经网络状态的特殊张量    张量运算,比如加法、relu、matmul    反向传播,一种计算数学表达式梯度的方法,在tensorflo......
  • 毕业项目推荐:基于yolov8/yolov5的草莓病害检测识别系统(python+卷积神经网络)
    文章目录概要一、整体资源介绍技术要点功能展示:功能1支持单张图片识别功能2支持遍历文件夹识别功能3支持识别视频文件功能4支持摄像头识别功能5支持结果文件导出(xls格式)功能6支持切换检测到的目标查看二、数据集三、算法介绍1.YOLOv8概述简介2.YOLOv5概述简......
  • R机器学习:神经网络算法的理解与实操,实例解析
    神经网络算法是一种模仿生物神经网络(尤其是人脑)结构和功能的算法。它由大量相互连接的节点(称为神经元)组成,这些神经元组织成层,通过传递信号来处理信息。神经网络算法在机器学习、人工智能等领域中扮演着至关重要的角色,尤其擅长处理复杂的模式识别、分类和预测问题。今天给大家介绍......
  • BP神经网络的反向传播算法
    BP神经网络(BackpropagationNeuralNetwork)是一种常用的多层前馈神经网络,通过反向传播算法进行训练。反向传播算法的核心思想是通过计算损失函数对每个权重的偏导数,从而调整权重,使得网络的预测输出与真实输出之间的误差最小。下面是反向传播算法的公式推导过程:1.前向传播(Forw......
  • (三)深度神经网络
    激活函数:添加非线性因素θ代表所有未知参数过拟合和欠拟合是复试重点(解决办法)在训练集上再好都没用呀,在测试集上好才是真的好去b站找小甲鱼学python到字典......
  • 15.小综合:人工神经网络逼近股票价格Ⅲ
    1.小综合:人工神经网络逼近股票价格Ⅲ2.小综合:人工神经网络逼近股票价格Ⅲ_代码解释13.小综合:人工神经网络逼近股票价格Ⅲ_代码解释24.小综合:人工神经网络逼近股票价格Ⅲ_代码解释3总结:这段代码定义了一个简单的神经网络模型MyModel,该模型包含一个线性层(权重和偏置),然后......
  • 【深度学习基础|知识概述】神经网络基础中的神经元结构是怎么样的?以及常用的激活函数
    【深度学习基础|知识概述】神经网络基础中的神经元结构是怎么样的?以及常用的激活函数有哪些?各有什么优缺点和应用场景。附公式及代码。(二)【深度学习基础|知识概述】神经网络基础中的神经元结构是怎么样的?以及常用的激活函数有哪些?各有什么优缺点和应用场景。附公式及代码。......
  • 手写 k近邻 与 全连接神经网络 算法
    KNN(K-近邻算法)K-近邻算法的介绍参考:https://blog.csdn.net/weixin_39910711/article/details/114440816手写knn算法,实现mnist的图片数字识别#手动实现knnimportiofromstructimportpack,unpackimportrandomfromPILimportImageimporttimeimportnumpyasnp......
  • 【人工智能机器学习基础篇】——深入详解深度学习之神经网络基础:理解前馈神经网络与反
    深入详解深度学习之神经网络基础:理解前馈神经网络与反向传播算法        深度学习作为人工智能(AI)的核心技术,已经在语音识别、图像处理、自然语言处理等诸多领域取得了显著的成果。而在深度学习的众多模型中,**前馈神经网络(FeedforwardNeuralNetworks,FNN)与反向传播......
  • pytorch中神经网络的定义方法
    1.继承torch.nn.Module类(推荐方法)最常见和推荐的方式是通过继承torch.nn.Module类来创建一个自定义的神经网络模型。在这种方式下,你需要定义__init__()方法来初始化网络层,并在forward()方法中定义前向传播逻辑。示例:一个简单的全连接神经网络importtorchimpor......