# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: 'Python 3.8.8 64-bit (''pytorch_cpu'': conda)'
#     name: python3
# ---

from __future__ import print_function
import torch as t
t.__version__

# # 第三章 PyTorch基础：Tensor和Autograd
#
# ## 3.1 Tensor
#
# Tensor，又名张量，读者可能对这个名词似曾相识，因它不仅在PyTorch中出现过，它也是Theano、TensorFlow、
# Torch和MxNet中重要的数据结构。关于张量的本质不乏深度的剖析，但从工程角度来讲，可简单地认为它就是一个数组，且支持高效的科学计算。它可以是一个数（标量）、一维数组（向量）、二维数组（矩阵）和更高维的数组（高阶数据）。Tensor和Numpy的ndarrays类似，但PyTorch的tensor支持GPU加速。
#
# 本节将系统讲解tensor的使用，力求面面俱到，但不会涉及每个函数。对于更多函数及其用法，读者可通过在IPython/Notebook中使用函数名加`?`查看帮助文档，或查阅PyTorch官方文档[^1]。
#
# [^1]: http://docs.pytorch.org

# ###  3.1.1 基础操作
#
# 学习过Numpy的读者会对本节内容感到非常熟悉，因tensor的接口有意设计成与Numpy类似，以方便用户使用。但不熟悉Numpy也没关系，本节内容并不要求先掌握Numpy。
#
# 从接口的角度来讲，对tensor的操作可分为两类：
#
# 1. `torch.function`，如`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
#
# 在PyTorch中新建tensor的方法有很多，具体如表3-1所示。
#
# 表3-1: 常见新建tensor的方法
#
# |函数|功能|
# |:---:|:---:|
# |Tensor(\*sizes)|基础构造函数|
# |tensor(data,)|类似np.array的构造函数|
# |ones(\*sizes)|全1Tensor|
# |zeros(\*sizes)|全0Tensor|
# |eye(\*sizes)|对角线为1，其他为0|
# |arange(s,e,step|从s到e，步长为step|
# |linspace(s,e,steps)|从s到e，均匀切分成steps份|
# |rand/randn(\*sizes)|均匀/标准分布|
# |normal(mean,std)/uniform(from,to)|正态分布/均匀分布|
# |randperm(m)|随机排列|
#
# 这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu).
#
#
# 其中使用`Tensor`函数新建tensor是最复杂多变的方式，它既可以接收一个list，并根据list的数据新建tensor，也能根据指定的形状新建tensor，还能传入其他的tensor，下面举几个例子。

# 指定tensor形状
a = t.Tensor(2, 3)  # 提前分配好空间
a   # 数值取决于内存空间的状态，

# 使用list数据创建tensor
b = t.Tensor([[1, 2, 3], [4, 5, 6]])
b

b.tolist()   # 和numpy的转化何其相似

# `tensor.size()`返回`torch.Size`对象，它是tuple的子类，但其使用方式与tuple略有区别

b_size = b.size()
b_size

b.numel()  #  b中元素总个数，2*3，等价于b.nelement()

# 创建一个与b形状一样的tensor
c = t.Tensor(b.size())
# 创建一个元素为2和3的tensor
d = t.Tensor((2, 3))   # 注意和t.Tensor(2, 3)完全不一样！
c, d

c.shape   # equal to c.size()

# 需要注意的是，`t.Tensor(*sizes)`创建tensor时，系统不会马上分配空间，只是会计算剩余的内存是否足够使用，使用到tensor时才会分配，而其它操作都是在创建完tensor之后马上进行空间分配。其它常用的创建tensor的方法举例如下。

t.ones(2, 3)

print(t.zeros(2, 3))
print(t.arange(1, 20, 4))
print(t.linspace(1, 10, 3), '\n', t.linspace(1, 10, 9))

print(t.randn(2, 3, device=t.device('cpu')))

t.randperm(5)  # 长度为5的随机排列

print(t.eye(4, 2, dtype=t.int))
t.eye(2, 3, dtype=t.int)  # 对角线为1， 不要求行列数一致

# `torch.tensor`是在0.4版本新增加的一个新版本的创建tensor方法，使用的方法，和参数几乎和`np.array`完全一致

