Deep Learning

[Deep Learning] 딥러닝 클래스 코드 구현(연산, layer, neuralnetwork, 배치학습, optimizer)

heedy 2022. 11. 23. 16:22
728x90
import numpy as np
from numpy import ndarray
from typing import *
def assert_same_shape(array: ndarray,
                      array_grad: ndarray):
  assert array.shape == array_grad.shape, \
  f"""
  두 ndarray의 모양이 같아야 하는데,
  첫 번째 ndarray의 모양은 {tuple(array_grad.shape)}이고,
  두 번째 ndarray의 모양은 {typle(array.shape)}이다.
  """
  return None

- 신경망 구성 요소: 연산

Operation 클래스

class Operation(object):
  """
  신경망 모델의 연산 역할을 하는 기반 클래스
  """
  def __init__(self):
    pass

  def forward(self, input_: ndarray):
    """
    인스턴스 변수 self._input에 입력값을 저장한 다음 self._output() 함수를 호출한다.
    """
    self.input_ = input_
    self.output = self._output()
    return self.output

  def backward(self, output_grad: ndarray) -> ndarray:
    """
    self._input_grad() 함수를 호출한다. 이때 모양이 일치하는지 먼저 확인한다.
    """
    assert_same_shape(self.output, output_grad)
    self.input_grad = self._input_grad(output_grad)

    assert_same_shape(self.input_, self.input_grad)
    return self.input_grad

  def _output(self) -> ndarray:
    """
    Operation을 구현한 모든 구상 클래서는 _output 메서드를 구현해야 한다.
    """
    raise NotImplementedError()

  def _input_grad(self, output_grad: ndarray) -> ndarray:
    """
    Operation을 구현한 모든 구상 클래스는 _input_grad 메서드를 구현해야 한다.
    """
    raise NotImplementedError()

ParamOperation 클래스

class ParamOperation(Operation):
  """
  파라미터를 갖는 연산
  """

  def __init__(self, param: ndarray) -> ndarray:
    """
    생성자 메서트
    """
    super().__init__()
    self.param = param

  def backward(self, output_grad: ndarray) -> ndarray:
    """
    self._input_grad, self._param_grad를 호출한다.
    이때 ndarray 객체의 모양이 일치하는지 확인한다.
    """
    
    assert_same_shape(self.output, output_grad)

    self.input_grad = self._input_grad(output_grad)
    self.param_grad = self._param_grad(output_grad)

    assert_same_shape(self.input_, self.input_grad)
    assert_same_shape(self.param, self.param_grad)

    return self.input_grad

  def _param_grad(self, output_grad: ndarray) -> ndarray:
    """
    ParamOperation을 구현한 모든 구상 클래스는 _param_grad 메서드를 구현해야 한다.
    """
    raise NotImplementedError()

- 신경망의 구성 요소: 층

Operaion의 구상 클래스


WeightMulitiply 클래스( 파라미터 행렬과 입력 행렬의 행렬곱 연산)

class WeightMultiply(ParamOperation):
  """
  신경망의 가중치 행렬곱 연산
  """

  def __init__(self, W: ndarray):
    """
    self.param = W로 초기화
    """
    super().__init__(W)

  def _output(self) -> ndarray:
    """
    출력값 계산
    """
    return np.dot(self.input_, self.param)

  def _input_grad(self, output_grad: ndarray) -> ndarray:
    """
    입력에 대한 기울기 계산
    """
    return np.dot(output_grad, np.transpose(self.param, (1, 0)))

  def _param_grad(self, output_grad: ndarray) -> ndarray:
    """
    파라미터에 대한 기울기 계산
    """
    return np.dot(np.transpose(self.input_, (1, 0)), output_grad)

BiasAdd 클래스(편향 향을 더하는 덧셈 연산)

class BiasAdd(ParamOperation):
  """
  편향을 더하는 연산
  """

  def __init__(self, B: ndarray):
    """
    self.param = B로 초기화한다.
    초기화 전에 행렬의 모양을 확인한다.
    """
    assert B.shape[0] == 1

    super().__init__(B)

  def _output(self) -> ndarray:
    """
    출력값 계산
    """
    return self.input_ + self.param

  def _input_grad(self, output_grad: ndarray) -> ndarray:
    """
    입력에 대한 기울기 계산
    """
    return np.ones_like(self.input_) * output_grad

  def _param_grad(self, output_grad: ndarray) -> ndarray:
    """
    파라미터에 대한 기울기 계산
    """
    param_grad = np.ones_like(self.param) * output_grad
    return np.sum(param_grad, axis = 0).reshape(1, param_grad.shape[1])

