深度学习计算

《动手学深度学习》-深度学习计算

在我学习动手学深度学习这本书的这一章时,它的顺序是:模型的构造、模型参数的访问初始化与共享、模型参数延后初始化、自定义层、读取与存储、GPU计算。

但是在我学习的构造模型的时候,它的正向传播函数用到了层,但是我却不知道层是怎么定义的。在访问模型的时候我会想它是怎么初始化的。所以在这片博客中我按照:模型参数的延后初始化、模型参数的访问初始化与共享、自定义层、模型的构造、读取与存储、GPU计算。可以先按照首段的顺序学习,捋一遍的时候参照我的顺序捋一遍就会清楚很多。

我是自学的,并没有老师可以询问。所以里面有些细节、小感悟可能会有些错误,请酌情参考。错误之处还望不吝指正。

模型的延后初始化

众所周知,mxnet的一大特色就是延后初始化。**在定义模型后并不知道w的值与他们的形状,只有在模型进行向前计算后,系统才根据输入的形状判断权重参数的值,**然后才进行上几行代码中的initialize()进行初始化。

from mxnet import init, nd
from mxnet.gluon import nn

class MyInit(init.Initializer):
    #重写了Initializer的初始化权重函数
    def _init_weight(self, name, all):
        print("Init", name, all.shape);
    
x = nd.random.uniform(shape = (2, 10))
net = nn.Sequential()
net.add(nn.Dense(100, activation="relu"),
       nn.Dense(20))
net.initialize(init=MyInit())
print("up or down?")
net(x)

若在initialize()处初始化则,信息会在up or down之上,否则反之。结果是:

Init dense0_weight (100, 10)
Init dense1_weight (20, 100)
up or down?

细心的同学可能会问,为什么name是什么,为什么形状会是(100,10)不是应该是(10,100)吗?

首先,name是collect_params中所有weight的key(collect_params是个字典,下面参数的访问中会讲到。好像改成这个顺序也有点乱(哭笑),但是在我学习参数访问时不知道怎么延后初始化的所以也乱)在之前学习softmax回归时,列才表示的是输出个数。我个人认为它的这种格式更利于延后初始化,因为已定的数组是不可以改变维度的。

模型进行一次向前计算后才可以访问参数,但是有时我们不想这么做。当然!延后初始化是可以避免的。

  1. 因为第一次初始化过,所以参数的形状固定了可以直接进行重新初始化,这是不需要再次进行向前计算的
net.initialize(init=MyInit(), force_reinit=True)
#force_reinit,若已初始化,是否强制重新初始化
Init dense0_weight (100,10)
Init dense1_weight (10,100)
  1. 初始化之前,固定了每层的输入,达到关闭延后初始化的效果。
#in_units是输入大小,units是输出大小就是神经元数
net.add(nn.Dense(units=100, in_units=30, activation="relu"),
       nn.Dense(units=20, in_units=100))
net.initialize(init=MyInit())
#这时直接就初始化,输出了。但是你要注意形状的关系,关闭了延时,这就是你要考虑的事情了。
Init dense0_weight (100, 30)
Init dense1_weight (20, 100)

模型参数的访问、初始化、共享

模型参数的访问

很多时候我们想看看训练好的模型的参数是个什么亚子,或者初始化成什么亚子。因为参数是封装在Parameter类中的,所以就让我们看看怎么访问吧。

在这里插入图片描述

在Block类中有个属性,prefix。应该是个静态成员,没创建一个对象+1。name是由prefix+weight或bias组成的。

这是个大概的组成。

from mxnet import init, nd
from mxnet.gluon import nn
x = nd.random.uniform(shape = (10, 10))
net = nn.Sequential()
net.add(nn.Dense(20, activation="relu"),
       nn.Dense(10))
net.initialize()
net(x)
#访问,某一层可以通过使用[],比如第0层。
print(net[0].params)
print(net[0].weight, net[0].params['dense0_weight'])
print(net[0].bias)
#Parameter对象通过.data()访问数据
print(net[0].weight.data()[0])
print(net.params)
dense0_ (
  Parameter dense0_weight (shape=(20, 10), dtype=float32)
  Parameter dense0_bias (shape=(20,), dtype=float32)
) 
#第二个输出俩一样,所以在这我就写了一个
Parameter dense0_weight (shape=(20, 10), dtype=float32)
Parameter dense0_bias (shape=(20,), dtype=float32) 
[ 0.01958894  0.01148278 -0.04993054  0.00523225  0.06225365  0.03620619
  0.00305876 -0.05517294 -0.01194733 -0.00369594]
