테스트 이미지(좌측 Label 7)을 예측하는데 있어, 가장 안좋은 영향력(harmful)한 데이터를 influence function을 이용하여 찾은 결과


요약


흔히, 딥러닝모델을 예측치에 대한 설명이 되지않아 블랙박스 모델이라고 한다. Influnence function은 특정 테스트 데이터포인트를 예측하는데 도움을 주었던, 도움을 주지 않았던 훈련데이터를 정량적으로 측정할 수 있는 방법이다. 즉, 훈련데이터에서 특정데이터가 빠진 경우, 테스트데이터를 예측하는데 어느정도 영향이 있었는지를 평가한다. 이를 쉽게 측정할 수 있는 방법은 모델에서 특정 데이터를 하나 뺴고, 다시 훈련(Re-training)하면 된다. 하지만 딥러닝 학습에는 너무 많은 자원이 소모되므로, 훈련후에 이를 influence function으로 추정하는 방법을 저자들은 고안을 했다.



 

Influence function 계산을 위한 가정


훈련데이터에 대한 정의를 다음과 같이 한다. 예를 들어, 이미지와 같이 입력값을 $z$라고 한다.

  • 훈련데이터 포인트(개별 하나하나의 데이터)를 $z_{n}$라고 한다.
  • 이 훈련 데이터 포인트에 개별 관측치는 입력값 x와 라벨y로 이루어져 다음과 같이 표기한다 $z_{i}=(x_{i}, y_{i})$
  • 그리고, 모델의 학습된 파라미터를 $\theta$라고 한다.
  • 주어진 파라미터한에서, 데이터포인트 z를 주었을 때의 loss 값을 $L(z,\theta)$ 라고한다.
  • 훈련데이터 1부터 n개까지의 총합 로스를 emprical loss라고 정의한다 $1/n \sum_{i=1}^{n}L(z_{i},\theta)$
  • 학습과정을 통해 얻어진 파라미터가 위의 empirical loss을 가장 줄일 수 있게 학습된 파라미터를 $\hat{\theta}=argmin_{\theta\in\Theta}1/n\sum_{i=1}^{n}L(z_{i}\theta)$

 

Influence function 의 종류


Influence functiond은 training point가 모델의 결과에 어떤 영향을 주었는지 이해하기위해서 3가지 방법론을 고안했다.
1. 해당 훈련데이터포인트가 없으면, 모델의 결과가 어떻게 변화하냐? (=가중치가 어떻게 바뀌어서 결과까지 바뀌게 되나?)
= 이 방법론은 loss가 얼마나 변화하냐로도 확인할 수 있다.
연구에서는 이를 up-weighing 또는, up,params이라는 표현으로 쓴다. up-weighting이라는 표현을 쓰는 이유는 특정 훈련데이터가 매우작은 $\epsilon$ 만큼 가중치를 둬서 계산된다고하면, 이 $epsilon$을 0으로 고려해서 계산하기 때문이다. 또는 이를 loss에 적용하면 "up,loss"라는 표현으로 기술한다.
2. 해당 훈련데이터포인트가 변경되면(perturnbing), 모델의 결과가 어떻게 변경되나?

Influence function 의 계산(up,loss)


아래의 계산식에서 좌측($I_{up,loss}(z,z_{test})$)은 예측값 $z_{test}$을 예측하는데있어, 특정 훈련데이터포인트 $z$가 얼마나 영향을 주었나(=loss을 얼마나 변화시켰나)를 의미한다. 마지막줄의 각 계산에 들어가는 원소는 다음 의미를 갖는다.

  • $-\nabla_{\theta}L(z_{test},\hat{\theta})^{T}$: 특정 테스트 z포인트를 넣었을 때, 나오는 loss을 학습파라미터로 미분한 값이다. 즉
  • $H_{\hat{\theta}}^{-1}$: 학습된 파라미터의 hessian의 inversed matrix이다.
  • $\nabla_{\theta}L(z,\hat{\theta})^{T}$: 특정 훈련데이터포인트 z를 넣었을 때, 나오는 loss을 학습파라미터로 미분한 값이다.

위의 세 값을 다 계산하여 곱해주기만하면 특정 훈련데이터z가 테스트데이트를 예측하는데 로스를 얼마나 변화시켰는지 파악할 수 있다.


여기서 문제는 해시안 메트릭스($H_{\hat{\theta}}^{-1}$)를 계산하는 것이 매우 오래걸린다는 것이다. 따라서 저자들은 아래와 같이 추정방법으로 해시안메트릭스의 역행열을 구한다.

Influence function 계산의 어려움(비용)


Influence function 계산이 어려운 부분(계산비용이 큰 이유) 2부분 때문이다.

  1. 해시안 메트릭스의 역행렬을 구해야함: 해시안 메트릭스는 각 행렬에 파라미터의 개수가 p개라고 하면, $O(np^{2}+p^{3})$의 계산량이 든다는 것이다.
  2. 훈련 데이터 포인트에 대해서 모든 데이터포인트를 다 찾아야한다는 것. 예측에 도움이 안되는 데이터포인트를 찾으려면 훈련데이터가 n개면 n개의 데이터포인트를 찾아야한다.

다행이도 첫 번째 문제인 해시안 메트릭스이 역행렬을 그하는 것은 잘 연구가 되어있어서 해결할 수 있다. 명시적으로 해시안의 역행렬을 구하는것 대신에 해시안 역행렬과 손실값을 곱한 Heissan-vector product(HPV) 을  구하는 것이다



Inversed hessian matrix 구하기(HPV구하기, 중요)


조금더 구체적으로는 저자들은 해시안메트릭스의 역행열을 구하기보다는, 해시안메트릭스의 역행렬과 loss에 해당하는 vector($\nabla_{\theta}L(z_{test},\hat{\theta})^{T}$)을 한번에 구하는 방법을 소개한다. 이는 책 또는 github코드에서 주로 Inversed hessian vector product (inversed HVP)라고 불린다.