Sigmoid 클래스(sigmoid 활성화 함수)

class Sigmoid(Operation):
  """
  Sigmoid 활성화 함수
  """

  def __init__(self) -> None:
    """ Pass"""
    super().__init__()

  def _output(self) -> ndarray:
    """
    출력값 계산
    """
    return 1.0 / (1.0 + np.exp(-1.0 * self.input_))

  def _input_grad(self, output_grad: ndarray) -> ndarray:
    """
    입력에 대한 기울기 계산
    """
    sigmoid_backward = self.output * (1.0 - self.output)
    input_grad = sigmoid_backward * output_grad
    return input_grad

Linear 클래스

class Linear(Operation):
  """
  항등 활성화 함수
  """
  def __init__(self) -> None:
    '기반 클래스의 생성자 메서드 실행'
    super().__init__()

  def _output(self) -> ndarray:
    '입력을 그대로 출력'
    return self.input_
  
  def _input_grad(self, output_grad: ndarray) -> ndarray:
    '그대로 출력'
    return output_grad

- Layer 클래스 설계

  • forward와 backward, 이 2개의 메서드는 층에 포함된 일련의 Operation 클래스 객체에 입력값을 순서대로 통과시키는 역할을 한다. 이 과정은 지금까지 우리가 다이어그램을 그려보며 해온 것과 완전히 같다. 이 부분이 실질적으로 Layer 객체가 하는 일이고, 나머지 코드는 코드를 감싸는 래퍼(wrapper) 역할이나 정보를 저장하는 역할을 맡는다.
    • _setup_layer 메서드를 통해 정확한 순서대로 Operation 클래스 객체의 연속을 정의하고 각 Operation 객체의 파라미터를 저장해서 초기화한다.
    • forward 메서드는 self.input_과 self.output에 각각 값을 저장한다.
    • backward 메더스는 역방향 계산을 수행하기 전, 행렬의 모양을 먼저 검사한다.
  • 마지막으로 _param._param_grads 메서드는 층에 포함된 ParamOperation 클래스 객체에서 파라미터와 파라미터에 대한 기울기의 값을 꺼낸다.
class Layer(object):
  """
  신경망 모델의 층 역할을 하는 클래스
  """

  def __init__(self, neurons: int):
    """
    뉴런의 개수는 층의 너비에 해당한다.
    """
    self.neurons = neurons
    self.first = True
    self.params: List[ndarray] = []
    self.param_grads: List[ndarray] = []
    self.operations: List[Operation] = []

  def _setup_layer(self, num_in: int) -> None:
    """
    Layer를 구현하는 구상 클래스는 _setup_layer 메서드를 구현해야 한다.
    """
    raise NotImplementedError()

  def forward(self, input_: ndarray) -> ndarray:
    """
    입력값을 각 연산에 순서대로 통과시켜 순방향 계산을 수행한다.
    """
    if self.first:
      self._setup_layer(input_)
      self.first = False

    self.input_ = input_

    for operation in self.operations:
      input_ = operation.forward(input_)

    self.output = input_

    return self.output

  def backward(self, output_grad: ndarray) -> ndarray:
    """
    output_grad를 각 연산에 역순으로 통과시켜 역방향 계산을 수행한다.
    계산하기 전, 행렬의 모양을 검사한다.
    """

    assert_same_shape(self.output, output_grad)

    for operation in reversed(self.operations):
      output_grad = operation.backward(output_grad)

    input_grad = output_grad

    self._param_grads()

    return input_grad

  def _param_grads(self) -> ndarray:
    """
    각 Operation 객체에서 _param_grad 값을 꺼낸다.
    """

    self.param_grads = []
    for operation in self.operations:
      if issubclass(operation.__class__, ParamOperation):      # 상속관계여부 확인 issubclass(자식 클래스, 부모 클래스)
        self.param_grads.append(operation.param_grad)

  def _params(self) -> ndarray:
    """
    각 Operation 객체에서 _params값을 꺼낸다.
    """

    self.params = []
    for operation in self.operations:
      if issubclass(operation.__class__, ParamOperation):
        self.params.append((operation.param))

