Pytorch_RNN_timeseries_many_to_one

RNN timeseries Many to One 다루기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • 이전 포스트에서는 문장의 window 크기만큼을 입력받아 다음 문자를 예측하는 모델을 살펴보았다.

  • 이번 포스트에서는 날마다의 주식 기록인 연속된 데이터를 입력받아 종가를 예측하는 모델을 만들어본다.

  • 7일간의 데이터를 입력으로받아 종가를 예측하는 모델이므로 Many(7일) to One(마지막 종가) 모델이다.

  • 주식의 시작가, 최고가, 최저가, 거래량, 종가 즉, 5개의 값을 input으로 이용하는데 output으로 원하는건 종가 하나이다.

  • 따라서, hidden_dim을 1로 정한다면 RNN셀이 마지막 종가를 바로 예측한다고 생각할 수 있다.

  • 하지만 5개의 변수를 하나의 hidden state로서 RNN셀이 유통하는것은 어려운 일이기 때문에 hidden_dim을 10으로 설정하고 마지막에 fully connected layer을 사용해 마지막 종가를 예측하는 모델을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#-parameters---
seq_length = 7
data_dim = 5
hidden_dim = 10
output_dim = 1
learning_rate = 0.01
iterations = 500
#---------------

# 모델 생성
class Net(torch.nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, layers):
super(Net, self).__init__()
self.rnn = torch.nn.LSTM(input_dim, hidden_dim, num_layers=layers, batch_first=True)
self.fc = torch.nn.Linear(hidden_dim, output_dim, bias=True)

def forward(self, x):
x, _status = self.rnn(x)
#print(x.shape) --> rnn셀을 통과한 x의 shape을 직접 확인해보자.
x = self.fc(x[:,-1])
return x

net = Net(data_dim, hidden_dim, output_dim, 1)
  • LSTM셀을 정의할때 우리는 input_dim으로 시작가, 최고가, 최저가, 거래량, 종가인 5차원을 넣고 이를 hidden_dim이 10차원으로 유통한다.

  • 이렇게 정의한 LSTM셀을 input data가 통과한 후 shape을 살펴보면 (batch_size, seq_length, hidden_dim)의 모양인것을 확인할 수 있다.

  • 즉, 여기서는 LSTM의 output은 (batch_size, 7, 10)의 모양이며 우리가 원하는것은 7일의 output중 마지막 output data이다.

  • 따라서 LSTM을 통과한 x의 마지막 output data를 fully connected layer에 넣어준다.

  • 이를 통해 10차원의 hidden_dim으로 유통되던 데이터가 마지막 종가인 하나의 값으로 예측할 수 있게된다.

Full Code

Full Code

Pytorch_RNN_long_sequence

RNN Long Sequence 다루기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • RNN intro를 통해 hello 예제를 살펴보았다.

  • 비슷하게 one hot encoding을 통해 hello보다 긴 문장을 다루어보고자 한다.

  • 먼저, 문장으로부터 각 알파벳과 인덱스를 매칭시키는 dict를 만들고 이를 활용하고자 한다.

1
2
3
4
5
6
sentence = ("if you want to build a ship, don't drum up people together to "
"collect wood and don't assign them tasks and work, but rather "
"teach them to long for the endless immensity of the sea.")

char_set = list(set(sentence))
char_dic = {c:i for i, c in enumerate(char_set)}
  • 윈도우 크기를 정하고 그에 맞게 문장을 잘라서 input data X로 사용하고 하나의 character만큼 오른쪽으로 쉬프트한 윈도우 크기의 문장을 Y로 사용하고자 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
예를 들어, 윈도우 크기가 5라면 처음 X는
X = if yo --> Y = f you
가 되는 방식이다.

이 방법을 통해 문장 전체를 윈도우 크기로 순회해서 데이터X와 Y를 만든다.

x_data = []
y_data = []

for i in range(0, len(sentence) - sequence_length):
x = sentence[i: i+sequence_length]
y = sentence[i+1: i+sequence_length+1]

x_data.append([char_dic[c] for c in x])
y_data.append([char_dic[c] for c in y])

