SilkyFinish

SilkyFinish的个人博客

首先流水线记录一下第一次科研的整个过程,并在每一步进行一些小总结:

  • 加入科研项目,简单阅读了四五篇论文了解这个项目
  • 参与构建了一个数据集
    • 从此步开始至倒数第二步都由个人完成[sad]
  • 租用远程服务器,上传并处理数据集
    • autodl,文件处理
    • 因为是租用,又贵又不方便,得蹲点找空档
  • 进行论文复现,并将其代码作为框架服务于自己之后的创新
    • 主要理解原来框架每个代码和文件之间的逻辑
  • 大量阅读论文进行调研,思考可行的改进方案
    • 直接搜索相关论文/引用之间找相关论文
  • 对提出的改进点逐一编写代码,经过大量debug使得代码可以跑通
  • 开始进行实验,根据结果对思路和代码进行调整,循环此步直至提点
  • 进行对比实验,消融实验和鲁棒实验
  • 撰写论文正文
    • latex,overleaf
    • 把代码包装成公式,把思路包装成故事(飞舞水论文大法)
  • 绘制模型图
    • drawio
    • 个人感觉蛮体现水准,撰写论文现在有llm加持,但图还得自己画(截止到我写论文的时间,不知道现在ai有没有进化),要充分理解整个模型并形象美观详略得当的展示,既要清晰展示过程又要注意布局和美观
  • 绘制其他图,如实验结果的柱状图、雷达图等;构建实验结果的表格
    • python绘图;latex制表
  • 进行一些可视化展示
  • 下载会议模板,对正文、图、表排版,对格式(如标点,空格)和语法拼写等调整
  • 根据导师的建议修改论文,循环执行至ddl
  • 注册openreview,投稿

整体总结:

  • cons
    • 本身因为第一次科研只能找了一个比较水的方向和比较一般的组导致:
    • 没有队友,要么什么都不会要么跑了,所有都是一个人做,工作量很大
    • 没有什么指导,导师在我论文写完之后给了建议,虽然这已经挺好,但是对于第一次科研,之前所有的步骤都得自己摸索
    • 没有资源,只能自己租卡,带来诸多麻烦和开销
    • 方向很水,个人觉得做起来没什么意义,特别是后期写论文的时候缺乏动力和信心
    • 知识上收获很少
  • pros
    • 工作量大的同时确实基本充分掌握了一篇文章从无到有的全过程
    • 小众方向希望可以不那么卷中的概率能大一点

lecture 2: Image Classification

Nearest Neighbor

L1(Manhattan) distance

$d{1}(I{1},I{2})=\sum{p}|I{1}^{p},I{2}^{p}|$

complexity: training:O(1);testing:O(n)

L2(Euclidean) distance

$d{2}(I{1},I{2})=\sqrt{\sum{p}(I{1}^{p},I{2}^{p})^2}$

Assignment 1

Tensor Basics

Creating and Accessing tensors

A torch tensor is a multidimensional grid of values, all of the same type, and is indexed by a tuple of nonnegative integers. The number of dimensions is the rank of the tensor; the shape of a tensor is a tuple of integers giving the size of the array along each dimension.

We can initialize torch tensor from nested Python lists. We can access or mutate elements of a PyTorch tensor using square brackets.

Accessing an element from a PyTorch tensor returns a PyTorch scalar; we can convert this to a Python scalar using the .item() method

Tensor constructors

PyTorch provides many convenience methods for constructing tensors; this avoids the need to use Python lists, which can be inefficient when manipulating large amounts of data. Some of the most commonly used tensor constructors are:

Datatypes

Each tensor has a dtype attribute that you can use to check its data type

We can cast a tensor to another datatype using the .to() method; there are also convenience methods like .float() and .long() that cast to particular datatypes

PyTorch provides several ways to create a tensor with the same datatype as another tensor:

  • PyTorch provides tensor constructors such as torch.zeros_like() that create new tensors with the same shape and type as a given tensor
  • Tensor objects have instance methods such as .new_zeros() that create tensors the same type but possibly different shapes
  • The tensor instance method .to() can take a tensor as an argument, in which case it casts to the datatype of the argument

Tensor indexing

Slice indexing

similar to python

There are two common ways to access a single row or column of a tensor: using an integer will reduce the rank by one, and using a length-one slice will keep the same rank.

you can use the clone() method to make a copy of a tensor

Integer tensor indexing

We can also use index arrays to index tensors

More generally, given index arrays idx0 and idx1 with N elements each, a[idx0, idx1] is equivalent to:

1
2
3
4
5
6
torch.tensor([
a[idx0[0], idx1[0]],
a[idx0[1], idx1[1]],
...,
a[idx0[N - 1], idx1[N - 1]]
])

Boolean tensor indexing

Boolean tensor indexing lets you pick out arbitrary elements of a tensor according to a boolean mask. Frequently this type of indexing is used to select or modify the elements of a tensor that satisfy some condition.

In PyTorch, we use tensors of dtype torch.bool to hold boolean masks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Find the elements of a that are bigger than 3. The mask has the same shape as
# a, where each element of mask tells whether the corresponding element of a
# is greater than three.
mask = (a > 3)
print('\nMask tensor:')
print(mask)

# We can use the mask to construct a rank-1 tensor containing the elements of a
# that are selected by the mask
print('\nSelecting elements with the mask:')
print(a[mask])

# We can also use boolean masks to modify tensors; for example this sets all
# elements <= 3 to zero:
a[a <= 3] = 0
print('\nAfter modifying with a mask:')
print(a)

Reshaping operations

View

PyTorch provides many ways to manipulate the shapes of tensors. The simplest example is .view(): This returns a new tensor with the same number of elements as its input, but with a different shape.

We can use .view() to flatten matrices into vectors, and to convert rank-1 vectors into rank-2 row or column matrices

As a convenience, calls to .view() may include a single -1 argument; this puts enough elements on that dimension so that the output has the same number of elements as the input.

shares the same data!

Swapping axes

The simplest such function is .t(), specificially for transposing matrices

For tensors with more than two dimensions, we can use the function torch.transpose) to swap arbitrary dimensions.

If you want to swap multiple axes at the same time, you can use torch.permute) method to arbitrarily permute dimensions

Contiguous tensors

you can typically overcome these sorts of errors by either by calling .contiguous() before .view(), or by using .reshape() instead of .view()

Tensor operations

Elementwise operations

Basic mathematical functions operate elementwise on tensors, and are available as operator overloads, as functions in the torch module, and as instance methods on torch objects

Reduction operations

We may sometimes want to perform operations that aggregate over part or all of a tensor, such as a summation; these are called reduction operations.

Like the elementwise operations above, most reduction operations are available both as functions in the torch module and as instance methods on tensor objects.

We can use the .sum() method (or eqivalently torch.sum) to reduce either an entire tensor, or to reduce along only one dimension of the tensor using the dim argument.Other useful reduction operations include mean, min, and max

After summing with dim=d, the dimension at index d of the input is eliminated from the shape of the output tensor

Reduction operations reduce the rank of tensors: the dimension over which you perform the reduction will be removed from the shape of the output. If you pass keepdim=True to a reduction operation, the specified dimension will not be removed; the output tensor will instead have a shape of 1 in that dimension

torch.argmin:This is the second value returned by torch.min()

Matrix operations

  • torch.dot: Computes inner product of vectors
  • torch.mm: Computes matrix-matrix products
  • torch.mv: Computes matrix-vector products
  • torch.addmm / torch.addmv: Computes matrix-matrix and matrix-vector multiplications plus a bias
  • torch.bmm / torch.baddmm: Batched versions of torch.mm and torch.addmm, respectively
  • torch.matmul: General matrix product that performs different operations depending on the rank of the inputs. Confusingly, this is similar to np.dot in numpy
  • torch.stack(tensors, dim=0, **, out=None*) :Concatenates a sequence of tensors along a new dimension

Vectorization

avoiding explicit Python loops in your code and instead using PyTorch operators to handle looping internally will cause your code to run a lot faster. This style of writing code, called vectorization, avoids overhead from the Python interpreter, and can also better parallelize the computation (e.g. across CPU cores, on on GPUs)

Broadcasting

Broadcasting usually happens implicitly inside many PyTorch operators. However we can also broadcast explicitly using the function torch.broadcast_tensors

torch.mm does not support broadcasting, but torch.matmul does

Out-of-place vs in-place operators

Out-of-place operators: return a new tensor

In-place operators: modify and return the input tensor

Running on GPU

ll PyTorch tensors also have a device attribute that specifies the device where the tensor is stored — either CPU, or CUDA (for NVIDA GPUs)

we can use the .to() method to change the device of a tensor. We can also use the convenience methods .cuda() and .cpu() methods to move tensors between CPU and GPU

Calling x.to(y) where y is a tensor will return a copy of x with the same device and dtype as y

Other

torch.topk(input, k, dim=None, largest=True, sorted=True, **, out=None*):Returns the k largest elements of the given input tensor along a given dimension

torch.chunk(input, chunks, dim=0):split tensor tuple

torch.cat(tensors, dim=0, **, out=None*):torch.cat() can be seen as an inverse operation for torch.split() and torch.chunk()

lecture 3:Linear Classifier

different viewpoint

Algebraic Viewpint

Visual VIewpoint

图片

Geometric Viewpoint

图片

Multiclass SVM Loss(hinge loss)

The score of the correct class should be higer than all the other scores

图片

score:s=f(xi,W)

$Li=\sum{j\ne yi}max(0,s{j}-s_{y_i}+1)$

Regularization

$L(W)=1/N\sum_{i=1}^NL_i(f(x_i,W),y_i)+\lambda R(W)$

$\lambda R(W)$:Regularization:Prevent the model from doing too well on traing data

simple examples:

L2 regularization:$R(W)=\sumk\sum_lW{k,l}^2$

L1 regularization:$R(W)=\sumk\sum_l|W{k,l}|$

L2 regularization:$R(W)=\sumk\sum_l\beta W{k,l}^2+|W_{k,l}|$

goal:1.express preferences;2.avoid overfitting;.improve optimization by adding curvature

图片

Cross-Entropy Loss(Multinomial Logistic Regression)

comapre the m pv):maximum likelihood estimation

lecture 4:Optimization

$w^*=argmin_wL(w)$

SGD+Momentum

Build up “velocity” as a running mean of gradients

$\rho$ gives “friction”;typically rho=0.9 or 0.99

$v_{t+1}=\rho v_t+\nabla f(x_t)$

$x{t+1}=x_t-\alpha v{t+1}$

1).At local minimum we still have some velocity that can help escape

2).smooth out the noise ,alleviate oscillatory problem

Nesterov Momentum

图片

“Look ahead” to the point where updating using velocity would take us; compute gradient there and mix it with velocity to get actual update direction

$v_{t+1}=\rho v_t-\alpha\nabla f(x_t+\rho v_t)$

$x{t+1}=x_t+v{t+1}$

AdaGrad

1
2
3
4
5
grad_squared=0
for t in range(num_steps):
dw=compute_gradient(w)
grad_squared+=dw*dw
w-=learning_rate*dw/(grad_quared.sqrt()+1e-7)

progress along “steep”directions is damped

progress along “flat”directions is accelerated

RMSProp:”Leak Adagrad”

1
2
3
4
5
grad_squared=0
for t in range(num_steps):
dw=compute_gradient(w)
grad_squared+=decay_rate*grad_squared+(1-decay_rate)*dw*dw
w-=learning_rate*dw/(grad_quared.sqrt()+1e-7)

avoid slowing down when square becomes bigger

Adam: RMSProp+Momentum

1
2
3
4
5
6
7
moment1=0
moment2=0
for t in range(num_steps):
dw=compute_gradient(w)
moment1=beta1*moment1+(1-beta1)*dw #similar to velocity
moment2=beta2*moment2+(1-beta2)*dw*dw
w-=learning_rate*moment1/(moment2.sqrt()+1e-7)

Bias correction for the fact that the first and the second moment estimates start at zero

1
2
3
4
5
6
7
8
9
moment1=0
moment2=0
for t in range(num_steps):
dw=compute_gradient(w)
moment1=beta1*moment1+(1-beta1)*dw #similar to velocity
moment2=beta2*moment2+(1-beta2)*dw*dw
moment1_unbias=moment1/(1-beta1**t)
moment2_unbias=moment2/(1-beta2**t)
w-=learning_rate*moment1_unbias/(moment2_unbias.sqrt()+1e-7)

图片

lecture 5:Neural Networks

Feature transforms

Fully-connected neural network:also MLP

Lecture 6:Back Propagation

Represent complex expressions as computational graphs

During the backward pass, each node in the graph receives upstream gradients and multiplies them by local gradients to compute downstream gradients

Backprop can be implemented with “flat” code where the backward pass looks like forward pass reversed

Lecture 7:Convolutional Networks

Receptive Fields

Each successive convolution adds K-1 to the receptive field size

With L layers the receptive field size is 1+L*(K-1)

图片

Strided Convolution

a way to add receptive field size quickly

LeNet

spatial size decreases:using pooling or strived conv

number of channels increases:total “volume” is preserved

Lecture 8:CNN Architecture

ZFNet:a bigger AlexNet

VGG

5x5=2 layer of 3x3 in receptive fields,but less FLOPS and fewer parametres;and it can add more relu between layers

