一、张量基础操作&梯度——pytorch学习

张量

  1. 创建sensor, torch.rand 构建随机初始化矩阵,(0,1)之间
import torch
x = torch.rand(4, 3)
print(x)
# 输出:
tensor([[0.5929, 0.9524, 0.2840],
        [0.2746, 0.3503, 0.3990],
        [0.1227, 0.8759, 0.6011],
        [0.9042, 0.1564, 0.3587]])

torch.zeros() 建立全0矩阵,torch.zeros_like()和torch.zero_() 将现有矩阵转为全0矩阵

y = torch.zeros(4, 3, dtype=torch.long)
z = torch.zeros_like(x)
print(y)
print(z)
# 输出
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
  1. 张量的构建
    torch.tensor()创建张量,类似于np.array
import torch
a = torch.tensor([5.5, 3])
print(a)
# 输出
tensor([5.5000, 3.0000])
  1. 基于已经存在的tensor,创建一个新的tensor
import torch
# torch.new_ones 创建一个新的全1矩阵tensor,返回的tensor默认具有相同的torch.dtype和torch.device
y = torch.zeros(4, 3, dtype=torch.long)
b = y.new_ones(4, 3, dtype=torch.double)
print(b)
# torch.rand_like()基于原有tensor,创建一个均值为0,方差为1的正则化矩阵,和原有tensor维度相同
b = torch.rand_like(b, dtype=torch.float64)
print(b)
# 返回的torch.size是一个tuple,支持tuple的所有操作
print(b.size())
# 输出
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([[0.0526, 0.6942, 0.4970],
        [0.6648, 0.4227, 0.6787],
        [0.2695, 0.3599, 0.8400],
        [0.9022, 0.6533, 0.6454]], dtype=torch.float64)
torch.Size([4, 3])
  1. 常见的构建tensor方法
  • torch.ones() 创建全1张量
  • torch.zeros() 创建全0 张量
  • torch.eye() 创建对角线为1的张量
  • torch.arange(s, e, step) 创建从s开始,e结束,步长为step的的一维张量,类似,
  • np.arange()
  • torch.linspace(s, e, steps) 创建从s开始,e结束,均匀分为steps的一维张量,类似np.linspace()
  • torch.rand() 创建[0,1)均匀分布,torch.randn()服从N(0,1)正态分布
  • torch.normal(mean, std) 创建指定均值,指定方差的tensor
  • torch.randperm(m) 随机排列
import torch
print(torch.ones(4, 3))
print(torch.zeros(4,3))
print(torch.eye(4,3))
print(torch.arange(1, 10, 2))
print(torch.linspace(1, 10, 6))
print(torch.rand(3,3))
print(torch.randn(3,3))
print(torch.normal(1, 2, [3,4]))
print(torch.randperm(5))
# 输出
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.],
        [0., 0., 0.]])
tensor([1, 3, 5, 7, 9])
tensor([ 1.0000,  2.8000,  4.6000,  6.4000,  8.2000, 10.0000])
tensor([[0.3827, 0.1842, 0.1163],
        [0.7383, 0.8150, 0.8816],
        [0.6863, 0.4619, 0.9042]])
tensor([[-0.0745, -0.5493, -0.3604],
        [ 1.4117,  0.4550,  0.6626],
        [ 0.8836,  0.3959, -0.1812]])
tensor([[-1.2770,  1.7557,  4.5646, -2.3573],
        [ 0.3190, -1.3570,  0.8338,  6.5721],
        [ 2.4897,  1.0439, -1.7107,  1.3649]])
tensor([0, 4, 2, 1, 3])
  1. 张量操作
    方式一直接相加
    方式二使用torch.add() 相加
    方式三 y.add_,原值修改
import torch
x = torch.rand(4, 3)
y = torch.ones(4, 3)
print(x)
print(y)
print(x+y)
print(torch.add(x, y))
y.add_(x)
print(y)
# 输出
tensor([[0.1498, 0.7496, 0.0949],
        [0.9943, 0.2537, 0.7342],
        [0.9411, 0.3955, 0.2247],
        [0.3054, 0.3009, 0.4307]])
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
tensor([[1.1498, 1.7496, 1.0949],
        [1.9943, 1.2537, 1.7342],
        [1.9411, 1.3955, 1.2247],
        [1.3054, 1.3009, 1.4307]])
