[ Architecture, Technology ,Web ] SSO(Single Sign On) 그리고 SAML에 대해

이미지
이번 프로젝트 내부에서 어쩌다보니  유저 인증 관련 업무를 담당하게 되었고, 해야하는 업무는 내부에 사용했던 적이 없던  새로운 개발 플랫폼에서  SSO의 프로토콜 중  SAML을 이용해 앱의 인증을 구현해야만 했다. SSO를 생각해본적 조차 없는 상황에 이를 새로운 개발 플랫폼에 도입해야 했기 때문에 많은 시행착오를 겪었으나 구현에 성공하였으며 덕분에 SSO에 대한 전반적인 지식을 쌓을 수 있었다. 이번에는 그러한 과정에서 나온 지식들과 경험을  공유하고자 한다. SSO에 대한 정의 먼저 사전적 정의 부터 살펴보자. 다만, 기술적인 용어다보니 자주 사용하는 옥스포드 사전에 정의를 찾을 수 없기 때문에  검색으로 찾을 수 있는 정의를 몇 가지 살펴보고 교차 검증을 해보자. 첫 번째 정의를 살펴보자. Single sign-on (SSO) is an identification method that enables users to log in to multiple applications and websites with one set of credentials.  SSO는 웹사이트에서 한 번의 인증(one set of credentials)으로 복수의 어플리케이션에 로그인 할 수 있는 인증(identification) 방법(method) 이다. 두 번째는 위키피디아의 정의이다. Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems. SSO는 독립적이지만 연관되어있는 몇몇 소프트웨어에 대해 하나의 ID로 로그인을 할 수 있도록 하는 인증 구조(scheme) 세부 설명에 조금 차이가 있어 보이지만 전체적인 틀은 매우 비슷해 보인다.  몇 가지 포인트가 되는 단어를 추출해 이를 연결해보자면 아래와 같은 의미를 산출 할 수 있다. 독립적이지만 연관되어 있

[ Neural Network, Python, Back Propagation ] Python에서 뉴럴 네트워크는 어떻게 표현되는가? : 역전파의 구현, 테스트(수직, 나선 분포 데이터)


손실 함수의 구현으로 활성화 함수 계산을 추가한
예측 값이 얼마 만큼의 오류를 가지고 있는 지에 대한 평균 값을 얻을 수 있었다.

그 다음은 역전파를 구현할 차례이다.

이번 포스팅의 코드들은 모듈화 되지 않은 
날것에 가까운 코드 이기 때문에 정렬되어 있지 않다.

이 포스팅 다음에 모듈화를 진행할 예정이다.

역전파에 대해 


앞서 이야기 했듯이 
손실을 구했다면, 당연히 예측 값을 조절해 
최대한 손실이 없게 끔 만들어야 한다.

어떻게 해야될까?

가장 일반적인 방법은 가중치를 미세하게 조절하면서
손실 함수가 최소화 될 때 까지 반복하는 것이다. 

즉, 상수 값인 기울기(가중치)를 조절하는 것이다.

역전파 프로세스는 
활성화 함수에서의 역전파(backward) 단계에서는 
순방향 전파에서 계산한 값을 가지고 
가중치와 편향을 미세 조절 하기 위한 가중치(gradient)를 결정 한다.

레이어의 역전파(backward) 단계에서
이 가중치를 레이어에서 넘겨 받아
이 값을 가지고 레이어가 가지고 있는 가중치와 편향을 업데이트 한다.

역전파는 말 그대로 역으로 값을 전달하며 
진행되기 때문에 연쇄 법칙(Chain Rule)이라고도 한다.

다만 이전 과정에서 구현했던 활성화 함수, 손실 함수를 포함해 
레이어의  역전파를 구현해야 하기 때문에 
수식에 대한 더 많은 이해와 
이전 보다 더 많은 코드 추가가 필요할 것 이다. 

그렇기에 구현 하는데 있어서 예상보다 시간이 소모되었기에 
역전파의 구현은 활성화 함수는 ReLu, Softmax를
그리고 손실 함수는 범주형 교차 엔트로피(Categorical Cross Entropy,CCE)만을 
포스팅 할 예정이다.

차후에 이진 교차 엔트로피와 다른 활성화 함수에 대한 
역전파 구현은 차후에 수학적인 내용에 들어갈때 같이 구현하기로 하겠다.

그렇다면 역전파라는 말대로, 반대로 타고 들어가보자.

역전파의 구현 : 출력 레이어

이전에 언급한대로 활성화 함수로 
소프트 맥스와 ReUL를 선택했기 때문에

이에 따라 범주형 교차 엔트로피를 구현해야 한다.