ResNet

Bottleneck Block:More layers, less computational cost

图片

ResNeXt

图片

add groups improves performance with same computational complexity

Squeeze-and-Excitation Networks

图片

Densely Connected Neural Networks

图片

Dense blocks where each layer is connected to every other layer in feedforward fashion

Alleviates vanishing gradient,strengthens feature propagation,encourages feature reuse

MobileNets:Tiny Networks(For MObile Devices)

图片

Lecture 9: Hardware and Software

autograd

requires_grad=True

Static Computation Graphs

graph=build_graph()

reuse the same graph on every iteration

graph=torch.jit.script(model):

Just-In-Time compilation:introspect the source code of the function,compile it into a graph object

advantage:optimization;serialize

diadvantage:debug

Lecture 10: Training Neural Networks I

One time setup

Activation functions, data preprocessing, weight initialization, regularization

Activation functions

Leaky ReLU

f(x)=max(0.01x,x):will not die

PReLU(parametric)

$f(x)=max(\alpha x,x) where\ \alpha\ is\ learnable$

Exponential Linear Unit(ELU)

$f(x)=x\ if\ x>0$

$f(x)=\alpha (exp(x)-1)\ if\ x>0$

closer to zero mean outputs

negative saturation regime compared with Leaky ReLU adds some robustness to noise

Scaled Exponential Linear Unit(SELU)

$f(x)=\lambda x\ if\ x>0$

$f(x)=\lambda\alpha (exp(x)-1)\ if\ x>0$

works better for deep networks

“self-normalizing”property;can train deep SELU networks without BatchNorm

Data Preprocessing

standardlize:less sensitive to small changes in weight,easier to optimize

图片

Weight Initialization

Gaussion:not good for deep network

Xavier Initialization

w=np.random.randn(Din,Dout)/np.sqrt(Din)

variance of output=variance of input->std:1/sqrt(Din)

problem:change from tanh to ReLU,activations collapse to zero again because Xavier assumes zero centred activation function

Kaiming/MSRA Initialization

std=sqrt(2/Din)

problem: not good for ResNet->intialize first conv with MSRA,second to zero

Regularization

batch normalization and data augmentation almost always a good idea

Dropout

for large fully-connected layers

dropout makes output random->$\int p(z)f(x,z)dz$

At test time, drop nothing and multiply by dropout probability:output at test time=expected output at traing time

inverted dropout:drop and scale during traing;test time is unchanged

Others

dropconnect:drop random connections between neurons(set weight to 0)

fractional max pooling:use randomized pooling regions

stochastic depth:skip some residual blocks in ResNet

cutout:set random images regions to 0

mixup:train on random blends of image

Lecture 11: Training Neural Networks II

2.Training dynamics Learning rate schedules; hyperparameter optimization

3.After training Model ensembles, transfer learning

Learning Rate Schedule

Learing Rate Decay:Step

reduce learning rate at a few fixed points

Learing Rate Decay:Sosine

$\alpha_t=1/2\alpha_0(1+cos(t\pi/T))$

Learing Rate Decay:Linear

$\alpha_t=\alpha_0(1-t/T)$

Learing Rate Decay:Inverse Sqrt

$\alpha_t=\alpha_0/\sqrt{t}$

Choosing Hyperparameters

grid search

random search

Model Ensembles

  1. Train multiple independent models 2. At test time average their results

Transfer Learning

feature extracter;fine tuning

Lecture 12: Recurrent Networks

key idea: RNNs have an “internal state” that is updated as a sequence is processed

$ht=f_W(h{t-1},x_t)$

Vanilla/Elman RNN

$ht=tanh(W{hh}h{t-1}+W{xh}x_t)$

$y{t}=W{hy}h_{t}$

Truncated Backpropagation Through Time

only backpropagate through finite chunks of the sequence

Example: Image Captioning

图片

Long Short Term Memory(LSTM)

图片

two vectors at each timestep: cell state and hidden state

four gates

图片

i:input gate,whether to write cell

f: forget gate,whether to erase cell

o:output gate,whether to reveal cell

g:gategate,how much to write to cell

图片

Highway Networks

图片

Gated Recurrent Unit(GRU)

Lecture 13: Attention

图片

Sequence-to-Sequence with RNNs and Attention

图片

how much should we attend to each hidden state of the encoder given the current state of the decoder

图片

Attention Layer

scaled similarity function:dot product

multiple query vectors

seperate key and value

图片

Self-Attention Layer

图片

don’t know the order of the vectors->positon embedding

Y1:This produces the output of the self-attention layer at this position

Clearly the word at this position will have the highest softmax score, but sometimes it’s useful to attend to another word that is relevant to the current word.

Masked Self-Attention Layer

图片

Multihead Self-Attention Layer

1.It expands the model’s ability to focus on different positions.

2.It gives the attention layer multiple “representation subspaces”.

图片

Three Ways of Processing Sequences

图片

Transformer

图片

For RNNs, instead of only encoding the whole sentence in a hidden state, each word has a corresponding hidden state that is passed all the way to the decoding stage. Then, the hidden states are used at each step of the RNN to decode.

img

img

img

The Decoder Side

The encoder start by processing the input sequence. The output of the top encoder is then transformed into a set of attention vectors K and V. These are to be used by each decoder in its “encoder-decoder attention” layer which helps the decoder focus on appropriate places in the input sequence

图片

img

Lecture 14: Visualizing and Understanding

Lecture 19: Generative Models I

Discriminative Model: learn a probability distribution p(y|x)

Generative Model: learn a probability distribution p(x)

Conditional Generative Model: learn p(x|y)

图片

Autoregressive Models

goal:write down an explicit function for p(x)=f(x,W)

given dataset $x^{(i)}$,train the model by solving$W^*=argmax\prod_ip(x^{(i)})=argmax\sum_ilogf(x^{(i)},W)$

图片

PixelRNN

generate from the upper left corner

compute a hidden state for each pixel that depends on hidden states and RGB values from the left and the above (LSTM)

$h{x,y}=f(h{x-1,y},h_{x,y-1},W)$

at each pixel,predict R,G,B:softmax over (0,1…255)

图片

slow

PixelCNN

slow

Variational Autoencoders

VAE define an intratable density that we cannot explicitly compute or optimize ,but can directly optimize a lower bound on the density

Autoencoders

图片

cmompress to low dimention

cnn;up cnn

no probabilistic: no way to sample new data from learned model

Variational Autoencoders

x is an image, z is latent factors(unobserved) used to generate x

Decoder must be probabilistic: Decoder inputs z, outputs mean μx|z and (diagonal) covariance ∑x|z ->Sample x from Gaussian with mean μx|z and (diagonal) covariance ∑x|z

After training(test)

1.sample a new latent variable from the prior distribution

2.z-decoder-x’s distribution

图片

assume simple prior p(z): Gaussian

assume the probability over the image:gaussian with a number of the gaussian equal to the numer of pixels->parametrize:mean value and standard deviation value for each pixel

output a high dimentional gaussian distribution

represent p(x|z) with a neural network

Train

maximize likelihood

图片

$p_{\theta}(x|z)$:decoder

$p_{\theta}(z)$:gaussian

Solution: Train another network (encoder) that learns $q{\phi}(z|x)=p{\theta}(z|x)$

图片

Math

图片

图片

->

图片

Summary

图片

Lecture 20: Generative Models II

VAE

图片

图片

图片

Generate

图片

Edit

图片

VQ-VAE

图片

GAN

Structure

图片

Loss

图片

Train

图片

图片

Math

图片

图片

图片

图片

图片

2024年 年终总结

学习篇

关于一些刷过的课

​ 依稀记得第一次看到csdiy时有激动兴奋又感觉被冲击和震撼,无论是知识上还是思想上都打开了一扇新的大门。作为2024下半年的一大部分,在此记录一下我的一些感受(按时间顺序)

CS50ai:第一门公开课,教授是华人也很年轻,但是当时给我极大的震撼。虽然讲的知识从现在来看并不难,但是我第一次见到一个老师讲两个小时甚至都没有任何额、啊的停顿,从头到尾没有磕绊甚至没有喝水,而且逻辑思路如此的连贯清晰循序渐进的讲述知识,属实膜拜

MITmissingsemester:比较硬核,前面的课还是很有用的,后面的暂时还没用到

小土堆:一个pytorch入门,说不上讲的多好,但是也还不错,一个认真负责的中国的知识技能传播者

d2l:李沐大神,之前发过好几次感想了就不多说了,伟大无需多言

CS61A:感觉已经被很多地方都吹爆了,csdiy基本描述的很到位。亲身测评确实牛逼,最让我钦佩的就是一个课程组能把一门课的课前课上课后做到如此程度,真是令人敬佩和羡慕

论文精读系列:zhu老师真是yyds,学术水平不必多说(真感觉他的大脑里面装了个论文库),而且讲解水平也相当高,深入浅出。还有点小幽默,真是理想学者的样子

EECS498:教授上课水平非常高,讲笑话水平也不错;同时这门课的作业简直无敌,可以说之前对dl的概念都是懂一些的水平,真正从零开始手搓所有层块模型框架真是特别的体验,虽然工作量相当大debug到疯狂,但成就感是前所未有的。同时作业设置的也很精妙,环环相扣,写的时候变成了米奇妙妙屋

CS50x:老师水平也很高,而且贼有激情,感觉是个真心热爱cs教学的教授

李宏毅ML:老师的台湾口音很特别,讲扩散模型那部分是真被折服了,太强了

CS188:笔记写的非常好,看到别人家的课竟然会官方准备如此详尽的笔记,太羡慕了

CS231n:和498相似,简洁一些
最后放一段csdiy的话吧,第一遍看的时候就被触动了:
“最后,希望大家少点浮躁和功利,多一些耐心和追求。很多人发邮件问我自学需不需要很强的自制力,我觉得得关键得看你自己想要什么。如果你依然抱着会一门编程语言便能月薪过万的幻想,想分一杯互联网的红利,那么我说再多也是废话。其实我最初的自学并没有太多功利的想法,只是单纯的好奇和本能的求知欲。自学的过程也没有所谓的“头悬梁,锥刺股”,该吃吃,该玩玩,不知不觉才发现竟然攒下了这么多资料。现如今中美的对抗已然成为趋势,而我们还在“卑微”地“师夷长技”,感叹国外高质量课程的同时也时常会有一种危机感。这一切靠谁来改变呢?靠的是刚刚入行的你们。所以,加油吧,少年!”

关于科研

​ 算是第一次接触科研,虽然说毕竟真正的大佬肯定也是不会来我们这肯定是去牛组的,但是确实有点失望。导师学长和组员人都还是不错的,但或许这是唯一的优点了。导师和学长几乎没有给我任何实质的帮助,组员的水平也确实比较低客观来说,所以我几乎承包了所有事情;没有任何算力,一张20系列都没有,也不知道交大的经费都给哪些爷了,光是租服务器我就自掏腰包充了一千块。总的来说,除了最开始队友搞了点数据集(我后来也基本没用上),整个过程从收集数据集,处理数据,到看论文、复现论文代码,到改进算法、创新算法,不停的看论文、思考、写代码、做实验、debug,全部都是我一个人在carry,其他人连两个月前我完成的最基本的任务也没完成

关于未来

​ 老实说大部分时间其实也没有特别焦虑或者悲观,但是迷茫肯定还是有的。作为一个非科班非硬核的半路出家的自学的人,反而要去到一个吃学历吃学术而且目前最卷的领域。且不说我未来能否转型成功并且在这个赛道取得不错的成绩,这个赛道未来是否是泡沫是否是红海也无从得知。又或许到头来自己的工作被ai取代了也未尝没有可能。或许当下能做的就是凭一股兴趣和冲劲去能做多少是多少。之后的一年能做出顶会顶刊或者找到实习吗,不知道。在之后的一年能申到好学校好项目好导师吗,不知道。以后能找到好工作,或者能找到工作吗,不知道。什么是好?不知道。作为一个debuff叠满的玩家,我对未来的操控力太小。

生活篇

第十章 注意力机制

64 注意力机制

1.

  • 卷积、全连接、池化层都只考虑不随意线索
  • 图片
  • 注意力机制考虑随意线索
    • 随意线索被称之为查询query
    • 每个输入是一个 值value和不随意线索key 的对
    • 通过注意力池化层来有偏向性地选择某些输入
    • 图片
      2.
      非参注意力池化层
  • Nadaraya-Watson核回归
  • $f(x)=\sum{i=1}^{n}\frac{K(x-x{i})}{\sum{j=1}^{n}K(x-x{i})}y_{i}$
  • 其中x是查询,(xi,yi)是键值对
  • K是核函数,用来计算距离,分数表示概率,即每个的相对重要性,离x越近的值越大,也就是说对于要拟合的x,通过这个系数找到离他比较近的点,给他们的y值比较大的权重,(获得了更多的注意力)最终得到一个加权平均
  • 简单来说,根据输入的位置对输出yi进行加权
  • 使用高斯核$K(u)=\frac{1}{\sqrt{2\pi}}exp(-\frac{u^2}2)$
  • $f(x)=\sum{i=1}^{n}\frac{exp(-\frac{(x-x{i})^2}{2})}{\sum{j=1}^{n}exp(-\frac{(x-x{j})^2}{2})}y_{i}$
  • $=\sum{i=1}^{n}softmax(-\frac{(x-x{i})^2}{2})y_{i}$
    3.
    参数化的注意力机制
  • 在之前的基础上引入可以学习的w
  • $f(x)=\sum{i=1}^{n}softmax(-\frac{((x-x{i})w)^2}{2})y_{i}$
    4.
    总结
  • 受试者使用非自主性和自主性提示有选择性地引导注意力。前者基于突出性,后者则依赖于意识
  • 注意力机制通过注意力汇聚使选择偏向于值(感官输入),其中包含查询(自主性提示)和键(非自主性提示)。键和值是成对的

