生成数据集(带有噪声的线性模型) w=[2,-3.4]^T、b=4.2和噪声项σ生成数据集和标签(σ即模型预测和标签时的观测误差),任务是恢复模型的参数 ,尽量使用低维数据(容易可视化,看得清形状)
X=tf.zeros((num_examples,w.shape[0]))
tf.zeros()是一个创建一个所有元素都为0的张量,后面的参数是形状。此处num_examples=1000,w.shape[0]=2,所以创建了shape=(1000, 2), dtype=float32的张量。
X+=tf.random.normal(shape=X.shape)
tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
tf.random_normal用于从正态分布中输出随机值
tf.reshape(
tensor, shape, name=None
)
返回值也是一tensor,输出类型和输入类型相同
tf.reshape函数用于对输入tensor进行维度调整,不会修改内部元素的数量以及元素之间的顺序,如果需要修改张量的维度来实现对元素重新排序,需要使用tf.transpose。(来源)
reshape(-1,1)中的-1是由整体形状确定这个参数,比如[1,6]变成[-1,3]
就是[2,3]
tf.matmul()矩阵相乘
该部分代码(生成数据集)
def synthetic_data(w,b,num_examples):
X=tf.zeros((num_examples,w.shape[0]))
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
true_w=tf.constant([2,-3.4])
true_b=4.2
features,labels=synthetic_data(true_w,true_b,1000)
读取数据,该函数接收批量大小、特征矩阵和标签向量,生成batch_size大小的小批量(包括一组特征和标签)
num_examples=len(features) #数据数量就由特征矩阵长度得到
range(start, stop, step)
range() 函数返回数字序列,默认从 0 开始,默认以 1 递增,并以指定的数字结束。
step为步长,每次挪动这么多,如3,6,9,12的step为3
random.shuffle()用于将一个列表中的元素打乱顺序
tf.constant(
value,
dtype=None,
shape=None,
name='Const',
verify_shape=False
)
创建一个常量tensor,按照给出value来赋值,可以用shape来指定其形状。value可以是一个数,也可以是一个list。value是必须的。
for i in range(0,num_examples,batch_size):
j=tf.constant(indices[i:min(i+batch_size,num_examples)])
这里是把打乱后的索引从0开始到num_examples以batch_size的大小生成数据
该部分代码(读取数据):
def data_iter(batch_size,features,labels):
num_examples=len(features) #数据数量就由特征矩阵长度得到
#print(len(features))
indices=list(range(num_examples))
print(indices)
# 随机读取样本
random.shuffle(indices)
for i in range(0,num_examples,batch_size):
j=tf.constant(indices[i:min(i+batch_size,num_examples)])
yield tf.gather(features,j),tf.gather(labels,j)
先简单把他理解成return,带yield的函数是一个生成器,而不是一个函数,使用next()就会从上次执行结束的地方继续执行。
yield()用法
通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
初始化参数之后更新参数,直到这些参数足够拟合我们的数据。每次更新都需要计算损失函数关于模型参数的梯度,有了这个梯度就可以向减小损失的方向更新每个参数。
w = tf.Variable(tf.random.normal(shape=(2, 1), mean=0, stddev=0.01),
trainable=True)
b = tf.Variable(tf.zeros(1), trainable=True)
将模型的输入和参数同模型的输出关联起来。
def linreg(X,w,b):
return tf.matmul(X,w)+b
定义损失函数来计算损失函数的梯度。
这里使用平方损失函数,此处需要将真实值y的形状转换为和预测值y_hat的形状相同。
在上面的代码中我们可以看出y的形状是(1000,1)。
y=tf.reshape(y,(-1,1))
看一下预测值y_hat的形状,在下面训练部分代码中y_hat是linreg(X,w,b)
的返回值,返回的就是tf.matmul(X,w)+b
,X的形状是(1000,2),w的形状是(2,0)。
def squared_loss(y_hat,y):
return (y_hat-tf.reshape(y,y_hat.shape))**2/2
小批量随机梯度下降法
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。
该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr决定。
计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params, grads, lr, batch_size):
for param,grad in zip(params,grads):
param.assign_sub(lr*grad/batch_size)
1.在每次迭代中,读取一小批量训练样本,通过模型来获得一组预测。
2.计算完损失后,开始反向传播,存储每个参数的梯度。
3.最后,调用优化算法sgd来更新模型参数。
在每个迭代周期(epoch)中,使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。
迭代周期个数num_epochs和学习率lr都是超参数。
梯度求解利器
该部分代码(训练):
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:
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}')
epoch 1, loss 0.000059
epoch 2, loss 0.000053
epoch 3, loss 0.000053
w的估计误差: [-7.3075294e-04 2.5033951e-05]
b的估计误差: [0.00141048]
线性模型的基本实现已经大概了解了,但是其中变量类型、参数传递和函数用法还不是很熟悉,需要再多看多敲多练,笔记中的文字有很多参考,摘抄于自动手深度学习网站的3.2节。