페이지

2022년 8월 26일 금요일

19.3 len 함수와 print함수

 이어서 Variable 클래스를 더 확장하여 파이썬의 len함수와도 함께 사용할 수 있도록 하겠습니다. len은 객체 수를 알려주는 파이썬의 표준 함수입니다. 다음과 같이 사용할 수 있지요.

x = [ 1234]
len(x)
4

x = np.array([1234])
len(x)
4

x = np.array([[123], [456]])
len(x)
2

이와 같이 리스트 등에 len함수를 사용하면 그 안에 포함된 원소 수를 반환합니다. ndarray인스턴스라면 첫 번째 차원의 원소 수를 반환합니다. 이제 이 len 함수가 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


  def __len__(self):
    return len(self.data)

이와 같이 __len__이라는 특수 메서드를 구현하면 Variable 인스턴스에 대해서도 len 함수를 사용할 수 있게 됩니다. 이제 다음과 같은 코드를 작성할 수 있습니다.


파이썬에서 __init__ 와 __len__등 특별한 의미를 지닌 메서드는 밑줄 두개로 감싼 이름을 사용합니다.

x = Variable(np.array([[123], [456]]))
print(len(x))
2

마지막으로 Variable의 내용을 쉽게 확인할 수 있는 기능을 추가합니다. 바로 print 함수를 사용하여 Variable의 안의 데이터 내용을 출력하는 기능입니다. 즉, 다음 예처럼 사용하고자 합니다.

x = Variable(np.array([123]))
print(x)
variable([1 2 3])

x = Variable(None)
print(x)
variable(None)

x = Variable(np.array([[123], [456]]))
print(x)
variable([[1 2 3] [4 5 6]])

이와 같이 Variable 인스턴스를 print 함수에 건네면 안에 담긴 ndarray인스턴스의 내용을 출력하도록 하겠습니다. 이때 출력 결과는 variable(...) 형태로 통일하여 사용자에게 Variable 인스턴스임을 알려줍니다. 값이 None이거나 내용을 여러 줄로 출력해야 하는 경우도 지원합니다. 여러 줄일때는 공백 문자로 시작 위치를 조정하여 보기 좋게 출력합니다. 다음은 이상의 조건을 만족하는 Variable의 __repr__메서드 모습입니다.

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


  def __len__(self):
    return len(self.data)

  def __repr__(self):
    if self.data is None:
      return 'variable(None)'
    
    p = str(self.data).replace('\n''\n' + ' ' * 9)
    return 'variable('+ p + ')'

이처럼 print 한수가 출력해주는 문자열을 입맛에 맞게 정의하려면 __repr__메서드를 재정의 하면 됩니다. 반환값을 출력하고자 하는 문자열입니다. 앞의 코드에서는 str(self.data)를 이용하여 ndarray 인스턴스를 문자열로 변환했습니다. str 할수 안에서는 ndarray 인스턴스의 __str__ 함수가 호출되고 숫자가 문자열로 반환됩니다. 줄바꿈(\n)이 있으면 줄을 바꾼 후 새로운 줄 앞에 공백9개를 삽입하여 여러 줄에 걸친 출력도 숫자의 시작 위치가 가지런하게 표시되게 했습니다. 마지막으로 변환된 문자열을 'variable(...)' 현태로 감쌉니다.

이상으로 Variable 클래스를 '투명한 상자'로 만드는 작업을 일부 끝마쳤습니다. 다음 단계에서도 이 작업을 계속 이어갈 것입니다.


댓글 없음: