머신러닝

ANN의 이해

content0474 2024. 10. 17. 10:01

import 및 데이터셋 불러오기

 

인공신경망을 정의

class. SimpleANN:

더보기

SimpleANN이라는 클래스에서 신경망 모델을 정의하고 있다.

 

class.SimpleANN(nn.Module)

더보기

이 클래스는 nn.Module이라는 부모클래스를 상속받고 있다.

nn.module이란? pytorch에서 모델을 만들 때 쓰는 기본 클래스로, 신경망 정의에 필요한 각종 기능들(파라미터 관리, 순전파, 역전파에서 그래디언트 계산 등)을 제공해줌

def__init__(self): 클래스의 생성자

더보기

super(SimpleANN, self).__init__()

super() 를 사용해서 부모클래스를 호출하고,

호출한 부모클래스(=  nnModule )의 __init__매서드를 호출해 부모클래스를 초기화한다.

부모클래스를 초기화해야 nn.Module의 여러 기능들이 제대로 동작한다.

 

self.fc1=nn.Linear(28*28.128)

더보기

우리가 가져온 MNIST데이터는 28*28크기의 2D 이미지이다. 이 이미지를 1차원 벡터로 만들고 (28*28=784), 이 1차원 벡터를 받아 128개의 노드로 변환함

1차원으로 만드는 이유? 아직 이 단계의 신경망은 선형계층으로 이미지를 처리하고 있다. 나중에 활성화함수를 통해 비선형으로 바꿔준다.

self.fc2=nn.Linear(128,64):

self.fc3=nn.Linear(64,10):

더보기

128개로 받은 것을 64개로, 64개로 받은 것을 10개로 변환해서 최종 출력노드는 10개

 

def forward(self.x):

더보기

이제 SimpleANN 클래스 내의 forward라는 함수를 정의할건데,

 

x=x.view(-1.28*28)

더보기

28*28의 2D 이미지를 1차원 벡터로 펼쳐주는 과정

view()함수는 텐서를 다른 모양으로 재구성하는 함수 즉 텐서의 차원 변경

 

view함수의 사용예시

더보기

x=torch.randn(2,3,4)

re_x=x.view(2,12)

-> 3차원 텐서(2*3 * 4)를 2차원 텐서(2 * 12)로 변경

 

x=torch.randn(64,1,28,28)

re_x=x.view(-1,28*28)

-> 배치 크기 64, 채녈1, 28*28의 2차원 이미지를 1차원 벡터(28*28=784)로 변경하는데, 배치크기는 64로 유지

 

x=torch.randn(4.6)

re_x=x.view(-1,3)

-> 2차원 텐서(4 * 6)를 2차원 텐서(8 * 3)로 변경

-1은 자동으로 크기를 맞춘다는 뜻이므로, 4*6=24에 맞게 차원을 자동으로 결정함

 

 

참고) 텐서란?

더보기

데이터를 0차원~n차원까지 다차원 배열 형태로 표현하는 객체

0차원 텐서=스칼라=숫자 하나 ex) 9

1차원 텐서=벡터=숫자들의 나열 ex)[1,2,3]

2차원 텐서=행렬=숫자의 2차원 배열 ex)[[1,2,3],[4,5,6]]

3차원 텐서= 3차원 배열 ex) [[[1,2],[3,4]], [[5,6],[7,8]]]

이미지 데이터는 3차원 텐서로 표현된다. 예를들어 MNIST는 28*28의 2D이미지이므로 (28,28,1)로 표현된다.

 

x=torch.relu(self.fc1(x))

x=torch.relu(self.fc2(x))

더보기

첫 번째 선형계층에 데이터를 통과시킨 뒤 (=self.fc1(x))

이 결과를 다시 relu함수에 넣음 -> 선형이 비선형이 되었다.

두번째 선형계층에도 마찬가지로 relu함수 적용

 

self.fc3(x):

더보기

마지막 선형계층은 relu함수나 다른 활성화함수를 적용하지 않는다.

이 값을 로짓이라고 한다. 로짓은 확률로 변환되지 않은 원시적인 출력값으로 음수나 매우 큰 값을 가질 수 있다.

나중에 여기에 소프트맥스 함수를 적용해 로짓을 확률로 변환해준다.

변환된 확률은 해당클래스에 속활 확률이 된다.

ex)

로짓: [2.3, -1.5, 0.7] -> 소프트맥스 적용 후 : [0.7, 0.05, 0.25]

이는 첫번째 클래스에 속할 확률이 70%, 두 번째 클래스에 속할 확률은 5%, 세번째 클래스에 속할 확률은 25%라는 뜻


학습

model = SimpleANN()

더보기

앞에서 정의한 SimpleANN이라는 클래스에 따라 모델을 생성

 

criterion = nn.CrossEntropyLoss()

더보기

