万字长文!手把手教你搭建自己的神经网络

文摘   2024-09-16 07:03   山东  

世界除了线性,还有非线性。

图片来源:zcool.com.cn

何谓神经网络

简而言之一句话:模拟人脑学习非线性关系的计算模型。学习非线性关系意味着可以学习的东西更多,更复杂,更深入。它的构成可以简化为:输入->处理->输出。这样的三步骤,其中的处理步骤就是可以大书特书的部分,由最原始的感知机,到卷积神经网络,循环神经网络等等。这不是本文最主要的内容,所以简要一提。

pytorch--搭建神经网络的利器

pytorch最大的好处就是灵活,你可以任意去构建属于自己的神经网络,但灵活就意味着很多内容需要你自己去编写,但是无所谓,它已经足够好了。

下载pytorch

不知道你的电脑有没有GPU,没有的话你直接使用这句话去在自己的anaconda环境里去下载吧。

pip3 install torch torchvision torchaudio

输入在这个地方,按回车键就行有GPU的大哥们,就去官网上看一看,下载对应的版本。

找到自己对应的cuda版本,复制run this command里的话,放到上图的anaconda环境里下载就行了。

pytorch的基础用法

1.张量操作

pytorch的基础用法和numpy特别像,熟悉numpy也可以在pytorch里照猫画虎,只不过人家用的东西叫张量,何谓张量,在pytorch中用tensor表示。一维张量,叫向量,二维张量叫矩阵,更高维的,不知道,没明确的定义。黑白的图片通常二维张量即可表示,但是彩色图片,多了颜色这个维度,那么就成了三维张量。哦对了,0维的叫标量。

张量的数据类型和numpy.array基本一一对应,但是不支持str类型。 包括:

  • torch.float64(torch.double),
  • torch.float32(torch.float),
  • torch.float16,
  • torch.int64(torch.long),
  • torch.int32(torch.int),
  • torch.int16,
  • torch.int8,
  • torch.uint8,
  • torch.bool

1.1 构建张量

#向量示例
vector = torch.tensor([1.0,2.0,3.0,4.0]) #向量,1维张量
print(vector)
print(vector.dim())#使用dim查看维度

#输出
tensor([1., 2., 3., 4.])
1
#矩阵示例
matrix = torch.tensor([[1.0,2.0],[3.0,4.0]]) #矩阵, 2维张量
print(matrix)
print(matrix.dim())

#输出
tensor([[1., 2.],
        [3., 4.]])
2
#三维张量示例
tensor3 = torch.tensor([[[1.0,2.0],[3.0,4.0]],[[5.0,6.0],[7.0,8.0]]])  # 3维张量
print(tensor3)
print(tensor3.dim())

#输出
tensor([[[1., 2.],
         [3., 4.]],

        [[5., 6.],
         [7., 8.]]])
3

规律这不就是来了,有几个括号就是几维张量(数一边就行了啊)

1.2 张量尺寸

  • 查看张量在每个维度上的长度,有两种方法size()和shape(注意括号的有无)
vector=torch.tensor([1.0,2.0,3.0])#一维张量
vector.shape,vector.size()
#输出
(torch.Size([3]), torch.Size([3]))
#可以看到是长度为3的一维张量

matrix = torch.tensor([[1.0,2.0],[3.0,4.0]]) #矩阵, 2维张量
matrix.shape,matrix.size()
#输出
(torch.Size([22]), torch.Size([22]))
#可以看到是2行,2列,元素个数为2*2的二维张量
  • 改变张量形状的方法有两个,一个是view(行数,列数),另一个是reshape(行数,列数)。如果存在转置操作,会使得数据结构扭曲,使用view会报错,这时候可以使用reshape强制转化
matrix = torch.tensor([[1.0,2.0],[3.0,4.0]]) #矩阵, 2维张量
matrix.view(4,1),matrix.reshape(4,1)
#输出
(tensor([[1.],
         [2.],
         [3.],
         [4.]]),
 tensor([[1.],
         [2.],
         [3.],
         [4.]]))

matrix = torch.tensor([[1.0,2.0],[3.0,4.0]]) #矩阵, 2维张量
matrix.view(4,-1)#使用-1表示该位置的长度由程序自己判断,你可别俩都输入-1
#输出
tensor([[1.],
        [2.],
        [3.],
        [4.]])

1.3 张量和numpy

可以用numpy方法从Tensor得到numpy数组,也可以用torch.from_numpy从numpy数组得到Tensor。这两种方法关联的Tensor和numpy数组是共享数据内存的。如果改变其中一个,另外一个的值也会发生改变。

#torch.from_numpy函数从numpy数组得到Tensor
array= np.ones(3)
tensor = torch.from_numpy(array)
#输出
tensor([1., 1., 1.], dtype=torch.float64)

#由tensor转换为numpy
tensor = torch.ones(3)
array = tensor.numpy()
print(type(array))
#输出
<class 'numpy.ndarray'>

item方法和tolist方法可以将张量转换成Python数值和数值列表
number 
= torch.tensor(1.0)
number = number.item()
print(type(number))
#输出
<class 'float'>

num_list 
= torch.rand(2,2)
num_list = tensor.tolist()
print(type(num_list))
#输出
<class 'list'>

1.4 张量的结构操作

张量的操作在很多地方和numpy非常的像

以代码为例

torch.arange(1,10,step = 2)#创建以1开始,9为结束,步长为2的一维张量
#输出
tensor([13579])

#构建110,个数为5的等差数列
torch.linspace(1,10,steps=5)
#输出
tensor([ 1.0000,  3.2500,  5.5000,  7.750010.0000])

#构建全为033列的二维张量
torch.zeros((3,3))
#输出
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

#构建全为133列的二维张量
torch.ones((3,3))
#输出
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])


#构建一个和a形状一样的全0二维张量
a=torch.ones((3,3))
b=torch.zeros_like(a)#如果要全为1的话使用ones_like
print(b)
#输出
tensor([[0.0.0.],
        [0.0.0.],
        [0.0.0.]])


#对每个元素都添加相同的数
#a是一个全为1的二维张量
a
=torch.ones((3,3))
b=torch.add(a,1)#每个元素都添加1
print(b)
#输出
tensor([[2., 2., 2.],
        [2., 2., 2.],
        [2., 2., 2.]])

#替换全部元素为某一个数,使用fill_
a=torch.ones((3,3))
b=torch.fill_(a,5)#将1全部替换为5
print(b)
#输出
tensor([[5., 5., 5.],
        [5., 5., 5.],
        [5., 5., 5.]])

#生成随机数种子,方便下次结果复现。
#如果不设置随机数种子,你每次随机取值都会变化
#设置之后,随机取值结果不再改变
torch.manual_seed(0)#设置随机数种子

#[01)的均匀分布的随机数
torch.rand(5)) # 返回一个张量,包含了从区间[01)的均匀分布中抽取的一组随机数,5代表返回张量的长度。
#输出
tensor([0.49630.76820.08850.13200.3074])

#正太分布随机数
#注意,下面这个是创建了一个均值0,标准差为10维张量
d=torch.normal(mean=torch.tensor(0.0),std=torch.tensor(1.0))
print(d)
d.size()
#输出
tensor(0.2490)
torch.Size([])

#创建服从正态分布的随机一维张量,长度为3
b = torch.normal(mean = torch.zeros(3), std = torch.ones(3))
print(b)
#输出
tensor([-0.3354,  0.4564, -0.6255])

#创建三维服从正态分布的随机张量
b = torch.normal(mean = torch.zeros(3,3), std = torch.ones(3,3))
print(b)
#输出
tensor([[ 0.4539, -1.3740,  2.8474],
        [-0.7322,  0.3833,  1.1000],
        [-0.0877,  0.4154,  0.6426]])


#创建一个09的整数随机排列一维张量
d = torch.randperm(10)
print(d)
#输出
tensor([9027364815])

#创建1个单位二维张量
m=torch.eye(2,2)
print(m)
#输出
tensor([[1., 0.],
        [0., 1.]])

#创建1个对角二维张量
m=torch.diag(torch.tensor([5,6,8]))#输入必须是tensor
print(m)
#输出
tensor([[500],
        [060],
        [008]])

对于张量的切片索引这一部分主要展示不规则索引,因为其他的索引方式几乎和numpy一模一样。这里借鉴算法美食屋的20天吃掉一个pytorch中的例子来做展示。对于不规则的切片提取,可以使用torch.index_select, torch.take, torch.gather, torch.masked_select.

生成一个类似于4个班,每班5人,每人7门课的一个三维张量。

minval=0
maxval=100
scores = torch.floor(minval + (maxval-minval)*torch.rand([4,5,7])).int()
print(scores)
#输出
tensor([[[79567325,  8,  799],
         [81156987999388],
         [38329178199474],
         [77186432894169],
         [58713374156116]],

        [[ 0,  989779690,  5],
         [154117841225,  1],
         [21919085889437],
         [72946699758132],
         [73553821211183]],

        [[85442188815326],
         [95701297873178],
         [21429252143336],
         [40549652195273],
         [74,  44112286814]],

        [[68925316326011],
         [74,  4,  1,  13983,  2],
         [91296452,  49176],
         [997516915273,  9],
         [35,  03060106576]]], dtype=torch.int32)

