# -----------第三章 PyTorch基础:Tensor和Autograd-----------
# ---------3.1 Tensor张量-------------------
# 工程角度来讲,可以是一个数(标量),一维数组(向量),二维数组(矩阵),更高维的数组,(高阶数组)
# Tensor和Numpy的narrays类似,但PyTorch的tensor支持GPU加速
# 如果新版本里有新的功能呢特性,这个特性与单签版本不兼容,则使用需要从future模块导入
from __future__ import print_function
import torch as t # 导入并重命名
t.__version__ # 查看当前torch版本
print(t.__version__)
# --------3.1.1 Tensor基础操作----------------------
# 从接口的角度,对tensor的操作可分为两类:
# 1、torch.funtion,如torch.save等;
# 2、tensor.function,如tensor.view等;
# 对tensor的大部分操作同时支持这两类接口,torch.sum(torch.sum(a,b));
# tensor.sum(a.sum(b))等价
# 从操作角度,对Tensor的操作有可分为两类:
# 1、不会修改自身的数据,如a.add(b),加法的结果会返回一个新的tensor
# 2、会修改自身的数据,如a.add_(b),加法的结果仍存储在a中,a被修改了
# 函数名以_结尾都是Inplace方式,即会修改调用自己的数据
# Tensor(*size)是基础构造函数,tensor(data)
# 使用Tensor函数新建tensor是最复杂多变的方式,既可以接受一个List,
# 并根据list的数据新建tensor,也能根据指定的形状新建tensor,还能传入其他的tensor
# 指定tensor的形状
a = t.Tensor(2, 3)
print(a) # 数值取决于内存空间的状态,可能会overflow
# 用List数据创建tensor
b = t.Tensor([[1, 2, 3], [4, 5, 6]])
print(b)
# 把tensor转化为List
print(b.tolist()) # 把tensor转为List
b_size = b.size()
print(b_size)
print(b.numel(), b.nelement()) # b中元素总个数#6 6
# 创建一个和b形状一样的tensor
c = t.Tensor(b.size())
d = t.Tensor((2, 3))
print(c, d)
# tensor.size()==tensor.shape
print(c.shape)
# 需要注意:t.Tensor(*sizes)创建tensor时,系统不会马上分配空间,
# 只是会计算剩余的内存是否足够使用,使用到tensor才会分配内存,
# 而其他操作都是创建完tensor之后马上进行空间分配
# 其他常用的创建tensor方法举例如下:
print(t.ones(2, 3))
print(t.zeros(2, 3))
print(t.arange(1, 6, 2)) # start,end,step【1,3,5】
print(t.linspace(1, 10, 3)) # start,end,等分个数【1,5,10】
print(t.randn(2, 3, device=t.device('cpu'))) # 标准正态分布,大小为2,3,
print(t.randperm(5)) # 长度为5的随机排列,随机打乱一个数字序列,其内的参数决定了随机数的范围
print(t.eye(2, 3, dtype=t.int)) # 对角矩阵,大小为2,3
scalar = t.tensor(3.14159) # scalar是一个数的张量tensor
print('scalar:%s,shape of scalar:%s' % (scalar, scalar.shape))
vector = t.tensor([1, 2])
print('vector:%s,shape of vector:%s' % (vector, vector.shape))
# 注意:t.tensor和t.Tensor的区别
print(t.tensor([1, 2]), t.Tensor(3, 4))
matrix = t.tensor([[0.1, 0.2], [0.3, 0.4], [4.9, 5.2]])
print(matrix, matrix.shape)
ftensor = t.tensor([[0.11111, 0.2222, 0.333333]],
dtype=t.float64, device=t.device('cpu'))
print(ftensor)
empty_tensor = t.tensor([])
print(empty_tensor.size()) # torch.size([0])
# ---------3.1.2 Tensor操作--------------------------
# view重构张量大小
# 通过tensor.view可以调整tensor的形状,但必须保证调整前后元素总数一致
# view不会修改自身的数据,返回的新的tensor与源tensor共享内存,更改一个均会改变
# 在实际应用中可能需要添加或减少某一维度,squeeze和unsqueeze
a = t.arange(0, 6) # start,end
print(a, a.view(2, 3)) # view不会修改自身的值
b = a.view(3, 2)
b[1, 1] = 10
print(a, b) # 返回的新的tensor与源tensor共享内存,更改一个均会改变
b = a.view(-1, 3)
print(b.shape) # [2,3]当某一维维-1时候,会自动计算其大小
# unsqueeze改变维度
b.unsqueeze(1) # 注意形状,在第1维(下标从0开始)上增加“1”
# 等价于b[:,None]
print(b[:, None].shape) # [2,1,3]
print(b.unsqueeze(-2)) # -2表示倒数第二个维度#[[[0,1,2]], ,[[10,4,5]]]
print(b.unsqueeze(-3)) # [[[0,1,2]],[[10,4,5]]]
c = b.view(1, 1, 1, 2, 3)
print(c.squeeze(0)) # 压缩第0维# [[[[ 0, 1, 2], [10, 4, 5]]]]
print(c.squeeze()) # 把所有维度为“1”的压缩([[ 0, 1, 2],[10, 4, 5]])
a[1] = 100
print(b) # a修改,b作为view之后的也会跟着修改
# resize是另一种可用来调整size的方法,但是它可以修改tensor的大小,
# 如果新大小超过了原大小,会自动分配新的内存空间,
# 如果新大小小于原大小,则之前的数据依旧会被保存
print(b.resize_(1, 3)) # [[ 0, 100, 2]]
print(b.resize_(3, 3)) # [[ 0, 100, 2],
# [ 10, 4, 5],
# [4908972408073642310, 7587550992672910697, 7308604895907509363]]
# 可以看到,原大小数据仍被保存,多出的大小会分配新空间
# ----------3.1.3 索引操作-------------------------------
# 索引出来的结果与原tensor共享内存,修改一个,另一个也会跟着修改
a = t.randn(3, 4) # 标准正态分布,大小为3,4
print(a)
print(a[0]) # 第0行,(下标从0开始)
print(a[:, 0]) # 第0列
print(a[0][2]) # 第0行第2个元素,等价于a[0,2],横纵下标均从0开始
print(a[0, -1]) # 第0行最后一个元素
print(a[:2]) # 前两行
print(a[:2, 0:2]) # 前两行,第0,1列
print(a[0:1, :2]) # 第0行,前两列
print(a[0, :2]) # 同上
# 为a增加了一个轴,等价于a.view(a,a.shape[0],a.shape[1])
# 等价于a[None,:,:]
print(a[None].shape) # torch.size([1,3,4])
print(a[:, None, :].shape) # torch.Size([3, 1, 4])
print(a[:, None, :, None, None].shape) # torch.Size([3, 1, 4, 1, 1])
print(a > 1)
# tensor([[False, False, False, False],
# [ True, False, False, True],
# [False, False, False, False]])
print(a[a > 1]) # 输出大于1的元素
# tensor([1.6015, 1.0503])
# -------------------选择函数gather-----------------
# gather是一个比较复杂的操作,对一个2维tensor,输出的每个元素如下:
a = t.arange(0, 16).view(4, 4)
print(a)
# 选取对角线元素
index = t.LongTensor([[0, 1, 2, 3]])
print(a.gather(0, index)) # tensor([[ 0, 5, 10, 15]])
# dim=0,按行索引,行为index
# dim=1,按列索引,列为index
# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t() # .t()转置
print(a.gather(1, index))
# 取反对角线上的元素,注意与上面不同
index = t.LongTensor([[3, 2, 1, 0]])
print(a.gather(0, index))
# 选取两个对角线上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t()
b = a.gather(1, index)
print(b)
# ([[ 0, 3],
# [ 5, 6],
# [10, 9],
# [15, 12]])
# --------------scatter逆操作--------------------------
# 与gather对应的逆操作是scatter_,gather把数据从input中按index取出,
# scatter——是把取出的数据再放回去,注意scatter_函数是inplace操作
# out=input.gather(dim,,index)
# out=Tensor()
# out.scatter_(dim,index)
# 把两个对角线元素放回去到指定位置
c = t.zeros(4, 4)
print(c.scatter_(1, index, b.float()))
# tensor([[ 0., 0., 0., 3.],
# [ 0., 5., 6., 0.],
# [ 0., 9., 10., 0.],
# [12., 0., 0., 15.]])
# -------------tensor.item()元素取值---------------------
# 对tensor的任何索引操作仍是一个tensor,想要获取标准的python对象数值,
# 需要调用tensor.item(),这个方法只对包含一个元素的tensor适用
print(a[0, 0]) # 依旧是tensor,tensor[0]
print(a[0, 0].item()) # 0
d = a[0:1, 0:1, None]
print(d.shape) # torch.size([1,1,1])
print(d.item()) # 0,#只包含一个元素的tensor即可调用tensor.item()与形状无关
# ----------高级索引-----------------------
x = t.arange(0, 27).view(3, 3, 3)
print(x)
print(x[[1, 2], [1, 2], [2, 0]]) # x[1,1,2],x[2,2,0]
print(x[[2, 1, 0], [0], [1]]) # x[2,0,1],x[1,0,1],x[0,0,1]
print(x[[0, 2], ...]) # x[0],x[2]
# ------------3.1.3 Tensor类型-----------------------------------------------------------
# Tensor有不同的数据类型,每种类型都对应有CPU和GPU版本,
# 默认的tensor是FloatTensor,通过t.set_default_tensor_type来修改tensor类型
# 各数据类型之间可以相互转换,type(new_type)使通用的做法,
# CPU tensor与GPU tensor之间的互相转换通过tensor.cuda与tensor.cpu方法实现
# 还可以使用tensor.to(device)
# Tensor还有一个new方法,与t.Tensor一样,会调用该tensor对应类型的构造函数,
# 生成与当前tensor类型一致的tensor,
# 设置默认tensor,注意参数是字符串
t.set_default_tensor_type('torch.DoubleTensor')
a = t.Tensor(2, 3)
print(a.dtype) # torch.float64
# 恢复之前的默认设置
t.set_default_tensor_type('torch.FloatTensor')
# 把a转为FloatTensor,等价于b=a.type(t.FloatTensor)
b = a.float()
print(b.dtype) # torch.float32
c = a.type_as(b)
print(c.dtype) # torch.float32
print(a.new(2, 3)) # 等价于torch.DoubleTensor(2,3),a.new_tensor
print(a.new_tensor)
print(t.zeros_like(a)) # 等价于t.zeros(a.shape,dtype=1.dtype,device=a.device)
print(t.zeros_like(a, dtype=t.int16)) # 修改某些属性
print(t.rand_like(a))
print(a.new_ones(4, 5, dtype=t.int)) # 全1矩阵,大小为4*5,元素类型为int
print(a.new_tensor([3, 4]))
# ---------------3.1.4 逐元素操作----------------------------------
a = t.arange(0, 6).view(2, 3).float()
print(t.cos(a))
print(a % 3, "\n", t.fmod(a, 3))
print(a ** 2, "\n", t.pow(a, 2))
# 取a中每个元素与3相比较大的一个,小于3的截断为3
print(a)
print(t.clamp(a, min=3))
b = a.sin_()
print(b)
# ----------归并操作------------------------
# 使输出操作小于输入形状,并可以沿着某一维度进行制定操作
# 制定在那个dim操作,哪个dim就没有了,keepdim=1,维度保留为1
# mean均值,sum和,median中位数,mode众数,norm范数,dist距离
# std标准差,var方差
# cumsum/cumprod累加/累乘
b = t.ones(2, 3)
print(b, "\n", b.sum(dim=0, keepdim=True).shape) # [[2,2,2]],torch.size[1,3]
print(b.sum(dim=0, keepdim=False).size()) # [2,2,2],torch.size[3]
a = t.arange(0, 6).view(2, 3)
print(a, "\n", a.cumsum(dim=1)) # 沿着行累加
# tensor([[0, 1, 2],
# [3, 4, 5]])
# tensor([[ 0, 1, 3],
# [ 3, 7, 12]])
# --------比较--------------------------------------
# max 有以下三种使用情况:
# (1)t.max(tensor)返回tensor中最大的一个数
# (2)t.max(tensor,dim)指定维度上最大数,返回tensor和下标
# (3)t.max(tensor1,tensor2)比较两个tensor相比较大的元素
# 至于比较一个tensor和一个数,可以用clamp函数
a = t.linspace(0, 15, 6).view(2, 3)
print(a)
b = t.linspace(15, 0, 6).view(2, 3)
print(b)
print(a > b) # 运算符重载
print(a[a > b]) # 返回a中大于b的元素#[9,12,15]
print(t.max(a)) # 返回a中最大的元素#tensor(15.)
print(b, "\n", t.max(b, dim=1))
# values=tensor([15,6])分别表示第0行和第1行的最大的元素
# indices=tensor([0,0])表示是该行的第0个元素
print(t.max(a, b)) # 输出的是最大值
# tensor([[15., 12., 9.],
# [ 9., 12., 15.]])
# 比较a和比10大的元素
print(t.clamp(a, min=10))
# tensor([[10., 10., 10.],
# [10., 12., 15.]])
# ---------------------3.1.5 线性代数----------------------------------------------
# trace-对角线元素之和(矩阵的迹)
# diag-对角线元素,triu/tril-矩阵的上三角/下三角,可指定偏移量
# mm/bmm-矩阵乘法,batch的矩阵乘法
# t-转置,dot-内积,外积-cross,inverse-求逆矩阵,svd-奇异值分解
print(a, "\n", a.t())
# 转置会共享同一内存,使用continguous断开,强制拷贝一份tensor
# b=a.t().contiguous()#断开联系
b = a.t() # 此时a,b对应位置均发生改变
b[1, 1] = 100
print(a, "\n", b)
# ---------------3.1.6 Tensor和Numpy---------------------------------------------
# Numpy和Tensor共享内存,由于Numpy历史悠久,支持丰富的操作,当遇到Tensor不支持的操作,
# 可先转成Numpy数组,处理后再转回tensor,其转换开销很小
import numpy as np
a = np.ones([2, 3], dtype=np.float32)
print(a)
# [[1. 1. 1.]
# [1. 1. 1.]]
b = t.from_numpy(a)
print(b)
# tensor([[1., 1., 1.],
# [1., 1., 1.]])
b = t.Tensor(a) # 可以直接将numpy对象传入Tensor
print(b)
a[0, 1] = 100
print(b) # 同时发生改变,同享同一内存
# tensor([[ 1., 100., 1.],
# [ 1., 1., 1.]])
c = b.numpy() # Tensor转Numpy,此时a,b,c三个对象共享内存
print(c)
# [[ 1. 100. 1.]
# [ 1. 1. 1.]]
# 注意:当numpy的数据类型与Tensor不同,数据会被复制,但不会共享内存
a = np.ones([2, 3])
print(a, "\n", a.dtype) # float64
b = t.Tensor(a) # 此处进行拷贝,但不共享内存
print(b.dtype) # torch.float32
c = t.from_numpy(a)
print(c) # c的类型为(DoubleTensor)
# tensor([[1., 1., 1.],
# [1., 1., 1.]], dtype=torch.float64)
a[0, 1] = 100
print(a, "\n", b, "\n", c) # 此时b不会改变,没有共享内存,c改变
# 注意:无论输入的类型是什么,t.tensor都会进行数据拷贝,不会共享内存
tensor = t.tensor(a)
tensor[0, 0] = 0
print(tensor, "\n", a) # a不会被改变
# ------广播法则(broadcast)-----------------------
# 快速执行向量化的同时不会占用额外的内存/显存
# Numpy的广播法则定义如下
# 1、让输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐
# 2、两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
# 3、当输入数组的某个维度的长度,计算时沿此维度复制扩充成一样的形状
# 注意,建议通过以下两个函数的组合手动实现广播法则,更直观不易出错
# 1、unsqueeze或者view,或者tensor[None],为数据某一维的形状补1,实现法则1
# 2、expand或者expand_as,重复数组,实现法则3,该操作不会复制数组,所以不会占用额外的空间
# 注意,repeat和expand有类似的功能,但是repeat会把相同的数据复制多份,因此会占用额外的空间
a = t.ones(3, 2)
b = t.zeros(2, 3, 1) # 第3维,行,列
print(a, "\n", b)
# 自动广播法则
# 第一步:a是2维,b是3维,所以现在较小的a前面补1
# 即,a.unsqueeze(0),a的形状变为(1,3,2),b的形状为(2,3,1)
# 第二步:a和b在第一维和第三维形状不一样,其中一个为1
# 可以利用广播法则扩展,两个形状都变为(2,3,2)
print(a + b)
# 手动广播法则
# 或者a.view(1,3,2).expand(2,3,2)+b.expand(2,3,2)
print(a[None].expand(2, 3, 2) + b.expand(2, 3, 2))
# expand不会占用额外的空间,只是在需要的时候才扩充,可极大节省内存
# -------------3.1.7 内部结构----------------------------------------------
# tensor分为头信息区(Tensor)和存储区(Storage),信息区保存tensor的形状(size)
# 步长(stride),数据类型(type)等信息,真正的数据则保存成连续数组
# 一个tensor对应一个storage,storage是在data之上的封装的接口,而不同的tensor的头信息一般不同
# 却可能使用相同的数据
a = t.arange(0, 6)
print(a.storage())
b = a.view(2, 3)
print(b.storage())
# 一个对象的id值可以看做它在内存中地址
# storage的内存地址一样,即是同一个storage
print(id(b.storage()) == id(a.storage())) # Ture
c = a[2:]
print(a, "\n", c)
print(c.storage()) # c与a还是用一块内存
print(c.data_ptr(), a.data_ptr()) # data_ptr返回tensor首元素的内存地址
print(c.dtype, a.dtype) # int64
# 2427098682896 2427098682880
# 相差16bit,2个字节,每个元素,1个字节,8bit
# 高级索引一般不共享storage,普通索引共享storage
# 普通索引可以通过只修改tensor的offset,stride和size,而不修改sotrage来实现
# ----------------3.1.8 Tensor的其他话题---------------------
# ----------GPU/CPU-----------------
# Tensor可以随意在GPU/CPU上传输,使用tensor.cuda(device_id)或者tensor.cpu()
# 另外通用的方法是tensor.to(device)
a = t.randn(3, 4) # 正态分布,大小为3,4
print(a.device) # cpu
if t.cuda.is_available():
a = t.randn(3, 4, device=t.device('cuda:1'))
# 等价于a.t.randn(3,4).cuda(1),但是前者更快
print(a.device)
device = t.device('cpu')
print(a.to(device))
# tensor([[ 1.8459, 0.5042, 0.3884, -0.7908],
# [-0.4362, 0.3278, 0.1421, 1.0480],
# [-2.2168, -0.3536, 0.5261, 0.0537]])
# 注意:尽量使用tensor.to(device),将device设为一个可配置的参数,轻松使得程序同事兼容GPU和CPU
# 数据在GPU之中传输的速度远快于内存(CPU)到显存(GPU),所以尽量避免频繁在内存和显存中传输数据
# ----------持久化--------------------------
if t.cuda.is_available():
a = a.cuda(1) # 把a转为GPU1上的tensor
t.save(a, 'a.path')
# 加载为b,存储在GPU1上,因为保存时tensor就在GPU1上
b = t.load('a.path')
# 加载为c,存储在CPU
c = t.load('a.path', map_location=lambda storage, loc: storage)
# 加载为d,存储在CPU0上
d = t.load('a.path', map_location={'cuda:1': 'cuda:0'})
# ------向量化-------------------------
# 向量化是一种特殊的并行计算方式,同一时间执行多个操作
# 极力避免Python原生的for循环
def for_loop_add(x, y):
result = []
for i, j in zip(x, y):
result.append(i + j)
return t.Tensor(result)
x = t.zeros(100)
y = t.ones(100)
# %timeit -n 10 for_loop_add(x,y)
# %timeit -n 10 x+y
# -------------3.1.9 线性回归-------------------------
# 导入库
# import torch as t
# %matplotlib inline
# 需要在代码的最后加入
# plt.show()
from IPython import display
# device = t.device('cpu')
# 如果想用gpu,改成t.device('cuda:0')
# 设置随机数种子,保证在不同电脑上运行时下面的输出一致
# t.manual_seed(1000)
# def get_fake_data(batch_size=8):
# """产生随机数据:y=x*2+3,加上噪声"""
# x = t.rand(batch_size, 1, device=device)
# y = x * 2 + 3 + t.randn(batch_size, 1, device=device)
# return x, y
# 来看看产生的x-y分布
# x, y = get_fake_data(batch_size=16)
# plt.scatter(x.squeeze(), )
Autograd