寒假博客日记——第四天

在Python中遇到的一些类的知识

想把这部分记录下来,主要是因为平常没有写类的习惯,为了加深对Python代码的理解,以及写类的能力,把新学到的类知识放在这一块

关于__init__函数和__call__函数

init函数比较熟悉了,在将类实例化的时候会执行的初始化函数,可以带一些参数,这样就能在实例化的时候把这些参数传到被实例化的对象里面。例如我定义了一个NaiveDense层:

class NaiveDense:
    def __init__(self,input_size,output_size,activation):
        self.activation=activation

        w_shape=(input_size,output_size) # W张量的大小
        w_initial_value=tf.random.uniform(w_shape,minval=0,maxval=1e-1) # 初始化W张量
        self.W=tf.Variable(w_initial_value) # 将W张量放入Variable可训练容器中

        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
    def weights(self):
        return [self.W,self.b]
        # 获取改层权重

我在实例化NaiveDense的时候,会执行__init__函数:

dense_1=NaiveDense(100,256,tf.nn.relu)

在执行完上面的代码后,dense_1中的init函数就会被执行一次,但此时还不会执行call函数

call函数我平常接触得很少,这次了解到了它的用法。下面的一句话是从其他博客里面找来描述__call__函数的:

Python中有一个有趣的语法,只要定义类型的时候,实现__call__函数,这个类型就成为可调用的。 换句话说,我们可以把这个类型的对象当作函数来使用,相当于重载了运算符。

这个说法听起来不好理解,简单来说就是在我上面实例化dense_1以后,可以直接把dense_1当做一个函数使用,用法如下:

首先随机生成一个inputs张量用于输入给dense_1:

inputs=np.random.random((20,100)).astype(np.float32)

要将其转换为单精度浮点数,因为W和b都单精度浮点数,而np.random.random产生双精度浮点数。

然后把dense_1直接当做一个函数调用,把inputs张量作为参数传入进去:

dense_1(inputs)

得到输出:

<tf.Tensor: shape=(20, 256), dtype=float32, numpy=
array([[2.5575395, 2.6418605, 2.4588299, ..., 2.5795076, 2.610356 ,
        2.6532674],
       [2.2876678, 2.2804286, 2.2772317, ..., 2.3244905, 2.2311337,
        2.3196971],
       [2.427024 , 2.1488943, 2.2635064, ..., 2.282372 , 2.4112282,
        2.3795252],
       ...,
       [2.5610175, 2.639574 , 2.436194 , ..., 2.6191926, 2.8255553,
        2.596984 ],
       [2.4655688, 2.7388172, 2.5334477, ..., 2.6523647, 2.5037825,
        2.593288 ],
       [2.8556366, 2.813027 , 2.8123813, ..., 2.7472234, 2.9274127,
        2.9834394]], dtype=float32)>

看似此时的dense_1是一个函数,其实并不存在一个叫做dense_1的函数。真正被执行的函数是__call__

@property的用法

python的@property是python的一种装饰器,是用来修饰方法的。

作用:

我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。

还拿上面的dense_1作为例子:

我们定义了一个weights函数,但是用@property修饰了,那么weights就不能被当做一个函数被调用,而是被视为是dense_1的一个属性,但改属性的值是通过weights函数计算出来的,所以改属性无法被修改,是只读的:

dense_1.weights
Out[18]: 
[<tf.Variable 'Variable:0' shape=(100, 256) dtype=float32, numpy=
 array([[0.00841386, 0.04154554, 0.09455848, ..., 0.03882213, 0.01188703,
         0.05531888],
        [0.0715877 , 0.03239784, 0.00466373, ..., 0.06878723, 0.07119443,
         0.05919527],
        [0.09278468, 0.03497422, 0.09659376, ..., 0.02735833, 0.09562129,
         0.04594043],
        ...,
        [0.0356217 , 0.09453787, 0.02683474, ..., 0.08472852, 0.03043708,
         0.05517278],
        [0.06955456, 0.02672439, 0.04372342, ..., 0.03403193, 0.03844366,
         0.04870069],
        [0.02865738, 0.0444525 , 0.00135843, ..., 0.01308964, 0.0465229 ,
         0.08778463]], dtype=float32)>,
 <tf.Variable 'Variable:0' shape=(256,) dtype=float32, numpy=
 array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
        0.], dtype=float32)>]

用TensorFlow从头开始写一个mnist分类模型

自定义了Dense层和Sequential模型生成方法,是按照keras之父写的书《python深度学习》第二版写的,主要是进一步理解底层的张量计算,熟悉神经网络的底层实现。代码如下:

import numpy as np
import tensorflow as tf
import keras.losses
import math

class NaiveDense:
    def __init__(self,input_size,output_size,activation):
        self.activation=activation

        w_shape=(input_size,output_size) # W张量的大小
        w_initial_value=tf.random.uniform(w_shape,minval=0,maxval=1e-1) # 初始化W张量
        self.W=tf.Variable(w_initial_value) # 将W张量放入Variable可训练容器中

        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
    def weights(self):
        return [self.W,self.b]
        # 获取改层权重

class NaiveSequential:
    def __init__(self,layers):
        self.layers=layers

    def __call__(self, inputs):
        x=inputs
        for layer in self.layers:
            x=layer(x)
        return x

    @property
    def weights(self):
        weights=[]
        for layer in self.layers:
            weights+=layer.weights
        return weights

class BatchGenrator:
    def __init__(self,images,labels,batch_size=128):
        assert len(images)==len(labels)
        self.index=0
        self.images=images
        self.labels=labels
        self.batch_size=batch_size
        self.num_batchs=math.ceil(len(images)/batch_size) # ceil是天花板,向上取整

    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_size
        return images,labels

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

def one_training_step(model,images_batch,labels_batch):
    with tf.GradientTape() as tape:
        predictions=model(images_batch) # 预测值
        per_sample_losses=keras.losses.sparse_categorical_crossentropy(labels_batch,predictions) # 所有样本的损失函数
        average_loss=tf.reduce_mean(per_sample_losses) #损失函数的平均值
    # 前向传播,在GradientTape作用域内计算模型预测值

    gradients=tape.gradient(average_loss,model.weights) # 计算损失值相对于权重的梯度。输出的是一个列表,对应model.weights列表中的权重
    update_weights(gradients,model.weights) # 利用梯度更新权重

    return average_loss

def fit(model,images,labels,epochs,batch_size=128):
    for epoch_counter in range(epochs): # 对于每个epoch
        print(f'Epoch {epoch_counter}')
        batch_generator=BatchGenrator(images,labels,batch_size)
        for batch_counter in range(batch_generator.num_batchs): # 对于每个batch
            images_batch,labels_batch=batch_generator.next() # 读取当前批的样本和标签
            loss=one_training_step(model,images_batch,labels_batch) # 训练一个batch
            if batch_counter%100==0: # 每送入100个批,就打印一下损失函数
                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((-1,28*28))
train_images=train_images.astype('float32')/255.0
test_images=test_images.reshape((-1,28*28))
test_images=test_images.astype('float32')/255.0


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)
])

for i in range(200):
    fit(model, images=train_images, labels=train_labels, epochs=2, batch_size=1024)

    predictions = model(test_images)
    predictions = predictions.numpy()  # 将其转换为numpy张量
    predicted_labels = np.argmax(predictions, axis=1)
    matches = predicted_labels == test_labels
    print(f'accuracy:{matches.mean():.2f}')

调整batch_size多次实验以后,发现准确率最高达到92%。考虑到模型仅有两层,效果算是挺不错了。

元旦鸽了两天,从今天开始继续学。加油!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

圆大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值