目录
第2章 预备知识
2.1 获取和运行本书的代码
2.1.1 获取代码并安装运行环境
1.Windows用户
2.Linux/macOS用户
2.1.2 更新代码和运行环境
conda env update -f environment.yml
2.1.3 使用GPU版的MXNet
2.2 数据操作
2.2.1 创建NDArray
书中的代码是基于mxnet来教学的,但由于我在电脑上没有能够正常安装mxnet,就用基于pytorch来实现书中的代码吧。
import torch
x = torch.arange(12)
print(x)
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
我们用arange函数创建了一个行向量,包含了从0开始的12个连续整数。
我们还可以通过shape属性来获取该实例的形状。
print(x.shape)
我们可以使用reshape函数把行向量x的形状改为(2,6),也就是一个2行6列的矩阵。
X = x.reshape((2, 6))
print(X)
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
注意,X属性中的形状发生了变化。上面x.reshape((3, 4))也可写成x.reshape((-1, 4))或x.reshape((3, -1))。由于x的元素个数是已知的,这里的-1是能够通过元素个数和其他维度的大小推断出来的。
X = x.reshape((-1, 6))
print(X)
X = x.reshape((2, -1))
print(X)
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
接下来创建一个各元素为0,形状为(2, 3, 4)的张量。
X = torch.zeros((2, 3, 4))
print(X)
tensor([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
类似地,我们可以创建各元素为1的张量。
X = torch.ones((2, 3, 4))
print(X)
tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
我们也可以通过list指定需要创建的张量中的每个元素的值。
Y = torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(Y)
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])
有些情况下,我们需要随机生成张量中每个元素的值。下面我们创建一个形状为(3, 4)的张量,它的每个元素都随机采样于均值为0、标准差为1的正态分布。
X = torch.randn(3, 4)
print(X)
tensor([[ 0.1779, -0.2801, 1.1669, 0.8222],
[ 0.4816, -2.8687, 1.0481, 0.6222],
[-0.9931, -1.9082, -0.8441, -0.5925]])
注意torch.rand和torch.randn的区别,一个返回的是一个均匀分布,一个返回的是标准正态分布:
torch.rand(*sizes, out=None) → Tensor
返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义。
参数:
sizes (int...) - 整数序列,定义了输出张量的形状
out (Tensor, optinal) - 结果张量
torch.randn(*sizes, out=None) → Tensor
返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义。
参数:
sizes (int...) - 整数序列,定义了输出张量的形状
out (Tensor, optinal) - 结果张量
2.2.2 运算
我们可以对之前创建的两个形状为(3,4)的张量做按元素加法,所得结果形状不变。
print(X + Y)
tensor([[ 0.5903, 0.5494, 4.5199, 3.6252],
[ 3.4962, 2.0545, 1.0886, 4.8041],
[ 3.0556, 3.3181, 1.9155, -0.0453]])
按元素乘法:
print(X * Y)
tensor([[-2.8193, -0.4506, 2.0798, 1.8755],
[ 2.4962, 0.1089, -5.7341, 3.2165],
[-3.7775, 0.9542, -0.1690, -1.0453]])
按元素除法:
print(X / Y)
tensor([[-0.2021, -0.6203, -0.2410, 0.7119],
[-1.4841, 0.6173, 0.4943, -0.1018],
[ 0.2762, -0.2819, -0.4036, -0.7872]])
按元素做指数运算:
print(Y.exp())
tensor([[ 7.3891, 2.7183, 54.5981, 20.0855],
[ 2.7183, 7.3891, 20.0855, 54.5981],
[54.5981, 20.0855, 7.3891, 2.7183]])
使用dot函数做矩阵乘法:
print(torch.dot(Y, X.T))
RuntimeError: 1D tensors expected, but got 2D and 2D tensors
这里程序运行报错,查了一下才知道,torch.dot只针对一维张量进行计算,于是改用torch.matmul
print(torch.matmul(X, Y.T))
tensor([[ 2.2096, 1.4280, -0.8171],
[-7.1253, -6.1242, -4.4061],
[ 3.1927, 3.4753, -0.5196]])
我们也可以将多个张量连结,torch.cat与torch.concat效果相同。
print(torch.cat((X, Y), 0))
print(torch.concat((X, Y), 1))
tensor([[ 1.5513, 1.0073, -0.9179, 2.7961],
[ 0.3576, 2.3224, 0.2562, -0.4383],
[ 0.4400, -1.4319, 0.5050, -0.4000],
[ 2.0000, 1.0000, 4.0000, 3.0000],
[ 1.0000, 2.0000, 3.0000, 4.0000],
[ 4.0000, 3.0000, 2.0000, 1.0000]])
tensor([[ 1.5513, 1.0073, -0.9179, 2.7961, 2.0000, 1.0000, 4.0000, 3.0000],
[ 0.3576, 2.3224, 0.2562, -0.4383, 1.0000, 2.0000, 3.0000, 4.0000],
[ 0.4400, -1.4319, 0.5050, -0.4000, 4.0000, 3.0000, 2.0000, 1.0000]])
使用条件判别式判断两个相同形状的张量的对应位置上的元素是否相同。
print(X == Y)
tensor([[False, False, False, False],
[False, False, False, False],
[False, False, False, False]])
对张量中的所有元素求和得到只有一个元素的张量。
print(X.sum())
tensor(4.8047)
2.2.3 广播机制
前面我们看到如何对两个形状相同的张量做按元素运算,当对两个形状不同的张量按元素运算时,可能会出发广播机制:先适当复制元素使这两个张量形状相同后再按元素运算。
import torch
A = torch.arange(3).reshape((3, 1))
B = torch.arange(2).reshape((1, 2))
C = A + B
print(A)
print(B)
print(C)
tensor([[0],
[1],
[2]])
tensor([[0, 1]])
tensor([[0, 1],
[1, 2],
[2, 3]])
由于A和B分别是3行1列和1行2列的矩阵,如果要计算A+B,那么A中第一列的3个元素被广播(复制)到了第二列,而B中第一行的2个元素被广播(复制)到了第二行和第三行。如此,就可以对2个3行2列的矩阵按元素相加。
2.2.4 索引
索引代表了元素的位置,从0开始逐一递增。
依据左闭右开指定范围的惯例,下面的例子截取了矩阵X中行索引为0的一行。
print(C[0:1])
tensor([[0, 1]])
我们也可以指定张量中需要访问的单个元素的位置,如矩阵中行和列的索引,并未该元素重新赋值。
C[1, 1] = 9
print(C)
tensor([[0, 1],
[1, 9],
[2, 3]])
当然,我们也可以截取一部分元素,并为它们重新赋值。在下面的例子中,我们为行索引为1的每一列元素重新赋值。
C[1:2, :] = 12
print(C)
tensor([[ 0, 1],
[12, 12],
[ 2, 3]])
2.2.5 运算的内存开销
在前面的例子里我们对每个操作新开内存来存储运算结果,即使像Y = X + Y这样的运算,我们也会新开内存,然后将Y指向新内存。为了演示这一点,我们可以使用Python自带的id函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。
before = id(C)
C = C + 1
print(id(C) == before)
False
如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。 下面我们先通过zeros_like创建和C形状相同且元素为0的张量,记为D。
D = torch.zeros_like(C)
before = id(D)
D[:] = A + B
print(id(D) == before)
实际上,上例中我们还是为A + B开了临时内存来存储计算结果,再复制到D对应的内存。如果想避免这个临时内存开销,我们可以使用运算符全名函数中的out参数。
(这一点暂未亲自实现,跳过)
如果X的值在之后的程序中不会复用,我们也可以用X[:] = X + Y或者X += Y来减少运算的内存开销。
before = id(C)
C += B
print(id(C) == before)
True
2.2.6 tensor和numpy相互变换
tensor to numpy
import torch
import numpy as np
P = torch.ones((2, 3))
print(P)
D = P.numpy()
print(D)
tensor([[1., 1., 1.],
[1., 1., 1.]])
[[1. 1. 1.]
[1. 1. 1.]]
除chartensor外所有tensor都可以转换为numpy。
numpy to tensor
a = [2., 2., 2., 2.]
b = torch.tensor(a)
print(b)
tensor([2., 2., 2., 2.])
2.3 自动求梯度
2.3.1 简单例子
(2022.03.08更新。今天是在受不了在mxnet(书上教学用的是这个)和pytorch之间的来回倒腾了,在anaconda里装了mxnet(之前装了好几次失败了,这次查了不少资料安装成功!!!)。)
我们先看一个简单例子:对函数y = 2xTx求关于列向量x的梯度。我们先创建变量x,并赋初值。
import torch
x = torch.arange(4).reshape((4, 1))
print(x)
为了求有关变量x的梯度,我们需要先调用attach_grad函数来申请存储梯度所需要的内存。
x.attach_grad()
下面定义有关变量x的函数。为了减少计算和内存开销,默认条件下MXNet不会记录用于求梯度的计算,我们需要调用record函数来要求MXNet记录与梯度有关的计算。
import mxnet
from mxnet import nd
from mxnet import autograd
x = nd.arange(4).reshape((4, 1))
print(x)
x.attach_grad()
with autograd.record():
y = 2 * nd.dot(x.T, x)
y.backward()
assert (x.grad - 4 * x).norm().asscalar() == 0
print(x.grad)
2.3.2 训练模式和预测模式
从上面可以看出,在调用record函数后,MXNet会记录并计算梯度。此外,默认情况下autograd还会将运行模式从预测模式转为训练模式。
print(autograd.is_training())
with autograd.record():
print(autograd.is_training())
2.3.3 对Python控制流求梯度
使用MXNet的一个便利之处是,即使函数的计算图包含了Python的控制流(如条件和循环控制),我们也有可能对变量求梯度。
import mxnet
from mxnet import nd
from mxnet import autograd
def f(a):
b = a * 2
while b.norm().asscalar() < 1000:
b = b * 2
if b.sum().asscalar() > 0:
c = b
else:
c = 100 * b
return c
a = nd.random.normal(shape=1)
a.attach_grad()
with autograd.record():
c = f(a)
c.backward()
print(a.grad == (c / a))
[1.]
<NDArray 1 @cpu(0)>
小结:
1.MXNet提供autograd模块来自动化求导过程;
2.MXNet的autograd模块可以对一般的命令式程序进行求导;
3.MXNet的运行模式包括训练模式和预测模式,我们可以通过autograd.is_training()来判断运行模式。
2.4 查阅文档
2.4.1 查找模块里的所有函数和类
当我们想知道一个模块里面提供了哪些可以调用的函数和类的时候,可以使用dir函数。
from mxnet import nd
print(dir(nd.random))
['NDArray', '_Null', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_internal', '_random_helper', 'current_context', 'exponential', 'gamma', 'generalized_negative_binomial', 'multinomial', 'negative_binomial', 'normal', 'numeric_types', 'poisson', 'shuffle', 'uniform']
2.4.2 查找特定函数和类的使用
想了解某个函数或者类的具体用法时,可以使用help函数。
from mxnet import nd
help(nd.ones_like)
Help on function ones_like:
ones_like(data=None, out=None, name=None, **kwargs)
Return an array of ones with the same shape and type
as the input array.
Examples::
x = [[ 0., 0., 0.],
[ 0., 0., 0.]]
ones_like(x) = [[ 1., 1., 1.],
[ 1., 1., 1.]]
Parameters
----------
data : NDArray
The input
out : NDArray, optional
The output NDArray to hold the result.
Returns
-------
out : NDArray or list of NDArrays
The output of this function.
x = nd.array([[0, 0, 0], [2, 2, 2]])
y = x.ones_like()
print(y)
[[1. 1. 1.]
[1. 1. 1.]]
<NDArray 2x3 @cpu(0)>
2.4.3 在MXNet网站上查阅
https://mxnet.apache.org/
(好像比较难连上)