# pytorch-损失函数

pytorch loss function.

## Cross Entropy

$$-\log P(X)$$

$$H(p) = -\sum_{i=1}^np_i\log p_i$$

$$H(p,q) = -\sum_{i=1}^np_i\log q_i$$

## BCELoss

Creates a criterion that measures the Binary Cross Entropy between the target and the output

$$sigmoid(x) = \dfrac{1}{1+e^{(-x)}}$$

y 是真实标签，$y\in {0,1}$, 那么对于每一个样本的概率为：

$$P(x_i, y_i)=P(y_i=1|x_i)^{y_i}P(y_i=0|x_i)^{1-y_i}$$

$$=P(y_i=1|x_i)^{y_i}(1-P(y_i=1|x_i))^{1-y_i}$$

$$-y_iP(y_i=1|x_i)-(1-y_i)(1-P(y_i=1|x_i))$$

$$loss(p,t)=−\dfrac{1}{N}\sum_{i=1}^{N}=\dfrac{1}{N}[t_i∗log(p_i)+(1−t_i)∗log(1−p_i)]$$

example:

## torch.nn.CrossEntropyLoss

This criterion combines nn.LogSoftmax() and nn.NLLLoss() in one single class.

It is useful when training a classification problem with C classes. If provided, the optional argument weight should be a 1D Tensor assigning weight to each of the classes. This is particularly useful when you have an unbalanced training set.

example:

## torch.nn.NLLloss

The negative log likelihood loss. It is useful to train a classification problem with C class.

input 是已经通过 log_softmax 层的输入。loss 是对应样本中真实标签对应的值的负数。

NLLloss

$$\ell(x, y) = L = {l_1,\dots,l_N}^\top, \quad l_n = - w_{y_n} x_{n,y_n}, \quad w_{c} = \text{weight}[c] \cdot \mathbb{1}{c \not= \text{ignore_index}}$$

example：

## MultiMarginLoss

$loss = \dfrac{1}{N}\sum_{j\ne y_i}^{N}max(0,s_j - s_{y_i}+\Delta)$

$s_{yi}$ 表示其真实标签对应的值，那么其他非真实分类的结果凡是大于 $s_{yi}−\Delta$ 这个值的，都对最后的结果 $loss$ 产生影响，比这个值小的就没事～

example:

## nn.L1loss

$$L1(\hat{y}, y)=\dfrac{1}{m}\sum|\hat{y}_i−y_i|$$

## nn.MSEloss

$$L2(\hat{y}, y)=\dfrac{1}{m}\sum|\hat{y}_i−y_i|^2$$

# pytorch-Tensor

## Tensor

1. torch.function，如torch.save等。

2. 另一类是tensor.function，如tensor.view等。

1. 不会修改自身的数据，如 a.add(b)， 加法的结果会返回一个新的tensor。

2. 会修改自身的数据，如 a.add_(b)， 加法的结果仍存储在a中，a被修改了。

|函数|功能|

|:—:|:—:|

|Tensor(*sizes)|基础构造函数|

|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)|随机排列|

• b.tolist() 把 tensor 转为 list

• b.numel() b 中元素总数，等价于 b.nelement()

• torch.Tensor(b.size()) 创建和 b 一样的 tensor

• 除了tensor.size()，还可以利用tensor.shape直接查看tensor的形状，tensor.shape等价于tensor.size()

tensor([[ 1.,  2.,  3.],

[ 4.,  5.,  6.]])

[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]

6

tensor(1.00000e-15 *

[[-3.4942,  0.0000,  0.0000],

[ 0.0000,  0.0000,  0.0000]])

tensor([ 2.,  3.])


### 常用Tensor操作

view, squeeze, unsqueeze, resize

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

tensorflow 里面是 tf.expand_dimtf.squeeze.

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

[ 3.,  4.,  5.]])

tensor([[ 0.,  1.,  2.],

[ 3.,  4.,  5.]])

tensor([[[ 0.,  1.,  2.]],

[[ 3.,  4.,  5.]]])

tensor([[[ 0.,  1.,  2.]],

[[ 3.,  4.,  5.]]])

tensor([[[[ 0.,  1.,  2.],

[ 3.,  4.,  5.]]]])

tensor([[ 0.,  1.,  2.],

[ 3.,  4.,  5.]])

tensor([[   0.,  100.,    2.],

[   3.,    4.,    5.]])

tensor([[   0.,  100.,    2.]])

tensor([[   0.0000,  100.0000,    2.0000],

[   3.0000,    4.0000,    5.0000],

[  -0.0000,    0.0000,    0.0000]])


### 索引操作

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

:—:|:—:|