65 注意力分数

1.
注意力分数

  • 图片
  • 图片
    2.
    Additive Attention
  • 可学参数:$W_k \in \mathbb{R}^{h\times k},W_q \in \mathbb{R}^{h\times q},v \in \mathbb{R}^{h}$
  • $a(k,q)=v^T tanh(W_kk+W_qq)$
  • 等价于将key和query合并起来后放入到一个隐藏大小为h输出大小为1的单隐藏层MLP
    3.
    Scaled Dot-Product Attention
  • 如果query和key都是同样的长度d,$a(ki,q)=/\sqrt{d}$
  • 分母保证对d没那么敏感
    4.
    总结
  • 注意力分数是query和key的相似度,注意力权重是分数的softmax结果

66. 使用注意力机制的seq2seq

1.
动机

  • 机器翻译中,每个生成的词可能相关于源句子中不同的词,在seq2seq中源句子所有信息都被压缩在最后一个隐藏状态,引入注意力可以去关注需要的部分
    2.
    加入注意力
  • 图片
  • 编码器对每次词的输出作为key和value
  • 解码器RNN对上一个词的输出是query
  • 注意力的输出和下一个词的词嵌入合并进入
    3.
    总结
  • 注意力机制可以根据解码器RNN的输出来匹配到合适的编码器的RNN的输出来更有效地传递信息
    4.
    Bahdanau注意力

带有注意力机制的解码器基本接口

1
2
3
4
5
6
7
8
class AttentionDecoder(d2l.Decoder):
"""带有注意力机制的解码器基本接口"""
def __init__(self, **kwargs):
super(AttentionDecoder, self).__init__(**kwargs)

@property
def attention_weights(self):
raise NotImplementedError

实现带有Bahdanau注意力的循环神经网络解码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Seq2SeqAttentionDecoder(AttentionDecoder):
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqAttentionDecoder, self).__init__(**kwargs)
self.attention = d2l.AdditiveAttention(
num_hiddens, num_hiddens, num_hiddens, dropout)
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(
embed_size + num_hiddens, num_hiddens, num_layers,
dropout=dropout)
self.dense = nn.Linear(num_hiddens, vocab_size)

def init_state(self, enc_outputs, enc_valid_lens, *args):
# outputs的形状为(batch_size,num_steps,num_hiddens).
# hidden_state的形状为(num_layers,batch_size,num_hiddens)
outputs, hidden_state = enc_outputs
return (outputs.permute(1, 0, 2), hidden_state, enc_valid_lens)

def forward(self, X, state):
# enc_outputs的形状为(batch_size,num_steps,num_hiddens).
# hidden_state的形状为(num_layers,batch_size,
# num_hiddens)
enc_outputs, hidden_state, enc_valid_lens = state
# 输出X的形状为(num_steps,batch_size,embed_size)
X = self.embedding(X).permute(1, 0, 2)
outputs, self._attention_weights = [], []
for x in X:
# query的形状为(batch_size,1,num_hiddens)
query = torch.unsqueeze(hidden_state[-1], dim=1)
#解码器RNN对上一个词的输出
# context的形状为(batch_size,1,num_hiddens)
context = self.attention(
query, enc_outputs, enc_outputs, enc_valid_lens)
# 在特征维度上连结
x = torch.cat((context, torch.unsqueeze(x, dim=1)), dim=-1)
# 将x变形为(1,batch_size,embed_size+num_hiddens)
out, hidden_state = self.rnn(x.permute(1, 0, 2), hidden_state)
outputs.append(out)
self._attention_weights.append(self.attention.attention_weights)
# 全连接层变换后,outputs的形状为
# (num_steps,batch_size,vocab_size)
outputs = self.dense(torch.cat(outputs, dim=0))
return outputs.permute(1, 0, 2), [enc_outputs, hidden_state,
enc_valid_lens]

@property
def attention_weights(self):
return self._attention_weights

67.自注意力

1.
自注意力

  • 给定序列x1,…,xn,xi作为key,value,query得到y1,…,yn
  • yi=f(xi,(x1,x1),…(xn,xn))
  • 图片
    2.
    对比
    图片
    k是窗口的大小,n是序列长度,d是词向量特征维度,d^2 是矩阵乘法。最长路径指信息传递。并行度是每个计算是否依赖其他的输出。n^2 d是每次q要和n组做乘法,每个长度为d。由此可以看出,自注意力最长路径短说明他擅长看很长的序列,但反映在计算复杂度上他需要很大的计算量(n方)
    3.
    位置编码
  • 跟CNN/RNN不同,自注意力并没有记录位置信息
  • 位置编码将位置信息注入到输入里
    • 假设长度为n的序列是:$X\in \mathbb{R}^{n\times d}$,那么使用位置编码矩阵$P\in \mathbb{R}^{n\times d}$来输出X+P作为自编码输入
  • $p{i,2j}=sin(\frac{i}{10000^{2j/d}}),p{i,2j+1}=cos(\frac{i}{10000^{2j/d}})$

68.Transformer

1.
架构
纯基于注意力

  • 编码器:多头自注意力
  • 解码器:解码器自注意力,编码器-解码器注意力
    图片
    2.
    多头注意力
    图片
    对同一key,value,query,希望抽取不同的信息(例如短距离关系和长距离关系)
    ->多头注意力使用h个独立的注意力池化,合并各个头输出得到最终输出
    3.
    基于位置的前馈网络
  • 输入形状由(b,n,d)变换成(bn,d)
  • 作用于两个全连接层
  • 输出形状由(bn,d)变化回(b,n,d)
  • 等价于两层核窗口为1的一维卷积层
    4.
    层归一化
  • Add:ResNet
  • Norm:层归一化
    • 批量归一化对每个特征/通道里元素进行归一化,不适合序列长度会变的NLP
    • 层归一化对每个样本里的元素进行归一化
    • 图片
      5.
      信息传递
  • 编码器中的输出y1,…yn
  • 将其作为解码器中第i个Transformer块中多头注意力的key和value,它的query来自目标序列
  • 意味着编码器和解码器中块的个数和输出维度都是一样的
    6.
    预测
  • 预测第t+1个输出时,解码器中输入前t个预测值。在自注意力中,前t个预测值作为key和value,第t个值还作为query
  • 图片
    7.
    代码

基于位置的前馈网络

1
2
3
4
5
6
7
8
9
10
class PositionWiseFFN(nn.Module):
def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
**kwargs):
super(PositionWiseFFN, self).__init__(**kwargs)
self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
self.relu = nn.ReLU()
self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

def forward(self, X):
return self.dense2(self.relu(self.dense1(X)))

使用残差连接和层归一化

1
2
3
4
5
6
7
8
class AddNorm(nn.Module):
def __init__(self, normalized_shape, dropout, **kwargs):
super(AddNorm, self).__init__(**kwargs)
self.dropout = nn.Dropout(dropout)
self.ln = nn.LayerNorm(normalized_shape)

def forward(self, X, Y):
return self.ln(self.dropout(Y) + X)

实现编码器中的一个层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class EncoderBlock(nn.Module):
def __init__(self, key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
dropout, use_bias=False, **kwargs):
super(EncoderBlock, self).__init__(**kwargs)
self.attention = d2l.MultiHeadAttention(key_size, query_size,
value_size, num_hiddens,
num_heads, dropout, use_bias)
self.addnorm1 = AddNorm(norm_shape, dropout)
self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
num_hiddens)
self.addnorm2 = AddNorm(norm_shape, dropout)

def forward(self, X, valid_lens):
Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
#这里q,k,v都是自己
return self.addnorm2(Y, self.ffn(Y))

Transformer编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TransformerEncoder(d2l.Encoder):
def __init__(self, vocab_size, key_size, query_size, value_size,
num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, num_layers, dropout, use_bias=False, **kwargs):
super(TransformerEncoder, self).__init__(**kwargs)
self.num_hiddens = num_hiddens
self.embedding = nn.Embedding(vocab_size, num_hiddens)
self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
self.blks = nn.Sequential()
for i in range(num_layers):
self.blks.add_module(
"block" + str(i),
EncoderBlock(key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, dropout, use_bias))

def forward(self, X, valid_lens, *args):
X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
self.attention_weights = [None] * len(self.blks)
for i, blk in enumerate(self.blks):
X = blk(X, valid_lens)
self.attention_weights[
i] = blk.attention.attention.attention_weights
return X

Transformer解码器也是由多个相同的层组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class DecoderBlock(nn.Module):
"""解码器中第i个块"""
'''类比之前使用注意力机制的seq2seq,第一个attention层相当于原来的rnn层,第二个attention层就是attention层'''
def __init__(self, key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
dropout, i, **kwargs):
super(DecoderBlock, self).__init__(**kwargs)
self.i = i
self.attention1 = d2l.MultiHeadAttention(
key_size, query_size, value_size, num_hiddens, num_heads, dropout)
self.addnorm1 = AddNorm(norm_shape, dropout)
self.attention2 = d2l.MultiHeadAttention(
key_size, query_size, value_size, num_hiddens, num_heads, dropout)
self.addnorm2 = AddNorm(norm_shape, dropout)
self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
num_hiddens)
self.addnorm3 = AddNorm(norm_shape, dropout)

def forward(self, X, state):
enc_outputs, enc_valid_lens = state[0], state[1]
# 训练阶段,输出序列的所有词元都在同一时间处理,q,k,v都是X
# 因此state[2][self.i]初始化为None。
# 预测阶段,输出序列是通过词元一个接着一个解码的,合并X和前面的输出
# 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示
if state[2][self.i] is None:
key_values = X
else:
key_values = torch.cat((state[2][self.i], X), axis=1)
state[2][self.i] = key_values
if self.training:
batch_size, num_steps, _ = X.shape
# dec_valid_lens的开头:(batch_size,num_steps),
# 其中每一行是[1,2,...,num_steps]
dec_valid_lens = torch.arange(
1, num_steps + 1, device=X.device).repeat(batch_size, 1)
else:
dec_valid_lens = None

# 自注意力
X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
Y = self.addnorm1(X, X2)
# 编码器-解码器注意力。
# enc_outputs的开头:(batch_size,num_steps,num_hiddens)
Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
Z = self.addnorm2(Y, Y2)
return self.addnorm3(Z, self.ffn(Z)), state

Transformer解码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class TransformerDecoder(d2l.AttentionDecoder):
def __init__(self, vocab_size, key_size, query_size, value_size,
num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, num_layers, dropout, **kwargs):
super(TransformerDecoder, self).__init__(**kwargs)
self.num_hiddens = num_hiddens
self.num_layers = num_layers
self.embedding = nn.Embedding(vocab_size, num_hiddens)
self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
self.blks = nn.Sequential()
for i in range(num_layers):
self.blks.add_module("block"+str(i),
DecoderBlock(key_size, query_size, value_size, num_hiddens,
norm_shape, ffn_num_input, ffn_num_hiddens,
num_heads, dropout, i))
self.dense = nn.Linear(num_hiddens, vocab_size)

def init_state(self, enc_outputs, enc_valid_lens, *args):
return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

def forward(self, X, state):
X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
self._attention_weights = [[None] * len(self.blks) for _ in range (2)]
for i, blk in enumerate(self.blks):
X, state = blk(X, state)
# 解码器自注意力权重
self._attention_weights[0][
i] = blk.attention1.attention.attention_weights
# “编码器-解码器”自注意力权重
self._attention_weights[1][
i] = blk.attention2.attention.attention_weights
return self.dense(X), state

@property
def attention_weights(self):
return self._attention_weights

第十四章 自然语言处理:预训练

69 BERT预训练

1.
NLP里的迁移学习

  • 使用预训练好的模型来抽取词、句子的特征
    • 例如word2vec或语言模型
  • 不更新预训练好的模型
  • 需要构建新的网络来抓取新任务需要的信息
    • word2vec忽略了时序信息,语言模型只看了一个方向
      2.
      BERT的动机
  • 基于微调的NLP模型
  • 预训练的模型抽取了足够多的信息
  • 新的任务只需要增加一个简单的输出层
    3.
    BERT架构
  • 只有编码器的Transformer
  • 两个版本:
    • Base:blocks=12,hidden size=768,heads=12,parameters=110M
    • Large:blocks=24,hidden size=1024,heads=16,parameters=340M
  • 在大规模数据上训练>3B词
    4.
    对输入的修改
  • 因为只有编码器,所以source和target都得给编码器
  • 每个样本是一个句子对
  • 加入额外的片段嵌入,用segment embedding分隔
  • 位置编码可学习,每个token有一个position embedding
  • 图片
    5.
    预训练任务1:带掩码的语言模型
  • Transformer的编码器是双向的,标准语言模型要求单向
  • 带掩码的语言模型每次随机(15%概率)将一些词元换成mask,这样Transformer的编码器仍然可以做双向,相当于完形填空
  • 因为微调任务中不会出现mask,所以预训练中
    • 80%概率,将选中的词元变成mask
    • 10%概率,换成一个随机词元
    • 10%概率,保持原有的词元
      6.
      预训练任务2:下一个句子预测
  • 预测一个句子对中两个句子是否相邻
  • 训练样本中:50%概率选择相邻句子对,50%概率选择随机句子对
  • 将cls对应输出放到一个全连接层预测
    7.
    代码

