저번의 if __name__ == "__main__": 에 이어, 자주 보이는 다른 구문이 있다.

2024.01.19 - [✍🏻Language & FrameWork/Python] - [Python] if __name__ = "__main__": 이란 ?

 

[Python] if __name__ = "__main__": 이란 ?

Python 소스코드를 보면 module(.py 파일) 내부에 if __name__ == "__main__": 이라는 구문을 많이 볼 수 있다. Python 문법을 알고, 함수도 어느정도 작성할 수 있는 나에게 위의 구문은 보기만해도 낯선 것에

jijibae.tistory.com


class 를 정의하면 바로 첫 method(함수) 가 

    def __init__(self, ~~~) 의 꼴을 띄는 것이다.

 

또 새로운 것에 대한 강한 거부감이 들었지만, 이번에도 하나하나 뜯어보며 이해해보자.


1. Class 란?

"Class 와 Object(객체)" 의 관계는 많은 책에서 "붕어빵 틀과 붕어빵"로 비유한다.

  • Class : 똑같은 무엇인가를 계속 만들어낼 수 있는 설계 도면 같은 것 (붕어빵 틀)
  • Object : Class에 의해서 만들어진 피조물(붕어빵)

Class - Object

# 객체 a 생성
class bread:
    pass
    
a = bread()

 

가끔 객체를 인스턴스로 일컫기도 하는데 이는 객체와 클래스의 관계를 알려주는 것이다.

  • 클래스에 의해서 만들어진 객체를 인스턴스라고도 한다.
  • a를 단독으로 지칭할 때는 'a는 객체'
  • 클래스와 연관지어서 지칭할 때는 'a는 bread의 인스턴스'

2. Class 예시

[Do it] 점프 투 파이썬 에서는 Class를 설명할 때 계산기를 예로 든다.

# class 정의
class FourCal:
    def setdata(self, first, second):
        self.first = first
        self.second = second
        
    def add(self):
        result = self.first + self.second
        return result
        
    def mul(self):
        result = self.first * self.second
        return result
# 예시
a = FourCal()
b = FourCal()

a.setdata(4,2)
b.setdata(3,7)

print(a.add())
print(b.add())

print(a.mul())
print(b.mul())

6

8

10

21

 

정의를 보면 setdata(self, first, second)는 3개의 매개변수를 필요로하는데

실제 setdata에는 2개의 매개변수(first, second) 만을 전달한다.

  • 일반적인 함수와 달리 메서드의 첫번째 매개변수 self는 다른 의미를 가진다.
  • 파이썬 메서드의 첫번째 매개변수 명은 관례적으로 self 라는 이름을 사용하며, 다른 이름을 사용해도 괜찮다
  • 객체와 호출 입력 값들이 메서드에 어떻게 전달되는지에 대한 설명은 아래와 같다.

 

3. __init__(self) 란?

위의 setdata의 정의를 보면 다음과 같다.

def setdata(self, first, second):
    self.first = first
    self.second = second

이 함수의 역할을 보면 객체에 초깃값을 설정한다.

만일 객체에 초깃값을 설정하지 않고, add(), mul() 메서드를 수행하면 오류가 발생한다.

그 이유는 당연한데 해당 객체에 first, second 의 변수 없이 더하기, 곱하기를 수행하는 명령을 주었기 때문이다.

이렇게 메서드를 호출하여 객체의 초깃값을 설정하는 방법도 있지만, 생성자를 구현하는 것이 더 좋은 방법이다.

이때 파이썬 메서드 명을 __init__ 을 사용하면 이 메서드는 생성자가 된다.

def __init__(self, first, second):
    self.first = first
    self.second = second

2) 의 예에서는 setdata(4,2) 와 같이 객체에 초깃값을 설정하기 위해 따로 변수 값을 설정하는 메소드를 호출했다.

반면, __init__ 을 사용하여 생성자를 구현하면 자동으로 객체에 초깃값을 설정해준다. (가장 중요한 부분)

 

 

4. 생성자(Conductor)는 무엇일까?

  • 객체가 생성될 때 '자동으로 호출'되는 메서드를 의미한다.
  • 생성자를 사용하여 객체를 생성할 때에는 선언시 초기값을 함께 전달해야 한다.

 

5. 2)의 코드를 수정

class Fourcal():
    def __init__(self, first, second):
        self.first = first
        self.second = second
        
    def add(self):
        result = self.first + self.second
        return result
        
    def mul(self):
        result = self.first * self.second
        return result
a = FourCal(4, 2)
b = FourCal(3, 7)

# setdata() 메소드 생략

print(a.add())
print(a.mul())
print(b.add())
print(b.mul())

6

8

10

21

 

6. 마치며

마지막으로 __init__ 메서드의 매개변수에 어떠한 값들이 대입되는지 정리하며 마치겠다.

