(一) Pytorch张量
1、张量:张量就是多维数组
eg:定义一个一维矩阵
import pytorch
import torch
defien a tensor
torch.FloatTensor([2])
2、torch模块提供了可对其进行扩展操作的库。
3、张量与列表的区别:
Python列表或数字元组(tuple)是在内存中单独分配的Python对象的集合,如图2.3左侧所示。然而,PyTorch张量或NumPy数组(通常)是连续内存块上的视图(view),这些内存块存有未封装(unboxed)的C数值类型。
4、张量的存储:
(1) 使用.storage属性可以访问给定张量的内存存储。
eg:
import torch
points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
points.storage()
1.0
4.0
2.0
1.0
3.0
5.0
[torch.FloatStorage of size 6]
(2)改变了内存存储中的值,相应的所有索引到该存储的张量值都会改变
points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
>>> points_storage = points.storage()
>>> points_storage[0] = 2.0
>>> points
tensor([[2., 4.],
[2., 1.],
[3., 5.]])
5、张量的尺寸(size)、存储偏移(offset)、步长(stride)。
(1)尺寸:表示张量每个维度上有多少个元素
(2)存储偏移:存储中与张量中的第一个元素相对应的索引
(3)步长:在存储中为了沿每个维度获取下一个元素而需要跳过的元素数量
import torch
point = torch.tensor([[1.0,4.0],[2.0,1.0],[3.0,5.0]])
second_point=point[1]
a=second_point.size()#提取尺寸
b=second_point.storage_offset()#提取存储偏移
c=point.stride()#步长是一个元组,表示当索引在每个维度上增加1时必须跳过的存储中元素的数量
print(a)
print(b)
print(c)
(4)提取子张量后,例如上述代码中second_point=point[1]。它并不会导致内存的重新分配,而是他们分配了一个新的张量对象,对象具有不同的尺寸、存储偏移或步长。如果给新提取的张量赋新值,原张量对应值也会改变。可以使用克隆(.clone)函数得到新的张量
import torch
point = torch.tensor([[1.0,4.0],[2.0,1.0],[3.0,5.0]])
second_point=point[1]
a=second_point.size()#提取尺寸
b=second_point.storage_offset()#提取存储偏移
c=second_point.stride()#步长是一个元组,表示当索引在每个维度上增加1时必须跳过的存储中元素的数量
print(a)
print(b)
print(c)#在这个这个步长变了,因为提取的张量是一维的
#使用克隆函数
points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
second_point = points[1].clone()
second_point[0] = 10.0
print(points )
(5)张量的转置:你更改了步长中元素的顺序。这样一来,增加行(张量的第一个索引)就会沿着存储跳过1个元素,就像points沿着列移动一样,这就是转置的定义。(这个过程)没有分配新的内存:仅通过创建一个步长顺序与原始张量不同的新的张量实例来实现转置。
import torch
points = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 5.0]])
points_t=points.t()
print(points_t)
#验证两个张量共享同一个存储空间 id()函数返回内存地址
if id(points_t.storage())==id(points.storage()):
print("ture")
else:
print("false")
(5)连续张量与非连续张量:从最右边的维开始将其值存放在存储中的张量(例如沿着行存放在存储中的二维张量)定义为连续(Contiguous)张量
6、数据类型
(1)通过dtype来获取张量的数据类型
import torch
double_points = torch.ones(10, 2, dtype=torch.double)
short_points = torch.tensor([[1, 2], [3, 4]], dtype=torch.short)
print(double_points)
print(short_points)
(2)使用相应的转换方法将张量创建函数的输出转换为正确的类型或者使用to方法
(3)使用type方法将一种类型的张量转换为另一种类型的张量
import torch
point = torch.tensor([[1,4],[2,1],[3,5],[9,10]])
points=point.type(torch.double)
7、索引张量
可以使用python中列表切片的方法对张量进行切片(索引)q8
import torch
point = torch.tensor([[1,4],[2,1],[3,5],[9,10]])
points=point.type(torch.double)
print(points)
a=point[1:3] # 第1行及之后所有行,(默认)所有列
b=point[1:, :] # 第1行及之后所有行,所有列
c=point[1:, 0] # 第1行及之后所有行,仅第0列
print(a)
print(b)
print(c)
8、将张量转移到GPU上运行
(1)在GPU上创建张量
pyTorch张量还具有设备(device)的概念,这是在设置计算机上放张量(tensor)数据的位置。 通过为构造函数指定相应的参数,可以在GPU上创建张量:
points_gpu = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 4.0]], device='cuda')
还可以使用to方法将在CPU上创建的张量(tensor)复制到GPU:
points_gpu = points.to(device='cuda')
(2)在GPU上进行张量运算
在GPU上进行张量运算发生三个过程:
a.将points张量复制到GPU
b.在GPU上分配了一个新的张量,并用于存储乘法的结果
c.返回该GPU张量的句柄
points = 2 * points # 在CPU上做乘法
points_gpu = 2 * points.to(device='cuda') # 在GPU上做乘法
(二)使用张量表示真实数据
1、表格数据
(1)三种数据类型:
第一种是连续值。即以单位进行计数和测量的值。例如重量值等。
第二种是序数值。即值之间的无固定关系。例如订购大中小杯饮料。将小号映射为值1,将中号映射为2,将大号映射为3。
第三种是类别。即无顺序也无数值含义。是可能性的枚举。例如将水分配1,将咖啡分配给2,将苏打分配给3,将牛奶分配给4。水在前牛奶在后没有逻辑。你仅仅是需要不同的值来区分它们而已
(2)对预测数据的两种处理方式:你可以将(葡萄酒质量分数)分数视为连续变量,视为实数,然后进行回归任务;或者将其视为标签,作为分类任务尝试从化学分析猜出该标签。在这两种方法中,往往都将分数从输入数据的张量中删除,并将其保存在单独的张量中,以便将分数用作ground truth而不将其输入到模型中
(3)标签数据的两种处理方法:
a.将标签数据视为整数向量:适用于数据之间有大小关系,比如预测红酒品质,分数9就比分数5要好
target = wineq[:, -1].long()
b.构建独热(one-hot)编码:即将10个分数编码成10个向量,每个向量除了一个元素为1外其他所有元素都设置为0。此时,分数1可以映射到向量(1,0,0,0,0,0,0,0,0,0),分数5映射到(0,0,0,0,1,0,0,0,0,0),等等。分数值与非零元素的索引相对应的事实纯属偶然;你可以打乱上述分配,从分类的角度来看,什么都不会改变。适用于颜色。
使用scatter_方法来实现独热编码
target_onehot = torch.zeros(target.shape[0], 10)
target_onehot.scatter_(1, target.unsqueeze(1), 1.0)
scatter_的参数是
指定后面两个参数所处理的维度
列张量,指示要填充的索引
包含填充元素的张量或者单个标量(上例中即1.0)
(4)对于红酒的数据处理以及预测
import csv
import numpy as np
import torch
wine_path="C:/Users/60947/Downloads/winequality-white.csv"
wineq_numpy=np.loadtxt(wine_path,dtype=np.float32,delimiter=";",skiprows=1)#分隔符为;并且不读第一行(跳过)
# 测试 print(wineq_numpy)
#检查是否读取所有数据
col_list = next(csv.reader(open(wine_path), delimiter=';'))
# 测试 print(wineq_numpy.shape)
# 测试 print(col_list)
#将numpy数组转化成pytorch张量
wineq=torch.from_numpy(wineq_numpy)
# 测试 print(wineq.shape)
# 测试 print(wineq.type())
#分离数据
data = wineq[:, :-1] # 除最后一列外所有列
target=wineq[:,-1] #最后一列
#one-hot测试
#print(target.shape[0])#target.shape==target.size返回一个元组 一个元组就是一个列表 所以返回列表的第一个元素的值
target_onehot = torch.zeros(target.shape[0], 10) #构建一个4898(target.shape[0])行,10列的0矩阵
#print(target_onehot)
#计算化学分析关联的十一个列的均值与标准差与方差
data_mean=torch.mean(data,dim=0)#沿着维数0进行计算
data_var=torch.var(data,dim=0)
data_normolized=(data-data_mean)/torch.sqrt(data_var)#归一化 其实就是求方差
#我们来审查数据以寻找一种简单的方法来一眼分辨好酒和坏酒。首先,使用torch.le函数确定target中哪些行对应的分数小于或等于3:
#bad_indexes=torch.le(target,3)#le函数https://blog.csdn.net/ljs_a/article/details/79848994
#print(bad_indexes.sum())
#bad_data=data[bad_indexes]#将坏品质酒的十一列提取出来
#开始获取被分为好、中、坏三类的葡萄酒的信息
bad_data=data[torch.le(target,3)]
mid_data=data[torch.gt(target,3)&torch.lt(target,7)]
good_data=data[torch.ge(target,7)]
bad_mean=torch.mean(bad_data,dim=0)
mid_mean=torch.mean(mid_data,dim=0)
good_mean=torch.mean(good_data,dim=0)
for i, args in enumerate(zip(col_list, bad_mean, mid_mean, good_mean)):
print('{:2} {:20} {:6.2f} {:6.2f} {:6.2f}'.format(i, *args))
#设置二氧化硫总含量为阈值
total_sulfur_threshold=141.83
total_sulfur_data=data[:,6]#取第六列是所有二氧化硫
predicted_indexes=torch.lt(total_sulfur_data,total_sulfur_threshold)#小于阈值的索引
#获取实际优质葡萄酒的索引
actual_indexes=torch.gt(target,6)
print(predicted_indexes.sum())
print(actual_indexes.sum())
#进行预测与实际吻合程度分析
n_matches = torch.sum(actual_indexes & predicted_indexes).item()
n_predicted = torch.sum(predicted_indexes).item()
n_actual = torch.sum(actual_indexes).item()
print(n_matches)
print(n_matches/n_predicted)
print(n_matches/n_actual)
2、文本数据
实现目标:将文本转换成神经网络可以处理的东西就像前面的例子一样,即数值张量。在处理成数值张量之后,再为你的文本处理工作选择正确的网络结构,然后就可以使用PyTorch进行NLP了
3、图像数据
import imageio
import torch
import os
#加载图像数据并且转换成NumPy数组对象,它有三个维度:两个空间维度(宽度和高度),以及对应于红色、绿色和蓝色的第三个维度
img_arr=imageio.imread('C:/Users/60947/Desktop/QQ图片20200625104844.jpg')
print(img_arr.shape)
#因为pytorch需要C*H*W(分别为通道、高度和宽度) 所以要对获得得数组进行转置
#给定W x H x C的输入张量,你可以通过交换第一个和最后一个通道来获得正确的维度设置
img=torch.from_numpy(img_arr)
out=torch.transpose(img,0,2)
#创建b包含多个图像得数据集作为神经网络得输入,然后沿着第一维将这些图像按照批量存储,以获得N x C x H x W 张量
batch=torch.zeros(100,3,256,256,dtype=torch.uint8)
#将文件夹中得图片存储在张量中
data_dir='C:/Users/60947/Desktop/image-people/'
filenames = [name for name in os.listdir(data_dir) if os.path.splitext(name) == '.png']
for i, filename in enumerate(filenames):
img_arr = imageio.imread(filename)
batch[i] = torch.transpose(torch.from_numpy(img_arr), 0, 2)
#将张量转换为浮点数并且归一化像素值
#将像素除以255进行归一
batch /= 255.0
#另一种可能的选择是计算输入数据的均值和标准偏差并对其进行缩放,以便于在每个通道上的均值和单位标准偏差输出为零:
n_channels = batch.shape[1]
for c in range(n_channels):
mean = torch.mean(batch[:, c])
std = torch.std(batch[:, c])
batch[:, c] = (batch[:, c] - mean) / std
(三) 学习机制
模型的学习过程: 给定输入数据和相应的期望输出(ground truth)以及权重的初始值,模型输入数据(前向传播),然后通过把结果输出与ground truth进行比较来评估误差。为了优化模型的参数,其权重(即单位权重变化引起的误差变化,也即误差相对于参数的梯度)通过使用对复合函数求导的链式法则进行计算(反向传播)。然后,权重的值沿导致误差减小的方向更新。不断重复该过程直到在新数据上的评估误差降至可接受的水平以下。
下面以一个例子来进行学习这个过程:
(1)记录能正常工作的旧摄氏温度计的数据和你刚带回来的新温度计对应的测量值
(2)首先选择线性模型:t_c=w*t_u+b ( t_c是摄氏度数,t_u是未知单位度数,w与b分别是权重和偏差)
(3)减少损失(引入损失函数):损失函数通常是计算训练样本的期望输出与模型接收这些样本所产生的实际输出之间的差异
补充:PyTorch学习-梯度下降算法
Gradient Descent Algorithm : 梯度下降算法(贪心思想,局部最优)
Gradient : 梯度,梯度大于0上升,梯度小于0下降,所以参数向梯度的反方向更新。
w=w−xg′(w)
x:学习率
损失函数的局部最优点比较少,但是我们有可能遇到鞍点,鞍点的导数等于0(梯度等于0, g′(w)=0),这时候参数无法更新。
梯度下降法得应用过程:
1、针对参数,随机赋初始值
2、对预测函数对每个参数求偏导(即求梯度)
3、让每个参数的初始值减去第二步求得偏导乘以一个很小的数:w=w−xg′(w)
将华氏度转换为摄氏度的训练过程:
import torch
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
#线性模型
def model(t_u,w,b):
return t_u*w+b
#损失函数
def loss_fun(t_p,t_c):
squared_diffs=(t_p-t_c)**2
return squared_diffs.mean()
#初始化w(权重参数)和b(偏执参数)
w=torch.ones(1)
b=torch.zeros(1)
t_p=model(t_u,w,b)
#print(t_p)
#loss=loss_fun(t_p,t_c)
#print(loss)
#如何估计w与使得损失达到最小呢?
#梯度下降法
#delta=0.1
#loss_rate_of_change_w = \
# (loss_fun(model(t_u, w + delta, b), t_c) -
# loss_fun(model(t_u, w - delta, b), t_c)) / (2.0 * delta)
#learning_rate=1e-2
#w=w-learning_rate*loss_rate_of_change_w
#loss_rate_of_change_b = \
# (loss_fun(model(t_u, w, b + delta), t_c) -
# loss_fun(model(t_u, w, b - delta), t_c)) / (2.0 * delta)
#b = b - learning_rate * loss_rate_of_change_b
#求损失函数对参数w与参数b的导数
#先求损失对t_p的导书
def dloss_fn(t_p,t_c):
dsq_diffs=2*(t_p-t_c)
return dsq_diffs
#再求t_p对参数w的导数
def dmodelw_fn(t_u,w,b):
return t_u
#求t_p对参数b的导数
def demodelb_fn(t_u,w,b):
return 1.0
#最后相乘的到损失对于w与b的导数
def grad_fn(t_u,t_c,t_p,w,b):
dloss_dw=dloss_fn(t_p,t_c) * dmodelw_fn(t_u,w,b)
dloss_db= dloss_fn(t_p, t_c) * demodelb_fn(t_u, w, b)
return torch.stack([dloss_dw.mean(),dloss_db.mean()])
#训练循环
def training_loop(n_epochs, learning_rate, params, t_u, t_c,
print_params=True, verbose=1):
for epoch in range(1, n_epochs + 1):
w, b = params
t_p = model(t_u, w, b) # 前向传播
loss = loss_fun(t_p, t_c)
grad = grad_fn(t_u, t_c, t_p, w, b) # 反向传播
params = params - learning_rate * grad
if epoch % verbose == 0:
print('Epoch %d, Loss %f' % (epoch, float(loss)))
if print_params:
print(' Params: ', params)
print(' Grad : ', grad)
return params
#限制learning_rate的大小,或者对t_u进行规范化
t_un=t_u*0.1
training_loop(
n_epochs = 10,
learning_rate = 1e-2,
params = torch.tensor([1.0, 0.0]),
t_u = t_un, # 规范化后的输入
t_c = t_c)
#绘图
%matplotlib inline
from matplotlib import pyplot as plt
t_p = model(t_un, *params) # 记住你是在规范后数据上训练的
fig = plt.figure(dpi=600)
plt.xlabel("Fahrenheit")
plt.ylabel("Celsius")
plt.plot(t_u.numpy(), t_p.detach().numpy()) # 在原数据上作图
plt.plot(t_u.numpy(), t_c.numpy(), 'o')
(四) PyTorch Autograd模块
1、概念介绍
PyTorch使用了一种叫做自动微分的技术。也就是说,它会有一个记录我们所有执行操作的记录器,之后再回放记录来计算我们的梯度。这一技术在构建神经网络时尤其有效,因为我们可以通过计算前路参数的微分来节省时间。
2、 对自动求导的初级认识
有一个函数y=x的平方,求它的导数。
x=torch.tensor(3.0,requires_grad=True)
y=torch.pow(x,2)
#判断x,y是否是可以求导的
print(x.requires_grad)
print(y.requires_grad)
#求导,通过backward函数来实现
y.backward()
#查看导数,也即所谓的梯度
print(x.grad)
(1)自动求导模块tensor的设置:
requires_grad属性表示该tensor是否可导。
需要注意的是,只有当所有的“叶子变量”,即所谓的leaf variable都是不可求导的,那函数y才是不能求导的。
下述代码可以解释:
#创建一个二元函数,即z=f(x,y)=x2+y2,x可求导,y设置不可求导
x=torch.tensor(3.0,requires_grad=True)
y=torch.tensor(4.0,requires_grad=False)
z=torch.pow(x,2)+torch.pow(y,2)
#判断x,y是否是可以求导的
print(x.requires_grad)
print(y.requires_grad)
print(z.requires_grad)
#求导,通过backward函数来实现
z.backward()
#查看导数,也即所谓的梯度
print(x.grad)
print(y.grad)
'''运行结果为:
True # x是可导的
False # y是不可导的
True # z是可导的,因为它有一个 leaf variable 是可导的,即x可导
tensor(6.) # x的导数
None # 因为y不可导,所以是none
'''
(2)函数的求导方法----y.backward()方法
函数定义:
backward(gradient=None, retain_graph=None, create_graph=False)
(3)查看函数求得的导数的值—x.grad属性
通过tensor的grad属性查看所求得的梯度值
总结:
总结:
(1)torch.tensor()设置requires_grad关键字参数
(2)查看tensor是否可导,x.requires_grad 属性
(3)设置叶子变量 leaf variable的可导性,x.requires_grad_()方法
(4)自动求导方法 y.backward() ,直接调用backward()方法,只会计算对计算图叶节点的导数。
(4)查看求得的到数值, x.gwarad 属性
易错点:
在tensor中要想让张量支持求导,就得让其为浮点类型。
3、求导的核心函数-----backward函数
3.1默认的求导规则
在pytorch里面,默认:只能是【标量】对【标量】,或者【标量】对向【量/矩阵】求导!这个很关键,很重要!
(1)标量对标量求导
(2)标量对向量/矩阵求导
一般我们求导是对损失函数求导,损失函数是一个标量,他是所有损失之和。而参数往往是一个向量或者矩阵。
3.2 向量/矩阵对向量/矩阵求导——通过backward的第一个参数gradient来实现
如果你想让不同的分量有不同的权重,从效果上来理解确实是这样子的,比如我是三个loss,loss1,loss2,loss3,它们的权重可能是不一样的,我们就可以通过它来设置。
**总结:**gradient参数的维度与最终的函数y保持一样的形状,每一个元素表示当前这个元素所对应的权
3.3自动求导函数backward的第二、第三个参数
(1)保留运算图——retain_graph
一个计算图在进行反向求导之后,为了节省内存,这个计算图就销毁了。 如果你想再次求导,就会报错。
(2)高阶导数——create_graph
(五)神经网络模块
使用非线性激活函数作为与线性模型的关键区别
介绍常用的激活函数
介绍包含神经网络构件的PyTorch的nn模块
用神经网络解决简单的线性拟合问题
1、神经元
从本质上讲,神经元不过是输入的线性变换(例如,输入乘以一个数[weight,权重],再加上一个常数[偏置,bias]),然后再经过一个固定的非线性函数(称为激活函数)。
之前的线性模型和深度学习模型之间最重要的区别就是:误差函数的形状。
2、激活函数
激活函数的作用:是将先前线性运算的输出聚集到给定范围内
(1)Hardtanh(torch.nn.Hardtanh):阶段输出值,它默认将输出值截断在(-1,1)之间。
(2)Sigmoid(torch.nn.Sigmoid):这些函数的曲线随着
x
x
x 趋于负无穷大而渐近地接近
0
0
0 或
−
1
-1
−1,随着
x
x
x 的增加而接近
1
1
1,并且在
x
=
0
x = 0
x=0 时具有大致恒定的斜率
这些函数可以让神经元对线性函数输出的中间值敏感,不必去关注那些很离奇的值。
3、Pytorch的nn模块
3.1线性模型
nn.Linear的构造函数接受三个参数:输入特征的数量,输出特征的数量以及线性模型是否包含偏差(此处默认为True)。
import torch.nn as nn
inear_model = nn.Linear(1, 1) # 参数: input size, output size, bias(默认True)
linear_model(t_un_val)
3.2使用神经网络来构建线性模型
重回温度计转化实例:
import torch
import torch.nn as nn
#将尺寸为B的输入reshape成B*Nin
t_c = [0.5, 14.0, 15.0, 28.0, 11.0, 8.0, 3.0, -4.0, 6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c).unsqueeze(1) # <1>
t_u = torch.tensor(t_u).unsqueeze(1) # <1>
#更新原来的训练代码,首先,将之前的手工模型替换为nn.Linear(1,1),然后将线性模型参数传递给优化器
linear_model = nn.Linear(1, 1)
optimizer = optim.SGD(
linear_model.parameters(),
lr=1e-2)
#可以使用parameters方法获取任何nn.Module或其子模块的参数列表:
linear_model.parameters()
#训练循环
def training_loop(n_epochs, optimizer, model, loss_fn,
t_u_train, t_u_val, t_c_train, t_c_val):
for epoch in range(1, n_epochs + 1):
t_p_train = model(t_un_train)
loss_train = loss_fn(t_p_train, t_c_train)
t_p_val = model(t_un_val)
loss_val = loss_fn(t_p_val, t_c_val)
optimizer.zero_grad()
loss_train.backward()
optimizer.step()
if epoch == 1 or epoch % 1000 == 0:
print('Epoch %d, Training loss %.4f, Validation loss %.4f' % (
epoch, float(loss_train), float(loss_val)))
linear_model = nn.Linear(1, 1)
optimizer = optim.SGD(linear_model.parameters(), lr=1e-2)
training_loop(
n_epochs=3000,
optimizer=optimizer,
model=linear_model,
loss_fn=nn.MSELoss(), # 不再使用自己定义的loss
t_u_train=t_un_train,
t_u_val=t_un_val,
t_c_train=t_c_train,
t_c_val=t_c_val)
print()
print(linear_model.weight)
print(linear_model.bias)
#nn提供了一种通过nn.Sequential容器串联模块的简单方法: