《PyTorch深度学习实践》

【《PyTorch深度学习实践》完结合集】 https://www.bilibili.com/video/BV1Y7411d7Ys/?share_source=copy_web&vd_source=684aec3a42a2952834511895317ee34b

链接:https://pan.baidu.com/s/1vZ27gKp8Pl-qICn_p2PaSw

提取码:cxe4

一、(学习开发深度学习网络的技能)overview

1、How to develop learning system?

基于规则

SVM受到的挑战

Brief history of neural Network

反向传播

模型的改进

关键是学会构建模型的套路,然后去构造自己的模型。学会基本块儿的实现,然后把它们组装起来。

二、线性模型

模型设计

找一个最好的w使得平均损失最小

穷举法

代码

import numpy as np
import matplotlib.pyplot as plt
from flatbuffers.flexbuffers import F

x_data=[1.0,2.0,3.0]
y_data=[2.0,4.0,6.0]

def forward(x):
    return  x*w
# 损失
def loss(x,y):
    y_pred=forward(x)
    return (y_pred-y)*(y_pred-y)

w_list=[]
mse_list=[]
# 梯度是随机生成的
for w in np.arange(0.0,4.1,0.1):
    print('w=',w)
    l_sum=0
    for x_val,y_val in zip(x_data,y_data):
        y_pred_val=forward(x_val)
        loss_val=loss(x_val,y_val)
        l_sum+=loss_val
        print('\t',x_val,y_val,round(y_pred_val,2),round(loss_val,2))
    print('MSE=',l_sum/3)
    w_list.append(w)
    mse_list.append(l_sum/3)

#画图
plt.plot(w_list,mse_list)
plt.ylabel('Loss')
plt.xlabel('w')
plt.show()

练习

待更新3D画图

三、梯度下降算法

前面我们学习了代价函数(损失函数),代价函数它可以反映我们的模型的好坏,我们可以通过代价函数让我们的模型更好。代价函数的作用就是衡量模型的预测值与真实值之间的差异。

学习了代价函数,下一步那我们就要了解如何去获取代价函数的较小值,当然我们可以手动获取等高线的图,然后自己一个一个的去试,最后找到合适的参数w,b的值,但是这不是我们想要的,我们需要一个有效的算法,它可以自己去查找合适的参数w,b的值,给我们最合适的模型,使得代价函数最小化。这个有效的算法就是梯度下降 gradient descent 。梯度下降是机器学习最重要的算法之一。梯度下降的变化不仅用于训练线性回归,像在一些先进的神经网络模型中等在很多方面都有应用。

梯度下降只能找到局部最优,无法找到全局最优

梯度等于0的时候,无法继续更新参数,无法继续迭代,这是重点要解决的问题

用最后的到的式子对整个训练过程进行更新

具体代码实现

代码段

import matplotlib.pyplot as plt

x_data=[1.0,2.0,3.0]
y_data=[2.0,4.0,6.0]

w=1.0

def forward(x):
    return  x*w
# 平均损失MSE
def cost(xs,ys):
    cost=0
    for x,y in zip(xs,ys):
        y_pred=forward(x)
        cost+=(y_pred-y)**2
    return cost/len(xs)
# 梯度下降算法
def gradient(xs,ys):
    grad=0
    for x,y in zip(xs,ys):
        grad+=2*x*(x*w-y)
    return grad/len(xs)
print('Predict (before training)',4,forward(4))
loss_list=[]
epoch_list=[]
for epoch in range(100):
    cost_val=cost(x_data,y_data)
    grad_val=gradient(x_data,y_data)
    w-=0.01*grad_val
    print("Epoch:",epoch,"w=",round(w,2),"loss=",round(cost_val,2))
    loss_list.append(cost_val)
    epoch_list.append(epoch)
print('Predict (after training)',4,forward(4))
#画图
plt.plot(epoch_list,loss_list)
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.show()

四、反向传播

这是一个非常繁重的任务,写解析式会相当复杂。面对这样复杂的网络时,我们能不能做一种这样的算法:把我们的网络看成是一个图,在图上传播梯度,最后跟据链式法则在图上把梯度求出来,这种算法就叫做反向传播

不管进行多少层线性的变换,最后都会统一成WX+b,为了解决这个问题,增加模型的复杂度,我们对每一层最终的输出加一个非线性变换函数,

链式法则

Chain Rule - 1.Create Computational Graph (Forward)

沿着边的方向去计算最终的Loss

Chain Rule - 2.Local Gradient

Chain Rule - 3. Given gradient from successive node

Chain Rule - 4.Use chain rule to compute the gradient(backward)

举例

具体过程(前馈——反向)

得到权重的梯度,然后运用梯度下降,更新权重参数

Tensor in Pytorch

x需要自动类型转化为Tensor(张量):是一个数据容器,矩阵就是一个二维张量。张量是矩阵向任意维度的推广,张量的维度(dimension)通常叫做轴(axis)

调用一次loss函数就把计算图动态的构建出来了,该代码是在构建一个计算图,不是在进行运算

训练过程

  • 前馈过程Forward:只需计算loss。训练100轮,然后用随机梯度下降,每一次都把这个x_data的y_data,zip成一个样本,去计算这个loss。

  • Backward:loss是最后算出来的张量,可以调用它的成员函数backward,然后它就会自动的把这条计算链路上(刚才画的计算图)。所有需要梯度地方把梯度都求出来,然后把梯度都存到变量里面,这里面涉及到这个变量就是之前创建这个w,存完之后,计算图就会被释放。所以下一次在进行loss计算的时候,它会创建一个新的计算图,因为有时候我们构建的神经网络,每一次运行的时候,计算图可能 是不一样的,所以每次进行反向传播,都会释放旧的,这是一个非常灵活的方式,也是Pytorch的核心竞争力。

  • 权重更新:取所以张量的data进行计算是不会建立计算图的

  • 把权重里的梯度数据全都清零

五、Pytorch实现线性回归(初步)

之前介绍了如何实现简单的线性模型,以及线性模型如何训练,如何更新权重。

接下来,使用pytorch提供的工具更方便的重现线性模型实现的过程,例如module :如何构造我们的神经网络、Loss、Sgd如何构造随机梯度下降的优化器

  • 确定模型(线性模型)

  • 定义损失函数:是一个标量值,这样我们才能找使其更小的值

训练过程

不同的优化器:

准备数据

重点在于构造计算图,在此之后梯度会自动地帮我们求出来。知道x和yhat的维度就可以确定权重和偏置张量的维度。计算出loss(必须是一个标量不能是向量)之后通过调用backward函数计算梯度。

设计模型

掌握该模型,后续通过扩展模型使其适用于各种各样的任务。

  • 所有的模型都要继承自Module,因为Module这个父类里有很多方法,是将来模型训练过程需要用到的。

  • 该类里至少需要实现两个函数

  1. 构造函数__init__:初始化对象是默认要调用的函数

调用父类的构造

构造对象self.linear:Liner是继承自Module的类,它可以自动的进行反向传播

  1. forward函数:执行前馈过程所要进行的计算

将来需要用的时候可以直接调用

构造损失函数和优化器

  • 损失函数

  • 优化器

训练过程

测试模型

增加训练次数 ...注意避免过拟合(训练集损失可以降到最小,但是测试集降到最小之后又上升)

