深层学习疑难知识点


有关深层学习的疑难知识点
self含义
init 方法的第一个参数永远是 self ,表示创建的实例本身,因此,在 init 方法的内部,就可以把各种属性绑定到 self,因为 self 就指向创建的实例本身。
使用了 init 方法,在创建实例的时候就不能传入空的参数了,必须传入与 init 方法匹配的参数,
但是 self 不需要传,python解释器会自己把实例变量传进去。自动调用构造函数。


class Student():
    name = ''
    age = 0
    def __init__(self,age,num):
        #   它是构造函数
        print('student')
    #行为     与   特征
    def do_homework(self):
        print('homework')

student1 = Student()
student1.init()

()的意义
类有一个名为 init() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:

def init(self):
self.data = []

类定义了 init() 方法,类的实例化操作会自动调用 init() 方法。
如下实例化类 MyClass,对应的 init() 方法就会被调用:
x = MyClass()
self代表类的实例,而非类

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响; 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据; 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。

optimizer理解

step这个函数使用的是参数空间(param_groups)中的grad,也就是当前参数空间对应的梯度,这也就解释了为什么optimzier使用之前需要zero清零一下,因为如果不清零,那么使用的这个grad就得同上一个mini-batch有关,这不是我们需要的结果。再回过头来看,我们知道optimizer更新参数空间需要基于反向梯度,因此,当调用optimizer.step()的时候应当是loss.backward()的时候

def step(self, closure=None):
    """Performs a single optimization step.

    Arguments:
        closure (callable, optional): A closure that reevaluates the model
            and returns the loss.
    """
    loss = None
    if closure is not None:
        loss = closure()

    for group in self.param_groups:
        for p in group['params']:
            if p.grad is None:
                continue
            grad = p.grad.data  #.data——获得该节点的值,即Tensor类型的值
                                #.grad——获得该节点处的梯度信息
            if grad.is_sparse:
                raise RuntimeError('Adam does not support sparse gradients, '
                                    'please consider SparseAdam instead')
            amsgrad = group['amsgrad']

            state = self.state[p]

            # State initialization
            if len(state) == 0:
                state['step'] = 0
                # Exponential moving average of gradient values
                state['exp_avg'] = torch.zeros_like(p.data)
                # Exponential moving average of squared gradient values
                state['exp_avg_sq'] = torch.zeros_like(p.data)
                if amsgrad:
                    # Maintains max of all exp. moving avg. of sq. grad. values
                    state['max_exp_avg_sq'] = torch.zeros_like(p.data)

            exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
            if amsgrad:
                max_exp_avg_sq = state['max_exp_avg_sq']
            beta1, beta2 = group['betas']

            state['step'] += 1
            bias_correction1 = 1 - beta1 ** state['step']
            bias_correction2 = 1 - beta2 ** state['step']

            if group['weight_decay'] != 0:
                grad.add_(group['weight_decay'], p.data)

            # Decay the first and second moment running average coefficient
            exp_avg.mul_(beta1).add_(1 - beta1, grad)
            exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
            if amsgrad:
                # Maintains the maximum of all 2nd moment running avg. till now
                torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
                # Use the max. for normalizing running avg. of gradient
                denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])
            else:
                denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])

            step_size = group['lr'] / bias_correction1

            p.data.addcdiv_(-step_size, exp_avg, denom)

    return loss

在optimizer的step函数的内部,只使用gradients来更新参数,根本不使用参数本身的值(除了weight decay,但我们将在外围处理)。然后我们可以在optimizer处理之前,简单地执行权重衰减。在计算梯度之后仍然必须进行相同操作(否则会影响gradients值),所以在训练循环中,你必须找到这个位置。

loss.backward()

#Do the weight decay here!

optimizer.step()

当然,optimizer应该设置为wd = 0,否则它会进行L2正则化,这正是我们现在不想要的。现在,在那个位置,我们必须循环所有参数,并做weight decay更新。你的参数应该都在optimizer的字典param_groups中,因此循环看起来像这样:

loss.backward()

for group in optimizer.param_groups():

for param in group['params']:

param.data = param.data.add(-wd * group['lr'], param.data)

optimizer.step()

闭包函数
闭包函数:
如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包。闭包是Python所支持的一种特性,它让在非global scope定义的函数可以引用其外围空间中的变量,这些外围空间中被引用的变量叫做这个函数的环境变量。环境变量和这个非全局函数一起构成了闭包。