x_one_hot = [np.eye(dic_size)[x] for x in x_data]


X = torch.FloatTensor(x_one_hot).to(device)
Y = torch.LongTensor(y_data).to(device)
  • 이제 원핫인코딩된 X로부터 라벨값 Y를 예측하는 문제의 데이터가 만들어졌다.

  • RNN모델을 만들때 이전과는 다르게 두 층의 RNN layer와 fully connected layer를 이용해보고자 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Net(torch.nn.Module):
def __init__(self, input_dim, hidden_dim, layers):
super(Net, self).__init__()
self.rnn = torch.nn.RNN(input_dim, hidden_dim, num_layers=layers, batch_first=True)
# num_layers를 설정함으로써 RNN layer를 여러겹 쌓을 수 있다.
self.fc = torch.nn.Linear(hidden_dim, hidden_dim, bias=True)

def forward(self, x):
x, _status = self.rnn(x)
x = self.fc(x)
return x

net = Net(dic_size, hiddend_size, 2).to(device)
  • 위의 모델을 통해 학습하면서 예측을 통해 완성되는 문장을 확인해본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
criterion = torch.nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(net.parameters(), lr=learning_rate)
for i in range(100):
optimizer.zero_grad()
outputs = net(X)
loss = criterion(outputs.view(-1,dic_size), Y.view(-1))
loss.backward()
optimizer.step()

results = outputs.argmax(dim=2)
predict_str = ''

for j, result in enumerate(results):
if j==0:
predict_str += ''.join([char_set[t] for t in result])
else:
predict_str += char_set[result[-1]]

print(predict_str)
  • 마지막에 j가 0일때와 아닐때로 나눈 이유는 첫 번째 예측에서 sequence_length만큼의 문장을 만들어주면 그 이후로부터는 result에서 마지막 문자를 제외하고는 이전 문장과 동일하기 때문이다.

  • 예를 들어 sequence_length가 5이고 j가 0일때 “if yo” 라는 문장을 만들었고 다음 result에서는 “f you”를 예측했다면 이미 이전 문장과 그 다음 문장에서 “f yo” 라는 단어들은 겹치게된다.

  • 따라서, j가 0일때 sequence_length만큼의 문장을 만들고 그 이후로는 result의 마지막 값만을 가져와서 predict_str에 이어붙이게 만들어준다.

Full Code

Full Code

Pytorch_RNN_intro

RNN Intro

모두를 위한 딥러닝 - 파이토치 강의 참고

  • 이미지 데이터가 아니라 순서 정보가 중요한 sequential data를 처리하기 위한 모델

  • RNN

  • 위 그림에서 왼쪽 그림과 같이 RNN 모델을 나타내며 이를 펼치면 오른쪽과 같다.

  • F 라는 하나의 셀에 입력값 X 가 처리되어 h를 출력하고 다음 셀에 hidden state를 유통한다.

  • 이렇게 하나의 셀에서 hidden state를 다음 셀에 넘겨주고 다음 셀이 입력값 X와 함께 계산해 h를 출력한다면, 해당 출력값 h는 이전 셀의 영향을 이어받을 것이다. 따라서 연속적인 데이터의 정보를 기억하는 효과를 나타낼 수 있게된다.

  • RNN은 모든 셀이 parameter를 공유한다. 다시 말해, 위의 그림처럼 셀 F 하나를 공유해서 사용하게 된다.

  • pytorch에서 RNN은 input_sizehidden_size를 입력받아 정의하고 input_data(batch_size, sequential_length, input_size) 형태의 data를 입력한다.

1
2
rnn = torch.nn.RNN(input_size, hidden_size)
outputs, _status = rnn(input_data)
  • 이때, RNN셀이 batch_size와 sequential_length는 자동으로 파악해준다.

  • 위처럼 RNN을 정의하게 되면 input_data로 (batch_size, sequential_length, input_size)형태를 입력해 (batch_size, sequential_length, hidden_size)형태의 출력값이 나오게 된다.

  • hello 라는 단어를 통한 예제를 살펴보자.

  • hello의 각 알파벳을 4차원의 one hot encoding을 해주고 각 알파벳의 조합으로 만들 수 있는 단어 3개를 하나의 배치로 묶어서 사용해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
input_size = 4
hidden_size = 2

# 4차원의 one hot 벡터로 표현
h = [1,0,0,0]
e = [0,1,0,0]
l = [0,0,1,0]
o = [0,0,0,1]

# input size는 (3,5,4) 가 된다
# 3 --> batch_size
# 5 --> sequence length : 문자열의 길이 = RNN sequence의 길이
# 4 --> input_size : one hot 벡터의 길이 --> embedding 을 한다면 embedding 크기가 될것
input_data_np = np.array([[h,e,l,l,o],
[e,o,l,l,l],
[l,l,e,e,l]], dtype=np.float32)

# numpy 데이터 텐서로 변환
input_data = torch.Tensor(input_data_np)

# RNN셀 정의
rnn = torch.nn.RNN(input_size, hidden_size)

# input_data rnn셀 통과
# output size는 2이므로 rnn셀을 통과한 output은 (3,5,2) shape을 가질것이고 이를 확인해본다.
output, _status = rnn(input_data)

print(output)
print(output.shape)
  • 위와같이 RNN 모델을 정의하고 간단한 데이터를 통해 output을 확인할 수 있었다. hidden_state의 size를 RNN에서 정의하면 이것이 output_data의 크기와 동일하다.

  • 예제를 통해 RNN을 정의할때의 값이 의미하는 바를 파악할 수 있었고 그에 따라 input_data의 shape을 맞춰서 사용하는 방법을 파악할 수 있었다.

Pytorch_Activation_plot

conv layer의 output을 그려보자

  • 파이토치를 메모리를 아끼기위해 모델에서 나오는 마지막 output만을 저장한다.

  • 그렇다면, 각 conv layer를 통과한 output이 궁금하다면 어떻게 해야할까??

  • hook을 이용하면 forward와 backward에서 각 레이어의 output을 가져올 수 있다.

  • 여기서는 forward hook을 이용하는 class를 만들어서 이용해보고자 한다.

  • 사용할 모델은 pretrain vgg16모델이며 input으로는 인터넷에서 아무 고양이 사진을 사용해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 이미지 불러와서 tensor로 변환 및 사이즈 조정