代码实现


# 用PyTorch实现线性回归
import torch
# 1、准备数据
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])

# 2、设计模型
class LinearModel(torch.nn.Module):
    def __init__(self):
        super(LinearModel, self).__init__()
        self.linear = torch.nn.Linear(1, 1)

    def forward(self, x):
        y_pred = self.linear(x)
        return y_pred
model = LinearModel()

# 3、构造损失函数和优化器
criterion=torch.nn.MSELoss(size_average=False)
optimizer=torch.optim.SGD(model.parameters(),lr=0.01)


# 4、训练模型
for epoch in range(100):
    y_pred=model(x_data)
    loss=criterion(y_pred,y_data)
    print(epoch,loss.item())

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
print('w=',model.linear.weight.item())
print('b=',model.linear.bias.item())

# 测试模型
x_test=torch.Tensor([[4.0]])
y_test=model(x_test)
print('y_pred',y_test.data)

运行结果

手写代码详细注释

更多Pytorch学习

六、逻辑斯蒂回归(初步)

计算属于每一个值的概率,概率最大的就是我们最终的预测结果。

回归VS分类

Logistic回归

想要得到概率,必须保证输出值在0-1之间。

Sigmoid 函数

函数值都是-1——1之间,都是单调的增函数

Logistic回归模型

Loss function for Binary Classification

KL散度,交叉熵计算分布之间的差异

Mini-Batch Loss function for Binary Classification

Implementation of Logistic Regression

Logistic回归跟线性回归的不同就在于在线性的基础上加了一个F.sigmoid,把输出结果映射到0-1之间,输出一个概率。

代码实现


# Logiest回归的应用
import torch
import torch.nn.functional as F
# 1、准备数据
# 是一个二分类的预测
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[0], [0], [1]])

# 2、设计模型
class LogisticRegressionModel(torch.nn.Module):
    def __init__(self):
        super(LogisticRegressionModel, self).__init__()
        self.linear = torch.nn.Linear(1, 1)

    def forward(self, x):
        #
        y_pred = F.sigmoid(self.linear(x))
        return y_pred
model = LogisticRegressionModel()

# 3、构造损失函数和优化器
criterion=torch.nn.BCELoss(size_average=False)
optimizer=torch.optim.SGD(model.parameters(),lr=0.01)

# 4、训练模型
for epoch in range(100):
    y_pred=model(x_data)
    loss=criterion(y_pred,y_data)
    print(epoch,loss.item())

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# 测试模型
x_test=torch.Tensor([[4.0]])
y_test=model(x_test)
print('y_pred',y_test.data)
# 最终输出的是概率,大于0.5就代表1,反之代表0

七、处理多维特征的输入(初步)

了解什么是多维特征

每一行是一个样本,每一列叫做特征(输入特征有8列就是8维),数据库中每一行叫做一条记录,每一列叫做一个字段。

多特征的分类预测和之前的回归预测不一样,不再是(wx*b),而是一个样本中的每个特征都要和权重相乘

Mini—batch

什么是mini-batch?

https://blog.csdn.net/qq_38343151/article/details/102886304?ops_request_misc=&request_id=&biz_id=102&utm_term=mini-batch&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-102886304.142^v73^insert_down4,201^v4^add_ask,239^v2^insert_chatgpt&spm=1018.2226.3001.4187

我们已知在梯度下降中需要对所有样本进行处理过后然后走一步,那么如果我们的样本规模的特别大的话效率就会比较低。假如有500万,甚至5000万个样本(在我们的业务场景中,一般有几千万行,有些大数据有10亿行)的话走一轮迭代就会非常的耗时。这个时候的梯度下降叫做full batch。 所以为了提高效率,我们可以把样本分成等量的子集。 例如我们把100万样本分成1000份, 每份1000个样本, 这些子集就称为mini batch。然后我们分别用一个for循环遍历这1000个子集。 针对每一个子集做一次梯度下降。 然后更新参数w和b的值。接着到下一个子集中继续进行梯度下降。 这样在遍历完所有的mini batch之后我们相当于在梯度下降中做了1000次迭代。 我们将遍历一次所有样本的行为叫做一个 epoch,也就是一个世代。 在mini batch下的梯度下降中做的事情其实跟full batch一样,只不过我们训练的数据不再是所有的样本,而是一个个的子集。 这样在mini batch我们在一个epoch中就能进行1000次的梯度下降,而在full batch中只有一次。 这样就大大的提高了我们算法的运行速度。

Pytorch提供的Sigmoid函数是按向量计算的

把z(1)-z(N)看成一个长度为N的向量,然后转换成向量化的矩阵运算,使得我们可以利用并行计算的能力来提高运算速度,所以在很多计算中需要变成向量化的计算。torch.nn.Linear(8,1)8表示输入维度,1表示输出维度

神经网络

把任意8维空间的向量映射到1维空间(线性映射)。但是我们通常做的可能是非常复杂的非线性的变换层,所以可以通过多个线性变换层,找到最优的权重把它们组合起来,来模拟非线性变换。神经网络本质是寻找一种非线性空间变换函数。

目标是找一个从8维到1维的非线性的空间变换,通过激活函数sigmoid给线性函数增加非线性因子,使得我们可以拟合相应的非线性变换,这是神经网络设置时所采取的方式。

矩阵是一个空间变换的函数,例如把x从N维空间映射到M维空间

Example: Artificial Neural Network

Example: Diabetes Prediction

准备数据

定义模型

构造损失函数和优化器

训练

尝试不同激活函数

心脏病预测(二分类)

案例介绍

心脏病数据集:如下图是一个有8个特征输入(x1-x8)的数据集,属于多维特征输入。我们所要做的是根据输入特征预测是否患有糖尿病,属于二分类问题(0:未患病,1:患病)。

和之前的回归预测不同,不再是(wx*b),而是一个样本中的每个特征都要和权重相乘。

代码实现

#处理多维特征的输入
# 输出的是一个分类:糖尿病数据集为例
import torch
import numpy as np

# 1、准备数据
# import pandas as pd
# xy=pd.read_csv('heart.csv')
# x_data=xy[xy.columns[0:-1]].values
# y_data=xy[xy.columns[-1]].values

# delimiter表示相应的分隔符,用numpy读取的时候要指定数据的类型
xy=np.loadtxt('heart.csv',delimiter=',',dtype=np.float32)
# x_data:前8列特征,y_data:最后一列标记
x_data=torch.from_numpy(xy[:,:-1])
y_data=torch.from_numpy(xy[:,[-1]])
# print(x_data)

# 2、定义模型
# 模型的作用就是根据输入来得到输出(预测),只要搞清楚每个模型的原理,就行构造就可以
class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        # 把任意8维空间的向量逐步映射到1维空间(线性映射)
        self.linear1=torch.nn.Linear(8,6)
        self.linear2=torch.nn.Linear(6,4)
        self.linear3=torch.nn.Linear(4,1)
        # 当一个神经元的激活函数是一个 Sigmoid函数时,这个单元的输出保证总是介于0和1之间
        self.sigmoid=torch.nn.Sigmoid()

    def forward(self,x):
        x=self.sigmoid(self.linear1(x))
        x=self.sigmoid(self.linear2(x))
        x=self.sigmoid(self.linear3(x))
        return x