이를 구하는 2가지 방법이 있다.

  1. Conjugate gradient (CG)
  2. Stocahstic estimtation

저자들은 주로 Stocahstic estimation 방법으로 이를 해결하려고 한다. 

다음의 서술은 위의 HVP을 구하는 방법에 관한 것이다.

  1. 훈련 데이터 포인트 전체 n개에서 매번 t개만을 샘플링 한다.
  2. HVP의 초기값은 v을 이용한다 (이 때, v은 $\nabla_{\theta}L(z_{test}, \hat{\theta})^{T}$을 사용한다. 테스트세트에 대한 로스값의 gradient이다.)
  3. $\tilde{H_{j}}^{-1}v=v+(I-\nabla_{\theta}^{2}L(z_{s_{j}, \hat{\theta}})\tilde{H}_{j-1}^{-1}v$ 을 계속한다.
  4. 위의 과정(1-3)을 반복한다.

위와 과정을 충분히 반복하고, t을 충분히 크게 뽑는 경우 HVP값이 안정화되고 수렴된다고 한다. 이 방법이 CG방법보다 빠르다고 언급이 되어있다.

하이라이트 결과


Leave one out 방법과 유사하게 Influence function이 loss값의 변화를 추정한다.

개인적으로 아래의 Figure 2가 제일 중요해보인다. 훈련데이터를 하나 뻈을때(leave-one-out)의 방법과 Inlfuence function으로 예측한 예측값이 거의 정확하다면 선형을 이룰것이다. 아래의 그림은 이를 뒷받침하는 결과로 사용된다.

 

손실함수를 미분할 수 없는 경우도, 손실함수를 대략적으로 추정(Smooth approximation)  한 경우 추정할 수 있다.


아래의 결과는 Linear SVM을 이용하여 예측한 loss의 값과, 실제 retraining한 경우의 예측값을 보여주고 있다. Linear SVM 자체에서는 Hinge 값이 쓰이는데 (ReLU와 유사하게) 미분이 불가능하다. 따라서, 저자들은 0에서 미분불가능할 때 smooth하게 변경하기위해서 아래와 같이 smooth hinge라는 것을 만들어서 어느정도 추정한 값을 쓰고 있다($smooth ~hinge(s,t)=tlog(1+exp((1-s)/t)$). 즉 이 함수는 t가 0이되면 0이될수록 미분이 불가능한 hinge loss와 동일해진다. 이렇게 적용했을 때 결과가 (b)와 같다. 미분불가능한 함수를 미분 가능하게 조금 수정한다면, (b) 와 같이 t=0.001인 경우, t=0.1인 경우와 같이 어느정도 influence function이 데이터를 빼고 재학습한 것과 같이 추정이 가능하다는 것이다)

 

Influence function의 유즈케이스


아래는 influence function을 언제 쓰는지에 관한 것이다. 

1. 모델이 어떤 방식으로 행동하는지를 알 수 있다.: 아래의 그림 (Figure 4)은 SVM과 Inception(CNN기반)에서의 물고기 vs 개의 분류문제이다. 이 Figure 은 여러가지를 우리에게 알려준다.

  1. 우리의 직관은 "SVM은 딱히 이미지의 위상정보을 이용하지 않으니, 이미지의 거리(L2 dist)가 클수록 분류에 도움이 안될 것"으로 생각할 수 있다. Figure 4을보면 x축이 Euclidean dist인데, 400이상인경우 influcen function의 크기가 0에 가까워지는 것을 볼 수 있다.
  2. 녹색의 데이터포인트는 물고기의 학습데이터이다. 물고기를 예측하는데 있어, 물고기의 이미지만 도움을 주는 것으로 파악할 수 있다.
  3. 반대로, Inception 은 딱히 위상정보가 아니어도, 이미지의 외형(contour)을 잘 학습하기 때문에, 거의 모든 이미지가 분류에 영향을 주는 것을 알 수 있다. 즉 물고기의 예측하는데 있어서, 일부 강아지 이미지가 도움을 줄 수 있다는 것이다.

 

 

 

반응형

요약


 

RandomForest 의 Feature importance은 여러가지가 있을 수 있지만 크게는 대표적으로 1) 불순도 기반 방식(Impurity based): tree을 나눌 때, 쓰이는 특징값으로 나눴을 때, 클레스가 잘 나뉘는 가 얼마나 변화하는지 측정하는 방식. 불순도의 변화가 크면 클수록 트리를 잘 나눴다는 것을 의미중요한 특징 값이라는 것, 2) 순열 기반 방식(Permutation based): 각 특징값을 데이터 포인터에 대해서 랜덤하게 순서를 바꿔서, 정확도가 떨어지는지 확인하는 방식. 정확도가 크게 떨어질 수록 중요한 특징값임을 가정하는 방식이 있다.

 

선행


랜덤포레스트는 이 포스팅에서 깊게 다루지는 않지만, 트리구조를 만들 때, 배깅(bagging) 중복을 포함해서 임의로 데이터를 N개씩 뽑고, Feature bagging (속성을 뽑을 때도 랜덤하게 K)만큼 뽑아서 각각의 트리를 만든다. 그렇게 만들어진 약한 분류기(Tree)을 여러개 만들고 분류모델이면 Voting하여 얻은 값을 반환한다.

https://medium.com/analytics-vidhya/random-forest-classifier-and-its-hyperparameters-8467bec755f6

 

 

Gini importance / Mean Decrease in Impurity (MDI)


이 방식은 Gini importance라고도 불리고, MDI라고도 불리는 방식이다. 의미하는 것은 모든 랜덤포레스트의 각각의 트리에서 해당 특징값으로 트리를 나눌 때, 감소한 불순도의 평균을 의미한다(For each feature we can collect how on average it decreases the impurity. The average over all trees in the forest is the measure of the feature importance[1]).