index_select(input, dim, index)|在指定维度dim上选取，比如选取某些行、某些列

non_zero(input)|非0元素的下标

gather(input, dim, index)|根据index，在dim维度上选取数据，输出的size与index一样

gather是一个比较复杂的操作，对一个2维tensor，输出的每个元素如下：

#### index_select(input, dim, index) 指定维度上选取某些行和列, 返回的是某行和某列

tensor([[ 0.5948, -0.5760,  1.3726, -0.9664],

[ 0.5705,  1.0374, -1.1780,  0.0635],

[-0.1195,  0.6657,  0.9583, -1.8952]])

tensor(-0.5760)

##### 返回行的四种方式
tensor([[ 0.5705,  1.0374, -1.1780,  0.0635],

[-0.1195,  0.6657,  0.9583, -1.8952]])

tensor([[ 0.5705,  1.0374, -1.1780,  0.0635],

[-0.1195,  0.6657,  0.9583, -1.8952]])


tensor([[ 0.5705, 1.0374, -1.1780, 0.0635],

        [-0.1195,  0.6657,  0.9583, -1.8952]])

tensor([[[ 0.5705,  1.0374, -1.1780,  0.0635]],

[[-0.1195,  0.6657,  0.9583, -1.8952]]])

##### 返回列的两种方式
tensor([[-0.5760,  1.3726],

[ 1.0374, -1.1780],

[ 0.6657,  0.9583]])

tensor([[-0.5760,  1.3726],

[ 1.0374, -1.1780],

[ 0.6657,  0.9583]])


tensor([[ 0.3464,  1.4499,  0.7417, -1.9551],

[-0.0042, -0.0141,  1.2861,  0.0691],

[ 0.5843,  1.6635, -1.2771, -1.4623]])

tensor([ 0.3464,  1.4499,  0.7417,  1.2861,  0.0691,  0.5843,  1.6635])

tensor([ 0.3464,  1.4499,  0.7417,  1.2861,  0.0691,  0.5843,  1.6635])

tensor([[ 1,  1,  1,  0],

[ 0,  0,  1,  1],

[ 1,  1,  0,  0]], dtype=torch.uint8)

tensor([[  80,  235,  127,  167],

[ 199,   85,    0,    0],

[   0,    0,    0,    0]], dtype=torch.uint8)

tensor([ 0.3464,  1.4499,  0.7417, -1.9551, -0.0042, -0.0141])


#### gather(input, dim, index) 根据 index 在 dim 维度上选取数据，输出 size 与 index 一样.

tensor([[  0.,   1.,   2.,   3.,   4.],

[  5.,   6.,   7.,   8.,   9.],

[ 10.,  11.,  12.,  13.,  14.],

[ 15.,  16.,  17.,  18.,  19.]])

tensor([[ 0,  1,  2,  1,  3]]) torch.Size([1, 5])

tensor([[  0.,   6.,  12.,   8.,  19.]])


torch.Size([4, 1])

tensor([[  1.],

[  7.],

[ 13.],

[ 19.]])

##### list 转换成 one-hot 向量
tensor([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],

[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],

[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],

[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],

[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]])


#### Tensor 类型

Tensor有不同的数据类型，如表3-3所示，每种类型分别对应有CPU和GPU版本(HalfTensor除外)。默认的tensor是FloatTensor，可通过t.set_default_tensor_type 来修改默认tensor类型(如果默认类型为GPU tensor，则所有操作都将在GPU上进行)。Tensor的类型对分析内存占用很有帮助。例如对于一个size为(1000, 1000, 1000)的FloatTensor，它有1000*1000*1000=10^9个元素，每个元素占32bit/8 = 4Byte内存，所以共占大约4GB内存/显存。HalfTensor是专门为GPU版本设计的，同样的元素个数，显存占用只有FloatTensor的一半，所以可以极大缓解GPU显存不足的问题，但由于HalfTensor所能表示的数值大小和精度有限[^2]，所以可能出现溢出等问题。

:—:|:—:|:–:|

32-bit 浮点| torch.FloatTensor |torch.cuda.FloatTensor

64-bit 浮点| torch.DoubleTensor| torch.cuda.DoubleTensor

16-bit 半精度浮点| N/A |torch.cuda.HalfTensor

8-bit 无符号整形(0~255)| torch.ByteTensor| torch.cuda.ByteTensor

8-bit 有符号整形(-128~127)| torch.CharTensor |torch.cuda.CharTensor

16-bit 有符号整形 | torch.ShortTensor| torch.cuda.ShortTensor

32-bit 有符号整形 |torch.IntTensor |torch.cuda.IntTensor

