学习《动手学习深度学习》
线性回归的从零开始实现
生成y=Xw+b+噪声
tf.random.normal()函数用于从“服从指定正态分布的序列”中随机取出指定个数的值。
tf.random.normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
- shape: 输出张量的形状,必选
- mean: 正态分布的均值,默认为0
- stddev: 正态分布的标准差,默认为1.0
- dtype: 输出的类型,默认为tf.float32
- seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样
- name: 操作的名称
函数:tf.matmul()表示:将矩阵 a 乘以矩阵 b,生成a * b
tf.matmul(a, b, transpose_a=False, transpose_b=False, adjoint_a=False, adjoint_b=False, a_is_sparse=False, b_is_sparse=False, name=None)
- transpose_a:如果 True,a 在乘法之前转置.
- transpose_b:如果 True,b 在乘法之前转置.
- adjoint_a: 如果为真, a则在进行乘法计算前进行共轭和转置。
- adjoint_b: 如果为真, b则在进行乘法计算前进行共轭和转置。
- a_is_sparse: 如果为真, a会被处理为稀疏矩阵。
- b_is_sparse: 如果为真, b会被处理为稀疏矩阵。
- name: 操作的名字(可选参数)
def synthetic_data(w, b, num_examples):
"""生成y=Xw+b+噪声"""
X = tf.zeros((num_examples, w.shape[0])) # tf.zeros(shape, dtype=tf.float32, name=None)创建一个所有元素都设置为零的张量.
X += tf.random.normal(shape=X.shape)
y = tf.matmul(X, tf.reshape(w, (-1, 1))) + b
y += tf.random.normal(shape=y.shape, stddev=0.01)
y = tf.reshape(y, (-1, 1))
return X, y
赋值并绘图
tf.constant()可以实现生成一个常量数值
tf.constant(value,dtype,shape,name)
- value:常量值
- dtype:数据类型
- shape:表示生成常量数的维度
- name:数据名称
true_w = tf.constant([2, -3.4])
true_b = 4.2
print('true_w:', true_w)
features, labels = synthetic_data(true_w, true_b, 1000)
print('features:', features[0], '\nlabels:', labels[0])
plt.scatter(features[:, (1)].numpy(), labels.numpy(), 1)
plt.show()
定义data_iter函数
tf.gather()该接口的作用:就是抽取出params的第axis维度上在indices里面所有的index
tf.gather(params, indices, validate_indices=None, name=None, axis=0)
- params: A Tensor.
- indices: A Tensor. types必须是: int32, int64. 里面的每一个元素大小必须在 [0, params.shape[axis])范围内.
- axis: 维度。沿着params的哪一个维度进行抽取indices
- 返回的是一个tensor
def data_iter(batch_size, features, labels):
"""
定义data_iter函数,该函数接收批量大小,特征矩阵和标签向量作为输入,
生成batch_size的小批量。每个小批量包含一组特征和标签
"""
num_examples = len(features)
indices = list(range(num_examples)) # 返回一个从0到(features-1)长度的向量
# 这些样本都是随机读取的,没有特定的顺序
random.shuffle(indices) # random.shuffle()用于将一个列表中的元素打乱顺序
for i in range(0, num_examples, batch_size):
# 从indices中提取从i到min(i + batch_size, num_examples)中的元素,num_examples是为了避免超过features序列的长度
# 一次循环提取的j为tf.Tensor([105 869 42 934 187 470 362 321 483 28], shape=(10,), dtype=int32)
j = tf.constant(indices[i: min(i + batch_size, num_examples)])
yield tf.gather(features, j), tf.gather(labels, j) # 提取features中顺序为j的序列,并将其抽取出来
# 读取第一个小批量数据样本并打印。每个批量的特征维度显示批量大小和输入特征数,批量形状与batch_size相等
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print('X:', X, '\ny:', y)
break
初始化模型参数
在使用小批量随机梯度下降优化模型参数之前,需要先有一些参数通过从均值为0,标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置始化为0
tf.Variable构造一个variable添加进图中,Variable()构造函数需要变量的初始值(是一个任意类型、任意形状的tensor)
这个初始值指定variable的类型和形状通过Variable()构造函数后,此variable的类型和形状固定不能修改了,但值可以用assign方法修改
w = tf.Variable(tf.random.normal(shape=(2, 1), mean=0, stddev=0.01), trainable=True)
b = tf.Variable(tf.zeros(1), trainable=True)
初始化参数之后的任务是更新参数,直到参数足够拟合数据,每次更新都需要计算损失函数关于模型参数的梯度利用梯度向减小损失的方向更新每个参数
sdg()函数中的params为原始函数的w和b,grads则是梯度,lr是学习率
tf.assign_sub(ref, value, use_locking=None, name=None)
释义:变量 ref 减去 value值,即 ref = ref - value
- ref,变量
- value,值
- use_locking,默认 False, 若为 True,则受锁保护
- name,名称
定义模型、损失函数和优化算法
def linreg(X, w, b): # 定义模型
""""线性回归模型"""
return tf.matmul(X, w) + b
def squared_loss(y_hat, y): # 定义损失函数
"""均方误差"""
return (y_hat - tf.reshape(y, y_hat.shape)) ** 2 / 2
def sgd(params, grads, lr, batch_size): # 定义优化算法
"""小批量随机梯度下降"""
for param, grad in zip(params, grads):
param.assign_sub(lr*grad/batch_size)
训练
在每次迭代中,读取一小批量训练样本,并通过模型来得到一组预测,计算完损失后,开始反向传播,存储每个参数的梯度,最后,调用优化算法sdg来更新模型参数
lr = 0.03 # 学习率
num_epochs = 3 # 迭代周期个数
net = linreg # 重命名函数
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
with tf.GradientTape() as g: # tf.GrandientTape()用于计算梯度
l = loss(net(X, w, b), y) # X和y的小批量损失
# 计算l关于[w, b]的梯度
dw, db = g.gradient(l, [w, b])
# 使用参数的梯度更新参数
sgd([w, b], [dw, db], lr, batch_size)
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss{float(tf.reduce_mean(train_l)):f}')
print(f'w的估计误差:{true_w - tf.reshape(w, true_w.shape)}')
print(f'b的估计误差:{true_b - b}')
线性回归的简介实现
读取数据集
调用框架中现有的API来读取数据,将features, labels作为API的参数传递并通过数据迭代器指定batch_size
tf.data.Dataset.from_tensor_slices(),是常见的数据处理函数,
它的作用是将给定的元组(turple)、列表(list)、张量(tensor)等特征进行特征切片,切片的范围是从最外层维度开始的,假设我们有一组特征集合(features),以及这组数据集合所对应的标签集合(labels),那么我们如何将每个数据与其对应的标签进行组合,构成一个个完整训练数据集合([feature_1, label_1],[feature_2, label_2],…)
dataset.shuffle(buffer_size=1000) 从data数据集中按顺序抽取buffer_size个样本放在buffer中,然后打乱buffer中的样本,buffer中样本个数不足buffer_size,继续从data数据集中安顺序填充至buffer_size,此时会再次打乱
- buffer_size:该函数的作用就是先构建buffer,大小为buffer_size,然后从dataset中提取数据将它填满
- batch操作,从buffer中提取。如果buffer_size小于Dataset的大小,每次提取buffer中的数据,会再次从Dataset中抽取数据将它填满
- 所以一般最好的方式是buffer_size=Dataset_size
def load_array(data_arrays, batch_size, is_train=True): # 布尔值表示是否希望数据迭代器对象在每个迭代周期内打乱数据
""""构造一个tensorflow数据迭代器"""
dataset = tf.data.Dataset.from_tensor_slices(data_arrays)
if is_train:
dataset = dataset.shuffle(buffer_size=1000) # 打乱之后再整合
dataset = dataset.batch(batch_size)
return dataset
定义模型
batch_size = 10 # 表明将10个数据整合在一组,构成一个小批量
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter)) # 使用iter构造Python迭代器,并使用next从迭代器中获取第一项
# 定义模型
# keras是TensorFlow的高级API
net = tf.keras.Sequential() # 将多个层串联在一起,并可以将第一层的输出作为第二层的输入
net.add(tf.keras.layers.Dense(1)) # 全连接层在Dense类中定义
# 初始化模型参数
initializer = tf.initializers.RandomNormal(stddev=0.01) # 从均值为0,标准差为0.01的正态分布中随机采样
net = tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1, kernel_initializer=initializer)) # 初始化,在创建层时指定kernel_initializer
# 注意,初始化是推迟执行的(因为不知道输入的维数),只有在第一次尝试通过网络传递数据时才会进行真正的初始化
# 定义损失函数
loss = tf.keras.losses.MeanSquaredError() # 计算均方误差,也称平方L2范数
# 定义优化算法
trainer = tf.keras.optimizers.SGD(learning_rate=0.03) # 利用小批量随机梯度下降算法来优化神经网络
训练
- 通过调用net(X)生成预测并计算损失l(前向传播)
- 通过进行反向传播来计算梯度
- 通过调用优化器来更新模型参数
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
with tf.GradientTape() as tape:
l = loss(net(X, training=True), y)
grads = tape.gradient(l, net.trainable_variables) # 通过 model.trainable_variables 找到需要更新的变量
trainer.apply_gradients(zip(grads, net.trainable_variables)) # 使用 trainer.apply_gradients 更新权重
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
- 比较生成数据集的真实参数和通过有限数据训练获得的模型参数
- 访问参数,首先从net访问所需的层,然后读取该层的权重和偏置
w = net.get_weights()[0]
print('w的估计误差:', true_w - tf.reshape(w, true_w.shape))
b = net.get_weights()[1]
print('b的估计误差:', true_b - b)