Input Representation

1
2
3
4
5
6
7
8
def get_tokens_and_segments(tokens_a, tokens_b=None):
"""Get tokens of the BERT input sequence and their segment IDs."""
tokens = ['<cls>'] + tokens_a + ['<sep>']
segments = [0] * (len(tokens_a) + 2)
if tokens_b is not None:
tokens += tokens_b + ['<sep>']
segments += [1] * (len(tokens_b) + 1)
return tokens, segments

BERTEncoder class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BERTEncoder(nn.Module):
"""BERT encoder."""
def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
ffn_num_hiddens, num_heads, num_layers, dropout,
max_len=1000, key_size=768, query_size=768, value_size=768,
**kwargs):
super(BERTEncoder, self).__init__(**kwargs)
self.token_embedding = nn.Embedding(vocab_size, num_hiddens)
self.segment_embedding = nn.Embedding(2, num_hiddens)
self.blks = nn.Sequential()
for i in range(num_layers):
self.blks.add_module(f"{i}", d2l.EncoderBlock(
key_size, query_size, value_size, num_hiddens, norm_shape,
ffn_num_input, ffn_num_hiddens, num_heads, dropout, True))
self.pos_embedding = nn.Parameter(torch.randn(1, max_len,
num_hiddens))

def forward(self, tokens, segments, valid_lens):
X = self.token_embedding(tokens) + self.segment_embedding(segments)
X = X + self.pos_embedding.data[:, :X.shape[1], :]
for blk in self.blks:
X = blk(X, valid_lens)
return X

Masked Language Modeling

把要预测的位置的词所对应的encoder的输出拿到这里来预测位置的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MaskLM(nn.Module):
"""The masked language model task of BERT."""
def __init__(self, vocab_size, num_hiddens, num_inputs=768, **kwargs):
super(MaskLM, self).__init__(**kwargs)
self.mlp = nn.Sequential(nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.LayerNorm(num_hiddens),
nn.Linear(num_hiddens, vocab_size))

def forward(self, X, pred_positions):
num_pred_positions = pred_positions.shape[1]
pred_positions = pred_positions.reshape(-1)
batch_size = X.shape[0]
batch_idx = torch.arange(0, batch_size)
batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)
masked_X = X[batch_idx, pred_positions]
masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
mlm_Y_hat = self.mlp(masked_X)
return mlm_Y_hat

Next Sentence Prediction

1
2
3
4
5
6
7
8
class NextSentencePred(nn.Module):
"""The next sentence prediction task of BERT."""
def __init__(self, num_inputs, **kwargs):
super(NextSentencePred, self).__init__(**kwargs)
self.output = nn.Linear(num_inputs, 2)

def forward(self, X):
return self.output(X)

Putting All Things Together

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class BERTModel(nn.Module):
"""The BERT model."""
def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
ffn_num_hiddens, num_heads, num_layers, dropout,
max_len=1000, key_size=768, query_size=768, value_size=768,
hid_in_features=768, mlm_in_features=768,
nsp_in_features=768):
super(BERTModel, self).__init__()
self.encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape,
ffn_num_input, ffn_num_hiddens, num_heads, num_layers,
dropout, max_len=max_len, key_size=key_size,
query_size=query_size, value_size=value_size)
self.hidden = nn.Sequential(nn.Linear(hid_in_features, num_hiddens),
nn.Tanh())
self.mlm = MaskLM(vocab_size, num_hiddens, mlm_in_features)
self.nsp = NextSentencePred(nsp_in_features)

def forward(self, tokens, segments, valid_lens=None, pred_positions=None):
encoded_X = self.encoder(tokens, segments, valid_lens)
if pred_positions is not None:
mlm_Y_hat = self.mlm(encoded_X, pred_positions)
else:
mlm_Y_hat = None
nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
return encoded_X, mlm_Y_hat, nsp_Y_hat

第十五章 自然语言处理:应用

70 BERT 微调

1.

  • BERT对每一个词元返回抽取了上下文信息的特征向量
  • 不同的任务使用不同的特征
  • 图片
    2.
    句子分类
  • 将cls对应的向量输入到全连接层分类
  • 图片
    3.
    命名实体识别
  • 识别一个词元是不是命名实体,例如人名、机构、位置
  • 将非特殊词元放进全连接层分类
  • 图片
    4.
    问题回答
  • 给定一个问题,和描述文字,找出一个片段作为回答
  • 对片段中的每个词元预测它是不是回答的开头和结尾(三分类问题)
  • 图片
  • 前面是问题,后面是描述

第十一章 优化算法

1.
优化问题

  • 一般形式:minimize f(x) subject to x $\in$C
    2.
    局部最小vs全局最小
  • 迭代算法一般只能找到局部最小
    3.
    凸集
  • $\alpha x+(1-\alpha)y\in C \forall\alpha\in [0,1]\forall x,y\in C$
  • 图片
    4.
    凸函数
  • $f(\alpha x+(1-\alpha)y)\leqslant \alpha f(x)+(1-\alpha)f(y)\in C \forall\alpha\in [0,1]\forall x,y\in C$
  • 图片
    5.
    凸函数优化
  • 如果代价函数f是凸的,且限制集合c是凸的,那么就是凸优化问题,那么局部最小就是全局最小
  • 严格凸优化问题有唯一的全局最小
    6.
    例子
  • 凸:线性回归,sofmax
  • 非凸:其他:MLP,CNN,RNN,attention
    7.
    冲量法
  • 不会一下很猛地改变很多,因为每次改变的方向不仅取决于当前的梯度,还却决于之前的梯度
  • 图片
    8.
    Adam
  • 对学习率不那么敏感,非常平滑
  • $vt=\beta_1v{t-1}+(1-\beta_1)g_t,\beta_1=0.9$
  • $vt=(1-\beta_1)(g_t+\beta_1g{t-1}+\beta1^2g{t-2}+\beta1^3g{t-3}+…)$
  • 无穷等比求和(betan次方)->权重和为(1-beta)1/(1-beta)=1
  • $\sum_{i=0}^{t}\beta_1^i=\frac{1-\beta_1^t}{1-\beta_1}$->在t比较小时修正$\hat{v_t}=\frac{v_t}{1-\beta_1^t}$
  • $st=\beta_2s{t-1}+(1-\beta_2)g_t^2,\beta_2=0.999$
  • 在t比较小时修正$\hat{v_t}=\frac{v_t}{1-\beta_2^t}$
  • 计算重新调整后的梯度$g_t’=\frac{\hat{v_t}}{\sqrt{\hat{s_t}}+\epsilon}$,分子是使得变化比较平滑,分母是和分子制衡,使得不同维度梯度变化差不多,不会让太大的影响比较小的
  • 最后用这个梯度更新

第八章 循环神经网络

8.1 序列模型

1.
很多数据具有时序结构
2.
统计工具

  • 在时间t观察到$x_t$,得到T个不独立的随机变量($x_1,…x_T$)~p(x
  • p(x)=p(x1)p(x2|x1)p(x3|x1,x2)…p(xT|x1,…xT-1)
  • p(x)=p(xT)p(xT-1|xT)p(xT-2|xT-1,xT-2)…p(x1|x1,…xT-1)
  • 对条件概率建模:p(xt|x1…xt-1)=p(xt|f(x1…xt-1)),对见过的数据建模,也称自回归模型
    3.
    方案A 马尔可夫假设
  • 假设当前数据只和$\tau$个过去数据点相关
  • p(xt|x1…xt-1)=p(xt|xt-$\tau$…xt-1)=p(xt|f(xt-$\tau$…xt-1)),例如在过去数据上训练MLP模型
  • $\tau$=1时,得到一阶马尔可夫模型, p(x)=$\Pi$p(xt|xt-1)
    4.
    方案B 潜变量模型
  • 潜变量ht=f(x1…xt-1)->xt=p(xt|ht)
  • 图片

8.2 文本预处理

1
2
3
import collections
import re
from d2l import torch as d2l

将数据集读取到由文本行组成的列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',
'090b5e7e70c295757f55df93cb0a180b9691891a')
'''读取一本书'''
def read_time_machine():
"""Load the time machine dataset into a list of text lines."""
with open(d2l.download('time_machine'), 'r') as f:
lines = f.readlines()
return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]
'''把不是字母和空格的都变成空格'''
lines = read_time_machine()
print(f'
print(lines[0])
print(lines[10])

text lines: 3221
the time machine by h g wells
twinkled and his usually pale face was flushed and animated the

每个文本序列被拆分成一个标记列表

文本行列表lines-文本序列line-词元列表tokens-词元token

1
2
3
4
5
6
7
8
9
10
11
12
def tokenize(lines, token='word'):  
"""将文本行拆分为单词或字符标记。"""
if token == 'word':
return [line.split() for line in lines]
elif token == 'char':
return [list(line) for line in lines]
else:
print('错误:未知令牌类型:' + token)

tokens = tokenize(lines)
for i in range(11):
print(tokens[i])

[‘the’, ‘time’, ‘machine’, ‘by’, ‘h’, ‘g’, ‘wells’]
[]
[]
[]
[]
[‘i’]
[]
[]
[‘the’, ‘time’, ‘traveller’, ‘for’, ‘so’, ‘it’, ‘will’, ‘be’, ‘convenient’, ‘to’, ‘speak’, ‘of’, ‘him’]
[‘was’, ‘expounding’, ‘a’, ‘recondite’, ‘matter’, ‘to’, ‘us’, ‘his’, ‘grey’, ‘eyes’, ‘shone’, ‘and’]
[‘twinkled’, ‘and’, ‘his’, ‘usually’, ‘pale’, ‘face’, ‘was’, ‘flushed’, ‘and’, ‘animated’, ‘the’]

构建一个字典,通常也叫做词表(vocabulary),用来将字符串标记映射到从0开始的数字索引中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Vocab:  
"""文本词表"""
def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
'''如果某个词出现次数小于min_freq,就不要了;保存那些被保留的词元, 例如:填充词元(“<pad>”); 序列开始词元(“<bos>”); 序列结束词元(“<eos>”)'''
if tokens is None:
tokens = []
if reserved_tokens is None:
reserved_tokens = []
counter = count_corpus(tokens)
self.token_freqs = sorted(counter.items(), key=lambda x: x[1],
reverse=True)'''频率排序'''
self.unk'''unknown记为0''', uniq_tokens = 0, ['<unk>'] + reserved_tokens
uniq_tokens += [
token for token, freq in self.token_freqs
if freq >= min_freq and token not in uniq_tokens]
self.idx_to_token, self.token_to_idx = [], dict()'''下标和token相互转换'''
for token in uniq_tokens:
self.idx_to_token.append(token)
self.token_to_idx[token] = len(self.idx_to_token) - 1

def __len__(self):
return len(self.idx_to_token)

def __getitem__(self, tokens):'''token->index,返回index'''
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]

def to_tokens(self, indices):'''index->token,返回token'''
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]

def count_corpus(tokens):
"""统计标记的频率。"""
if len(tokens) == 0 or isinstance(tokens[0], list):
tokens = [token for line in tokens for token in line]
return collections.Counter(tokens)


构建词汇表

1
2
vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[:10])

[(‘unk’, 0), (‘the’, 1), (‘i’, 2), (‘and’, 3), (‘of’, 4), (‘a’, 5), (‘to’, 6), (‘was’, 7), (‘in’, 8), (‘that’, 9)]

将每一行文本转换成一个数字索引列表

1
2
3
4
for i in [0, 10]:
print('words:', tokens[i])
print('indices:', vocab[tokens[i]])
'''当编写_getitem_方法并包含在你的类中时,python解释器会在实例上使用方括号自动调用方法'''

words: [‘the’, ‘time’, ‘machine’, ‘by’, ‘h’, ‘g’, ‘wells’]
indices: [1, 19, 50, 40, 2183, 2184, 400]
words: [‘twinkled’, ‘and’, ‘his’, ‘usually’, ‘pale’, ‘face’, ‘was’, ‘flushed’, ‘and’, ‘animated’, ‘the’]
indices: [2186, 3, 25, 1044, 362, 113, 7, 1421, 3, 1045, 1]

将所有内容打包到load_corpus_time_machine函数中

1
2
3
4
5
6
7
8
9
10
11
12
def load_corpus_time_machine(max_tokens=-1):  
"""返回时光机器数据集的标记索引列表和词汇表。"""
lines = read_time_machine()
tokens = tokenize(lines, 'char')
vocab = Vocab(tokens)'''对应字典'''
corpus = [vocab[token] for line in tokens for token in line]'''每一个单词(这里是字母)的数字'''
if max_tokens > 0:
corpus = corpus[:max_tokens]
return corpus, vocab

corpus, vocab = load_corpus_time_machine()
len(corpus), len(vocab)