抽取每个班级第0个学生,第2个学生,第4个学生的全部成绩

torch.index_select(scores,dim = 1,index = torch.tensor([0,2,4]))
#输出
tensor([[[79567325,  8,  799],
         [38329178199474],
         [58713374156116]],

        [[ 0,  989779690,  5],
         [21919085889437],
         [73553821211183]],

        [[85442188815326],
         [21429252143336],
         [74,  44112286814]],

        [[68925316326011],
         [91296452,  49176],
         [35,  03060106576]]], dtype=torch.int32)

如果dim设置为2的话,意味着抽取,每个班,每个学生,第0门,第2和第4门课的成绩

tensor([[[7973,  8],
         [816999],
         [389119],
         [776489],
         [583315]],

        [[ 08996],
         [151712],
         [219088],
         [726675],
         [733821]],

        [[852181],
         [951287],
         [219214],
         [409619],
         [744128]],

        [[685332],
         [74,  139],
         [9164,  4],
         [991652],
         [353010]]], dtype=torch.int32)

如果dim设置为0,tensor设置为[0,2],则表示取出第0个班和第二个班的所有人的所有成绩

torch.index_select(scores,dim =0,index = torch.tensor([0,2]))
#输出
tensor([[[79567325,  8,  799],
         [81156987999388],
         [38329178199474],
         [77186432894169],
         [58713374156116]],

        [[85442188815326],
         [95701297873178],
         [21429252143336],
         [40549652195273],
         [74,  44112286814]]], dtype=torch.int32)

抽取第0个班级第0个学生的第0门课程,第2个班级的第3个学生的第1门课程,第3个班级的第4个学生第6门课程成绩

#take将输入看成一维数组,输出和index同形状
s = torch.take(scores,torch.tensor([0*5*7+0,2*5*7+3*7+1,3*5*7+4*7+6]))
print(s)
#输出
tensor([795476], dtype=torch.int32)

抽取分数大于等于80分的分数(布尔索引),结果是1维张量

g = torch.masked_select(scores,scores>=80)
print(g)
#输出
tensor([998187999388919489899690849190858894,
        949981838588819597879296928391919991],
       dtype=torch.int32)

赋值操作

#如果分数大于60分,赋值成1,否则赋值成0
ifpass = torch.where(scores>60,torch.tensor(1),torch.tensor(0))
print(ifpass)
#输出
tensor([[[1010001],
         [1011111],
         [0011011],
         [1010101],
         [0101010]],

        [[0011110],
         [0001000],
         [0111110],
         [1111110],
         [1000001]],

        [[1001100],
         [1101101],
         [0010000],
         [0010001],
         [1000010]],

        [[1100000],
         [1000010],
         [1010011],
         [1101010],
         [0000011]]])


#将每个班级第0个学生,第2个学生,第4个学生的全部成绩赋值成满分
torch.index_fill(scores,dim = 1,index = torch.tensor([0,2,4]),value = 100)
#等价于 scores.index_fill(dim = 1,index = torch.tensor([0,2,4]),value = 100)
#输出
tensor([[[100100100100100100100],
         [ 81,  15,  69,  87,  99,  93,  88],
         [100100100100100100100],
         [ 77,  18,  64,  32,  89,  41,  69],
         [100100100100100100100]],

        [[100100100100100100100],
         [ 15,  41,  17,  84,  12,  25,   1],
         [100100100100100100100],
         [ 72,  94,  66,  99,  75,  81,  32],
         [100100100100100100100]],

        [[100100100100100100100],
         [ 95,  70,  12,  97,  87,  31,  78],
         [100100100100100100100],
         [ 40,  54,  96,  52,  19,  52,  73],
         [100100100100100100100]],

        [[100100100100100100100],
         [ 74,   4,   1,   1,  39,  83,   2],
         [100100100100100100100],
         [ 99,  75,  16,  91,  52,  73,   9],
         [100100100100100100100]]], dtype=torch.int32)
         
#将分数小于60分的分数赋值成60
b = torch.masked_fill(scores,scores<60,60)
#等价于b = scores.masked_fill(scores<60,60)
#输出
tensor([[[79607360606099],
         [81606987999388],
         [60609178609474],
         [77606460896069],
         [60716074606160]],

        [[60608977969060],
         [60606084606060],
         [60919085889460],
         [72946699758160],
         [73606060606083]],

        [[85606088816060],
         [95706097876078],
         [60609260606060],
         [60609660606073],
         [74606060606860]],

        [[68926060606060],
         [74606060608360],
         [91606460609176],
         [99756091607360],
         [60606060606576]]], dtype=torch.int32)