simple_transform = transforms.Compose([transforms.Resize((224,224))
,transforms.ToTensor()
,transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
img = Image.open('../image/cat_1.jpg')
img = simple_transform(img)

# unsqueeze_를 통해 shape에 (1,3,224,224) 로 변경 --> batch_size를 추가한것 --> 모델에 넣기위해
img.unsqueeze_(0)

# pretrained 모델을 불러와서 사용한다. --> 직접 train시킨 모델도 상관없지만 현재 목표는 모델 학습이 아니기 때문에 pretrained모델 사용
vgg = models.vgg16(pretrained=True).cuda()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LayerActivations():
# layer의 output을 저장할 공간을 만들자.
features=None

# hook을 정의하고 정한 layer_num에 hook을 등록할 수 있게 만든다.
def __init__(self,model,layer_num):
self.hook = model[layer_num].register_forward_hook(self.hook_fn)

# 정해진 layer에서 forward함수가 실행되면 output을 저장할 수 있는 함수를 만든다.
def hook_fn(self,module,input,output):
self.features = output.cpu().data.numpy()

# 등록된 hook을 없애줄 수 있는 함수를 만든다.
def remove(self):
self.hook.remove()
  • 이제 모델에서 첫번째 conv layer를 통과한 후 나오는 output을 LayerActivations객체에 저장하고 이미지를 그려보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# vgg.features의 첫번째(0) 에 hook을 등록하고 output을 conv_out에 받도록 한다.
conv_out = LayerActivations(vgg.features,0)

# 가장 처음 가져온 이미지를 vgg모델에 넣어보자.
# 이미지가 모델을 통과할때 위에서 정의한 conv_out에 첫번째 conv layer를 통과하며 내놓은 output을 저장했을 것이다.
o = vgg(Variable(img.cuda()))

# 등록된 hook을 제거해준다.
conv_out.remove()

# conv_out객체의 features값을 가져오면 우리가 알고싶은 첫번째 conv layer를 통과했을때의 output을 알 수 있다.
act = conv_out.features

# 30개의 채널만을 plot으로 그려보자.
fig = plt.figure(figsize=(20,50))
fig.subplots_adjust(left=0,right=1,bottom=0,top=0.8,hspace=0,wspace=0.2)
for i in range(30):
ax = fig.add_subplot(12,5,i+1,xticks=[],yticks=[])
ax.imshow(act[0][i])
  • 첫번째 conv layer를 통과한 output을 그려보면 다음과 같다.

  • output conv first

  • 첫 번째 conv layer는 윤곽선에 집중되는 모습을 확인할 수 있다.

  • 이를 이용해 마지막 conv layer 통과한 결과도 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 마지막 conv layer는 28번째 feature이다.
# 모든 feature를 확인해보고자 한다면 vgg.features 를 입력해서 확인해보자.
conv_out = LayerActivations(vgg.features,28)

o = vgg(Variable(img.cuda()))

conv_out.remove()

act = conv_out.features

fig = plt.figure(figsize=(20,50))
fig.subplots_adjust(left=0,right=1,bottom=0,top=0.8,hspace=0,wspace=0.2)
for i in range(30):
ax = fig.add_subplot(12,5,i+1,xticks=[],yticks=[])
ax.imshow(act[0][i])
  • output conv last

  • 위는 마지막 conv layer를 통과한 output을 시각화한 것이다.

  • 마지막 레이어는 첫번째 레이어와 다르게 해석하기 어려운 특성을 학습하는 것으로 파악할 수 있었다.

  • 이처럼 필요에 따라서 각 레이어의 output을 hook을 이용해 시각화하는 방법을 공부해 볼 수 있었으며 이를 통해 모델의 각 layer가 집중하는 부분도 확인할 수 있을것이라 생각한다.

Full Code

Full Code

Pytorch_RESNET_with_CIFAR10

CIFAR10에 RESNET모델 적용해보기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • RESNET은 3X224X224를 기본 input으로 만들어졌다. 이 모델에 다른 크기를 가진 CIFAR10 이미지를 적용시키고자 한다.

  • BasicBlock과 Bottleneck class 부분은 그대로 유지하면 RESNET Class만 수정해서 사용하고자 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ResNet(nn.Module):
# CIFAR10 이미지 --> 32x32 크기의 이미지가 입력이므로 기본 resnet을 일부 수정해서 사용
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False):
super(ResNet, self).__init__()
self.inplanes = 16
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1,
bias=False)
self.bn1 = nn.BatchNorm2d(16)
self.relu = nn.ReLU(inplace=True)

self.layer1 = self._make_layer(block, 16, layers[0], stride=1)
self.layer2 = self._make_layer(block, 32, layers[1], stride=1)
self.layer3 = self._make_layer(block, 64, layers[2], stride=2)
self.layer4 = self._make_layer(block, 128, layers[3], stride=2)

self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(128 * block.expansion, num_classes)
.
.
.
  • 위와 같이 layer1~4에서 output의 채널수와 layer2의 strid를 2에서 1로 수정하였고 각 layer를 통과하기전의 max pooling을 삭제하였다.

  • 위와같이 수정했을 때 forward함수에서 각 layer를 통과한 output의 shape을 예상해 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ResNet(nn.Module):
.
.
.
# 3x32x32 이미지를 input으로 넣었을 때, 각 layer를 통과한 후의 output shape
def forward(self, x):
x = self.conv1(x)
#x.shape =[1, 16, 32,32]
x = self.bn1(x)
x = self.relu(x)

x = self.layer1(x)
#x.shape =[1, 128, 32,32]
x = self.layer2(x)
#x.shape =[1, 256, 32,32]
x = self.layer3(x)
#x.shape =[1, 512, 16,16]
x = self.layer4(x)
#x.shape =[1, 1024, 8,8]