(170580, 28) 28=16+ukn+空格

8.3 语言模型

1.
给定文本序列x1-xT,语言模型的目标是估计联合概率p(x1-xT)
2.
使用计数来建模

  • 假设序列长度为2,$p(x,x’)=p(x)p(x’|x)=\frac{n(x)}{n}\frac{n(x,x’)}{n(x)}$,n是语料库中的总词数,n(x),n(x,x’)是单个单词和连续单词对的出现次数
    3.
    N元语法
  • 当序列很长时,因为文本量不够大,很可能n(x1-xT)<=1
  • 使用马尔可夫假设
    • 一元语法:$p(x_1,x_2,x_3,x_4)=p(x_1)p(x_2)p(x_3)p(x_4)=\frac{n(x_1)}{n}\frac{n(x_2)}{n}\frac{n(x_3)}{n}\frac{n(x_4)}{n}$
    • 一元语法:$p(x_1,x_2,x_3,x_4)=p(x_1)p(x_2|x_1)p(x_3|x_2)p(x_4|x_3)=\frac{n(x_1)}{n}\frac{n(x_1,x_2)}{x_1}\frac{n(x_2,x_3)}{x_2}\frac{n(x_3,x_4)}{x_3}$
      4.
      代码
      1.
      F1:随机地生成一个小批量数据的特征和标签以供读取。 在随机采样中,每个样本都是在原始的长序列上任意捕获的子序列
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      def seq_data_iter_random(corpus, batch_size, num_steps'''tau'''):  
      """使用随机抽样生成一个小批量子序列。"""
      '''不同于8.1中的遍历每个都需要用很多次,这个是把总长切成n份,然后一个epoch就n份都过一遍。每个epoch开始的起点都是从0~tau中随机取,这样份就不会重复'''
      corpus = corpus[random.randint(0, num_steps - 1):]
      num_subseqs = (len(corpus) - 1) // num_steps
      initial_indices = list(range(0, num_subseqs * num_steps, num_steps))'''每个子序列开始的下标'''
      random.shuffle(initial_indices)

      def data(pos):
      return corpus[pos:pos + num_steps]

      num_batches = num_subseqs // batch_size'''在n段里面取batch'''
      for i in range(0, batch_size * num_batches, batch_size):
      initial_indices_per_batch = initial_indices[i:i + batch_size]'''取了batch size个开始的下标'''
      X = [data(j) for j in initial_indices_per_batch]'''生成batch size个段'''
      Y = [data(j + 1) for j in initial_indices_per_batch]
      yield torch.tensor(X), torch.tensor(Y)
      2.
      F2:保证两个相邻的小批量中的子序列在原始序列上也是相邻的

8.4 循环神经网络

1.
图片
更新隐藏状态:$ht=\phi(W{hh}h{t-1}+W{hx}x{t-1}+b_h)$ (去掉$W{hh}h{t-1}$就是MLP)
$o_t=W
{ho}h_{t}+b_o$
图片
图片
2.
困惑度 perplexity

  • 衡量一个语言模型的好坏可以用平均交叉熵$\pi=\frac{1}{n}\sum{i=1}^{n}-logp(x_t|x{t-1},…)$ 其中p是语言模型预测的概率,xt是真实词
  • NLP使用困惑度exp(pi)来衡量,1表示完美,无穷大是最差情况
    3.
    梯度裁剪
  • 迭代中计算T个时间步上的梯度,在反向传播中产生长度为O(T)的矩阵乘法链,导致数值不稳定
  • 梯度裁剪能有效预防梯度爆炸
    • 如果梯度长度超过$\theta$,那么将拖回$\theta$:$g\leftarrow min(1,\frac{\theta}{\lVert g\rVert})g$
      4.
      其他应用
      图片
      5.
      由于在当前时间步中, 隐状态使用的定义与前一个时间步中使用的定义相同, 因此 $ht=\phi(W{hh}h{t-1}+W{hx}x_{t-1}+b_h)$的计算是循环的

8.5 循环神经网络的从零开始实现

1
2
3
4
5
6
7
8
9
%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

独热编码

给一个下标,用向量来表示
将每个索引映射为相互不同的单位向量: 假设词表中不同词元的数目为N(即len(vocab)), 词元索引的范围为0到N−1。 如果词元的索引是整数i, 那么我们将创建一个长度为N的全0向量, 并将第i处的元素设置为1。 此向量是原始词元的一个独热向量

1
F.one_hot(torch.tensor([0, 2]), len(vocab))

tensor([[1, 0, 0, 0, 0, 0, 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, 0, 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
2
X = torch.arange(10).reshape((2, 5))
F.one_hot(X.T, 28).shape

torch.Size([5, 2, 28])

初始化循环神经网络模型的模型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size

def normal(shape):
return torch.randn(size=shape, device=device) * 0.01

W_xh = normal((num_inputs, num_hiddens))
W_hh = normal((num_hiddens, num_hiddens))
b_h = torch.zeros(num_hiddens, device=device)
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
params = [W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.requires_grad_(True)
return params

一个init_rnn_state函数在初始化时返回隐藏状态

1
2
def init_rnn_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device),)

下面的rnn函数定义了如何在一个时间步计算隐藏状态和输出

1
2
3
4
5
6
7
8
9
def rnn(inputs, state, params):
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
Y = torch.mm(H, W_hq) + b_q
outputs.append(Y)
return torch.cat(outputs, dim=0), (H,)

创建一个类来包装这些函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class RNNModelScratch:  
"""从零开始实现的循环神经网络模型"""
def __init__(self, vocab_size, num_hiddens, device, get_params,
init_state, forward_fn):
self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
self.params = get_params(vocab_size, num_hiddens, device)
self.init_state, self.forward_fn = init_state, forward_fn

def __call__(self, X, state):
X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
return self.forward_fn(X, state, self.params)

def begin_state(self, batch_size, device):
return self.init_state(batch_size, self.num_hiddens, device)

检查输出是否具有正确的形状

1
2
3
4
5
6
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape

(torch.Size([10, 28]), 1, torch.Size([2, 512]))

首先定义预测函数来生成用户提供的prefix之后的新字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def predict_ch8(prefix, num_preds, net, vocab, device):  
"""在`prefix`后面生成新字符。"""
state = net.begin_state(batch_size=1, device=device)
outputs = [vocab[prefix[0]]]
get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(
(1, 1))
for y in prefix[1:]:
_, state = net(get_input(), state)
outputs.append(vocab[y])
for _ in range(num_preds):
y, state = net(get_input(), state)
outputs.append(int(y.argmax(dim=1).reshape(1)))
return ''.join([vocab.idx_to_token[i] for i in outputs])

predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())

梯度裁剪

1
2
3
4
5
6
7
8
9
10
def grad_clipping(net, theta):  
"""裁剪梯度。"""
if isinstance(net, nn.Module):
params = [p for p in net.parameters() if p.requires_grad]
else:
params = net.params
norm = torch.sqrt(sum(torch.sum((p.grad**2)) for p in params))
if norm > theta:
for param in params:
param.grad[:] *= theta / norm

定义一个函数来训练只有一个迭代周期的模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
"""训练模型一个迭代周期(定义见第8章)。"""
state, timer = None, d2l.Timer()
metric = d2l.Accumulator(2)
for X, Y in train_iter:
if state is None or use_random_iter:
state = net.begin_state(batch_size=X.shape[0], device=device)
else:
if isinstance(net, nn.Module) and not isinstance(state, tuple):
state.detach_()
else:
for s in state:
s.detach_()
y = Y.T.reshape(-1)
X, y = X.to(device), y.to(device)
y_hat, state = net(X, state)
l = loss(y_hat, y.long()).mean()
if isinstance(updater, torch.optim.Optimizer):
updater.zero_grad()
l.backward()
grad_clipping(net, 1)
updater.step()
else:
l.backward()
grad_clipping(net, 1)
updater(batch_size=1)
metric.add(l * y.numel(), y.numel())
return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

训练函数支持从零开始或使用高级API实现的循环神经网络模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
use_random_iter=False):
"""训练模型(定义见第8章)。"""
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
legend=['train'], xlim=[10, num_epochs])
if isinstance(net, nn.Module):
updater = torch.optim.SGD(net.parameters(), lr)
else:
updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)
for epoch in range(num_epochs):
ppl, speed = train_epoch_ch8(net, train_iter, loss, updater, device,
use_random_iter)
if (epoch + 1) % 10 == 0:
print(predict('time traveller'))
animator.add(epoch + 1, [ppl])
print(f'困惑度 {ppl:.1f}, {speed:.1f} 标记/秒 {str(device)}')
print(predict('time traveller'))
print(predict('traveller'))

8.6 循环神经网络的简洁实现

第九章 现代循环神经网络

9.1 门控循环单元GRU

1.
不是所有观察值同等重要->只记住相关的观察需要

  • 能关注的机制:更新门
  • 能遗忘的机制:重置门
    2.

    $\sigma$是sigmoid
    图片
    候选隐藏状态
    图片
    Rt可以学习,sigmoid介于0-1之间,0就忘了过去的Ht,只和Xt有关,1就过去的Ht全留着,和正常的隐藏状态一样
    图片
    Zt可以学习,sigmoid介于0-1之间,0就拿来候选Ht(调整过的,用了
    Xt),1就直接用上一个Ht,和新输入Xt没有关系
    3.
    门控循环单元具有以下两个显著特征:
  • 重置门有助于捕获序列中的短期依赖关系
  • 更新门有助于捕获序列中的长期依赖关系

9.2 长短期记忆网络LSTM

1.

  • 忘记门:将值朝0减少
  • 输入门:决定是否忽略掉输入数据
  • 输出门:决定是不是使用隐状态
    2.
    图片
    图片
    图片
    图片
    只要输出门接近1,我们就能够有效地将所有记忆信息传递给预测部分, 而对于输出门接近0,我们只保留记忆元内的所有信息,而不需要更新隐状态。(只有隐状态才会传递到输出层, 而记忆元Ct不直接参与输出计算)

9.3 深度循环神经网络

图片

9.4 双向循环神经网络

1.
RNN只看过去,但我们也可以看未来(完形填空)
图片
2.
总结:

  • 双向循环神经网络通过反向更新的隐藏层来利用反向时间信息
  • 通常用来对序列抽取特征、填空,而不是预测未来(推理)

9.5 机器翻译与数据集

  • 下载和预处理数据集
  • 几个预处理步骤
  • 词元化
  • 词汇表
  • 序列样本都有一个固定的长度 截断或填充文本序列
  • 转换成小批量数据集用于训练
  • 训练模型

9.6 编码器-解码器架构

1.
CNN

  • 编码器:将输入编程成中间表达形式(特征)
  • 解码器:将中间表示解码输出
  • 图片
    2.
    RNN
  • 编码器:将文本表示成向量
  • 解码器:向量表示成输出
  • 图片
    3.
    架构
  • 编码器处理输入
  • 解码器处理输出
  • 图片

9.7 序列到序列学习seq2seq

1.
seq2seq
图片

  • 编码器是一个RNN,读取输入句子(可以是双向)
  • 解码器使用另外一个RNN来输出
    2.
    细节
  • 编码器是没有输出的RNN
  • 编码器最后时间步的隐藏状态用作解码器的初始隐藏状态
  • 图片
    3.
    训练
    训练时解码器使用目标句子作为输入
    图片
    4.
    衡量生成的好坏:BLEU
  • pn是预测中所有n-gram的精度
    • e.g.:标签序列A B C D E F 和预测序列A B B C D
    • p1=4/5,预测序列中五个只有第二个B没有出现
    • p2=3/4,预测序列中四个2元,BB没出现
    • p3=1/3,p4=0
  • BLEU:
    • $exp(min(0,1-\frac{len{label}}{len{pred}}))\prod{n=1}^{k}p{n}^{1/2^{n}}$
    • if len label> len pred,后一项为负,那么exp负数就变成很小的数了,BLEU越大越好,最大就是exp0=1,所以min用来惩罚过短的预测
    • 后面的连乘 pn<1所以n越大这个乘积越大,即长匹配有高权重

9.8 束搜索

1.
贪心搜索

  • 在seq2seq中使用了贪心搜索来预测序列,即将当前时刻预测概率最大的词输出
  • 但贪心并不一定最优
  • e.g.图片
    2.
    穷举搜索
  • 最优算法:对所有可能的序列,计算概率,选取最好的
  • 但计算上不可行
    3.
    束搜索
  • 保存最好的k个候选
  • 在每个时刻,对每个候选新加一项(n种可能),在kn中选出最好的k个
  • e.g.图片
  • 图片
  • 长句子的概率越低,为了避免每次都只找短的,所以前面乘了一个东西,L是长度,越长,分母越大,这个玩意儿越小,而log出来是负数,所以整体值越大,相当于对长句子做了补偿

第十二章 计算性能

31 深度学习硬件: CPU和GPU

图片
1.
提升CPU利用率

  • 提升空间和时间的内存本地性
    • 时间:重用数据使得保持它们再缓存里
    • 空间:按序读写数据使得可以预读取
  • 并行来利用所有核
    图片
    2.
    提升GPU利用率
  • 并行
    • 使用数千个线程
  • 内存本地性
    • 缓存更小,架构更简单
  • 少用控制语句
    • 支持有限
    • 同步开销大
      3.
      不要频繁在CPU和GPU之间传输数据:带宽限制,同步开销
      4.
      总结
  • CPU:可以处理通用计算。性能优化考虑数据读写效率和多线程
  • GPU:使用更多的小核和更好的内存带宽,适合能大规模并行的计算任务