增维和降维

torch.squeeze 可以减少维度。

torch.unsqueeze 可以增加维度。

#由二维降维至一维
a = torch.tensor([[1.0,2.0]])
s = torch.squeeze(a)
print(a)
print(s)
#输出
tensor([[1., 2.]])
tensor([1., 2.])

#增维
#二维升至三维
a = torch.tensor([[1.0,2.0]])
s = torch.unsqueeze(a,dim=0)#dim赋值01效果相同
print(a)
print(s)
#输出
tensor([[1., 2.]])
tensor([[[1., 2.]]])

合并分割

合并方法:

cat:不会增加维度,只起到连接作用

stack:堆叠,会增加维度

分割方法:

split:cat的逆运算,可以指定每份分割的数量

tensor1 = torch.tensor([[1.0,2.0],[3.0,4.0]])
tensor2 = torch.tensor([[5.0,6.0],[7.0,8.0]])
tensor3 = torch.tensor([[9.0,10.0],[11.0,12.0]])

cat = torch.cat([tensor1,tensor2,tensor3],dim = 0)
#输出
tensor([[ 1.,  2.],
        [ 3.,  4.],
        [ 5.,  6.],
        [ 7.,  8.],
        [ 9., 10.],
        [11., 12.]])
tensor1 = torch.tensor([[1.0,2.0],[3.0,4.0]])
tensor2 = torch.tensor([[5.0,6.0],[7.0,8.0]])
tensor3 = torch.tensor([[9.0,10.0],[11.0,12.0]])

stack= torch.stack([tensor1,tensor2,tensor3],dim = 0)
#输出
tensor([[[ 1.,  2.],
         [ 3.,  4.]],

        [[ 5.,  6.],
         [ 7.,  8.]],

        [[ 9., 10.],
         [11., 12.]]])
tensor1 = torch.tensor([[1.0,2.0],[3.0,4.0]])
tensor2 = torch.tensor([[5.0,6.0],[7.0,8.0]])
tensor3 = torch.tensor([[9.0,10.0],[11.0,12.0]])

cat= torch.cat([tensor1,tensor2,tensor3],dim = 0)
cat_split=torch.split(cat,split_size_or_sections =[3,2,1],dim = 0)#cat的逆运算,第一份3个,第二份2个,第三份1
#输出
(tensor([[1., 2.],
         [3., 4.],
         [5., 6.]]),
 tensor([[ 7.,  8.],
         [ 9., 10.]]),
 tensor([[11., 12.]]))

1.5 张量的数学运算

这一部分通过代码来展示

#基本操作
a = torch.tensor([[1.0,2],[-3,4.0]])
b = torch.tensor([[5.0,6],[7.0,8.0]])
a+b
a-b
a*b
a**2 #a的2次方
a/b
a%3 #求模
#输出
tensor([[ 6.,  8.],
        [ 4., 12.]])
tensor([[ -4.,  -4.],
        [-10.,  -4.]])
tensor([[  5.,  12.],
        [-21.,  32.]])
tensor([[ 1.,  4.],
        [ 9., 16.]])
tensor([[ 0.2000,  0.3333],
        [-0.4286,  0.5000]])
tensor([[1., 2.],
        [-0., 1.]])
        
#
print(torch.ge(a,2))#判断是不是大于等于2
print(torch.eq(a,2))#判断是不是等于2
print(torch.sqrt(a))#对a开平方
print(torch.min(a,b))#取出a,b在同维度上的最小值
print(torch.max(a,b))#取出a,b在同维度上的最大值
print(torch.div(a,b))#做除法

#
x = torch.tensor([2.6,-2.7])

print(torch.round(x)) #保留整数部分,四舍五入
print(torch.floor(x)) #保留整数部分,向下归整
print(torch.ceil(x))  #保留整数部分,向上归整
print(torch.trunc(x)) #保留整数部分,向0归整
print(torch.fmod(x,2)) #作除法取余数 
print(torch.remainder(x,2)) #作除法取剩余的部分,结果恒正
#输出
tensor([ 3., -3.])
tensor([ 2., -3.])
tensor([ 3., -2.])
tensor([ 2., -2.])
tensor([ 0.6000, -0.7000])
tensor([0.60001.3000])