밀집층 구현하기

class Dense(Layer):
  """
  Layer 클래스를 구현한 전결합층
  """
  def __init__(self,
               neurons: int,
               activation: Operation = Sigmoid()) -> None:
    """
    초기화 시 활성화 함수를 결정해야 함
    """
    super().__init__(neurons)
    self.activation = activation

  def _setup_layer(self, input_: ndarray) -> None:
    """
    전결합층의 연산을 정의
    """
    if self.seed:
      np.random.seed(self.seed)
    
    self.params = []

    # 가중치
    self.params.append(np.random.randn(input_.shape[1], self.neurons))

    # 편향
    self.params.append(np.random.randn(1, self.neurons))

    self.operations = [WeightMultiply(self.params[0]),
                       BiasAdd(self.params[1]),
                       self.activation]

    return None

- NeuralNetwork 클래스와 그 외 클래스

  • 데이터로부터 학습하는 클래스
  1. 신경망은 X를 입력받아 각 Layer(여러개의 Operation을 묶은 편리한 래퍼)를 순서대로 통과시킨다. 신경망 끝에서 출력된 값이 예측값 prediction이 된다.
  2. prediction을 y와 비교하여 손실을 계산한 다음, 이 손실로 마지막 층(prediction을 출력한 층)의 각 요소에 대한 '손실의 기울기'를 계산한다.
  3. 마지막으로 손실의 기울기를 각 층에 역순으로 전달한다. 그 과정에서 각 Operation 객체마다 '파라미터에 대한 손실의 기울기'를 계산하고 해당 객체에 그 값을 저장한다.

Loss 클래스

class Loss(object):
  """
  신경망 모델의 손실을 게산하는 클래스
  """

  def __init__(self):
    """Pass"""
    pass

  def forward(self, prediction: ndarray, target: ndarray) -> float:
    """
    실제 손실값을 계산함
    """
    assert_same_shape(prediction, target)

    self.prediction = prediction
    self.target = target

    loss_value = self._output()

    return loss_value

  def backward(self) -> ndarray:
    """
    손실함수의 입력값에 대해 손실의 기울기를 계산함
    """
    self.input_grad = self._input_grad()

    assert_same_shape(self.prediction, self.input_grad)

    return self.input_grad

  def _output(self) -> float:
    """
    Loss 클래스를 확장한 모든 구상 클래스는 이 메서드를 구현해야 함
    """
    raise NotImplementedError()

  def _input_grad(self) -> ndarray:
    """
    Loss 클래스를 확장한 모든 구상 클래스는 이 메서드를 구현해야 함
    """
    raise NotImplementedError()

MeanSquaredError 클래스

class MeanSquaredError(Loss):

  def __init__(self):
    """Pass"""
    super().__init__()

  def _output(self) -> float:
    """
    관찰 단위로 오차를 집계한 평균제곱오차 손실함수
    """
    loss = np.sum(np.power(self.prediction - self.target, 2)) / self.prediction.shape[0]

    return loss

  def _input_grad(self) -> ndarray:
    """
    예측값에 대한 평균제곱오차 손실의 기울기를 계싼
    """
    
    return 2.0 * (self.prediction - self.target) / self.prediction.shape[0]

딥러닝 구현하기

  1. NeuralNetwork 클래스 객체는 Layer들의 리스트를 속성으로 가지고 있어야 한다. Layer들의 객체는 앞서 정의한 대로 forward와 backward 메서드를 갖추고 있으며 이들 메서드는 ndarray 객체를 인자로 받고 반환한다.
  2. 각각의 Layer 클래스 객체는 Operation 객체의 리스트를 operations 속성에 담고 있으며 _setup_layer 메서드에 이 리스트가 저장된다.
  3. Operation 클래스 객체 역시 Layer와 마찬가지로 forward와 backward, 이 2개의 메서드를 갖추고 있으며 이 메서드 역시 ndarray 객체를 인자로 받고 반환한다.
  4. 각 Operation 객체의 backward 메서드에서 인자로 받은 output_grad는 Layer의 output 속성과 모양이 같아야 한다. input_grad와 input_ 속성 역시 모양이 같아야 한다.
  5. 연산 중에 파라미터(param 속성에 저장된다)가 있는 연산이 있다. 이 연산은 ParamOpertaion 클래스를 확장해 정의한다. 또한 Layer의 forward 와 backward 메서드에서 인자로 받은 ndarray의 모양에 대한 제약(input과 output이 각각 그에 해당하는 기울기와 모양이 일치해야 함)이 여기서도 마찬가지로 적용된다.
  6. NeuralNetwork 클래스는 Loss 클래스로 정의되는 손실함수를 가져야 한다. 이 클래스는 NeuralNetwork 클래스 객체의 마지막 연산의 출력값과 목푯값을 인자로 받아 이들의 모양이 같은지 검사한다음, 이들로부터 손실(단일한 숫자값)과 손실의 기울기(ndarray 객체)를 계산한다. 이 값이 다시 출력층으로 되돌아가면서 역방향 계산이 시작된다.
 

배치 학습 구현하기

  1. 데이터를 모델에입력해 함수를 통과시켜(순방향 계산) 예측값을 구한다.
  2. 손실값을 계산한다.
  3. 연쇄법칙과 순방향 계산 과정에서 계산된 값을 이용해 파라미터에 대한 손실값의 기울기를 계산한다.
  4. 기울기를 이용해 파라미터를 수정한다.

그리고 새로운 데이터 배치를 가져와 위 과정을 반복한다.


위의 절차를 그대로 코드로 옮겨 NeuralNetwork 프레임워크에 이 절차를 도입한다.

  1. X와 y를 입력받는다. 이 두 값은 모두 ndarray 객체다.
  2. X를 각 Layer에 순서대로 통과시키며 순방향 계산을 수행한다.
  3. Loss 클래스에서 손실값과 손실값의 기울기를 계산한다.
  4. backward 메서드에서 손실값의 기울기를 전달받아 역방향 계산을 수행한다. 이 과정에서 각 층마다 param_grads를 계산한다.
  5. 각 층마다 update_params 메서드를 호출해서 NeuralNetwork에 정의된 전체 학습률에 맞춰 param_grads의 방향으로 파라미터를 수정한다.

NeuralNetwork 클래스

class NeuralNetwork(object):
  """
  신경망을 나타내는 클래스
  """
  def __init__(self, layers: List[Layer],
               loss: Loss,
               seed: float = 1):
    """
    신경망의 층과 손실함수를 정의
    """
    self.layers = layers
    self.loss = loss
    self.seed = seed
    if seed:
      for layer in self.layers:
        setattr(layer, 'seed', self.seed)    # object에 존재하는 속성의 값을 바꾸거나, 새로운 속성을 생성하여 값을 부여

  def forward(self, x_batch: ndarray) -> ndarray:
    """
    데이터를 각 층에 순서대로 통과시킴(순방향 계산)
    """
    x_out = x_batch
    for layer in self.layers:
      x_out = layer.forward(x_out)

    return x_out

  def backward(self, loss_grad: ndarray) -> None:
    """
    데이터를 각 층에 역순으로 통과시킴(역방향 계산)
    """

    grad = loss_grad
    for layer in reversed(self.layers):
      grad = layer.backward(grad)

    return None

  def train_batch(self,
                  x_batch: ndarray,
                  y_batch: ndarray) -> float:
    """
    순방향 계산 수행
    손실값 계산
    역방향 계산 수행
    """

    predictions = self.forward(x_batch)
    
    loss = self.loss.forward(predictions, y_batch)

    self.backward(self.loss.backward())

    return loss

  def params(self):
    """
    신경망의 파라미터값을 받음
    """
    for layer in self.layers:
      yield from layer.params     # 값 함수 밖으로 하나씩 전달

  def param_grads(self):
    """
    신경망의 각 파라미터에 대한 손실값의 기울기를 받음
    """
    for layer in self.layers:
      yield from layer.param_grads

Optimizer와 Trainer 클래스

Optimizer 클래스
class Optimizer(object):
  """
  신경망을 최적화하는 기능을 제공하는 추상 클래스
  """
  def __init__(self, 
               lr: float = 0.01):
    """
    초기 학습률이 반드시 설정되어야 한다.
    """
    self.lr = lr

  def step(self) -> None:
    """
    Optimizer를 구현하는 구상 클래스는 이 메서드를 구현해야 한다.
    """
    pass

SGD 클래스

