반응형
8-4 합성곱 신경망을 만들고 훈련
- 합성곱 신경망의 전체 구조
- 합성곱 신경망의 정방향 계산 구현
1. 합성곱 적용
1) self.conv_w : 합성곱에 사용할 가중치
2) stride, padding : 특성 맵의 가로와 세로 크기를 일정하게 만들기 위해 각각 1, 'SAME'으로 지정
2. 렐루 함수 적용
3. 풀링 적용하고 완전 연결층 수정
def forpass(self, x):
# 3x3 합성곱 연산 수행
c_out = tf.nn.conv2d(x, self.conv_w, strides=1, padding='SAME') + self.conv_b
# 렐루 활성화 함수 적용
r_out = tf.nn.relu(c_out)
# 2x2 최대 풀링 적용
p_out = tf.nn.max_pool2d(r_out, ksize=2, strides=2, padding='VALID')
# 첫 번째 배치 차원을 제외하고 출력을 일렬로 펼침
f_out = tf.reshape(p_out, [x.shape[0], -1])
z1 = tf.matmul(f_out, self.w1) + self.b1 # 첫 번째 층의 선형 식을 계산
a1 = tf.nn.relu(z1) # 활성화 함수를 적용
z2 = tf.matmul(a1, self.w2) + self.b2 # 두 번째 층의 선형 식을 계산
return z2
- 합성곱 신경망의 역방향 계산 구현
자동 미분(automatic differentiation) 기능 사용
x = tf.Variable(np.array([1.0, 2.0, 3.0]))
with tf.GradientTape() as tape:
y = x**3 + 2 * x +5
print(tape.gradient(y, x))
##출력: tf.Tensor([ 5. 14. 29.], shape=(3,), dtype=float64)
1. 역방향 계산 구현하기
2. 그레이디언트 계산하기
x = tf.Variable(np.array([1.0, 2.0, 3.0]))
with tf.GradientTape() as tape:
y = tf.nn.softmax(x)
# 그래디언트를 계산합니다.
print(tape.gradient(y, x))
##출력: tf.Tensor([1.99908031e-17 5.43406367e-17 1.47713165e-16], shape=(3,), dtype=float64)
def training(self, x, y):
m = len(x) # 샘플 개수를 저장합니다.
with tf.GradientTape() as tape:
z = self.forpass(x) # 정방향 계산을 수행합니다.
# 손실을 계산합니다.
loss = tf.nn.softmax_cross_entropy_with_logits(y, z)
loss = tf.reduce_mean(loss)
weights_list = [self.conv_w, self.conv_b,
self.w1, self.b1, self.w2, self.b2]
# 가중치에 대한 그래디언트를 계산합니다.
grads = tape.gradient(loss, weights_list)
# 가중치를 업데이트합니다.
self.optimizer.apply_gradients(zip(grads, weights_list))
반응형
- 옵티마이저 객체를 만들어서 가중치 초기화
1. fit() 메서드 수정
def fit(self, x, y, epochs=100, x_val=None, y_val=None):
self.init_weights(x.shape, y.shape[1]) # 은닉층과 출력층의 가중치를 초기화합니다.
self.optimizer = tf.optimizers.SGD(learning_rate=self.lr)
# epochs만큼 반복합니다.
for i in range(epochs):
print('에포크', i, end=' ')
# 제너레이터 함수에서 반환한 미니배치를 순환합니다.
batch_losses = []
for x_batch, y_batch in self.gen_batch(x, y):
print('.', end='')
self.training(x_batch, y_batch)
# 배치 손실을 기록합니다.
batch_losses.append(self.get_loss(x_batch, y_batch))
print()
# 배치 손실 평균내어 훈련 손실 값으로 저장합니다.
self.losses.append(np.mean(batch_losses))
# 검증 세트에 대한 손실을 계산합니다.
self.val_losses.append(self.get_loss(x_val, y_val))
2. init_weights()메서드 수정
def init_weights(self, input_shape, n_classes):
g = tf.initializers.glorot_uniform()
self.conv_w = tf.Variable(g((3, 3, 1, self.n_kernels)))
self.conv_b = tf.Variable(np.zeros(self.n_kernels), dtype=float)
n_features = 14 * 14 * self.n_kernels
self.w1 = tf.Variable(g((n_features, self.units))) # (특성 개수, 은닉층의 크기)
self.b1 = tf.Variable(np.zeros(self.units), dtype=float) # 은닉층의 크기
self.w2 = tf.Variable(g((self.units, n_classes))) # (은닉층의 크기, 클래스 개수)
self.b2 = tf.Variable(np.zeros(n_classes), dtype=float) # 클래스 개수
- glorot_uniform()
init_weights() 메서드의 glorot_uniform() 함수는 가중치를 촉화 할 때 그로럿(Glorot) 초기화를 사용할 수 있게 함
※ glorot_uniform()은 GlorotUniform 클래스의 별명으로 클래스를 함수처럼 호출한 것
-> 신경망 모델이 너무 커지면 손실 함수도 복잡해져서 출발점에 따라 결과가 달라질 수 있음
1차원 손실함수에서 경사 하강법은 출발점으로부터 기울기가 0이 되는 최저점 찾음
-> 가중치를 적절히 초기화하지 않으면 왼쪽 그래프처럼 지역 최적점 찾음
-> 가중치를 적절히 초기화하면 오른쪽 그래프처럼 전역 최적점 찾음
글로럿 초기화 방식으로 가중치 초기화
텐서플로의 glorot_uniform()은 위의 두 수 사이에서 균등하게 난수를 발생시켜 가중치 초기화
※ 전체 코드
class ConvolutionNetwork:
def __init__(self, n_kernels=10, units=10, batch_size=32, learning_rate=0.1):
self.n_kernels = n_kernels # 합성곱의 커널 개수
self.kernel_size = 3 # 커널 크기
self.optimizer = None # 옵티마이저
self.conv_w = None # 합성곱 층의 가중치
self.conv_b = None # 합성곱 층의 절편
self.units = units # 은닉층의 뉴런 개수
self.batch_size = batch_size # 배치 크기
self.w1 = None # 은닉층의 가중치
self.b1 = None # 은닉층의 절편
self.w2 = None # 출력층의 가중치
self.b2 = None # 출력층의 절편
self.a1 = None # 은닉층의 활성화 출력
self.losses = [] # 훈련 손실
self.val_losses = [] # 검증 손실
self.lr = learning_rate # 학습률
def forpass(self, x):
# 3x3 합성곱 연산을 수행합니다.
c_out = tf.nn.conv2d(x, self.conv_w, strides=1, padding='SAME') + self.conv_b
# 렐루 활성화 함수를 적용합니다.
r_out = tf.nn.relu(c_out)
# 2x2 최대 풀링을 적용합니다.
p_out = tf.nn.max_pool2d(r_out, ksize=2, strides=2, padding='VALID')
# 첫 번째 배치 차원을 제외하고 출력을 일렬로 펼칩니다.
f_out = tf.reshape(p_out, [x.shape[0], -1])
z1 = tf.matmul(f_out, self.w1) + self.b1 # 첫 번째 층의 선형 식을 계산합니다
a1 = tf.nn.relu(z1) # 활성화 함수를 적용합니다
z2 = tf.matmul(a1, self.w2) + self.b2 # 두 번째 층의 선형 식을 계산합니다.
return z2
def init_weights(self, input_shape, n_classes):
g = tf.initializers.glorot_uniform()
self.conv_w = tf.Variable(g((3, 3, 1, self.n_kernels)))
self.conv_b = tf.Variable(np.zeros(self.n_kernels), dtype=float)
n_features = 14 * 14 * self.n_kernels
self.w1 = tf.Variable(g((n_features, self.units))) # (특성 개수, 은닉층의 크기)
self.b1 = tf.Variable(np.zeros(self.units), dtype=float) # 은닉층의 크기
self.w2 = tf.Variable(g((self.units, n_classes))) # (은닉층의 크기, 클래스 개수)
self.b2 = tf.Variable(np.zeros(n_classes), dtype=float) # 클래스 개수
def fit(self, x, y, epochs=100, x_val=None, y_val=None):
self.init_weights(x.shape, y.shape[1]) # 은닉층과 출력층의 가중치를 초기화합니다.
self.optimizer = tf.optimizers.SGD(learning_rate=self.lr)
# epochs만큼 반복합니다.
for i in range(epochs):
print('에포크', i, end=' ')
# 제너레이터 함수에서 반환한 미니배치를 순환합니다.
batch_losses = []
for x_batch, y_batch in self.gen_batch(x, y):
print('.', end='')
self.training(x_batch, y_batch)
# 배치 손실을 기록합니다.
batch_losses.append(self.get_loss(x_batch, y_batch))
print()
# 배치 손실 평균내어 훈련 손실 값으로 저장합니다.
self.losses.append(np.mean(batch_losses))
# 검증 세트에 대한 손실을 계산합니다.
self.val_losses.append(self.get_loss(x_val, y_val))
# 미니배치 제너레이터 함수
def gen_batch(self, x, y):
bins = len(x) // self.batch_size # 미니배치 횟수
indexes = np.random.permutation(np.arange(len(x))) # 인덱스를 섞습니다.
x = x[indexes]
y = y[indexes]
for i in range(bins):
start = self.batch_size * i
end = self.batch_size * (i + 1)
yield x[start:end], y[start:end] # batch_size만큼 슬라이싱하여 반환합니다.
def training(self, x, y):
m = len(x) # 샘플 개수를 저장합니다.
with tf.GradientTape() as tape:
z = self.forpass(x) # 정방향 계산을 수행합니다.
# 손실을 계산합니다.
loss = tf.nn.softmax_cross_entropy_with_logits(y, z)
loss = tf.reduce_mean(loss)
weights_list = [self.conv_w, self.conv_b,
self.w1, self.b1, self.w2, self.b2]
# 가중치에 대한 그래디언트를 계산합니다.
grads = tape.gradient(loss, weights_list)
# 가중치를 업데이트합니다.
self.optimizer.apply_gradients(zip(grads, weights_list))
def predict(self, x):
z = self.forpass(x) # 정방향 계산을 수행합니다.
return np.argmax(z.numpy(), axis=1) # 가장 큰 값의 인덱스를 반환합니다.
def score(self, x, y):
# 예측과 타깃 열 벡터를 비교하여 True의 비율을 반환합니다.
return np.mean(self.predict(x) == np.argmax(y, axis=1))
def get_loss(self, x, y):
z = self.forpass(x) # 정방향 계산을 수행합니다.
# 손실을 계산하여 저장합니다.
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y, z))
return loss.numpy()
※ 해당 내용은 <Do it! 딥러닝 입문>의 내용을 토대로 학습하며 정리한 내용입니다.
반응형
'딥러닝 학습' 카테고리의 다른 글
9장 텍스트 분류 - 순환신경망 (1) (0) | 2023.03.21 |
---|---|
8장 이미지 분류 - 합성곱 신경망 (5) (0) | 2023.03.20 |
8장 이미지 분류 - 합성곱 신경망 (3) (0) | 2023.03.18 |
8장 이미지 분류 - 합성곱 신경망 (2) (0) | 2023.03.17 |
8장 이미지 분류 - 합성곱 신경망 (1) (0) | 2023.03.16 |