인공지능

합성곱(3)

yul_S2 2023. 1. 18. 09:48
반응형

ReLu

합성곱층에 적용되는 활성화 함수
합성곱 신경망이 성능을 더 높임
0보다 큰값은 통과
0보다 작은값은 0

 

# 1. ReLu함수 구현

def relu(x) :
    return np.maximum(x,0)

▶ numpy의 maximum함수 사용 : 두 매개값중 가장큰값을 골라 반환

 

 

 

 

# 1-1) 확인 

x=np.array([-1,2,-3,4,-5])
relu(x)
# array([0, 2, 0, 4, 0])

▶ 음수는 0이된다

 

 

 

# 1-2) 텐서플로의 relu

r_out = tf.nn.relu(x)
r_out.numpy() 
# array([0, 2, 0, 4, 0])

▶ 텐서플로는 tensor 객체를 반환하므로 numpy로 변환해야한다

 

 

 

 

# 2. ReLu함수의 도함수

▶ 0보다 크면 1이고 0보다 작으면0이다
*합성곱 신경망은 이미지의 2차원 배열을 펼칠 필요가 없다 -> 이미지정보가 손상되지 않는다.
채널이라는 차원을 하나 더 가진다.
특성맵의 마지막차원을 채널이라고한다.
이미지의 모든채널에 합성곱이 한번에 적용되어야 하므로 
커널의 마지막차원은 입력채널의 개수와 동일해야한다.

 

 

 

 

 

합성곱신경망구조

ConvolutionNetwork

합성곱, relu함수, 풀링적용 
합성곱과 풀링이 일어나는 정방향 계산 : forpass

 

glorot_uniform

가중치를 초기화할때 글로럿 초기화라는 방법을 사용할 수 있게한다.
*신경망 모델이 너무 커지면 손실 함수도 복잡해지기 때문에 출발점에 따라 결과가 달라질 수 있다.

 

 

 

# MultiClassNetwork 클래스

forpass() 메서드에 있던 z1, a1, z2를 계산하는 식은 그대로 두고 그 앞에 합성곱과 풀링층을 추가

import tensorflow as tf
 
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 합성곱 연산을 수행합니다.,self.conv_w : 합성곱에 사용할 가중치
        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])
        # np.dot -> tf.matmul 로 변경 : conv2d, max_pool2d tensor객체를 반환하기때문
        z1 = tf.matmul(f_out, self.w1) + self.b1  # 첫 번째 층의 선형 식을 계산합니다
        a1 = tf.nn.relu(z1)  # 활성화 함수를 적용합니다
        z2 = tf.matmul(a1, self.w2) + self.b2  # 두 번째 층의 선형 식을 계산합니다.
        return z2
    """
     glorot_uniform 함수로 초기화한다.
    점과 텐서플로의 자동 미분 기능을 사용하기 위해 가중치를 tf.Variable 함수로 만든다
    : 입력값에 따라 자동으로 자료형이 결정된다.
    np.zeros 함수는 기본적으로 64비트 실수를 만든다.
    -> 절변 변수와 가중치 변수가 동일하게 32비트 실수로 맞추기 위해 dtype매개변수에 float지정하였다.
    """
    def init_weights(self, input_shape, n_classes):
        g = tf.initializers.glorot_uniform()
        # glorot 방식으로 초기화 : 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
        #  glorot 방식으로 초기화
        self.w1 = tf.Variable(g((n_features, self.units)))  # (특성 개수, 은닉층의 크기)
        self.b1 = tf.Variable(np.zeros(self.units), dtype=float)  # 은닉층의 크기
        #  glorot 방식으로 초기화
        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만큼 슬라이싱하여 반환합니다.
    # backprop메서드를 구현할 필요가 없음 : 자동미분기능을 사용하기때문
    def training(self, x, y):
        m = len(x)  # 샘플 개수를 저장합니다.
        with tf.GradientTape() as tape:
            z = self.forpass(x)  # 정방향 계산을 수행합니다.
            # 정방향계산의 결과(z), 타깃(y)을 기반으로 손실을 계산합니다.
            loss = tf.nn.softmax_cross_entropy_with_logits(y, z)
            # 배치의 각 샘플에 대한 손실을 반환하므로 reduce_mean함수로 평균을 계산
            loss = tf.reduce_mean(loss)
 
        weights_list = [self.conv_w, self.conv_b,
                        self.w1, self.b1, self.w2, self.b2]
        # 그레이디언트 자동계산 tape.gradient
        # : 가중치에 대한 그래디언트를 계산합니다.
        grads = tape.gradient(loss, weights_list)
        # 그레이디언트와 가중치를 튜플로 묶은 리스트를 전달 apply_gradients
        # : python zip반복자 사용 : 가중치를 업데이트합니다.
        self.optimizer.apply_gradients(zip(grads, weights_list))
    """
    prdict메서드는 정방향 계산으로 얻은 출력값인 tensor객체를 넘파이 배열로 바꾼다.
    """
    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))
 
    """
    softmax_cross_entropy_with_logits 함수를 사용하는 get_loss함수로 계산한다.
    """
    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()
 

 

 

 

반응형