tensor([[1.1498, 1.7496, 1.0949],
        [1.9943, 1.2537, 1.7342],
        [1.9411, 1.3955, 1.2247],
        [1.3054, 1.3009, 1.4307]])
tensor([[1.1498, 1.7496, 1.0949],
        [1.9943, 1.2537, 1.7342],
        [1.9411, 1.3955, 1.2247],
        [1.3054, 1.3009, 1.4307]])
  1. 索引操作,类似numpy
    需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法
import torch
x = torch.rand(4, 3)
print(x)
print(x[:, 1])
y = x[:, 1]
y += 1
print(y)
print(x[:, 1])
# 输出
tensor([[0.7755, 0.7065, 0.2731],
        [0.3742, 0.3654, 0.2566],
        [0.3552, 0.5135, 0.2361],
        [0.5460, 0.5074, 0.1195]])
tensor([0.7065, 0.3654, 0.5135, 0.5074])
tensor([1.7065, 1.3654, 1.5135, 1.5074])
tensor([1.7065, 1.3654, 1.5135, 1.5074])
  1. 维度张量变换,torch.view() 和torch.reshape()
    注: torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),更改其中的一个,另外一个也会跟着改变。(顾名思义,view()仅仅是改变了对这个张量的观察角度)
import torch
x = torch.rand(4,4)
y = x.view(2, 8)
# -1表示这一维的数由其他维度决定
z = x.view(-1, 8)
print(y)
print(z)
# 输出
tensor([[0.3544, 0.3086, 0.0605, 0.8646, 0.3699, 0.9313, 0.5907, 0.8219],
        [0.8304, 0.6211, 0.2969, 0.9637, 0.6833, 0.5921, 0.3213, 0.3536]])
tensor([[0.3544, 0.3086, 0.0605, 0.8646, 0.3699, 0.9313, 0.5907, 0.8219],
        [0.8304, 0.6211, 0.2969, 0.9637, 0.6833, 0.5921, 0.3213, 0.3536]])

上面我们说过torch.view()会改变原始张量,但是很多情况下,我们希望原始张量和变换后的张量互相不影响。为了使创建的张量和原始张量不共享内存,我们需要使用第二种方torch.reshape(), 同样可以改变张量的形状,但是此函数并不能保证返回的是其拷贝值,所以官方不推荐使用。推荐的方法是我们先用 clone() 创造一个张量副本然后再使用 torch.view()进行函数维度变换 。
注:使用 clone() 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor

  1. 取值操作
    如果我们有一个元素 tensor ,我们可以使用 .item() 来获得这个 value,而不获得其他性质
import torch
x = torch.randn(1)
print(x)
print(x.item())
print(type(x))
print(type(x.item()))
# 输出
tensor([-0.0448])
-0.044772230088710785
<class 'torch.Tensor'>
<class 'float'>
  1. 广播机制
    当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。只有两个tensor有一个维度为1时可以广播
import torch
x = torch.rand(1, 2)
y = torch.rand(3, 1)
print(x)
print(y)
print(x+y)
# 输出
tensor([[0.5110, 0.0495]])
tensor([[0.8719],
        [0.5785],
        [0.2149]])
tensor([[1.3829, 0.9214],
        [1.0895, 0.6279],
        [0.7259, 0.2644]])

梯度

  • Autograd简介
  • torch.Tensor 是这个包的核心类。如果设置它的属性 .requires_grad 为 True,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。
  • 还有一个类对于autograd的实现非常重要:Function。Tensor 和 Function 互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fn是 None )。
from __future__ import print_function
import torch
x = torch.rand(3, 3, requires_grad=True )
# 下面给出的例子中,张量由用户手动创建,因此grad_fn返回结果是None。
print(x.grad_fn)
# 输出
None

反向传播.backward()

因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价。