x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)

return x
  • 이후 bottleneck을 통한 resnet모델을 정의하고 학습시킬 수 있다. 이렇게 크기가 다른 이미지를 모델에 적용하고자 할 때 resnet class를 수정해서 사용할 수 있었다.

Full Code

Full Code

Pytorch_About_RESNET

RESNET 모델의 생성과정 살펴보기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • CNN advanced model중 하나인 RESNET의 모델 생성과정을 소스코드를 보며 살펴보고자 한다.

  • RESNET에서 파이토치 소스코드를 볼 수 있으며 이를 기준으로 모델 생성 과정을 따라가보았다.

  • RESNET은 RESNET Class외에 BasicBlock과 Bottleneck class를 통해 모델이 생성된다.

  • RESNET 모델의 생성과정에서 downsample을 이용해 output의 shape을 맞춰주게 된다.

  • 예를 들어, BasicBlock에 stride=2 로 설정해 3x64x64의 이미지가 들어갔을때 아래의 forward함수를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

class BasicBlock(nn.Module):
expansion = 1
# stride값으로 2가 들어갔을 때를 살펴보자
def __init__(self, inplanes, planes, stride=2, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride

# x로 3x64x64의 input이 들어갔을때 각 output의 shape을 생각해보자.
def forward(self, x):
identity = x
# identity shape = 3x64x64

out = self.conv1(x)
# out shape = 3x32x32
out = self.bn1(out)
out = self.relu(out)

out = self.conv2(out)
#out shape = 3x32x32
out = self.bn2(out)

if self.downsample is not None:
identity = self.downsample(x)

# stride를 2로 적용하니 out의 shape과 identity의 shape이 맞지 않아서
# 둘을 아래와 같이 더해줄 수 없는 문제가 발생한다.
# 이러한 문젤르 해결해 shape를 맞춰주도록 사용하는게 downsample 이다.

out += identity

out = self.relu(out)

return out
  • 파이토치에서는 resnet18~152까지 지원하는데 이번에는 resnet50의 생성과정을 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
model.load_state_dict(state_dict)
return model

def resnet50(pretrained=False, progress=True, **kwargs):
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)
  • resnet50은 Bottleneck class를 ResNet class가 받으며 만들어지며 layers로 [3,4,6,3]의 리스트를 받는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ResNet(nn.Module):
.
.
.
def _forward_impl(self, x):
# See note [TorchScript super()]
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)

x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)

return x
  • 기본적으로 앞의 conv1, bn1, relu, maxpool를 지난뒤 layer1~4까지를 통과하게 된다. 이때 각 layer는 VGG와 비슷하게 make_layer함수를 통해 이루어지게 되며 다음과 같은 값을 받게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ResNet(nn.Module):
# _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, **kwargs)
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
.
.
.
# layers값으로 [3,4,6,3]의 리스트를 받았으므로 아래의 layer1~4는 각각 3,4,6,3을 받게된다.
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
# make_layer함수가 block를 Bottleneck으로, planes로 64 그리고 blocks로 3을 입력받게 된다.
  • make_layer에서 layer1이 생성되는 과정은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)

layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))

return nn.Sequential(*layers)

# layer1 = _make_layer(Bottleneck, 64, 3)
# Bottleneck.expansion = 4

1. (self.inplanes = 64) != (planes = 64) * (Bottleneck.expansion = 4) 이므로 downsample이 실행된다.
이때 downsample은 채널의 수를 맞춰주기 위해서 실행되는 과정이다.

2. 이제 layers 리스트에 block()이 하나씩 추가되게 되는데 처음으로 Bottleneck(64, 64, 1, downsample)이 추가된다.

layers = [
Bottleneck(64, 64, 1, downsample)
]

3. 그리고 self.inplanes값이 planes * block.expansion = 64 * 4 = 256으로 변한다.

4. 다음으로 blocks의 값이 3을 입력받았으므로 두번의 for loop를 돌며 두 개의 Bottleneck()이 추가된다.

