importtorch#导入PyTorch库导入之后就可以用Tensor张量进行各种复杂的数学运算 【自动求导】importmatplotlib.pyplotasplt#画图的#导入绘图库 Matplotlib 中的 pyplot模块并给它起个简短的别名plt调用画图功能时只需要输入plt.plot()或 plt.scatter()而不用写那一长串名字。importrandom#导入 Python 原生的 随机数 模块1、数据处理 2、模型 3、训练 4、可视化defcreate_data(w,b,data_num):#生成数据#weight:斜率 bias:截距 data_num:你想要生成的样品总数xtorch.normal(0,1,(data_num,len(w)))#正态分布#均值为0大部分生成的数据都会在0附近 #标准差为1标准差越大数据越分散反之 #为什么用它 在机器学习中我们常用正态分布来初始化权重或生成特征因为它能保证数据的多样性且大部分数据比较稳定。 #data_num:矩阵的行数代表总共要生成多少个样本 #len(w):矩阵的列数代表每个样本有多少个特征维度true_wtorch.tensor([8.1,2,2,4])对应有四个特征所以为4列 ytorch.matmul(x,w)b#matmul表示矩阵相乘#torch.matmul(x, w)执行矩阵乘法。 #这是线性回归的核心公式 y wX b。 #计算理想状态下的y。noisetorch.normal(0,0.01,y.shape)#噪声要加到y上#0, 0.01噪声的幅度。均值为 0标准差为 0.01说明噪声很细微。 #y.shape噪声的形状必须和y一致这样才能给每一个结果都加上一点扰动。 #意义模拟现实中的测量误差或不可控因素。ynoise#叠加噪声returnx,y#把生成的特征和对应的标签吐出来供你后面的 data_provider 调用。num500#定义样本数量true_wtorch.tensor([8.1,2,2,4])#定义真实的权重#这是一个包含 4 个元素的向量意味着每个样本有 4 个特征Dimensions。#这里的 8.1, 2, 2, 4 是预设的“标准答案”稍后你的线性回归模型目标就是跑出接近这些数字的预测值。true_btorch.tensor(1.1)#定义真实的偏置。预设的截距为1.1X,Ycreate_data(true_w,true_b,num)#调用之前定义的函数正式生成数据集#X会是一个500*4的矩阵500行每行4个特征。#Y是对应的标签值plt.scatter(X[:,3],Y,1)#绘制散点图#X[:, 3]python的切片操作表示取出X矩阵所有行的第4列特征#Y对应的标签值作为纵坐标。#1指定散点的大小Size这里设为 1让点看起来很小方便观察密集的数据。plt.show()#将绘制好的图形窗口显示出来defdata_provider(data,label,batchsize):#每次访问这个函数就可以访问一批数据#data特征矩阵X。label标签向量Y。#batchsize批量大小。比如总共 500 个数据你设置batchsize10那模型每次就只看 10 个样本一共看 50 次。lengthlen(label)#获取数据的总样本数。indiceslist(range(length))#如果总共有 500 个样本这一步会生成 [0, 1, 2, ..., 499]。random.shuffle(indices)#打乱索引我不能按顺序取 把数据打乱#这是深度学习训练的关键。如果数据是按某种顺序排列的比如先全是苹果后全是橘子模型可能会产生偏见。打乱顺序能让模型学得更“客观”。foreachinrange(0,length,batchsize):#循环遍历数据每次跳过一个 batchsize 的距离#假设 batchsize10那么 each 的取值依次是 0, 10, 20, ...。这决定了每一小批数据的起点。get_indicesindices[each:eachbatchsize]#切片操作获取当前这一批次的索引子集赋给变量get_indices#比如第一轮循环切出的就是打乱后的前 10 个索引如 [42, 7, 129, ...]get_datadata[get_indices]get_labellabel[get_indices]#根据切出的索引去原始数据里把对应的“特征”和“标签”拎出来yieldget_data,get_label#有存档点的return#当程序运行到这一句它会把当前的这批数据丢出去然后暂停在这里。#等到模型下一次问它要数据时它会从刚才暂停的地方继续跑直到完成下一次 yield。batchsize16# for batch_x,batch_y in data_provider(X,Y,batchsize):# print(batch_x,batch_y)# breakdeffun(x,w,b):#模型ywxbpred_ytorch.matmul(x,w)b#代表模型根据当前的权重和偏置计算出来的预测值returnpred_y#模型训练的目标就是让这个 pred_y 无限接近真实的标签YdefmaeLoss(pre_y,y):#损失函数returntorch.sum(abs(pre_y-y))/len(y)#abs(pre_y-y)计算预测值和真实值之间的距离差的绝对值#torch.sum(abs(pre_y-y))/len(y)把所有样本的距离加起来除以样本数得到平均距离。数值越大说明模型猜得越离谱反之。defsgd(paras,lr):#随机梯度下降更新参数更新模型。withtorch.no_grad():#属于这句代码的部分不计算梯度#暂时关闭梯度跟踪。#这一步是在更新参数改答案而不是在计算推导过程。我们不希望更新参数这个动作本身也被记录在计算图中否则会乱套。forparainparas:#遍历所有需要更新的参数即w和bpara-para.grad*lr#参数更新核心公式para.grad梯度 #不能写成para para-para.gradlrpara.grad.zero_()#使用过的梯度归0#梯度清零。#PyTorch 默认会累加梯度。如果你不清零下一轮计算出的梯度就会加在这一轮的梯度上导致模型跑偏。就像每次做新题目之前要把黑板擦干净。lr0.03#学习率设为0.03太大会跳过最优解太小收敛地太慢w_0torch.normal(0,0.01,true_w.shape,requires_gradTrue)#这个w需要计算梯度#“基于正态分布随机初始化权重参数并开启自动求导监控。#requires_gradTrue 是整段代码的灵魂开关。#不加它PyTorch 只会把它当成一个普通的常量数组。当你运行 Loss.backward() 时程序会因为找不到求导对象而报错或者梯度永远是空。#加上它PyTorch 会在内存里为 w_0 额外开辟一个空间即 w_0.grad专门用来存储稍后算出来的“修正方案”。b_0torch.tensor(0.01,requires_gradTrue)#初始化偏置print(w_0,b_0)epochs50#设置迭代轮数#50 意味着模型要把整个 500 个样本的数据集完整地“学习” 50 遍。#轮数太少学不透欠拟合轮数太多可能死记硬背过拟合。forepochinrange(epochs):#大循环外循环每一轮成为一个epochdata_loss0#每一轮开始前清零用来记录这一轮所有 Batch 跑完后总共产生的误差forbatch_x,batch_yindata_provider(X,Y,batchsize):#小循环内循环#这行 for 循环其实包含了三个隐藏步骤#呼叫调度员程序调用 data_provider(X, Y, batchsize) 函数。#获取批次由于你函数里写了 yield程序每执行一次循环就会去 data_provider 里“抓”出 16 个 (batchsize) 随机打乱后的样本。#拆解包裹将抓出来的这批数据自动赋值给 batch_x特征和 batch_y标签pred_yfun(batch_x,w_0,b_0)#前向传播Forward Pass。把这 16 个人的特征丢进模型 $y wx b$ 里算出一组预测值。lossmaeLoss(pred_y,batch_y)#计算损失Calculate Loss。对比预测出的房价和真实房价差了多少。得到一个具体的数值标量loss.backward()#求偏导sgd([w_0,b_0],lr)#参数更新得到新的w和bdata_lossloss#记录误差。把每一个 Batch 的损失加起来方便我们最后观察模型有没有变聪明。print(epoch %03d: loss: %.6f%(epoch,data_loss))#输出每一轮训练后的总误差print(真实的函数值是,true_w,true_b)print(训练得到的参数值是,w_0,b_0)idx3#选择第 4 个特征索引为 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()plt.plot中#去掉.detach().numpy()后就是plt.plot( 横坐标x, 纵坐标y )#plt.plot(X[:, idx], X[:, idx]*w_0[idx]b_0)#横坐标是 X[:, idx] 纵坐标(y)是 X[:, idx]*w_0[idx]b_0