# 幅值裁剪
x = torch.tensor([0.9,-0.8,100.0,-20.0,0.7])
y = torch.clamp(x,min=-1,max = 1)
z = torch.clamp(x,max = 1)
print(y)
print(z)
#输出
tensor([ 0.9000, -0.8000,  1.0000, -1.0000,  0.7000])
tensor([  0.9000,  -0.8000,   1.0000, -20.0000,   0.7000])

矩阵运算

!!!操作的张量至少是二维张量

tensor1 = torch.tensor([[1.0,2],[-3,4.0]])
tensor2 = torch.tensor([[5.0,6],[7.0,8.0]])
print(tensor1@tensor2)#矩阵乘法
#输出
tensor([[19., 22.],
        [43., 50.]])
        

#矩阵转置
print(tensor1.t())
#输出
tensor([[ 1., -3.],
        [ 2.,  4.]])

#求逆矩阵
print(torch.inverse(tensor1))
#输出
tensor([[ 0.4000, -0.2000],
        [ 0.3000,  0.1000]])

#求矩阵trace
print(torch.trace(tensor1))
#输出
tensor(5.)

#求矩阵的范数
print(torch.norm(tensor1))
#输出
tensor(5.4772)

#求矩阵的行列式
print(torch.det(tensor2))
#输出
tensor(-2.0000)

#求矩阵的特征值和特征向量
print(torch.linalg.eig(tensor2))
#输出
torch.return_types.linalg_eig(
eigenvalues=tensor([-0.1521+0.j, 13.1521+0.j])
,
eigenvectors
=tensor([[-0.7587+0.j, -0.5928+0.j],
        [ 0.6515+0.j, -0.8054+0.j]]))

2.nn.Module介绍

Pytorch和神经网络相关的功能组件大多都封装在 torch.nn模块下。 这个模块下面有搭建神经网络,需要的很多东西。如:

1.模型层

基础层

  • nn.Linear:全连接层。参数个数 = 输入层特征数× 输出层特征数(weight)+ 输出层特征数(bias)
  • nn.Flatten:压平层,用于将多维张量样本压成一维张量样本。
  • nn.BatchNorm1d:一维批标准化层。通过线性变换将输入批次缩放平移到稳定的均值和标准差。可以增强模型对输入不同分布的适应性,加快模型训练速度,有轻微正则化效果。一般在激活函数之前使用。可以用afine参数设置该层是否含有可以训练的参数。
  • nn.BatchNorm2d:二维批标准化层。
  • nn.BatchNorm3d:三维批标准化层。
  • nn.Dropout:一维随机丢弃层。一种正则化手段。
  • nn.Dropout2d:二维随机丢弃层。
  • nn.Dropout3d:三维随机丢弃层。

卷积网络相关层

  • nn.Conv1d:普通一维卷积,常用于文本。参数个数 = 输入通道数×卷积核尺寸(如3)×卷积核个数 + 卷积核尺寸(如3)

  • nn.Conv2d:普通二维卷积,常用于图像。参数个数 = 输入通道数×卷积核尺寸(如3乘3)×卷积核个数 + 卷积核尺寸(如3乘3) 通过调整dilation参数大于1,可以变成空洞卷积,增大卷积核感受野。 通过调整groups参数不为1,可以变成分组卷积。分组卷积中不同分组使用相同的卷积核,显著减少参数数量。 当groups参数等于通道数时,相当于tensorflow中的二维深度卷积层tf.keras.layers.DepthwiseConv2D。 利用分组卷积和1乘1卷积的组合操作,可以构造相当于Keras中的二维深度可分离卷积层tf.keras.layers.SeparableConv2D。

  • nn.Conv3d:普通三维卷积,常用于视频。参数个数 = 输入通道数×卷积核尺寸(如3乘3乘3)×卷积核个数 + 卷积核尺寸(如3乘3乘3) 。

  • nn.MaxPool1d: 一维最大池化。

  • nn.MaxPool2d:二维最大池化。一种下采样方式。没有需要训练的参数。

  • nn.MaxPool3d:三维最大池化

循环网络相关层

  • nn.Embedding:嵌入层。一种比Onehot更加有效的对离散特征进行编码的方法。一般用于将输入中的单词映射为稠密向量。嵌入层的参数需要学习。

  • nn.LSTM:长短记忆循环网络层【支持多层】。最普遍使用的循环网络层。具有携带轨道,遗忘门,更新门,输出门。可以较为有效地缓解梯度消失问题,从而能够适用长期依赖问题。设置bidirectional = True时可以得到双向LSTM。需要注意的时,默认的输入和输出形状是(seq,batch,feature), 如果需要将batch维度放在第0维,则要设置batch_first参数设置为True。

  • nn.GRU:门控循环网络层【支持多层】。LSTM的低配版,不具有携带轨道,参数数量少于LSTM,训练速度更快。

