参数说明:
features_in其实就是输入的神经元个数,features_out就是输出神经元个数,bias默认为True,这里为了表达方便,就写了False,一般调用都是torch.nn.Linear(10, 5),就是输入10个,输出5个神经元,且考虑偏置。
下面我们来看看源码:
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() ... note:可以看到两个重要的属性self.weight、self.bias
@weak_script_method def forward(self, input): return F.linear(input, self.weight, self.bias) note:这是用来计算y=X*W.T + b的前向传播
输出结果:
从上面可以看出,输出y是(10,3) , weight是(3,5) ,b是(1,3),且w、b的requires_grad均为True
这个函数Linear做了什么工作呢?
首先我们输入X,一般都是mini-batch形式,然后它会将配置一个转置之后可以与X进行矩阵乘法的W,当然还有偏置,形成y=X*W.T+b的Affine层(全连接层)结构。当我们对这个类实例化成对象Affine_layers并给定具体输入X的时候,该对象就会调用forward进行线性运算并输入结果。
在forward代码中,他的主要源码如下:
if input.dim() == 2 and bias is not None: # fused op is marginally faster ret = torch.addmm(bias, input, weight.t()) else: output = input.matmul(weight.t()) if bias is not None: output += bias ret = output return ret从matmul可以看出全连接层的输入可以是二维张量,也可以是一维张量,但不能是标量,且bias可有可无。特别要注意的是:如果输入为一维张量,那么根据matmul的特性,输出也是一维张量。
例如:
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc = nn.Sequential( nn.Linear(1, 10), nn.ReLU(), nn.Linear(10, 2) ) def forward(self, x): out = self.fc(x) return out net = Net() # x = torch.tensor(5, dtype=torch.float32) # 0维不可以 x = torch.tensor([5], dtype=torch.float32) # 1维可以 # x = torch.arange(6, dtype=torch.float32).view(6, 1) # 二维可以 y = net(x) print(y)
1、这里可能有刚接触python的同学看不懂 上面打“******************”的地方,会认为为啥例化后实例对象进行着类似函数的操作。
其实是因为__call__(self, *args, **kwargs)这个魔法方法的存在,Linear这个类继承了nn.Module,Linear提供了自动的__call__实现,那么__call__实现代表了什呢?
这个是官方的解释:
object.__call__(self[, args...])
Called when the instance is “called” as a function; if this method is defined, x(arg1, arg2, ...) is a shorthand for x.__call__(arg1, arg2, ...).
简而言之,就是__call__实现了将例化对象当作函数使用
因此Affine_layers(x) 等价于 Affine_layers.forward(x)
理解了这个,就能明白为啥下面out1和out2的内容是相同的了
class LinearNet(nn.Module): def __init__(self, featuresin): super().__init__() self.linear = nn.Linear(featuresin, 1) # 实例化对象做属性 def forward(self, input): out = self.linear(input) return out x = torch.rand(10, 5) net = LinearNet(x.size()[1]) #实例化 out1 = net(x) out2 = net.linear(x) # out1=out2
2、
全连接层的初始化采用标准初始化(读者可以去看看源码)。也就是说pytorch对全连接层默认使用了标准初始化方式,好处是,不需要自己再去初始化,坏处就是,无法达到最优,比如我们常用Relu做激活函数,那么根据何凯明大神指出,relu函数最佳的初始化方式是He初始化,因此如果想要最优初始化参数的话,就必须要调用torch.nn.init模块中的初始化方式来重新对FC层进行初始化当然,默认的方式问题也不大,也可以一直使用的。