Galaxy Zoo classification

  • 목적

  1. Galaxy Zoo data를 이용하여 머신러닝으로 은하의 morphology를 구별의 정확도를 확인 하고자 한다.

  2. CAM을 통해서 은하를 분류하는데 있어 머신러닝 모델이 가장 중요하게 생각하는 structure를 확인 하고자 한다.


자료 정리

Galaxy Zoo 2 : Galaxy Zoo 2: detailed morphological classifications for 304 122 galaxies from the Sloan Digital Sky Survey

Galaxy Zoo "Class 1"

Class 1 - 1 : Smooth --> move to Class 7
Class1 - 2 : Features or Disk --> move to Class 2
Class 1 - 3 : star or Artifact --> move to END

준비작업
*** 각 Class별 폴더를 만들고 해당 하는 이미지를 분류해서 넣어주자. ****


1-1. 학습을 위한 이미지 "transforms"

from torchvision import transforms

input_size=224

rotation=90

full_transforms = transforms.Compose(
[transforms.Resize((input_size,input_size)),
transforms.RandomRotation(rotation),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5],[1,1,1])])

  • transforms.Resize : 학습할 이미지 size를 통일 하기 위해 이미지 사이즈를 재정의한다.

  • RandomRotation() : 학습할 이미지를 Random하게 회전시킨다.

  • Normalize([],[]) : image 자료가 jpg, png처럼 color image라면 r,g,b 3개의 레이어를 가지고 있을 것이다. 각각의 레이어에 Normalize한다고 생각하면 된다.

  • *** transforms.Compose의 다양한 함수들을 알아둘 필요가 있다.!!

  • “transforms” 함수는 학습할 이미지를 어떻게 변화시킬것인지에 대한 함수를 정의하는 것이다. 따라서 우리는 이미지를 불러올때 이 함수를 적용하여 이미지를 변환한 후 학습 이미지로 사용할 것이다.

1-2 이미지 불러오기

*** 참고자료***
https://honeyjamtech.tistory.com/38
https://github.com/ufoym/imbalanced-dataset-sampler?fbclid=IwAR1IYT6RzMTjk3CBwRlK99w-npjURktHKiUC2AJ3Qs2blaTd2WfLoKilUS0


from torchvision import datasets


full_dataset=datasets.ImageFolder(train_dir,transform=full_transforms)

train_size = int(0.8 * len(full_dataset)) # 전체 자료의 80% size를 계산한다.

valid_size = len(full_dataset) - train_size # 나머지 20% size를 계산한다.

train_dataset, valid_dataset = torch.utils.data.random_split(full_dataset, [train_size, valid_size]) # 전체 자료를 train data와 validation data로 나누는데 "torch.utils.data.random_split"을 이용하여 random하게 8:2로 자료를 나눠준다.


  • datasets.ImageFolder 를 사용하기 위해서는 이미지의 label별로 Folder 형태로 정리되어 있어야 한다. 예로들이 GalImage/elliptical, GalImage/spiral ... 이런식으로 폴더가 정리 되어 있다고 한다면, train_dir은 ./GalImage/가 되어야 한다. 즉, datasets.Folder는 GalImage 하위 폴터를 탐색하여 image를 불러드린다는 것을 의미한다.

  • 학습할 이미지를 가져오기전에 우리는 학습자료를 두 그룹으로 나눌 것이다. 학습자료(Train data) + 모델확인하기 위한 자료(validation data)

import torch

_batch_size=64
train_loader =
torch.utils.data.DataLoader(train_dataset, batch_size=_batch_size, shuffle=True)

valid_loader = torch.utils.data.DataLoader(valid_dataset, batch_size=_batch_size, shuffle=True)


train_loader = torch.utils.data.DataLoader(train_dataset, sampler=ImbalancedDatasetSampler(train_dataset), batch_size=_batch_size)

"torch.utils.data.DataLoader" 는 불러드린 image를 학습하기위한 적당한 형태의 구조로 묶는 역확을 한다다. 예로 들어 batch_size = 64는 64개의 이미지를 1개의 set로 묶는 것을 의미하는데, training_dataset에서 "datasets.ImageFolder"를 이용한 dataset을 만들었기 때문에 각 이미지 당 해당하는 larbel를 같이 가지고 다닌다는 점을 기억하자!!

ImbalancedDatasetSampler : train sample 중 분류할 class가 동일 수로 분포해 있어야 옳은 학습을 할 수 있다. 이렇게 동일한 수로 맞추기 위한 함수

모델 (Model)

2.1 vgg16 model

이번에 사용한 model은 VGG16이라는 모델이다. 모델에 자세한 설명은 다음 링크에서 확인하시면 된다.
Transfer Learing을 사용할 것이다.
https://neurohive.io/en/popular-networks/vgg16/
https://bskyvision.com/504

Transfoer Learing에 대한 정보
http://incredible.ai/artificial-intelligence/2017/05/13/Transfer-Learning/
https://jeinalog.tistory.com/13
https://towardsdatascience.com/transfer-learning-from-pre-trained-models-f2393f124751

from torchvision import models

Net = models.vgg16(pretrained=True) : # vgg16을 다운 받는다. 기본적으로 'torchvision'에 'models' 함수에 링크가 걸려있는것 같다.

display(Net) # model의 구조를 확인한다

# 사전 훈련 된 모델에서 가중치를 업데이트하지 않도록합니다.
for param in Net.parameters():
param.requires_grad = False

in_features = 25088 # 사전에 학습된 모델을 통해서 알 수 있다. vgg16을 받아서 사용한다면, display(model)을 통해서 확인하는 값을 넣으면 된다.
out_categories = 3 # 분류하고자 하는 label의 개수이다. 앞서 이미지를 load할때 "datasets.ImageFolder" 를 사용했다면, sub-class의 폴더 개수와 값아야 한다.
layer_1 = 512

