본문 바로가기
Study 공부할레나

[스터디/밑시딥3] 제 4고지 신경망 만들기_44. 매개변수를 모아두는 계층

by LENA-cfg 2023. 10. 10.

44.1 Parameter 클래스 구현

class Parameter(Variable):
    pass

Variable 클래스를 상속하기만 했습니다.

44.2 Layer 클래스 구현

Layer 클래스는 매개변수를 유지하고 매개변수를 사용하여 변환을 하는 클래스입니다.

from dezero.core import Parameter

class Layer:
    def __init__(self):
        self._params = set() # 매개변수 보관

    def __setattr__(self, name, value):
        if isinstance(value, (Parameter, Layer)):
            self._params.add(name)
        super().__setattr__(name, value)

__setattr__은 인스턴스 변수를 설정할 때 호출되는 특수 메서드입니다.

__setattr__(self, name, value)는 이름이 name인 인스턴스 변수에 값으로 value로 전달해줍니다.

 

이렇게 구현해서 어떻게 쓰냐면 아래 코드처럼 씁니다.

layer = Layer()

layer.p1 = Parameter(np.array(1))
layer.p2 = Parameter(np.array(2))
layer.p3 = Parameter(np.array(3))
layer.p4 = 'test'

print(layer._params)

for name in layer._prams:
    print(name, layer.__dict__[name])

Layer 클래스에 call, forward, parmas, cleargrads 메서드를 추가합니다.

class Layer:


    def __call__(self, *inputs):
        outputs = self.forward(*inputs)
        if not isinstance(outputs, tuple):
            outputs = (outputs,)
        self.inputs = [weakref.ref(x) for x in inputs]
        self.outputs = [weakref.ref(y) for y in outputs]
        return outputs if len(outputs) > 1 else outputs[0]

    def forward(self, inputs):
        raise NotImplementedError()

    def params(self):
        for name in self._params:
            obj = self.__dict__[name]

            if isinstance(obj, Layer):
                yield from obj.params()
            else:
                yield obj

    def cleargrads(self):
        for param in self.params():
            param.cleargrad()
def __call__(self, *inputs):
    outputs = self.forward(*inputs)
    if not isinstance(outputs, tuple):
        outputs = (outputs,)
    self.inputs = [weakref.ref(x) for x in inputs]
    self.outputs = [weakref.ref(y) for y in outputs]
    return outputs if len(outputs) > 1 else outputs[0]

call 메서드는  forward 메서드를 호출해 입력받은 인수를 넣어 아웃풋을 뽑아주고 출력이 하나면 출력을 직접 반환합니다. 그리고 입력과 출력 변수를 약한 참조로 유지하고 있습니다.

def params(self):
    for name in self._params:
        obj = self.__dict__[name]

        if isinstance(obj, Layer):
            yield from obj.params()
        else:
            yield obj

파람 메서드는 layer 인스턴스에 담겨있는 Parameter 인스턴스들을 꺼내줍니다.

def cleargrads(self):
    for param in self.params():
        param.cleargrad()

cleargrads 메서드는 Layer 인스턴스에 담겨 있는 파라미터 인스턴스들을 꺼내주고 모든 매개변수 기울기를 재설정합니다.

44.3 Linear 클래스 구현

class Linear(Layer):
    def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None):
        super().__init__()
        self.in_size = in_size
        self.out_size = out_size
        self.dtype = dtype

        self.W = Parameter(None, name='W')
        if self.in_size is not None:
            self._init_W()

        if nobias:
            self.b = None
        else:
            self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b')

    def _init_W(self, xp=np):
        I, O = self.in_size, self.out_size
        W_data = xp.random.randn(I, O).astype(self.dtype) * np.sqrt(1 / I)
        self.W.data = W_data

    def forward(self, x):
        if self.W.data is None:
            self.in_size = x.shape[1]
            xp = cuda.get_array_module(x)
            self._init_W(xp)

        y = F.linear(x, self.W, self.b)
        return y
class Linear(Layer):

Linear 클래스는 Layer 클래스를 상속합니다.

def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None):

입력 크기, 출력 크기, 편향 사용 여부 플래그를 인수로 받습니다.

in_size가 None으로 지정되어 있고 None이면 self.W.data 초기화를 연기합니다.

forward(self, x) 메서드에서 입력 x의 크기에 맞게 가중치 데이터를 생성합니다.

self.W = Parameter(None, name='W')
if self.in_size is not None:
    self._init_W()

if nobias:
    self.b = None
else:
    self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b')

가중치와 편향은 Parameter 인스턴스를 인스턴스 변수에 설정합니다.

44.4 Layer를 이용한 신경망 구현

if '__file__' in globals():
    import os, sys
    sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import numpy as np
import dezero.functions as F
import dezero.layers as L # L로 임포트


np.random.seed(0)
x = np.random.rand(100, 1)
y = np.sin(2 * np.pi * x) + np.random.rand(100, 1)

l1 = L.Linear(10) # 출력 크기 지정
l2 = L.Linear(1)


def predict(x):
    y = l1(x)
    y = F.sigmoid(y)
    y = l2(y)
    return y


lr = 0.2
iters = 10000

for i in range(iters):
    y_pred = predict(x)
    loss = F.mean_squared_error(y, y_pred)

    l1.cleargrads()
    l2.cleargrads()
    loss.backward()

    for l in [l1, l2]:
        for p in l.params():
            p.data -= lr * p.grad.data
    if i % 1000 == 0:
        print(loss)