# 更多举例
x = torch.ones(2, 2, requires_grad=True)
print(x)
y = x ** 2
# y是计算的结果,所以它有grad_fn属性。
# .requires_grad_(...) 原地改变了现有张量的requires_grad标志。如果没有指定的话,默认输入的这个标志是 False。
print(y)
print(y.grad_fn)
z = y * y * 3
out = z.mean()
print(z, out)
print(out.backward())
# 输出
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
tensor([[1., 1.],
        [1., 1.]], grad_fn=<PowBackward0>)
<PowBackward0 object at 0x7fce21a33a10>
tensor([[3., 3.],
        [3., 3.]], grad_fn=<MulBackward0>) tensor(3., grad_fn=<MeanBackward0>)
None

缺失情况下默认 requires_grad = False

a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
# 输出
False
True
<SumBackward0 object at 0x7fce22b45690>

注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。

# 因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad)
y = x ** 2
z = y * y * 3
out = z.mean()
# 第一次反向传播
out.backward()
print(x.grad)
# 第二次反向传播,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)
# 清零后,第三次反向传播
out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)

# 输出
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]])
tensor([[4., 4.],
        [4., 4.]])
tensor([[1., 1.],
        [1., 1.]])

备注:如下这种情况下,y 不再是标量。torch.autograd 不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward:

x = torch.randn(3, requires_grad=True)
print(x)

y = x * 2
i = 0
while y.data.norm() < 1000:
    y = y * 2
    i = i + 1
print(y)
print(i)

v = torch.tensor([1, 1.0, 1], dtype=torch.float)
y.backward(v)
print(x.grad)
# 也可以通过将代码块包装在 with torch.no_grad(): 中,来阻止 autograd 跟踪设置了.requires_grad=True的张量的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)

# 输出
tensor([-0.2354, -0.2037, -0.4056], requires_grad=True)
tensor([-482.0371, -417.1968, -830.5679], grad_fn=<MulBackward0>)
10
tensor([2048., 2048., 2048.])
True
True
False

如果我们想要修改 tensor 的数值,但是又不希望被 autograd 记录(即不会影响反向传播), 那么我们可以对 tensor.data 进行操作。

x = torch.ones(1, requires_grad=True)
print(x.data)   # 还是一个tensor
print(x.data.requires_grad)   # 独立于计算图之外,不会进行反向传播累加

y = 2 * x
x.data *= 100   # 只改变了值,不会记录在计算图,所以不会影响梯度传播
y.backward()
print(x)   # 更改data的值也会影响tensor值
print(x.grad)   # 梯度值还是data运算前的值
# 输出
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])

并行计算

为什么需要cuda

CUDA是我们使用GPU的提供商——NVIDIA提供的GPU并行计算框架。对于GPU本身的编程,使用的是CUDA语言来实现的。但是,在我们使用PyTorch编写深度学习代码时,使用的CUDA又是另一个意思。在PyTorch使用 CUDA表示要开始要求我们的模型或者数据开始使用GPU了。

在编写程序中,当我们使用了 .cuda() 时,其功能是让我们的模型或者数据从CPU迁移到GPU(0)当中,通过GPU开始计算。

注:

  • 我们使用GPU时使用的是.cuda()而不是使用.gpu()。这是因为当前GPU的编程接口采用CUDA,但是市面上的GPU并不是都支持CUDA,只有部分NVIDIA的GPU才支持,AMD的GPU编程接口采用的是OpenCL,在现阶段PyTorch并不支持。

  • 数据在GPU和CPU之间进行传递时会比较耗时,我们应当尽量避免数据的切换。

  • GPU运算很快,但是在使用简单的操作时,我们应该尽量使用CPU去完成。

  • 当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0),这将会导致爆出out of memory的错误。我们可以通过以下两种方式继续设置。

数据并行方式:

  • 主流的数据并行方式:不同的数据分布到不同的设备中,执行相同的任务
  • 它的逻辑是,我不再拆分模型,我训练的时候模型都是一整个模型。但是我将输入的数据拆分。所谓的拆分数据就是,同一个模型在不同GPU中训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传
#设置在文件最开始部分
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2" # 设置默认的显卡
CUDA_VISBLE_DEVICE=0,1  # 使用0,1两块GPU
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352