과제2. Fully-Connected-Nets
다층 신경망 로직 구현
1. introduce
심층 신경망을 구체적으로 구현하는 과정을 걸치게 됩니다.

2. init
파라미터는 weight, bias 등 네트워크에 기본이 되는 값들을 설정해야 한다.

hidden_dim_layer = copy.deepcopy(hidden_dims)
hidden_dim_layer.insert(0, input_dim)
hidden_dim_layer.append(num_classes)
for index in range(1,len(hidden_dim_layer)):
self.params[f"W{index}"] = np.random.randn(hidden_dim_layer[index -1], hidden_dim_layer[index]) * weight_scale
self.params[f"b{index}"] = np.zeros((1, hidden_dim_layer[index]))
input layer 기준으로 한다면, input 기준으로 한다면, bias 설정에서 for 문안에서 if 작성으로 분기를 나눠야한다.

3. train
cache_list = []
input_list = [0, X]
for index in range(1, self.num_layers +1):
keys = [f'W{index}', f'b{index}', f'gamma{index}', f'beta{index}']
w, b, gamma, beta = (self.params.get(k, None) for k in keys) # get param vals
bn = self.bn_params[index -1] if gamma is not None else None
do = self.dropout_param if self.use_dropout else None
if index == self.num_layers:
fc, cache = affine_forward(input_list[index], self.params[f'W{index}'], self.params[f'b{index}'])
else:
fc, cache = affine_normally_forward(input_list[index], w, b, gamma, beta, bn,
bn_type=self.normalization, do=do)
input_list.append(fc)
cache_list.append(cache)

4. loss
loss, dz = softmax_loss(scores, y)
weight = 0
for index in range(self.num_layers, 0, -1):
weight = (self.params[f'W{index}'] * self.params[f'W{index}']).sum()
do = self.dropout_param if self.use_dropout else None
if index == self.num_layers:
dz, grads[f'W{index}'], grads[f'b{index}'] = affine_backward(dz, cache_list[index -1])
else:
dz, grads[f'W{index}'], grads[f'b{index}'], gamma, beta = affine_batchnoral_relu_backward(dz, cache_list[index - 1], bn_type=self.normalization)
if gamma is not None and beta is not None :
grads[f'gamma{index}'], grads[f'beta{index}']= gamma, beta
grads[f'W{index}'] += self.reg * self.params[f'W{index}']
loss += 0.5 * self.reg * weight
5. optimizer
5.1 SGD

5.1.1 SGD 문제점
- saddle point & Local minima을 유발시킨다.
- Saddle point는 기울기가 0인 지점에서는 더 이상 업데이트를 실행하지 않습니다.
- Local minima 지역적인 최솟값을 Grobal 극소로 착각할 수 있다.
5.1.2 code
def sgd(w, dw, config=None):
"""
Performs vanilla stochastic gradient descent.
config format:
- learning_rate: Scalar learning rate.
"""
if config is None:
config = {}
config.setdefault("learning_rate", 1e-2)
w -= config["learning_rate"] * dw
return w, config
5.2 SGD + Momentum
문제를 해결하기 위해 일정 한 가속도를 설정해 값이 zero 머물지 않도록 설정하게 됩니다.

5.2.1 Momentum 문제점
단순 해결 방법는 rho 값은 마찰계수를 강하게 지정하게 됩니다
5.2.2 code
def sgd_momentum(w, dw, config=None):
if config is None:
config = {}
config.setdefault("learning_rate", 1e-2)
config.setdefault("momentum", 0.9)
v = config.get("velocity", np.zeros_like(w))
next_w = None
v = config['momentum'] * v - config['learning_rate'] * dw
next_w = w + v
config["velocity"] = v
return next_w, config
5.3 RMSprop
이를 개선한 방식이 RMSProp 방식으로 과거의 기울기들을 똑같이 더해나가는 것이 아니라, 먼과거의 기울기는 조금 반영하고 최신의 기울기는 많이 반영한다. (지수 이동 평균 원리 사용)
5.3.1 code
def rmsprop(w, dw, config=None):
if config is None:
config = {}
config.setdefault("learning_rate", 1e-2)
config.setdefault("decay_rate", 0.99)
config.setdefault("epsilon", 1e-8)
config.setdefault("cache", np.zeros_like(w))
next_w = None
config['cache'] = config['cache'] * config['decay_rate'] + (1 - config['decay_rate']) * dw ** 2
next_w = w - config['learning_rate'] * dw / (np.sqrt(config['cache']) + config['epsilon'])
return next_w, config