매개변수
self 생성되는 객체
first 4
second 2

 

Python 소스코드를 보면 module(.py 파일) 내부에

if __name__ == "__main__": 이라는 구문을 많이 볼 수 있다.

 

Python 문법을 알고, 함수도 어느정도 작성할 수 있는 나에게 위의 구문은 보기만해도

낯선 것에 의한 강한 거부감이 든다...

 

하지만 프로젝트를 진행하는 과정에서 이 문법을 포함한 몇 가지의 문법들은 몰라서는 소스코드를 한참을 봐도 이해가 안되고,

사용할 때 마다 기술 부채를 느끼기 때문에 이번 기회에 제대로 뜯어보며 정리하려고 한다.

딱 나와 같은 수준을 가진 Python 사용자가 있다면 이 글이 도움이 되었으면 좋겠다.


 

< 가정 >

1. test.py 와 exam.py 이 있음

2. exam.py 에서 test 를 import 한다. (import test)

 

1. __name__ ,  __main__  이란?

__name__ : 모듈의 이름을 담고 있는 파이썬 내장 변수, test.py 라는 파일에서 확장자 .py를 제외한 test가 모듈의 이름

__main__ : 최상위 코드가 실행되는 환경의 이름. 실행을 시작하는 첫 번째 Python 모듈

 

__main__ 의 이해가 어려울 듯 하여 다음의 예시를 참고

 

1-1) 간단한 예

# test.py 에서 다음을 작성 후 test.py 에서 실행
print('__name__ value :', __name__)

__name__ value : __main__     

(import 없이 test.py 에서 실행을 했기 때문에 최상위 코드에서 실행한 것 __name__ = __main__)

# 위의 test.py 수정없이 작성
# exam.py 에서 다음을 작성 후 exam.py 에서 실행
import test
print('__name__ value :', __name__)

__name__ value : test

__name__ value : __main__

(import test 를 했으므로 test.py 에 있는 print() 가 실행되는데 이때 exam.py 입장에서 모듈의 이름은 test 이다.

이후 exam.py 에 있는 print() 가 실행되는데, exam.py 입장에서 모듈의 이름은 __main__ 이다. (최상위 코드 환경, exam 아님 주의

따라서 exam.py 에서 실행을 하면 test.py 모듈을 가져온 것에서 1번 실행, exam.py 에서 1번 실행 하여 총 2회 실행된다.

 

2. 왜 사용하는가?

위의 예에서 exam.py 에서 test를 import 할 때, test.py 에 있는 명령어가 실행되는 것을 보았다.

만일, test.py 에 있는 명령어가 실행되는 것을 원치 않을 경우, if __name__ == "__main__": 을 추가하여 막을 수 있다.

 

 

3. 모든 경우의 예시

필자는 스스로 다음 코드를 돌려보면서 과정을 이해를 했다.

<가정>에서 나올 수 있는 경우는 총 4개이다.

작성의 편의를 위해 if __name__ == "__main__": 를 A 라고 치환하면 아래의 4경우가 존재한다.

(모든 실행은 exam.py 에서 진행된다.)

  1. test.py + exam.py  모두  A 가 없는 경우
  2. test.py 에는 A 가 있지만, exam.py 에는 A 가 없는 경우
  3. test.py 에는 A 가 없고, exam.py 에는 A 가 있는 경우
  4. test.py + exam.py 모두 A 가 있는 경우

3-1) test.py + exam.py  모두  A 가 없는 경우

# test.py 에서 작성
print('__name__ value :', __name__)
# exam.py 에서 작성
import test
print('__name__ value :', __name__)

__name__ value : test

__name__ value : __main__

 

3-2) test.py 에는 A 가 있지만, exam.py 에는 A 가 없는 경우

# test.py 에서 작성
if __name__ == "__main__":
    print('__name__ value :', __name__)
# exam.py 에서 작성
import test
print('__name__ value :', __name__)

__name__ value : __main__   (test 가 모듈이름 이므로 첫 if 문 실행 X)

 

3-3) test.py 에는 A 가 없고, exam.py 에는 A 가 있는 경우

# test.py 에서 작성
print('__name__ value :', __name__)
# exam.py 에서 작성
import test
if __name__ == "__main__":
    print('__name__ value :', __name__)

__name__ value : test

__name__ value : __main__

(3-1 과 동일)

 

3-4) test.py + exam.py 모두 A 가 있는 경우

# test.py 에서 작성
if __name__ == "__main__":
    print('__name__ value :', __name__)
# exam.py 에서 작성
import test
if __name__ == "__main__":
    print('__name__ value :', __name__)

__name__ value : __main__

(3-2 와 동일)

 

 

4. 마치며

3)의 결과로부터

사실상 작성하는 모듈이 import 될 여지가 없다면 if __name__ == "__main__": 구문은 필요없다.