① 범주형 교차 엔트로피(SoftMax)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Loss_CategoricalCrossentropy(Loss_CCE):
    def CCE_forward(self, y_pred, y_true):
        #오버플로우 방지를 위해 값을 조정
        y_pred_clipped = np.clip(y_pred, 1e-71-1e-7)
 
        loss_cce = -np.sum(y_true*np.log(y_pred_clipped))
 
        return loss_cce
 
    def CCE_backward(self,y_pred, y_true):
        n = y_true.shape[0]
 
        # 실제 확률 분포가 one-HotEncoding화 되어있지 않을때
        if len(y_true.shape) == 1:
            gradient = y_pred
            gradient[range(n),y_true] -= 1
            gradient = gradient/n
        #실제 확률 분포가 one-HotEncoding화 되어있을때
        elif len(y_true.shape) == 2:
            gradient = (y_pred-y_true)/n
 
        return gradient
cs

범주형 교차 엔트로피의 경우 각 요소의 합이 1이기 때문에 간단하다.

예측된 확률 분포와 실제 확률 분포의 차이 값에 전체 갯수를 나누어주면 된다.

계산되어 기울기(gradient)에 저장된 값은 SoftMax에 전달된다.

shape 매소드를 활용해 실제 확률 분포 변수인 y_true가
one-HotEncdoing화가 되어 있는지를 판단하게 하였다.

정상적인 y_true 값이라면, 모두 동일한 값이 산출 된다.

역전파의 구현 : 활성화 함수




① 소프트 맥스 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Activation_Softmax(Layer):
    def __init__(self, output_cnt):
 
        self.units = output_cnt
        self.type = 'Softmax'
 
    def forward(self,inputs):
        #오버플로우를 방지하기 위해 최대값을 빼줌
        exp_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True))
        probabilities = exp_values / np.sum(exp_values, axis=1, keepdims=True)
 
        self.output = probabilities
 
        return self.output
 
    def backward(self,gradient):
        forward_soft = self.output
        self.output_back = forward_soft * (gradient -( gradient * forward_soft).sum(axis=1, keepdims=True))
 
        return self.output_back
cs

위에서 잠깐 언급한대로 레이어에 전달할 기울기를 산출 한다.

이 뉴럴 네트워크 코드에서 
소프트 맥스의 역전파(backward) 단계에서 매개변수로 받는 기울기는 
범주형 교차 엔트로피의 역전파에서 산출된 값이다.

② ReUL


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Activation_ReLU(Layer):
    def __init__(self, output_cnt):
 
        self.units = output_cnt
        self.type = 'ReLU'
 
    def forward(self,inputs):
        self.output = np.maximum(0, inputs)
        return self.output
 
    def backward(self,gradient):
        foward_ReLU = self.output
 
        self.output_back = gradient * np.where(foward_ReLU<=001)
        #self.output_back = gradient * np.heaviside(foward_ReLU,0)
 
        return self.output_back
cs


np.where 함수는 ReLU 순방향 전파로 얻은 각 요소가 
0보다 작거나 같을 경우 0으로 값을 바꾸며,
0보다 클 경우 1로 바꾸는 작업을 한다.

밑에 주석 처리한 코드와 정확히 동일한 작업을 한다.

역전파의 구현 : 레이어



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Layer:
    def __init__(self):
        pass
 
    def forward(self):
        pass
 
    def backward(self):
        pass
 
class Layer_Dense(Layer):
    def __init__(self, n_inputs, n_neurons):
        #randn : 0으로 중심으로한 가우스 분포를 무작위 생성
        #실제 randn 함수는 1을 초과한 값을 출력하기 때문에 0.10을 곱함
        self.weights = 0.10 * np.random.randn(n_inputs, n_neurons)
        #뉴런 수만 큼 무작위로 편향(값) 생성
        self.biases = np.random.rand(1,n_neurons)
 
    def forward(self, inputs):
        self.input = inputs
        #순방향 전파 수식을 표현
        self.output = np.dot(inputs, self.weights) + self.biases
 
        return self.output
 
    def backward(self, d_activation):
        learning_rate = 0.15
        #delta
        d_input = np.dot(d_activation,self.weights.T)
        #가중치 오류률 산출
        d_weight = np.dot(self.input.T,d_activation)
        #편향 오류률 산출
        d_bias = d_activation.mean(axis=0,keepdims=True)
 
        #가중치, 편향 업데이트
        self.weights -= learning_rate * d_weight
        self.biases -= learning_rate * d_bias
 
        return d_input
cs

후에 모듈화 작업을 하기 위해 
먼저 Layer라는 인터페이스를 만들었다. 

레이어의 역전파(backward)는 활성화 함수 기울기 값을 이용해 
가중치, 편향의 오류률을 산출하고
오류률을 사용해 업데이트 할 상수를 계산해 
레이어가 가지고 있는 가중치와 편향을 업데이트 한다.

변수 learning_rate는 모듈화 할때 매개 변수로 사용할 것이다.

테스트 : 학습


학습의 전체적인 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#X, y = spiral_data(samples=100, classes=3)
X, y = vertical_data(samples=100, classes=3)
 
NN_tools = NN_tools()
 
y_true = NN_tools.oneHotEncoding(y)
 
dense1 = Layer_Dense(2,4)
activation1 = Activation_ReLU(4)
 
dense2 = Layer_Dense(4,3)
activation2 = Activation_Softmax(3)
 
