在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%。考虑到模型仅有两层,效果算是挺不错了。
元旦鸽了两天,从今天开始继续学。加油!