그 이유를 답할 수 있다면 이 process를 완벽하게 숙지 했다고 생각할 수 있다.

이제 우리는 module 단위로 함수를 작성하는 등. 효율적인 Python 프로그래밍을 할 수 있게 되었다. 복잡한 프로젝트에 도전해보자.

잘못된 정보가 있으면 피드백 주세요 감사히 수정하겠습니다.

PyTorch = Numpy + AutoGrad

라는 개인적인 공식에 맞게 Numpy에 대해 알면 PyTorch 를 이해하기 쉽다. 

가장 중요한 AutoGrad와 함께 PyTorch Operations 에 대해 알아보자


1. Tensor

  • 다차원 Arrays 를 표현하는 PyTorch 클래스
  • Numpy의 ndarray와 동일, TensorFlow의 Tensor와도 동일
  • Tensor를 생성하는 함수도 거의 동일
# Numpy - ndarray
import numpy as np
n_array = np.arange(10).reshape(2,5)
print(n_array)
print("ndim :", n_array.ndim, "shape :", n_array.shape)

# PyTorch - tensor
import torch
t_array = torch.FloatTensor(n_array)
print(t_array)
print("ndim :", t_array.ndim, "shape :", t_array.shape)

[[0 1 2 3 4]
 [5 6 7 8 9]]
ndim : 2 shape : (2, 5)
tensor([[0., 1., 2., 3., 4.],
              [5., 6., 7., 8., 9.]])
ndim : 2 shape : torch.Size([2, 5])

 

1-1. numpy like operations

기본적으로 PyTorch의 대부분의 사용법이 그대로 적용됨

data = [[3, 5, 20],[10, 5, 50],[1, 5, 10]]
x_data = torch.tensor(data)

x_data[1:]
# tensor([[10, 5, 50],
#        [ 1, 5, 10]])
          
x_data[:2, 1:]
# tensor([5, 20],
#        [5, 50]])
         
x_data.flatten()
# tensor([3, 5, 20, 10, 5, 50, 1, 5, 10])

torch.ones_like(x_data)
# tensor([[1, 1, 1],
#         [1, 1, 1],
#         [1, 1, 1]])
          
x_data.numpy()
# array([[ 3, 5, 20],
#        [10, 5, 50],
#        [ 1, 5, 10]], dtype=int64)

x_data.shape
# torch.Size([3,3])

x_data.dtype
# torch.int64

 

1-2. Numpy Vs PyTorch

  • PyTorch의 tensor는 GPU에 올려서 사용가능
data = [[3, 5, 20],[10, 5, 50],[1, 5, 10]]
x_data = torch.tensor(data)

x_data.device
# device(type='cpu')

# GPU를 사용할 수 있는지 확인 (M1 MAC 환경)
device = torch.device("mps") if torch.backends.mps.is_available() else "cpu"
print(f"device: {device}")

# GPU 상에서 실행 (M1 MAC 환경)
x_data = torch.tensor(data, device=device)
print(x_data)

cpu
device: mps
tensor([[ 3,  5, 20],
        [10,  5, 50],
        [ 1,  5, 10]], device='mps:0')  <- mps:0이 출력되어야 GPU 사용한 것

2. Tensor handling

  • view, squeeze, unsqueeze 등으로 tensor 조정가능
  • view : reshape과 동일하게 tensor의 shape을 변환
  • squeeze : 차원의 개수가 1인 차원을 삭제 (압축)
  • unsqueeze : 차원의 개수가 1인 차원을 추가

 

2-1. view

tensor_ex = torch.rand(size=(2, 3, 2))
print(tensor_ex)
# tensor([[[0.6388, 0.0342],
#          [0.2073, 0.5346],
#          [0.3281, 0.5916]],

#        [[0.5568, 0.2371],
#         [0.0448, 0.7719],
#         [0.0505, 0.3120]]])

print(tensor_ex.view([-1, 6]))
# tensor([[0.6195, 0.0416, 0.6620, 0.1878, 0.8998, 0.7728],
#        [0.1560, 0.6493, 0.2845, 0.1738, 0.0421, 0.9934]])

print(tensor_ex.reshape([-1, 6]))
# tensor([[0.6195, 0.0416, 0.6620, 0.1878, 0.8998, 0.7728],
#        [0.1560, 0.6493, 0.2845, 0.1738, 0.0421, 0.9934]])

 

 

2-2. squeeze & unsqueeze

tensor_ex = torch.rand(size=(2, 1, 2))
tensor_ex.squeeze()
# tensor([[0.8510, 0.8263],
#         [0.7602, 0.1309]])

tensor_ex = torch.rand(size=(2,2))
tensor_ex.unsqueeze(0).shape
# torch.Size([1, 2, 2])
tensor_ex.unsqueeze(1).shape
# torch.Size([2, 1, 2])
tensor_ex.unsqueeze(2).shape
# torch.Size([2, 2, 1])

 

 

3. Tensor Operation

  • 행렬곱셈 연산은 dot이 아닌 mm 사용, 벡터내적은 dot 가능
  • matmul 은 broadcasting을 지원해준다. -> 헷갈릴 수 있음.  -> mm 사용
n2 = np.arange(10).reshape(5,2)
t2 = torch.FloatTensor(n2)

t1.mm(t2)
# tensor([[ 60.,  70.],
#         [160., 195.]])

t1.dot(t2)
# RuntimeError

t1.matmul(t2)
# tensor([[ 60.,  70.],
#         [160., 195.]])

 

 

4. nn.functional Module

  • nn.functional 모듈을 통해 softmax, one_hot, cartesian_prod 등 많은 기능 제공
# softmax 와 one_hot 예
import torch
import torch.nn.functional as F

tensor = torch.FloatTensor([0.5, 0.7, 0.1])
h_tensor = F.softmax(tensor, dim=0)  # dim 은 axis 와 같다.
h_tensor
# tensor([0.3468, 0.4224, 0.2318])

y = torch.randint(5, (10,5))
y_label = y.argmax(dim=1) 

print(torch.nn.funtional.one_hot(y_label))  # one_hot 적용

tensor([[0, 1, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [1, 0, 0, 0, 0],
        [1, 0, 0, 0, 0],
        [1, 0, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0],
        [1, 0, 0, 0, 0],
        [1, 0, 0, 0, 0]])

 

# cartesian_prod 예
import itertools
a = [1, 2, 3]
b = [4, 5]
list(itertools.product(a, b)) # 모든 경우의 수를 구해줌

tensor_a = torch.tensor(a)
tensor_b = torch.tensor(b)
print(torch.cartesian_prod(tensor_a, tensor_b)) # 모든 경우의 수를 구해줌

tensor([[1, 4],
        [1, 5],
        [2, 4],
        [2, 5],
        [3, 4],
        [3, 5]])

 

5. AutoGrad

  • PyTorch의 핵심은 자동 미분의 지원 -> backward 함수 사용
  • 미분의 대상이 되는 것을 requires_grad=Ture 로 한다. (대부분 weight 값)
# Ex1
w = torch.tensor(2.0, requires_grad=True)  # w = 2 이고, w에 대해 미분가능
y = w**2
z = 10*y + 25
z.backward()  # .backward() 가 미분 명령어
print(w.grad)

tensor(40.)

 

# Ex2
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
Q = 3*a**3 - b**2
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)

print(a.grad)
print(b.grad)

tensor([36., 81.])
tensor([-12.,  -8.])

'✍🏻Language & FrameWork > Pytorch' 카테고리의 다른 글

[PyTorch] 프레임워크 소개  (0) 2024.01.10

PyTorch, TensorFlow 등 프레임워크의 도움으로 더 이상 밑바닥부터 딥러닝을 구현하지 않아도 된다.

현재는 반복적인 작업을 누군가 미리 만들어 놓은 프레임워크를 사용하기 때문이다.

그럼, 프레임워크가 무엇인지. 딥러닝 프레임워크들에는 무엇이 있는지 더 알아보자.


1. 프레임워크란?

 프로그램을 다룸에 있어 공통적으로 사용되는 기능들을 표준화된 소스코드로 만들어 놓고 사용할 수 있도록 제공하는 것

 

2. 딥러닝 프레임워크의 종류와 특징 

  • 대표적으로 Google에서 개발된 TensorFlowMeta(구 Facebook)에서 개발된 PyTorch 가 있다.
  • 두 프레임워크의 가장 큰 차이점은 Define and Run 과 Define by Run 에 있다.
  • Keras는 TensorFlow 2.0이 나오면서 TensorFlow와 합쳐졌다.

2020.12.09 기준

 

3. Define and Run vs Define by Run

Computational Graph(연산의 과정을 그래프로 표현한 것)를 언제 생성하느냐에 따라 구분한 것

  • Define and Run : 그래프를 먼저 정의 -> 실행시점에 데이터 feed
  • Define by Run : 실행을 하면서 그래프를 생성하는 방식(Dynamic Computational Graph, DCG)

Computational Graph

 

 

4. Why PyTorch ?

  • Define by Run 의 장점 -> 즉시 확인 가능 -> pythonic code
  • GPU support (Multi-GPU)
  • 자동미분을 지원하여 DL 연산을 지원

DL 프레임워크로 PyTorch가 적절한 것 같아

앞으로 PyTorch를 좀 더 공부해볼 계획이다.

'✍🏻Language & FrameWork > Pytorch' 카테고리의 다른 글

[PyTorch] PyTorch Operations  (2) 2024.01.10