1.数据生成
def create_data(w, b, data_num):
x = torch.normal(0, 1, (data_num,len(w)))
y = torch.matmul(x, w) + b
noise = torch.normal(0,0.01,y.shape) # 与Y同型
y += noise # y = y+ noise
return x, y
输入w,b为两个张量,data_num为int类型,也是x的行数。
torch.normal是按照正态分布生成的随机数,其参数为(EX, DX, (row, col)),如上述代码,生成一个按照均值为0, 方差为1的标准正态分布生成一个随机的data_num*len(w)的矩阵。
torch.matmul用于计算矩阵乘法。使用时需要注意两个向量(或矩阵)的维度是否符合。
noise表示噪声,实际情况必然会存在干扰,取一个方差小的数模拟现场噪声。其中,y.shape表示噪声的分布与y一致,如y为一个列向量的,则noise同上。
最终得到矩阵x与向量y,结果如下:
tensor([[ 0.0488, 0.5074, 1.3997, 1.4776],
[-0.6572, -0.9599, 1.3429, 0.9530],
[ 1.3438, -0.9554, -1.4330, 0.1978],
[ 0.5174, -0.4910, 0.4167, -0.6533],
[ 0.0413, 1.2715, -0.2776, 0.7668]]) tensor([11.2242, 0.3532, 8.0114, 2.5337, 6.4912])
tensor([[ 0.5174, -0.4910, 0.4167, -0.6533],
[ 0.0488, 0.5074, 1.3997, 1.4776],
[-0.6572, -0.9599, 1.3429, 0.9530],
[ 0.0413, 1.2715, -0.2776, 0.7668],
[ 1.3438, -0.9554, -1.4330, 0.1978]]) tensor([ 2.5337, 11.2242, 0.3532, 6.4912, 8.0114])
2.数据提供
def data_provider(data, label, batchsize): # 每次访问, 就提供一笔数据。
length = len(label)
indices = list(range(length))
random.shuffle(indices)
for each in range(0, length, batchsize):
get_indices = indices[each: each+batchsize]
get_data = data[get_indices]
get_label = label[get_indices]
yield get_data, get_label # 有存档点的return
data与label为两个张量,不妨设为上一步所得到的X, Y,batchsize则为一批数据的个数。如果num=500或更大,若一个一个训练则面临内存紧缺的风险。采用batchsize一批一批处理,即使有异常值,也不会对结果造成过大的偏差。
shuffle()函数打乱了indices的顺序。
yield可以理解为有存档点的return。如上例,每调用一次data_provider,就返回两个数据,这两个数据是继承之前结果的:第一次调用返回内容对应的下标为0-15,第二次则为16-31,以此类推。
每batchsize个数据分为一批,故get_data为batchsize行的矩阵,get_label为batchsize行的向量。
根据之前生成的X,Y,由data_provider所提供的数据如下:
batchsize = 16
for batch_x, batch_y in data_provider(X, Y, batchsize):
print(batch_x)
print('----------------------------------')
print(batch_y)
print('---------------一轮完毕-------------')
tensor([[-0.6969, 0.5601, -1.0732, -0.9905],
[ 1.0412, -1.3832, 1.0866, 1.5020],
[ 0.2739, -1.6060, 1.6882, 1.1389],
[-0.1840, 0.6515, 0.4144, -0.9390],
[-1.7548, 0.7327, -0.1830, -2.0444],
[ 0.1011, 0.4585, 0.4756, -0.2226],
[ 0.3497, 1.3155, 2.8315, 0.6365],
[ 0.6119, -0.1277, -0.5672, 0.4576],
[-0.8589, -0.1322, -0.9338, -1.6677],
[-1.5129, -1.2890, -0.1689, 1.5021],
[ 0.0945, 1.3924, 0.5321, 1.3429],
[-0.1838, 0.8477, -1.4569, 0.4252],
[ 0.3052, 1.1281, -2.0296, -0.1155],
[-0.6086, 1.1425, 0.4968, -0.1325],
[ 0.9622, -0.2039, 2.4217, -0.1469],
[-0.5701, 1.2485, 1.1627, 0.2082]])
----------------------------------
tensor([ -9.5497, 14.9488, 8.0482, -2.0173, -20.1968, 2.8986, 14.7851,
6.5136, -14.6656, -8.0559, 11.0894, 0.1062, 1.3021, -1.0893,
12.7336, 2.1376])
---------------一轮完毕-------------
3.过程函数
def fun(x, w, b):
pred_y = torch.matmul(x,w) + b
return pred_y
def maeLoss(pred_y, y):
loss = torch.sum(abs(pred_y - y)) / len(y)
return loss
def sgd(paras, lr):
with torch.no_grad(): # 接下来的计算 不算梯度
for para in paras:
para -= para.grad*lr # A -= 3 A = A-3
para.grad.zero_() # 把梯度归0
a.预测函数
由第一节内容可得,。此处x,w,b均为向量或矩阵(张量形式),一层全连接层的pred_y可视为上述计算过程。
b.Loss计算
Loss的输入为预测值pred_y与真实值y。对二者差值的模求和并取平均值,可得到目标loss值。
c.梯度下降
paras表示所有参数,lr(learning rate)表示学习率,para.grad表示这个参数的梯度。
前向过程需要计算梯度,而梯度下降过程即反向回传时不能计算梯度,故需要使用torch,no_grad()。根据梯度下降算法,每一次减去学习率与梯度的积。
para.grad_zero()表示梯度归零。梯度清零是为了避免梯度累加导致训练效果不佳。分批计算使得每次只使用一个 batch 的数据进行反向传播来计算梯度并更新模型参数。如果不清零梯度,每次计算的梯度都会累加在一起,导致模型的更新不准确。
(注意:必须使用para -= para.grad*lr,若写成para = para - para.grad * r 则会被认为创建了一个para)
4.训练
lr = 0.03
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True) # 计算梯度
b_0 = torch.tensor(0.01, requires_grad=True) # 计算梯度
print(w_0, b_0)
epochs = 50
for epoch in range(epochs):
data_loss = 0
for batch_x, batch_y in data_provider(X, Y, batchsize):
pred = fun(batch_x, w_0, b_0)
loss = maeLoss(pred, batch_y)
loss.backward()
sgd([w_0,b_0], lr)
data_loss += loss
print("epoch %03d: loss: %.6f"%(epoch,data_loss))
print("原来的函数值:",true_w, true_b)
print("当前值", w_0, b_0)
基于上述函数,我们可以模拟训练过程。
首先,设置一个学习率lr,确定训练轮数epochs。初始化w_0, b_0,w_0为一个服从正态分布的向量,b_0为一个数值。requires_grad=True表示梯度会在前向过程中保留下来,允许backward()追踪这个参数并求梯度。
接下来可以进行多轮训练。每一轮更新data_loss为0并开始累加,data_provider提供两个张量,故用batch_x(训练集输入参数-矩阵形式), batch_y(训练集真实值y)来接收。backward()表示梯度回传,对每一个全连接层的参数进行梯度下降算法,并用data_loss记录这一批数据的loss值。
最后,对处理前后的值打印并比较,打印结果如下。
epoch 000: loss: 7.677499
epoch 000: loss: 17.258682
epoch 000: loss: 25.320398
epoch 000: loss: 32.237797
epoch 001: loss: 9.790411
epoch 001: loss: 17.207645
epoch 001: loss: 24.195601
epoch 001: loss: 37.130936
epoch 002: loss: 7.068033
epoch 002: loss: 15.839411
epoch 002: loss: 25.096804
epoch 002: loss: 28.144030
epoch 003: loss: 8.310600
epoch 003: loss: 15.232446
epoch 003: loss: 24.112923
epoch 003: loss: 32.670464
epoch 004: loss: 4.021797
epoch 004: loss: 14.452908
epoch 004: loss: 24.347878
epoch 004: loss: 28.234314
......
原来的函数值: tensor([8.1000, 2.0000, 2.0000, 4.0000]) tensor(1.1000)
当前值 tensor([3.7664, 1.5355, 1.6656, 2.8454], requires_grad=True) tensor(1.1762, requires_grad=True)
5.画图
idx = 1
plt.plot(X[:, idx].detach().numpy(), X[:,idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy(), label="pred")
plt.scatter(X[:,idx], Y, 1, label="true")
plt.show()