model=Model()

# 3、构造损失函数和优化器
# 交叉熵损失
criterion=torch.nn.BCELoss(size_average=True)
# 随机梯度下降
optimizer=torch.optim.SGD(model.parameters(),lr=0.1)

# 4、训练
for epoch in range(100):
    # Forward
    y_pred=model(x_data)
    loss=criterion(y_pred,y_data)
    print(epoch,loss.item())

    # Backward
    # 梯度归0
    optimizer.zero_grad()
    # 反向传播
    loss.backward()
    # Update
    optimizer.step()

运行结果

八、加载数据集(初步)

  • Dataset:构造数据集,可以支持索引(通过下标操作可以把快速拿出数据集中的样本)

  • DataLoader:拿出Mini-batch,供我们训练时快速使用

Revision: Manual data feed

针对该糖尿病数据集,我们在训练的时候,里面这个model,每次做前馈的的时候,我们是把所有的数据都放进去了,在进行梯度下降的时候,有这样几种选择,一种就是全部的数据,另一种就是随机梯度下降(Stochastic gradient descent),随机梯度下降只用一个样本,如果只用这一个样本,我们将来会得到一个比较好的随机性,将来会帮助我们克服鞍点的问题。而batch的优点是可以最大化的利用向量计算来提升它的计算的速度。如果都用一个样本的这种随机梯度下降,那么我们将来训练出的模型性能可能会比其他的性能都更好,但是它会导致在优化的时候用时很长,因为每次一个样本,没法利用GPU和CPU的能力,所以训练的时间非常长。而对于batch来说,虽然计算速度非常快,但是在处理的性能上会遇到一些问题,所以在深度学习里面,用Mini-batch这样一种方法来均衡在性能上以及训练时间上的需求。

Terminology: Epoch, Batch-Size, Iterations

外层表示训练周期,内层对batch进行迭代

DataLoader: batch_size=2, shuffle=True

How to define your Dataset(代码实现)

Dataset是一个抽象类,是不能实例化的,需要其他的类去继承

DataLoader是可以实例化的

自定义类DiabetesDataset继承Dataset类

__getitem__魔法方法:支持下标操作

__len__魔法方法:可以返回数据集的数据条数

实例化自定义的类

Extra: num workers in Windows

在Linux(用fork创建一个新的进程)和Windows(用spawn代替fork)下多进程库的实现是不一样的。

解决办法:把用Loader进行迭代的代码封装起来

数据集的实现

xy是一个N行9列的数据,xy.shape=(N,9),xy.shape[0]=N,这样可以知道数据集用了多少个,所以在__len__函数中就直接返回self.len就可以。

__getitem__返回的是(x,y)的元组

train_loader是下图所示的样子303/32=10,

外层循环:把所有的数据都跑100遍,一整批数据都跑一次叫做一个epoch

内层:直接对train_loader进行迭代,enumerate是为了获得当前迭代次数。train_loader中拿出来的x和y元组就放到data中,所以在训练开始之前我们通过(inputs,labels=data)先把输入x和相应的标签y从data中拿出来。Dataset每次拿过来的都是x[i],y[i],每次拿到的都是一组样本,DataLoader根据Mini-batch的数量,把x和y变成矩阵,train_loader会自动把它们转化成Tensor。

整体代码

torchvision提供很多数据集

torchvision内置了很多数据集,这些数据集都可以使用。

心脏病预测代码改进


# 用Dataset和DataLoader心脏病案例代码的改变
import numpy as np
import torch
# • Dataset:构造数据集,可以支持索引(通过下标操作可以把快速拿出数据集中的样本)
# • DataLoader:拿出Mini-batch,供我们训练时快速使用
from torch.utils.data import Dataset,DataLoader

# 1、准备数据
# 定义了一个类
class DiabetesDataset(Dataset):
    def __init__(self,filepath):
        # delimiter表示相应的分隔符,用numpy读取的时候要指定数据的类型
        xy=np.loadtxt(filepath,delimiter=',',dtype=np.float32)
        # xy.shape是(303,9),303个样本,9个特征
        self.len=xy.shape[0]
        # 确定输入特征维度和输出特征的维度
        self.x_data=torch.from_numpy(xy[:,:-1])
        self.y_data=torch.from_numpy(xy[:,[-1]])
    # __getitem__魔法方法:支持下标操作
    def __getitem__(self, index):
        return self.x_data[index],self.y_data[index]
        print(self.x_data[index])
    # 返回数据样本个数
    def __len__(self):
       return self.len
# 创建dataset实例
dataset=DiabetesDataset('heart.csv')
# train_loader=DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=2)
train_loader=DataLoader(dataset=dataset,batch_size=32,shuffle=True)
# print(train_loader)
# 2、定义模型
# 这部分没有改变
class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.linear1=torch.nn.Linear(8,6)
        self.linear2=torch.nn.Linear(6,4)
        self.linear3=torch.nn.Linear(4,1)
        self.sigmoid=torch.nn.Sigmoid()

    def forward(self,x):
        x=self.sigmoid(self.linear1(x))
        x=self.sigmoid(self.linear2(x))
        x=self.sigmoid(self.linear3(x))
        return x
model=Model()

# 3、构造损失函数和优化器
# 这部分没有改变
criterion=torch.nn.BCELoss(size_average=True)
optimizer=torch.optim.SGD(model.parameters(),lr=0.1)

# 4、训练
# if __name__=='__main__':
for epoch in range(100):
    # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
    for i,data in enumerate(train_loader,0):
        # print(i) i是100个0-9
        # print(data) data是包含100组每组32个样本的x_data,y_data
        # prepare data
        inputs,labels=data
        # Forward
        # 每训练一个mini-batch
        y_pred=model(inputs)
        loss=criterion(y_pred,labels)
        # print(epoch,i,loss.item())

        # Backward
        optimizer.zero_grad()
        loss.backward()

        # Update
        optimizer.step()

九、多分类问题(初步)

二分类中yhat输出的是y=1时的概率值P(y-1)是多少,因为只有2个分类,所以只需要输出一个概率,另外一个概率用P(y=0)=1-P(y-1)

本节要点:

  1. 如何用Softmax分类器去解决多分类问题?

  1. 在PyTorch中怎么去实现?

神经网络设计

MNIST数据集为例:有10中不同的分类标签0-9

第一种:Design 10 outputs using Sigmoid?

输出属于每一个类别的概率:在输出的时候,我们把原来只有一个P(y-1)的一个输出变成10个输出,输出这个样本属于与每个分类的概率(把每一个类别看成二分类的问题)

第二种:Output a Distribution of prediction with Softmax

Softmax层

多分类问题需要用到softmax分类器

softmax函数:把线性层的输出都变成大于等于0,分类输出的值加起来等于1。假如有3个分类,经过线性层的空间变换,得到了3个输出值(0.2,0.1,-0.1),首先先进行指数运算算出3个值都是大于0 的(1.22,1.11,0.90),然后为了让这三个数加起来等于1,又要保证彼此之间平等的缩放,所以除以它们的和(0.38,0.34,0.28)

交叉熵:计算损失

实现过程

举例
CrossEntropyLoss vs NLLLoss(两者区别)