Gini impurity은 아래와같이 계산한다 (주의: Gini coefficient와 다른 내용이다).

$Gini impurity = 1 - Gini$

$Gini = p_{1}^{2}+p_{2}^{2}+...+p_{n}^{2}$

n은 데이터의 각 클레스를 의미한다. 가령 아래와 같이 주황색 데이터와, 파란색데이터를 구분하는 문제라면 n은 2가된다. 노드를 나누기전을 부모노드라고하면, 부모노드에서의 Gini은 파란색공이 5개중에 3개, 노란색이 5개중에 2개이므로 $Gini=(3/5)^{2}+(2/5)^{2}$가 된다. 따라서, Gini impurity은 $Gini impurity=1-(3/5)^{2}+(2/5)^{2}$이다. 그리고 나눈 후를보면, 좌측은 $Gini imputiry = 1- ((2/3)^{2}+(1/3)^{2}$. 우측은 $Gini imputiry = 1-((1/2)^{2}+(1/2)^{2})$ 이다. Gini impurity의 변화량이 얼마나 변화했는지를 파악하는 것이기 때문에, 다음과 같이 부모의 impurity - (자식노드의 imputiry의합)과 같이 계산한다.

$1-(3/5)^{2}+(2/5)^{2} - [(1- ((2/3)^{2}+(1/3)^{2}) - 1-((1/2)^{2}+(1/2)^{2})]$

위의 계산에서 중요한 것을 빠뜨렸는데 특징값 A에 대해서 위와 같은 과정을 진행했다고하면, 모든 노드 중에서 A을 이용한 분기에를 모든 트리에서 찾은다음에 평균낸 값이 Gini importance이다. 혹은, 어떤 방식에서는 나누려는 노드의 가중치를 곱해서 많이 나눴던 노드에서는 더 가중치를 주기도 한다.

Gini importance 예시

 

아래의 그림을 보면 feature importances (MDI)라는 것이 표시되어있다. 이는 위의 과정(Gini importance)을 이용해서 각 노드를 나눴을때 불순도가 크게 변화하는 정도의 평균을 계산했다는 의미이다. (이 데이터셋은 타이타닛 데이터셋). 생존자와 사망자를 구분할 때, sex을 이용해서 구분했을 때, sex을 안썼을 때보다 평균적으로 더 트리구조가 깔끔하게 나뉘어져나갔다는 것을 의미한다. 

sklearn에서의 feature importance 예시: https://scikit-learn.org/stable/auto_examples/inspection/plot_permutation_importance.html

 

[1] https://mljar.com/blog/feature-importance-in-random-forest/#:~:text=Random%20Forest%20Built%2Din%20Feature%20Importance&text=It%20is%20a%20set%20of,sets%20with%20similars%20responses%20within.

 

Permutational importance / Mean Decrease in Accuracy (MDA)


Permutation importance은 트리를 만들때 사용되지 않았던 데이터들 OOB(Out of bagging, 랜덤포레스트의 각 트리를 만들 때, 랜덤으로 N개의 데이터를 중복을 포함하여 뽑는데, 그 때 사용되지 않았던 데이터들)을 이용하여, OOB인 데이터들에 대해서 해당 특징값을 랜덤하게 섞은후에 예측력을 비교한다.

아래와 같이 a,b,c,d,e,의 특징값을 가진 1,2,3,4,5번 데이터가 있다고하자. 모델을 빌딩할 때, 랜덤으로 뽑은 데이터가 1,3,5였고, 안타깝게 2,4번은 중복을 포함혀어 뽑았지만 뽑히지 않았던 데이터라고 하자. 

OOB인 데이터포인트 2,4가 있고, a,b,c,d,e 특징값이 있는 데이터의 예시

이 때, 특징값 a에 대해서 1,2,3,4,5번의 a특징값의 순서를 랜덤하게 바꿔버리고 원래 데이터셋으로 만든 모델과 성능을 비교한다. 성능차이가 크면 클수록 랜덤으로 돌리지 말았어야할 중요한 별수라는 것이다. 또는 2번과 4번의 데이터 또는 바꿔서 차를 계산해도 이론상 큰 차이는 안날 것이다. 이렇게 특징값a에 대해서, 순서변경 전후의 성능을 비교하고, b,c,d,e,f에 대해서도 비교하면 permutation feature importance가 계산된다.

 

아래와 같이 permuation importance을 계산하면 성별이 나온다. boxplot으로 나온 이유는 random forest의 각 tree에서의 틀린정도의 분포를 그렸기 때문이다. 해석은 다음과 같다. 테스트 세트에서 생존여부를 맞추는 정도는 sex을 랜덤하게 바꿀 경우 가장 많이 성능이 떨어졌다는 것을 의미한다. 각 트리별로 고려했을 때, 평균적으로 0.25정도 성능이 떨어졌음을 의미한다.반대로 random_num, age등은 어떤 tree에서는 빼나 안빼나 구별력차이가 없었던 것을 의미하기도 한다 (box plot에 0에 맞닿아있다 (이는 랜덤으로 뽑은 데이터셋이 운이 안좋게 구별을 못한 경우일 수도 어서 0이 나왔을 수도있다.

https://scikit-learn.org/stable/auto_examples/inspection/plot_permutation_importance.html

반응형

쿠버네티스에서 kubectl get pods 등 여러 명령어를 치더라도 아래와 같이 에러를 반환하는 경우가 있다. 

The connection to the server localhost:8080 was refused - did you specify the right host or port 

 

원인: 마스터노드에서의 kubectl 관련 config가 설정이 되지 않았기 때문


쿠버네티스 초기화 내 config가 설정이 안되어있는 경우로 생각된다. 실제로 master 노드에서의 config을 확인해보면 아래와 같다.

 

 

해결방법1: (마스터노드만) kubeadm을 이용한 마스터노드의 초기화


쿠버네티스 kubeadm을 설치가 안되어있다면 설치가 필요하고, 설치했음에도 에러가 떴다면 초기화 해주는 과정이 필요하다.

설치를 한적이 없으면, 여기 URL 에서 kubeadm을 설치하는 과정을 따라하여 설치한다. 아래의 리눅스 운영체제 또는 패키지관리도구를 이용해서 설치할 것인지 말것인지 결정해서 1~4번까지 복붙하여 따라한다. 설치되어있으면 kubeadm을 초기화한다.

 

아래의 명령어로 쿠버네티스 마스터노드의 kubeadm을 초기화해준다.

sudo kubeadm init // 처음 설치한 경우
sudo kubeadm reset // 이미 설치한 경우

 

위의 명령어로도 쿠버네티스 마스터노드가 초기화 안되고 에러가 뜨는 경우가 발생했는데, 필자의 경우는 도커의 설치가 되지않아 초기화되지 않았다. 마찬가지로 도커설치도 진행한다. docker.io라고 할지 어떤 종류를 할지는 sudo apt-get install docker을 한번 실행해보면, 가이드를 해주는 설명이 출력된다.

sudo apt-get install docker.io

 

그리고나서 다시 kubeadm으로 마스터노드를 초기화 한다. 그러면, 아래와 같이 정살정으로 kubectl 관련 서비스들이 생성된다.

그림. 마스터노드에서의 쿠버네티스 초기화 작업. 자세히보면 마스터노드에서 필요한 서비스들이 생성된다. 예를 들어 etcd와 같은 클라이언트 노드들의 상태를 보관하는 저장소들도 하나씩 생성해나간다.

 

관련 리소스들이 제대로 설치가 되었는지 아래와 같이 namespace을 검색해본다.

# kubectl get pods --all-namespaces

 

해결방법2: WorkerNode인경우


마스터노드와의 Join이 안되는 경우에 이런 문제가 발생 수 있다. 마스터노드에서 다음의 명령어로 해당 노드가 제대로 참여중인지 확인한다. 아래의 명령어로 control-plane과 해당 worker노드가 확인되어야한다. 확인되지 않으면, 워커노드가 클러스터로 join이 안된것이다.

# kubectl get nodes

 

워커노드가 클러스터에 조인이 안되었음을 확인했다면, 일단 워커노드를 초기화한다.

# kubeadm reset

그리고 마스터노드에 join에 필요한 hash, cluster API및 포트, token정보를 받아온다.

// 마스터노드에서
root@k8s-master:/home/ubuntu# kubeadm token create --print-join-command
kubeadm join 172.16.2.231:6443 --token 8yxfot.ktr4irill5hqw6h7 --discovery-token-ca-cert-hash sha256:bd91d60981b5774add11106cd42e1f8d6db18b241b5fd529b0d638e71

다시 워커노드에서 위의 명령어를 복붙하여 조인한다.

root@k8s-test:/home/ubuntu# kubeadm join 172.16.2.231:6443 --token 8yxfot.ktr4irill5hqw6h7 --discovery-token-ca-cert-hash sha256:bd91d60981b5774add11106cd42e1f8d6db18b241b5fd529b0d638e713522494
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

 

반응형

쿠버네티스의 컨트롤러는 특정 Pod의 수를 보장해주는 역할을 하는 자원을 의미한다. 아래의 쿠버네티스의 마스터노드(컨트롤플레인)가 어플리케이션을 각 노드에서 실행하고, 이를 각 etcd에서 제대로 수행되고 있는지 모니터링하다가 부족한 Pod은 더 실행해주고, 많은 Pod은 종료시켜주는 역할을 한다.

그림. 쿠버네티스 동작원리

 

쿠버네티스의 컨트롤러의 종류


쿠버네티스 컨트롤러는 Replication controller, replicaset, deployment, daemonset등 여러가지가 있어서 헷갈린다. 이 자원들은 모두 다 컨트롤로에 속한다. 다만 각각의 자원들이 쿠버네티스가 발전해나감으로서 생긴 디테일의 차이(대동소이)한 것들이 있어서 몇가지 대표적으로 필요한 것들만 알아보면 좋다. 사용목적에 따른 대표적인 것들은 아래와 같다.

  • 오랜시간 켜두어야할 어플리케이션이 파드로 존재하는 경우: Replication contoller, Replicaset, Deploment
  • 클러스터에 속한 전체 노드에 같은 파드를 실행해야하는 경우: Daemonset
  • 일회성 작업을 해야할 때: Job
  • 주기적인 배치작업을 해야하는 경우: Cronjob

자세히는 아래와 같다. 리플리케이션 컨트롤러 (Replication controller)은 쿠버네티스 초기부터 있던 컨트롤러로, 지정된 숫자만큼 파드의 개수를 항상 보장해줄 수 있도록 제어해주는 컨트롤러이다. 요즘은 이 리플리케이션 컨트롤러보다 리플리카셋(replicaset)을 주로 이용하거나, 앱 배포면 디플로이먼트(deployment)을 이용한다고 한다.

사용방법은 아래와 같다. 1) Kind에 replicationContoller라고 적는다. 2) spec.replicas내에 몇개의 파드를 유지할 것인지를 명시한다. 3) selector은 레이블이 동일한 어떤 파드를 관찰할 것인지를 명시하는 것이다. 여기서는 app: my-ngnix라는 라벨을 가진 파드를 바라보고 관찰한다. 4) template은 명시한 파드가 개수가 부족한 경우, 어떤 스펙으로 다시 만들지를 정의한다. *주의할 것은 selector이하의 속성값과 template.labels의 속성값은 동일해야 에러가 안난다.