(nn=torch.nn 맨 위에 import를 보면 torch.nn을 nn으로 불러왔다. torch.nn안에는 여러 클래스와 함수들이 있고, nn.module, nn.CrossEntropyLoss도 모두 torch.nn모듈 안에 있는 클래스이다.)

CrossEntropyLoss는 예측값과 실제값(=라벨)의 차이를 계산해서(=이 차이를 계산하는 것이 바로 손실함수) 모델이 얼마나 잘못 예측했는지 알려준다. 


optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

더보기

SGD는 경사하강법의 일종

 

경사하강법이란?

특정 함수가 줄어드는 방향으로 변수값을 업데이트하는 방법
그러니까 경사하강법을 사용하면 loss function(손실함수)이 줄어드는 방향으로 가중치가 업데이트된다. 

 

Ir은 학습률, 모멘텀은 관성이다. 모멘텀으로 학습속도를 높이고 지역최적값에 갇히지 않게 할 수 있다.

 

for epoch in range(10):

더보기

학습데이터 전체로 한 번 학습하는 과정을 1에포크라고 한다. 이 경우 10번의 학습을 실행한다.(10 에포크)

 

    running_loss = 0.0

더보기

학습과정중 미니배치마다 누적되는 손실값을 저장하는 변수를 running_loss라고 했다. 나중에 여기다가 계산된 손실값을 계속 더해줄 예정이다.


    for i, data in enumerate(trainloader, 0):
        inputs, labels = data

더보기

trianloader는 갑자기 어디서 나온 것일까?

사실 데이터셋 로드할 때 trainloader가 뭔지 정의했다.

trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

 

이 정의에 따르면 trainset는 MNIST데이터셋을 가져와서 transform을 적용해 텐서로 변환한 것이다.

trainloader는 trainset을 64개씩 한 번에 가져오는데, 무작위로 섞어서 가져온다. 즉 미니배치의 사이즈=64

 

enumerate 는 파이썬 내장함수로, 반복문에서 인덱스와 데이터를 동시에 가져온다.

그러니까 enumerate(trainloader,0)은 trainloader에서 데이터를 하나씩 가져오면서 그 데이터에 0부터 시작하는 인덱스(여기서는 i)도 붙이는 것이다. 이렇게 해야 반복문 내에서 몇 번째 인지를 알 수 있다.

data는 (inputs, labels)라는 하나의 배치 데이터를 의미한다. trainloader로부터 입력데이터(input)과 그에 해당하는 (label)을 가져온다.

optimizer.zero_grad()

더보기

기울기를 초기화하는 함수

기울기란? 손실함수가 가중치에 따라 얼마나 변하는지를 계산한 것으로 모델의 출력이 입력에 얼마나 민감하게 반응하는지를 나타냄

학습과정에서 기울기가 계속 누적되지 않게 초기화해준다.

loss.backward()

더보기

손실값에 대한 각 파라미터의 기울기를 계산한다. 이 기울기를 이용해 가중치를 업데이트한다.

 

optimizer.step()

더보기

가중치 업데이트 함수

 

if i % 100 == 99: 
    print(f'[Epoch {epoch + 1}, Batch {i + 1}] loss: {running_loss / 100:.3f}')
    running_loss = 0.0

더보기

i%100==99 : i를 100으로 나눴을 때 나머지가 99인 경우 즉, i==99,199,299.. 

i=0에서 시작했으므로 100번쨔 ,200번째, 300번째를 의미한다.

running_loss는 위에서도 설명했듯 미니배치마다 손실을 더한 값이다. 100번째 배치에서 평균값을 계산한 뒤 다시 running_loss를 0.0으로 초기화시키고 있다.

참고로 .3f는 반올림해서 소수점 3자리까지 출력한다는 뜻이다. ex)3.141592.. ->3.142


정확도 측정

 

correct = 0
total = 0

더보기

correct는 맞춘 예측 개수를 저장할 변수이고, total은 전체 테스트 샘플의 개수를 저장하는 변수이다.

 

with torch.no_grad():

더보기

기울기 계산 비활성화 함수

기울기는 학습할 때 사용되는 것이지 평가할때는 계산할 필요가 없다. 그래서 비활성화 해둬야 메모리도 세이브하고 속도도 빨라진다.

 

for data in testloader:
    images, labels = data
    outputs = model(images)

더보기

testloader도 데이터셋 불러올 때 정의했다. trainset과 분리된 testset으로부터 가져온 데이터이다.

마찬가지로 여기서도 데이터를 가져와서 images와 labels를 얻고

model에 images를 넣으면 model이 해당 이미지에 대한 예측값을 반환한다.(output)

model이 뭐냐면 model=SimpleANN() 에 있는 그 model이다. 위에서 열심히 학습해서 똑똑해진 model

 

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

 

더보기

