1 转变
1.1 前言
在之前的机器学习中,我们都是讲解完知识,然后使用sklearn提供的API,然后疯狂传参调参。但是实际上底层的逻辑,比如它们为什么能这样,为什么有这样的效果,我们是不知道的。
在深度学习中就不一样了,我们必须掌握好数学这个画笔,用它规划出我们想要的神经网络。而对于颜料来说,各种深度学习框架已经提供了我们所需的各种颜料。我们要做的,就是利用不同的颜料,在空白的纸上,一笔一划画出我们所需的网络。
深度学习
改变了传统互联网业务。第一次听到这个名词时可能大家都会对这方面的知识感到一头雾水,到底什么是深度学习?实际上,深度学习已经应用到生活中的点点滴滴了,比如我们熟知的自动无人驾驶,小爱同学音箱和其他的一些人工智能产品。在这个笔记中,你可以无需任何视频直接从头看到尾,也可以搭配任何一个深度学习的课程视频进行观看,当然,除了里面的代码部分,其他的对于所有的深度学习框架是通用的,代码部分主要用的是pytorch框架来书写的。
1.2 基本元素
在深度学习中,实际上最基本的元素就是张量。所以如何利用pytorch来创建一个张量成为我们第一个问题。在后面的学习中,我会告诉你为何张量很重要。
1.2.1 张量
在机器学习中,我们谈论过了线性代数的部分知识,当时我们讲到,0维叫标量,1维叫向量,2维叫矩阵,2维以上叫多维矩阵。
而实际上,在torch中其赋予了这些维度一个统一的名词,即张量
。如果你学过Numpy,张量实际上就相当于Numpy中的adarray。由此我们不禁疑问,为何torch还要搞自己的这套方法呢?直接用Numpy的不好吗?
在大部分的学习框架中实际上都有属于自己的方法来创建张量,这是因为深度学习一般用来处理大量的数据,而仅仅用电脑的CPU硬件已经不能满足我们深度学习的算力了,为此,我们需要使用GPU来加速我们的算法,而Numpy是不支持GPU加速的,而深度学习框架的张量可以。
换而言之,在深度学习框架中的张量一般都可以很好地支持GPU加速运算,而Numpy仅仅支持CPU计算。
1.2.2 创建张量
在Pytorch中,张量的英文继承于TensorFlow深度学习框架中的张量Tensor。如果我们要利用最简单的方法来创建一个tensor,可以用列表来创建,如下:
import torch
tensor = torch.FloatTensor([[1, 2, 3], [4, 5, 6]])
print(f"张量为:{tensor}")
out:
张量为:tensor([[1., 2., 3.],
[4., 5., 6.]])
实际上,torch提供了创建不同数据类型张量的方法,上述的是创建一个浮点数张量,所以无独有偶,我们可以尝试把Float改成Int,其可以为我们创建一个Int类型的常量。
torch.IntTensor([[1,2,3],[4,5,6]])
当然类似于Numpy,在Numpy中有ones,zeros等方法创建全1和全0向量,在torch也可以这么做。
torch.IntTensor(2,4).zero_()
torch.zeros((2,3,4))
torch.ones((2,3,4))
利用python的索引和切片,我们可以获取和修改一个张量中的任意一个元素。
A = torch.IntTensor([[1,2,3],[4,5,6]])
print(A[1][2])
前面我们不是说过可以利用GPU来加速张量吗?详情可以见下表:
Data type | CPU tensor | GPU tensor |
---|---|---|
32-bit floating point | torch.FloatTensor |
torch.cuda.FloatTensor |
64-bit floating point | torch.DoubleTensor |
torch.cuda.DoubleTensor |
16-bit floating point | N/A | torch.cuda.HalfTensor |
8-bit integer (unsigned) | torch.ByteTensor |
torch.cuda.ByteTensor |
8-bit integer (signed) | torch.CharTensor |
torch.cuda.CharTensor |
16-bit integer (signed) | torch.ShortTensor |
torch.cuda.ShortTensor |
32-bit integer (signed) | torch.IntTensor |
torch.cuda.IntTensor |
64-bit integer (signed) | torch.LongTensor |
torch.cuda.LongTensor |
上面这表格里面包含的玩意就别背了,我都背不下来,没事多用用,忘记查就行,上面的方法还挺有规律的不是?
我们可能在平时学习python时不怎么关注内存,但是在深度学习中内存也是十分重要的。为此,torch提供了节省内存的方法。如你想对A张量的元素全部做绝对值操作,那么abs方法可以在原有的张量基础上在创建一个张量用于存放新的计算后的张量。而使用abs_方法则是在原有的张量上直接操作,把原有的张量做绝对值操作后直接覆盖。
就地操作
可以节省一些内存,但由于会立即丢失历史记录,因此在计算导数时可能会出现问题。因此,不鼓励使用它们。
1.2.3 操纵张量
张量有了,我们就得会各种各样的操作,其中不乏就是加减乘除。为了下面的学习,我们当然需要先会一些操作了。
1.2.3.1 张量乘法
张量乘法就是我们在机器学习中谈到的矩阵——矩阵乘法
。在pytorch中,我们使用mm(matrix multiplication)
或matmul
方法来做这么一件事情。
import torch
A = torch.arange(12).reshape(3, 4)
B = torch.arange(12).reshape(4, 3)
print(f"两矩阵乘积为:{torch.mm(A, B)}")
print(f"两矩阵乘积为:{torch.matmul(A, B)}")
out:
两矩阵乘积为:tensor([[ 42, 48, 54],
[114, 136, 158], [186, 224, 262]])
两矩阵乘积为:tensor([[ 42, 48, 54],
[114, 136, 158], [186, 224, 262]])
如果是两个一维张量即向量相乘时,我们还可以使用dot方法。
import torch
A = torch.arange(12)
B = torch.arange(12)
print(f"两向量点积为:{torch.dot(A, B)}")
out:
两向量点积为:506
1.2.3.2 哈达玛积
如果你并不想做矩阵乘法,而是想要让两个矩阵对应元素相乘,那么使用运算符*
即可达到这种效果。
import torch
A = torch.arange(12)
B = torch.arange(12)
print(A)
print(B)
print(f"哈达玛积为:{A*B}")
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])
哈达玛积为:tensor([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121])
1.2.3.3 降维
emm,实际上我觉得可以稍微偷懒,先把数据用sklearn中的PCA降维先降维掉,或者用特征选取先整好。当然深度学习它们不允许我这么干,它们通常都是把矩阵中的某些数据往轴上合并。如求和、求平均啥的。
python的内置函数sum()允许我们对张量内的元素求和。当前我们可以发现,通过求和,本来是一维张量,变成了0维的张量。
import torch
x = torch.arange(4, dtype=torch.float32)
print(f"x:{x}")
print(f"x_sum:{x.sum()}")
out:
x:tensor([0., 1., 2., 3.])
x_sum:6.0
明显地,这实际上就是一种降维的方法,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量;一维可以变0维,那么说明二维也能变一维,我们可以指定沿着哪个轴来通过求和降低维度。
import torch
A = torch.arange(12).reshape(3, 4)
A_sum_axis0 = A.sum(axis=0)
print(f"A_sum_axis0:{A_sum_axis0}")
print(f"A_sum_axis0_shape:{A_sum_axis0.shape}")
out:
A_sum_axis0:tensor([12, 15, 18, 21])
A_sum_axis0_shape:torch.Size([4])
对比矩阵A,我们可以发现如果沿着0轴(行)求和,那么实际上就是把该列所有的元素全部相加,加到该列的第0个元素上去。
同样的,如果指定axis = 1,那么将沿着列求和,那么实际上就是把每行的所有元素相加,加到该行的第0个元素上去。
A = torch.arange(12).reshape(3, 4)
A_sum_axis1 = A.sum(axis=1)
print(f"A_sum_axis1:{A_sum_axis1}")
print(f"A_sum_axis1_shape:{A_sum_axis1.shape}")
out:
A_sum_axis1:tensor([ 6, 22, 38])
A_sum_axis1_shape:torch.Size([3])
axis = 0按照行,可以理解为把“行”给抹去只剩1行,也就是上下压扁。
axis = 1按照列,可以理解为把“列”给抹去只剩1列,也就是左右压扁。
当然,如果你又对行又对列求和,那么实际上就是对矩阵的所有元素求和。
import torch
A = torch.arange(12).reshape(3, 4)
print(A.sum(axis=[0, 1])) # 相当于 A.sum()
out:
tensor(66)
也许有人说不喜欢通过求和加到某条轴上这种方式去降维,那你可以选择通过求平均值然后把平均值写在某条轴上来降维。求平均值有两种方法,一种是调用python内置函数mean(),另外一种就是用sum()/numel,这实际上也是一种求平均值的方法,但是,我相信你不会那么蠢选择后者是吧。
1.2.3.4 转置
用张量自带的T方法即可完成转置。
import torch
A = torch.arange(12).reshape(3,4)
print(A)
print(A.T)
out:
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7], [ 8, 9, 10, 11]])
tensor([[ 0, 4, 8],
[ 1, 5, 9], [ 2, 6, 10], [ 3, 7, 11]])
1.2.3.5 范数
如果要计算一个向量的$L_2$范数,可以使用norm来计算。
import torch
u = torch.tensor([3.0, -4.0])
print(torch.norm(u))
out:
tensor(5.)
如果是$L_1$范数,他表示为向量元素的绝对值之和。这么说,我们求$L_1$范数可以这么求:先求每个元素的绝对值,然后再求和。
import torch
u = torch.tensor([3.0, -4.0])
print(torch.abs(u).sum())
out:
tensor(7.)
1.2.3.6 拼接张量
如果想要拼接n个张量,可以使用torch.cat方法。其中dim = 0,则n个张量按竖轴拼接;若dim = 1,则按横轴拼接。
import torch
tensor = torch.arange(4).reshape(2, 2)
t1 = torch.cat([tensor, tensor, tensor], dim=0)
print(t1)
out:
tensor([[0, 1],
[2, 3], [0, 1], [2, 3], [0, 1], [2, 3]])
1.2.4 张量属性
我们用shape查看张量形状,用dtype查看张量数据类型,用device查看张量存储设备。
import torch
tensor = torch.rand(3, 4)
print(f"shape of tensor:{tensor.shape}")
print(f"shape of tensor:{tensor.dtype}")
print(f"Device tensor is stored on:{tensor.device}")
out:
shape of tensor:torch.Size([3, 4])
shape of tensor:torch.float32
Device tensor is stored on:cpu
如果你的张量中只有一个元素,即单元素张量
,那么使用tensor.item()方法可以使其从tensor类型变为int、float等类型。具体是什么类型看张量中的单元素而定。
import torch
A = torch.arange(12).reshape(3,4)
agg = A.sum()
agg_item = agg.item()
print(agg_item,type(agg_item))
out:
66
1.2.5 张量和Numpy
1.2.5.1 张量变Numpy
如果想要使一个Tensor变为adarray,则调用tensor自身的numpy方法即可。
import torch
t = torch.ones(5)
print(t)
n = t.numpy()
print(n)
out:
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]
1.2.5.2 Numpy变张量
如果想要使一个adarray变为Tensor,则调用torch的from_numpy方法即可。
import torch
import numpy as np
n = np.ones(5)
t = torch.from_numpy(n)
print(t)
out:
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
1.3 后话
从机器学习到深度学习思路要学会转变了,我们不再是那个调包侠了,我们也能利用学到的知识从0搭建一个网络了。这是非常重要的一个点,在后面的学习中你会慢慢适应的。这一讲就到这里吧。