scalar = t.tensor(3.14159)
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)) # 向量

tensor = t.Tensor(1, 2)  # 注意与t.tensor([1, 2])区别
tensor.shape

matrix = t.tensor([[0.1, 1.2], [2.2, 3.1], [4.9, 5.2]])
matrix, matrix.shape

empty_tensor = t.tensor([])
empty_tensor.shape

# 通过`tensor.view`方法可以调整tensor的形状，但必须保证调整前后元素总数一致。`view`不会修改自身的数据，返回的新tensor与源tensor共享内存，也即更改其中的一个，另外一个也会跟着改变。在实际应用中可能经常需要添加或减少某一维度，这时候`squeeze`和`unsqueeze`两个函数就派上用场了。

a = t.arange(0, 6)
a.view(2, 3)

b = a.view(-1, 3)
b.shape

b.unsqueeze(1)  # 注意形状，在第1维（下标从0开始）上增加“1”
# 等价于b[, None]
b[:, None].shape

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之后的，也会跟着修改

# `resize`是另一种可用来调整`size`的方法，但与`view`不同，它可以修改tensor的大小。如果新大小超过了原大小，会自动分配新的内存空间，而如果新大小小于原大小，则之前的数据依旧会被保存，看一个例子。

# +
# help(t.squeeze)
# -

b.resize_(1, 3)
b

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

# #### 索引操作
#
# Tensor支持与numpy.ndarray类似的索引操作，语法上也类似，下面通过一些例子，讲解常用的索引操作。如无特殊说明，索引出来的结果与原tensor共享内存，也即修改一个，另一个会跟着修改。

a = t.randn(3, 4)
a

a[0]  # 第0行（下标从0开始）

a[:, 0]  # 第0列

a[0, 2], a[0][2]

a[:2]  # 前两行

a[:2, 0:2] # 前两行， 第0，1列

print(a[0:1, :2])  # 第0行， 前两列
print(a[0, :2])  # 注意两者区别，形状不同（这个是直接取的第一行的0，1列，上面那个是取的第0行第1行（虽然取不到）的前两列

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

a[None].shape   # 等价于a[None, :, :]

a[:, None, :].shape

a[:, None, :, None, None].shape

a > 1  # 返回一个ByteTensor

a[a>1]  # 等价于a.masked_select(a>1)
# 选择结果与原tensor不共享内存空间

a[a>0.5]

# 其它常用的选择函数如表3-2所示。
#
# 表3-2常用的选择函数
#
# 函数|功能|
# :---:|:---:|
# index_select(input, dim, index)|在指定维度dim上选取，比如选取某些行、某些列
# masked_select(input, mask)|例子如上，a[a>0]，使用ByteTensor进行选取
# non_zero(input)|非0元素的下标
# gather(input, dim, index)|根据index，在dim维度上选取数据，输出的size与index一样
#
#
# `gather`是一个比较复杂的操作，对一个2维tensor，输出的每个元素如下：
#
# ```python
# out[i][j] = input[index[i][j]][j]  # dim=0
# out[i][j] = input[i][index[i][j]]  # dim=1
# ```
# 三维tensor的`gather`操作同理，下面举几个例子。

a = t.arange(0, 16).view(4, 4)
a

# 选取对角线上的元素
index = t.LongTensor([[0, 1, 2, 3]])
a.gather(0, index)

# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t()
a.gather(1, index)

# 选取反对角线上的元素， 注意与上边的不同
index = t.LongTensor([[3, 2, 1, 0]])
a.gather(0, index)

# 选取两个对角线上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t()
b = a.gather(1, index)
b

# 与`gather`相对应的逆操作是`scatter_`，`gather`把数据从input中按index取出，而`scatter_`是把取出的数据再放回去。注意`scatter_`函数是inplace操作。
#
# ```python
# out = input.gather(dim, index)
# -->近似逆操作
# out = Tensor()
# out.scatter_(dim, index)
# ```

# +
# 
# -

# # tensor操作还有很多，暂时没有继续看下去的兴趣了，目前打算实现一下简单的一个线性回归

# 请见下一个文档linreg.ipynb

StarcoderdataJupyterScriptsDedupFiltered