Implementation of classifier to MNIST dataset

过程

代码实现

导包

准备数据

  • ToTensor把图片转换成PyTorch中的张量,单通道(28x28)变成多通道(1x28x28),取值从0-255变成0-1

Normalize归一化:均值mean,标准差std

设计模型

全连接神经网络中要求输入是一个矩阵,把1x28x28的三阶张量变成二阶张量(矩阵),把图像的一行一行拼起来构成一串。view函数改变张量形状,...view(-1,784),-1表示自动计算该处的值,例如N=64,会自动算出64

最后一层不用做激活,直接对原生线形层的输出接入softmax

构造损失函数和优化器

带冲量的优化函数:把冲量值设置为0.5来优化训练过程

训练和测试

损失在在不断降低,测试集的准确率不断上升。准确率是有极限的,一般到了97%就上不去了,为什么上不去呢?做图像的时候,用全连接的神经,忽略了对局部信息的利用,把所有的元素之间都做的全连接。所以在处理的时候,第一个就是权重不够高,第二个就是在处理图像的时候,我们更关心一些比较抽象级别的特征。我们现在用的是非常原始的特征,所以如果说我们用一些特征提取方法,然后再去做这个分类训练,可能效果会更好一点。深度学习里边不用人工提取特征,可以自动的提取特征,例如CNN。

整体代码


# 多分类问题:引入Softmax分类器,在Pytorch中实现

# 1、导包
import torch
from torchvision import transforms # 针对图像的一些操作
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim

# 准备数据
batch_size=64
transform=transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,),(0.3081,))
])

train_dataset=datasets.MNIST(root='../dataset/mnist',
                             train=True,
                             transform=transforms.ToTensor(),
                             download=True)
test_dataset=datasets.MNIST(root='../dataset/mnist',
                             train=False,
                             transform=transforms.ToTensor(),
                             download=True)
train_loader=DataLoader(dataset=train_dataset,
                        batch_size=32,
                        shuffle=True)
test_loader=DataLoader(dataset=test_dataset,
                        batch_size=32,
                        shuffle=True)

# 3、设计模型
class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.linear11=torch.nn.Linear(784,512)
        self.linear12=torch.nn.Linear(512,256)
        self.linear13=torch.nn.Linear(256,128)
        self.linear14=torch.nn.Linear(128,64)
        self.linear15=torch.nn.Linear(64,10)

    def forward(self,x):
        x=x.view(-1,784)
        x=F.relu(self.linear11(x))
        x=F.relu(self.linear12(x))
        x=F.relu(self.linear13(x))
        x=F.relu(self.linear14(x))
        return self.linear15(x)

model=Net()

# 4、构造损失函数和优化器
criterion=torch.nn.CrossEntropyLoss()
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.5)

# 5、训练和测试
def train(epoch):
    running_loss=0.0
    for batch_idx,data in enumerate(train_loader,0):
        inputs,target=data
        optimizer.zero_grad()

        # forward+backward+update
        outputs=model(inputs)
        loss=criterion(outputs,target)
        optimizer.step()

        running_loss+=loss.item()
        if batch_idx % 300==299:
            print('[%d,%5d loss:%.3f'%(epoch+1,batch_idx+1,running_loss/300))
            running_loss=0.0
def test():
   correct=0
   total=0
   with torch.no_grad():
       for data in test_loader:
            images,labels=data
            outputs=model(images)
            _,predicted=torch.max(outputs.data,dim=1)
            total+=labels.size(0)
            correct+=(predicted==labels).sum().item()

            print('Accuracy on test set:%d %%'%(100*correct/total))
if __name__=='__main__':
    for epoch in range(10):
        train(epoch)
        test()

十、卷积神经网络(基础篇)

复习全连接神经网络:

网络中用的全都是线性层(由线性层串行的连接起来),在线性层里,输入值和每一个输出值都存在权重,每一个输入节点都要参与到下一层任何一个输出节点的运算上,我们把这样的线性层叫做全连接层(Fully Connected)

卷积神经网络基本结构

以二维卷积神经网络为例:

  • 二维卷积神经网络:首先要明确输入和输出张量的维度

  • 卷积层:保留图像的空间特征(全连接的时候是直接把图像拼成一长串,可能会丧失原有的一些空间特征,本来上下相邻的可能在长串里离得很远)

  • Subsampling下采样层:减少Feature maps中元素数量,降低运算需求。(通道数不变,图像的宽度和高度会发生改变)

  • 因为我们最终要做分类,所以要不断的卷积或者其他的运算,最终输出的是一个10维的向量[[0,1,2,3,4,5,6,7,8,9],所以要处理初始维度(先升高再降低....)不断进行维度的变化,把这个1x28x28的张量的空间转换成10维的向量。

一个图像(1x28x28)扔到5x5卷积层卷积出来的结果是一个3维张量(4x24x24),然后做一个2X2的下采样...——>三阶张量(8x4x4)——>一阶张量(只有10个元素)也就是一个向量

把三阶张量展开成一维向量:用view实现,8个通道每个通道有4x4个元素(————第一个通道的4行————第二个通道的4行......)得到线性的只有向量的的输入,然后用全连接层,映射到10维的输出,再接上交叉熵损失利用softmax去计算分布。

Feature Extraction特征提取层:通过卷积运算找到某种特征在特征提取阶段,我们是直接对图像进行卷积运算,特征提取之后把其转换成向量,然后再利用一个全连接的网络来做分类。

卷积具体介绍

单通道卷积运算过程

多通道卷积运算过程

N Input Channels and M Output Channels

卷积核通道数和输入通道数一样(input.channel=filter.channel),卷积核的个数和输出通道数一样(filter.sum=output.channel),卷积核的大小自己定,和图像的大小没有关系

卷积实现过程

padding

stride步长

可以有效降低Feature map的宽度和高度

Max Pooling Layer

简单的卷积神经网络

x=x.view(batch_size,-1)之后构建一个全连接层,直接降到10维,然后输出就和图像数字的输出一致了,扔到交叉熵里边获得损失进行训练。

如何使用GPU计算

把模型迁移到GPU

把用来计算的张量迁移到GPU

整体代码

# 完整代码
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim

batch_size = 64
transform = transforms.Compose([
    # convert the PIL Image to tensor,单通道变为多通道
    transforms.ToTensor(),
    # 数据标准化,切换到(0.1)分布,均值mean和标准差std,对MNIST所有像素值计算的结果
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST(root='./mnist/',
                               train=True,
                               download=True,
                               transform=transform)
train_loader = DataLoader(dataset=train_dataset,
                          shuffle=True,
                          batch_size=batch_size)

test_dataset = datasets.MNIST(root='./mnist/',
                              train=False,
                              download=True,
                              transform=transform)
test_loader = DataLoader(dataset=test_dataset,
                         shuffle=False,
                         batch_size=batch_size
                         )


class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(320, 10)

    def forward(self, x):
        batch_size = x.size(0)
        x = F.relu(self.pooling(self.conv1(x)))
        x = F.relu(self.pooling(self.conv2(x)))
        x = x.view(batch_size, -1)
        x = self.fc(x)
        return x


model = Net()
# 把模型迁移到GPU上
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

criterion = torch.nn.CrossEntropyLoss()
# 带冲量的梯度下降,冲量可以优化训练过程
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)


def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        inputs, target = data
        inputs, target = inputs.to(device), target.to(device)
        optimizer.zero_grad()

        outputs = model(inputs)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss:%.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
            running_loss = 0.0


def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            inputs, target = data
            inputs, target = inputs.to(device), target.to(device)
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, dim=1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
    print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))


