페이지

2022년 8월 26일 금요일

19.2 ndarray 인스턴스 변수

 Variable은 데이터를 담는 '상자'역할을 합니다. 그러나 사용하는 사람 입장에서 중요한 것은 상자가 아니라 그 안의 '데이터'입니다. 그래서 Variable이 데이터인 것처럼 보이게 하는 장치, 즉 상자를 투명하게 해주는 장치를 만들겠습니다. 

1단계에서 언급했듯이 수치 계산과 머신러닝 시스템은 다차원 배열(텐서)을 기본 데이터 구조로 사용합니다. 따라서 Variable클래스는 (스칼라는 무시하고)  ndarray만을 취급하기로 했습니다. 그래서 이번 절의 목표는 Variable 인스턴스를 ndarray 인스턴스처럼 보이게 하는 것입니다.


Variable 안에 ndarray 인스턴스가 있습니다. 넘파이의 ndarray 인스턴스에는 다차원 배열용 인스턴스 변수가 몇 가지 제공됩니다. 다음은 그중 하나인 shape 인스턴스 변수를 사용하는 모습입니다.

import numpy as np
x = np.array([[123], [456]])
x.shape

인스턴스 변수 shape는 다차원 배열의 형상을 알려줍니다. 참고로 앞의 결과에서 (2, 3)은 수학에서 말하는 2 * 3 행렬을 뜻합니다. 이제 똑같은 작업을 Variable 인스턴스에서도 할 수 있도록 확장하겠습니다.

class Variable:
  def __init__(selfdataname=None):
    if data is not None:
      if not isinstance(data, np.ndarray):
        raise TypeError('{} is not supported'format(type(data)))
    
    self.data = data
    self.name = name
    self.grad = None
    self.creator = None
    self.generation = 0
    
  def set_creator(selffunc):
    self.creator = func
    self.generation = func.generation + 1     


  def backward(selfretain_grad=False):
    if self.grad is None:
      self.grad = np.ones_like(self.data)


    funcs = []
    seen_set = set()

    def add_func(f):
      if f not in seen_set:
        funcs.append(f)
        seen_set.add(f)
        funcs.sort(key=lambda x: x.generation)
    
    add_func(self.creator)

    while funcs:
      f = funcs.pop()   

      # 수정전 gys = [output.grad for output in f.outputs]  
      gys = [output().grad for output in f.outputs]  #
      gxs = f.backward(*gys)   
      if not isinstance(gxs, tuple):  
        gxs = (gxs,)
      
      for x, gx in zip(f.inputs, gxs):  
        if x.grad is None:
          x.grad = gx
        else:
          x.grad = x.grad + gx

        if x.creator is not None:
          add_func(x.creator)
      
      if not retain_grad:
        for y in f.outputs:
          y().grad = None   # y는 약한 참조(weakref)

  def cleargrad(self):
    self.grad = None

  
  @property
  def shape(self):
    return self.data.shape

shape라는 메서드를 추가한 후 실제 데이터의 shape를 반환하도록 했습니다. 여기서 중요한 부분은 def shape(self): dkvdp cnrkehls @property라는 한줄입니다. 이 한 줄 덕분에 shape 메서드를 인스턴스 변수처럼 사용할 수 있게 됩니다. 확인해 보겠습니다.

x = Variable(np.array([[123], [456]]))
print(x.shape)    # x.shape() 대신 x.shape로 호출할 수 있다.
(2, 3)

이와 같이 메서드 호출이 아닌 인스턴스 변수로 데이터의 형상을 얻을 수 있습니다. 같은 방법으로 ndarray의 다른 인스턴스 변수들을 Variable에 추가할 수 있습니다. 여기에서는 다음 세 인스턴스 변수를 더 추가하겠습니다.


class Variable:
  def __init__(selfdataname=None):
    if data is not None:
      if not isinstance(data, np.ndarray):
        raise TypeError('{} is not supported'format(type(data)))
    
    self.data = data
    self.name = name
    self.grad = None
    self.creator = None
    self.generation = 0
    
  def set_creator(selffunc):
    self.creator = func
    self.generation = func.generation + 1     


  def backward(selfretain_grad=False):
    if self.grad is None:
      self.grad = np.ones_like(self.data)


    funcs = []
    seen_set = set()

    def add_func(f):
      if f not in seen_set:
        funcs.append(f)
        seen_set.add(f)
        funcs.sort(key=lambda x: x.generation)
    
    add_func(self.creator)

    while funcs:
      f = funcs.pop()   

      # 수정전 gys = [output.grad for output in f.outputs]  
      gys = [output().grad for output in f.outputs]  #
      gxs = f.backward(*gys)   
      if not isinstance(gxs, tuple):  
        gxs = (gxs,)
      
      for x, gx in zip(f.inputs, gxs):  
        if x.grad is None:
          x.grad = gx
        else:
          x.grad = x.grad + gx

        if x.creator is not None:
          add_func(x.creator)
      
      if not retain_grad:
        for y in f.outputs:
          y().grad = None   # y는 약한 참조(weakref)

  def cleargrad(self):
    self.grad = None

  
  @property
  def shape(self):
    return self.data.shape

  @property
  def ndim(self):
    return self.data.ndim
  
  @property
  def size(self):
    return self.data.size

  @property
  def dtype(self):
    return self.data.dtype


보다시피 ndim, size, dtype이라는 3개의 인스턴스 변수를 추가했습니다. ndim은 차원 수, size는 원소 수, dtype은 데이터 타입을 나타냅니다. 이상으로 Variable에 필요한 인스턴스 변수를 모두 추가했습니다. 이외에도 ndarray에는 많은 인스턴스 변수가 존재하며, 그 모두를 추가할 수도 있습니다. 하지만 단순한 작업이라서 지면으로 설명하지 않겠습니다. 더 필요한 독자는 직접 추가햅조기 바랍니다.


이 책은 지금까지 ndarray 인스턴스의 데이터 타입의 dtype은 특별히 의식하지 않고 이야기를 진행했습니다. dtype을 지정하지 않으면 ndarray인스턴스는 (환경에 따라) float64 또는 int64로 초기화됩니다. 한편 신경망에서는 float32를 사용하는 경우가 많습니다.

댓글 없음: