인공지능/머신러닝

[PyTorch]PyTorch 기초

우향우@ 2023. 8. 6. 04:29

개요

PyTorch는 파이썬의 머신러닝 라이브러리이다. 내부적으로 GPU 사용이 가능하기에 속도가 빠르다.PyTorch는 다음의 핵심적인 요소들로 구성된다.

tensor : numpy의 ndarray같은 n차원 데이터

module : 인공지능 모델의 한 계층(한 개 이상의 레이어가 겹쳐진)을 나타내는 존재, module 하나를 곧 모델로 볼 수도 있으며 module을 모델 속 한 레이어로 쓸 수도 있다. 즉 모듈은 계층적으로 쌓일 수 있다. 

loss function : 모델로 계산한 결과와 실제 정답 라벨을 입력 시 loss를 리턴하는 함수

optimizer: 모델에서 학습시켜나가야하는 파라미터를 관리하며 편미분값등을 참조하여 어떻게 학습시킬지를 정의하고 실제로 학습시키는 역할을 하는 객체

 

Device

PyTorch에서는 연산에 사용할 하드웨어를 나타내는 객체인 Device 객체가 존재한다.

torch.device("cpu")를 통해 cpu 디바이스 객체를 얻을 수 있으며 

torch.device("cuda")를 통해 그래픽 카드 디바이스 객체를 얻을 수 있다.

torch.cuda.is_available()을 통해 현재 환경에서 cuda가 사용가능한지를 bool값으로 받을 수 있다. 이를 통해 cpu와 cuda중 적절한 device를 만들어 사용하면 된다.

이렇게 생성된 device객체는 이후 tensor를 생성할때나 연산등을 호출할 때 매개변수로 같이 넘겨주어 해당 연산에 사용할 device를 지정하는데 사용할 수 있다.

 

Tensor

텐서는 PyTorch에서 사용되는 numpy의 ndarray와 유사한 데이터이다. shape나 연산등이 넘파이와 거의 유사하게 수행된다.

넘파이와 다른 점으로는 우선 생성 시 매개변수에 device = device객체를 넘겨줌으로써 해당 객체 연산에 사용할 device를 설정할 수 있다는 점이있다.

다음으로는 이것이 텐서의 가장큰 특징인데 require_grad=true로 하여 텐서를 생성하면 해당 텐서에 대한 연산은 미분 추적이 되게 된다. PyTorch는 이를 기반으로 자동 미분 기능을 제공한다.

 

Autograd

PyTorch는 오차역전파를 기반으로한 자동 미분기능을 제공한다. 이러한 자동 미분기능은 위에서 말한 require_grad=true인 텐서들을 대상으로 진행된다. 이러한 자동 미분기능을 사용하는 예시를 보자

x= torch.tensor([1,2,3] , require_grad=true)

y = (x * 3).sum()

y.backward()

print(x.grad)

위 코드를 수행하면 x의 각 파라미터들(1,2,3)의 y에 대한 편미분 값들이 x.grad에 배열로 들어가게 된다. 이에 대한 정확한 의미는 다음과 같다. require_grad=true인 텐서들이 각종 텐서연산을 진행하면 그 결과들은 단순 계산 결과뿐 아니라 오차역전파를 위한 미분 데이터도 쌓아간다. 그렇게 최종적으로 스칼라에 해당하는 결과가 나왔을때 해당 데이터에 .backward()를 호출하면 그 데이터에 쌓인 require_grad=true인 텐서들이 가진 각 값들의 해당 최종 데이터에 대한 편미분 값(연산할때의 값 기준에서)이 각 텐서들의 .grad에 저장되게 된다. 더 많은 연산을 하는 예시를 보자.

x1= torch.tensor([1,2,3] , require_grad=true)

y = x1 * 3

x2 = torch.tensor([3,2,1] , require_grad=true)

z = (y * x2).sum()

z = z ** 3

z.backward()

print(x1.grad)
print(x2.grad)

이렇게 코드를 짜주면 최종값인 z에 대한 x1과 x2 각 파라미터들의 편미분 값이 x1.grad, x2.grad에 들어가게된다.

 

이를 통해 신경망 학습시 각 파라미터들의 오차함수에 대한 편미분값을 구하고 스텝을 밟을 수 있게 된다. 구체적인 방법은 다음과 같다.

1. 각 레이어의 파라미터에 해당하는 텐서들을 require_grad=true로 만든다.

2. 학습용 입력 데이터를 입력하여 각 레이어의 파라미터 텐서들과 연산하여 최종 결과를 낸다.