아래와 같은 rc의 실행결과를 확인할 수 있다.

PS C:\Users\PC\Documents\repository\kube> kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
my-rc   3         0         0       24s

 

리플리카셋(Replicaset, RC): ReplicationContorller의 확장판(+POD 검색기능)


ReplicationContoller은 POD의 개수를 보장한다고 했다. 리플리카셋도 마찬가지로 POD의 개수를 보장한다. 하지만, 리플리카셋이 추가적으로 해주는 기능은,

 "="의 selector외에 집합연산자 (in, notin)등을 지원해서 저 쉽게 사용할 수 있다. 리플리카셋의 spec에는 matchLabels과 matchExpressions라는 것을 사용할 수 있는데, matchLabels은 리플리케이션컨트롤러가 해주는 기능과 비슷하다. 다만, matchExpressions으로 좀더 상세하게 POD의 선정을 할 수 있다. 가령 아래와 같이 matchExpression을 써주면 key의 In조건에 맞는 POD들만 선택해서 관리할 수 있다.

# replicaset.yaml

...
spec:
    replicas:3
    selector:
         matchLabels:
             app: my-app
         matchExpression:
             {key: verison, operator: In, value:["3.5", "2.1"]}

 

하지만 matchExpressions을 꼭 써야하는 것은 아니다. 다음과 같이 작성해도 충분히 동작한다.