5.4 Adam
5.4.1 code
def adam(w, dw, config=None):
if config is None:
config = {}
config.setdefault("learning_rate", 1e-3)
config.setdefault("beta1", 0.9)
config.setdefault("beta2", 0.999)
config.setdefault("epsilon", 1e-8)
config.setdefault("m", np.zeros_like(w))
config.setdefault("v", np.zeros_like(w))
config.setdefault("t", 0)
next_w = None
keys = ['learning_rate','beta1','beta2','epsilon','m','v','t']
lr, beta1, beta2, eps, m, v, t = (config.get(k) for k in keys)
config['t'] = t = t + 1
config['m'] = m = beta1 * m + (1 - beta1) * dw
mt = m / (1 - beta1**t)
config['v'] = v = beta2 * v + (1 - beta2) * (dw**2)
vt = v / (1 - beta2**t)
next_w = w - lr * mt / (np.sqrt(vt) + eps)
return next_w, config
Question
5.1 Question 1
가중치 초기값이 작은 값으로 설정되면, 얕은 신경망 (예: 3 계층)의 경우에는 순전파(forward propagation)가 원할하게 진행되고 출력 레이어까지 문제 없이 정보가 전달될 수 있습니다. 그러나 계층이 깊어질수록 작은 초기값을 곱하게 되면 가중치 값들이 0으로 수렴하는 경향이 있습니다. 이것은 그보다 더 깊은 계층이 가중치에 민감하게 반응하게 됨을 의미합니다.
따라서, 신경망의 깊이가 증가할 때 가중치 초기값을 적절하게 설정하는 것이 중요합니다. 일반적으로 Xavier 초기화 또는 He 초기화와 같은 초기화 기법을 사용하여 가중치를 적절하게 설정하고, 그로 인해 그래디언트 소실 또는 폭발 문제를 완화할 수 있습니다. 이렇게 하면 심층 신경망에서도 효과적인 학습이 가능하며, 과적합을 줄이고 모델의 성능을 개선할 수 있습니다.
5.1 Question 2
AdaGrad, like Adam, is a per-parameter optimization method that uses the following update rule:
cache += dw**2
w += - learning_rate * dw / (np.sqrt(cache) + eps)
한편, AdaGrad 방식은 이전에 계산된 기울기를 제곱하여 루트를 씌우고 현재 기울기로 나누는 방식으로 학습률을 조절합니다. 이로 인해 이전에 큰 기울기를 가진 매개변수는 학습 속도가 느려지고, 작은 기울기를 가진 매개변수는 학습 속도가 빨라집니다.
그러나 AdaGrad 방식은 과거 기울기의 누적 제곱값으로 인해 학습이 느려질 수 있습니다. 이를 해결하기 위해 RMSProp 방식을 도입합니다. RMSProp은 과거 기울기의 누적을 감소시키고 최신 기울기에 더 많은 가중치를 부여하여 학습률을 더 잘 조절합니다.
더 나아가, RMSProp과 모멘텀(Momentum)을 결합한 방식으로 Adam이 등장합니다. Adam은 최신 기울기와 모멘텀을 활용하여 더욱 효과적인 학습률을 설정하며, 이를 통해 빠른 수렴과 안정적인 학습을 가능하게 합니다.