64-bit 有符号整形 |torch.LongTensor |torch.cuda.LongTensor

• torch.set_sefault_tensor_type(‘torch.IntTensor)

#### 逐元素操作

|函数|功能|

|:–:|:–:|

|abs/sqrt/div/exp/fmod/log/pow..|绝对值/平方根/除法/指数/求余/求幂..|

|cos/sin/asin/atan2/cosh..|相关三角函数|

|ceil/round/floor/trunc| 上取整/四舍五入/下取整/只保留整数部分|

|clamp(input, min, max)|超过min和max部分截断|

|sigmod/tanh..|激活函数

$$y_i = \begin{cases} min, & \text{if } x_i \lt min \ x_i, & \text{if } min \le x_i \le max \ max, & \text{if } x_i \gt max\ \end{cases}$$

clamp常用在某些需要比较大小的地方，如取一个tensor的每个元素与另一个数的较大值。

tensor([[ 0.,  1.,  2.],

[ 3.,  4.,  5.]])

tensor([[ 3.,  3.,  3.],

[ 3.,  4.,  5.]])


#### 归并操作

|函数|功能|

|:—:|:—:|

|mean/sum/median/mode|均值/和/中位数/众数|

|norm/dist|范数/距离|

|std/var|标准差/方差|

|cumsum/cumprod|累加/累乘|

• 如果指定dim=0，输出的形状就是(1, n, k)或者(n, k)

• 如果指定dim=1，输出的形状就是(m, 1, k)或者(m, k)

• 如果指定dim=2，输出的形状就是(m, n, 1)或者(m, n)

size中是否有”1”，取决于参数keepdimkeepdim=True会保留维度1。注意，以上只是经验总结，并非所有函数都符合这种形状变化方式，如cumsum

tensor([[ 0.,  1.,  2.],

[ 3.,  4.,  5.]])

(tensor([ 3.,  5.,  7.]),

tensor([ 3.0000,  4.1231,  5.3852]),

tensor([ 3.0000,  4.0207,  5.1045]))


$||x||{p} = \sqrt[p]{x{1}^{p} + x_{2}^{p} + \ldots + x_{N}^{p}}$

##### torch.dist??

dist(input, other, p=2) -> Tensor

Returns the p-norm of (:attr:input - :attr:other)

tensor(2.)

tensor([ 0.7617,  1.0060,  1.6778])


#### 比较

|函数|功能|

|:–:|:–:|

|gt/lt/ge/le/eq/ne|大于/小于/大于等于/小于等于/等于/不等|

|topk|最大的k个数|

|sort|排序|

|max/min|比较两个tensor最大最小值|

• t.max(tensor)：返回tensor中最大的一个数

• t.max(tensor,dim)：指定维上最大的数，返回tensor和下标

• t.max(tensor1, tensor2): 比较两个tensor相比较大的元素

• max/min

• sort

• topk

tensor([[ 0.1845,  0.4101,  0.1470,  0.0083],

[ 0.7520,  0.8871,  0.9494,  0.2504],

[ 0.3879,  0.4554,  0.4080,  0.1703]])

(tensor([ 0.7326,  0.6784,  0.9791,  0.9011]), tensor([ 1,  2,  1,  1]))

(tensor([[ 0.1424,  0.5681,  0.1833,  0.1654],

[ 0.4556,  0.6418,  0.3242,  0.5120],

[ 0.7326,  0.6784,  0.9791,  0.9011]]), tensor([[ 2,  0,  0,  2],

[ 0,  1,  2,  0],

[ 1,  2,  1,  1]]))

(tensor([[ 0.7326,  0.6784,  0.9791,  0.9011],

[ 0.4556,  0.6418,  0.3242,  0.5120]]), tensor([[ 1,  2,  1,  1],

[ 0,  1,  2,  0]]))


#### 线性代数

PyTorch的线性函数主要封装了Blas和Lapack，其用法和接口都与之类似。常用的线性代数函数如表3-7所示。

|函数|功能|

|:—:|:—:|

|trace|对角线元素之和(矩阵的迹)|

|diag|对角线元素|

|triu/tril|矩阵的上三角/下三角，可指定偏移量|

|mm/bmm|矩阵乘法，batch的矩阵乘法|

|t|转置|

|dot/cross|内积/外积

|inverse|求逆矩阵

|svd|奇异值分解

tensor([[ 0.8260,  1.3392,  0.5944],

[ 1.3392,  2.7192,  1.0062],

[ 0.5944,  1.0062,  0.6130]])

torch.Size([3, 4]) torch.Size([4, 3])

tensor([[ 0.8260,  1.3392,  0.5944],

[ 1.3392,  2.7192,  1.0062],

[ 0.5944,  1.0062,  0.6130]])