3. 최종 결과를 정답 라벨과 오차함수로 연산하여 오차값을 계산한다.

4. 오차값에 대해 backward를 하면 오차함수에 대한 현재 파라미터 값들의 편미분값들이 계산된다.

5. 각 편미분 값들에 leaningRate를 곱하여 각자 빼준다.

이렇게 하면 기본적인 학습이 가능하다. 하지만 실제로 모델을 만들때는 직접 이 로직을 만들진 않고 제공되는 lossFuction들과 optimizer 객체를 통해 편하게 기능을 구현할 수 있다.

 

추가로 backward함수는 기본적으론 스칼라 결과에 쓸 수 있지만 다음과 같이 사용하면 고차원 데이터에서도 backward가 가능하다. 바로 인자로 해당 고차원 데이터의 각 파라미터의 최종 결과 스칼라에 대한 편미분값을 같은 shape의 데이터로 넣어주며 backward를 호출하면 그 값을 그대로 받아 오차역전파하여 앞의 require_grad 텐서들의 편미분 값들을 계산해주게 된다.

 

마지막으로 require_grad=true인 텐서를 포함한 연산의 결과에 의해 나온 텐서는 마찬가지로 require_grad=true의 값을 가지지만 중간결과로써의 의미를 가지기에 이후 backward가 호출되어도 해당 텐서는 .grad에 값이 생기지 않는다. 우리가 직접  require_grad=true로써 생성한 텐서들에만 편미분값이 갱신되게된다.

 

DataLoader

PyTorch는 데이터셋을 효과적으로 관리해주는 여러 기능을 제공해주는데 그중하나가 DataLoader객체이다. 이 객체는 입력, 라벨 쌍 데이터를 반복자를 통해 편하게 관리할 수 있게 해준다. 사용 예시를 보자.

inp = torch.tensor([입력1,입력2,입력3...])
label = toruch.tensor([라벨1,라벨2,라벨3...])

dataset = TensorDataSet(inp,label)

dataLoader = DataLoader(dataset, batch_size = 32, Shuffle = True)


for data in dataLoader :
	nowInp, nowLabel = data # 32개 Inp데이터, 32개 Label 데이터

# 또는

it = iter(dataLoader)
it.next()

 이런식으로 입력, 라벨 데이터를 batch_size씩 가져오는 반복자로써 사용이 가능하다.

 

module

PyTorch에서 module은 인공지능 모델의 한 계층을 나타낸다. 보통 더 이상 나눌수없는 가장 작은 계층을 레이어라하고 한개 이상의 레이어로 구성된 것을 모듈이라한다. 일단 편의상 모두 모듈로 통일해서 이야기 하겠다.

PyTorch의 module은 한 객체로 표현되며 대표적으로 다음 동작들을 지원한다.

md가 한 모듈 객체라하자.

md(inp) => 해당 모듈에 inp를 입력한 결과를 리턴

md.parameters() => 해당 모듈에 속한 파라미터들을 리턴

그외에도 모듈과 관련된 다양한 기능을 지원한다.

 

모듈은 자신의 연산에 사용되는 파라미터들을 내부적으로 가지고 있으며 기본적으로 이들은 require_grad=true이기에 해당 모듈에 입력을 통해 연산을 수행하면 그 결과는 모듈의 파라미터들에 대한 미분 추적이 이루어진다. 즉 이후 결과에서 backward를 하면 해당 모듈의 파라미터들의 grad가 계산된다.

 

PyTorch에서 기본적으로 제공되는 모듈(레이어)에는 다음과 같은 것들이 있다.

torch.nn.Linear(입력개수, 출력개수,바이어스여부bool ...) : full connected 레이어이다.

torch.nn.conv2d(입력채널수,출력채널수(커널개수), 커널 shape, stride shape, padding ...) : 컨볼루션 레이어이다.

torch.nn.MaxPool2d(커널 shape...) : 맥스 풀링 레이어이다.

torch.nn.softmax : 소프트 맥스 활성화함수이다. 활성화 함수이기에 파라미터가 없다.

그외에도 ReLu등의 다양한 기본 레이어들이 존재한다.

 

추가적으로 require_grad가 true인 텐서는 바로 넘파이로 변환할 수 없다. tensor객체.detach()를 하면 전부 같지만 require_grad만 다른 텐서가 나오므로 이를 통해 변환하고 .numpy()를 통해 변환하면 정상적으로 변환된다. 보통 모듈을 거친 결과는 해당 모듈이 파라미터가 있다면 require_grad=true이므로 detach를 통해 변환후 넘파이 변환이 가능하고 파라미터가 없는 모듈은 그대로 변환이 가능하다.

 