layers = [
Bottleneck(64, 64, 1, downsample)
Bottleneck(256, 64)
Bottleneck(256, 64)
]
  • 위와 같은 과정을 통해 layer1이 만들어졌는데 실제 ResNet50을 불러서 위에서 계산한 값과 동일하게 Bottleneck()을 통해 생성되었는지 확인해 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import torchvision
resnet = torchvision.models.resnet50()
print(resnet)
#>>>
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
(conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
.
.
.
  • 실제 resnet50에서도 layer1에서 총 3개의 Bottleneck이 생성된것을 확인할 수 있다. 각 Bottleneck의 convolution layer값 또한 확인해 볼 수 있었다.

  • 위와 같은 방식으로 총 4개의 layer가 구성되며 각 layer의 깊이는 입력받는 blocks수로 결정된다.

  • 이와같은 ResNet모델의 기본 입력은 3x224x224이다. 하지만 우리가 입력하고자 하는 이미지의 크기가 다를 수 있는데 이와 같은 상황일 때 어떻게 모델을 수정하고 적용할 수 있는지 다음 포스트에서 살펴보고자 한다.

Pytorch_About_SGD

stochastic gradient descent(SGD)에 대하여

  • 데이터 학습을 할 때 optimizer로 많이 사용되는 SGD에 대해 알아보고자 한다.

  • SGD는 Batch Gradient Descent(BGD)같이 전체 데이터셋에 대한 미분값을 계산하지 않고, mini-bathc 만큼의 데이터셋에 대해서만 계산을 진행한다.

  • 이렇게 되면 전체 데이터셋을 메모리에 올릴 필요가 없으므로 큰 메모리가 요구되지 않게 됩니다.

  • stochastic 이라고 말하는 이유 또한 mini-batch만 보기 때문입니다.

    • BGD가 전체 데이터셋을 다룬다는 의미는 같은 데이터를 계속 살펴본다는 의미이다. 그렇기 때문에 gradient 값이 하나로 주어지게 되며 이를 deterministic 하다고 말합니다.

    • 하지만, SGD의 경우 mini-batch로 데이터를 보기 때문에 mini-batch를 어떻게 선택하냐에 따라서 gradient값이 다르게 나오게 됩니다.

    • 따라서 gradient의 흐름이 정해지지 않고 확률적으로 나타난다는 의미에서 stochastic 이라고 말하게 됩니다.

  • SGD의 장점

    • mini-batch로 step을 진행하므로 더 빠른 학습이 가능하다.

    • local-minima을 피할 수 있게 된다.

    • gradient descent

    • 위의 그림을 보면 BGD는 전체 데이터셋에 대한 계산을 진행하기 때문에 minimum 방향으로 곧장 나아가게 된다.

    • 하지만 SGD는 어떤 batch를 선택했느냐에 따라 minimum을 향해가는 방향이 달라지게 된다. 이렇게 minima의 방향을 알고서 향해가는 것이 아니기 때문에 local minima를 피해갈 수 있게 된다.

  • SGD의 단점

    • SGD는 하나의 축에 대해서는 minima이지만 다른 축에 대해서는 아닌 saddle point를 벗어나지 못하는 문제점이 있다.

    • SGD saddle point

    • 이를 해결하기 위한 SGD의 변형 알고리즘인 Momentum, NAG, Adadelta 등이 존재하며 이는 다음 포스트에서 살펴보려고 한다.

  • 출처

Pytorch_VGG_with_CIFAR10

CIFAR10 데이터셋에 VGG 모델 적용해보기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • VGG는 3x224x224의 input을 기본으로 만들어져 있다. 따라서, 이미지의 크기가 다를경우 이미지 크기를 조정하거나 모델을 수정해서 사용할 수 있다.

  • 이번에는 이미지 사이즈는 그대로 사용하며 VGG모델을 조금 수정해서 적용해보자.

  • VGG의 모델이 어떻게 생성되는지는 이전 포스트 - VGG 모델 생성 살펴보기에서 확인할 수 있다.

  • 이번에는 custom convolution layer 을 만들고 이를 maye_layers함수를 통해 생성한 후, 이를 통한 모델을 만들고자 한다.

  • convolution layer 13개와 fully connected layer3개를 가지는 VGG13 configuration을 생성한다.

1
cfg = [32,32,'M', 64,64,128,128,128,'M',256,256,256,512,512,512,'M']
  • 그리고, VGG Source Code에서 가져온 VGG class를 다음과 같이 일부 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class VGG(nn.Module):
def __init__(self, features, num_classes=1000, init_weights=True):
super(VGG, self).__init__()
self.features = features
self.classifier = nn.Sequential(
nn.Linear(512*4*4, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes)
)
if init_weights:
self._initialize_weights()

def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x

def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
  • 위의 코드는 Source Code에서 아래와 같은 두 가지 사항을 변경했다

    • a. nn.AdaptiveAvgPool2d를 삭제했다.

      • 왜냐하면, features layer를 통과하고 우리의 이미지 사이즈는 4x4이므로 nn.AdaptiveAvgPool2d((7, 7))을 통해 사이즈를 키워줄 필요가 없었다.
    • b. classifier layer의 fully connected layer의 input size가 (batch size x 4 x 4) 로 수정되었다.

      • 왜냐하면, 위와 마찬가지로 features를 통과한 이미지의 사이즈가 4x4이기 때문이다.
  • 이처럼, input size가 다를 경우 기존의 모델을 수정해서 사용할 수 있다.

  • 중요한점은, layer를 통과하면서 image의 size가 어떻게 변하는지를 알고 fully connected layer까지 수정해야 size에러가 발생하지 않는다는 점이다.

  • 이를 위해서, 직접 공식을 통해 계산할 수도 있으며 아래와 같이 forward함수에서 shape를 프린트하며 확인할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class VGG(nn.Module):
.
.
.
def forward(self, x):
x = self.features(x)
print(x.shape) # features layer를 통과하고 shape을 확인해보자.
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
.
.
.
  • 이처럼, 사이즈가 다른 CIFAR10 이미지를 수정한 VGG모델에 넣어서 학습시켜볼 수 있었다.

Full Code

Full Code

Pytorch_About_VGG_Advance_CNN

VGG 모델의 생성과정 살펴보기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • torchvision.models를 통해 VGG11~VGG19까지 이용할 수 있다.

  • 3x224x224 input을 기준으로 만들어져 있으며, input size가 다른경우 모델을 약간 수정해서 사용할 수 있다.

  • 이번에는 VGG에서 layer가 생성되는 과정을 살펴보려고 한다.

  • VGG의 Source Code이며 일부만 가져와서 살펴보자.

  • VGG 모델의 Class선언의 __init__ 부분을 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class VGG(nn.Module):

def __init__(self, features, num_classes=1000, init_weights=True):
super(VGG, self).__init__()
self.features = features
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes),
)
if init_weights:
self._initialize_weights()
.
.
.
  • features라는 레이어를 통과하고 pooling and linear layer를 통과하는 모습을 확인할 수 있다.

  • 즉 처음 layer는 features부분에서 생성되는데 이는 make_layers라는 함수를 통해 이루어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def make_layers(cfg, batch_norm=False):
