第三章 Pytorch基础:3.1 Tensor

3.1 Tensor

3.1.1.  基础操作:

1. 创建Tensor:

 (1)Tensor是最复杂多变的方式,既可以接收一个list,并据其数据新建tensor,也可以根据制定的形状新建tensor,还能传入其他tensor;

(2)b.tolist可以吧tensor b 转化为list;

(3)一些命令:

a = t.tensor(2, 3)
a.size                #输出torch.size([2,3])
a.numel()             #输出 6
c = t.Tensor(b_size)
c.shape

t.ones(2, 3)
t.zeros(2, 3)
t.arrange(1, 6, 2)
t.linspace(1, 10, 3)
t.randn(2, 3, device=t.device('cpu'))]
t.randperm(5)            # 长度为5的随机排列
t.eye(2, 3, dtype=t.int) # 对角线为1, 不要求行列数一致

2. 常用tensor操作:

(1)view、unsqueeze、squeeze:

b = a.view(-1, 3) # 当某一维为-1的时候,会自动计算它的大小
b.unsqueeze(1) # 注意形状,在第1维(下标从0开始)上增加“1” 
b.unsqueeze(-2) # -2表示倒数第二个维度

c = b.view(1, 1, 1, 2, 3)
c.squeeze(0) # 压缩第0维的“1”
c.squeeze() # 把所有维度为“1”的压缩

a[1] = 100
b # a修改,b作为view之后的,也会跟着修改

 (2)resize:与view不同,它可以修改tensor的大小,如果新大小超过了原大小,则自动分配新的内存空间;如果新大小小于原大小,则之前的数据依旧会被保存:

b.resize_(1, 3)
b

b.resize_(3, 3) # 旧的数据依旧保存着,多出的大小会分配新空间
b

3. 索引操作:

***如无特殊说明,索引出来的结果与原tensor共享内存,修改一个另一个也会跟着改变。***

(1)普通索引:

a = t.randn(3, 4)

a[0] # 第0行(下标从0开始)
a[:, 0] # 第0列
a[0][2] # 第0行第2个元素,等价于a[0, 2]
a[0, -1] # 第0行最后一个元素
a[:2] # 前两行
a[:2, 0:2] # 前两行,第0,1列

print(a[0:1, :2]) # 第0行,前两列 
print(a[0, :2]) # 注意两者的区别:形状不同
#tensor([[-1.1001,  0.7252]])
#tensor([-1.1001,  0.7252])

# None类似于np.newaxis, 为a新增了一个轴
# 等价于a.view(1, a.shape[0], a.shape[1])
a[None].shape                            # torch.Size([1, 3, 4])
a[None].shape                            # 等价于a[None,:,:]
a[:,None,:].shape                        # torch.Size([3, 1, 4])
a[:,None,:,None,None].shape              # torch.Size([3, 1, 4, 1, 1])


a > 1                                    # 返回一个ByteTensor
a[a>1]                                   # 等价于a.masked_select(a>1),且选择结果与原tensor不共享内存空间
a[t.LongTensor([0,1])]                   # 第0行和第1行

 (2)选择函数:

        其中,gather函数比较复杂:gather(input, dim, index),在dim的维度上,选取与index一样的值,且index的维度要与input的相符。

(2-1)当dim=0时,需要从做往右看,有“列数”个样本,再选取每个样本由上往下的第index个数据:

a = torch.tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]])
index = torch.tensor([0,1,2,2])
a.gather(dim=0,index)            #输出为:tensor([[0, 5, 10, 11]])
a.gather(dim=0,index.t())        #输出为:tensor([[0], [1], [2], [2]])

(2-2)当dim=1时,需要从上往下看,有“行数”个样本,再选取每个样本由左往右2的第index个数据:

a = torch.tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]])
index = torch.tensor([0,1,2,2])
a.gather(dim=1,index)            #输出为:tensor([[0, 1, 2, 2]])
a.gather(dim=1,index.t())        #输出为:tensor([[0], [5], [10], [14]])

(2-3) scatter_函数则与gather相反,需要把取出来的数据按照index的位置放回去。

c = t.zeros(4,4)
c.scatter_(1, index, b.float())

#tensor([[ 0.,  0.,  0.,  3.],
#        [ 0.,  5.,  6.,  0.],
#        [ 0.,  9., 10.,  0.],
#        [12.,  0.,  0., 14.]])

 (2-4)对tensor的索引操作结果仍然是一个tensor,需要调用tensor.item( ),可以将只包含一个元素的tensor的数据提取出来,且不用管tensor的形状(只要只有一个元素就可以用)。

(3)高级索引:

x = t.arange(0,27).view(3,3,3)
x[[1, 2], [1, 2], [2, 0]]                 # x[1,1,2]和x[2,2,0]
x[[2, 1, 0], [0], [1]]                    # x[2,0,1],x[1,0,1],x[0,0,1]
x[[0, 2], ...]                            # x[0] 和 x[2]

(4) 生成与其他tensor类似属性(类型,形状等)的tensor:tensor 默认是32bit的,占用4Byte

c = a.type_as(b)                #如果类型相同,则不作改变;否则将其变为和b一样类型的tensor
a.new(2,3)                      # 等价于torch.DoubleTensor(2,3),建议使用a.new_tensor
t.zeros_like(a)                 #等价于t.zeros(a.shape,dtype=a.dtype,device=a.device)
t.rand_like(a)
a.new_ones(4,5, dtype=t.int)
a.new_tensor([3,4])

 4. 逐元素操作

5. 归并操作

注:dim=0表示“跨行”进行操作;dim=1表示“跨列”进行操作。

 6. 比较

7. 线性代数

注: 矩阵的转置会导致存储空间不连续,需要调用它的.contiguous方法使其连续。

3.1.2 Tensor和Numpy

        Tensor和Numpy数组之间具有很高的相似性,彼此之间的互操作也非常简单高效。需要注意的是,Numpy和Tensor共享内存。由于Numpy历史悠久,支持丰富的操作,所以当遇到Tensor不支持的操作时,可先转成Numpy数组,处理后再转回tensor,其转换开销很小。

1. 互相转换:

import numpy as np
a = np.ones([2, 3],dtype=np.float32)
b = t.from_numpy(a)
b = t.Tensor(a)                         # 也可以直接将numpy对象传入Tensor
a[0, 1]=100
b                                       # b的值会跟着a的值变化而变化
c = b.numpy()                           # a, b, c三个对象共享内存

注:当Numpy和Tensor的类型不一样的时候,数据会被复制,但不会共享内存:

a = np.ones([2, 3])        # 注意此时a是64bit
a.dtype

b = t.Tensor(a)            # 此处进行拷贝,不共享内存,b是32bit的
b.dtype

c = t.from_numpy(a)        # c也是64bit

a[0, 1] = 100
b                          # b与a不共享内存,所以b不会变
c                          # c与a共享内存,所以c会变

注:t.tensor与t.Tensor不同,t.tensor不论输入的类型是什么,都只会进行拷贝,而不会共享内存。 

 2. 广播法则:

(1)三个法则:

  • 让所有数组都向其中最长的数组看齐,shape中不足的部分通过在前面加1补齐
  •  两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
  • 当输入数组某个维度的长度为1时,计算时沿此维度复制扩充为一样的形状

(2)手动广播法则的方法:

  • unsqueeze或者view,或者tensor[None]:为数据某一维的形状补1,实现法则1
  • expand或者expand_as,重复数组,实现法则3;该操作不会复制数组,故不会占用额外的空间
a = t.ones(3, 2)
b = t.zeros(2, 3,1)

# 自动广播法则
# 第一步:a是2维,b是3维,所以先在较小的a前面补1 ,
#               即:a.unsqueeze(0),a的形状变成(1,3,2),b的形状是(2,3,1),
# 第二步:   a和b在第一维和第三维形状不一样,其中一个为1 ,
#               可以利用广播法则扩展,两个形状都变成了(2,3,2)
a+b

# 手动广播法则
# 或者 a.view(1,3,2).expand(2,3,2)+b.expand(2,3,2)
a[None].expand(2, 3, 2) + b.expand(2,3,2)

3.1.3  内部结构

(1)tensor的数据结构包括信息区(Tensor)和存储区(Storage),信息区保存着形状(size)、步长(stride)、数据类型(type) 等信息,真正的数据则保存成连续数组。由于数据可能很大,故主要内存占用取决于tensor中元素的数目,也即存储区的大小。

(2)另外,一般来说一个tensor有着与之对应的storage,所以不同的tensor头信息可能不同,但是可能使用相同的数据。

 (3)storage_offset命令是指tensor的第一个数据,是从连续存储的数据区中的第几个位置开始;tensor.data_ptr( )可以返回tensor首元素的内存地址。

3.1.4 其他

1. GPU/CPU转换:

***使用tensor.cuda(device_id)或者tensor.cpu()可随意在gpu/cpu上传输,另一个通用的方法是tensor.to(device)

a = t.randn(3, 4)
a.device                            # device(type='cpu')

if t.cuda.is_available():
    a = t.randn(3,4, device=t.device('cuda:0'))
    # 等价于
    # a.t.randn(3,4).cuda(0)
    # 但是前者更快
    a.device

device = t.device('cpu')
a.to(device)

注:

  • 尽量使用`tensor.to(device)`, 将`device`设为一个可配置的参数,这样可以很轻松的使程序同时兼容GPU和CPU
  • 数据在GPU之中传输的速度要远快于内存(CPU)到显存(GPU), 所以尽量避免频繁的在内存和显存中传输数据。

2. 持久化

        Tensor的保存和加载十分的简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的`pickle`模块,在load时还可将GPU tensor映射到CPU或其它GPU上。

if t.cuda.is_available():
    a = a.cuda(0) # 把a转为GPU0上的tensor,
    t.save(a,'a.pth')

    # 加载为b, 存储于GPU1上(因为保存时tensor就在GPU0上)
    b = t.load('a.pth')
    # 加载为c, 存储于CPU
    c = t.load('a.pth', map_location=lambda storage, loc: storage)
    # 加载为d, 存储于GPU1上
    d = t.load('a.pth', map_location={'cuda:0':'cuda:1'})

3. 向量化

        向量化可在同一时间执行多个操作,通常是对不同的数据执行同一个或一批命令,提高运算效率,避免使用for循环。

(1)时间节省实示例:

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

# 运算结果为:
# 497 µs ± 31.2 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# The slowest run took 13.83 times longer than the fastest. This could mean that an intermediate result is being cached.
# 5.75 µs ± 9.06 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

(2)其他注意点:

  • 大多数't.function'函数有一个参数"out",用于存储产生的结果;
  • t.set_num_threads 可以设置Pytorch进行CPU多线程并行计算时所占用的线程数,可以限制占用CPU的数目
  • t.set_printoptions 可以用来设置打印tensor时的数值精度和格式
a = t.arange(0, 20000000)
print(a[-1], a[-2])          # 32bit的IntTensor精度有限导致溢: tensor(19999999) tensor(19999998)      
b = t.LongTensor()
t.arange(0, 20000000, out=b) # 64bit的LongTensor不会溢出
b[-1],b[-2]                  # (tensor(19999999), tensor(19999998))


a = t.randn(2,3)
a
t.set_printoptions(precision=10)
a

3.1.5 线性回归示例

        线性回归利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的,其表达形式为y = wx+b+e,e为误差服从均值为0的正态分布,其损失函数为:

        采用随机梯度下降法更新参数w和b来最小化损失函数,最终学习得到w和b的数值

import torch as t
%matplotlib inline
from matplotlib import pyplot as plt
import matplotlib
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) * 5
    y = x * 2 + 3 +  t.randn(batch_size, 1, device=device)
    return x, y


x, y = get_fake_data(batch_size=16)
plt.scatter(x.squeeze().cpu().numpy(), y.squeeze().cpu().numpy())    # 查看产生的x-y分布


# 随机初始化参数
w = t.rand(1, 1).to(device)
b = t.zeros(1, 1).to(device)

lr =0.02 # 学习率

for ii in range(500):
    x, y = get_fake_data(batch_size=4)
    
    # forward:计算loss
    y_pred = x.mm(w) + b.expand_as(y) # x@W等价于x.mm(w);for python3 only
    loss = 0.5 * (y_pred - y) ** 2 # 均方误差
    loss = loss.mean()
    
    # backward:手动计算梯度
    dloss = 1
    dy_pred = dloss * (y_pred - y)
    
    dw = x.t().mm(dy_pred)
    db = dy_pred.sum()
    
    # 更新参数
    w.sub_(lr * dw)
    b.sub_(lr * db)
    
    if ii%50 ==0:
       
        # 画图
        display.clear_output(wait=True)
        x = t.arange(0, 6).view(-1, 1)
        y = x.float().mm(w) + b.expand_as(x)
        plt.plot(x.cpu().numpy(), y.cpu().numpy()) # predicted
        
        x2, y2 = get_fake_data(batch_size=32) 
        plt.scatter(x2.numpy(), y2.numpy()) # true data
        
        plt.xlim(0, 5)
        plt.ylim(0, 13)
        plt.show()
        plt.pause(0.5)
        
print('w: ', w.item(), 'b: ', b.item())

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值