如果你想先跑起来一个神经网络代码,那么请你先移步我的上一篇博客,是关于如何手动构建softmax神经网络的,那篇博客的代码是可以直接跑起来的.
在构建整个神经网络的过程中我们不可避免地会碰到很多语言概念理解使用上的问题,ai工具碰上这类问题基本就寄了,所以我们将会从以下几个方面来补充说明,如何更加随心所欲地构建代码.
ps:一点题外话
我在前两个月因为一些感情和社交问题,学习基本处在停摆的一个状态,所以产出不是很多而且主要集中在机器学习这方面吧.在这里先道个歉了.
一直以来我都觉得,我想去做好某些事情,也想保护好某些人,让身边的每个人都感觉到很舒服, 但我大概真的有点累了 , 我不够聪慧,也不够强大,唯一的优点就算能坚持点事情.在感情和友情方面也是一样,我想,我大概是没有什么优点能值得别人过多的关注.我这个人也会因为种种原因敏感多疑, 所以在很多地方我选择了完全离开, 不在沾染任何瓜葛.
告别一段习惯的生活i和地方,固然足够痛苦, 但是人生就像迭代训练一样,每个人都有自己的偏好,每个人的都有自己的权重,或许我在任何方面都是0吧. 但我仍然期待着未来,因为每个人都有着自己的学习率,只要能经受得住反向传播,就一定能得到我希望的东西吧.
接下来的两月我会尽快更新完计算机网络部分,并且及时发布vilas.js的下一个版本
遍体鳞伤,而热切地期待着未来
1.关于向量梯度无法参加运算的问题:
其实这个情况在实际工程中遇到的很少,我也是在一个很机缘巧合的情况下才发现这个问题,张量tensor类型并不只是单纯的保存一些数据,而是会存在一些属性.
其中某个属性被称之为grad_fn,这个属性会指明张量是怎么样计算得到的,并且会指向梯度计算的函数.有了这个函数,神经网络才能进行正常的梯度计算以及迭代.一般来书评,绝大部分经过正常可微分计算的结果,都是携带这个属性. 而用户自己定义的张量一般不具有这个属性.
我在这边碰到的情况是这样的,在某个神经网络的自定义层中.我们的计算逻辑是这个
max_value,max_indices=torch.max(X,dim=1)
这个函数返回了两个张量,具体是什么无所谓,重点是第一个张量是携带grad_fn属性的,而另一个张量不存在这个属性,如果返回后者,在迭代更新的步骤会出现问题.
如果想要强行让其参与训练,可以尝试使用函数声明"这个张量虽然可能没有梯度函数,但是被允许用来训练"
max_indices.requires_grad_()
但是这样做的后果就是神经网络本身的参数是无法迭代的.导致模型无法训练
# 总结来说就是:问题就出在有些自定义张量无法添加合适的grad_fn属性,虽然反响传播可以正常计算,但是在进行参数迭代的时候就出问题。。。
#大多数操作,比如+-*/ ,就会把张量带上grad_fn属性,但是少数操作会产生一个纯的空白梯度
# 没有梯度无法参与计算反响传播,因此这种情况我们一般会避免,所以不用担心
2. 关于参数的读取和一些api
其实这个部分算是很重要的,在有些小型的迭代过程中,我们不可避免地需要查询一些属性信息,并且把参数拿去迭代,所以这里我们分成两个部分
(1)查看形式
我们用的很多的api其实都是查看形式,会返回一个矩阵张量/字典,然后查看其中的数据参数
参数的读取方式其实很简单
想要读取整体数据可以使用state_dict()函数,可以对单独的层使用,也可以对整体的神经网络使用
print(net.state_dict())#读取一整个神经网络的参数信息
print(net[0].state_dict())#读取一个层的权重以及相关的信息
print(net[0][1].state_dict())#读取嵌套层的权重以及相关的信息
(2)使用形式
首先是对于整体网络来说,我们常用的一个参数是net.parameters(),这个其实更多用来传入给学习函数把...
train = torch.optim.SGD(net.parameters(), lr=0.003)
然后是对于单一层,在这里我们可以直接访问weights和bias两个参数,甚至可以读取和修改
# 直接修改
self.weight=torch.randn(256,10,requires_grad=False)
self.bias=torch.randn(10,requires_grad=False)
# 也可以用来初始化
nn.init.normal_(m.weight,std=0.1)
nn.init.constant_(m.bias,0.1)
3.关于张量数据的存取(pt,pth格式文件),参数加载
在pytorch中,存在一种pt,pth文件格式用来保存张量(虽然这俩关键字一般不会出现在文件的后缀里面),并且支持使用save和load函数在文件和张量之间来回转化,并且支持结合state_dict(), load_state_dict函数实现把训练好的模型参数保存,然后重新加载.
首先张量可以实现直接存储,使用torch.save函数
torch.save(torch.tensor([0,1,2,3,4,5]), 'target')
此时这个张量0123456,就会保存在一个名为target的文件里面
加载的话需要使用torch.load
print(torch.load('target'))
# (Tensor[0,1,2,3,4,5,6])
接下来尝试一下参数的加载和保存
有些时候,我们可以直接在pytorch中加载别人已经训练好的参数,免去了我们自己训练一个神经网络.
参数的存储:
torch.save(net.state_dict(), 'mlp.params')
参数的加载:
net.load_state_dict(torch.load('mlp.params'))
4.关于CSV数据的读取和处理
csv的全称为逗号分隔文件,内部结构很类似一个excel表格,所以本质上也是一个文本文件.
既然是文本文件,我们就需要创建文件的链接对象.
然后通过pandas库中的read_csv函数进行操作,得到一个DataFrame类型对象
这个对象可以直接使用函数values转化为python数组
最后可以调用对应的tensor方法,将其转化为参数了
举个例子:
with open('./data/house_tiny.csv','r') as file:
data=pandas.read_csv(file)
print('数据',type(data))
print('使用values可以转化为数组的形式',data.values)
#values的输出结果忽略了列,纯纯的数据数组,然后数组就可以直接传入进入torch.tensor中
print('最后使用torch中的语法直接获取一个张良',torch.tensor(data.values))
#然后就可以使用高级语法对数据进行划分了
print('x1,x2',torch.tensor(data.values)[:,0:2])
print('y',torch.tensor(data.values)[:,2])
#下面这个部分就可以直接用来训练了
#简单做个总结嗷:
#csv本身也是一种文本文件,可以使用正常的文件读取方式读取,并且根据其本身的结构固定,可以使用pandas中read_csv进行读取
#然后使用values转化为一个数组,最后直接使用torch.tensor转化为张量,最后使用高级语法划分数据集合
另外在之前的文章中,我们获取到的是一个dataset对象,并且使用对应的dataloader函数将其转化为迭代器小批量,不过这里问题不大,需要一个工具类下的工具TensorDataset
from torch.utils.data import TensorDataset
#但是在这里我们会遇到最后一个问题:我们之前是使用小批量来训练的,但是如果我们是从csv里面得到的数据,那么最终是一个张量
#在上一个测试的博客中,我们挖空心思想要把dataset转化为数据,嘿,在这里还要变样
data = torch.tensor([[1,1], [2,2], [3,3], [4,4], [5,5]])
labels = torch.tensor([2, 4, 6, 8, 10])
# 将张量转换为 TensorDataset 对象,这是pytorch的某个包下提供的工具类
# dataset数据的特征就是能把测试数据变成n×2的二维数组,都是这样子的
dataset = TensorDataset(data, labels)
print('x',dataset[0][0])
print('y',dataset[0][1])
另外补充一个很重要的点,DataFrame是pandas中很重要的一个数据结构,很多时候我们对数据进行处理不是在张量中,而是在DataFrame里面实现的.
例如热编码处理,对于非数字的离散数值,会将其修改结构,然后转化为更多的二元列
with open(data_file, 'r') as f:
f=pandas.read_csv(f)
print("修改之前的数据",f)
f=pandas.get_dummies(f, dummy_na=True)
print("热编码之后的数据",f)
# 首先,热编码这些操作是针对’DataFrame‘这个对象,这也是read——csv函数的读取结果
# 这是一种pandas中常用的数据格式之一,结构类似excel表格
5.关于自定义神经网络层的另一种写法
net=nn.Sequential(
nn.Flatten(),
nn.Linear(784,256),
nn.ReLU(),
nn.Linear(256,10),
nn.Softmax(dim=1)#先用softmax处理一下
)
在之前我们定义神经网络通常是这样子写的,我感觉没啥问题......就算不够美观........
其实我们更多的是用这种写法,写在一个类里面
class Net(nn.Module):
#初始化函数,在这个部分里面我们负责把这些东西都进行一个输入
def __init__(self):
super().__init__()
#先把神经网络的层给定义出来
#初始化参数的方法:
self.flatten=nn.Flatten()
self.layer1=nn.Linear(784,256)
self.layer2=nn.Linear(256,10)
#初始化参数
self.init()
# 向前传播,这是一个具体的计算逻辑
def forward(self,X):
X=self.flatten(X)
X=self.layer1(X)
X=nn.ReLU()(X)
X=self.layer2(X)
X=nn.Softmax(dim=1)(X)
return X
# 封装一个初始化参数方法
def init_para(self):
def init(m):
if(type(m)==nn.Linear):
nn.init.normal_(m.weight,std=0.1)
nn.init.constant_(m.bias,0.1)
self.apply(init)
net=Net()
在这个神经网络类中,我们把神经网络层直接放置在构造函数中,作为属性初始化
然后构造向前传播的方法,形成运算逻辑
最后构建一个初始化方法
这样的一个特点就是代码清晰度更高了,可以把类全部移动到定义时刻,这样计算逻辑的发挥更加自由
但是大家应该可以看到一个问题,就算展平层和激活函数层,要么重新构建了对象,要么使用了双括号的语法.
这是因为其本身是属于一个函数,而非一个层,其参数列表内是一些自身的性质参数,而不是向前传播的数据
torch.Flatten(X) #错误
torch.Flatten()(X)#正确
如果想要向前传播就要写在第二个括号内,注明这是向前传播的参数,这样也才能正常返回一个计算结果.
因此,为了代码美观,我们才有了上述的另一种写法,即把激活函数也定义在构造函数中,将其视为类的一个属性,这样让代码更可观了
class Net(nn.Module):
def __init__(self):
.......
self.flatten=nn.Flatten() #先定义为属性
........
self.init()
def forward(self,X):
X=self.flatten(X) #然后视觉效果就和其他层一致了
........
return X
...................
net=Net()