layers = []
in_channels = 3
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)]
in_channels = v
return nn.Sequential(*layers)

cfgs = {
'A': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
  • make_layers함수를 살펴보면 cfg값에 따라서 알맞는 layer를 return하고 있다.

  • cfgs에서 ‘A’값을 받았을때 어떻게 Convolution layer가 만들어지는지 따라가보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
순차적으로 'A'의 값이 for문을 통해 들어가면 layers에 어떻게 layer가 쌓이는지 누적시키며 따라가보자.
(batch_norm=False)인 상황이다.
1. 처음에 64 값을 받으면,
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)
]
가 되며 in_channels가 입력받은 64로 바뀌게된다!

2. 'M' 을 입력받으면, MaxPool2d가 들어가며 in_channels값은 바뀌지 않는다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)
]

3. 다시 128 값을 받으면, 처음 64를 입력받았을때와 마찬가지로 ```Conv2d```와 ```ReLU```가 추가되며 in_channels는 128로 바뀌게된다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)
]

4. 다음으로 'M'을 입력받으면, 두번째 단계와 마찬가지로 MaxPool2d가 들어가고 in_channels는 유지된다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)
]

5. 다음으로 256 값을 두번 연속해서 받으면, 첫번째 단계가 두번 실행되는것과 같다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(128, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(256, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)
]