if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()

十一、卷积神经网络(高级篇)

GoogleNet

Inception Module

我们在构造神经网络的时候有一些超参数是比较难选的,比如卷积核大小Kernel...GoogelNet的出发点是既然不知道哪个卷积核比较好用,所以在一个块中把这几个卷积都用一下,把结果叠在一起,如果3x3的比较好用,那自然3x3的权重就会变得比较大,其他路线的权重就会相对较小。

Concatenate:把张量沿着通道进行拼接

1x1Conv作用

1x1Conv的个数=输入的通道数,1x1卷积可以改变输入的通道数

通过改变通道数降低运算量

Inception代码实现

加入Padding是为了保证图像的宽度和高度不变

最终输出24+16+24+24=88个通道

dim=1表示把张量沿着通道进行拼接,张量维度(b,c,w,h)

总体代码

结果

通过改变卷积层的结构来提升性能

Residual Net

并非层数越多效果越好

Residual Net可以解决梯度消失问题:通过在激活前加上加上一个x来避免梯度消失

虚线表示:输入x和输出x的张量的维度是不一样的,因为经过了一个池化

Implementation of Simple Residual Network

3x3的卷积核为了保证输出大小不变使padding=1,还要保证输出和输入通道数一样,所以每次初始化时告诉通道设置为多少

不同Residual的构造

DenseNet

十二、循环神经网络(基础篇)

把x1-x3拼成一个有9个维度的常向量,然后去训练,去预测第四天(四天一组),前三天作为输入,第四天 作为输出。

第一种方法:用全连接去预测,但是如果序列很长,并且每个序列维度很高的话。(全连接网络的权重太多),训练起来相当复杂。

什么是RNN

RNN是专门用来处理带有序列模式数据,同时也需要使用权重共享来减少需要权重的数量。

RNN Cell本质是一个线性层(这个线性层是共享的),可以把某一个维度映射到另一个维度。

序列的每一项都送到RNN Cell中,因为序列之间每一项都和前一项存在着联系,例如先

x2计算出的h2不仅要包含x2的信息还要包含x1的信息(h1不仅输出了还送到了x2的运算中)

构造RNN

在PyTorch中有两种构造RNN的方式

  • 方式一:自己写RNN Cell,自己写处理序列的循环

如何使用RNN Cell

  • 方式二:直接使用RNN

如何使用RNN-numlayer

一个需要注意的点

例子

用RNN Cell实现序列——>序列

输入:是一个一个的独热向量

输出:因为只有4个字符,输出就是要判别是哪一个类别(e,h,l,o),对应的是一个多分类问题,类别为4个,输出的就是一个长度为4的向量,然后接一个softmax,就变成一个分布,将来输出的是属于哪个类别。

RNNCell代码实现

# 例子代码:用RNN Cell
import torch
# 1、准备数据
# 字母列表:方便我们之后根据索引来表示字符,也叫做字典
idx2char=['e','h','l','o']
# 根据字典进行转变的数据
x_data=[1,0,2,2,3]
y_data=[3,1,2,3,2]
#构造独热向量:可以简单的查询如下
# x_data=[1,0,2,2,3]——>[[0,1,0,0],
#                       [1,0,0,0],
#                       [0,0,1,0],
#                       [0,0,1,0],
#                       [0,0,0,1]]
one_hot_lookup=[[1,0,0,0],
                [0,1,0,0],
                [0,0,1,0],
                [0,0,0,1]]
# 是seqLen X input_size维度的数组
x_one_hot=[one_hot_lookup[x] for x in x_data]
# print(x_one_hot)

# inputs维度(seqLen,batch_size,input_size)
inputs=torch.Tensor(x_one_hot).view(-1,batch_size,input_size)
# print(inputs.shape)——>(5,1,4)
# labels维度(seqLen,1)
labels=torch.LongTensor(y_data).view(-1,1)
# print(labels)——>tensor([[3],
                #         [1],
                #         [2],
                #         [3],
                #         [2]])

# 2、构建模型
class Model(torch.nn.Module):
    def __init__(self,input_size,hidden_size,batch_size):
        super(Model,self).__init__()
        self.batch_size=batch_size
        self.input_size=input_size
        self.hidden_size=hidden_size
        # 维度要求:(batch_size,input_size),(batch_size,output_size)
        self.rnncell=torch.nn.RNNCell(input_size=self.input_size,
                                      hidden_size=self.hidden_size)

    #     执行的时候就是用rnncell把输入和隐层送进去,转换成下一个隐层
    def forward(self,input,hidden):
        hidden=self.rnncell(input,hidden)
        return hidden

    # 定义工具的方法:生成默认的初始h0,全0的
    def init_hidden(self):
        return torch.zeros(self.batch_size,self.hidden_size)

net=Model(input_size,hidden_size,batch_size)

# 3、构造损失函数和优化器
criterion=torch.nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(net.parameters(),lr=0.1)

# 4、训练过程

for epoch in range(15):
    loss=0
    # 每一轮训练之前先把优化器的梯度归零
    optimizer.zero_grad()
    # 先初始化hidden,计算h0
    hidden=net.init_hidden()
    print('predicted string',end='')
    # input(batch_size,input_size)  inputs(seq,batch_size,input_size)
    # label(1)  labels(seq,1)
    for input,label in zip(inputs,labels):
        hidden=net(input,hidden)
        # 每一步的损失加到一起
        loss+=criterion(hidden,label)
        # 为了输出预测:max是找hidden输出中的最大值,hidden是一个4维的表示4个字母的概率值
        # 找出最大的下标,就可以推测出这个字母最可能是哪个
        _,idx=hidden.max(dim=1)
        # 根据词典打印出每一项
        print(idx2char[idx.item()],end='')
    loss.backward()
    optimizer.step()
    print(',epoch[%d/15 loss=%.4f'%(epoch+1),loss.item())

RNN代码实现


# 用RNN
import torch
input_size=4
hidden_size=4
num_layers=1
batch_size=1
seq_len=5
# 1、准备数据
# 字母列表:方便我们之后根据索引来表示字符,也叫做字典
idx2char=['e','h','l','o']
# 根据字典进行转变的数据
x_data=[1,0,2,2,3]
y_data=[3,1,2,3,2]
#构造独热向量:可以简单的查询如下
# x_data=[1,0,2,2,3]——>[[0,1,0,0],
#                       [1,0,0,0],
#                       [0,0,1,0],
#                       [0,0,1,0],
#                       [0,0,0,1]]
one_hot_lookup=[[1,0,0,0],
                [0,1,0,0],
                [0,0,1,0],
                [0,0,0,1]]
# 是seqLen X input_size维度的数组
x_one_hot=[one_hot_lookup[x] for x in x_data]
# print(x_one_hot)