class SGD(Optimizer):
  """
  확률적 경사 하강법을 적용한 Optimizer
  """
  def __init__(self,
               lr: float = 0.01) -> None:
    """ Pass """
    super().__init__()

  def step(self):
    """
    각 파라미터에 학습률을 곱해 기울기 방향으로 수정함
    """
    for (param, param_grad) in zip(self.net.params(),
                                   self.net.param_grads()):
      param -= self.lr * param_grad

Trainer 클래스

from copy import deepcopy

class Trainer(object):
  """
  신경망 모델을 학습시키는 역할을 수행함
  """

  def __init__(self,
               net: NeuralNetwork,
               optim: Optimizer) -> None:
    """
    학습을 수행하려면 NeuralNetwork, Optimizer 객체가 필요함
    Optimizer 객체의 인스턴스 변수로 NeuralNetwork 객체를 전달할 것
    """
    self.net = net
    self.optim = optim
    self.best_loss = 1e9
    setattr(self.optim, 'net', self.net)

  def generate_batches(self,
                       X: ndarray,
                       y: ndarray,
                       size: int = 32) -> Tuple[ndarray]:
    """
    배치 생성
    """
    assert X.shape[0] == y.shape[0], \
    f"""
    특징과 목표값은 행의 수가 같아야 하는,
    특징은 {X.shape[0]}행, 목표값은 {y.shape[0]}행이다.
    """
    N = X.shape[0]

    for ii in range(0, N, size):
      x_batch, y_batch = X[ii : ii * size], y[ii : ii * size]

      yield x_batch, y_batch

  def fit(self, X_train: ndarray, y_train: ndarray,
          X_test: ndarray, y_test: ndarray,
          epochs: int = 100,
          eval_every: int = 10,
          batch_size: int = 32,
          seed: int = 1,
          restart: bool = True) -> None:
    """
    일정 횟수의 에폭을 수행하여 학습 데이터에 신경망을 최적화함
    eval_every 변수에 설정된 횟수의 매 에폭마다 테스트 데이터로
    신경망의 에측 성능을 측정함
    """

    np.random.seed(seed)

    if restart:
      for layer in self.net.layers:
        layer.first = True

      self.best_loss = 1e9

    for e in range(epochs):

      if (e + 1) % eval_every == 0:
        # 조기종료
        last_model = deepcopy(self.net)

      X_train, y_train = permute_data(X_train, y_train)
      batch_generator = self.generate_batches(X_train, y_train, batch_size)

      for ii, (X_batch, y_batch) in enumerate(batch_generator):
        self.net.train_batch(X_batch, y_batch)
        self.optim.step()

      if (e + 1) % eval_every == 0:
        test_preds = self.net.forward(X_test)
        loss = self.net.loss.forward(test_preds, y_test)

        if loss < self.best_loss:
          print(f'{e+1} 에폭에서 검증 데이터에 대한 손실값: {loss:.3f}')
          self.best_loss = loss

        else:
          print(f'''{e+1} 에폭에서 손실 값이 증가했다. 
          마지막으로 측정한 손실값은 {e+1-eval_every} 에폭까지 학습된 모델에서 계산된 {self.best_loss:.3f}이다.''')
          self.net = last_model
          # self.optim이 self.net을 수정하도록 다시 설정
          setattr(self.optim, 'net', self.net)
          break

평가기준

def mae(y_true: ndarray, y_pred: ndarray):
  """
  신경망 모델의 평균절대오차 계산
  """
  return np.mean(np.abs(y_true - y_pred))

def rmse(y_true: ndarray, y_pred: ndarray):
  """
  신경망 모델의 제곱근 평균제곱오차 계산
  """
  return np.sqrt(np.mean(np.power(y_true - y_pred, 2)))

def eval_regression_model(model: NeuralNetwork,
                          X_test: ndarray,
                          y_test: ndarray):
  """
  신경망 모델의 평균절대오차 및 제곱근 평균제곱오차 계산
  Compute mae and rmse for a neural network.
  """
  preds = model.forward(X_test)
  preds = preds.reshape(-1, 1)
  print(f'평균절대오치: {mae(preds, y_test):.2f}')
  print()
  print(f'제곱근 평균제곱오차: {rmse(preds, y_test):.2f}')
lr = NeuralNetwork(
    layers=[Dense(neurons=1,
                   activation=Linear())],
    loss=MeanSquaredError(),
    seed=20190501
)

