《深度学习框架PyTorch入门与实践》学习笔记---第三章 Tensor及Autograd

# -----------第三章 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

你可能感兴趣的:(深度学习,pytorch,深度学习,python)