classifier = nn.Sequential(OrderedDict([
('dropout1', nn.Dropout(0.5)),
('fc1', nn.Linear(in_features, layer_1)),
('relu', nn.ReLU()),
('dropout2', nn.Dropout(0.5)),
('fc2', nn.Linear(layer_1, out_categories)),
('output', nn.LogSoftmax(dim=1))
]))

Net.classifier = classifier

2.1 resnet18 model

만약에 resnet18 model을 사용하고 싶다면...

다음과 같습니다.
Num_class 는 분류하고자 하는 종류의 수 입니다.
Num_class 는 model의 최종 out_feature를 바꿔주는 역활을 합니다. 물론 구별하고자 하는 수보다 높게 들어가 있어도 정상 작동을 하지만, 그렇게 되면 out_feature에 필요없는 자료가 쌓이게 됩니다. 따라서 구별하고자 하는 class 숫자를 맞춰주는 편이 좋습니다.

#resnet model loader

device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")

num_class=2

Net = models.resnet18(pretrained=True)
num_ftrs = Net.fc.in_features
Net.fc = nn.Linear(num_ftrs,num_class)
Net = Net.to(device)
display(Net)


Machine Learning

3.1 setting

# Instantiate loss function
loss_function = nn.NLLLoss()


# Instantiate optimization algorithm
learning_rate = 0.0005
optimizer = optim.Adam(Net.classifier.parameters(), lr=learning_rate)


# Enable CUDA: use GPUs for model computation
if torch.cuda.is_available():
Net.to('cuda')

param = list(Net.parameters())
print(len(param))
for i in param:
print(i.shape)

3.2 Run

#using Multi gpu setting

Net = nn.DataParallel(Net)
Net.cuda()

# Epochs: number of iterations over the entire training dataset

epochs = 3

# Number of iterations between printing loss and accuracy

print_steps = 30

# Initialize steps

step = 0

# Iterate over number of epochs

for e in range(epochs):
running_loss = 0

# Iterate over the entire training dataset
# one batch per iteration

for i, data in enumerate(train_loader, 0):

# get the inputs

inputs, labels = data

# Enable CUDA: use GPUs for model computation

if torch.cuda.is_available():
inputs, labels = inputs.to('cuda'), labels.to('cuda')

# Clear the gradients of all optimized tensors
optimizer.zero_grad()

# Forward pass
outputs = Net.forward(inputs)
loss = loss_function(outputs, labels)

# Backward pass
loss.backward()
optimizer.step()

# Calculate and print running training loss
running_loss += loss.item()
if step % print_steps == 0:
print("Epoch: {}/{}... ".format(e+1, epochs),
"Loss: {:.4f}".format(running_loss/print_steps))
running_loss = 0

print('Finished Training')

3.3 validation data를 통해 정확도 확인하기

validation_correct = 0

validation_total = 0


# no_grad() prevents tracking history (and using memory)

with torch.no_grad():

# Iterate over the entire validation dataset

for input_images, labels in valid_loader:

# Enable CUDA: use GPUs for model computation

if torch.cuda.is_available():

input_images, labels = input_images.to('cuda'), labels.to('cuda')

# Make predictions

outputs = Net(input_images)

_, predicted = torch.max(outputs.data, 1)

# Count total and correct predictions

validation_total += labels.size(0)

validation_correct += (predicted == labels).sum().item()


# Print validation accuracy

print('Validation accuracy ({0:d} validation images): {1:.1%}'

.format(validation_total, validation_correct / validation_total))

3.3 model save

save_path="/home/virgo/ML_TUTORIAL/PyTorch/Classification/GalaxyZoo/vgg/resnet18.pth"

torch.save(Net,save_path)

model=torch.load(save_path)



3.4 결과 확인

softmax : https://pytorch.org/docs/stable/generated/torch.nn.Softmax.html


softmax는 classification 의 결정하는 output value 들의 합이 1이 되게 normalized 해주는 function이다. 따라서 어떤 분류 결과값에 max 값을 가지는 위치로 classification 하게 된다. 이때 softmax 를 사용하게 된다면, 각 분류 목록에 해당되는 classification fraction 을 확인할 수 있다.


np.set_printoptions(precision=2,suppress=True)


images, labels = next(iter(valid_loader))


outputs = Net(images)

_, predicted = torch.max(outputs.data, 1)

m=nn.Softmax(dim=1)

frac=m(outputs)

frac=frac.cpu()

frac=frac.detach().numpy()


print((frac[1]*100))

print(frac[1][1]*100)


print((predicted[0]))


Nr =4

Nc = np.int(images.shape[0]/Nr)

ImSize=3

fig=plt.figure(figsize=(Nc*ImSize,Nr*ImSize),dpi=80)

ax = fig.subplots(Nr,Nc)


plt.subplots_adjust(wspace=0.05, hspace=0.1)


for i in range(Nr):

for j in range(Nc):

data=images[j+Nc*i]

if np.array(labels[j+Nc*i]) == 0:

label='E'

if np.array(labels[j+Nc*i]) == 1:

label='L'

if predicted[j+Nc*i] == 0:

Mlabel='E'

if predicted[j+Nc*i] == 1:

Mlabel='L'

ax[i,j].imshow(np.transpose(vutils.make_grid(data.to(device), padding=2, normalize=True).cpu()))

ax[i,j].text(30,70,label,color='white',fontsize=15)

ax[i,j].text(430,70,Mlabel,color='red',fontsize=15)

ax[i,j].text(30,460,frac[j+Nc*i]*100,color='white',fontsize=15)

ax[i,j].axis('off')