Transformer相关层

  • nn.Transformer:Transformer网络结构。Transformer网络结构是替代循环网络的一种结构,解决了循环网络难以并行,难以捕捉长期依赖的缺陷。它是目前NLP任务的主流模型的主要构成部分。Transformer网络结构由TransformerEncoder编码器和TransformerDecoder解码器组成。编码器和解码器的核心是MultiheadAttention多头注意力层。
  • nn.TransformerEncoder:Transformer编码器结构。由多个 nn.TransformerEncoderLayer编码器层组成。
  • nn.TransformerDecoder:Transformer解码器结构。由多个 nn.TransformerDecoderLayer解码器层组成。
  • nn.TransformerEncoderLayer:Transformer的编码器层。
  • nn.TransformerDecoderLayer:Transformer的解码器层。
  • nn.MultiheadAttention:多头注意力层。

2.激活函数

  • Sigmoid又叫作 Logistic 激活函数,它将实数值压缩进 0 到 1 的区间内,还可以在预测概率的输出层中使用。
  • Tanh 激活函数又叫作双曲正切激活函数。与 Sigmoid 函数类似,Tanh 函数也使用真值,但 Tanh 函数将其压缩至-1 到 1 的区间内。与 Sigmoid 不同,Tanh 函数的输出以零为中心,因为区间在-1 到 1 之间。你可以将 Tanh 函数想象成两个 Sigmoid 函数放在一起。在实践中,Tanh 函数的使用优先性高于 Sigmoid 函数。
  • ReLU激活函数使网络更快速地收敛。它不会饱和,即它可以对抗梯度消失问题,至少在正区域(x> 0 时)可以这样,因此神经元至少在一半区域中不会把所有零进行反向传播。由于使用了简单的阈值化(thresholding),ReLU 计算效率很高。

3.损失函数

  • 均方误差损失 MSELoss。计算 output 和 target 之差的均方差。
torch.nn.MSELoss(reduction='mean')
#参数
reduction-三个值,none: 不使用约简;mean:返回loss和的平均值; sum:返回loss的和。默认:mean。
  • 交叉熵损失 CrossEntropyLoss.当训练有 C 个类别的分类问题时很有效. 可选参数 weight 必须是一个1维 Tensor, 权重将被分配给各个类别. 对于不平衡的训练集非常有效。在多分类任务中,经常采用 softmax 激活函数+交叉熵损失函数,因为交叉熵描述了两个概率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要 softmax激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算 loss。
torch.nn.CrossEntropyLoss(weight=None, ignore_index=-100, reduction='mean')
#参数
weight (Tensor, optional) – 自定义的每个类别的权重. 必须是一个长度为 C 的 Tensor
ignore_index (int, optional) – 设置一个目标值, 该目标值会被忽略, 从而不会影响到 输入的梯度。
reduction-三个值,none: 不使用约简;mean:返回loss和的平均值; sum:返回loss的和。默认:mean。
  • 二进制交叉熵损失 BCELoss二分类任务时的交叉熵计算函数。用于测量重构的误差, 例如自动编码机. 注意目标的值 t[i] 的范围为0到1之间.
torch.nn.BCELoss(weight=None, reduction='mean')
#参数
weight (Tensor, optional) – 自定义的每个 batch 元素的 loss 的权重. 必须是一个长度为 “nbatch” 的 的 Tensor
pos_weight(Tensor, optional) – 自定义的每个正样本的 loss 的权重. 必须是一个长度 为 “classes” 的 Tensor
  • BCEWithLogitsLoss。BCEWithLogitsLoss损失函数把 Sigmoid 层集成到了 BCELoss 类中. 该版比用一个简单的 Sigmoid 层和 BCELoss 在数值上更稳定, 因为把这两个操作合并为一个层之后, 可以利用 log-sum-exp 的 技巧来实现数值稳定.
torch.nn.BCEWithLogitsLoss(weight=None, reduction='mean', pos_weight=None)
#参数
weight (Tensor, optional) – 自定义的每个 batch 元素的 loss 的权重. 必须是一个长度 为 “nbatch” 的 Tensor
pos_weight(Tensor, optional) – 自定义的每个正样本的 loss 的权重. 必须是一个长度 为 “classes” 的 Tensor

搭建属于自己的神经网络

