1. Convolution kernel weight of self defined neural network
Neural network is deeply loved by deep learners. One of the reasons is the convenience of neural network. Users only need to build neural network framework like building blocks according to their own needs. In the process of building, we only need to consider the size of convolution core, the number of input and output channels, convolution mode and so on.
When we are used to using our own parameters, when we want to customize the convolution kernel parameters, suddenly there is a feeling of no way to start, ha ha ha ha ha ha ha ha ~ ~, please allow me to be happy, ha ha ha! Because I met the same problem when I first entered the neural network, I stepped on too many pits at that time, and my baby wanted to cry! What makes me sad is that I searched all the resource areas and didn't find any sharing. Therefore, I want to write out my method, hoping to help you baby, happy (* ^ ▽ ^ *).
Don't say much, the text begins
2. Define convolution kernel weight
Here is the self-defined convolution kernel weight of dtt coefficient, directly up the weight code:
2.1 dtt coefficient weight Code
Def DTT matrix (n): this function is the DTT coefficient matrix of n*n, and the author's is the coefficient matrix of 8 * 8.
Def DTT kernel (out channels, in channels, kernel size): this method is to set the weight. The weight needs to include four parameters (output channel number, input channel number, convolution kernel height, convolution kernel width). There are many details to be noted here. The babies have to lie down in the pit to have a deep image. I won't go into it.
import numpy as np import torch import torch.nn as nn # ================================ # DTT coefficient matrix of n * n # ================================ def dtt_matrix(n): dtt_coe = np.zeros([n, n], dtype='float32') for i in range(0, n): dtt_coe[0, i] = 1/np.sqrt(n) dtt_coe[1, i] = (2*i + 1 - n)*np.sqrt(3/(n*(np.power(n, 2) - 1))) for i in range(1, n-1): dtt_coe[i+1, 0] = -np.sqrt((n-i-1)/(n+i+1)) * np.sqrt((2*(i+1)+1)/(2*(i+1)-1)) * dtt_coe[i, 0] dtt_coe[i+1, 1] = (1 + (i+1)*(i+2)/(1-n)) * dtt_coe[i+1, 0] dtt_coe[i+1, n-1] = np.power(-1, i+1) * dtt_coe[i+1, 0] dtt_coe[i+1, n-2] = np.power(-1, i+1) * dtt_coe[i+1, 1] for j in range(2, int(n/2)): t1 = (-(i+1) * (i+2) - (2*j-1) * (j-n-1) - j)/(j*(n-j)) t2 = ((j-1) * (j-n-1))/(j * (n-j)) dtt_coe[i+1, j] = t1 * dtt_coe[i+1, j-1] + t2 * dtt_coe[i+1, j-2] dtt_coe[i+1, n-j-1] = np.power(-1, i-1) * dtt_coe[i+1, j] return dtt_coe # =============================================================== # DTT coefficient matrix of (out_channels * in_channels * n * n) # =============================================================== def dtt_kernel(out_channels, in_channels, kernel_size): dtt_coe = dtt_matrix(kernel_size) dtt_coe = np.array(dtt_coe) dtt_weight = np.zeros([out_channels, in_channels, kernel_size, kernel_size], dtype='float32') temp = np.zeros([out_channels, in_channels, kernel_size, kernel_size], dtype='float32') order = 0 for i in range(0, kernel_size): for j in range(0, kernel_size): dtt_row = dtt_coe[i, :] dtt_col = dtt_coe[:, j] dtt_row = dtt_row.reshape(len(dtt_row), 1) dtt_col = dtt_col.reshape(1, len(dtt_col)) # print("dtt_row: ", dtt_row) # print("dtt_col: ", dtt_col) # print("i:", i, "j: ", j) temp[order, 0, :, :] = np.dot(dtt_row, dtt_col) order = order + 1 for i in range(0, in_channels): for j in range(0, out_channels): # dtt_weight[j, i, :, :] = flip_180(temp[j, 0, :, :]) dtt_weight[j, i, :, :] = temp[j, 0, :, :] return torch.tensor(dtt_weight)
2.2 'same' convolution
If the baby needs to keep the data size before and after convolution unchanged, that is, convolution in'same'mode, then you can use my convolution kernel directly.
import torch.utils.data from torch.nn import functional as F import math import torch from torch.nn.parameter import Parameter from torch.nn.functional import pad from torch.nn.modules import Module from torch.nn.modules.utils import _single, _pair, _triple class _ConvNd(Module): def __init__(self, in_channels, out_channels, kernel_size, stride, padding, dilation, transposed, output_padding, groups, bias): super(_ConvNd, self).__init__() if in_channels % groups != 0: raise ValueError('in_channels must be divisible by groups') if out_channels % groups != 0: raise ValueError('out_channels must be divisible by groups') self.in_channels = in_channels self.out_channels = out_channels self.kernel_size = kernel_size self.stride = stride self.padding = padding self.dilation = dilation self.transposed = transposed self.output_padding = output_padding self.groups = groups if transposed: self.weight = Parameter(torch.Tensor( in_channels, out_channels // groups, *kernel_size)) else: self.weight = Parameter(torch.Tensor( out_channels, in_channels // groups, *kernel_size)) if bias: self.bias = Parameter(torch.Tensor(out_channels)) else: self.register_parameter('bias', None) self.reset_parameters() def reset_parameters(self): n = self.in_channels for k in self.kernel_size: n *= k stdv = 1. / math.sqrt(n) self.weight.data.uniform_(-stdv, stdv) if self.bias is not None: self.bias.data.uniform_(-stdv, stdv) def __repr__(self): s = ('{name}({in_channels}, {out_channels}, kernel_size={kernel_size}' ', stride={stride}') if self.padding != (0,) * len(self.padding): s += ', padding={padding}' if self.dilation != (1,) * len(self.dilation): s += ', dilation={dilation}' if self.output_padding != (0,) * len(self.output_padding): s += ', output_padding={output_padding}' if self.groups != 1: s += ', groups={groups}' if self.bias is None: s += ', bias=False' s += ')' return s.format(name=self.__class__.__name__, **self.__dict__) class Conv2d(_ConvNd): def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True): kernel_size = _pair(kernel_size) stride = _pair(stride) padding = _pair(padding) dilation = _pair(dilation) super(Conv2d, self).__init__( in_channels, out_channels, kernel_size, stride, padding, dilation, False, _pair(0), groups, bias) def forward(self, input): return conv2d_same_padding(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups) # custom con2d, because pytorch don't have "padding='same'" option. def conv2d_same_padding(input, weight, bias=None, stride=1, padding=1, dilation=1, groups=1): input_rows = input.size(2) filter_rows = weight.size(2) effective_filter_size_rows = (filter_rows - 1) * dilation[0] + 1 out_rows = (input_rows + stride[0] - 1) // stride[0] input_cols = input.size(3) filter_cols = weight.size(3) effective_filter_size_cols = (filter_cols - 1) * dilation[1] + 1 out_cols = (input_cols + stride[1] - 1) // stride[1] padding_needed = max(0, (out_rows - 1) * stride[0] + effective_filter_size_rows -input_rows) padding_rows = max(0, (out_rows - 1) * stride[0] + (filter_rows - 1) * dilation[0] + 1 - input_rows) rows_odd = (padding_rows % 2 != 0) padding_cols = max(0, (out_cols - 1) * stride[1] + (filter_cols - 1) * dilation[1] + 1 - input_cols) cols_odd = (padding_cols % 2 != 0) if rows_odd or cols_odd: input = pad(input, [0, int(cols_odd), 0, int(rows_odd)]) return F.conv2d(input, weight, bias, stride, padding=(padding_rows // 2, padding_cols // 2), dilation=dilation, groups=groups)
2.3 assign weight to convolution kernel
This is what the babies care about most. Don't panic. Here comes ha, happy (* ^ ^ *), and into the main body.
Here is a simple network model (a fixed convolution + 3 full connections, the full connection is 1 * 1 Conv2d). I give a comment in the code, the babies should be able to understand every second, (*)!
import torch import torchvision import torch.nn as nn import torch.nn.functional as F import numpy as np import dtt_kernel import util import paddingSame # Define weights dtt_weight1 = dtt_kernel.dtt_kernel(64, 2, 8) class DttNet(nn.Module): def __init__(self): super(DttNet, self).__init__()
self.conv1 = paddingSame.Conv2d(2, 64, 8)
# Assign weight to convolution kernel self.conv1.weight = nn.Parameter(dtt_weight1, requires_grad=False) self.fc1 = util.fc(64, 512, 1) self.fc2 = util.fc(512, 128, 1) self.fc3 = util.fc(128, 2, 1, last=True) def forward(self, x): x = self.conv1(x) x = self.fc1(x) x = self.fc2(x) x = self.fc3(x) return x
2.4 supplement my util class
import torch.nn as nn def conv(in_channels, out_channels, kernel_size, stride=1, dilation=1, batch_norm=True): if batch_norm: return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=(kernel_size // 2)), nn.BatchNorm2d(out_channels), nn.ReLU() ) else: return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=(kernel_size // 2)), nn.ReLU() ) def fc(in_channels, out_channels, kernel_size, stride=1, bias=True, last=False): if last: return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=(kernel_size // 2)), ) else: return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=(kernel_size // 2)), nn.BatchNorm2d(out_channels), nn.ReLU() )
3. Summary
Wow, I'm finished. I don't know if the babies have any harvest. o((⊙)) o, o (⊙)) o. You can leave a message if you don't understand. I will often pay attention to my garden. If there's something wrong with it, the babies are also squeaking at me in the message area. We'll see you next time.