32 深度学习硬件: TPU和其他

1.
DSP:数字信号处理

  • 为数字信号处理算法设计:点积,卷积,FFT
    2.
    FPGA:可编程阵列
  • 有大量可以编程逻辑单元和可配置的连接
    3.
    AI ASIC
  • 大公司自己的芯片 e.g.Google TPU
  • 核心:systolic array
    • 图片
    • 计算单元(PE)阵列,特别适合做矩阵乘法
      4.
      总结
      图片

33 单机多卡并行

1.
将小批量计算分到多个GPU上

  • 数据并行图片
  • 模型并行
  • 通道并行(数据+模型)

34 多GPU训练实现

35 分布式计算

图片
图片
1.
计算每一个小批量
图片

  • 每个计算服务器读取小批量中的一块
  • 进一步将数据切分到每个GPU上
  • 每个worker从参数服务器那里获取模型参数
  • 复制参数到每个GPU上
  • 每个GPU计算精度
  • 将所有GPU上的梯度求和
  • 梯度传回服务器,每个服务器对梯度求和并更新参数
    2.
    同步SGD:n个GPU,每个每次处理b个样本,同步SGD等价于单GPU运行批量大小为nb的SGD->也即得到相对单个的n倍加速
    3.
    性能的权衡
    图片
    批量大小增加导致需要更多计算来得到给定的模型精度

第十三章 计算机视觉

13.1 图像增广

1.
数据增强:增加一个已有数据集。使得有更多的多样性

  • 在语音里加入各种不同的背景噪音
  • 改变图片的颜色和形状
    2.
    类型:
  • 翻转:上下、左右
  • 切割:然后变形到固定形状
    • 随机高宽比
    • 随机大小
    • 随机位置
  • 颜色:色调、饱和度、明亮度、对比度
    3.
    总结:数据增广通过变形数据来获取多样性从而使得模型泛化性能更好

13.2 微调

1.
网络架构
图片
一个神经网络一般可以分成两块:

  • 特征抽取将原始像素变成容易线性分割的特征
  • 线性分类器来做分类
    2.
    图片
    源模型的特征抽取部分可能仍可以对目标模型进行特征抽取,当然线性分类器用不了了了
    ->预训练的模型的特征抽取部分可以复制给目标模型,然后微调即可使用,输出层就只能从头训练了(随机初始化)
    即:迁移学习,微调(fine-tuning)是其中一个常用技巧
    3.
    训练:使用更小的学习率和更少的数据迭代(更强的正则化)
    源数据集远复杂与目标数据集的话通常微调效果会更好,有助于泛化
    4.
    trick
  • 重用分类器权重
    • 源数据集可能也有目标数据中的部分符号->可以使用预训练好的模型分类器中对应标号对应的向量来做初始化
  • 固定一些层
    • 神经网络学习有层次的特征:低层次的特征更加通用,高层次的特征更跟数据集相关->可以固定底部一些层的参数不参与更新(更强的正则)
  • 对特征提取部分学习率用小一点,对线性分类层学习率用大的(乘以10)
    5.
    总结:微调通过使用在大数据上得到的预训练好的模型来初始化权重来完成提升精度->
    预训模型质量很是重要,微调通常速度更快精度更高
    6.
    代码:调用net时加上pretrained=True

13.3 目标检测和边界框

1.
边缘框(bounding box):四个数字定义

  • (左上x,左上y,右上x,右上y)
  • (左上x,左上y,宽,高)
    2.
    目标检测数据集
    图片文件名,物体类别,边缘框
    3.
    总结:物体检测识别图片里面的多个物体的类别和位置,位置通常用边缘框表示

13.4 锚框

1.
一类目标检测算法基于锚框(anchor box):

  • 提出多个被称为锚框的区域(边缘框)
  • 预测每个锚框是否含有关注的物体
  • 如果是,基于此预测从这个锚框到真实边缘框的偏移
    2.
    IoU-交并比:用来计算两个框之间的相似度
    0~1.0:无重叠,1:重合
    图片
    3.
    赋予锚框标号(每个锚框是一个训练样本):
  • 标注成背景
  • 关联上一个真实边缘框
  • 图片
    4.
    使用非极大值抑制(NMS)输出
  • 每个锚框预测一个边缘框,而NMS可以合并相似的预测
  • 1.选中非背景类(不是背景是物体)的最大预测值(softmax回归,概率最大值),即计算每个类别的预测概率,最大的概率p所对应的类别就是预测的类别
  • 2.去掉其他所有和这个被选中的的IoU值大于$\theta$的预测
  • 3.重复上述过程直到所有预测要么被选中要么被去掉
    5.
    总结
  • 首先生成大量锚框,并赋予编号,每个锚框作为一个样本来进行训练
  • 在预测的时候,使用NMS来去掉冗余的预测
    6.
    代码实现
    以每一个像素为中心,生成不同高宽的锚框
    锚框的宽度和高度分别是$w\sqrt{s}\sqrt{r}$和$h\sqrt{s}/\sqrt{r}$。我们只考虑组合:
    $(s_1,r_1),(s_1,r_2),…,(s_1,r_m),(s_2,r_1),(s_3,r_1),…,(s_n,r_1)$
    其中w’、h’是锚框的宽和高,w、h是图片的宽和高,s是锚框的大小(占图片大小的百分比),r是锚框的宽高比与图片的宽高比之比
    $w\times\sqrt\frac{size\ of\ box}{size\ of\ picture}\times\sqrt{\frac{w’}{h’}/\frac{w}{h}}=w\times\sqrt{\frac{h’\times w’}{h\times w}}\times\sqrt\frac{w’\times h}{h’\times w}=w’$

训练流程:
训练数据集是带有ground truth bounding box和class的,就像图片分类的带有class一样

  • 用上述算法,生成许多锚框。(这里是对每个pixel作用)
    • multibox_prior(data,sizes,ratios)->Y(批量大小,锚框的数量,4)(1,图片像素数x(s+r-1),4个坐标量)
  • 用ground truth 框(真实边界框)去标记所有1中生成的锚框(对应代码中的multibox_targe函数) 标记方法:计算所有锚框和ground-truth的IoU值,给每一个锚框分配一个真实边界框(小于阈值的为背景,大于的选一个最接近的ground-truth),即用最近的那个标记,详见3.
    • assign_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5)->anchors_bbox_map
  • 标注类别:锚框的类别与被分配的真实边界框的类别相同
  • 标注偏移量:对刚刚标记好的所有锚框预测偏移量(offset)(背景类偏移量为零(负类)新类别的整数索引递增一,对应offset_boxes函数) 图片
    • offset_boxes(anchors, assigned_bb, eps=1e-6)->offset
    • multibox_target(anchors, labels(真实边框))->(bbox_offset, bbox_mask, class_labels)
    • 返回的第二个元素是掩码(mask)变量,形状为(批量大小,锚框数的四倍)。 掩码变量中的元素与每个锚框的4个偏移量一一对应。 由于我们不关心对背景的检测,负类的偏移量不应影响目标函数。 通过元素乘法,掩码变量中的零将在计算目标函数之前过滤掉负类偏移量。
  • 一个预测好的边界框根据其中某个带有预测偏移量的锚框生成->offset_inverse函数,该函数将锚框和偏移量预测作为输入,并应用逆偏移变换来返回预测的边界框坐标。
    • offset_inverse(anchors, offset_preds)->predicted_bbox
  • NMS合并属于同一目标的类似的预测边界框(选)(挑一个预测最好的,然后把没我预测的好,但是和我预测的很近的框框删掉)
    • multibox_detection(cls_probs(预测的概率), offset_preds, anchors, nms_threshold=0.5,pos_threshold=0.009999999)->torch.stack(out)
    • 我们可以看到返回结果的形状是(批量大小,锚框的数量,6)。 最内层维度中的六个元素提供了同一预测边界框的输出信息。 第一个元素是预测的类索引,从0开始(0代表狗,1代表猫),值-1表示背景或在非极大值抑制中被移除了。 第二个元素是预测的边界框的置信度。 其余四个元素分别是预测边界框左上角和右下角的(x,y)轴坐标(范围介于0和1之间)
    • tensor([[[ 0.00, 0.90, 0.10, 0.08, 0.52, 0.92],
      [ 1.00, 0.90, 0.55, 0.20, 0.90, 0.88],
      [-1.00, 0.80, 0.08, 0.20, 0.56, 0.95],
      [-1.00, 0.70, 0.15, 0.30, 0.62, 0.91]]])
  • 最终得到 每一个 对象或物体 的 一个 最终预测边界框

13.5 多尺度目标检测

问题:为每个像素生成锚框太多了->均匀抽样一小部分像素,以他们为中心生成锚框
另外,在不同尺度下,可以生成不同数量和不同大小的锚框

  • 比起较大的目标,较小的目标在图像上出现的可能性更多样。 例如,1×1、1×2和2×2的目标可以分别以4、2和1种可能的方式出现在2×2图像上。 因此,当使用较小的锚框检测较小的物体时,我们可以采样更多的区域(压缩阶段的开始),而对于较大的物体,我们可以采样较少的区域

13.5.1 多尺度锚框

1.
读取图像,获得高和宽

1
2
3
4
5
6
7
%matplotlib inline
import torch
from d2l import torch as d2l

img = d2l.plt.imread('../img/catdog.jpg')
h, w = img.shape[:2]
h, w

2.
在特征图 (fmap) 上生成锚框 (anchors),每个单位(像素)作为锚框的中心
在特征图上按像素生成->在原图上均匀生成
feature map:某个卷积层的输出
1
2
3
4
5
6
7
def display_anchors(fmap_w, fmap_h, s):
'''获得特征图的宽和高,得到有多少个像素,然后以每个像素为中心生成对应(size)的锚框'''
d2l.set_figsize()
fmap = torch.zeros((1, 10, fmap_h, fmap_w))
anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])'''13.4实现的生成锚框的函数'''
bbox_scale = torch.tensor((w, h, w, h))'''anchor出来的是介于0~1所以显示的时候需要乘以真实图片的高和宽'''
d2l.show_bboxes(d2l.plt.imshow(img).axes, anchors[0] * bbox_scale)

3.
探测小目标
1
display_anchors(fmap_w=4, fmap_h=4, s=[0.15])

图片
压缩成了4x4,一共十六个像素,每个像素生成s+r-1=1+3-1=3个锚框
4.
将特征图的高度和宽度减小一半,然后使用较大的锚框来检测较大的目标
1
display_anchors(fmap_w=2, fmap_h=2, s=[0.4])

图片
5.
将特征图的高度和宽度减小一半,然后将锚框的尺度增加到0.8
1
display_anchors(fmap_w=1, fmap_h=1, s=[0.8])

6.
总结,s的设置,越后面的段越大

13.5.2 多尺度检测

假设我们有c张hxw的特征图,这c张特征图是CNN基于输入图像的前向传播算法获得的中间输出。每张特征图上都有hw个不同的空间位置,相同空间位置可以看作含有c个单元。特征图在相同空间位置的c个单元在输入图像上的感受野相同: 它们表征了同一感受野内的输入图像信息。 因此,我们可以将特征图在同一空间位置的c个单元变换为使用此空间位置生成的a个锚框类别和偏移量。 本质上,我们用输入图像在某个感受野区域内的信息,来预测输入图像上与该区域位置相近的锚框类别和偏移量
注:在卷积神经网络中,对于某一层的任意元素x,其感受野(receptive field)是指在前向传播期间可能影响x计算的所有元素(来自所有先前层)

13.6 目标检测数据集

目标检测中的标签还包含真实边界框的信息

13.7 单发多框检测SSD

1.SSD模型

由13.5.2,当不同层的特征图在输入图像上分别拥有不同大小的感受野时,它们可以用于检测不同大小的目标->利用深层神经网络在多个层次上对图像进行分层表示,从而实现多尺度目标检测
接近顶部的多尺度特征图较小,但具有较大的感受野,它们适合检测较少但较大的物体
图片

  • 首先进入base network(CNN),进行抽取特征
  • 然后进入右边的anchor box,对每一个像素生成锚框,然后预测类别和真实边界框
  • 之后通过多个卷积层来减半高宽
  • 在每段都生成锚框:越到后面压的越小,那么稍微取大一点的锚框,最后映射回原始的图片框的地方就很大了,所以底部端来拟合小物体,顶部段来拟合大物体
  • 不在一开始就搞size很大的原因是,这样的重复就会很多

2.总结

  • SSD通过单神经网络来检测模型
  • 在多个段的输出上进行多尺度的检测
  • 训练算法:首先把训练数据集中的图片分别经过上面那个ssd模型,得到一堆不同尺度下的锚框,和他们预测的类别与偏差,作为预测值。然后利用这些图片本来就标好的真实边界框为每个锚框标注类别和偏移量,作为标签值。最后利用预测值和标签值之间的差别,计算损失函数,通过优化算法训练。
  • 预测算法:把需要预测的图片放到训练好的模型中,输出生成的锚框的预测的类别和和偏差,然后计算每个锚框的概率,放入nms中去重,最后筛掉置信度低于阈值的,输出。