loss_function = Loss_CategoricalCrossentropy()
 
for iteration in range(100):
 
    #순방향 전파 프로세스
    dense1.forward(X)
    activation1.forward(dense1.output)
    dense2.forward(activation1.output)
    activation2.forward(dense2.output)
 
    #손실 계산
    loss = loss_function.forward(activation2.output, y_true)
    print("Loss:", loss)
 
    #손실의 기울기 계산
    back_CCE = loss_function.backward(activation2.output, y_true)
 
    #역전파 프로세스(산출된 손실 기울기를 기반으로 반복)
    for iteration_back in range(100):
 
        act2_back = activation2.backward(back_CCE)
        dense2_back = dense2.backward(act2_back)
 
        act1_back = activation1.backward(dense2_back)
        dense1_back = dense1.backward(act1_back)
 
#역전파로 조정된 가중치, 편향으로 다시 한번 순전파 프로세스를 실행
dense1.forward(X)
activation1.forward(dense1.output)
 
dense2.forward(activation1.output)
activation2.forward(dense2.output)
 
loss = loss_function.forward(activation2.output, y_true)
 
print("predict Loss:", loss)
 
cs

이제 수직, 나선 분포의 데이터를 가지고 결과값을 살펴보자.

테스트 : 수직 분포 데이터의 경우 경우


데이터의 분포는 보통 2가지로 예상해볼 수 있을 것이다.

수직(vertical)형과 나선(Spiral)형이다.

사용하고 있는 훈련 데이터의 수직형은 아래와 같은 분포로 데이터가 위치해 있다.


한눈에 봐도 같이 색이 되도록 주변에 분포되어 있기 때문에 분류하기 매우 편해보인다.

최적화가 최대한 이루어진 경계선이 정확히는 알 수 없으나
대략적으로 추측이 가능하기 때문이다.


아마 최적화된 경계선은 위와 같은 선이 될 가능성이 크다.

이 데이터를 가지고 훈련을 해보면 아래와 같은 결과가 나온다.

훈련 시작 부분

위의 사진으로 보면 약 손실 값이 1.1에서 눈에 띌 정도로
줄어드는 것을 확인할 수 있다.

이를 통해 구현이 제대로 이루어 진것을 알 수 있다. 


훈련 끝 부분

끝 부분에서 오류률이 0.44까지 내려가며 
다시 순전파를 진행할 때 0.46의 값이 나옴으로써 
역전파를 통해 레이어의 가중치와 편향이 이전보다 오류률이 줄은 것을 알수 있다.

다만, 값을 보면 값이 증가했다가 줄어들었다하는 패턴을 보이는 것을 
볼 수 있는데, 이는 학습 비율(변수 learning_rate)이 크기 때문이다.

이 값을 적절하게 조절하면 안정적이게 
손실 값이 줄어드는 것을 확인할 수 있을 것이다.

테스트 : 나선 분포 데이터의 경우 경우

나선 분포는 아래와 같이 데이터가 위치해 있다.


수직 분포와는 다르게 
분류에 따른 데이터가 산발적으로 분포되어 있기 때문에
적절한 경계를 정하기가 어려워 보인다.

결과 값을 살펴보자.

훈련 시작 부분


오류값이 1.1부터 시작해 수직 분포의 경우보다 
오류률이 눈에 띄게 줄어들지 않는 것을 확인할 수 있다.


훈련 종료 부분

훈련이 종료될 시점에는 약 1.08로 
오류률이 고작 0.02밖에 줄어들지 않은 것을 확인할 수 있다.

따라서 가지고 있는 데이터의 분포에 따라
최적의 오류률을 계산하기 위한 비용이 증가할 수도 있다는 것을 알 수 있다.

마치며

이제 뉴럴 네트워크의 전체적인 소스 코드는 사실상 완성되었다.

하지만, 이 코드는 좋은 코드라고 보기는 힘들며
뉴럴 네트워크를 이해하기 위한 
하나의 예로서 바라보는 것이 좋을 것이다.

Pytorch라던가 이전 순방향 전파의 결과 값을 확인할 때 
잠깐 사용했던 구글의 tensorflow 등의 훌륭한 프레임 워크가 있기 때문이다.

지금까지는 함수 하나하나를 호출해서 
레이어를 구성하는 등의 호출 했지만,
여기서 좀 더 나아간다면 모듈화를 통해
클래스로 호출하는 것이 더욱 적절할 것 이다.

다음에는 새로운 클래스를 하나 구현해 
이 곳을 통해 레이어의 추가 하고, 
순전파를 비롯해 역전파를 통한 뉴럴 네트워크 훈련 할 필요가 있을 것이다.

이 블로그의 인기 게시물

[ Web ] 웹 애플리케이션 아키텍처 (Web Application Architecture)

[ Web ] 서버 사이드(Sever Side) ? 클라이언트 사이드(Client Side)? 1 [서론, 클라이언트 사이드(Client Side)]

[ Web ] 웹 애플리케이션? 웹 사이트?(Web Application? Web Site?)