之前进行过机器学习的数学笔记-回归 中,主要是人工计算导数以及更新计算
本次为对应《动手学深度学习》 线性回归部分内容,使用PyTorch 来简化实现
构造测试数据
找到合适的数据比较麻烦,我们可以自己生成对应的测试用的数据,添加一定的噪声
线性模型:$\mathbf{y}=\mathbf{W}\mathbf{X}+b$,其中 $\mathbf{W}$ 和 $\mathbf{X}$ 都是矩阵
比如可以是$y=w_1x_1 + b$ 也可以是 $y=w_1x_1+w_2x_2+b$ 等等
我们可以根据输入的 $\mathbf{W}$ 和 $b$ ,根据$\mathbf{W}$的形状生成对应高斯分布的$\mathbf{X}$,执行 $\mathbf{y}=\mathbf{W}\mathbf{X}+b$ 获取对应的 $y$ 值
X 的值可以从高斯分布中采样,在pytorch中使用 torch.normal(均值, 标准差, 数据形状)
函数
对于$y=w_1x_1 + b$ 的形式,比如 w1=5.2, b = 3,那么可以用如下方法生成X
1 2 3 4 5 6 7 8 9 10 11 12 13 14 torch.normal(0 , 1 , (10 , 1 )) > tensor([[ 1.7538 ], [ 0.6664 ], [-0.4381 ], [ 2.4668 ], [ 0.4584 ], [-2.2409 ], [ 1.2411 ], [ 1.0235 ], [-0.6828 ], [ 1.3618 ]])
而对于 $y=w_1x_1+w_2x_2+b$ 的形式,比如 w1=2.3, w2= 4.5, b = 3,那么可以用如下方法生成X
1 2 3 4 5 6 7 8 9 torch.normal(0 , 1 , (5 , 2 )) > tensor([[ 0.7917 , -0.4366 ], [-1.8961 , 1.1405 ], [-0.7060 , 0.1232 ], [ 0.4644 , 1.7307 ], [ 2.6247 , -1.2155 ]])
矩阵乘法,可以使用torch.matmul(input, other)
函数
1 2 3 4 5 6 7 8 9 10 import torchA = torch.tensor([[1 , 2 ], [3 , 4 ]]) B = torch.tensor([[5 , 6 ], [7 , 8 ]]) C = torch.matmul(A, B)
最终生成测试数据函数定义如下:
1 2 3 4 5 6 7 8 9 10 def synthetic_data (w, b, num_examples ): X = torch.normal(0 , 1 , (num_examples, len (w))) y = torch.matmul(X, w) + b y += torch.normal(0 , 0.01 , y.shape) return X, y
构造测试数据
1 2 3 4 5 6 true_w = torch.tensor([2 , -3.4 ]) true_b = 4.2 features, labels = synthetic_data(true_w, true_b, 1000 )
线性回归实现
定义模型
1 2 3 def linreg (X, w, b ): return torch.matmul(X, w) + b
初始化模型参数
随机初始化,w的两个值也是从高斯分布中采样,b的值初始化为0,requires_grad=True表示会自动计算
1 2 3 4 5 6 7 8 w = torch.normal(0 , 0.01 , size=(2 ,1 ), requires_grad=True ) b = torch.zeros(1 , requires_grad=True )
定义损失函数
损失值就是真实值与预测值的差异,所以可以定义:真实值与预测值的差的平方和,再除以2(为了方便求导)
即:$l^{(i)}(w,b)=\frac 1 2 (\hat{y}^{(i)}-y^{(i)})^2$
1 2 def squared_loss (y_hat, y ): return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
我们的目标就是使损失函数最小
定义优化算法
根据学习率与对应的梯度,更新参数 w 和 b
1 2 3 4 5 6 7 def sgd (params, lr, batch_size ): with torch.no_grad(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_()
训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 lr = 0.03 num_epochs = 500 for epoch in range (num_epochs): y_hat = linreg(features, w, b) l = squared_loss(y_hat, labels) l.sum ().backward() sgd([w, b], lr, 1000 ) if epoch % 100 == 0 : with torch.no_grad(): train_l = squared_loss(linreg(features, w, b), labels) print (f'epoch {epoch + 1 } , loss {float (train_l.mean()):f} ' )
这时候我们可以看下计算出的参数和实际的参数值的区别
实际值是 w=[2, -3.4] 和 b=4.2, 已经非常接近
PyTorch高级API实现
对于常用的线性回归等,PyTorch中已经提供了一些高级组件可以直接使用
数据分批读取
1 2 3 4 5 6 7 8 9 10 from torch.utils import datadef load_array (data_arrays, batch_size, is_train=True ): dataset = data.TensorDataset(*data_arrays) return data.DataLoader(dataset, batch_size, shuffle=is_train) batch_size = 10 data_iter = load_array((features, labels), batch_size)
初始化模型及参数
1 2 3 4 5 6 from torch import nnnet = nn.Sequential(nn.Linear(2 , 1 )) net[0 ].weight.data.normal_(0 , 0.01 ) net[0 ].bias.data.fill_(0 )
定义损失函数及优化算法
1 2 3 4 5 loss = nn.MSELoss() trainer = torch.optim.SGD(net.parameters(), lr=0.03 )
训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 num_epochs = 3 for epoch in range (num_epochs): for X, y in data_iter: l = loss(net(X), y) trainer.zero_grad() l.backward() trainer.step() l = loss(net(features), labels) print (f'epoch {epoch + 1 } , loss {l:f} ' )
结果
1 2 3 4 5 w = net[0 ].weight.data b = net[0 ].bias.data w, b