# inputs维度(seqLen,batch_size,input_size)
inputs=torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
# print(inputs.shape)——>(5,1,4)
# labels维度(seqLen,batch_size,1)
labels=torch.LongTensor(y_data)
# print(labels)——>tensor([[3],
                #         [1],
                #         [2],
                #         [3],
                #         [2]])

# 2、构建模型
class Model(torch.nn.Module):
    def __init__(self,input_size,hidden_size,batch_size,num_layers):
        super(Model,self).__init__()
        self.num_layers=num_layers
        self.batch_size=batch_size
        self.input_size=input_size
        self.hidden_size=hidden_size
        # 维度要求:(batch_size,input_size),(batch_size,output_size)
        self.rnn=torch.nn.RNN(input_size=self.input_size,
                                      hidden_size=self.hidden_size,
                                      num_layers=num_layers)

    #     执行的时候就是用rnncell把输入和隐层送进去,转换成下一个隐层
    def forward(self,input):
        hidden=torch.zeros(self.num_layers,
                         self.batch_size,
                         self.hidden_size)
        out,_=self.rnn(input,hidden)
        return out.view(-1,self.hidden_size)

net=Model(input_size,hidden_size,batch_size,num_layers)
# 3、构造损失函数和优化器
criterion=torch.nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(net.parameters(),lr=0.05)

# 4、训练
for epoch in range(1000):
    # 训练过程
    optimizer.zero_grad()
    # (seqLen,batch_size,input_size)
    # outputs(seqLen,batch_size,hidden_size)
    outputs=net(inputs)
    # labels(seqLen,batch_size,1)指明序列中每一个样本是哪个分类
    loss=criterion(outputs,labels)
    loss.backward()
    optimizer.step()

    _,idx=outputs.max(dim=1)
    idx=idx.data.numpy()
    print('Predicted:',''.join([idx2char[x] for x in idx]))
    print(',Epoch [%d/15 loss=%.3f'%(epoch+1,loss.item()))

十三、循环神经网络(高级篇)

案例:名字分类

任务目标:实现一个循环神经网络的分类器(根据名字预测对应的国家)

循环神经网络模型复习

处理自然语言的时候一般先把字或词转换为独热向量,但是one-hot向量维度过高并且过于稀疏,因此使用嵌入层Embed:把它转为低维的,稠密的向量。然后经过循环神经网络,因为隐层的输出不一定符合我们最终的要求,因此可能还需要一个线性层Linear Layer

案例模型

此案例只需要最终输出一个大的分类(属于哪个国家),和隐层的输出无关,因此去掉上边的部分。

先做嵌入,然后RNN,然后把最后的隐层输出再经过一个线性层再去做分类

模型的处理过程

GRU神经网络:GRU(Gate Recurrent Unit)是循环神经网络(Recurrent Neural Network, RNN)的一种。

案例实现过程

准备数据

首先要读取文件,然后分别拿出姓名(['Adsit', 'Ajdrna', 'Antonowitsch',...])以及国家,国家需要做进一步的处理(去除重复元素转换成一个字典{'Arabic': 0, 'Chinese': 1, 'Czech': 2, 'Dutch': 3, 'English': 4, 'French': 5, 'German': 6, 'Greek': 7, 'Irish': 8, 'Italian': 9, 'Japanese': 10, 'Korean': 11, 'Polish': 12, 'Portuguese': 13, 'Russian': 14, 'Scottish': 15, 'Spanish': 16, 'Vietnamese': 17})。

# 1、数据准备
class NameDataset(Dataset):

    def __init__(self,is_train_set=True):
        # 如果是训练集就从训练集读,如果是测试集就从测试集读
       # filename='data/names_train.csv.gz' if  is_train_set else 'data/names_test.csv.gz'
        filename='data/names_train.csv.gz' if  is_train_set else 'data/names_test.csv.gz'
        #  用gzip.open来打开zip文件,然后用csv.reader去读里边的数据,‘rt’只读方式打开文本文件
        with gzip.open(filename,'rt') as f:
            reader=csv.reader(f)
            # print(reader)
            # row是由[name,country]
            rows=list(reader)
            # print(rows) #[['Adsit', 'Czech'], ['Adrian', 'Czech']...]
            # print(rows[0]) #['Adsit', 'Czech']
        self.names=[row[0] for row in rows]
        # print(self.names)#['Adsit', 'Ajdrna', 'Antonowitsch',...]
        self.len=len(self.names)
        # print(self.len)#13374
        self.countries=[row[1] for row in rows]
        # print(self.countries)#['Czech', 'Czech'...]
        # set:把列表变成集合,去除重复的元素,每个国家只剩一个实例,然后sorted进行排序变成一个列表
        # print(set(self.countries))
        self.country_list=list(sorted(set(self.countries)))
        # print( self.country_list)#是一个不重复的国家列表,如下
# ['Arabic', 'Chinese', 'Czech', 'Dutch', 'English', 'French', 'German', 'Greek', 'Irish', 'Italian', 'Japanese', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Scottish', 'Spanish', 'Vietnamese']
        # 把列表转化成一个词典,调用函数getCountryDict()
        self.country_dict=self.getCountryDict()
        # print(self.country_dict)#{'Arabic': 0, 'Chinese': 1, 'Czech': 2, 'Dutch': 3, 'English': 4, 'French': 5, 'German': 6, 'Greek': 7, 'Irish': 8, 'Italian': 9, 'Japanese': 10, 'Korean': 11, 'Polish': 12, 'Portuguese': 13, 'Russian': 14, 'Scottish': 15, 'Spanish': 16, 'Vietnamese': 17}
        self.country_num=len(self.country_list)
        # print(self.country_num)#18

    def __getitem__(self, index):
        # 拿出名字以及其对应的国家,然后根据国家,从表中查找对应的索引
        # 拿出的名字是一个字符串,拿出的国家是一个索引
        return self.names[index],self.country_dict[self.countries[index]]
    # 返回数据集长度
    def __len__(self):
        return self.len

    def getCountryDict(self):
        # 先做一个空字典
        country_dict=dict()
        # 对self.country_list进行遍历,构造键值对
        # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
        for idx, country_name in enumerate(self.country_list,0):
            country_dict[country_name]=idx
        return country_dict
    # 根据索引返回国家的字符串,比如得到一个分类9,要根据9把对应的国家拿出来
    def idx2country(self,index):
        return self.country_list[index]
    # 到底有多少个国家
    def getCountriesNum(self):
        return self.country_num
# 构建NameDataset的实例
trainset=NameDataset(is_train_set=True)
# DataLoader构造加载器
trainloader=DataLoader(trainset,batch_size=BATCH_SIZE,shuffle=True)

testset=NameDataset(is_train_set=False)
testloader=DataLoader(trainset,batch_size=BATCH_SIZE,shuffle=True)
# 总的类别数量放到N_COUNTRY,决定将来我们模型最终输出的大小
N_COUNTRY=trainset.getCountriesNum()
名字处理

用ASCII作为字典

因为每个数据维度不一样所以要进行Padding,找出最长的字符串,其它的补零,每一个维度的长度一样才可以构成张量

国家处理

把每个国家转换成分类的索引,必须是0开始的整数

   # set:把列表变成集合,去除重复的元素,每个国家只剩一个实例,然后sorted进行排序变成一个列表
        self.country_list=list(sorted(set(self.countries)))
        # 把列表转化成一个词典
        self.country_dict=self.getCountryDict()