dense0_ (
) 

思考一下,为什么net也是Block的子类对象params却为空,因为它里面没有定义参数,这就涉及到了层的定义,所以学习的时候来回穿插很心累(哭笑)

collect_params访问模型以及其中的层的所有参数

print(net[0].collect_params)
print(net.collect_params)
#也可以使用正则表达式
print(net.collect_params(".*weight"))
dense0_ (
  Parameter dense0_weight (shape=(20, 10), dtype=float32)
  Parameter dense0_bias (shape=(20,), dtype=float32)
)
sequential0_ (
  Parameter dense0_weight (shape=(20, 10), dtype=float32)
  Parameter dense0_bias (shape=(20,), dtype=float32)
  Parameter dense1_weight (shape=(10, 20), dtype=float32)
  Parameter dense1_bias (shape=(10,), dtype=float32)
)
sequential0_ (
  Parameter dense0_weight (shape=(20, 10), dtype=float32)
  Parameter dense1_weight (shape=(10, 20), dtype=float32)
)

模型参数的初始化

在参数访问例子中,initialize()函数的没有参数,使用的是默认的uniform随机初始化。但是也可以自定义初始化方式。

#Block的子类调用的(net.initialize()),下面是源码
def initialize(self, init=initializer.Uniform(), ctx=None, verbose=False,force_reinit=False):
    self.collect_params().initialize(init, ctx, verbose, force_reinit)

#params调用的(↑collect_params().initialize(init, ctx, verbose, force_reinit))
def initialize(self, init=None, ctx=None, default_init=initializer.Uniform(),force_reinit=False):
    if self._data is not None and not force_reinit:
        warnings.warn("Parameter '%s' is already initialized, ignoring. " \
                      "Set force_reinit=True to re-initialize." % self.name,
                      stacklevel=2)
        return
    self._data = self._grad = None

    if ctx is None:
        ctx = [context.current_context()]
    if isinstance(ctx, Context):
        ctx = [ctx]
    if init is None:
        init = default_init if self.init is None else self.init
    if not shape_is_known(self.shape):
        if self._allow_deferred_init:
            self._deferred_init = (init, ctx, default_init, None)
            return
        raise ValueError("Cannot initialize Parameter '%s' because it has " \
                         "invalid shape: %s." % (self.name, str(self.shape)))

    self._deferred_init = (init, ctx, default_init, None)
    self._finish_deferred_init()

#这是Initialize类的函数,上面是Block的。默认传的是Uniform类对象,这是重写好的。
def _init_weight(self, name, arr):
    raise NotImplementedError("Must override it")

init中提供了多种初始化方法:

  • init.Normal(sigma=0.01),正态分布初始化
  • init.Constant(1),初始化为常数1
  • init.Xavier()

可以对net进行初始化,所有的参数将初始化。也可对单个的Parameter对象初始化,与模型初始化相同。

net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
net[0].weight.initialize(init=init.Constant(2), force_reinit=True)

除了使用现成的初始化方法,也可以使用自定义初始化方法。继承自Initialize类。

class MyInit(init.Initialize):
    #这个在自定义类中,必须重写,可以看看上面的源码。name就是参数的名字,all是data。这是初始化一个参数的函数
    def _init_weight(self, name, all):
        #shape不可以变的,在此之前形状以确定,这只是延后初始化的方案,不是延后初始化
        all[:]=nd.random.uniform(low=-10, high=10, shape=data.shape)
        data *= data.abs() >= 5
        #初始化为[-10,-5][5,10]之间的数
        
net.initialize(init=MyInit())
net(x)

Parameter类有set_data()可以直接对参数进行改写

net[0].weight.set_data(net[0].weight.data() + 1)

模型参数的共享

不同层使用同一层参数。

net = nn.Sequential()
shared = nn.Dense(8, activation="relu")
net.add(nn.Dense(8, activation="relu"),
       shared,
       nn.Dense(8), params=shared.params,
       nn.Dense(10))
#第二层与第三层共享参数,但是是不同层因为Dense的name不同。但是也相当于一层,若激活函数相同的话,因为参数相同、激活函数相同。输出值就是一样的方法计算的,而且优化函数也是一样的计算方法,因为w、b使用同一组。
#注意:使用同一组参数的话输出要相同。

在模型的构造中会介绍,使用同一层作为两层(绕口,我也不知道怎么形容,意会意会(笑))

自定义层

  1. 不含模型参数的自定义层:
#通过继承Block创建层
class Layer(nn.Block):
    def __init__(self, **kwargs):
        #初始化父类
        super(Layer, self).__init__(**kwargs)
        
    def forward(self, x):
        return x - x.mean()
  1. 含模型参数的自定义层:
class MyDense(nn.Block):
    def __init__(self, **kwargs):
        super(MyDense, self).__init__(**kwargs)
        #在字典中查有无weight若有返回,无加上前缀放入字典。注意这个形状是不是与nn就有的层形状相反。自定义时写成这样好计算。
        self.weight = self.params.get('weight', shape=(in_units, units))
        self.bias = self.params.get('bias', shape=(units,))
        
    def forward(self, x):
        linear = nd.dot(x, self.weight.dara()) + self.bias.data()
        return relu(linear)

你自己定义的形状注意确定,有能力也可以写成延时初始化。

模型的构造

模型与层都是相同的东西,都是继承自Block类,都是由输入、输出、向前计算撑起来的。只不过层比模型更小,在forward中是最底层的计算而不是单纯的(x),是吧x与w进行运算后输出的。层是模型的基本单位。

class MLP(nn.Block):
    def __init__(self, **kwargs):
        #初始化父类
        super(MLP, self).__init__(**kwargs)
        #定义模型,但是这种直接固定死了
        self.hidden = nn.Dense(256, activation="relu")
        self.output = nn.Dense(20)

    #在对象像函数一样用函数调用时,首先调用__call__函数,然后call函数调用重写后的你这个forward函数
    def forward(self, x):
        return self.output(self.hidden(x))
class MySequential(nn.Block):
    def __init__(self, **kwargs):
        super(MySequential, self).__init__(**kwargs)

    def add(self, *args):
        #将模型或者层添加进来,放到args中,每个block都有自己的名字,name是个静态属性在随着Block子类对象的创建在增加
        #将block放入父类的字典_children中(它是一个顺序字典,按照添加顺序排列)
        for block in args:
            self._children[block.name] = block

    def forward(self, x):
        #从Orderdict中按照上面的添加顺序,读取字典的值(也就是你的模型或层)
        for dence in self._children.values():
            x = dence(x)
        return x
'''这是一个更复杂的MPL,有多层共用一组权重,与常数dot,以及控制流'''
class FancyMPL(nn.Block):
    def __init__(self, **kwargs):
        super(FancyMPL, self).__init__(**kwargs)
        '''
        这是get_constant的源码
        ---------------------------------------------------------------------------
        name = self.prefix + name
        param = self._get_impl(name)
        if param is None:
            if value is None:
                raise KeyError("No constant named '{}'. Please specify value " \
                               "if you want to create a new constant.".format(
                                   name))
            param = Constant(name, value)
            self._params[name] = param
        ---------------------------------------------------------------------------
        '''
        #在父类的params中若有名为rand_weight的参数则返回其参数,若没有将它名字加个前缀加入params然后返回value
        #value是nd.random.uniform(shape=(20, 20))
        self.rand_weight = self.params.get_constant("rand_weight", nd.random.uniform(shape=(20, 20)))
        self.dense = nn.Dense(20, activation="relu")

    def forward(self, x):
        #输出一个(2,20)
        x = self.dense(x)
        #在这里一定要注意,rand_weight的shape是(20,20)的;且第0维度必须是20,因为它要与dense的输出做dot运算
        #第一个维度的20是一个样本的输出个数
        #赋值后的x为(2,20)
        x = nd.dot(x, self.rand_weight.data()) + 1
        #重复调用,两层公用一组权重
        x = self.dense(x)
        while x.norm().asscalar() > 1:
            x /= 2
        if x.norm().asscalar() < 0.8:
            x *= 10
        return x.sum()
from mxnet.gluon import nn
from mxnet import nd
from tectonic_model.fancyMPL import FancyMPL
from tectonic_model.model1 import MLP

x = nd.random.uniform(shape=(2, 20))
net = nn.Sequential()
#将自己构造的模型作为一层添加到net中,形成更复杂的模型,这就是灵活性的体现
net.add(MLP(), FancyMPL())
net.initialize()
print(net.params, net.collect_params())
print(net(x))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值