首先,对于神经网络做一个剖析。神经网络的构造如下:

  • 输入层
  • 隐藏层(可多可少)
  • 输出层

就好比,一个人问你世界最高的山峰是哪个(输入层),你的大脑经过思考之后(隐藏层),告诉那个人是珠穆朗玛峰(输出层)。

激活函数的意义,就像神经元受到刺激,刺激达到一定程度之后,对刺激做出反应,而激活函数必须是非线性的。

损失函数就更好说,我们要衡量真实情况和预测情况之间的差距,差距越小则代表学习的越好。

到这一步,还不算真正意义上的神经网络,真正的神经网络必须自己去主动纠正偏差以达到最真实的情况。优化器横空出世,最流行的方法便是随机梯度下降。(曲面上方向导数的最大值的方向就代表了梯度的方向,因此我们在做梯度下降的时候,应该是沿着梯度的反方向进行权重的更新,可以大致找到全局的最优解)。这就让神经网络自己学会怎么改变权重和偏置的值。

改变的权重和偏置的值该怎么回到最初的位置,再来一次这样的计算呢,这就需要反向传播了。

这样便可以设置训练次数,来循环这个计算过程,来训练模型。

知道了神经网络是什么之后,那我便来搭建自己的神经网络吧。

1.导包

import torch
from torch import nn
import torch.nn.functional as F

2.搭建神经网络的方式有几种,这里我们采用class类的方式来搭建。(都是标准化的,跟搭积木一样)。全连接层是比较贵的,针对不同的任务可以使用不同的层,这在上文中都有提及。

class Net(nn.Module):
    def __init__(self):
        super().__init__()#继承nn.Module的功能
        #使用三个全连接层(最简单的模式)
        self.layer1 