构建模型过程

# 2、模型设计
class RNNClassifier(torch.nn.Module):
    def __init__(self, input_size, hidden_size, output_size, n_layers=1, bidirectional=True):
        super(RNNClassifier, self).__init__()
        # 要用在GRU的处理上
        self.hidden_size = hidden_size
        self.n_layers = n_layers
        # 双向是2
        self.n_directions = 2 if bidirectional else 1
        # 嵌入层:input_size相当于 N_CHARS = 128
        self.embedding = torch.nn.Embedding(input_size, hidden_size)
        # GRU层:bidirectional
        self.gru = torch.nn.GRU(hidden_size, hidden_size, n_layers,
        bidirectional=bidirectional)
        # 线性层:把hidden_size转换成输出的维度
        self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)
    # 根据输入的batch_size来构成一个全0的hidden张量,全0的初始隐层
    def _init_hidden(self, batch_size):
        hidden = torch.zeros(self.n_layers * self.n_directions,
                            batch_size, self.hidden_size)
        return create_tensor(hidden)

    def forward(self,input,seq_lengths):
        # input shape:BxS-->SxB转置(SeqLen,batch_size)
        input = input.t()
        # 保存batch_size以便我们构造最初始的隐层
        batch_size = input.size(1)
        #创建隐状态,根据batch_size利用_init_hidden()函数来创建
        hidden = self._init_hidden(batch_size)
        # 把input扔进嵌入层,做完嵌入层,输入的维度变成(seqLen,batch_size,hidden_size)
        embedding = self.embedding(input)
        # 提高神经网络运行效率(面对序列长短不一,如果后边填充的是0,是没必要参与运算的),seq_lengths是从参数来的
        gru_input = pack_padded_sequence(embedding,seq_lengths)
        # 用GRU去计算(gru输入,初始隐层)
        output,hidden = self.gru(gru_input,hidden)
        # 如果是双向循环神经网络,会有两个hidden,要把两个拼起来
        if self.n_directions == 2:
            hidden_cat = torch.cat([hidden[-1],hidden[-2]],dim = 1)
        else:
            hidden_cat = hidden[-1]
        # 把hidden_cat放到fc层,变换成想要的维度
        fc_output = self.fc(hidden_cat)
        return fc_output
什么是双向神经网络?

正向反向算出来的进行拼接,out:每个隐层的输出

最终输出的隐层hidden

每个层的输入输出
嵌入层
GRU层
线性层

如果是双向的hidden_size需要乘于2

self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)
forward过程

batch_size:每一批的样本数 SeqLen:每个样本的长度(补全后)

gru_input = pack_padded_sequence(embedding,seq_lengths)功能:

需要继续理解这个过程

把名字转换为tensor

# 把每一个名字都变成列表,给一个名字,读出每个字母的ASCII值
def name2list(name):
    arr = [ord(c) for c in name]
    return arr, len(arr)
# 判断是否使用GPU
def create_tensor(tensor):
    if USE_GPU:
        device = torch.device("cuda:0")
        tensor = tensor.to(device)
    return tensor

# 把名字转换成我们想要的Tensor的过程
def make_tensors(names,countries):
        sequences_and_lengths = [name2list(name) for name in names]
        # 拿出列表
        name_sequences = [s1[0] for s1 in sequences_and_lengths]
        # 单独拿出列表长度
        seq_lengths = torch.LongTensor([s1[1] for s1 in sequences_and_lengths])
        countries = countries.long()

        # 做padding的过程(先做一个全0的张量,用复制的方式把列表贴到全0的张量上)
        seq_tensor = torch.zeros(len(name_sequences),seq_lengths.max()).long()
        for idx,(seq,seq_len) in enumerate(zip(name_sequences,seq_lengths),0):
            seq_tensor[idx,:seq_len] = torch.LongTensor(seq)
        # 按序列长度排序seq_lengths:排完序的序列,perm_idx:对应元素的id
        seq_lengths,perm_idx = seq_lengths.sort(dim = 0,descending = True)
        seq_tensor = seq_tensor[perm_idx]
        countries = countries[perm_idx]

        return create_tensor(seq_tensor),create_tensor(seq_lengths),create_tensor(countries)
实现原理
  • 先把字符串变成一个一个字符,再把字符变成相应的ASCII值

  • 做填充

  • 做转置

  • 做排序

代码解析

name2list返回的是一个元组([列表本身],长度)

最终代码实现


# RNN高级
import torch
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pack_padded_sequence
import time
import math
import csv
import gzip
# Parameters
# 隐层100维
HIDDEN_SIZE = 100
# 每一个批量256个名字
BATCH_SIZE = 256
# GRU用了2层
N_LAYER = 2
# 训练100轮
N_EPOCHS = 100
# 128个字典长度
N_CHAPS = 128
# 不用Gpu
USE_GPU = False



# 1、数据准备
class NameDataset(Dataset):

    def __init__(self,is_train_set=True):
        # 如果是训练集就从训练集读,如果是测试集就从测试集读
       # filename='data/names_train.csv.gz' if  is_train_set else 'data/names_test.csv.gz'
        filename='data/names_train.csv.gz' if  is_train_set else 'data/names_test.csv.gz'
        #  用gzip.open来打开zip文件,然后用csv.reader去读里边的数据,‘rt’只读方式打开文本文件
        with gzip.open(filename,'rt') as f:
            reader=csv.reader(f)
            # print(reader)
            # row是由[name,country]
            rows=list(reader)
            # print(rows) #[['Adsit', 'Czech'], ['Adrian', 'Czech']...]
            # print(rows[0]) #['Adsit', 'Czech']
        self.names=[row[0] for row in rows]
        # print(self.names)#['Adsit', 'Ajdrna', 'Antonowitsch',...]
        self.len=len(self.names)
        # print(self.len)#13374
        self.countries=[row[1] for row in rows]
        # print(self.countries)#['Czech', 'Czech'...]
        # set:把列表变成集合,去除重复的元素,每个国家只剩一个实例,然后sorted进行排序变成一个列表
        # print(set(self.countries))
        self.country_list=list(sorted(set(self.countries)))
        # print( self.country_list)#是一个不重复的国家列表,如下
# ['Arabic', 'Chinese', 'Czech', 'Dutch', 'English', 'French', 'German', 'Greek', 'Irish', 'Italian', 'Japanese', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Scottish', 'Spanish', 'Vietnamese']
        # 把列表转化成一个词典,调用函数getCountryDict()
        self.country_dict=self.getCountryDict()
        # print(self.country_dict)#{'Arabic': 0, 'Chinese': 1, 'Czech': 2, 'Dutch': 3, 'English': 4, 'French': 5, 'German': 6, 'Greek': 7, 'Irish': 8, 'Italian': 9, 'Japanese': 10, 'Korean': 11, 'Polish': 12, 'Portuguese': 13, 'Russian': 14, 'Scottish': 15, 'Spanish': 16, 'Vietnamese': 17}
        self.country_num=len(self.country_list)
        # print(self.country_num)#18

    def __getitem__(self, index):
        # 拿出名字以及其对应的国家,然后根据国家,从表中查找对应的索引
        # 拿出的名字是一个字符串,拿出的国家是一个索引
        return self.names[index],self.country_dict[self.countries[index]]
    # 返回数据集长度
    def __len__(self):
        return self.len

    def getCountryDict(self):
        # 先做一个空字典
        country_dict=dict()
        # 对self.country_list进行遍历,构造键值对
        # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
        for idx, country_name in enumerate(self.country_list,0):
            country_dict[country_name]=idx
        return country_dict
    # 根据索引返回国家的字符串,比如得到一个分类9,要根据9把对应的国家拿出来
    def idx2country(self,index):
        return self.country_list[index]
    # 到底有多少个国家
    def getCountriesNum(self):
        return self.country_num