def line_conf(a, b):
    def line(x):
        return a * x + b
    return line

嵌套函数line中的代码访问了a和b变量,line本身函数体内并不存在这两个变量,所以会逐级向外查找,往上走一层就找到了来自主函数line_conf传递的a, b。若往外直至全局作用域都查找不到的话代码会抛异常。

line_A = line_conf(2, 1) #y=2x+b
line_B = line_conf(3, 2) #y=3x+2

打印x对应y的值
print(line_A(1)) #3
print(line_B(1)) #5

内包的语法为

def 父函数(参数):             #这里的参数外部传递给子函数作其函数参数
    def 子函数(参数):  #这里的参数为外部传递给子函数作其自变量额值
        fuction
    return  子函数值

调用语法:

变量=父函数(赋给子函数的参数)
result=变量(子函数自变量的值)
result的值为子函数的计算值

!!当闭包执行完后,仍然能够保持住当前的运行环境。
这里每次经过权重更新后得到的新的参数

zip的使用方法
zip 方法在 Python 2 和 Python 3 中的不同:在 Python 3.x 中为了减少内存,zip() 返回的是一个对象。如需展示列表,需手动 list() 转换。
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
这里* 单独就可以看作一个解压的步骤,将列表中的元素提出
zip按照对应关系一一打包

mask
这里的non_final_mask是判断下一步状态是否存在的索引
state_batch 是四个元素
action_batch 是一个元素
决定每一步的动作时
如果时根据Q价值最大选择
则:
action的决定取决于decide_action,每走一步更新一下决定下一步的action,并且记录下来形成action_batch?
否则:
随机选择
no_final_mask中元素为0时,则next_state_values不存在
self.model(state_batch)最后经过神经网络输出的是两个结果,分别对应向左和向右移动的Q值

gather函数
c = a.gather(0, b) # dim=0 索引b按照矩阵a的每一列去寻找
d = a.gather(1, b) # dim=1 索引b按照矩阵a的每一行去寻找

expected_state_value
相当于qtable
loss损失函数去计算state_value和expected_state_value的差值

super() 函数是用于调用父类(超类)的一个方法。

super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
super(type[, object-or-type])
type – 类
object-or-type – 类,一般是 self

class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super().add(x)
b = B()
b.add(2)  # 3

np.squeeze()函数用法:

np.squeeze()函数可以删除数组形状中的单维度条目,即把shape中为1的维度去掉,但是对非单维的维度不起作用。
作用:从数组的形状中删除单维度条目,即把shape中为1的维度去掉
场景:在机器学习和深度学习中,通常算法的结果是可以表示向量的数组(即包含两对或以上的方括号形式[[]]),如果直接利用这个数组进行画图可能显示界面为空(见后面的示例)。我们可以利用squeeze()函数将表示向量的数组转换为秩为1的数组,这样利用matplotlib库函数画图时,就可以正常的显示结果了。

greedy方法

最简单的exploration方法就是epsilon-greedy,即设置一个探索率epsilon来平衡两者的关系——在大部分时间里采用现阶段最优策略,在少部分时间里实现探索。
什么是贪婪算法? 简单说,就是,任何时候的决策,都是选择当前观察的最优解,而没有整体长远的规划.
贪婪算法的优点是容易理解,简单快速.但缺点是,得到的往往是局部最优解,而不是全局最优.

贪婪算法是如何在“exploit”和“explore”之间实现权衡,以尽可能实现最大化收益的呢? 首先,从算法的名称我们知道,这是一种贪婪的算法。
纯粹贪婪的算法,放到这种多臂老虎机的场景来看就是每次都选择当前那个最好的臂摇,即使从长远来看可能非常不好。那么,ϵϵ-贪婪算法和贪婪算法的区别在哪呢?
就像它的名字所展示的那样,区别仅仅就在这个ϵ。这个ϵ代表执行执行“探索”的概率。比如设置ϵ=0.1,那么就表示有10%的概率会进行“探索”操作,
而90%会进行“利用”操作,也就是摇当前最好的臂。如果以摇老虎机臂的过程,用次数来算的话,也就是,每10次操作,仅有1次操作去进行探索——尝试其他的臂。

若当前随机产生的0-1的浮点数大于当前的贪婪参数epsilon,则随机决策产生动作。反之则由训练网络net产生Q值并选择最大Q值的动作。

