李哥说这是最基本的需要用到的东西,以后的项目都围绕这些来做。他用代码带我们写了怎样实现线性方程组y=x*w+b这样的算法
import torch
import matplotlib.pyplot as plt # 画图的
import random #随机
李哥首先引用了上面三个库,我将torch理解为某一群高手发明的用于矩阵运算的库,plt是一个用于画图的库,random很好理解,是一个随机的库。
def create_data(w, b, data_num): #生成数据
x = torch.normal(0, 1, (data_num, len(w)))
y = torch.matmul(x, w) + b #matmul表示矩阵相乘
noise = torch.normal(0, 0.01, y.shape) #噪声要加到y上
y += noise
return x, y
上面是李哥定义的第一个函数create_data(),用于生成数据,其中的x和y就是为了满足y=x*w+b这个运算的真实数,x是一个矩阵,y是x经过矩阵运算后得到的列向量。传入的w和b都是向量,w是权重,用来预测x求出的y,b是偏置,用来提供基础值
x = torch.normal(0, 1, (data_num, len(w)))
矩阵x的形状由传入的参数data_num确定行,len(w)确定列,在torch.normal()中,前两个参数0和1代表的是正态分布中的均值为0,标准差为1,生成随机数作为矩阵x中的元素。
noise = torch.normal(0, 0.01, y.shape)
noise是噪声,目的是为了追求普遍适应性,所以要模拟出来再加到y上。0和0.01依然是正态分布中的均值0和标准差0.01,根据向量y的形状y.shape生成元素,加到y上。最后返回矩阵x和真实值y。
num = 500
true_w = torch.tensor([8.1,2,2,4])
true_b = torch.tensor(1.1)
X, Y = create_data(true_w, true_b, num)
plt.scatter(X[:, 3], Y, 1)
plt.show()
这里李哥定义了传入create_data()的参数,num=500说明要做一个500行的矩阵,true_w和true_b分别预测了这个f(x)会输出什么结果。
plt是画图,绘制散点图函数scatter()中参数X[:,3]的“:”意思是二维数组(矩阵)X的所有行,“3”意思是下标为3实际上是第4列的元素,总体来说就是拿第四列数据为散点图的x轴。Y作为散点图的y轴。1表示点的大小。
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
batchsize = 16
# for batch_x, batch_y in data_provider(X, Y, batchsize):
# print(batch_x, batch_y)
# break
上面是李哥又定义了一个data_provider()函数,传进来的三个参数分别是数据,标签,步长。目的应该是为了随机抽取这么多数据中的一部分样本,数量根据步长来看是每次取16行的数据。
length是标签的长度。indices是数据的索引,生成了一个顺序的列表,里面是刚才的num定下的总共500个数据
random.shuffle()将会把indices这个顺序列表打乱
for each in range(0, length, batchsize):
这个循环的意思是从indices这个列表长度的下标0开始,以步长batchsize=16遍历整个indices这个数据集合。这个时候遍历出来的数据虽然下标是按顺序的,但是实际上数据的顺序是被打乱了的。
get_indices = indices[each: each+batchsize]
get_data = data[get_indices]
get_label = label[get_indices]
yield get_data,get_label #有存档点的return
然后把这一次取出的样本存到get_indices中。get_data和get_label就是要让这一份样本的数据和标签能够一一对应。yield是一个与return类似功能的东西,会把当前遍历的这份样本数据先返回出来,在下一次返回的就会是下一批样本的数据了。比如这次返回的是0到15共16个数据,下一次就是返回16到31共16个数据,这样一批一批地输出。
def fun(x, w, b):
pred_y = torch.matmul(x, w) + b
return pred_y
接下来李哥定义了一个功能函数fun(),为了预测本该得出的y是什么样的。和create_data()类似,也是实现了y=x*w+b,但是没有添加噪声noise来模拟真实情况。
def maeLoss(pre_y, y):
return torch.sum(abs(pre_y-y))/len(y)
上面是李哥定义了一个计算损失的函数maeLoss(),目的是为了得到模拟出的真实值和预测值的差距。abs()计算了pre_y-y这两个张量的差的绝对值,然后sum()求和,再除以y的长度(len(y)),这样就得到了平均绝对误差。返回。算法思想如下图
def sgd(paras, lr): #随机梯度下降,更新参数
with torch.no_grad(): #属于这句代码的部分,不计算梯度
for para in paras:
para -= para.grad * lr #不能写成 para = para - para.grad*lr
para.grad.zero_() #使用过的梯度,归0
sgd()是李哥定义的实现简单《随机》梯度下降的函数。设置torch.no_grad()是为了在更新参数的时候不要再去计算梯度,这样容易出错。
这个式子中η是学习率,L对θ求偏导以后就是梯度,学习率和梯度相乘,加到参数θº得到θ¹,相当于优化了参数。
所以函数sgd()的功能是循环遍历传入的paras,定义para,作为参数优化以后得到的结果。每次循环都要把使用了的梯度归零,以保证数据的准确性。
lr = 0.03
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True) #这个w需要计算梯度
b_0 = torch.tensor(0.01, requires_grad=True)
print(w_0, b_0)
李哥将学习率lr设定为0.03,w_0是根据均值0,标准差0.01随机出的一个参数,而且规模和真实参数true_w一样,
true_w = torch.tensor([8.1,2,2,4])
requires_grad=True意思是w_0这个参数是求偏导以后的,同理下面的b_0,是直接设置为0.01了,再求b_0的偏导。
epochs = 50
for epoch in range(epochs):
data_loss = 0
for batch_x, batch_y in data_provider(X, Y, batchsize):
pred_y = fun(batch_x,w_0, b_0)
loss = maeLoss(pred_y, batch_y)
loss.backward()
sgd([w_0, b_0], lr)
data_loss += loss
print("epoch %03d: loss: %.6f"%(epoch, data_loss))
然后李哥就设置了一个50次的循环。定义一个data_loss = 0,初始化损失的累计值。里面又一个循环,根据data_provider()函数返回的,随机抽取的一批样本,共16个数据(get_data)和对应的标签(get_label)。这个双层循环的作用是遍历50次,每次遍历一批16个数据。
首先pred_y是预测值,没有加噪声的,由fun()函数算出来,这是深度学习训练过程中的向前传播。loss是损失值,由marLoss()函数得出。
loss.backward()是实际上进行了的对参数求偏导的操作,实现反向传播,结果会放在grad中,李哥说这一行代码会把所有求出的loss值加到这些参数上(应该是w_0,b_0)。
sgd()进行简单随机梯度下降,按照:
优化后的参数=优化前的参数-学习率*参数的偏导数
求得优化后的参数。
data_loss += loss累加损失。随后打印结果,每次打印的结果说明损失值的大小,越接近0,说明模型训练的性能越好。
print("真实的函数值是", true_w, true_b)
print("训练得到的参数值是", w_0, b_0)
训练结束后输出真实函数值和训练得到的参数值。
这些训练以后得到的参数,之后都会被梯度计算。
idx = 3
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
plt.scatter(X[:, idx], Y, 1)
plt.show()
最后画图。先看plot()里的参数:
1.X[:,idx]代表矩阵X的所有行的第4列(前面定义了idx=3,下标从0到3,实际上是第四个的意思)
2. .detach()。李哥说为了画图和以后要进行的操作,就要把它们从张量网上摘下来,不再计算梯度。大模型说是从当前计算图中分离出这个张量。
3. .numpy()。把这些张量转化为NumPy数组。大模型说是因为matplotlib期望输入的是NumPy数组的结构
4.w_0[idx]是w_0这个参数同时也是向量的第4个元素。
总体来看依然是线性表达y=x*w+b这个形式在特定特征维度idx上的拟合线,x是特征值X[:, idx].detach().numpy(),y是模型预测值X[:,idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy()。
plt.scatter(X[:, idx], Y, 1)
拿矩阵X的第四列(idx=3)元素作为图像的x轴,Y作图像y轴,点大小为1。