# 构建NameDataset的实例
trainset=NameDataset(is_train_set=True)
# DataLoader构造加载器
trainloader=DataLoader(trainset,batch_size=BATCH_SIZE,shuffle=True)

testset=NameDataset(is_train_set=False)
testloader=DataLoader(trainset,batch_size=BATCH_SIZE,shuffle=True)
# 总的类别数量放到N_COUNTRY,决定将来我们模型最终输出的大小
N_COUNTRY=trainset.getCountriesNum()

# 把每一个名字都变成列表,给一个名字,读出每个字母的ASCII值
def name2list(name):
    arr = [ord(c) for c in name]
    return arr, len(arr)
# 判断是否使用GPU
def create_tensor(tensor):
    if USE_GPU:
        device = torch.device("cuda:0")
        tensor = tensor.to(device)
    return tensor

# Parameters
# HIDDEN_SIZE = 100
# BATCH_SIZE =256
# N_LAYER = 2
# N_EPOCHS = 100
# N_CHARS = 128
# USE_GPU = False

# 2、模型设计
class RNNClassifier(torch.nn.Module):
    def __init__(self, input_size, hidden_size, output_size, n_layers=1, bidirectional=True):
        super(RNNClassifier, self).__init__()
        # 要用在GRU的处理上
        self.hidden_size = hidden_size
        self.n_layers = n_layers
        # 双向是2
        self.n_directions = 2 if bidirectional else 1
        # 嵌入层:input_size相当于 N_CHARS = 128
        self.embedding = torch.nn.Embedding(input_size, hidden_size)
        # GRU层:bidirectional
        self.gru = torch.nn.GRU(hidden_size, hidden_size, n_layers,
        bidirectional=bidirectional)
        # 线性层:把hidden_size转换成输出的维度
        self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)
    # 根据输入的batch_size来构成一个全0的hidden张量,全0的初始隐层
    def _init_hidden(self, batch_size):
        hidden = torch.zeros(self.n_layers * self.n_directions,
                            batch_size, self.hidden_size)
        return create_tensor(hidden)

    def forward(self,input,seq_lengths):
        # input shape:BxS-->SxB转置(SeqLen,batch_size)
        input = input.t()
        # 保存batch_size以便我们构造最初始的隐层
        batch_size = input.size(1)
        #创建隐状态,根据batch_size利用_init_hidden()函数来创建
        hidden = self._init_hidden(batch_size)
        # 把input扔进嵌入层,做完嵌入层,输入的维度变成(seqLen,batch_size,hidden_size)
        embedding = self.embedding(input)
        # 提高神经网络运行效率(面对序列长短不一,如果后边填充的是0,是没必要参与运算的),seq_lengths是从参数来的
        gru_input = pack_padded_sequence(embedding,seq_lengths)
        # 用GRU去计算(gru输入,初始隐层)
        output,hidden = self.gru(gru_input,hidden)
        # 如果是双向循环神经网络,会有两个hidden,要把两个拼起来
        if self.n_directions == 2:
            hidden_cat = torch.cat([hidden[-1],hidden[-2]],dim = 1)
        else:
            hidden_cat = hidden[-1]
        # 把hidden_cat放到fc层,变换成想要的维度
        fc_output = self.fc(hidden_cat)
        return fc_output

# 把名字转换成我们想要的Tensor的过程
def make_tensors(names,countries):
        sequences_and_lengths = [name2list(name) for name in names]
        # 拿出列表
        name_sequences = [s1[0] for s1 in sequences_and_lengths]
        # 单独拿出列表长度
        seq_lengths = torch.LongTensor([s1[1] for s1 in sequences_and_lengths])
        countries = countries.long()

        # 做padding的过程(先做一个全0的张量,用复制的方式把列表贴到全0的张量上)
        seq_tensor = torch.zeros(len(name_sequences),seq_lengths.max()).long()
        for idx,(seq,seq_len) in enumerate(zip(name_sequences,seq_lengths),0):
            seq_tensor[idx,:seq_len] = torch.LongTensor(seq)
        # 按序列长度排序seq_lengths:排完序的序列,perm_idx:对应元素的id
        seq_lengths,perm_idx = seq_lengths.sort(dim = 0,descending = True)
        seq_tensor = seq_tensor[perm_idx]
        countries = countries[perm_idx]

        return create_tensor(seq_tensor),create_tensor(seq_lengths),create_tensor(countries)

# 训练模型
def trainModel():
    total_loss = 0
    for i,(names,countries) in enumerate(trainloader,1):
        # inputs:输入矩阵  ,seq_lengths:每个序列长度 ,target:标签
        inputs,seq_lengths,target = make_tensors(names,countries)
        output = classifier(inputs,seq_lengths)
        loss = criterion(output,target)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 总的损失量
        total_loss += loss.item()
        if i % 10 == 0:
            print(f'[{time_since(start)}] Epoch {epoch}',end = '')
            print(f'[{i * len(inputs)} / {len(trainset)}]',end = '')
            print(f'loss = {total_loss / (i * len(inputs))}')
    return total_loss

def testModel():
    correct = 0
    total = len(testset)
    print("evaluating trained model ...")
    # 不需要求梯度
    with torch.no_grad():
        for i, (names,countries) in enumerate(testloader,1):
            inputs,seq_lengths,target = make_tensors(names,countries)
            output = classifier(inputs,seq_lengths)
            pred = output.max(dim =1,keepdim = True)[1]
            correct += pred.eq(target.view_as(pred)).sum().item()

        percent = '%.2f' % (100 * correct / total)
        print(f'Test set:Accuracy {correct} / {total} {percent} %')
    return correct / total

def time_since(since):
    s = time.time() - since
    m = math.floor(s / 60)
    s -= m * 60
    return '%dm %ds' % (m,s)

if __name__ == '__main__':
    classifier = RNNClassifier(N_CHAPS,HIDDEN_SIZE,N_COUNTRY,N_LAYER)
    if USE_GPU:
        device = torch.device("cuda:0")
        classifier.to(device)

    criterion = torch.nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(classifier.parameters(),lr = 0.001)

    start = time.time() #距离开始训练过去了多少时间
    print("Training for %d epochs..." % N_EPOCHS)
    acc_list = []
    for epoch in range(1,N_EPOCHS + 1):
        trainModel()
        acc = testModel()
        acc_list.append(acc) #测试的结果添加到列表中

    epoch = np.arange(1, len(acc_list) + 1, 1)
    acc_list = np.array(acc_list)
    plt.plot(epoch, acc_list)
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.grid()
    plt.show()

猜你喜欢

转载自blog.csdn.net/weixin_48137421/article/details/128756578