1、ϵ设置的越高,收敛到最佳收益的速度越快。这个很好理解。随机探索的概率越大,越能更快的探索到最好的臂。
2、在0.1-0.5,ϵ设置的越低,最终的平均收益越高。这个也是比较好理解的。每次都很大概率的随机探索,就会存在很多利用效果不好的臂的情况。

一个状态的Q值 = (当前状态进行某一个动作a进行到下一个状态的价值 + 时间折扣 * 采取某一行动a到下一个状态的Q值)
state_action_value
expected_state_action_value

state_action_value
从当前状态s实际执行动作a走到下一步s+1 所获得推测eval Q值

若当前状态s所对应的下一个状态s+1存在

nn.Linear
linear 的初始化部分代码
y=Ax+b
y = xA^T +b

class Linear(Module):
    ...
    __constants__ = ['bias']
    
    def __init__(self, in_features, out_features, bias=True):
        super(Linear, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.Tensor(out_features, in_features))
        if bias:
            self.bias = Parameter(torch.Tensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

示例

import torch
nn1 = torch.nn.Linear(100, 50)
input1 = torch.randn(140, 100)
output1 = nn1(input1)
output1.size()
torch.Size([140, 50])

这里weight = [out_features,in_features]
A 是weight
具体计算为[140,100] × [50,100]的转置 + bias = [140,100] × [100,50] + bias 最后得到的维度为[140,50]。
至于对于bias和weight的初始化,根据网上所讲的是来有关维度值的均匀分布

forward
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__和forward这两个方法。但有一些注意技巧:

(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数init()中,当然我也可以把不具有参数的层也放在里面;

(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替

(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。

拿线性变化举例,其中的权重需要不停的更新,这就是需要维持的状态,
而以激活函数来说,一般来说都是可以直接使用的,
只要给定相应的输入,那么使用后就有确定的输出。

linear的计算方式,

就是权重矩阵W乘以输入矩阵X,然后加上偏置项b,即Wx+b。

fc1(x)的调用流程

Python中可以在一个类中定义__call__方法来让一个类的实例对象直接调用这个函数。也就是说比如我下面这段程序:

class A():
def init(self, n):
self.n = n

def call(self, x):
return self.n + x

调用A(3)(5)的结果就是8。由于nn.Linear是module是子类,然后module又定义了__call__方法来隐式的调用了forward,所以fc1(x)实质上就等价于fc1.forward(x)。

nn.module
输出权重和bias的方法

print("self.fc1.weight")
print(self.fc1.weight.t(),self.fc1.bias)
print("self.fc2.weight")
print(self.fc2.weight,self.fc1.bias)
print("self.fc3.weight")
print(self.fc3.weight,self.fc1.bias)

权重更新

每个episode之间权重参数相互关联
main_q_main_network和target_q_network在初始化的时候权重参数不同
每进行两次episode试错后,将main_q_network的参数传递给target网络

DQN

DDQN的逻辑
从理论上说,这种方法不能完全减少overestimate,只能减少一部分。但经过数值实验,人们发现上述的改变对于overestimate的减少还是非常可观的。由于其与一般DQN(fixed Q-target)相比几乎没有产生新的成本,所以DDQN也成为了目前DQN训练的必备技术之一。

bellman equation

贝尔曼方程:当前状态的价值和下一步的价值及当前的奖励
价值函数分解为当前奖励(离开当前状态的即时价值)和下一步的价值两部分
当前状态的价值函数 =
当前状态根据策略(概率)进行下一步动作时所获得的价值
+
根据某一策略(概率)达到下一个状态,下一个状态的价值函数 * discount

我们如何知道它后继状态的value funciton为0.8,10。其实这些值一开始可以任意初始化,
后面可以学习更新,就类似于神经网络的权值参数,一开始任意初始化,后面通过loss反向更新一样。
https://zhuanlan.zhihu.com/p/35261164
即 [Vπ(s)] 为 [Qπ(s,a)] 在策略 [π(a|s)] 下的和 (所有动作下的期望价值)
https://www.zhihu.com/question/59122948

神经网络
线性方程:f = Wx
非线性方程:f = W2xmax(0,W1x)
这里的max相当于一个激活函数

激活函数

sigmoid

某些数据是线性不可分的,所以需要激活函数去非线性分割数据
在每一层输出的时候都需要激活函数,去达到非线性表达
问题:
看梯度变化,求导
当存在多层隐藏层的神经网络,会达到梯度消失
w1 w2 w3 w4 loss
0.01 0.1 0.3 0.5

RELU

ReLU处理了它的sigmoid、tanh中常见的梯度消失问题,
同时也是计算梯度最快的激励函数。

所以目前sigmoid不用,开始使用RELU
tanh

过拟合

正则化项在神经网络中的重要作用
L2正则化惩罚项 λW^2
实际预测过程中泛化越强越好
越多的神经元,越能够表达复杂的模型

初始化

权重初始化为随机初始化
bias初始化可为0,可为1

##dropout
全连接操作
dropout随机选择
保留率60%
去掉40%
迭代的时候,去随机选择
去降低过拟合的风险

bacthsize

每次取样品的大小
数值越大越好,
性能好的话64,128.
一般的16,32.
取决于计算机的性能。显存大小。

学习率

学习率设置过高可能导致在用梯度损失更新梯度后获得的是极小值,而不是最小值

卷积神经网络

卷积神经网络的组成
输入层
卷积层
激活函数
池化层
全连接层

卷积层

filter帮助进行信息的提取
将输入划分成一个个小区域,提取每一个区域获得一个数值。
经过卷积层之后获得特征图。
32* 32 * 3 image
5* 5* 3 filter
提取的数据X和filter对应的W 做内积操作 +bias
卷积之后都需要采取激活函数,进行非线性转化

stride 滑动

stride的数值越大,会获得较小的特征图。
为了获得更多的特征,需要让stride值变小。为了效率不能取太小。

padding

防止边缘数据提取次数过少

在原始的输入像5* 5 * 3四周加了两圈0 变成7 * 7 * 3
输入 = 7 * 7
Filter = 3* 3
Pad = 1
Output = ?

height out = height in - height kernel +2* padding / stride +1
width out = width in - width kernel +2* padding / stride +1

池化层pooling layer

对于特征图进行的操作
压缩特征图
对于filter size内的特征数据进行下面两种操作
mean
max
-2+0.8* 4.3 +0

神经元死亡bias的更新

神经元在训练过程中的“死亡”,通常是学习率过大造成的。

这个神经元已经经过若干次迭代,其参数 (w⃗ ,b) 已经迭代得趋于稳定。
现在,神经元接收到了一个异常输入 x⃗ 。比方说,它的某一维特征 xi 与对应的权重 wi 的乘积 wixi 非常大。
一般来说,这意味着 xi 的绝对值非常大。于是,ReLU 的输入就会很大,对应 ReLU 的输出 y 也就会很大。好了,假设这个 ReLU 神经元期望的输出(ground truth)是 y^,这个时候损失就会很大——损失一般是 |y−y^| 的增函数,记为 f(|y−y^|)。
解答:
这里当输入一个异常x,会导致输出远远大于期望y值,在反向传播过程中,会因为此次异常数据X,而降低权重w,
但更新权重的时候根据学习率部分更新。bias的系数为1.b的变化直接映射到loss的变化。当异常数据导致relu的输出足够大的时候,
bias会变成一个负数。

duelingwork

Dueling的这种结构的优势在于学习效率,Value[V(s)]的更新将会触及到所有的动作跟着
value[V(s)]一起更新,如果是Q的话,你只能更新当前状态下的状态-动作值函数。

TD-error

常将 rt+γ⋅Qπ(st+1,at+1) 的值记作 yt 。
因为 yt 中包含了真实的奖励值 rt ,所以认为 yt 比 Qπ(st,at) 更准确。
将 yt 命名为 TD target,而将 yt−Qπ(st,at) 的值命名为 TD error。
TD error 的绝对值越小,就表明模型预测误差越小,预测的结果越准确。所以,在确定好 TD error 的计算方式后,可以通过 梯度下降 方法来更新模型的参数,降低 TD error 。
时序差分学习的目的是让模型预测出的 Qπ(st,at) 值不断的接近TD target 。

梯度裁剪

torch.nn.utils.clip_grad_norm_(parameters, max_norm, norm_type=2)
parameters (Iterable[Tensor] 或 Tensor) —— 一个由张量或单个张量组成的可迭代对象(模型参数),将梯度归一化(原文: an iterable of Tensors or a single Tensor that will have gradients normalized)
max_norm (float or int) – 梯度的最大范数(原文:max norm of the gradients)
norm_type (float or int) – 所使用的范数类型。默认为L2范数,可以是无穷大范数(‘inf’)(原文:type of the used p-norm. Can be for infinity norm.‘inf’)


评论
  目录