= nn.Linear(2,4)
        self.layer2 = nn.Linear(4,8
        self.layer3 = nn.Linear(8,1)
        
    def forward(self,x):
        x 
= F.relu(self.layer1(x))#对于第一层的内容进行激活操作
        x = F.relu(self.layer2(x))#对于第一层激活到第二层的内容再激活
        y = self.layer3(x)#输出不需要激活!!!
        return y

这就结束了,一个简单的神经网络,就搭建好了,等等损失函数,优化器,反向传播呢??先别着急嘛。我们先打印看看,自己的神经网络是什么样子。

net=Net()#对于类进行实例化操作,必要步骤!
print(net)
#输出
Net(
  (layer1): Linear(in_features=2, out_features=4, bias=True)
  (layer2): Linear(in_features=4, out_features=8, bias=True)
  (layer3): Linear(in_features=8, out_features=1, bias=True)
)

由于pytorch并没有集成训练函数,不能像keras一样使用fit进行训练,但好在有一位大佬,写了一个python库--torchkeras,使我们可以像keras一样,优雅的使用fit去训练模型。介绍如下:

from torchkeras import KerasModel 
from torchkeras.metrics import Accuracy#验证精度,集成的方法比较多

#损失函数,不同的问题选用不同的损失函数
loss_fn = nn.BCEWithLogitsLoss()#损失函数

#优化器
#一般有两种优化器,Adam和SGD
#lr:学习率
#net.parameters():对网络的参数进行优化
optimizer= torch.optim.Adam(net.parameters(),lr = 0.01)   
#验证精度(字典形式)
metrics_dict = {"acc":Accuracy()}

接下来,将模型net和损失函数这些放进模型里,然后启用model.fit进行训练。

model = KerasModel(net,
                   loss_fn=loss_fn,
                   metrics_dict = metrics_dict,
                   optimizer = optimizer)
model.fit(
    train_data = train_data,#训练数据集
    val_data= val_data,#验证数据集
    epochs=10,#训练批次数
    monitor="val_acc", #可选val_loss和val_acc
    mode="max",#如果是val_acc,则选择max,如果是val_loss,则选择min.
    plot=True,
    cpu=True#可以不设置,如果有gpu,会默认使用gpu。
)

这里以一个很经典的例子,给大家,展示从数据处理,到模型搭建,再到训练,验证的全流程。(其实最麻烦的就是数据处理)

判断泰坦尼克号上的人的生存与否。

https://tianchi.aliyun.com/dataset/111507 这是数据集的网址链接。

OK,那开始吧。代码如下:

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
from torch import nn
import torch
import torchkeras 
from torchkeras.metrics import Accuracy
from torch.utils.data import Dataset,DataLoader,TensorDataset

#读取数据
train = pd.read_csv("train.csv")
test= pd.read_csv("test.csv")

我们可以看到,test数据里居然没有生存情况,也就是survived情况。 无奈,只有把训练集划分出一部分做验证集

#从sklearn里导出划分用的工具
from sklearn.model_selection import train_test_split

#训练集大小设置为70%,也可以为80%
df_train,df_test=train_test_split(train,train_size=0.7)

survived就只有0和1,Pclass和Embarked有三种情况

这不好,我们只希望用0和1表示,这就需要用到独热编码了。效果如下,False就是0,True就是1。对于Pclass,Embarked和sex都进行这样的处理。对于其余存在null的,进行如下处理

def pre_data(data):
    """
    定义一个处理数据的函数
    """
    #建立一个空dataframe
    df
=pd.DataFrame()
    
    #对sex,Embarked和pclass进行onehot处理
    pclass=pd.get_dummies(data['Pclass'])
    pclass.columns = ['Pclass_' +str(x) for x in pclass.columns ]#更换列名
    
    sex = pd.get_dummies(data['Sex'])
    
    Embarked = pd.get_dummies(data['Embarked'],dummy_na=True)#对于null也进行onehot处理
    Embarked.columns = ['Embarked_' + str(x) for x in Embarked.columns]
    
    df=pd.concat([pclass,sex,Embarked],axis=1)
    
    df['Age'] = data['Age'].fillna(0)
    df['Age_null'] = pd.isna(data['Age']).astype('int32')

    
    df['SibSp'] = data['SibSp']
    df['Parch'] = data['Parch']
    df['Fare'] = data['Fare']

    df['Cabin_null'] =  pd.isna(data['Cabin']).astype('int32')
    
    return df

x_train = pre_data(df_train).values
y_train=df_train[['Survived']].values

x_test = pre_data(df_test).values
y_test = df_test[['Survived']].values

ok,来看看数据的形状吧

数据处理还没完哦,这并不是pytorch能接受的形式,我们需要经过以下处理。

x_train = x_train.astype(float)
y_train=y_train.astype(float)
x_test = x_test.astype(float)
y_test=y_test.astype(float)

#dataloader,数据迭代器一样
train_data = DataLoader(TensorDataset(torch.tensor(x_train).float(),torch.tensor(y_train).float()),
                     shuffle = True, batch_size = 8)
#shuffle:随机取,batch_size:一次取多少
val_data = DataLoader(TensorDataset(torch.tensor(x_test).float(),torch.tensor(y_test).float()),
                     shuffle = False, batch_size = 8)

看看最终处理好的数据吧

for features,labels in train_data:
    print(features,labels)
    break

接下来,来训练模型吧,这里要做一点点改动,数据有15列,所以将模型改成如下,一般都是先升再降15->20->15->1.

class Net(nn.Module):
    def __init__(self):
        super().__init__()#继承nn.Module的功能
        #使用三个全连接层(最简单的模式)
        self.layer1 
= nn.Linear(15,20)
        self.layer2 = nn.Linear(20,15
        self.layer3 = nn.Linear(15,1)
        
    def forward(self,x):
        x 
= F.relu(self.layer1(x))#对于第一层的内容进行激活操作
        x = F.relu(self.layer2(x))#对于第一层激活到第二层的内容再激活
        y = self.layer3(x)#输出不需要激活!!!
        return y
net=Net()
loss_fn = nn.BCEWithLogitsLoss()#损失函数

optimizer= torch.optim.Adam(net.parameters(),lr = 0.01)   
metrics_dict = {"acc":Accuracy()}

model = KerasModel(net,
                   loss_fn=loss_fn,
                   metrics_dict = metrics_dict,
                   optimizer = optimizer)
model.fit(
    train_data = train_data,#训练数据集
    val_data= val_data,#验证数据集
    epochs=20,#训练批次数
    monitor="val_acc", #可选val_loss和val_acc
    mode="max",#如果是val_acc,则选择max,如果是val_loss,则选择min.
    plot=True,
    cpu=True#可以不设置,如果有gpu,会默认使用gpu。
)

训练结果如下:训练的过程是动态可视化的,效果一般般,但并不妨碍这是一个全流程的框架。

好啦,以上就是今天的全部内容,写了一天了,可累死我了


参考文章及代码:

  • https://pytorch.org/
  • https://gitee.com/iiiiiisu/eat_pytorch_in_20_days
  • https://blog.csdn.net/shanglianlm/article/details/85019768
  • https://www.jiqizhixin.com/articles/2017-11-02-26
  • https://blog.csdn.net/q923714892/article/details/118411839


人工智能科学与技术
分享教学成果 | 传播前沿科技| 推荐优秀图书
 最新文章