False

(tensor([[ 0.4556,  0.7326,  0.1424],

[ 0.5681,  0.6418,  0.6784],

[ 0.1833,  0.9791,  0.3242],

[ 0.5120,  0.9011,  0.1654]]), tensor([ 0.4556,  0.6418,  0.3242]))

tensor([[ 0.0000,  1.5959, -0.2253,  0.2349, -0.5151],

[ 0.0000,  0.0000, -0.0366, -0.0867,  0.2737],

[ 0.0000,  0.0000,  0.0000,  0.9904, -1.4889],

[ 0.0000,  0.0000,  0.0000,  0.0000, -1.1053],

[ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000]])


### Tensor和Numpy

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

float64

array([[1., 1., 1.],

[1., 1., 1.]])

torch.FloatTensor

tensor([[   1.,  100.,    1.],

[   1.,    1.,    1.]])

torch.DoubleTensor

tensor([[ 1.,  1.,  1.],

[ 1.,  1.,  1.]], dtype=torch.float64)

tensor([[ 1.,  1.,  1.],

[ 1.,  1.,  1.]])

tensor([[   1.,  100.,    1.],

[   1.,    1.,    1.]], dtype=torch.float64)


Numpy的广播法则定义如下：

• 让所有输入数组都向其中shape最长的数组看齐，shape中不足的部分通过在前面加1补齐

• 两个数组要么在某一个维度的长度一致，要么其中一个为1，否则不能计算

• 当输入数组的某个维度的长度为1时，计算时沿此维度复制扩充成一样的形状

PyTorch当前已经支持了自动广播法则，但是笔者还是建议读者通过以下两个函数的组合手动实现广播法则，这样更直观，更不易出错：

• unsqueeze或者view：为数据某一维的形状补1，实现法则1

• expand或者expand_as，重复数组，实现法则3；该操作不会复制数组，所以不会占用额外的空间。

tensor([[[ 1.,  1.],

[ 1.,  1.],

[ 1.,  1.]],

[[ 1.,  1.],

[ 1.,  1.],

[ 1.,  1.]]])

tensor([[[ 1.,  1.],

[ 1.,  1.],

[ 1.,  1.]],

[[ 1.,  1.],

[ 1.,  1.],

[ 1.,  1.]]])


### 内部结构

tensor的数据结构如图3-1所示。tensor分为头信息区(Tensor)和存储区(Storage)，信息区主要保存着tensor的形状（size）、步长（stride）、数据类型（type）等信息，而真正的数据则保存成连续数组。由于数据动辄成千上万，因此信息区元素占用内存较少，主要内存占用则取决于tensor中元素的数目，也即存储区的大小。

tensor([ 0.,  1.,  2.,  3.,  4.,  5.])

 0.0

1.0

2.0

3.0

4.0

5.0

[torch.FloatStorage of size 6]

 0.0

1.0

2.0

3.0

4.0

5.0

[torch.FloatStorage of size 6]

True

 0.0

1.0

2.0

3.0

4.0

5.0

[torch.FloatStorage of size 6]

True

(tensor([[   0.,  100.,    2.],

[   3.,    4.,    5.]]), tensor([ 0.,  1.,  2.,  3.,  4.,  5.]))

(139719200619016, 139719200619016)

tensor([ 2.,  3.,  4.,  5.])

0.0

100.0

2.0

3.0

4.0

5.0

[torch.FloatStorage of size 6]

(94551854283064, 94551854283056)

tensor([   0.,  100., -100.,    3.,    4.,    5.])

tensor([[ 6666.,   100.,  -100.],

[    3.,     4.,     5.]])

True

(0, 2, 0)

True

False


#### 持久化

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

tensor([ 6666.,   100.,  -100.,     3.,     4.,     5.], device='cuda:0')


#### 向量化

351 µs ± 9.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

The slowest run took 16.46 times longer than the fastest. This could mean that an intermediate result is being cached.

4.24 µs ± 7.12 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)


• 大多数torch.function都有一个参数out，这时候产生的结果将保存在out指定tensor之中。

• torch.set_num_threads可以设置PyTorch进行CPU多线程并行计算时候所占用的线程数，这个可以用来限制PyTorch所占用的CPU数目。

• torch.set_printoptions可以用来设置打印tensor时的数值精度和格式。

tensor([[-0.3306640089, -0.0507176071, -0.4223535955],

[-0.8678948879, -0.0437202156, 0.0183448847]])


#### 线性回归

$$loss = \sum_i^N \frac 1 2 ({y_i-(wx_i+b)})^2$$

tensor(2.0264241695) tensor(2.9323694706)