6. 세번째 'M'을 입력받아서 다음과 같다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(128, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(256, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)
]

7. 위의 과정을 통해 익숙해졌으므로 [512, 512, 'M'] 입력을 한번에 살펴보자. 세 개의 입력이 들어오면 다음과 같이 layers가 쌓이게된다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(128, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(256, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(256, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(512, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)
]

8. 마지막으로 다시 한번 [512, 512, 'M'] 입력을 받는다. 따라서 최종적으로는 다음과 같은 layers가 생성된다.
layers = [
conv2d= nn.Conv2d(3, 64, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(64, 128, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(128, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(256, 256, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(256, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(512, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)

conv2d= nn.Conv2d(512, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

conv2d= nn.Conv2d(512, 512, kernel_size=3, padding=1)
nn.ReLU(inplace=True)

nn.MaxPool2d(kernel_size=2, stride=2)
]
  • 위의 과정을 따라서 만들어진 모델은 VGG11을 의미하게 되는데, 다음과 같이 계산해 볼 수 있다.

  • make_layers를 통해 만들어진 Convolution layer의 갯수는 8개였다. 그리고 Source Code를 살펴보면 이후에 fully connected layer가 3개있다.

  • 따라서, 위의 과정으로 만들어진 모델은 8 + 3 = 11 로 VGG11 을 의미한다.

  • 이와 마찬가지로, 나머지 모델은 다음과 같다.

1
2
3
'B' --> 10 + 3 = 13 --> VGG13
'C' --> 13 + 3 = 16 --> VGG16
'D' --> 16 + 3 = 19 --> VGG19
  • Source Code를 따라가면서 VGG모델이 생성되는 과정을 살펴볼 수 있었고 다음에는 input size가 다를경우 VGG를 수정해서 사용하는 방법을 살펴볼 것이다.

Full Code

Full Code

Pytorch_ImageFolder

ImageFolder를 통해 데이터 가져오기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • 분류된 이미지 데이터셋이 준비되어있다면 ImageFolder를 통해서 데이터를 가져올 수 있다.

  • 예를들어, 3개의 클래스를 가지는 이미지셋을 준비했다면 다음과 같은 폴더 형태로 담아내면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
/project
ㄴdata
ㄴdataset_name
ㄴtrain_data
ㄴclass1
ㄴclass2
ㄴclass3
ㄴtest_data
ㄴclass1
ㄴclass2
ㄴclass3
ㄴImageFolder_EX.py
  • 위와 같이 각 클래스별로 데이터를 준비했다면 ImageFolder 를 이용해 데이터를 불러온다.

  • MNIST나 CIFAR10의 데이터를 불러올 때처럼, tansform을 통해 텐서로 변환해 가져오게 된다.

1
2
3
4
5
6
7
8
9
10
11
trans = transforms.Compose([
transforms.ToTensor()
])

train_data = torchvision.datasets.ImageFolder(root='data/custom_data/train_data', transform=trans)

data_loader = DataLoader(dataset=train_data, batch_size=8, shuffle=True)

test_data = torchvision.datasets.ImageFolder(root='data/custom_data/test_data', transform=trans)

test_loader = DataLoader(dataset=test_data, batch_size=len(test_data))
  • 이후 간단한 CNN 모델을 만들어서 자신이 가진 데이터셋을 학습시킬 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(2),
)
.
.
.
# Full Code 참조
  • 이전까지는 Pytorch에서 바로 다운로드하고 불러올 수 있는 MNIST, CIFAR10과 같은 데이터셋을 이용했다.

  • 하지만, 이번에는 자신이 가진 고유의 데이터셋을 ImageFolder를 통해 불러오고 학습시키는 과정을 알 수 있었다.

Full Code

Full Code

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×