위와 같이 파드의 개수를 3개로 유지하게끔 선언했는데, 아래와 같이 파드가 1개 생성된 경경우는 2개가 아직 모종의 이유로 생성되지 안음을 확인할 수 있다. cpu 요구량을 못맞춰서 중단된것을 알 수 있다.

> kubectl get pods
NAME                  READY   STATUS              RESTARTS   AGE
my-replicaset-q42qp   0/1     ContainerCreating   0          3s

> kubectl describe replicaset my-replicaset
....
Events:
  Type     Reason            Age                 From                   Message
  ----     ------            ----                ----                   -------
  Normal   SuccessfulCreate  2m7s                replicaset-controller  Created pod: my-replicaset-q42qp
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-86kwg" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-n4rkz" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-jg6xc" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-66pht" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-5zg4z" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-8ns6f" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m7s                replicaset-controller  Error creating: pods "my-replicaset-w5gsb" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m6s                replicaset-controller  Error creating: pods "my-replicaset-8j4dh" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      2m6s                replicaset-controller  Error creating: pods "my-replicaset-vtqgv" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1
  Warning  FailedCreate      24s (x8 over 2m6s)  replicaset-controller  (combined from similar events): Error creating: pods "my-replicaset-fx7rt" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.cpu=1, used: requests.cpu=1, limited: requests.cpu=1

 

디플로이먼트(Deployment)


컨트롤러기보다는 컨트롤러인 레플리카셋(RS)을 제어해주는 역할이다. 디플로이먼트의 가장 큰 목적은 롤링업데이트(Rolling update) 또는 롤링백(Rolling back)을 하기위함이다. 롤링업데이트의 자세한 설명은 링크와 같다. 소프트웨어를 업데이트를 위한 것인데 요약하면 "롤링 업데이트는 파드가 여러 개 있다고 했을 때, 한 번에 모든 파드를 동시에 버전업하는 것이 아닌, 파드를 하나씩 업데이트해서 무중단 업데이트를 해주는 과정"이라고 생각하면 된다. 핵심은 "무중단 + 업데이트"이다.  

Deployment은 정확히는 컨트롤러는 아니고, 컨트롤러인 Replicaset을 제어하는 자원이다. Replicaset은 POD을 제어한다.

아래와 같이 대화형 튜토리얼을 이용하면 예시를 직접 해볼 수 있다(링크). 아래의 이미지를 보면, 이름이 "kubernetes-bootcamp"인 deployment가 하나가 실행됨을 확인할 수 있다. 위에서도 디플로이먼트가 레플리카셋을 제어한다고하니, 레플리카셋이 하나 있을 것이고, 레플리카셋은 또 파드를 제어하니, 파드의 생성도 함께 확인할 수 있을 것이다.

그러면, 디플로이먼트를 사용하려면 어떻게 yaml파일을 작성해야하나? 일단, deployment의 버전을 확인하기위해서 아래와 같이 확인한다. deployment은 apps/v1의 APIVERSION에 있다.

PS C:\Users\PC> kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
...
deployments                       deploy       apps/v1                                true         Deployment

그리고 아래와 같이 스펙에 맞춰 yaml파일을 작성한다. 형태는 replciaset과 동일하다.

apiVersion: apps/v1
kind: Deployment
metadata:
    name: my-deployment
spec:
    replicas: 2
    selector:
        matchLabels:
            app: my-nginx
    template:
        metadata:
            name: nginx-pod
            labels: 
                app: my-nginx
        spec:
            containers:
            -   name: nginx-container
                image: nginx:latest
                resources:
                    limits:
                        memory: 500Mi
                        cpu: 1
                    requests:
                        memory: 500Mi
                        cpu: 1

 

그러면 아래와같이 동작하는 것을 확인할 수 있다. (POD 하나는 리소스 이유 때문에 동작은 하지 않았다.)

PS C:\Users\PC\Documents\repository\kube> kubectl get all -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP          NODE             NOMINATED NODE   READINESS GATES
pod/my-deployment-cff6c8d86-jgb5g   1/1     Running   0          12s   10.1.0.25   docker-desktop   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   44d   <none>

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS        IMAGES         SELECTOR
deployment.apps/my-deployment   1/2     1            1           12s   nginx-container   nginx:latest   app=my-nginx

NAME                                      DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES         SELECTOR
replicaset.apps/my-deployment-cff6c8d86   2         1         1       12s   nginx-container   nginx:latest   app=my-nginx,pod-template-hash=cff6c8d86

 

 

Deployment을 이용하여 pod을 스케일업하기: kubectl scale deployment [deployment명] --replicas=3