3.代码

1.类别预测层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
'''这里采用卷积层进行预测而不是全连接层,类似于NiN。在NiN中已经经过global pooling,高宽变成了1x1,所以输出通道数就是类别数。
我们这里需要预测的是每个像素生成的每个锚框的类别,所以总数量应该是总像素数(输入的高x宽)x每个像素的锚框数(s+r-1)x每个锚框预测的类别数(类别数+1)
这里卷积层kernal size=3,padding=1,也即输出保留输入的高宽,这样输出和输入在特征图宽和高上的空间坐标一一对应。
扫一遍我们对每个像素通过周围3x3的块来判断这个像素的类别,反应为一个数,然后通过通道数(在通道里)最后反映出这个像素的所有锚框在每个类别上的得分,index为i(q+1)+j(0≤j≤q)的通道代表了索引为i的锚框有关类别索引为j的预测
所以最后的输出每个维度都是信息,高、宽和通道
'''
def cls_predictor(num_inputs, num_anchors, num_classes):
return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1),
kernel_size=3, padding=1)'''+1是加上背景类,对每一个锚框都需要预测是哪一类的概率'''

2.边界框预测层

1
2
3
'''预测和真实的bounding box的offset,offset是四个值(差)->num_anchors*4'''
def bbox_predictor(num_inputs, num_anchors):
return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)

3.连接多尺度的预测

1
2
3
4
5
6
def forward(x, block):
return block(x)

Y1 = forward(torch.zeros((2, 8, 20, 20)), cls_predictor(8, 5, 10))
Y2 = forward(torch.zeros((2, 16, 10, 10)), cls_predictor(16, 3, 10))
Y1.shape, Y2.shape

(torch.Size([2, 55, 20, 20]), torch.Size([2, 33, 10, 10]))
->在不同的尺度下,特征图的形状或以同一单元为中心的锚框的数量可能会有所不同。 因此,不同尺度下预测输出的形状可能会有所不同

1
2
3
4
5
6
7
8
9
def flatten_pred(pred):
'''permute是调换顺序,这里把通道维放到了最后,这样就是对像素拉,把每个像素的每个锚框预测出来的类别放在一起。然后把4d的展成了2d的(batch size为一个维度,后面的三个展成一个维度'''
return torch.flatten(pred.permute(0, 2, 3, 1), start_dim=1)

def concat_preds(preds):
'''几个张量先flatten然后再合并,方便后面处理'''
return torch.cat([flatten_pred(p) for p in preds], dim=1)

concat_preds([Y1, Y2]).shape

torch.Size([2, 25300])(55x20x20+33x10x10)

4.高和宽减半块

1
2
3
4
5
6
7
8
9
10
11
12
def down_sample_blk(in_channels, out_channels):
blk = []
for _ in range(2):
blk.append(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
blk.append(nn.BatchNorm2d(out_channels))
blk.append(nn.ReLU())
in_channels = out_channels
blk.append(nn.MaxPool2d(2))
return nn.Sequential(*blk)

forward(torch.zeros((2, 3, 20, 20)), down_sample_blk(3, 10)).shape

torch.Size([2, 10, 10, 10])
同时改变通道数,和之前的神经网络架构中的stage一样

5.基本网络块

从抽特征到第一次对feature map做锚框中间的net

1
2
3
4
5
6
7
8
9
def base_net():
blk = []
num_filters = [3, 16, 32, 64]
for i in range(len(num_filters) - 1):
blk.append(down_sample_blk(num_filters[i], num_filters[i + 1]))
'''接了三个block,通道数3到16,16到32,32到64,每次高宽减半'''
return nn.Sequential(*blk)

forward(torch.zeros((2, 3, 256, 256)), base_net()).shape

torch.Size([2, 64, 32, 32])

6.完整的单发多框检测模型由五个模块组成

1
2
3
4
5
6
7
8
9
10
def get_blk(i):
if i == 0:
blk = base_net()
elif i == 1:
blk = down_sample_blk(64, 128)
elif i == 4:
blk = nn.AdaptiveMaxPool2d((1, 1))
else:'''i=2,3'''
blk = down_sample_blk(128, 128)
return blk

7.为每个块定义前向计算

1
2
3
4
5
6
def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
Y = blk(X)
anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)'''生成锚框'''
cls_preds = cls_predictor(Y)'''这里直接把Y传进去就可以了,因为函数不需要知道具体锚框长什么样,知道数量就可以了'''
bbox_preds = bbox_predictor(Y)
return (Y, anchors, cls_preds, bbox_preds)

8.超参数

0.2,0.37,0.54:0.17;0.272=$\sqrt{0.2\times0.54}$,从零到一逐步增大