module 클래스

PyTorch에서 nn.module 클래스를 상속받으면 우리가 직접 module을 만들 수 있다. 이후 init에서 부모의 init를 호출해주고 자신의 멤버 변수들에 각종 모듈들을 만들어 넣어준다. 마지막으로 forward(inp)를 정의하여 입력에 대한 모듈의 출력을 리턴해주면 모듈이 완성된다. 이렇게 만들어진 모듈은 모듈의 모든 기능을 사용할 수 있다.

 

Sequantial

nn.sequantial(모듈, 모듈, 모듈...) 함수를 사용하면 인자로 넣은 모듈들을 합쳐 하나의 모듈로 만들어 리턴해준다. 이를 이용하면 여러 모듈을 하나로 묶을수 있을뿐 아니라 하나의 모델(모듈)을 클래스 상속없이 이 함수만으로 만들 수 있기에 굉장히 편리하다.

 

Loss function

PyTorch에서는 모델 출력값과 정답 라벨을 넘겨주면 오차를 계산해주는 각종 오차함수들을 제공해준다. 다음과 같은 것들이 있다.

torch.nn.CrossEntropyLoss()

torch.nn.BCELoss()

해당 함수를 호출하면 함수 객체를 리턴해주므로 그 객체를 받아서 모델 출력값과 정답 라벨을 넘겨주면 오차값을 계산하여 오차 객체에 담아 넘겨준다. 이러한 오차 객체에 backward를 호출하면 연산에 사용된 파라미터들의 편미분을 계산하여 학습에 사용할 수 있다.

 

Optimizer

Optimizer는 모델에서 학습시켜나가야하는 파라미터를 관리하며 편미분값등을 참조하여 어떻게 학습시킬지를 정의하고 실제로 학습시키는 역할을 하는 객체이다. 대표적으로 다음과 같은 것들이 있다.

torch.optim.SGD

torch.optim.AdaDelta

기본적으로 학습 방법에 따라 옵티마이저 객체가 나뉘며 객체를 생성할때 넣는 인자로 세부적인 하이퍼 파라미터 조정이 가능하다. SGD,즉 Stochastic Gradient Descent 객체의 사용 예시는 다음과 같다.

여기서 md는 기존에 만들어져있는 모듈이다.

opsgd = torch.optim.SGD(md.parameters(),lr=0.001)

...

#학습 한 스텝 inp는 학습용 batch input, label은 학습용 batch label,losscrs는 기존에 만들어둔 오차함수
opsgd.zero_grad()

out = md(inp)
loss = losscrs(out,label)

loss.backward()

opsgd.step()

맨 처음 모델(모듈)의 파라미터를 넘겨주며 leaning rate가 0.001로 설정된 옵티마이저를 만들었다.

이후 모델에 대한 학습을 한 스텝 진행할 때 다음을 수행한다.

1. 옵티마이저의 zero_grad()로 관리중인 파라미터들의 편미분값을 0으로 초기화

2. 모델에서 학습용 forward 

3. 그 결과와 실제 label를 오차 함수에 계산

4. 그 결과인 오차에 대해 backward를 하여 모델의 각 파라미터의 오차함수에 대한 편미분값을 갱신

5. 그 후 옵티 마이저에서 step()을 호출하면 lr= 0.001로 각 파라미터의 편미분값만큼 SGD를 실행하여 파라미터들을 갱신

이를 통해 파라미터들에 대한 학습이 한 스텝 진행된다.

 

간단한 모델 만들기

지금까지의 내용을 토대로 PyTorch에서 간단한 모델을 만드는 방법은 다음과 같이 정리할 수 있다.

 

1. PyTorch의 각종 기본 레이어로 nn.module 자식 또는 sequantial로 모델을 만든다.

2. 모델에 사용할 loss function과 optimizer를 만든다.

3. 학습에 사용할 dataLoader를 만든다.

4. epoch만큼 for data in dataLoader를 다음 학습을 반복하여 모델을 학습한다.

 

loop1. 옵티마이저로 편미분 초기화

loop2. 현재 학습 데이터 입력값 모델에 forward

loop3. 결과값과 label를 오차함수에 넣어 오차 계산

loop4. 오차에 backward 수행하여 각 파라미터의 오차함수에 대한 편미분 계산

loop5. 옵티마이저의 step()으로 파라미터 갱신 

 

5. 루프가 끝나고 나면 모델 완성

6. 모델에 테스트하고싶은 데이터를 forward하면 결과가 나온다.