nn = NeuralNetwork(
    layers=[Dense(neurons=13,
                   activation=Sigmoid()),
            Dense(neurons=1,
                   activation=Linear())],
    loss=MeanSquaredError(),
    seed=20190501
)

dl = NeuralNetwork(
    layers=[Dense(neurons=13,
                   activation=Sigmoid()),
            Dense(neurons=13,
                   activation=Sigmoid()),
            Dense(neurons=1,
                   activation=Linear())],
    loss=MeanSquaredError(),
    seed=20190501
)

데이터 로드, 테스트 / 학습 데이터 분할

from sklearn.datasets import load_boston

boston = load_boston()
data = boston.data
target = boston.target
features = boston.feature_names
# 데이터 축척 변환

from sklearn.preprocessing import StandardScaler
s = StandardScaler()
data = s.fit_transform(data)

텐서 변환

def to_2d_np(a: ndarray, 
          type: str="col") -> ndarray:
    '''
    1차원 텐서를 2차원으로 변환
    '''

    assert a.ndim == 1, "입력된 텐서는 1차원이어야 함"
    
    if type == "col":        
        return a.reshape(-1, 1)
    elif type == "row":
        return a.reshape(1, -1)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size = 0.3, random_state = 80718)

# 목표값을 2차원 배열로 변환
y_train, y_test = to_2d_np(y_train), to_2d_np(y_test)

3가지 모델 학습

# 헬퍼 함수

def permute_data(X, y):
  perm = np.random.permutation(X.shape[0])
  return X[perm], y[perm]

1) Dense 1개 - neurons 1개 - Linear activation 

trainer = Trainer(lr, SGD(lr = 0.01))

trainer.fit(X_train, y_train, X_test, y_test,
            epochs = 50,
            eval_every = 10,
            seed = 20190501)

print()
eval_regression_model(lr, X_test, y_test)
10 에폭에서 검증 데이터에 대한 손실값: 32.721
20 에폭에서 검증 데이터에 대한 손실값: 28.873
30 에폭에서 검증 데이터에 대한 손실값: 26.134
40 에폭에서 검증 데이터에 대한 손실값: 25.631
50 에폭에서 검증 데이터에 대한 손실값: 25.155

평균절대오치: 3.53

제곱근 평균제곱오차: 5.02

2) Dense 2개 - neurons 13개, 1개 - Sigmoid, Linear activation 

trainer = Trainer(nn, SGD(lr = 0.01))

trainer.fit(X_train, y_train, X_test, y_test,
            epochs = 50,
            eval_every = 10,
            seed = 20190501)

print()
eval_regression_model(nn, X_test, y_test)
10 에폭에서 검증 데이터에 대한 손실값: 29.225
20 에폭에서 검증 데이터에 대한 손실값: 22.751
30 에폭에서 검증 데이터에 대한 손실값: 19.623
40 에폭에서 검증 데이터에 대한 손실값: 17.861
50 에폭에서 검증 데이터에 대한 손실값: 16.730

평균절대오치: 2.65

제곱근 평균제곱오차: 4.09

3) Dense 3개 - neurons 13개, 13개, 1개 - Sigmoid, Sigmoid, Linear activation 

trainer = Trainer(dl, SGD(lr=0.01))

trainer.fit(X_train, y_train, X_test, y_test,
       epochs = 50,
       eval_every = 10,
       seed=20190501);
print()
eval_regression_model(dl, X_test, y_test)
10 에폭에서 검증 데이터에 대한 손실값: 48.183
20 에폭에서 검증 데이터에 대한 손실값: 26.772
30 에폭에서 검증 데이터에 대한 손실값: 22.721
40 에폭에서 검증 데이터에 대한 손실값: 17.378
50 에폭에서 검증 데이터에 대한 손실값: 15.922

평균절대오치: 2.61

제곱근 평균제곱오차: 3.99

 

- 출처: 처음 시작하는 딥러닝
- notebook ipynb file: https://github.com/heejvely/Deep_learning/blob/main/%EC%B2%98%EC%9D%8C_%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94_%EB%94%A5%EB%9F%AC%EB%8B%9D/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%B6%80%ED%84%B0%20%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EB%8A%94%20%EB%94%A5%EB%9F%AC%EB%8B%9D.ipynb

 

GitHub - heejvely/Deep_learning: deep learning 기초 공부

deep learning 기초 공부. Contribute to heejvely/Deep_learning development by creating an account on GitHub.

github.com

 

728x90