10월 안에 CNN-LSTM모델을 짜야 하는데 논문 구현해 놓은 깃허브를 보니 계속 nn.Sequential과 nn.ModuleList가 나와서 정리해야겠다 싶었음.
[nn.Sequential]
이것은 입력값이 하나일 때, 즉 각 레이어를 데이터가 순차적으로 지나갈 때 사용하면 좋은 방법이라는 것 같다.
간단히 말하자면 여러 nn.Module을 한 컨테이너에 집어넣고 한 번에 돌리는 방법이다.
구글에서 찾은 예제를 바탕으로 적당히 적어보면 아래와 같다.
import torch import torch.nn as nn class CNNModel(nn.Module): def __init__(self, class_num, filter_num, num): super().__init__(): self.layer1 = nn.Sequential(nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 5, stride = 1, padding = 2), nn.BatchNorm2d(num_feature = 32), nn.ReLu(), nn.MaxPool2d(kernel_size = 2, stride = 2)) self.layer2 = nn.Sequential(nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 5, stride = 1, padding = 2), nn.BatchNorm2d(num_features = 64), nn.ReLu(), nn.MaxPool2d(kernel_size = 2, stride = 2)) self.fc = nn.Linear(out_channels * num * num, class_num) self.dropout = nn.Dropout() def forward(self, seq): seq = self.layer1(seq) seq = self.layer2(seq) seq = self.fc(seq) return seq
class CNNModel(nn.Module): def __init__(self, class_num, filter_num, num): super().__init__(): self.layer1 = make_sequential(1, 32, kernel_size = 5, stride = 1, padding = 2) self.layer2 = make_sequential(32, 64, kernel_size = 5, stride = 1, padding = 2) self.fc = nn.Linear(out_channels * num * num, class_num) self.dropout = nn.Dropout() def forward(self, seq): seq = self.layer1(seq) seq = self.layer2(seq) seq = self.fc(seq) return seq
위 코드를 읽으면 바로 알 수 있겠지만 nn.Sequential은 코드에 적힌 순서대로 값을 전달해 처리한다. 빠르고 간단히 적을 수 있기 때문에 간단한 모델을 구현할 때에 쓰면 된다는 것 같다.
대신 위에서도 말했다시피 여러 입력값을 따로 따로 동시에 돌리는 것은 할 수 없다.
Medium에서 찾은 기사(Pytorch: how and when to use Module, Sequential, ModuleList and ModuleDict)에는 아예 class 앞에 Sequential을 만드는 함수를 만들어 간편하게 쓸 수 있다고 되어있다.
def make_sequential(in_channels, out_channels, *args, **kwargs): return nn.Sequential(nn.Conv2d(in_channels, out_channels, *args, **kwargs), nn.BatchNorm2d(out_channels), nn.ReLu(), nn.MaxPool2d(*args, **kwargs))
이 함수를 이용하면 당연하게도 첫 번째 코드가 간결해진다.
class CNNModel(nn.Module): def __init__(self, class_num, filter_num, num): super().__init__(): self.layer1 = make_sequential(1, 32, kernel_size = 5, stride = 1, padding = 2) self.layer2 = make_sequential(32, 64, kernel_size = 5, stride = 1, padding = 2) self.fc = nn.Linear(out_channels * num * num, class_num) self.dropout = nn.Dropout() def forward(self, seq): seq = self.layer1(seq)
seq = self.layer2(seq) seq = self.fc(seq) return seq
이제까지 적은 것은 내가 하나 하나 in_channel값, out_channel값 등을 지정해 적어줬는데 위 기사에 따르면 배열(array) 크기에 따라 자동으로 지정할 수 있다고 한다.
class CNNModel(nn.Module): def __init__(self, in_channels, out_channels1, out_channels2, class_num, filter_num, num): super().__init__(): self.layer_size = [in_channels, out_channels1, out_channels2] layers = [make_sequential(in_channels, out_channels, kernel_size = 5, stride = 1, padding = 2)\ for in_channels, out_channels in zip(self.layer_size, self.layer_size[1:])] self.encoder = nn.Sequential(*layers) self.fc = nn.Linear(out_channels * num * num, class_num) self.dropout = nn.Dropout() def forward(self, seq): seq = self.encoder(seq) seq = self.fc(seq) return seq
기사에는 encoder와 decode를 전부 nn.Sequential로 묶어서 CNN모델을 만들었는데 아예 class MyEncoder(nn.Module):, class MyDecoder(nn.Module):로 나눠서 위의 for문을 돌리고 class MyCNN(nn.Module):을 만들어서 정말 간결하게 적어놓았다.
정말 알아보기 쉽게 작성하는 방법이라 이런 스타일을 기억해 놨다가 꼭 좀 따라해봐야겠다.
[nn.ModuleList]
이것은 간단히 말해 nn.Module을 리스트로 정리하는 방법이다.
각 레이어를 리스트에 전달하고 레이어의 iterator를 만든다. 덕분에 forward처리를 간단하게 할 수 있다는 듯 하다.
처음으로 적는 것은 아주 무식하게 하나하나 다 적어서 리스트에 넣고 for로 돌리는 방식이다.
class CNNModel(nn.Module): def __init__(self, in_channels, out_channels1, out_channels2, class_num, filter_num, num): super().__init__(): self.conv1 = nn.Conv2d(1, 32, kernel_size = 5, stride = 1, padding = 2) self.batchnorm1 = nn.BatchNorm2d(32) self.relu1 = nn.ReLu() self.maxpool1 = nn.MaxPool2d(kernel_size = 2, stride = 2) self.conv2 = nn.Conv2d(32, 64, kernel_size = 5, stride = 1, padding = 2) self.batchnorm2 = nn.BatchNorm2d(64) self.relu2 = nn.ReLu() self.maxpool2 = nn.MaxPool2d(kernel_size = 2, stride = 2) self.fc = nn.Linear(out_channels * num * num, class_num) self.dropout = nn.Dropout() layers = [self.conv1, self.batchnorm1, self.relu1, self.maxpool1, self.conv2, self.batchnorm2, self.relu2, self.maxpool2, self.fc, self.dropout] self.module_list = nn.ModuleList(layers) def forward(self, seq): for layer in self.module_list: x = f(x) return x
두 번째로는 text classification cnn pytorch라고 검색했을 때 가장 많이 보이던 것인데, 커널의 region size를 for로 받아 nn.Conv2d를 리스트 안에 수납하는 형식이다.
class CNNModel(nn.Module): def __init__(self, vocab_size, embedding_dim, kernel_sizes, num_class): super().__init__() self.vocab_size = vocab_size self.embedding_dim = embedding_dim self.kernel_sizes = kernel_sizes self.embed = nn.Embedding(vocab_size, embedding_dim) self.conv = nn.ModuleList([nn.Conv2d(in_channels, out_channels, (kernel, embedding_dim))\ for kernel in kernel_sizes]) self.dropout = nn.Dropout() self.fc = nn.Linear(len(kernel_sizes) * out_channels, num_class)
Medium의 또 다른 기사 Understanding how Convolutional Neural Network (CNN) perform text classification with word embeddings에 따르면 여러 region size의 커널(filter)를 사용하는 것은 n-gram을 이용하는 것이라 되어있다.
가장 기본적(?)인지는 모르겠지만 가장 많이 보이는 것은 [2, 3, 4] 즉, 2-gram, 3-gram, 4-gram을 이용하는 것이었다. 그러니 커널 사이즈는 kernel * embedding_size가 되는 것이다.
'파이썬3 노트' 카테고리의 다른 글
pathlib 이야기: [Path.open()] [Path.home()] [exists()] [mkdir()] (0) | 2018.12.15 |
---|---|
[return] [yield] [yield from] (0) | 2018.12.15 |
모듈/함수 공부: [__hash__()][Collections-Counter] (0) | 2018.08.11 |
numpy 공부: [선형대수] (0) | 2018.08.10 |
numpy 공부: [Indexing] (3) (0) | 2018.08.09 |