1
2
3
4
sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],
[0.88, 0.961]]
ratios = [[1, 2, 0.5]] * 5'''常用组合'''
num_anchors = len(sizes[0]) + len(ratios[0]) - 1'''每一层两个size,三个ratio,所以4个锚框

9.定义完整的模型

setattr(object, name, value):object — 对象,name — 字符串,对象属性,value — 属性值
getattr(object, name[, default]): default — 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TinySSD(nn.Module):
def __init__(self, num_classes, **kwargs):
super(TinySSD, self).__init__(**kwargs)
self.num_classes = num_classes
idx_to_in_channels = [64, 128, 128, 128, 128]
for i in range(5):
setattr(self, f'blk_{i}', get_blk(i))
setattr(
self, f'cls_{i}',
cls_predictor(idx_to_in_channels[i], num_anchors,
num_classes))
setattr(self, f'bbox_{i}',
bbox_predictor(idx_to_in_channels[i], num_anchors))

def forward(self, X):
anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
for i in range(5):
X, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(
X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],
getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))
anchors = torch.cat(anchors, dim=1)
cls_preds = concat_preds(cls_preds)
cls_preds = cls_preds.reshape(cls_preds.shape[0], -1,
self.num_classes + 1)
bbox_preds = concat_preds(bbox_preds)
return anchors, cls_preds, bbox_preds

10.创建一个模型实例,然后使用它 执行前向计算

1
2
3
4
5
6
7
net = TinySSD(num_classes=1)
X = torch.zeros((32, 3, 256, 256))
anchors, cls_preds, bbox_preds = net(X)

print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)

output anchors: torch.Size([1, 5444, 4])1(所有的图片都和一张一样,因为是按像素生成的),5444个,4个坐标
output class preds: torch.Size([32, 5444, 2])批量大小32,5444个,1+1类
output bbox preds: torch.Size([32, 21776])批量大小32,5444个x4个差距

11.读取香蕉检测数据集

1
2
batch_size = 32
train_iter, _ = d2l.load_data_bananas(batch_size)

read 1000 training examples
read 100 validation examples

12.初始化其参数并定义优化算法

1
2
device, net = d2l.try_gpu(), TinySSD(num_classes=1)
trainer = torch.optim.SGD(net.parameters(), lr=0.2, weight_decay=5e-4)

13.定义损失函数和评价函数

由于我们不关心对背景的检测,负类的偏移量不应影响目标函数。 通过元素乘法,掩码变量中的零将在计算目标函数之前过滤掉负类偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cls_loss = nn.CrossEntropyLoss(reduction='none')
bbox_loss = nn.L1Loss(reduction='none')
'''可以看作是分类loss和回归loss加一起'''
def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
'''labels就是真实的类别和boundingbox,参见13.4'''
batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]
cls = cls_loss(cls_preds.reshape(-1, num_classes),'''把batch size和个数合一'''
cls_labels.reshape(-1)).reshape(batch_size, -1).mean(dim=1)
bbox = bbox_loss(bbox_preds * bbox_masks,
bbox_labels * bbox_masks).mean(dim=1)
return cls + bbox
'''下面的是评价函数'''
def cls_eval(cls_preds, cls_labels):
return float(
(cls_preds.argmax(dim=-1).type(cls_labels.dtype) == cls_labels).sum())'''类似之前分类问题的准确率'''

def bbox_eval(bbox_preds, bbox_labels, bbox_masks):
return float((torch.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())'''平均绝对误差'''

14.训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['class error', 'bbox mae'])
net = net.to(device)
for epoch in range(num_epochs):
metric = d2l.Accumulator(4)
net.train()
for features, target in train_iter:
timer.start()
trainer.zero_grad()
X, Y = features.to(device), target.to(device)
anchors, cls_preds, bbox_preds = net(X)'''生成多尺度锚框,为每个锚框预测类别和偏移量,对应图片分类的生成各类别的softmax概率值'''
bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)'''使用真实边界框为每个锚框标注类别和偏移量,在图片分类中直接用数据集中标注好的标签'''
l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
bbox_masks)'''根据上面算的预测值和label算损失函数'''
l.mean().backward()
trainer.step()
metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),
bbox_eval(bbox_preds, bbox_labels, bbox_masks),
bbox_labels.numel())
cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]
animator.add(epoch + 1, (cls_err, bbox_mae))
print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
print(f'{len(train_iter.dataset) / timer.stop():.1f} examples/sec on '
f'{str(device)}')

15.预测目标

1
2
3
4
5
6
7
8
9
10
11
12
X=torchvision.io.read_image('../img/banana.jpg').unsqueeze(0).float()
img = X.squeeze(0).permute(1, 2, 0).long()

def predict(X):
net.eval()'''预测模式'''
anchors, cls_preds, bbox_preds = net(X.to(device))
cls_probs = F.softmax(cls_preds, dim=2).permute(0, 2, 1)'''算每个的概率,用于后面的nms'''
output = d2l.multibox_detection(cls_probs, bbox_preds, anchors)
idx = [i for i, row in enumerate(output[0]) if row[0] != -1]
return output[0, idx]

output = predict(X)

16.筛选所有置信度不低于 0.9 的边界框,做为最终输出

1
2
3
4
5
6
7
8
9
10
11
12
def display(img, output, threshold):
d2l.set_figsize((5, 5))
fig = d2l.plt.imshow(img)
for row in output:
score = float(row[1])
if score < threshold:
continue
h, w = img.shape[0:2]
bbox = [row[2:6] * torch.tensor((w, h, w, h), device=row.device)]
d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')

display(img, output.cpu(), threshold=0.9)

13.8 区域卷积神经网络 R-CNN

图片

1.R-CNN

  • 使用启发式搜索算法来选取多个高质量提议区域(锚框也是一种选取方法)(多尺度下),每个提议区域标注类别和真实边缘框
  • 使用预训练好的模型(CNN),将每个提议区域变形为网络所需尺寸,并通过前向传播输出抽取的提议区域特征
  • 将每个提议区域的特征连同其标注的类别作为一个样本。训练多个支持向量机对目标分类,其中每个支持向量机用来判断样本是否属于某一个类别
  • 将每个提议区域的特征连同其标注的边界框作为一个样本,训练线性回归模型来预测真实边界框
    问题:第一、二步计算量太大

2.兴趣区域(RoI)池化层

利于做batch
图片

3.Fast R-CNN

关键:CNN不再是对每个锚框抽取,而是对整个图片
R-CNN的主要性能瓶颈在于,对每个提议区域,卷积神经网络的前向传播是独立的,而没有共享计算。 由于这些区域通常有重叠,独立的特征抽取会导致重复的计算
图片

  • 首先对图片用CNN进行处理,设输入为一张图像,将卷积神经网络的输出的形状记为1×c×h1×w1
  • 同时在原始图片上进行选择性搜索(选择锚框),n个。(标出了形状各异的兴趣区域)
  • 然后将选择好的锚框映射到处理过的图像上
  • 这些感兴趣的区域需要进一步抽取出形状相同的特征(比如指定高度h2和宽度w2),以便于连结后输出。
    • 在R-CNN中,先生成区域,在用卷积修改尺寸和提取特征,最后能不同区域统一用svm和回归进行预测,缺点就是每个都要卷一次。在fast里面,就卷一次,直接对图片卷,然后把区域映射上去,相当于先框区域再压缩,缺点是尺寸不一样,所以要用RoI
  • 再对锚框进行RoI pooling,变成一个形状
    • 将卷积神经网络的输出和提议区域作为输入,输出连结后的各个提议区域抽取的特征,形状为n×c×h2×w2
  • 通过全连接层将输出形状变换为n×d
  • 预测n个提议区域中每个区域的类别和边界框。更具体地说,在预测类别和边界框时,将全连接层的输出分别转换为形状为n×q(q是类别的数量)的输出和形状为n×4的输出。其中预测类别时使用softmax回归

4.Faster R-CNN

图片
用Regional proposal network来代替原来的Selective search获得更好的锚框(减少提议区域的生成数量,生成不怎么重复的),一个比较糙的目标检测算法:two stage

  • 图片先CNN
  • 然后输出进入RPN,先再CNN,将输出通道数记为c。这样,卷积神经网络为图像抽取的特征图中的每个单元均得到一个长度为c的新特征
    • 在这个小network里先要筛一次,所以需要有特征,然后后面可以预测、筛选
  • 然后生成锚框(13.4的生成方法)
  • 使用长度为c的特征,两件事
    • binary category prediction:是否圈住
    • bounding box prediction
  • NMS->从预测类别为目标的预测边界框中移除相似的结果。最终输出的预测边界框即是兴趣区域汇聚层所需的提议区域

5.Mask R-CNN

图片
如果有像素级别的标号,使用FCN来利用这些信息(fully convolutional network)
RoI pooling->RoI align,直接中间切开,不管原有边缘,对于被切开的像素,进行加权,保留特征图上的空间信息,从而更适于像素级预测

6.总结

1.
R-CNN基于锚框和CNN的目标检测算法
2.
Faster R-CNN &Mask R-CNN是在追求高精度场景下的常用算法

44.物体检测算法:YOLO

1.
you look only once
2.
问题:SSD中锚框大量重叠,浪费了很多计算->YOLO将图片均匀分成SxS个锚框,每个锚框预测B个边缘框
3.
总结:目标检测算法主要分为两个类型

  • (1)two-stage方法,如R-CNN系算法(region-based CNN),其主要思路是先通过启发式方法(selective search)或者CNN网络(RPN)产生一系列稀疏的候选框,然后对这些候选框进行分类与回归,two-stage方法的优势是准确度高
  • (2)one-stage方法,如Yolo和SSD,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度快,但是均匀的密集采样的一个重要缺点是训练比较困难,这主要是因为正样本与负样本(背景)极其不均衡,导致模型准确度稍低

13.9 语义分割和数据集

1.
语义分割(semantic segementation)将图片中的每个像素分类到对应的类别:语义区域的标注和预测是像素级的
2.
应用:背景虚化、路面分割
3.
与实例分割的区别:图片

13.10 转置卷积

1.
问题:之前的卷积不会增大输入的高宽,通常要么不变,要么减半。这对于像素级处理是不行的。转置卷积(transposed convolution)可以用来增大输入高宽
2.
计算方法图片
0和kernal中的元素逐个做乘法,然后逐个写上去(保持核的大小)
$Y[i:i+h,j:j+w]+=X[i,j]\cdot K$
3.
为什么是“转置”

  • 对于卷积$Y=X\bigstar W$
    • 可以对W构造一个V,使得卷积等价于矩阵乘法Y‘=VX’($n=n\times m \cdot m$)
    • Y‘,X’是Y,X对应的向量版本
  • 转置卷积等价于$Y’=V^T X’$’($m=m\times n \cdot n$)
  • ->如果卷积将输入从(h,w)变成(h‘,w)
    • 同样超参数的转置卷积则从(h‘,w’)变成(h,w)
  • 转置卷积层能够交换卷积层的正向传播函数和反向传播函数
    4.
    填充
    tconv = nn.Conv2DTranspose(1, kernel_size=2, padding=1)
    转置卷积的输出中将删除第一和最后的行与列
    就是说转置卷积可以理解为就是卷积反着来,padding=1是加在输入层上。先想个卷积的过程,一个1x1的图像用(1, kernel_size=2, padding=1)卷积,得到1-2+2+1,也就是2x2的输出。所以逆过程2x2的经过转置卷积,得到1x1的输出。所以转置卷积填充是让输入变小。
    5.
    步幅
    图片
    所以转置卷积步幅是让输入变大
    6.
    通道
    与卷积相同
    1
    2
    3
    4
    X = torch.rand(size=(1, 10, 16, 16))
    conv = nn.Conv2d(10, 20, kernel_size=5, padding=2, stride=3)
    tconv = nn.ConvTranspose2d(20, 10, kernel_size=5, padding=2, stride=3)
    tconv(conv(X)).shape == X.shape
    True
    7.
    反卷积的意义是卷积之后的矩阵的每个元素有一个感受视野,反卷积希望通过这个元素还原感受视野里面的内容。并且由于卷积后的元素的感受视野有相交的情况,所以反卷积中也出现了结果中有些元素的值来源于卷积结果的一个或多个元素的现象,理论上通过反卷积,我们可以通过将特征图反卷积还原到原图大小从而获取到我们的卷积核在原图中提取的是什么信息

13.11 全卷积网络

1.
FCN(fully convolutional network)
它用转置卷积层来替换CNN最后的全连接层,从而可以实现每个像素的预测
图片
k(通道数)是k个类别,表示每个像素k个类别的概率

13.12 风格迁移

1.
样式迁移:将样式中图片中的样式迁移到内容图片上,得到合成图片
2.
基于CNN的样式迁移

  • 奠基性工作图片
  • 合成图像是风格迁移过程中唯一需要更新的变量,即风格迁移所需迭代的模型参数
  • 全变分损失有助于减少合成图像中的噪点
  • 假设该输出的样本数为1,通道数为c,高和宽分别为h和w,我们可以将此输出转换为矩阵X,其有c行和hw列。 这个矩阵可以被看作由c个长度为hw的向量$x1,…,x_c$组合而成的。其中向量xi代表了通道i上的风格特征。在这些向量的格拉姆矩阵$XX^T∈R^{c\times c}$中,i行j列的元素$x{ij}$即向量$x_i$和$x_j$的内积。它表达了通道i和通道j上风格特征的相关性。我们用这样的格拉姆矩阵来表达风格层输出的风格
  • 简而言之,用通道表示一个点的特征,用通道之间的统计关系表示图片的风格(gram matrix)
  • 合成图像里面有大量高频噪点,即有特别亮或者特别暗的颗粒像素。 一种常见的去噪方法是全变分去噪(total variation denoising): 假设xi,j表示坐标(i,j)处的像素值,降低全变分损失$\sum{i,j}|x{i,j}-x{i+1,j}|+|x{i,j}-x_{i,j+1}|$能够尽可能使邻近的像素值相似。

第六章 卷积神经网络

经典卷积神经网络 LeNet

图片

1.
卷积层代替全连接层:1.可以在图像中保留空间结构;2.模型更简洁,所需参数更少
2.
LeCun 1989:识别图像中的手写数字,第一篇通过反向传播成功训练卷积神经网络论文
3.
LeNet(LeNet-5)由两个部分组成:
卷积编码器:由两个卷积层组成;
全连接层密集块:由三个全连接层组成
每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均汇聚层
图片

第七章 现代卷积神经网络

7.1 深度卷积神经网络 AlexNet

1.
更深更大的LeNet
2.
主要改进:dropout(模型控制,正则)、relu(梯度更大)、maxpooling(输出大,梯度更大)、数据增强(模拟改变)
3.
past:image-人工特征提取(重点)-SVM(标准机器学习模型:后两部份独立
now:image-通过CNN学习特征-Softmax回归:后两部份一起,一起训练
优势:1.CNN相对简单,不需要太多cv知识;2.一起训练更加高效,端到端(原始输入直接到结果(分类等))
图片

7.2 使用块的网络 VGG

1.
更大更深的AlexNet(重复的VGG块)
2.
深且窄的卷积比浅且宽的更有效
图片

7.3 网络中的网络 NiN

1.
前述网络卷积层后第一个全连接层的参数太多了,内存+过拟合
2.
用1x1kernal,stride=1,padding=0起到全连接层作用。在每个像素位置应用一个全连接层
图片
3.
全局平均池化层:输入通道是类别数,每一个通道算出平均值,再用softmax作为概率(用卷积层的通道来输出类别预测)
4.
卷积层输入输出-四维张量:样本、通道、高度、宽度
全连接层输入输出-二维张量:样本、特征
——>空间维度中的每个像素视为单个样本,通道维度视为不同特征

7.4 含并行连接的网络 GoogLeNet

图片

1.
模型复杂度-参数个数-输入通道x输出通道xkernal大小
2.
白色1x1:改变通道数 蓝色:抽取空间信息
3.
inception块比单3x3、5x5卷积层有更少的参数个数和计算复杂度
图片
4.
每个stage代表高宽减半,inception不改变高宽,只改变通道数
图片
5.
段1、2:比AlexNet小的kernal,保留更大的高宽,方便后续更深的网络,同时增加通道数
图片
6.
inception块用4条有不同超参数的卷积层和池化层的路来抽取不一样的信息,解决了多大卷积核是合适的这个问题,不同滤波器可以识别不同范围的图像细节
7.
注意:

  • 卷积层:通常用来提取特征(识别相邻元素间作用力),并且通常不希望改变图像的大小(老的也有就让他变小的),所以通常会取padding=(k-1)/2 (这个padding值是一边加多少)。同时也可以通过kernal的数量来改变通道数(主要是增加)
  • 1x1卷积层:通常用于调整网络层的通道数量和控制模型复杂度,可增加可减少
  • 汇聚层:通常用于降低卷积层对位置的敏感性和对空间降采样表示的敏感性,通常会取stride=2来使得高宽减半(默认情况下,深度学习框架中的步幅与汇聚窗口的大小相,所以直接MaxPoo2d(2))(在inception中也可以起到提取信息的作用)
  • flatten层:用于展平(直接列成像素),用于后面的全连接层
  • 线性层(全连接):用于把那些值的数量最后变成分类种类的数量

7.5 批量规范化

1.
问题:损失在最后计算,在反向传播中,后面的层梯度较大,因此后面的层训练较快,底部的层训练较慢。而底部往往训练底层的东西(线段之类),导致底部层一变化所有都得变,最后的那些层需要重新学习多次,导致收敛变慢—->学习底部层时能否避免改变顶部层
也即加速深层网络的收敛

  • 1.数据预处理(标准化)可以将参数的量级进行统一
  • 2.控制中间层变量的变化,控制变量分布的偏移(不同层如果参数可变范围不同,学习率也应该对应不同,所以在适用一个学习率的情况下,就需要控制)
  • 3.更深的网络很复杂,容易过拟合,需要正则化
    • Regularization,中文翻译过来可以称为正则化,或者是规范化。可以说是一个限制通过这种规则去规范他们再接下来的循环迭代中,不要自我膨胀
    • 欠拟合原因:1.训练样本数量少2.模型复杂度过低3.参数还未收敛就停止循环
    • 欠拟合的解决办法:1.增加样本数量2.增加模型参数,提高模型复杂度3.增加循环次数4.查看是否是学习率过高导致模型无法收敛
    • 过拟合原因:1.数据噪声太大2.特征太多3.模型太复杂
    • 过拟合的解决办法:1.清洗数据2.减少模型参数,降低模型复杂度3.增加惩罚因子(正则化),保留所有的特征,但是减少参数的大小(magnitude)。
      2.
      批量归一化:
      要使得分布在不同层之间尽量不变化->固定小批量里面的均值和方差
      $\muB=\frac{1}{|B|}\sum\limits{i\in B}xi$
      $\sigma_B^2=\frac{1}{|B|}\sum\limits
      {i\in B}(xi-\mu_B)^2+\epsilon$
      $\Rightarrow x
      {i+1}=\gamma\frac{x_i-\mu_B}{\sigma_B}+\beta$
      其中$\gamma$和$\beta$是可以学习的参数(比例系数和比例偏移or拉伸参数和偏移参数),因为如果变成均值为0,方差为1的分布不那么合适的话可以学习新的均值(后者)和方差(前者)—>基于批量统计的标准化—>批量规范化
      注:$+\epsilon$以保证分母永远不会等于零
      3.
      全连接层:作用在特征维,仿射变化和激活函数之间
  • 线性模型:输入包含d个特征:$\hat{y}=w_1x_1+…+w_dx_d+b$
  • 将所有特征放在一个向量$\mathbf{x}\in\mathbb{R}^d$里,将所有权重放在向量$\mathbf{w}\in\mathbb{R}^d$里:$\hat{y}=\mathbf{w}^T\mathbf{x}+b$
  • 这里的向量x是单个数据的样本特征,所以可以用矩阵$\mathbf{X}\in\mathbb{R}^{n\times d}$引用n个样本,每一行是一个样本,每一列是一种特征:$\hat{y}=\mathbf{X}\mathbf{w}+b$
  • 也即对每一个特征。计算标量的均值和方差,然后进行变换
    4.
    卷积层:作用在通道维,卷积层和激活函数之间
  • 对于卷积层的输入,假设批量大小为1,输入一个图片,每一个像素就是一个样本,所以样本的数量就是高乘以宽,而多个通道就是每个样本的不同特征,因此通道维可以看作是全连接层的特征维
    5.
    最初的想法是控制分布,后来发现可能是将$\hat{\mu}_B$作为随机偏移,$\hat{\sigma}_B$作为随机缩放,也即可以理解为在每个小批量加入噪音来控制模型复杂度,因此没必要和dropout混合使用
    6.
    批量归一化可以加速收敛速度(学习率可以调的更大),但一般不改变模型精度
    7.
    代码部分:
    1
    2
    3
    4
    5
    #在定义batch_norm函数中
    #moving_是全局变量,momentum用于更新
    # 更新移动平均的均值和方差
    moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
    moving_var = momentum * moving_var + (1.0 - momentum) * var
    BN在训练模式:每个小批量的均值和方差;在预测模式:整个数据集的均值和方差

7.6 残差网络 ResNet

图片

1.
后面的层覆盖范围包含之前的层覆盖范围,防止走偏离目标越来越远->使得很深的网络更加容易训练
也即残差快(residual block)核心:每一个附加层都应该更容易地包含原始函数作为其元素之一
图片
图片
图片
2.
通常在第一块步幅为2来使高宽减半,同时用1x1kernal来增加通道数,后接多个高宽不变的ResNet块
3.
总体架构类似VGG、GoogleNet,替换成ResNet块
4.
学习嵌套函数是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射较容易(尽管这是一个极端情况)
5.
残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零

0%