아래의 그림과 같이 deployment은 Replica Set(RS)을 선언적으로 지정해서 관리한다. 또한, Replica Set은 pod을 선언적으로 관리한다.(선언적= 지정한대로 개수, 자원등을 보장해줌). 따라서, deployment을 생성해놓고, replicaset 또는 pod을 직접 관리하면, 삭제되고 재생산된다. 따라서, pod수나 replicas을 변경하고싶으면 deployment을 이용해서 조작해야한다.

Deployment, Replica Set, Pod의 관계예시: 자료(https://theithollow.com/2019/01/30/kubernetes-deployments/)

아래와같이 쿠버네티스 공식홈페이지에 있는 것과 동일하게 웹서버인 nginx을 서비스하는 deployment을 정의했다.

 

이를 이용해서 Deployment 및 스케일업을 아래와같이 진행해볼 수 있다. 아래의 코드블럭처럼 replicas을 조절하면 replicas에 딸려있는 pod수가 조절되므로 replicas을 조정해도 된다. replicas을조정하기위해서는 deployment을 조정한다. 

// deployment 자원의 생성
$ kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deployment created

// 자원이 생성되었는지 확인
$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           18s

// replicas을 5개로 스케일업
$ kubectl scale deployment nginx-deployment --replicas=5
deployment.apps/nginx-deployment scaled


// 스케일업 되었는지 pod수를 확인
$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fb96c846b-88ctt   1/1     Running   0          52s
nginx-deployment-7fb96c846b-dqbhk   1/1     Running   0          7s
nginx-deployment-7fb96c846b-jhgq2   1/1     Running   0          52s
nginx-deployment-7fb96c846b-m2bbg   1/1     Running   0          7s
nginx-deployment-7fb96c846b-xj74r   1/1     Running   0          52s

그리고, kubectl descirbde 을 이용하면 아래와같이 스케일업된것을 확인할 수 있다.

 

 

롤링업데이트의 사용은 어떻게 해야하나? 예시로 

# set 명령어 인자
kubectl set image deployment <디플로이먼트 이름> <컨테이너이름>=<새 버전>

# 디플로이먼트 내 컨테이너 ngnix 을 1.9.1로 업데이트
kubectl set image deployment/nginx nginx=nginx:1.9.1

# 모든 디플로이먼트와 RC의 컨테이너 내에 있는 ngnix이미지를 1.9.1로 업데이트
kubectl set image deployments,rc nginx=nginx:1.9.1 --all

*references:https://www.macstadium.com/blog/how-to-k8s-pods-replicasets-and-deployments

 

Deployment의 정지: kubectl delete deployment [deployment명]

// deployment 상태확인
$ kubectl get deployment
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
simple-app-deployment   3/3     3            3           11m


$ kubectl delete deployment simple-app-deployment
deployment.apps "simple-app-deployment" deleted

 

 

스테이트풀세트(Stateful set)


작성중

 

데몬세트(Daemon set)


작성중

 

잡 컨트롤러(Job contoller)


작성중

 

크론잡(Cronjob)


작성중

 

기타(트러블 슈팅)


이슈1: error: resource mapping not found for name: "my-rc" namespace: "" from ".\\my_rc.yaml": no matches for kind "ReplicationContoller" in version "v1" ensure CRDs are installed first

해결방법: 아래와 같이 쿠버네티스에서 지원하는 자원들과 APIVERSION을 확인한다. 참고로 해당 이슈는 "ReplicationContoller"을 kind에 썼는데, 아래와 같이 "ReplicationController"에 오타가 난 것을 알 수 있다. 즉, 아래의 리스트의 KIND와 동일한 KINDS을 써주어야한다.

$ kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
limitranges                       limits       v1                                     true         LimitRange
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
...

 

이슈 2: 리플리카셋에서 아래와 같이 메시지가 나온 경우(failed quota) Error creating: pods "my-replicaset-vvh6n" is forbidden: failed quota: mem-cpu-demo: must specify limits.cpu for: nginx-container; requests.cpu for: nginx-container

해결방법: 리플리카셋 내 template.spec.container.resources 내에 limit 또는 reqeusts을 명시한다.

반응형

의외로 다른 IT 프로젝트와 달리, 특히 AI 프로젝트의 성공율이 15%정도라고 한다. 왜 15%밖에 안되는 것인가? 해당 보고서에는 아래와 같이 설명하고 있다.

  • ML은 아직 리서치 단계(연구단계)의 작업들이다. 따라서, 애초에 프로젝트 시에 무조건 된다는 버려야 한다.
  • 기술적을 불가능한 문제를 푸려고 한다.
  • 프로덕션 단계를 고려하지 않는다.
  • ML 프로젝트의 성공여부가 불분명하다.
  • 팀 관리가 안된다.

 

Lifecycle (생애주기)


ML project의 생애주기는 ML 프로젝트에 각각 무엇이 수행될 수 있는지에 관한 내용이다. 크게는 프로젝트 계획하기-> 데이터 수집 및 라벨링 -> 모델 학습 및 디버깅 -> 모델 적용 및 테스트로 구분된다. 이 단계가 무조건 1, 2,3,4의 순차적인 단계로 실행되는 것은 아니고, 진행하던 단계가 문제에 봉착했는데 풀 수 없을 경우는 이전, 또는 더 이전단계로 돌아가서 계획부터 다시 할 수 있다는 것을 알아야 한다.

ML 프로젝트의 생애주기

- 프로젝트 계획하기(Planning and Project setup): 실제 문제를 풀어 나갈 것인지, 목표는 무엇인지, 리소스는 어떤 것들이 있는지 파악하는 단계이다.

- 데이터 수집 및 라벨링(data collection and labeling): 훈련용 데이터를 수집하고, ground truth(실제 참이라고 할 수 있는 라벨)을 이용하여 데이터를 라벨한다. 만일 이 단계에서 뭔가 문제가 있거나 데이터를 구하기 어려우면 프로젝트 계획부터 다시 실행하는 것을 권장한다.

- 모델 학습 및 디버깅(Model traning and debugging): 이 단계에서는 베이스 라인 모델을 빨리 적용해놓고, 가장 좋은 모델들을 찾아서 성능을 높혀나가는 것을 의미한다. 그리고 이 단계에서는 뭔가 라벨링이 이상하거나 데이터가 더 필요한 경우는 이전 단계인 "데이터 수집 및 라벨링" 단계로 돌아간다. 혹은 문제가 너무 어려워서 풀지 못할 것 같거나, 프로젝트 요구사항에서 밀릴 경우는 프로젝트 게획하기 단계부터 다시 실행한다.

- 모델 적용 및 테스트(ML Deploying and Model testing): 이 단계에서는 파일럿 모델을 연구실 환경에서 한번 돌려보고, 잘되는지 확인하고, 실제 프로덕션 단계로 적용한다. 만일, 성능이 애초에 안나오는 경우, 모델 학습 및 디버깅 단계로 돌아간다. 또는 데이터가 더 필요하거나, 훈련데이터랑 실제 데이터랑 너무 안맞는 경우 데이터 수집 및 라벨링 단계로 간다. 마지막으로 실제 환경에서 잘 동작하지 않거나, 사용자의 사용 환경과 너무 안맞는 결과들이 나올 경우에는 계획부터 다시해야할 수도 있다.

 

프로젝트의 우선순위 결정하기(Priortizing projects)


ML프로젝트를 실행하기전에, 이 프로젝트가 얼마나 비지니스 모델에 영향을 줄 것인지? 아래의 그림처럼, 가능한 실현가능성도 높고, 실제 비지니스에 영향을 줄만한 프로젝트를 하는 것이 중요하다. 

영향도 평가: 니즈(Needs). 니즈를 파악하는 것은 얼마나 의사결정 과정에서 마찰을 경험하고 있는지를 파악해보는 것이다. ML의 장점(Strength)은 Software 2.0(데이터가 많아질 수록 성능이 강력해지는 소프트웨어)과 데이터가 실제 복잡한 규칙기반의 소프트웨어이며, 이 규칙이 데이터로 학습할 수 있는 지를 의미한다. 

실현가능성 평가(Feasibiltiy): 실현 가능성을 평가하는 것들중에 비용이 많이 드는 순서로 문제의 난이도 -> 요구되는 모델의 정확성 -> 사용가능한 데이터순으로 높다. 첫 째로 가장 문제가되는 데이터는 "데이터가 많은지", "얻기 쉬운지?", "얼마나 많이 필요할지" 등이다. 흔히 생각할 수 있는 내용이다. 둘 째로, 모델의 정확도 요구사항은 혹시 모델이 틀렸을 때 얼마나 비용이 많이 드는지, 어느정도 정확해야 쓸만한지, 윤리적인 이슈는 없는지에 관한 내용이다. 마지막으로 문제의 난이도는 "문제가 잘 정의도리 수 있는지", "이전에 해결한 문제들은 없는지" 컴퓨팅 요구사항"은 되는지, 사람이 할 수 있는일인지에 관한 내용이다.

 

아키텍처(Archetypes)


ML 프로젝트의 메인 카테고리로, 프로젝트 관리 및 구현을 어떻게 할 것인지에 관한 내용이다. 이 챕터는 그 원형(Archetypes)에 대한 얘기로, 크게 3가지로 분류되며, Sofware 2.0, HITL(Human in the loop), Autonomous system이다. 본인이 풀고자하는 문제들이 어느부분에 해당하는지, 어떻게 구현되는지 개념적으로 명확히 이해하는데 도움을 줄 수 있다.

  • Software 2.0: 소프트웨어의 성능이 데이터가 많아질 수록 강력해지는 것을 의미한다. 기존의 규칙기반 시스템을 능가하는 것을 의미한다.
  • HITL: 사람의 컨펌하에 쓰는 소프트웨어이다. 가령, 구글 이메일 작성시, 오토 컴플릿되는 것을 의미한다.
  • 자동화 시스템: 사람 손을 안타는 시스템이다.

 

평가지표(metrics)


위의 내용처럼 어떻게 구현하겠다라는 가이드가 잡히면, 실제로 어떤 지표를 삼아야 성공여부를 달성했는지, 효율적으로 평가할지를 알 수 있다. 당연하겠지만 평가도구는 모델의 성능을 평가할 수 있는 지표를 삼아야한다. 처음에는 모델을 성능을 단순히 평가할만한 하나의 지표로 삼다는다. 그 후에, 안정화되면, 여러 평가지표를 복합적으로 쓸 수 있다. 가령, 평균적인 모델의 성능은 어느정도가 되어야하는지지표(average)와 지표를 넘는지 안넘는지 임계치(Threshold)로 계산해볼 수 있다. 가령 임계치는 모델 몇 MB이하여야한다 정도로 써볼 수 있다. 

 

베이스라인 모델 구하기


실제 ML프로젝트를 하다보면 무조건 SOTA(State of the Art)부터 가져와서 적용할라다보니까, 어렵다. 그리고, SOTA을 적용하다가 너무어려워서 다 따라가지도 못하고 접는 경우도 생긴다. 그리고 SOTA부터 적용하니까 어느정도가 적정한 모델 성능인지 파악하기 어렵다. 베이스라인 모델을 구해서 적용해보는 것은 위의 내용을 파악하는데 도움을 준다. 베이스라인 모델은 대강 우리의 모델이 어느정도 성능이 나올 것이다하는 바로미터로 써볼 수 있다. 예를 들어, 질병 추천시스템의 Top 5 Recall이 대충 선형회귀로 해도 80%가나오면 이문제는 어느정도 풀만한 모델이고, 잘만하면 90%도 나올 수 있다는 것을 알 수 있다. 

반응형

 

Grad-CAM의 FeatureMap의 기여도를 찾는 방법은 각 FeatureMap의 셀로판지가 최종 색깔에 어느정도 기여하는지와 동일하다.

 

요약


Grad-CAM은 CNN 기반의 모델을 해석할 때 사용되는 방법이다. 인공지능의 해석방법(XAI)에서는 Grad-CAM은 흔히 Post-hoc으로 분류되고(일단, 모델의 결과(Y)가 나오고 나서 다시 분석사는 방법)으로 취급된다. 또한, 딱 CNN에서만 사용되기 때문에 Model-specific 방법이다. 이 Grad-CAM의 가장 큰 쉬운 마지막 CNN이 나오고 나서 반환되는 Feature Map(본문에서:A)이 평균적으로 Y의 분류에 어느정도 되는지를 계산하고, 각 픽셀별로 이를 선형으로 곱하는 방법이다.

 

 

상세 내용


Grad-CAM은 아래와 같이 계산할 수 있다. 

식1: Grad-CAM의 핵심적인 계산과정

 

각, 기호에 대한 설명은 아래와 같다. y은 모델이 내뱉는 확률이며, c는 특정 라벨을 의미한다. 개 vs 고양이의 분류기이면 c의 최대값은 2이 된다(=1, 2의 분류). 그 다음, $A^k$은 각 Feature Map을 의미한다. Feature Map은 이미지가 CNN을 통과한 결과를 의미한다. $k$가 붙는 이유는 CNN을 통과하고나서 CNN의 필터의 개수(k)만큼 반환되는 값(feature map)의 k채널이 생기기 때문이다. 일종의 채널과 같다. 

마지막으로 $a_{k}^{c}$은 $A^{k}$의 필터맵이 이미지와 유사한데, 이 필터맵의 각 픽셀들이 분류에 어느정도 기여했는지를 의미한다. 아래의 그림(Figure 1)을 보자. Feature maps이 4개가 있으니 $k=4$인 예시이다. 녹색의 feature maps을 보면, 5x5로 이미지가 이루어져 있는데, 각 $i, j$에 해당하는 픽셀이 $y^{c}$에 어느정도 기여했는지를 구한 것이다.

 

Figure 1. GAP의 그림

즉, Grad-CAM은 각 feature Map이 어느정도 모델의 결과값($y$)에 기여했는지($A^{k}$)와 각 픽셀이 모델의 결과값에 어느정도 기여했는지($a_{k}^{c}$)을 곱하는 것이다. 결국, 각 픽셀이 feature maps을 고려하였을 때(가중하였을 때) 결과값에 어느정도 영향을 미쳤는지를 계산할 수 있다.

 

구현:


torch에서는 register_foward_hook과 register_backward_hook을 이용하여 grad cam을 계산할 수 있습니다. register_forward_hook과 register_backward_hook은 PyTorch에서 모델의 레이어에 대한 forward pass와 backward pass 중간에 호출되는 함수를 등록하는 메서드입니다. 이를 통해 레이어의 활성화 맵이나 그래디언트를 추출하거나 조작할 수 있습니다.

register_forward_hook:
이 메서드는 모델의 레이어에 대해 forward pass가 수행될 때 호출되는 함수를 등록합니다. 등록된 함수는 해당 레이어의 출력을 인자로 받아 다양한 작업을 수행할 수 있습니다. 주로 활성화 맵 등 중간 결과를 추출하거나 조작하는 데 사용됩니다.

def forward_hook(module, input, output):
    # module: 레이어 인스턴스
    # input: forward pass의 입력
    # output: forward pass의 출력
    pass

target_layer.register_forward_hook(forward_hook)

 

register_backward_hook:
이 메서드는 모델의 레이어에 대해 backward pass가 수행될 때 호출되는 함수를 등록합니다. 등록된 함수는 해당 레이어의 그래디언트를 인자로 받아 다양한 작업을 수행할 수 있습니다. 주로 그래디언트를 조작하거나 특정 그래디언트 정보를 추출하는 데 사용됩니다.

def backward_hook(module, grad_input, grad_output):
    # module: 레이어 인스턴스
    # grad_input: 입력 그래디언트
    # grad_output: 출력 그래디언트
    pass

target_layer.register_backward_hook(backward_hook)

 

위의 두 함수를 이용하여 아래와 같이 구현할 수 있습니다.

def grad_cam(
    model: torch.nn.Module,
    image: np.ndarray,
    target_layer: torch.nn.Module,
) -> np.ndarray:
    """
    Args:
        model (torch.nn.Module): Grad-CAM을 적용할 딥러닝 모델.
        image (np.ndarray): Grad-CAM을 계산할 입력 이미지.
        target_layer (Type[torch.nn.Module]): Grad-CAM을 계산할 대상 레이어.

    Returns:
        np.ndarray: Grad-CAM 시각화 결과.
    """

    def forward_hook(module, input, output):
        grad_cam_data["feature_map"] = output

    def backward_hook(module, grad_input, grad_output):
        grad_cam_data["grad_output"] = grad_output[0]

    grad_cam_data = {}
    target_layer.register_forward_hook(forward_hook)
    target_layer.register_backward_hook(backward_hook)

    output = model(image)  # 모델의 출력값을 계산합니다. y_c에 해당
    model.zero_grad()

    # 가장 예측값이 높은 그레디언트를 계산합니다. output[0,]은 차원을 하나 제거
    output[0, output.argmax()].backward()

    feature_map = grad_cam_data["feature_map"]
    grad_output = grad_cam_data["grad_output"]
    weights = grad_output.mean(dim=(2, 3), keepdim=True)
    cam = (weights * feature_map).sum(1, keepdim=True).squeeze()
    cam = cam.detach().cpu().numpy()

    return cam
반응형

+ Recent posts