2019
无所得
有所失
2019
无所得
有所失
先回顾一下全贝叶斯推断:
训练阶段:
p(θ∣Xtr,Ytr)=∫p(Ytr∣Xtr,θ)p(θ)dθp(Ytr∣Xtr,θ)p(θ)
测试阶段:
p(y∣x,Xtr,Ytr)=∫p(y∣x,θ)p(θ∣Xtr,Ytr)dθ
红色分布难以计算,使得后验分布只有在面对简单的共轭模型才能被精确求解
概率模型:p(x,θ)=p(x∣θ)p(θ)
变分推断(Variational Inference):
采用一个简单的分布来近似后验:p(θ∣x)≈q(θ)∈Q
蒙特卡洛方法(MCMC):
从没有标准化的p(θ∣x)里面进行采样(因为下面归一化的部分难以计算):
logp(x)=∫q(θ)logp(x)dθ=∫q(θ)logp(θ∣x)p(x,θ)dθ=∫q(θ)logp(θ∣x)q(θ)p(x,θ)q(θ)dθ=∫q(θ)logq(θ)p(x,θ)dθ+∫q(θ)logp(θ∣x)q(θ)dθ=L(q(θ))+KL(q(θ)∣∣p(θ∣x))
前面的绿色部分是ELBO(Evidence lower bound)
后面的红色部分是用于变分推断的KL散度,KL散度越小,说明我们的估计与后验分布越接近
但是后验分布是未知的,否则就不需要求解了,再看一遍上面这个公式:
logp(x)=L(q(θ))+KL(q(θ)∣∣p(θ∣x))
可以发现前面logp(x)与q(θ)是没有关系的,那么要最小化KL散度,实际上就相当于最大化ELBO:
KL(q(θ)∣∣p(θ∣x))→q(θ)∈Qmin⇔L(q(θ))→q(θ)∈Qmax
改写一遍变分下界:
L(q(θ))=∫q(θ)logq(θ)p(x,θ)dθ=∫q(θ)logq(θ)p(x∣θ)p(θ)dθ=Eq(θ)logp(x∣θ)−KL(q(θ)∣∣p(θ))
前面绿色的为数据项,后面红色的为正则项
最终的优化问题就在于:
L(q(θ))=∫q(θ)logq(θ)p(x,θ)dθ→q(θ)∈Qmax
问题的关键是,怎么对于一个概率分布进行最优化
L(q(θ))=∫q(θ)logq(θ)p(x,θ)dθ→q(θ)=q1(θ1)⋅…⋅qm(θm)max
块坐标上升法(Block coordinate assent):
每次都固定除了一个维度分布其他的部分{qi(θi)}i≠j,然后对一个维度上的分布进行优化
L(q(θ))→qj(θj)max
由于除了qj(θj)其他维度都是固定的,可以得到如下的数学推导:
L(q(θ))=∫q(θ)logq(θ)p(x,θ)=Eq(θ)logp(x,θ)−Eq(θ)logq(θ)=Eq(θ)logp(x,θ)−k=1∑mEqk(θk)logqk(θk)=Eqj(θj)[Eqi≠jlogp(x,θ)]−Eqj(θj)logqj(θj)+Const{rj(θj)=Zj1exp(Eqi≠jlogp(x,θ))}=Eqj(θj)logqj(θj)rj(θj)+Const=−KL(qj(θj)∣∣rj(θj))+Const
在块坐标下降中的每一步优化问题转化为了:
L(q(θ))=−KL(qj(θj)∣∣rj(θj))+Const→qj(θj)max
实际上就是要最小化KL散度,容易发现解为:
qj(θj)=rj(θj)=Zj1exp(Eqi≠jlogp(x,θ))
考虑对于变分分布的参数族:
q(θ)=q(θ∣λ)
限制在于,我们选择了一族固定的分布形式:
但这样就把变分推断就转变成了一个参数优化问题:
L(q(θ∣λ))=∫q(θ∣λ)logq(θ∣λ)p(x,θ)dθ→λmax
只要我们能够计算变分下界(ELBO)对于θ的导数,那么就可以使用数值优化方法来对这个优化问题进行求解
Full Bayesian inference | p(θ∣x) |
---|---|
MP inference | p(θ∣x)≈δ(θ−θMP) |
Mean field variational inference | p(θ∣x)≈q(θ)=∏j=1mqj(θj) |
Parametric variational inference | p(θ∣x)≈q(θ)=q(θ∣λ) |
Conditional=MarginalJoint,p(x∣y)=p(y)p(x,y)
Product Rule
联合分布可以被表示为一维的条件分布的乘积
p(x,y,z)=p(x∣y,z)p(y∣z)p(z)
Sum Rule
任何边缘分布可以利用联合分布通过积分得到
p(y)=∫p(x,y)dx
p(y∣x)=p(x)p(x,y)=p(x)p(x∣y)p(y)=∫p(x∣y)p(y)dyp(x∣y)p(y)
Bayes理论定义了当新的信息到来的时候,可能性的改变:
Posterior=EvidenceLikelihood×Prior
问题描述:
给定从分布p(x∣θ)中得到的独立同分布变量X=(x1,…,xn),来估计θ
常规方法:
采用极大似然估计(maximum likelihood estimation)
θML=argmaxp(X∣θ)=argmaxi=1∏np(xi∣θ)=argmaxi=1∑n=logp(xi∣θ)
贝叶斯方法:
用先验p(θ)来编码θ的不确定性,然后采用贝叶斯推断
p(θ∣X)=∫∏i=1np(xi∣θ)p(θ)dθ∏i=1np(xi∣θ)p(θ)
频率学派vs. 贝叶斯学派
频率学派 | 贝叶斯学派 | |
---|---|---|
变量 | 有随机变量也有确定的 | 全都是随机变量 |
适用范围 | n>>d | ∀n |
现代机器学习模型中可训练的参数数量已经接近训练数据的大小了
频率学派得到的结果实际上是一种受限制的Bayesian方法:
n/d→∞limp(θ∣x1,…,xn)=δ(θ−θML)
注:此处的δ函数指的是狄拉克函数
数据:
模型:
通常假设θ的先验与x没有关系
p(y,θ∣x)=p(y∣x,θ)p(θ)
Examples:
可能会很难训练,因为通常而言观测到的x会比隐层复杂很多。
Examples:
Training阶段:θ上的贝叶斯推断
p(θ∣Xtr,Ytr)=∫p(Ytr∣Xtr,θ)p(θ)dθp(Ytr∣Xtr,θ)p(θ)
结果:采用p(θ)分布比仅仅采用一个θML有着更好地效果
Testing阶段:
p(y∣x,Xtr,Ytr)=∫p(y∣x,θ)p(θ∣Xtr,Ytr)dθ
重新看一遍
训练阶段:
p(θ∣Xtr,Ytr)=∫p(Ytr∣Xtr,θ)p(θ)dθp(Ytr∣Xtr,θ)p(θ)
测试阶段:
p(y∣x,Xtr,Ytr)=∫p(y∣x,θ)p(θ∣Xtr,Ytr)dθ
红色部分的内容通常是难以计算的!
我们说分布p(y)和p(x∣y)是共轭的当且仅当p(y∣x)和p(y)是同类的,即后验分布和先验分布同类。
p(y)∈A(α),p(x∣y)∈B(β)⇒p(y∣x)∈A(α′)
Intuition:
p(y∣x)=∫p(x∣y)p(y)dyp(x∣y)p(y)∝p(x∣y)p(y)
这种情况下贝叶斯推断可以得到闭式解!
常见的共轭分布如下表:
Likelihood p(x∣y) | y | Conjugate prior p(y) |
---|---|---|
Gaussian | μ | Gaussian |
Gaussian | σ−2 | Gamma |
Gaussian | (μ,σ−2) | Gaussian-Gamma |
Multivariate Gaussian | Σ−1 | Wishart |
Bernoulli | p | Beta |
Multinomial | (p1,…,pm) | Dirichlet |
Poisson | λ | Gamma |
Uniform | θ | Pareto |
举个例子:丢硬币
概率模型如下:
p(x,θ)=p(x∣θ)p(θ)
其中对于p(x∣θ)的似然为:
Bern(x∣θ)=θx(1−θ)1−x
但是不知道p(θ)的先验是多少
怎样选择先验概率分布?
Beta分布是满足所有条件的!
Beta(θ∣a,b)=B(a,b)1θa−1(1−θ)b−1
同样也适用于大部分不均匀硬币的情况
让我们来检验似然和先验是不是共轭分布:
p(x∣θ)=θx(1−θ)1−xp(θ)=B(a,b)1θa−1(1−θ)b−1
方法——检验先验和后验是不是在同样的参数族里面
p(θ)p(θ∣x)=Cθα(1−θ)β=C′p(x∣θ)p(θ)=C′θx(1−θ)1−xB(a,b)1θa−1(1−θ)b−1=B(a,b)C′θx+a−1(1−θ)b−x=C′′θα′(1−θ)β′
由于先验和后验形式相同,所以确实是共轭的!
现在考虑接收到数据之后的贝叶斯推断:
p(θ∣X)=Z1p(X∣θ)p(θ)=Z1p(θ)i=1∏np(xi∣θ)=Z1B(a,b)1θa−1(1−θ)b−1i=1∏nθxi(1−θ)1−xi=Z′1θa+∑i=1nxi−1(1−θ)b+n−∑i=1n−1=Z′1θa′−1(1−θ)b′−1
新的参数为:
a′=a+i=1∑nxib′=b+n−i=1∑nxi
那么问题来了,当没有共轭分布的时候我们应该怎么做?
最简单的方法:选择可能性最高的参数
训练阶段:
θMP=argmaxp(θ∣Xtr,Ytr)=argmaxp(Ytr∣Xtr,θ)p(θ)
测试阶段:
p(y∣x,Xtr,Ytr)=∫p(y∣x,θ)p(θ,Xtr,Ytr)dθ≈p(y∣x,θMP)
这种情况下我们并不能计算出正确的后验。
传统处理数据集中缺失值一般有两种方法:
考虑一个这样的数据,时间序列X是T=(t0,…,tn−1)的一个观测,X=(xt0,…,xti,…,xtn−1)⊤∈Rn×d,例如:
X=⎣⎡1796 none none none 7 none 9 none 79⎦⎤,T=⎣⎡0513⎦⎤
利用mask矩阵M∈Rn×d来表示X中的值存在与否,如果存在,Mtij=1否则的话Mtij=0。
总体的基本框架如下,generator从随机的输入中生成时间序列数据,discriminator尝试判别是真的数据还是生成的假数据,通过bp进行优化:
由于最初始的GAN容易导致模型坍塌的问题,采用WGAN(利用Wasserstein距离),他的loss如下:
LG=Ez∼Pg[−D(G(z))]LD=Ez∼Pg[D(G(z))]−Ex∼Pr[D(x)]
采用基于GRU的GRUI单元作为G和D的基本网络,来缓解时间间隔不同所带来的的问题。可以知道的是,老的观测值所带来的影响随着时间的推移应当更弱,因为他的观测值已经有了一段时间的缺失。
采用一个time lag矩阵δ∈Rn×d来表示当前值和上一个有效值之间的时间间隔。
δtij=⎩⎨⎧ti−ti−1,δti−1j+ti−ti−1,0,Mti−1j==1Mti−1j==0&i>0i==0;δ=⎣⎡05805130580513⎦⎤
利用一个时间衰减向量β来控制过去观测值的影响,每一个值都应当是在(0,1]的,并且可以知道的是,δ中的值越大,β中对应的值应当越小,其中Wβ更希望是一个完全的矩阵而不是对角阵。
βti=1/emax(0,Wβδti+bβ)
GRUI的更新过程如下:
hti−1′μtirtih~tihti=βti⊙hti−1=σ(Wμ[hti−1′,xti]+bμ)=σ(Wr[hti−1′,xti]+br)=tanh(Wh~[rti⊙hti−1′,xti]+bh~)=(1−μti)⊙hti−1′+μti⊙h~ti
D过一个GRUI层,最后一个单元的隐层表示过一个FC(带dropout)
G用一个GRUI层和一个FC,G是自给的网络(self-feed network),当前的输出会作为下一个迭代的输入。最开始的输入是一个随机噪声。假数据的δ的每一行都是常量。
G和D都采用batch normalization。
考虑到x的缺失,可能G(z)在x没有缺失的几个值上面都表现的非常好,但是却可能和实际的x差得很多。
文章中定义了一个两部分组成的loss function来衡量填补的好坏。第一部分叫做masked reconstruction loss,用来衡量和原始不完整的时间序列数据之间的距离远近。第二部分是discriminative loss,让生成的G(z)尽可能真实。
Masked Reconstruction Loss
只考虑没有缺失值之间的平方误差
Lr(z)=∥X⊙M−G(z)⊙M∥2
Discriminative Loss
Ld(z)=−D(G(z))
Imputation Loss
Limputation(z)=Lr(z)+λLd(z)
对于每个原始的时间序列x,从高斯分布中采样z,通过一个已经训练好的G获得G(z)。之后通过最小化Limputation(z)来进行训练,收敛之后用G(z)填充缺失的部分。
ximputed=M⊙x+(1−M)⊙G(z)
这一章采用神经网络方法来搭建模型,从而能够解决更为实际的问题。
一个神经单元可以看做o=f(w∗x+b),一个线性的变换再加上一个非线性的激活函数,常见的激活函数如下:
其中ReLU是最为通用的激活函数!
激活函数的通用特征:
PyTorch中有一系列构建好的module来帮助构造神经网络,一个module是nn.Module基类派生出来的一个子类。每个Module有一个或多个Parameter对象。一个Module同样可以可以由一个或多个submodules,并且可以同样可以追踪他们的参数。
注意:submodules不能再list或者dict里面。否则的话优化器没有办法定位他们,更新参数。如果要使用submodules的list或者dict,PyTorch提供了nn.ModuleList
和nn.ModuleDict
。
直接调用nn.Module
实际上等同调用了forward
方法,理论上调用forward
也可以达到同样的效果,但是实际上不应该这么操作。
现在的training loop长这个样子:
1 | def training_loop(n_epochs, optimizer, model, loss_fn, t_u_train, t_u_val, t_c_train, t_c_val): |
调用方法:
1 | linear_model = nn.Linear(1,1) |
现在考虑一个稍微复杂一点的情况,一个线性模型套一个激活函数再套一个线性模型,PyTorch提供了nn.Sequential
容器:
1 | seq_model = nn.Sequential(nn.Linear(1,13), |
可以通过model.parameters()
来得到里面的参数:
1 | [param.shape for param in seq_model.parameters()] |
如果一个模型通过很多子模型构成的话,能够通过名字辨别是非常方便的事情,PyTorch提供了named_parameters
方法
1 | for name, param in seq_model.named_parameters(): |
Sequential
按模块在里面出现的顺序进行排序,从0开始命名。Sequential
同样接受OrderedDict
,可以在里面对传入Sequential
的每个model进行命名
1 | from collections import OrderedDict |
同样可以把子模块当做属性来对于特定的参数进行访问:
1 | seq_model.output_linear.bias |
可以定义nn.Module
的子类来更大程度上的自定义:
1 | class SubclassModel(nn.Module): |
这样极大提高了自定义能力,可以在forward
里面做任何你想做的事情,甚至可以写类似于activated_t = self.hidden_activation(hidden_t) if random.random() >0.5 else hidden_t
,由于PyTorch采用的是动态的运算图,所以无论random.random()
返回的是什么都可以正常运行。
在subclass内部所定义的module会自动的注册,和named_parameters中类似。nn.ModuleList
和nn.ModuleDict
也会自动进行注册。
PyTorch中有functional
,它代表输出完全由输入决定,像nn.Tanh
这种可以直接写在forward
里面。
1 | class SubclassFunctionalModel(nn.Module): |
在PyTorch1.0中有许多函数被放到了torch
命名空间中,更多的函数留在torch.nn.functional
里面。
开普勒从数据中得到三定律,同样利用的是现在数据科学的思想,他的步骤如下:
今日的学习方法实际上就是自动寻找适合的函数形式来拟合输入输出,流程如下:
输入测试数据->计算输出->计算误差->反向传播->更新权重
一个简单的摄氏度和华氏度转换的方法。
定义model和loss函数:
1 | def model(t_u, w, b): |
正向过程:
1 | w = torch.ones(1) |
采用梯度下降进行反向传播,这里采用最简单的方法进行梯度的模拟计算:
1 | delta = 0.1 |
上面这种方法会存在误差,可以考虑采用链式法则进行导数的计算:
1 | def loss_fn(t_p, t_c): |
对于一个训练轮次可以写成下面的样子:
1 | def training_loop(n_epochs, learning_rate, params, t_u, t_c): |
对于不同的参数,可能得到的梯度大小会很不一样,一般将所有的输入做一个标准化的操作,从而能够使得训练更有效的收敛。
autograd可以自动的根据运算求出导数,而不需要手动的对复杂的函数进行计算,考虑用autograd重写之前的内容:
1 | def model(t_u, w, b): |
requires_grad
的效果是让pytorch在运算过程中对他的值进行追踪,每个参数都有.grad
对象,正常情况下值为None
。
1 | loss = loss_fn(model(t_u, *params), t_c) # 加*相当于对参数进行解包,分别作为w,b传入 |
通过backward()
反传之后,params.grad
不再是None
。
多次运算,params
上的梯度会被叠加,为了防止这样的事情出现,需要将梯度清零:
1 | if params.grad is not None: |
现在训练过程长这个样子:
1 | def training_loop(n_epochs, learning_rate, params, t_u, t_c): |
detach
将旧版本的参数从运算图中分离,requires_grad_
使得参数可以被追踪导数。调用方法如下:
1 | training_loop( |
可以通过下面的方法列出所有的优化器:
1 | import torch.optim as optim |
每个优化器在构造的时候都针对一系列的参数(requires_grad = True),每个参数都被存在优化器内部,使得可以通过访问grad
来对他们进行更新。
每个优化器都有两个方法:zero_grad
和step
,前者将所有在构建优化器时候传入的参数的grad
全部设置成0,后者通过优化器自己的方法利用梯度对参数进行更新。
1 | params = torch.tensor([1.0, 0.0], requires_grad = True) |
更改之后的训练流程:
1 | def training_loop(n_epochs, optimizer, params, t_u, t_c): |
规则一:如果训练loss不下降,那么可能是模型太简单,或者是输入的信息不能很好地解释输出
规则二:如果验证集loss偏离,说明过拟合
缓解过拟合方法:
可以考虑利用随机排序的下标来获得shuffle后的训练集和验证集:
1 | n_samples = t_u.shape[0] |
由于并不会考虑在验证集的loss上反向传播,为验证集构造运算图是非常浪费内存和时间的事情,可以考虑利用torch.no_grad
来提升效率:
1 | def training_loop(n_epochs, optimizer, params, train_t_u, val_t_u, train_t_c, val_t_c): |
或者可以使用set_grad_enabled
来条件的启用反向传播
1 | def calc_forward(t_u, t_c, is_train): |
用CSV或者其他表格形式组织的表格数据是最易于处理的,不同于时间序列数据,其中的每个数据项都是独立的,不存在时序上的关系。面对多种数值型的和定类型的数据,我们需要做的是把他们全部转化为浮点数表示的形式。
winequality-whit.csv是一个用;进行分隔的csv文件,第一行为各种相关的数值。
利用numpy导入的方法如下:
1 | wine_path = "./winequality-white.csv" |
其中delimiter每行中分隔元素的分隔符。
将score从输入中分离:
1 | data = wineq[:, :-1] |
将score作为一个定类型的数据,用one_hot向量来表示
1 | # 将target作为一个整数组成的向量 |
由于下划线,scatter_
是原地修改的,其中三个参数的意义如下:
unsqueeze
把本来是4898大小的一维tensor转换成了size为4898x1大小的二维tensor。
可以对输入做一个标准化的处理:
1 | data_mean = torch.mean(data, dim=0) |
同时可以考虑使用le
,lt
,gt
,ge
方法简单的进行划分
1 | # le的返回值是一个0,1的tensor,可以直接用于索引 |
采用的数据集为https://archive.ics.uci.edu/ml/datasets/bike+sharing+dataset
1 | bikes_numpy = np.loadtxt("hour-fixed.csv", |
在这种时间序列数据中,行是按照连续的时间点进行有序排列的,所以不能把每一行当做一个独立的数据项进行处理。
对每个小时有的数据如下:
1 | instant # index of record |
神经网络需要看到一个序列的输入,是N个大小为C的平行序列,C代表channel,就如同一维数据中的column,N表示时间轴上的长度。
数据集的大小为(17520, 17)的,下面把它改为三个维度(天数,小时,信息):
1 | daily_bikes = bikes.view(-1, 24, bikes.shape[1]) |
使用view
方法不会改变tensor的存储,事实上只是改变了索引的办法,是没有什么开销的。这样实际上就得到了N个24连续小时,有7个channel组成的块。如果要得到所希望的N×C×L的数据,可以采用transpose
:
1 | daily_bikes = daily_bikes.transpose(1, 2) |
天气情况实际上是一个定类型的数据,可以考虑把它改成onehot的形式
1 | daily_weather_onehot = torch.zeors(daily_bikes.shape[0], 4 daily_bikes.shape[2]) |
深度学习采用基于循环神经网络的方法,在许多的NLP任务上都达到了SOTA的水平,这一章主要讲怎么把文本数据进行组织。采用的数据是《Pride and Prejudice》。
1 | with open('1342-0.txt', encoding = 'utf-8') as f: |
一种最为简单的方法就是onehot方法,在这里先考虑字母级别的,可以考虑将所有字母都转换成小写,从而减少需要encoding的量,或者可以删掉标点,数字等于任务没有什么关系的内容。
1 | # line是text里面的任意一行 |
对于词语级别的,可以通过构建一个词语表来完成:
1 | def clean_words(input_str): |
Onehot是一种简单方法,但是存在很多缺点:
embedding是一种把单词映射到高维的浮点数向量的方法,以便用于下游的深度学习任务。想法就是,相近的词语,在高维的空间中有更接近的距离。
Word2vec是一个确切的算法,我们可以通过一个利用上下文预测词语的任务,利用神经网络从onehot向量训练出embedding。
通过排列在规律网格中的标量,可以表示黑白图片,如果每个格点利用多个标量来表示的话,可以描述彩色图片,或者例如深度之类的其他feature。
可以利用imageio
来加载图片
1 | improt imageio |
在PyTorch里面,对于图片数据采用的布局是C×H×W的(通道,高度,宽度)。可以使用transpose
进行转换。
1 | img = torch.from_numpy(img_arr) |
对于大量的图片导入,预先分配空间是一个更为有效的方法:
1 | batch_size = 100 |
由于神经网络对0~1范围内的数值能够鞥有效的处理,所以一般会采用下面的处理方法:
1 | # 直接处理 |
同时可以考虑对图片进行旋转,缩放,裁剪等操作,进行数据增强,或者通过修改来适应神经网络的输入尺寸。
除去一般的2D图像,还可能处理类似CT图像这样的数据,是一系列堆叠起来的图片,每一张代表一个切面的信息。本质上来说,处理这种体积的数据和图片数据没有很大区叠,只不过会增加一个深度维度,带来的是一个N×C×H×W×D的五维tensor。
同样可以采用imageio
库进行加载:
1 | import imageio |
Input Representation -> Intermediate Representation -> Output Representation
神经网络学到的就是怎样把Input Representation转化成Output Representation。
PyTorch中的tensor起始就是一个n维数组,可以和NumPy中的ndarray相类比,Tensor支持numpy的无缝衔接。
对比NumPy中的ndarray,tensor可以
Python内置List的不足点
可以类似numpy中的索引方式。
可以利用torch.zeros(3,2)
或者torch.ones(3,2)
的函数进行初始化。
存储形式类似C中数组的方式。
可以利用tensor.storage()
方法获得连续的存储,无论本来是几维数组,都可以最终得到一个连续的数组,用正常方法进行索引。类似于得到C中多维数组的首地址。
通过改变storage中的值同样可以改变对应tensor的内容。
Size:一个tuple,能告诉这个tensor的每一维有多少元素,tensor.size()
或者tensor.shape
Storage offset:相对于tensor中第一个元素的offset,tensor.storage_offset()
Stride:每一维度上,所需要得到下一个元素的步长,tensor.stride()
注意:子tensor有着更少的维度,但是实际上有着和原来的tensor都在相同的地方存储,所以对子tensor的改变会改变原来的tensor(直接类比C语言中的多维数组)。可以采用tensor.clone()
得到tensor的克隆,这样更改不会改变原来的tensor。
tensor.t()
可以将tensor转置,但是他们的存储空间仍然是一样的,只是改变了size和stride。确切的说,只是把对应维度的size和stride进行了交换。
tensor.transpose()
可以用来对多维数组的两个维度进行交换,接受两个参数,分别代表dim0
和dim1
。
contiguous表示tensor在存储中是否按照直接的形式进行存储。可以用tensor.is_contiguous()
进行判断,并且可以用tensor.contiguous
方法对存储重新排布,不改变size,改变storage和stride。
在创建的时候可以用dtype
进行指定,默认的是32-bit浮点数,torch.Tensor
就是torch.FloatTensor
的别名,下面是一些可能的值:
torch.float32 or torch.float—32-bit floating-point
torch.float64 or torch.double—64-bit, double-precision floating-point
torch.float16 or torch.half—16-bit, half-precision floating-point
torch.int8—Signed 8-bit integers
torch.uint8—Unsigned 8-bit integers
torch.int16 or torch.short—Signed 16-bit integers
torch.int32 or torch.int—Signed 32-bit integers
torch.int64 or torch.long—Signed 64-bit integers
可以通过tensor.dtype
来获取类型,可以用对应的方法或者to()
进行转换,type()
进行同样的操作,但是to()
还可以接受额外的参数。
1 | double_points = torch.zeros(10,2).double() |
正常的列表索引,不同维度上切片什么的随你玩
利用tensor.numpy()
把tensor转换为numpy中的array。利用tensor.from_numpy()
把numpy中的array转换成tensor。
注意一点,如果tensor在CPU上分配的话,是共享存储的,但是如果在GPU上分配的话,会在CPU上重新创造一个array的副本。
tensor的保存与加载,即可以使用路径,也可以使用文件描述符
1 | # Save |
如果要将tensor保存成一个更加可互用的形式,可以采用HDF5格式,一种用于表示多维数组的格式,他内部采用一个字典形式的键值对来进行保存。python通过h5py库支持HDF5格式,它可以接受和返回NumPy array。
1 | import h5py |
在这里’coords’就是key,有趣的一点在于可以只从HDF5中加载一部分的内容,而不用加载全部!
1 | f = h5py.File('../data/p1ch3/ourpoints','r') |
在这种情况下只取出了后面几个点的坐标,返回了一个类似NumPy数组的对象。可以直接采用from_numpy()
方法构造tensor。
这种情况下,数据会复制到tensor的storage。
1 | last_points = torch.from_numpy(dset[1:]) |
记得在加载完数据之后关闭文件!
在GPU上可以对tensor进行高效的并行计算,tensor有一个device
可以用来指定在CPU或者GPU上面,可以在创建时候指定,或者利用to
方法创建一个GPU上的副本。
1 | points_gpu = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 4.0]], |
注意!这个时候类型会从torch.FloatTensor
变成torch.cuda.FloatTensor
,其他的类型类似。
如果有多GPU的情况,可以用一个从零开始的int来指定特定的GPU,如下
1 | points_gpu = points.to(device='cuda:0') |
注意到一个问题,运算结束后,并不会把结果返回到CPU,只是返回一个handle
,除非调用了to
方法把它弄回了CPU。
可以使用cuda()
方法和cpu()
方法完成类似上面的事情
1 | points_gpu = points.cuda() #默认是分配到下标为0的GPU |
但是使用to
方法可以传递多个参数!比如同时改变device
和dtype
。
注意有些api会在最后有一个下划线,表示他们是原地修改的,并不会返回一个新的tensor,例如zero_()
会原地把矩阵清零。如果没有下划线会返回一个新的tensor,而原tensor保持不变。大致的API分类如下:
Creation ops—Functions for constructing a tensor, such as ones
and from_numpy
Indexing, slicing, joining, and mutating ops—Functions for changing the shape,
stride, or content of a tensor, such as transpose
Math ops—Functions for manipulating the content of the tensor through computations:
abs
and cos
mean
, std
, and norm
equal
and max
stft
and hamming_window
trace
Random sampling ops—Functions for generating values by drawing randomly
from probability distributions, such as randn
and normal
Serialization ops—Functions for saving and loading tensors, such as load
and
save
CPU execution, such as set_num_threads
Hello ?
Hello !