torch.max는 다음 두 가지 값을 반환한다.

(최댓값, 최댓값이 위치한 인덱스)

 

예를 들어 outputs=model(images)에서 모델이 images를 보고 다음과 같은 outputs를 냈다.

[0.1, 0.05, 0.02, 0.7, 0.05, 0.03, 0.01, 0.02, 0.01, 0.01]

이는 이 이미지가 클래스0에 속할 확률이 10%, 클래스1에 속할 확률이 5%... 클래스3에 속할 확률이 70%... 클래스9에 속할 확률이 1%라는 뜻이다. 즉 이 이미지는 클래스3에 속한다고 판단한 것

torch.max는 이것을 보고 (0.7, 3) 을 반환한다.

이 때 우리는 0.7(70%)이라는 확률 자체는 필요없고 3에 속한다는 정보만 필요하다. 그래서 _,predicted로 받아서 앞에 0.7은 무시하고 3만 받는 것이다. 즉 이 경우 predicted에 3이 저장됨

'_'는 파이썬에서 변수명을 무시하고 싶을 때 쓰는 관용적 표현이다.


_,predictd= torch.max(output.data)까지는 알겠는데, 1은 뭘까?

MNIST 에서 모델의 출력은 2차원 텐서구조를 갖는데, 0차원은 배치(batch), 1차원은 클래스 이렇게 총 2차원이다.

outputs = [ [0.1, 0.3, 0.6],  [0.2, 0.5, 0.3] ]

이렇게 생긴 output이라고 하면 0차원은  첫번째 이미지에 대한 예측인 [0.1, 0.3, 0.6], 두번째 이미지에 대한 예측인  [0.2, 0.5, 0.3] 이 두 개이다. 

1차원은 각 이미지 내에서 클래스에 속할 확률이다. 즉 0.1, 0.3, 0.6이 1차원이다.

그러니까 torch.max는 이 1차원 내에서 가장 높은 것을 찾아야 어느 클래스에 속할지 알게 되는 것이다.

이게 바로 torch.max(output.data, 1) 의 의미

 

total += labels.size(0)

더보기

labels에는 이미지가 어디에 속하는지에 대한 정답이 있다. 이미지와 정답은 일대일로 대응하므로, 이 정답의 개수는 이미지의 개수와 동일하다.

size()는 텐서의 크기를 반환한다. size(0)은 1차원 텐서의 크기이다.

 

예시

labels = torch.tensor([3, 1, 4, 0, 2]) 
print(labels.size())     # 출력: torch.Size([5])
print(labels.size(0))    # 출력: 5

이미지가 5개 있고, 각각 3번, 1번, 4번, 0번, 2번 클래스에 속한다는 정답이 있다.

이 라벨의 크기는 5이고, 이를 통해 5개의 이미지가 있음을 추정할 수 있다.

 

labels = torch.tensor([[3, 1], [4, 0], [2, 5]])  
print(labels.size())     # 출력: torch.Size([3, 2])
print(labels.size(0))    # 출력: 3 (첫 번째 차원의 크기)
print(labels.size(1))    # 출력: 2 (두 번째 차원의 크기)

MNIST데이터와는 다르지만, 예를 들어 3개의 이미지가 있고 각 이미지에 두 개의 라벨이 있다고 해보자

size(0)을 하면 첫번째 차원(=배치의 크기)인 3이 나온다. size(1)을 하면 두번째 차원인 라벨의 개수가 나온다.

 

즉 total += labels.size(0) 이란,

labels.size(0)= 이미지의 개수 이므로, 처리한 이미지를 다 더해서 이미지의 총 개수를 알려주는 코드이다.


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

더보기

예측한값(predicted)과 실제 정답(labels)이 일치하는 것을 합하는데 ->sum()

이렇게 합해진 것은 텐서의 형태로 나온다. 이 때 안에 있는 숫자 하나만 뽑아 파이썬 기본 자료형으로 변환해주는게 바로 item()이다.  즉 이 코드는 맞게 예측한 개수를 다 합해주는 코드

참고로 딕셔너리에서 키-벨류 쌍을 얻는 메서드는 items()였다. 

 

print(f'Accuracy of the network on the 10000 test images: {100 * correct / total:.2f}%')

더보기

(맞춘이미지개수(correct)/전체이미지개수(total)) *100  이것을 소숫점 둘째자리까지 나타내고 뒤에 % 붙여서 출력

ex 10000개 중 9500개 맞았으면 9500/10000 *100 -> 정확도: 95%

 

'머신러닝' 카테고리의 다른 글

RNN의 이해  (0) 2024.10.21
CNN의 이해  (0) 2024.10.18
간단한 수학 통계 지식(1)  (5) 2024.10.16
k-means clustering  (0) 2024.10.15
선형회귀, 다항회귀, 로지스틱 회귀  (0) 2024.10.14