Machine Learning을 시작한 지는 오래되었는데, 자주 나오던 likelihood 개념은 통 익숙하지 않았던 것 같다. 지금 와서 “아하” 하고 있으니 부끄러운 마음도 든다. 한번 이렇게 이해하고 지나가기보다는 부족하더라도 글로 남겨보고자 한다. 내용 자체는 “공돌이의 수학정리노트 - 최대우도법(MLE)”의 내용을 참고했다.
한국어로는 가능도 혹은 우도라고 불리는 likelihood는 likelihood function이라고도 부른다. 위키피디아의 likelihood function을 보면, likelihood function은 주어진 데이터가 어느 확률 분포와 추출되었는지에 대한 indicate function이라고 이야기한다. 즉, 추출된 데이터는 고정되어있는 상태에서 이 데이터가 어느 확률분포에서 왔는지 계산했을 때, 어느 확률분포에서 왔는지에 대한 유사도가 수치로 나타나는게 likelihood function이라고 이해하면 되겠다.
likelihood function을 수식으로 나타내면 아래와 같다.
다음과 같이 5개의 데이터를 얻었다고 가정하자.
이때, 아래의 그림을 봤을 때 데이터 는 주황색 확률분포와 파란색 확률분포 중 어떤 곡선으로부터 추출되었을 확률이 더 높을까?
눈으로 보기에도 파란색 확률분포보다는 주황색 확률분포에서 이 데이터들을 얻었을 가능성이 더 커 보인다. 왜냐면 획득한 데이터들의 분포가 주황색 확률분포의 중심에 더 일치하는 것처럼 보이기 때문이다. 이러한 해석은 우리의 직관적인 해석이고, 이를 수학적으로 표기한 것이 likelihood function이라고 보면 된다. 파란색의 확률분포와 주황색의 확률분포에서 데이터 에 대한 likelihood function의 값은 주황색 확률분포가 더 크게 나올 것이다.
수치적으로 이 가능도를 계산하기 위해서는 각 데이터 샘플에서 후보 분포에 대한 높이(즉, likelihood 기여도)를 계산해서 다 곱한 것을 이용할 수 있다. 계산된 높이를 더해주지 않고 곱해주는 것은 모든 데이터의 추출이 독립적으로 연달아 일어나는 사건이기 때문이다. 그렇게 해서 계산된 가능도를 생각해볼 수 있는 모든 후보군에 대해 계산하고 이것을 비교하면 우리는 지금 얻은 데이터를 가장 잘 설명할 수 있는 확률분포를 얻어낼 수 있게 된다.
이를 수학적으로 표기하면 아래와 같다.
Stable Diffusion에 대해서 공부하다보니 마르코프 체인이 나와서 살펴봐야할 것 같아 정리한 글이다.
마르코프 체인 관련된 블로그 포스팅 등 여러 글들을 참고하여 정리하였다.
마르코프 체인은 ‘현재 몇 가지의 선택지가 있고, 과거 비율을 충분한 회수로 확인할 수 있다면 미래에 특정한 선택지를 고를 확률에 대해서도 표현할 수 있지 않을까’라는 고민에서 출발했다.
쇼핑몰을 운영하는 기업은 특정 조건의 고객군이 로그인 후 어떤 검색을 거쳐 장바구니에 넣고 최종 결재를 하게 되었는지, 그리고 해당 조건의 고객군이 미래에 어떠한 선택을 하게 될 것인지 궁금해할 것이고, 이는 마르코프 체인을 활용하여 풀 수 있을 것이다.
마르코프 체인이란 마르코프 특성(Markov Property)를 지니는 이산시간(discrete time) 확률과정(stochastic process)이라고 정의한다.
즉, 과거와 현재 상태 모두를 고려했을 때, 미래 상태가 나타날 확률과 현재 상태만을 고려했을 때, 미래 상태가 발생할 확률이 동일하다. 이를 수식으로 표현하면 아래와 같다.
$$
P[s_{t+1}|s_{t}] = P[s_{t+1} | s_1, \cdots, s_t]
$$
마르코프 프로세스는 과거 상태를 기억하지 않기 때문에 메모리 리스(memoryless)프로세스라고 불리며, 마르코프 체인(Markov chain)이라 불리기도 한다.
어떤 상태에서 다음 단계의 상태로 변화하는 것을 변이(transition)라고 하고, 그 확률을 상태 변이확률(state transition probability)라고 한다. 시간 $t$에서의 상태를 $s$라고 하고, 시간 $t+1$에서의 상태를 $s^{‘}$이라고 할 때, 상태 변이확률은 아래 식과 같이 표현할 수 있다.
마르코프 체인 중 $$N$$차 마르코프 체인이 있는 듯하다. 이 경우를 “메모리 k 마르코프 체인”이라고도 부른다.
$$
r=0,\ P(o_t|o_{t-1}o_{t-1}\cdots o_{1}) = P(o_t) \
r=1,\ P(o_t|o_{t-1}o_{t-1}\cdots o_{1}) = P(o_t|o_{t-1}) \
r=2,\ P(o_t|o_{t-1}o_{t-1}\cdots o_{1}) = P(o_t|o_{t-1}, o_{t-1}) \
$$
$$
P_{ss^{‘}} = P[s_{t+1}=s^{‘}| s_{t}=s]
$$
마르코프 체인을 활용해 오늘 날씨를 통해 내일의 날씨를 확률적으로 예측하고, 다시 내일의 날씨 정보에 기반해서 모레의 날씨를 예측하는 행위를 충분히 반복했다고 가정하자. 날씨에 관한 확률의 묶음이 특정 성질을 반복할 때, 반복 계산의 어느 지점에서 날씨가 흐릴지, 비가 올지, 맑을지에 대한 확률이 특정하게 수렴하게 될 수 있다. 따라서 마르코프 체인은 오늘 흐림이라서 내일은 무조건 비가 아니라 내일도 흐릴 확률은 어느 정도인지, 혹은 맑거나 눈이 내릴 확률은 어느 정도인지 확률적으로 표현하게 된다.
문제를 간소화하여 맑음, 흐림 2가지의 조건만 활용해서 마르코프 체인을 계산해보자.
위 확률값을 상태 전이도(State Transition Diagram)로 표현하면 아래와 같다.
이는 아래와 같이 전이확률 행렬로 표현할 수 있다.
위의 전이 흐름도는 아래와 같이 행렬 곱을 통하여 계산할 수 있다. 모레의 확률 변화는 오늘과 내일의 전이가 연속되어 일어나는 경우이기 때문에 전이행렬을 곱하여 계산한다.
만약 지난 3년간 특정 일의 날씨 중 80%가 맑았다면 특정일 기준 모레가 맑을 확률은 0.8 x 0.565 + 0.2 x 0.362 = 0.524이기 때문에 52.4% 확률로 예측할 수 있게 된다.
이러한 상태에서 충분히 많은 횟수를 반복한다면 어느 순간에는 전이행렬이 변하지 않는 상태가 오는데 이를 두고 **안정상태(steady state)**라 부르고, 확률이 직전 상태와 동일하게 수렴하게 된다. 이러한 확률 분포를 **정적 분포(Stationary Distribution)**라고 부른다.
현재 상태를 알고 있고 전이 확률행렬을 알고 있다면, 다음 단계의 상태도 알 수 있을 것이다. 마찬가지로 다음 단계의 상태를 구했고 전이 확률행렬을 이미 알고 있으니 그다음 단계의 상태도 알 수 있을 것이다. 이를 식으로 나타내면 아래와 같다.
$$
P^{n}{ij} = [P{ij}]^{n}
$$
$P_{ij}$는 상태 $i$에서 $j$로 가는 상태전이 확률이고, $n$의 의미는 $n$번째 상태이다. 만약 $n\rightarrow \infty$라면 아래와 같은 수식이 성립한다.
$$
\pi = \pi P
$$
이때, $\pi$는 각 상태의 확률분포이고 $P$는 전이행렬이다. 이 상태에서 다음 상태를 알기 위해 전이행렬을 더 곱한다 해도 같은 값을 유지하는데, 이를 **안정상태(Steady state)**라고 하고 이때의 확률분포를 **정적분포(Stationary distribution)**라고 한다.
제품은 크게 2가지 유형이 있다.
“1 to N”은 “1 to 0”에서 시장 적합성을 찾은 제품을 확장하는 단계이다.
“1 to N” 전략은 다른 회사들이 모방하기 어렵게하는데 초점이 맞춰져있다.
사이드 프로젝트에서 프론트 페이지만 만들고 백단은 모두 수동으로 진행해서 실제로 유저들이 사용하는지 테스트를 해봤었는데(컨시어지 MVP), 개인적으로 제일 어렵게 느껴졌던 것은 시장 적합성(PMF)를 찾았는지 어떻게 알지? 였던 것 같다. 예를들어 돈을 태워 마케팅을 진행했는데, N명에게 노출되고 0.1% 정도의 극소수 유저만 전환(실 구매)가 일어났는데 이게 적합한지 아닌지 알기 어렵다는 것이었다. 아마 유저획득 비용관점이 너무 크고, 전환율도 좋지 않아서 PMF는 못찾았다고 볼 수도 있을 것 같다. 관련해서 Superhuman이 PMF를 찾는 엔진을 구축한 방법이라는 아티클을 참고해봐도 좋을 것 같다.
해당 아티클을 보면 일단 꽤나 많은 유저들을 확보한 상황에서 해당 제품이 가치가 있는지를 판단한 것이라, 어느정도 초기부터 전환유저가 있었던 것으로 보인다. 즉, 제품관점에서 유저들의 반응성은 어느정도 이끌어냈고, 지속적으로 PMF를 맞춰가는 여정처럼 보여서 내가 원하는 대답은 아니었던 것 같다. 아티클에서 언급하는 지표도 후행지표이기 때문에 제품을 런칭하기 전에 PMF를 찾고 들어가야한다고 이야기하신 분도 계셨다.
]]>AC2 그룹 내에서 홍영기님의 Personal OKR 세션이 있어, 냉큼 신청해서 듣고왔습니다.
그 이후에, AC2 그룹 내에서 서로 Personal OKR을 리뷰해주고 하는 활동들이 있었는데, 당시에는 정신이 없던 시기라 부담이되어 참여하지는 않았는데요.
이후에 흘러가는 삶의 패턴을 지켜보다보니, 의식해서 사는 삶이 아니라 흘러가는 삶을 살 것 같다는 두려움이 들었습니다. 그래서 어설프겠지만, 저만의 지표를 세워볼겸 Personal OKR을 작성해봤습니다.
OKR을 작성할 때, 김창준님의 영리하나 열정이 없다 라는 글을 참고했습니다. 그 외에 창준님의 조언도 참고했습니다.
예를 들어 OO님의 “내 블로그 글 중 5개가 조회수 1000회, SNS 리액션 150회, 인용 50회를 달성한다”(A)를 갖고 생각할 때, 이렇게 상상해 보는 겁니다.
- A를 완벽히 달성했지만 불만족하는 경우를 상상해 본다. 적어도 3가지.
- A를 거의 달성하지 못했지만 만족하는 경우를 상상해 본다. 적어도 3가지.
그 후에 A를 수정한다. 만약 A에 큰 수정이 일어난다면 내가 처음 만든 A는 초점이 잘못된 것일 확률이 높다(특히 측정하기 쉬운 걸 측정하게 되는 경향면에서).
[목표]
[이 목표가 중요한 이유]
[목표를 이루기 위해 필요한 결과(Key Result)]
완벽히 달성했지만 불만족하는 경우를 상상해 본다. 적어도 3가지.
A를 거의 달성하지 못했지만 만족하는 경우를 상상해 본다. 적어도 3가지.
경제나 돈에 대해서 더 이상 노동자의 관점이(돈 많은게 짱이야. 집있는게 짱이야?) 아니라 새로운 관점을 갖고있다.
AS IS: 그냥 무지성 투자. 부동산도 그냥 하자고하는데로.. 내가 전문가는 아니니까..
TO BE: 기업에 대해서, 투자에 대해서, 현금 흐름에 대해서 대략적으로 거시적인 나만의 관점을 가지고있다.
2~4 Quarter는 아직 먼 미래라서 별도의 OKR을 적어두진 않았다. 어차피 2022년 OKR이 있으니…
4월에 1 Quarter가 끝나고 회고를하면서 2, 3 Quarter를 작성할까 한다.
[목표]
[목표가 중요한 이유]
[목표를 이루기 위해 필요한 결과(Key Result)]
[목표]
[목표]
[목표]
오늘 리뷰할 논문은 Google Play Store에 적용된 논문 “Wide & Deep Learning for Recommender Systems”입니다.
리뷰 내용에는 제 나름대로 해석하고 의역한 내용이 많습니다.
논문에서는 추천 시스템을 search ranking system이라는 관점에서 보고있습니다.
즉, 입력은 사용자 정보와 맥락(contextual) 정보이고, 출력은 랭킹화된 아이템들의 리스트라는거죠.
추천 시스템을 search ranking system이라는 관점으로 해석한다면, 당연하게도 일반적인 search ranking system이 해결하고자하는 문제도 따라오게됩니다. 논문에서는 이를 “memorization”과 “generalization”이라고 주장합니다.
“memorization”은 과거 데이터에서 나타나는 특징(feature)와 아이템(items)의 상관관계를 학습하는 것을 의미하고, “generalization”은 과거 데이터들의 상관관계를 기반으로 한번도 본적이 없던 데이터 패턴에서 특징과 연관될 수 있는 아이템들을 추천해주는 것을 말합니다. 논문에서는 이를 “transitivity of correlation”이라고 이야기합니다.
논문에서는 “generalization”은 “memorization”과 비교했을 때, 추천 시스템의 다양성을 증가시켜주는 경향이 있다고 주장하는데요. 논문의 저자들은 “generalization”이 추천 시스템에서 매우 중요하다라고 주장하는 것으로 이해했습니다.
산업계에서 대규모 온라인 추천 시스템, ranking system은 간단하면서 해석이 가능하고, 확장성이 좋은 linear model을 폭넓게 쓰고있습니다.
“memorization”은 sparse feature들을 cross-product transformation해서 효율적으로 달성이 가능하다고합니다.
예를들어 “user_installed_app=netflix AND impression_app=pandora“일 경우에 값을 “1“로 맵핑하는 것이죠. 이는 사용자가 이미 Netflix를 설치했고 그 이후에 “pandora“라는 앱을 본 것을 의미합니다.
즉, “user_installed_app=netflix“, “impression_app=pandora“라는 두 가지 feature를 가지고 하나의 feature를 새로 만드는 것이죠. 이렇게 cross-product transformation로 새로 만들어진 feature는 “user_installed_app“, “impression_app“라는 feature pair가 target label과 얼마나 상관관계가 있는지 직접적으로 설명할 수 있습니다.
“memorization”에서 “generalization”은 덜 세분화된 feature를 사용하여 추가할 수 있습니다. 예를들어 “user_installed_category=video AND impression_category=music“ 형태로 사용할 수 있죠. 다만, 이 경우에는 직접 raw data에서 이러한 cross-product transformation을 해서 새로운 feature table을 만드는 것 과 같은 feature engineering을 해줘야할 수 있습니다. 하지만 cross-product transformation은 학습 데이터에서 나타나지 않았던 query item - feature pair에 대해서는 generalization을 할 수 없다는 단점이 있습니다.
deep neural network나 factorization machine과 같은 embedding-based model은 낮은 차원의 query - item feature에 대한 dense embedding vector를 학습하여 이전에 보지 못했던 query-item feature pair를 일반화할 수 있습니다. embedding-based model은 상대적으로 feature engineering에 대한 부담이 적다는 장점이 있습니다.
아마 이는 상대적으로 cross-product transformation에 대한 부담이 크게 줄어서 그렇게 주장하지 않았을까 싶습니다.
하지만 embedding-based model은 query-item matrix가 고차원이고(rank가 높고) sparse한 경우 저차원에 대한 표현을 효과적으로 학습하기 어렵다는 단점이 있습니다. 예를들어 특정 선호도를 가진 사용자인데, 여러 사용자 그룹에서는 그 비율이 매우 작은 경우가 고차원이면서 sparse한 query-item matrix를 가지는 예가 될 수 있습니다. 이런 경우에는 대부분의 query pair들과 상호작용을 없게해줘야하지만, 그렇게 했을 경우에 모든 query pair에 대해서 0이 아닌 예측값을 출력하기 때문에 과하게 일반화가 되어 관련이 없는 추천 아이템을 추천해줄 수 있습니다. 반대로 linear model에서는 이러한 예외적인 문제를 더 작은 parameter로 기억할 수 있습니다.
저는 특정 분야에 선호도가 강한 매니악한 유저그룹의 경우 대부분의 유저 세그먼트에 비해서 비율이 낮지만 비지니스적으로는 유효할 수 있어서 잘 챙겨줘야한다는 이슈가 있다고 생각했습니다.
이런 경우에는 데이터 자체가 imbalance해서 함께 학습되면 학습이 안되어 따로 떼어내서 학습하거나 다른 lable에 영향을 덜 받는 방향으로 학습되도록 해야한다고 생각했습니다. 하지만 그럼에도 저차원에서 표현 학습이 잘 안되게되면 추천하는 item자체가 실제 사용자가 원하는 추천 아이템이 아닌 무작위의 item들을 추천해줄 확률이 높다고 생각했고, 논문에서는 이를 over-generalization이라고 표현했다고 생각합니다.
딥러닝 아키텍쳐 자체는 매우 간단한 구조라서 아래 그림으로 설명이 충분한 것 같습니다.
모델 예측값은 아래 수식에 의해 결정됩니다.
$$P(Y=1|x)=\alpha(w^{T}_{wide}[x, \phi(x)] + w^{T}_{deep} a^{l_{f}}+b)$$
wide component는 $y=w^{T}x+b$ 형태의 일반화된 모델입니다.
이 때, feature는 raw input과 transformed를 포함합니다. 여기서 가장 중요한 transformation은 cross-product transformation입니다.
$$\phi_{k}(x) = \prod^{d}_{i=1}x^{c_{ki}}_{i} \ \ \ \ c_{ki} \in {0, 1}$$
deep component에서는 카테고리 feature를 다룰 때, embedding vector를 사용하면 되겠다는 생각이 들었습니다.
나머지 부분들은 아래 수식을 보고는 그냥 MLP라고 생각하면서 봤습니다.
$$a^{(l+1)} = f(W^{(l)}a^{(l)} + b^{(l)})$$
label은 app acquisition으로 impressed app이면 1이고 그렇지 않으면 0입니다. 아마 구글에서는 app을 설치했을 경우 “impressed_app=1“이라고 표기했나봅니다.
카테고리 feature는 정수로 구성된 id 공간을 mapping해서 사용했다고하는데, 저는 “male=0“, “female=1“ 이런 형태로 맵핑했다고 이해했습니다. 지금 이 논문을 구현한다면 저는 아마 NLP쪽의 embedding layer를 쓰면 되지 않을까 생각해봅니다.
continuous real-valued feature는 누적분포함수에 맵핑해서 $[0,1]$로 정규화했다고 합니다.
정확히 어떤 방식으로 했는지는 아직 감이 잘 안오네요.. min-max normalization을 하더라도 누적분포함수를 보존할 것 같은데… 그냥 이를 말로 길게 풀어놓은 것 같습니다. 만약에 min-max normalization이라면, unseen continous real-valued feature가 max값 보다 높아질 수 있는 가능성이 있는데, 이는 어떻게 처리했는지 궁금하네요.
모델 학습 부분은 크게 눈에 띄는 부분이 없어서 생략했습니다.
궁금하신 분들은 논문을 보시면 좋을 것 같습니다.
저는 실험결과에서 아래 테이블에서 보여주듯이 AUC에서는 Wide, Deep, Wide & Deep 모두가 비슷하지만, 실제 Online에서는 Acquisition Gain이 높았던게 재밌었습니다.
서빙쪽은 크게 개선되었다고 써놓았는데, 단순 쓰레딩처리로 큰 novelty는 없어보여 사진이나 설명을 별도로 첨부하진 않았습니다.
글쓰기에 앞서 본 포스팅은 제가 Ray Datasets를 이해하고자 쓰는 글입니다.
글의 구성은 1). Ray Datasets에 대한 공식문서 번역, 2). parquet 파일을 Ray Dataset으로 활용하는 방법에 대해서 소개하려고 합니다.
Ray Dataset은 Ray 라이브러리와 애플리케이션에서 데이터를 불러오고 교환하는데 사용하는 기본적인 방법입니다. Datasets는 map.filter
, repartition
과 같은 분산 데이터 변환을 기본적으로 제공하고, 다양한 데이터 포맷, 데이터 소스, 분산 프레임워크와 호환됩니다.
Ray Datasets는 Distributed Arrow의 구현체입니다. Dataset은 block
을 참조하고있는 Ray object의 list로 구성되어있습니다. 각 block은 Python list나 Arrow Table을 가지고있습니다. Dataset에 다수의 block이 있다면, 데이터 병렬 변환 및 수집이 가능합니다.
다음은 3개의 Arrow table block를 갖는 Dataset을 시각화한 그림입니다. 여기서 각 block은 1,000개의 row를 가지고있다고 가정했습니다.
Ray Dataset은 Ray object reference들을 모아놓은 list일 뿐이므로, Ray tasks, actor, 라이브러리 간에 자유롭게 전달할 수 있습니다. 이러한 유연성은 Ray Dataset의 고유한 특징입니다.
Spark RDDs나 Dask Bags과 비교했을 때, Datasets은 조금 더 기본적인 feature의 집합을 제공하고 단순함을 위해 작업(operation)을 즉시 실행(eagerly)합니다. 이는 사용자가 Datasets를 다른 dataframe type(ds.to_dask()
)으로 casting할 수 있도록 의도한 것입니다. 다른 dataframe type으로 cating하게되면, 특정 dataframe type에서만 사용할 수 있는 고급 operation을 사용할 수 있게됩니다.
호환되는 Datasource 리스트는 링크에서 확인할 수 있습니다.
ray.data.range()
와 ray.data.from_items()
를 이용해서 생성된 데이터로 Ray Datasets를 만들어볼 수 있습니다. 이 때, Datasets는 Plain Python object나 Arrow records를 갖게됩니다.
1 | import ray |
Datasets는 local disk의 파일이나 S3와 같이 원격 datasource로부터 만들 수도 있습니다. pyarrow가 지원하는 filesystem이라면 특정한 파일 위치를 사용하면 됩니다.
1 | # Read a directory of files in remote storage. |
마지막으로, Ray object store에 있거나 Ray와 호환되는 Distributed DataFrame에 있는 데이터로부터 Dataset을 만들 수 있습니다. 아래 예제는 pandas
의 DataFrame
과 dask
의 dataframe
을 Ray Dataset으로 변환하는 예제입니다.
1 | import pandas as pd |
Datasets는 .write_csv()
, .write_json()
, .write_parquet()
API를 통해 local이나 remote storage에 저장할 수 있습니다.
1 | # Write to csv files in /tmp/output. |
또한 Dataset을 Ray와 호환되는 Distributed DataFrames로 변환할 수 있습니다.
1 | # Convert a Ray Dataset into a Dask-on-Ray DataFrame. |
Datasets는 .map()
을 사용하면 병렬적으로 변환작업을 수행할 수 있습니다. 변환(Transformation)은 즉시(eagerly) 실행되며 작업(operation)이 끝날 때까지 blocking됩니다. Datasets는 .filter()
, .flat_map()
을 지원합니다.
1 | ds = ray.data.range(10000) |
벡터화 함수(vectorized function)의 장점을 취하고 싶을 때에는 .map_batches()
를 사용할 수 있습니다.
filter
,flat_map
을.map_batches()
를 통해 구현할 수 있습니다. 이 때, map function은 특정 크기를 갖는 batch 출력을 반환해야합니다.
1 | ds = ray.data.range_arrow(10000) |
변환은 기본적으로 Ray tasks를 사용해서 실행됩니다. 설정이 필요한 변환의 경우 compute="actors"
를 지정합니다. compute="actors"
를 설정하면, Ray는 autoscaling actor pool을 사용하여 변환작업을 실행합니다.
1 | # Example of GPU batch inference on an ImageNet model. |
Datasets는 Ray tasks나 actor에 전달할 수 있고 .iter_batches()
나 .iter_rows()
를 통해 읽어들일 수 있습니다. 이 때, 읽기 작업은 복사를 수행하는 것이 아니라 block들의 reference가 담긴 Ray objects로 전달합니다.
1 |
|
Datasets는 sub-datasets로 분리할 수 있습니다. 원하는 분할 개수와 actor의 handle을 split()
함수에 전달하면 Locality-aware 분리를 수행할 수 있습니다.
1 |
|
Datasets는 python에서 정의된 custom datasource을 사용해서 병렬적으로 read/write작업을 수행할 수 있습니다.
1 | # Read from a custom datasource. |
용량이 매우 큰 parquet 파일들을 읽어들여 Ray tasks에 전달하여 작업해야한다고 했을 때, 이를 Datasets로 처리할 수 있습니다.
json data를 pandas의 DataFrame으로 읽어들이고 이를 sample.parq
이름으로 저장합니다.
1 | import json |
Ray의 Dataset을 batch로 처리하는 actor class를 정의합니다.
1 | import ray |
이전에 저장했던 sample.parq
을 Ray Dataset으로 만듭니다.
1 | from pathlib import Path |
locality-aware을 사용해서 Datasets을 split해줍니다.
1 | workers = [Worker.remote(i) for i in range(10)] |
아래 코드를 실행하여 split한 데이터를 처리합니다.
1 | ray.init() |
1 | import json |
먼저, 글을 풀어내기 전에 제가 쓴 글이 읽는 분들께 어떻게 읽힐지 예측이 안되기 때문에 두려운 마음이 큽니다. 마치 살얼음이 깨질까 까치발을 들고 얼어붙은 호수를 건너가는 기분이랄까요? 그럼에도 불구하고, 먼 미래의 나를 위해, 중간지점을 찍어두는게 좋을 것 같다고 생각하기에 조심스럽게 적어봅니다.
저는 2021년 8월 31일부로 퇴사하게되었습니다. 입사한지 3년 7개월만이네요.
퇴사를 결심했다면, 결심한 저 나름의 이유가 있었던 것 같습니다. 그래서 그 이야기들을 해볼까 합니다.
원래 저는 AI 스타트업에서 근무 중이었습니다. 했던 역활은 사업 아이디어들을 실현하기 위한 기술을 제안하고 구현해나가는 것 이었습니다. 사업을 만들어나가는 과정이 고되긴 했지만, 과정 자체를 경험하는 것은 재미있었습니다. 아쉬움이 있다면 딥러닝 엔지니어가 저 혼자뿐이라는 것 정도….?
그러던 와중에 모두의 연구소라는 곳을 알게되었고, 자율자동차에서 사용되는 영상처리 기술에 대해서 공부하는 연구실에 들어갔습니다. 연구실 사람들과 논문을 읽고 학술적인 논의를 하면서 “함께 무언가를 한다는 것이 이렇게 재밌을 수 있구나”라는 것을 느꼈던 것 같습니다.
어느날, 함께 공부하던 연구원 분 중 한 분이 자신이 일하는 연구소에서 자율차 연구를 하고있고 차량이 준비될 예정인데, 함께하지 않겠냐는 감사한 제안을 주셨습니다.
모두의 연구소의 즐거웠던 경험이 업무로 확장되어 재밌게 일할 수 있겠다는 생각도 있었고, 연구소 환경은 어떤지, 그리고 연구소의 환경은 어떤지 호기심이 들어서 이직하게되었습니다.
지금 회사로 이직하면서, 저는 몇가지 목표를 세웠습니다.
“1. 3년 이상 근속”은 잦은 이직이 제 커리어게 긍정적이지 않겠다는 생각과 3년 정도는 버틸 수 있는 사람인지 확인해보고자했고, “2. 능력을 인정받아보자”는 대체 불가능한 사람을 꿈꿨기 때문에 그랬던 것 같습니다.
하지만 입사하고 난 후, 1년 안에 팀이 공중분해되어 다른 부서로 발령이 났습니다.
발령된 부서는 클라우드 및 인프라 기술을 연구하는 팀이었고, 연구 주제는 AI 지원하는 인프라 기술이었습니다. 지금은 그걸 MLOps라고 부르죠. 특이사항으로는 옆에 있는 헬스케어팀에서 필요한 인공지능 기술을 연구하고 개발해야한다는 점이 있었습니다.
당시에는 이직을 해야하는가에 대해서 격정적인 고민 사이에 있었는데요. 인프라 기술을 경험해보고싶다는 생각이 들어서 계속 근무하게 되었습니다.
돌이켜보면 리서치 엔지니어만으로는 무언가를 이루기 어렵겠다는 생각이 지배적이었던 것 같습니다. 부족하겠지만 필요하다면 대부분의 것들을 혼자 해볼 수 있는 사람이 되고싶었습니다.
연구소에서 3년 6개월 동안 일하면서 아래 항목들을 배운 것 같습니다.
항목들의 순서는 제가 생각했을 때, 저에게 도움이 많이 되었던 경험 순서대로입니다.
이 중에 제일 받아들이기 어려웠던 것은 “1. 다른 사람들의 삶을 존중하는 태도” 였네요.
“1. 다른 사람들의 삶을 존중하는 태도”를 못했을 때, 치명적인 단점은 제가 동료들에게는 함께 일하기 싫은 사람이 된다는 것입니다. 저는 “무릇 개발자란 이래야한다~”라는 이상적인 모양이 있었던 터라 다른 “개발보다는 개인의 삶이 중요했던”사람들을 이해하기 어려웠었네요.
아마 대학교때부터 개발을 놀이처럼 하기도 했었고, 늦깎이 비전공자 출신이라 도태될까하는 두려움에 여유시간 대부분을 자기계발에 쏟는 삶을 살아와서 그랬을 수 있습니다.
아이러니하게도 다양한 삶에 대한 이해나 필요성이 아주 강력하게 각인되는 계기가 있었는데, 업무를 과하게하다가 병이 생기면서 제 삶의 가치관에 대해서 다시 한 번 생각해봤던 것 같습니다.
아쉬운 점이라고하면!… 퇴사하게 된 계기와도 맞물리는 것이겠죠?.
퇴사를 결심하게 된 계기는 더 이상 새로운 것에 도전적이지 않은 사람이 되어가는 제 모습을 알아차렸을 때였던 것 같습니다.
도전적이지 않게 된 이유는 초기에 조직의 변화를 만들어내는 시도에서 왔던 좌절감도 있었을 것이고, 업무 부하가 강해짐에 따라서 스스로를 살필 수 있는 심적 여유가 사라졌던게 제일 큰 것 같습니다.
주변의 다양한 일들로부터 한발 떨어져서 어떤 상황이고, 이 상황에서 내가 해볼 수 있는 시도나 선택할 수 있는 선택지가 무엇이 있는지 전략적으로 살펴볼 수 있어야했는데, 심적여유가 많이 사라지다보니 새로운 일들이 들어오거나 변화가 발생하면 일단 저항하거나 방어하고있는 모습을 발견하게 되었습니다.
처음에는 시간이 지나면 나아질 것이라고 생각했던 것 같은데, 실제로는 시간이 지나면 지날 수록 스스로 부정적인 감정을 감당하기 어렵다는 생각이 들었습니다.
일정조정, 업무조정, 회사와의 대화와 같은 시도에서 타협점을 찾기가 어렵다는 생각이 들었고, 그렇게 저는 퇴사를 결심하게 되었습니다.
지금의 저는 퇴사를 하고 천천히 지난 3년 7개월을 돌아보는 시간을 가지고있습니다. 일하느라 바쁘다는 핑계로 돌보지 못했던 제 삶의 다른 영역도 돌보고있고요.
제가 다음 회사를 가서 개인적으로 해보고싶은 것을 한 문장으로 표현하자면 “나의 감정 상태와 내가 선택할 수 있는 선택지들을 큰 시각에서 살펴보고 선택할 수 있는 힘을 기르는 것”이 될 것 같습니다.
저는 퇴사하기 직전에 AC2과정을 수강하고있었는데요. AC2에서 들었던 1). “마인크래프트로 하는 협력 시뮬레이션”, 2). “KAI 점수에 따른 의사결정 양상”, 3). “마인드 리딩”, 4). “CTA”, 5). “퍼실리테이션”과 같은 워크샵들은 잘 몰랐던 제 모습을 적나라하게 보여준다는 측면에서 굉장히 충격적이었어요. AC2에서 들었던 워크샵 덕분에 저는 특정 상황에서 트리거링되는 제 감정상태와 무의식적인 선택패턴들을 살펴볼 수 있었습니다.
이후, 남은 AC2과정에서 저는 코치님, 멘토님과 함께 저의 무의식적인 패턴을 알아차리고 의식적으로 다른 선택을 하는 변화를 시도하는 연습을 하게되었는데요. 이런 시도들이 회사에서도 충분히 작동한다는 것을 느끼게되면서 저는 이를 점점 더 발전시켜나가고 싶었습니다. AC2에서 배운 전략들은 다른 부수적인 것들을 모두 포함하고있는 상위수준의 전략이라고 느꼈기 때문에 제가 달성하기 원하는 목표들은 부수적으로 달성되지 않을까 생각했습니다.
대충 꾸준히 해볼 수 있는 것들은 아래정도이지 않을까 싶습니다.
결론적으로 현재 회사를 입사하면서 세웠던 목표들은 대략적으로 달성했습니다.
세부적으로는 여러 포인트에서 “이 때, 이렇게 했더라면 어땠을까?”라는 아쉬움이 많은게 사실입니다.
(글은 뻔지르르하게 썼지만, 우아하지 않았어요…)
부정적인 감정을 잘 다스리고 회사에 더 머물렀다면, 어쩌면 정년까지도 보장이되는 안정적인 직장생활도 이룰 수 있었을 것이라고 생각합니다.
그래도 저는 아직까지는 야생에서 어떻게든 살아남아서 “생존력”이 있는 사람이 되고싶은 열망이 있습니다. 그래서 어떻게 보면 스스로를 차디찬 야생에 집어던지는 짓(?)을 하지 않았을까… 라고 생각합니다.
어렸을 때의 불꽃이 많이 사그러들긴했지만, 아직까지는 어렸을 때의 열망들을 잘 간직하고 당시에 상상하던 미래의 나의 모습이 실제로 이뤄질 수 있게 계속 노력했으면 좋겠습니다.
]]>최근 셋팅한 제 회사 컴퓨터에는 RTX-3090이 장착되어있습니다. RTX-3090을 장착한 김에 딥러닝 모델의 Hyperparameter search를 제 컴퓨터에서 수행하였는데요. 매번 프로그램을 실행할 때마다 GPU is lost
라는 에러를 출력하고 프로그램이 중단되곤했습니다. 이 에러가 나오면 더 이상 OS에서 GPU에 접근할 수 없기 때문에 매번 OS를 재부팅해야하는 번거로움이 있었습니다.
1 | $ python3 train.py |
이 에러를 마주하고나서는 제가 세운 가설은 다음과 같았습니다.
컴퓨터에 장착된 파워 서플라이의 용량이 GPU가 장착된 컴퓨터의 전력 수요를 맞출 수 없다면, nvidia-smi
에서 GPU가 사용하는 전력량을 제한할 수 있습니다.
GPU의 발열이 심한 경우에도 GPU가 사용하는 전력량을 제한하여 온도상승을 제한할 수 있습니다.
GPU가 사용할 수 있는 전력량을 제한하는 명령어는 아래와 같습니다.
1 | # -pm --persistence-mode= |
먼저 nvidia-smi
명령어를 통해서 nvidia 커널 모듈이 지정된 GPU에 대해서 항상 활성화 상태가 되도록 강제합니다. nvidia 커널 모듈은 그래픽 GPU가 작동하기 위해 필요한데, X윈도우 상태이거나 CUDA 애플리케이션이 작동할 때에 커널 모듈이 활성화됩니다. X윈도우가 종료되거나 CUDA 애플리케이션이 더 이상 작동하지 않는 경우에는 커널 모듈이 비활성화 상태가 됩니다.
nvidia-smi
명령어를 통한 전력제한 옵션은 nvidia 커널 모듈이 활성화되어있을 때만 적용됩니다. 즉, nvidia 커널 모듈이 활성화된 상태에서 전력제한이 적용되지만 비활성화된 상태에서는 전력제한이 풀려서 원래의 값으로 복원되게 됩니다. 전력제한이 동적으로 적용되는 것을 방지하기 위해서 persistence mode를 적용하여 nvidia 커널 모듈을 활성합니다.
RTX-3090에 전력제한을 하고, GPU와 CPU의 온도를 모니터링하면서 같은 에러가 발생하는지 테스트하였습니다.
GPU가 허용하는 온도는 아래의 명령어를 확인할 수 있습니다.
1 | $ nvidia-smi -q | grep -i temp |
GPU가 Shutdown되는 온도는 98도임을 확인할 수 있고, 권장 최대 온도는 83도임을 확인할 수 있습니다.
CPU 의 온도를 모니터링하기 위해서는 lm-sensor를 이용하였습니다.
1 | $ watch -n 0.1 sensor |
학습을 돌려놓고, 확인해본 결과 전력제한을 걸어 전력소모와 발열량을 줄였음에도 같은 에러가 발생하는 것을 확인하였습니다.
RTX-3090은 지금까지 나왔던 GPU 중에 제일 크고 무게가 있는 GPU입니다. 메인보드가 세로로 세워져있는 컴퓨터 케이스에 RTX-3090을 장착하게되면 PCIE slot에 장착된 GPU가 무게 때문에 내려앉습니다. 이로 인해 GPU가 잘 장착되지 않거나 메인보드에 PCIE 슬롯이 망가질 수 있습니다. 이를 방지하기 위해서 GPU 지지대를 사용하고있습니다만, 혹시 모르는 마음에 GPU를 탈착하고 재 장착하였습니다.
GPU를 재장착하고 테스트한 결과 같은 에러가 발생하는 것을 확인할 수 있었습니다.
문제에 대해서 계속 검색하다가 리눅스 부팅 메세지를 확인하는 것을 통해서 GPU의 상태를 추적할 수 있다는 사실을 알았습니다. 부팅메세지는 dmesg
명령어를 통해서 확인할 수 있었는데, 여기서 몇가지 키워드들을 확인할 수 있었습니다.
1 | $ dmesg |
저는 이 키워드들에 집중했고, 모종의 하드웨어 호환 이슈로 인해 발생하는 PCIE Bus Error의 문제는 매우 흔하고, 이를 kernel parameter를 수정하여 해결할 수 있다는 것을 확인하였습니다.
우분투는 /etc/default/grub
에서 이를 설정할 수 있습니다. 해당 파일을 열어 아래의 내용을 추가해줍니다.
1 | GRUB_CMDLINE_LINUX_DEFAULT="quiet splash pci=nomsi" |
해당 내용을 추가하였다면, sudo update-grub
으로 수정된 내용을 반영해주고 컴퓨터를 재부팅하면 됩니다.
저는 최종적으로 위 내용을 반영하고나서, 위의 에러가 다시 발생하지 않는다는 것을 확인하였습니다.
애플리케이션이 다국어를 지원할 수 있도록 docker image에 다른 locale을 설치하는 것이 필요할 수 있습니다. 올바른 locale이 설치되지 않는다면 텍스트 데이터가 올바르게 표현되지 않는 문제를 겪을 수 있습니다. alpine docker image는 locale
, locale-gen
명령어가 존재하지 않으며, apk add locale
로 설치를 시도할 경우 locale이라는 패키지가 없다는 에러를 확인할 수 있습니다. alpine에서 locale을 사용하기 위해서는 alpine과 호환되는 locale 패키지를 별도로 설치해줘야합니다.
(해당 포스팅에서는 명령어는 dockerfile을 기준으로 설명합니다.)
1 | FROM alpine:3.6 |
1 | RUN apk --no-cache add ca-certificates wget && \ |
위 명령어는 alpine linux를 지원하는 locale
패키지를 설치합니다. 해당 명령어를 실행하고나면, /usr/glibc-compat/bin
디렉토리에서 locale관련 명령어인 locale
과 localedef
를 확인할 수 있습니다.
locale
관련 패키지를 설치하고나면, 현재 시스템이 사용중인 locale과 사용 가능한 locale을 /usr/glibc-compat/bin/locale
명령어와 /usr/glibc-compat/bin/locale -a
로 확인할 수 있습니다. 이를 확인해보면, 내가 사용하고자하는 ko_KR.UTF-8
이 없음을 확인할 수 있습니다.
locale을 정의한 파일들은 /usr/glibc-compat/share/i18n/locales
폴더 아래에 있고, charmap(캐릭터맵)에 대한 정보는 /usr/glibc-compat/share/i18n/charmaps
폴더 아래에 있습니다. 이 두 가지 정보가 localedef
의 명령어로 컴파일 되며 컴파일을 하게되면 /usr/glibc-compat/lib/locale
폴더에 locale-archive
라는 접두사(prefix)를 가진 파일이 생성되게 됩니다.
ko_KR.UTF-8
locale을 사용하기 위해서 아래 명령어로 locale을 컴파일 합니다.
1 | RUN /usr/glibc-compat/bin/localedef -i ko_KR -f UTF-8 ko_KR.UTF-8 |
이제 locale 관련 패키지도 설치하였고 필요한 locale도 컴파일 해줬으니, 환경변수에 locale을 설정해봅시다.
1 | RUN /usr/glibc-compat/bin/localedef -i ko_KR -f UTF-8 ko_KR.UTF-8 && \ |
ubuntu는 기본적으로 bash shell을 사용하기 때문에, 환경변수를 로그인 쉘의 경우에는 ~/.bashrc
에 미 로그인 쉘에는 /etc/profile
에 설정합니다. alpine은 bash shell이 아닌 ash를 제공하는 busybox를 기본 shell로 사용합니다. 따라서 환경변수 설정은 로그인 쉘의 경우 ~/.profile
, 미 로그인 쉘은 /etc/profile
에 설정해야합니다.
(저의 경우는 미로그인 쉘을 사용했으므로 환경변수를 /etc/profile
에 설정하였습니다.)
설정을 완료하고나면, /usr/glibc-compat/bin/locale
와 /usr/glibc-compat/bin/locale -a
를 통해 locale이 ko_KR.UTF-8
로 잘 설정되었음을 확인할 수 있습니다.
1 | $ /usr/glibc-compat/bin/locale |
2021.05.05 현재 RTX3090은 CUDA11 이상을 지원하는 딥러닝 프레임워크에 버전에서만 사용할 수 있습니다. 하지만 단순하게 pip install torch==1.7.1 torchvision==0.8.2
형태로 설치하면 CUDA error: no kernel image is available for execution on the device
에러를 마주할 수 있습니다.
1 | $ python3 -m pip install torch==1.7.1 torchvision==0.8.2 |
이 때에는 반드시 pip install torch==1.7.1+cu110 torchvision==0.8.2+cu110 -f https://download.pytorch.org/whl/torch_stable.html
형태로 설치해주어야합니다.
(RTX3090을 지원하는 PyTorch는 python3.8이상을 요구한다고 하여 해당 예제는 python3.8에서 테스트하였습니다)
1 | $ python3 -m pip install torch==1.7.1+cu110 torchvision==0.8.2+cu110 -f https://download.pytorch.org/whl/torch_stable.html |
Definition 3.9 (Orthonormal Basis)
$n$차원의 벡터 공간 $V$과 $V$에서의 basis 를 생각했을 때, $\forall\ i,j=1,…,n$ 에서 다음을 만족하면 이를 orthonormal basis(ONB)라고 부릅니다.
만약에 (3.33)만 만족한다면 이 basis는 orthogonal basis라고 부릅니다. (3.34)는 모든 basis vector의 length나 norm이 1임을 의미합니다. 따라서 orthonormal basis라는 것은 basis가 서로 orthogonal하면서, length나 norm이 1인 basis를 말하는 것입니다.
Chapter 2.6.1의 내용을 다시 생각해보면, vector들의 집합으로부터 스팬된 vector space에서의 basis를 찾기 위해서 가우시안 소거법을 사용했었습니다.
우리에게 non-orthogonal이고 unnormalized basis vecotr의 집합인 이 주어졌다고 생각해보십니다.
이때, orthonormal basis를 얻기 위해서는 다음과 같은 과정을 반복합니다.
위와 같이 반복적인 작업을 통해서 orthonormal basis 를 구하는 방법을 Gram-Schmidt process라고 부릅니다.
]]>inner product는 벡터의 length나, 두 벡터 간의 distance를 정의하게 하는 것 외에도 두 벡터 간의 각도 $\omega$를 정의할 수 있습니다.
이때, 두 벡터 $x, y$ 사이의 inner product space에서 각도 $\omega$를 정의하기 위해서 우리는 Cauchy-Schwarz inequality (3.17)를 사용합니다.
Cauchy-Schwarz Inequality
$x \neq 0, y \neq0$을 가정할 때, Angle은 다음과 같습니다.
Cauchy-Schwarz Inequality에서 좌항의 절대값을 제거
모든 항을 $||x||\ ||y||$ 로 나눔
이때, $\omega \in [0, \pi]$는 다음과 같이 정의되며, Figure 3.4과 같이 표현할 수 있습니다.
이때, $\omega$는 두 벡터 $x, y$간의 각도입니다. 각도는 직관적으로 두 벡터의 방향성이 얼마나 일치하는지에 대해서 알려줍니다. 예를들어 inner product가 dot product이고 벡터 $x, y=4x$가 있을 때, $y$는 $x$가 스케일된 벡터이므로 Angle은 0입니다. Angle이 0이라면 방향이 같다는 의미가 됩니다.
length, distance, angle을 유도하는 것 외에 inner product의 핵심 특징은 두 벡터가 orthogonal인지 아닌지에 대한 알 수 있게 해준다는 것입니다.
Definition 3.7 (Orthogonality)
만약 $\big< x, y \big> =0$이라면, 두 벡터 $x,y$는 orthogonal이며 우리는 이를 $x \perp y$라고 적습니다. 추가적으로 $||x||=1=||y||$라면 벡터는 unit vector라는 속성도 추가되어 orthonormal하다라고 이야기합니다.
이러한 정의가 함축하는 것은 $0$-벡터는 벡터 공간에서 모든 벡터와 orthogonal하다는 것을 의미합니다.
Remark
Orthogonality는 inner product에 대한(bilinear forms) 수직(perpendicularity) 개념의 일반화입니다.
기하학적인 맥락에서는 두 벡터가 orthogonal이라면 두 벡터가 서로 직각인 벡터로써 생각할 수 있습니다.
Definition 3.8 (Orthogonal Matrix)
만약에 모든 column들 끼리 모두 orthonormal하다면 square matrix $A \in \mathbb{R}^{n \times n}$은 orthogonal matrix입니다.
그리고, 이는 아래의 식과 같은 관계를 갖습니다.
즉, 역행렬을 transpose로 간단히 구할 수 있다는 의미가 됩니다.
orthogonal transformation matrix $A$를 이용한 transformation($Ax$)은 벡터 $x$의 length가 변하지 않는다는 특성이 있습니다. inner product를 dot product라고 생각하면, $||Ax||^{2}$ 은 $||x||^{2}$ 와 같습니다.
거기에 두 벡터 $x, y$사이의 각도 또한 inner product로써 측정되므로 orthogonal matrix $A$를 이용해 transformation시 각도 또한 변하지 않습니다. dot product를 inner product로 가정하고 orthogonal matrix$A$ 가 있을 때, $Ax, Ay$ 간의 각도는 두 벡터 $x, y$ 와 같음을 확인할 수 있습니다.
]]>앞서 norm은 vector의 vector의 lenght 혹은 magnitude의 직관이라고 이야기했었습니다.
아래 식과 같이 어떤 inner product지 간에 norm을 유도한다는 측면에서 Inner product와 norm은 매우 밀접하게 연관 되어있습니다.
위의 식에서 $\big< \cdot, \cdot \big>$는 inner product를 의미합니다.
하지만, 모든 norm이 inner product에 의해 유도되는 것은 아닙니다. Manhattan norm은 inner product로부터 유도되지 않는 대표적인 norm입니다.
norm이 inner product에서 유도되었는지 아닌지를 확인하는 방법에 대해서 알고싶다면, “An example of a norm which can’t be generated by an inner product“과 “Parallelogram law“를 참고하세요.
Remark. (Cauchy-Schwarz Inequality)
inner product vector space $(V, \big< \cdot, \cdot \big>)$에서 유도된 norm $||\cdot||$은 Cauchy-Schwarz inequality를 만족합니다.
norm에 의해 계산되는 length는 어떤 inner product냐에 따라 값이 변할 수 있습니다. 아래 Example 3.5에서는 dot product가 아닌 다른 inner product를 사용해서 norm을 구하면 값이 어떻게 변할 수 있는지에 대해 나타냅니다.
Definition 3.6. (Distance and Metric)
inner product space $(V, \big< \cdot, \cdot \big>)$을 고려했을 때, 아래의 식을 벡터 $x$와 $y$의 distance라고 부릅니다. $(x, y \in V)$
만약에 inner product로 dot product를 사용한다면, 그 distance는 Euclidean distance라고 부릅니다.
이 때, distance에 대한 mapping은 아래와 같이 표현되며, 이를 metric이라고 부릅니다.
Remark
벡터의 길이와 유사하게 벡터 간의 거리를 구할 때는 inner product가 필요하지 않습니다. 단지 inner product로 유도된 norm이면 됩니다. 다만 거리는 norm이 어떤 inner product로부터 유도된 norm이냐에 따라 다를 수 있습니다.
지금까지 distnace와 metric의 정의에 대해서 살펴봤습니다. 두 벡터 공간 $V, V$ 에서 $\mathbb{R}$ 공간으로 mapping하는 함수 metric $d$ 는 아래와 같은 속성을 갖습니다.
positive definite
$d(x, y) \geqslant 0, \forall x, y \in V$
$d(x, y) = 0 \Longleftrightarrow x=y.$
symmetric
Triangle inequality
Remark
inner product와 metric의 속성은 매우 유사해보입니다. 하지만 위에 언급한 Definition 3.6과 이전에 챕터에서 Definition 3.3을 비교해보면 $\big< x, y \big>, d(x,y)$ 는 서로 반대되는 방식으로 동작합니다.
Definition 3.3
$V$를 벡터공간, $\Omega: V \times V \rightarrow \mathbb{R}$을 두 벡터를 real number로 맵핑하는 bilinear mapping이라고 해봅시다.
즉, 벡터 $x, y$가 서로 유사할 때, inner product는 매우 큰 값을 갖으며 metric은 매우 작은 값을 갖습니다.
]]>직관적인 개념을 형식화할 때, 공통적인 접근법은 다음과 같습니다.
이러한 접근법으로 만들어진 개념들의 집합은 우리에게 대수(Algebra)라고 알려져 있습니다.
선형대수는 벡터(vector)와 벡터들을 조작하기 위한 규칙들을 연구하는 학문입니다.
벡터라고 하면 많은 사람이 고등학교에서 배운 geometric vector를 생각합니다. 하지만 책에서는 벡터의 일반화된 개념에 대해서 논의합니다.
A라는 유형을 갖는 객체가 있다고 가정해봅시다. 이때,
1), 2)라는 연산의 결과로 같은 유형의 객체(A라는 유형의 객체)가 나온다면 이를 벡터라고 정의합니다.
다시 말해, 수학적인 관점에서 벡터는 아래 두 성질을 충족하면 벡터로 간주합니다.
벡터에 대한 대표적인 예는 아래와 같습니다.
Geometric vectors:
Figure 2.1 (a) 에서 표현하고 있는 geometric vector는 고등학교 수학이나 물리에서 많이 배워 매우 친숙한 벡터입니다.
Polynomial vectors:
Figure 2.1 (b) 에서 표현하고 있는 polynomial(다항식) 또한 벡터입니다.
두 다항식은 서로 더해질 수 있으며, 더해진 결과 또한 다항식입니다. 그리고 다항식은 스칼라( )로 곱셈 연산을 해도 다항식입니다. 따라서 다항식 또한 벡터입니다.
그 외에도 3. Audio signals, 4. Elements of (tuples of real numbers)도 벡터입니다.
Audio signals, Elements of $R^{n}$은 내용상 크게 문제가 되지 않기 때문에 생략했습니다.
자세히 알고싶은 분들은 MML Book 18 page를 참고하시면 됩니다.
본 책에서는 다음과 같은 내용에 초점을 두고 선형 대수를 설명합니다.
만약 내가 새로운 연산자(operator)를 만들었다고 생각해봅시다. 이때, 내가 제안한 연산(operation)으로 만들어지는 것들의 집합은 무엇인가?라는 궁금증이 생길 수 있습니다.
이를 선형대수 관점에서 다시 생각해보면, 작은 벡터의 집합에서 시작해서 서로 더해지고, 스케일링 되었을 때의 그 결과인 벡터들의 집합은 무엇인가?와 같습니다. 이 질문에 대한 답은 Section 2.4에서 언급할 vector space가 됩니다. vector space의 개념과 속성은 기계학습 알고리즘의 기초가 됩니다.
선형대수는 기계학습과 수학에서 중요한 역할을 합니다. 이번 장에서 소개되는 개념들은 chapter 3에서 geometry에 대한 아이디어로 확장됩니다. chapter 5에서는 벡터 미적분(vector calculus)에 대해서 논의합니다. 벡터 미적분은 매트릭스 연산에 대한 지식이 필수적으로 요구됩니다. chapter 10에서는 PCA를 활용한 차원 축소를 위해 사영(projection)을 사용할 것입니다. chapter 9에서는 선형 회귀(linear regression)에 대해서 논의합니다. 선형 회귀에서 선형대수는 least-square problems을 푸는 중요한 역할을 합니다.
선형 시스템은 선형 대수에서 중심적인 역할을 합니다. 많은 문제는 선형 시스템으로 형식화되며, 선형대수는 이를 풀 수 있는 도구를 제공합니다.
Example 2.1
어떤 회사가 리소스 가 들어가는 제품 을 생산합니다. 이때, 의 일부 파트를 생산하기 위해서 리소스 의 일부인 가 필요합니다. (, ).
우리의 목적은 한정된 리소스에서 얼마나 많은 제품을 만들 수 있는지에 대한 최적의 생산 계획을 찾는 것입니다. 예를 들어 “우리에게 리소스 가 있을 때, 제품 의 일부 파트인 를 얼마나 많이 생산할 수 있는가?”와 같은 질문의 답을 찾는 것이지요.
만약 우리가 제품과 대응되는 파트들 을 생산할 때, 다음과 같은 리소스가 필요하게 됩니다.
이때, 최적의 생산계획은 실수공간에 존재하게 됩니다. 따라서, 최적의 생산 계획은 아래의 선형 시스템을 만족하는 해를 찾으면 됩니다.
식 (2.3)은 일반적인 형태의 선형 시스템입니다.
은 시스템에서 미지수가 되며, 식 (2.3) 을 만족하는 해가 이 선형 시스템의 해가 됩니다.
Example 2.2
다음 선형 시스템이 있을 때,
이 선형 시스템은 해가 없습니다. (1)과 (2)를 더하면 입니다. 이 결과를 (3)과 빼게되면, 부등식은 모순이 됩니다. 이 경우에는 선형 시스템에 해는 없게 됩니다.
또 다른 선형 시스템을 살펴봅시다.
이를 위와 같은 방식으로 풀게 되면, 이 유일한 해(유일해; unique solution)가 됩니다.
마지막으로 아래 선형 시스템을 살펴봅시다.
이를 똑같이 풀어보면, (1)과 (2)를 더한 결과가 (3)과 같음을 확인할 수 있습니다.
이때, (1)과 (2)를 이용해서 , 을 얻을 수 있습니다.
$x_{3} = a \in R$로 자유 변수(free variable)로 정의하면, 다음과 같은 해를 찾을 수 있습니다.
이 해는 무수히 많은 해가 존재한다는 것을 의미하며, 기하학적으로 Figure 2.3과 같습니다.
일반적으로 실수값을 갖는 선형 시스템으로 구성된 시스템에서 우리는 아래와 같은 종류의 해를 구할 수 있습니다.
선형 회귀(linear regression)은 Example 2.1과 같은 예제에서 선형 시스템을 풀지 못했을 경우 이를 푸는 방법입니다.
두 개의 변수()를 갖는 선형 시스템에서 각각의 시스템은 - 평면에서 하나의 선이 됩니다.
만약에 각 시스템을 모두 만족하는 해가 있다면, 이 해는 각 선의 교집합이 됩니다.
이 교집합은 다음과 같은 형태로 나타납니다.
이를 확장하여 변수가 세 개가 된다면, 각 시스템은 면(plane)이 되며, 교집합은 선(line)이 됩니다.
이때의 교집합은 1)면, 2)선, 3)점, 4)공집합(해가 없음)으로 나타나게 됩니다.
행렬 또한 선형 대수에서 중요한 역할을 합니다. 행렬은 선형 시스템을 간단히 표기하는데 사용하기도 하지만, Section 2.7에서 살펴볼 선형 함수(linear mapping)를 표현하기도 합니다.
행렬의 재밌는 주제를 논의하기 전에 먼저, 1) 행렬이 무엇이고, 2) 행렬로 어떤 연산이 가능한지 살펴봅시다.
Definition 2.1 (Matrix)
$m, n \in N,\ \ N \in R$인 $(m, n)$ 행렬 $A$는 $m$ rows와 $n$ columns으로 구성된 직사각형 구조를 갖습니다.
행렬의 원소 $a_{ij},\ i=1,…,m,\ j=1,…,n$ 는 $m\cdot n$ - tuple입니다.
일반적으로 $(1, n)$-행렬은 rows라고 부르고, $(m, 1)$-행렬은 columns라고 부릅니다.
rows, colums와 같이 특별한 행렬은 row, column vectors라고 부릅니다.
$R^{m \times n}$는 실수공간에 있는 $(m, n)$-행렬입니다.
$A \in R^{m \times n}$는 모든 $n$ columns의 행렬이 길게 붙여진 $a \in R^{mn}$과 같은 표현입니다.
행렬의 표현이 달라질 뿐이지, 벡터 개념에서는 벗어나지 않는다는 것에 주목합시다.
표현이 변경된 행렬은 다른 행렬을 더해도 같은 형태를 유지하는 행렬이며, 스칼라와 곱해도 같은 형태를 유지하는 행렬입니다.
추가적으로, 우리가 알고 있는 행렬곱의 연산자(opration)를 일부 변경하면 행렬 또한 행렬곱 정의가 가능합니다.
두 행렬 $A \in R^{m \times n}$, $B \in R^{m \times n}$의 합은 element-wise sum으로 정의됩니다
두 행렬 $A \in R^{m \times n}$, $B \in R^{n \times k}$가 있을 때, 행렬곱( $C = AB \in R^{m \times k}$)의 결과인 행렬 원소 $c_{ij}$는 다음과 같이 계산합니다
위와 같이 $A$의 $i$th row의 원소와 $B$의 $j$th columns의 원소끼리 곱하는 연산을 Section 3.2에서는 dot product라고 부르며, $A \cdot B$로 표기합니다.
Remark
행렬의 곱은 인접 차원이 같아야합니다. 예를 들어 $n \times k$ 행렬 $A$는 $k \times m$ 행렬 $B$와 곱셈 연산을 수행할 수 있습니다. 이때, 행렬 $B$는 행렬 $A$ 우측에 있어야만 합니다.
프로덕트(product) $BA$는 이웃 차원이 맞지 않기 때문에 정의되지 않습니다.
Remark
행렬 곱은 element-wise 연산으로 정의되지 않습니다. 프로그래밍 언어에서 종종 나타나는 array 간 element-wise 곱은 Hadamard product라고 부릅니다.
Definition 2.2 (Identity Matrix)
$R^{n \times n}$에서 단위행렬(identity matrix)은 대각방향으로는 1, 그 외에는 0을 갖는 $n \times n$- 행렬로 정의됩니다.
지금까지 우리는 행렬의 1) 합, 2) 곱, 3) 단위행렬에 대해서 정의했습니다. 이를 이용하여 매트릭스의 속성을 살펴보면 아래와 같습니다.
Associativity (결합법칙)
Distributivity (분배법칙)
Multiplication with the identity matrix (단위행렬과의 곱셈)
Definition 2.3 (Inverse)
정방행렬 $A \in R^{n \times n}$이 있을 때, 매트릭스 $B \in R^{n \times n}$와의 관계가 $AB = I_{n} = BA$라는 속성을 만족할 때, $B$는 $A$의 역행렬이라고 부르며 $A^{-1}$로 표기합니다.
모든 행렬 $A$가 역행렬 $A^{-1}$를 갖진 않습니다. $A$가 역행렬을 가지려면 $A$는 regular / invertible / nonsigular(정칙행렬) 이어야 합니다. 다른 경우는 singular / noninvertible (특이 행렬)이라고 부릅니다.
역행렬이 존재한다면, 이는 유일합니다. Section 2.3에서는 선형 시스템을 풀어 역행렬을 계산하는 일반적인 방법을 논의합니다.
Remark (Existence of the Inverse of a $2 \times 2$-matrix)
위와 같은 행렬이 있을 때, 두 행렬을 곱하게 되면 다음과 같은 행렬을 얻을 수 있습니다.
따라서 역행렬 $A^{-1}$은 아래와 같습니다.
이때, 이어야 합니다. Section 4.1에서는 가 -matrix의 행렬식(determinant)임을 살펴봅니다. 우리는 행렬식을 통해 행렬이 역행렬을 갖는지 확인할 수 있습니다.
Definition 2.4 (Transpose)
행렬 가 다음과 같을 때 , , 면 를 의 전치행렬(transpose)이라고 부릅니다. 전치행렬은 로 표기합니다.
역행렬과 전치행렬의 중요한 속성은 다음과 같습니다.
$AA^{-1} = I = A^{-1}A \
(AB)^{-1} = B^{-1}A^{-1} \
(A + B)^{-1} \neq A^{-1} + B^{-1} \
(A^{T})^{T} = A \
(A+B)^{T} = A^{T} + B^{T} \
(AB)^{T} = B^{T}A^{T}$$
Definition 2.5 (Symmetric Matrix, 대칭행렬)
$A \in R^{n \times n}$인 행렬이 대칭행렬이라면 $A = A^{T}$입니다.
대칭행렬은 정방행렬인 $(n, n)$-행렬에서만 가능하며, 역행렬을 가지며, 역행렬은 $A^{T}$입니다.
$(A^{-1})^{T} = (A^{T})^{-1} =: A^{-T}$
Remark (Sum and Product of Symmetric Matrices)
임의의 두 대칭행렬의 합은 항상 대칭행렬이 됩니다. $A, B \in R^{n \times n}$
대칭행렬의 곱은 항상 정의되지만, 결과는 일반적으로 대칭행렬이 아닙니다.
$\begin{bmatrix}
1&0 \
0&0\
\end{bmatrix}
\begin{bmatrix}
1&1 \
1&1\
\end{bmatrix} =
\begin{bmatrix}
1&1 \
0&0\
\end{bmatrix}$
행렬이 스칼라에 의해 곱해질 때를 고려해보겠습니다.
행렬 $A \in R^{m \times n}$과 스칼라 $\lambda \in R$가 있을 때, $\lambda A = K$입니다.
이때, 가 됩니다.
이때, $\lambda$는 $A$의 원소를 스케일링한다고 볼 수 있습니다.
스칼라가 $\lambda, \psi \in R$일 때, 행렬과 스칼라 곱의 성질은 아래와 같습니다.
Associativity (결합법칙)
Distributivity (분배법칙)
위와 같은 선형 시스템이 있을 때, 선형 시스템을 하나하나 표기하는 것이 아닌, 계수(coefficient $a_{ij}$)들의 집합과의 관계로 표기하게 되면, 행렬곱으로 간소화하여 표현할 수 있습니다.
이때, 은 첫 번째 column을, 는 두 번째 column을, 은 세 번째 column을 스케일링한다고 볼 수 있습니다.
일반적으로 선형 시스템은 행렬 형태로( ) 간소화하여 표현할 수 있습니다.
프로덕트 $Ax$는 $A$의 columns의 선형 조합입니다. 우리는 Section 2.5에서 선형 조합에 대해서 논의해볼 것입니다.
]]>Robert Nishihara의 허락을 받아, Modern Parallel and Distributed Python: A Quick Tutorial on Ray을 번역한 글입니다.
Ray는 파이썬에서 병렬, 분산 프로그래밍을 위한 오픈소스 프로젝트입니다.
병렬, 분산 컴퓨팅은 현대 애플리케이션을 구성하는 요소 중 하나로 자리잡았습니다. 우리는 필요에 따라 멀티코어나 여러 대의 머신의 리소스를 최대한 활용해서 애플리케이션을 가속해야할 필요가 있습니다.
웹 사이트를 크롤링하거나 사용자 질의에 응답하는 소프트웨어들은 누군가의 노트북에서 돌아가는 single thread기반의 프로그램이 아니고, 서로 통신하고 상호작용하는 서비스 집합이라고 볼 수 있습니다.
이번 포스팅은 Ray를 사용해서 병렬,분산 어플리케이션을 만드는 방법에 대해서 설명합니다.
많은 튜토리얼들이 Python의 multiprocessing 모듈을 어떻게 사용하는지 설명합니다.
하지만 Python의 multiprocessing 모듈은 한계점을 가지고 있어 현대 애플리케이션이 요구하는 분산, 병렬에 대한 필수사항을 충족하지 못합니다.
현대 애플리케이션이 요구하는 분산, 병렬처리에 대한 필수사항은 다음과 같습니다.
Ray는 위에서 언급한 요구사항을 모두 충족합니다. 또한 간단한 작업을 단순하게 만들며, 복잡한 동작을 하게끔 프로그래밍하는 것 또한 가능합니다.
다른 회사들이 자신들의 Python 프로덕션을 확장하기 위해서 Ray를 어떻게 활용하고있는지 배우고싶다면, Ray Summit에 등록하세요!
전통적으로 프로그래밍은 1). 함수(Functions), 2) 클래스(Classes)라는 핵심 개념에 의존합니다. 생각해보면 우리는 함수와 클래스만으로 많은 애플리케이션들을 만들어왔습니다.
하지만, 함수와 클래스로 구성된 애플리케이션을 분산 환경으로 마이그레이션하려고하면 함수, 클래스라는 개념을 사용할 수 없게됩니다.
따라서 현재까지 알려진 병렬, 분산 도구를 활용해서 싱글 스레드 애플리케이션을 병렬, 분산 애플리케이션으로 마이그레이션을 하기 위해서는 애플리케이션 코드를 처음부터 다시 작성해야합니다.
현재까지 알려진 병렬, 분산도구는 저수준에서 고수준까지 다양한 도구들이 있습니다.
먼저 저수준 도구로는 메세지의 송수신을 저수준의 프리미티브로 제공하는 OpenMPI, Python Multiprocessing, ZeroMQ이 있습니다. 이 도구들은 분산, 병렬 환경을 위한 강력한 기능들을 제공합니다. 하지만, 전통적인 프로그래밍과는 다른 추상화 개념을 사용합니다. 이로 인해 위 도구들을 활용해서 기존의 싱글 스레드 애플리케이션을 분산, 병렬 어플리케이션으로 마이그레이션하기 위해서는 코드 전체를 재작성해야합니다.
또 다른 예로 도메인에 특화되어 고수준의 추상화를 제공하는 도구들이 있습니다. 딥러닝 모델을 학습하기 위한 TensorFlow, 데이터와 SQL 처리를 위한 Spark, 스트림 처리를 위한 Flink가 대표적입니다. 이 도구들은 neural network나 데이터셋, 스트림에 대한 고수준의 추상화 API를 제공합니다. 하지만, 고수준 추상화를 제공하는 도구들 역시 직렬화된 프로그래밍(serial programming)에서 사용하는 추상화와 다르기 때문에, 애플리케이션 코드 전체를 그에 맞게 재작성해줘야하는 단점이 있습니다.
Ray는 위에서 설명한 도구들과 같은 고수준, 저수준이 아닌 중간수준에 위치합니다. Ray는 함수와 클래스를 task, actor라고 불리는 분산환경에 적합한 형태로 변환하며, 이를 통해 병렬, 분산 컴퓨팅을 지원하는 메커니즘을 가지고 있습니다. 따라서 사용자들은 이전과 다르게 코드를 재작성 없이 기존의 함수와 클래스 구조를 유지하면서 분산, 병렬 프로그래밍을 할 수 있습니다.
Ray의 ray.init()
명령어는 Ray에서 사용하는 프로세스들을 모두 구동합니다.
만약 클러스터 환경을 이용해서 분산 컴퓨팅을 하고자 한다면, 클러스터의 주소(address)를 입력하는 코드 라인 하나만 변경하면 됩니다.
ray.init()
명령어로 구동되는 Ray의 프로세스들은 아래와 같습니다.
Ray worker는 thread가 아니며, thread와는 다른 개념의 process입니다.
Python은 GIL(Global Interpreter Lock)으로 인해 multi-threding 지원에 한계가 있습니다.
@ray.remote
라는 데코레이터를 함수 위에 선언해주는 것만으로 파이썬 함수를 Ray에서 실행 가능한 remote function으로 변경할 수 있습니다.
remote function은 Ray의 프로세스에 의해 비동기적으로 실행됩니다.
아래 예제와 같이 함수 f
를 @ray.remote
데코레이터를 통해서 remote function으로 변경했다면, f.remote()
를 호출해서 함수를 실행할 수 있습니다. 이때, 호출된 f.remote()
는 즉각적으로 future를 반환하고 실제 함수의 실행은 백그라운드에서 진행됩니다.
future는 나중에 반환될 함수의 출력값에 대한 참조입니다.
아래 예제에서 f.remote()
에 대한 호출이 즉시 반환되고 다음 remote function이 실행되기 때문에, 백그라운드에서 실행되는 f
에 대한 4개의 복사본(task)은 단순히 해당 라인을 4번 실행하는 것으로 분산, 병렬로 실행할 수 있습니다.
파이썬 함수 f
를 “remote function”으로 바꾸기 위해서는 함수에 @ray.remote
라는 데코레이터를 선언해줘야합니다. 그리고 함수를 f.remote()
로 호출하면 즉시 future를 리턴합니다. 그리고 실제 함수의 실행은 백그라운드에서 실행됩니다.
1 | import ray |
task는 또 다른 task에 의존할 수 있습니다.
아래 예제에서 multiply_matrices
task는 두개의 create_matrix
task의 결과를 사용합니다. 따라서 첫번째 두 task의 출력은 자동으로 세번째 task의 인자로 입력됩니다.
결론적으로, 아래 예제를 실행해보면, multiply_matrices
는 첫번째 두 task의 출력의 값이 반환되기 전까지는 실행되지 않습니다.
이러한 방식으로 task들을 arbitrary DAG dependencies로 구성할 수 있습니다.
1 | import numpy as np |
task 의존성을 잘 설계하면 효율적인 방식으로 작업을 수행할 수 있습니다.
예를 들어 아래의 그림처럼 8개의 정수를 더한다고 생각해봅시다.
매우 간단한 예제이지만, 실제로 이러한 형태로 큰 벡터를 통합하는 것은 애플리케이션에 큰 병목이 되기도 합니다. 이런 병목 지점에서 task 의존성을 잘 설계한다면, 단 한줄의 코드 변경으로 시간 복잡도를 선형 시간에서 로그메틱 시간으로 변경할 수 있습니다.
위에서 설명한데로 하나의 task에서 생성된 output을 다른 task의 입력으로 사용하기 위해서는 첫번째 task로부터 반환받은 future를 두번째 task의 입력으로 넣으면 됩니다.
이때, 두번째 task가 첫번째 task의 출력을 의존하고있으면 두번째 task는 첫번째 task가 끝나기 전에는 실행되지 않습니다.
task 의존성은 자동으로 ray의 스켸쥴러가 추적하고 관리하므로, 만약 분산환경일 경우, 첫번째 task의 출력은 자동으로 두번째 task가 있는 머신으로 보내져 실행되게됩니다.
1 | import time |
위의 코드는 명확합니다. 하지만, 이를 while
loop를 통해 구현한다면 더 간결하게 구현할 수 있습니다.
1 | # Slow approach. |
클래스없이 좋은 애플리케이션을 만드는 것은 어려운 일입니다. 그리고 이는 분산환경에서도 마찬가지로 어렵습니다.
클래스 데코레이터 @ray.remote
를 사용하면 Ray에서 파이썬 클래스를 사용할 수 있습니다. 클래스를 인스턴스화하면 Ray는 새로운 액터(Actor)를 생성합니다. 액터는 분산환경 어딘가에서 실행되지만 객체의 복제본(object copy)을 유지하는 프로세스입니다.
액터의 메소드를 실행하면 Ray는 해당 메소드를 액터 프로세스 위에서 작동하는 task로 변환합니다. 액터 프로세스 위에서 작동하는 task는 액터의 상태(state)에 접근이 가능하고 상태를 변경할 수 있습니다. 이러한 방법으로 액터는 액터의 상태값을 여러 task간 공유합니다.
개별적인 액터는 메소드를 직렬로 실행하며(블럭킹), 액터의 메소드는 atomic 속성을 갖습니다. 따라서 race condition이 발생하지 않게됩니다. 액터를 이용한 병렬성은 다수의 액터를 생성하는 방식으로 구현합니다.
아래 예제는 액터를 사용하는 간단한 예제입니다. Counter.remote()
는 새로운 액터 프로세스를 생성합니다.
액터 프로세스는 Counter
객체의 복사본을 갖으며, c.get_value.remote()
와 c.inc.remote()
는 원격 액터 프로세스(remote actor process)에서 task를 실행하고 액터의 상태를 변경합니다.
1 |
|
위에서 우리는 파이썬의 메인 스크립트에서 액터의 메소드를 실행하는 예제를 살펴봤습니다.
액터의 강력한 장점은 핸들(handle)을 액터에 전달할 수 있는 것입니다. 이는 다른 액터나 다른 task가 동일한 액터의 메소드를 호출할 수 있게 해줍니다.
아래 예제는 메세지를 저장하는 액터를 생성합니다. 몇몇의 worker task는 반복적으로 messages를 액터로 푸쉬합니다. 그리고 파이썬 메인 스크립트는 주기적으로 이 메세지를 읽습니다.
1 | import time |
Ray의 액터는 매우 강력합니다. 액터는 파이썬의 클래스를 가져와서 다른 액터와의 작업 혹은 다른 애플리케이션에 질의할 수 있는 마이크로 서비스로 인스턴스화할 수 있습니다.
task와 액터는 Ray가 제공하는 핵심적인 추상입니다. 이 두 가지 개념은 매우 일반적이면서 정교한 애플리케이션 구현에 사용할 수 있습니다.
Ray는 딥러닝에 사용되는 정교한 애플리케이션 중 하나인 분산 강화학습, 하이퍼파라미터 튜닝 도구, 가속화된 판다스를 제공하니 한번 살펴보시기 바랍니다.
이제 2019년이 가고 2020년이 되었다.
지난 2019년 초에 많은 계획들을 세웠던 것 같은데, 예상치 못한 일들로 다사다난했던 해였다.
2020년을 맞이하여 2019년 회고하고 2020년 계획을 다짐하는 포스팅을 작성한다.
2019년을 시작하면서 그때의 상황에 맞춰 여러 계획들을 많이 세웠다.
큰 카테고리로 나누어보자면 아래와 같이 나눌 수 있다.
일을 오래 잘하려면 체력이 좋아야하고 건강한 것이 필수이다. 2019년엔 그에 맞춰 아래와 같은 계획을 세웠었다.
성향상 아침 잠이 많은 편이고 점심, 저녁은 회사 업무를 하다보면 잘 챙겨먹는 편이었던 나는 세 가지를 한꺼번에 잡기 위해서 규칙적인 생활 아래에서 이른 기상으로 아침 식사와 운동을 잘 잡으려 노력했다. 운동은 원래 즐겨하던 비보이 연습과 회사에서 헬스를 병행하는 방식으로 했다. 2월 달을 기점으로 살짝 흔들렸던 것 같고 본격적으로 9월부터는 전혀 해당 목표에 대해서 관리하지 못했다.
이제는 나이도 나이인지라 미래의 나를 위해 경제적인 부분에서 자립할 수 있을 정도로 스스로 지출 설계 및 소비 패턴 제어가 필요했다. 이를 위해 아래와 같은 작업을 했다.
이 또한 9월까지는 순조롭게 잘 관리했고 년초에 목표했던 금액에 80%를 달성할 정도로 근사한 달성률을 보였다. 9월 이후에는…. 👀
항상 언제든지 도태될 수 있다는 생각을 달고 살았던지라 자기계발을 놓고 살지는 않았다. 2019년에는 아래와 같은 내용으로 자기계발 내용을 조금 더 구조화하고 체계적으로 바꿀까했다.
현실감각이 없었던 계획이었지만, 연구직이라는 업무 특성과 대학원 수업에서 강제적인 논문 발표 수업으로 인하여 논문을 꽤나 읽게되어 나름 만족으럽게 80%의 달성률을 보였다. 읽은 논문 리스트는 아래와 같다.
SqueezeNet / BinaryConnect / Binarized Neural Network / XNOR Net / EfficientNet / EfficientDet / Trained CNNs are biased towards texture / rafiqi / TFX / TrIMS / Clipper / 기타 헬스케어 관련 논문 20편 내외
9월달 대학원에 입학하자마자 석사 1기생들과 팀을 꾸려 인공지능 수업에서 BinaryConnect와 Binarized Neural Network를 구현하는 프로젝트를 진행하였다. Binarized Neural Network까지 구현을 하지는 못하였지만 BinaryConnect까지는 진행 완료하게 되었다.
2019년 초에 세웠던 계획과 다르게 삶에 터닝 포인트가 되거나 여러 사건들이 많이 생기게 되었다. 년초에 세웠던 계획들이 크게 틀어지게된 계기는 대학원 입학과 여러 새로운 업무들이 제일 컸다.
2019년에 여러 일들을 겪으며 내가 얻게된 화두의 키워드는 아래와 같다.
지금까지는 호흡이 맞는 사람들과 프로젝트를 했다면 2019년에는 회사 업무와 학교 프로젝트에서 호흡을 맞춰 보지 않았던 사람들과 호흡을 맞췄다. 호흡을 맞추던 과정에서 당연히 알 것이라고 생각했던 것들을 사람들이 모를 수 있다는 것을 깨달았다. 덕분에 예상하지 못했던 부분에서 많은 시간을 투입하게 되었고 스스로가 병목이 되는 경험을 해볼 수 있었다. 다음번부터는 팀원들의 능력에 따른 예상시간을 추정해보고 그에 따라서 업무를 진행해보려고 한다. 또한 스스로 병목이 되는 구간에 대해서는 어디까지 병목을 허용하고 허용하지 않을지에 대한 지점을 찾아볼까 한다.
2019년 9월부터 대학원과 회사 업무를 병행하게 되었다. 대학원은 SoC연구실로 입학하게 되었고 회사에서는 인공지능 모델 서빙을 위한 클라우드 연구에서 마이크로 아키텍쳐 서비스 시스템에 GPU를 연동하기 위한 설정과 마이크로 아키텍쳐 서비스에 사용되는 컨테이너 변경하는 작업, 마이크로 아키텍쳐 시스템을 시연하는 여러 데모 프로그램을 작성하게 되었다. 그 과정에서 개인적으로 힘듦을 느껴 주변에 스스로의 상태에 대해 많이 토로했던 것 같다. 새해가 지나고 천천히 돌아보니 나뿐만 아니라 모두가 그런 일상을 보내고 있었다라는 것을 깨닫고 부끄러웠다. 계속 경험하다보면은 자연스러워지겠지만 당분간은 의식적으로 마음의 여유를 잘 찾아서 의연함을 잃지 않게 노력해보려고 한다.
2019년도 과제를 마무리하기 위해서 회사에서 9월부터 12월까지 여러 작업을 진행하면서 순간순간 예측을 벗어나는 일들이 많았던 것 같다. 예측을 크게 벗어나 몇번 할당된 업무를 제때 수행하지 못할 뻔한 위기상황을 많이 겪었는데, 프로젝트를 관리하는 측면에서 나의 이러한 상황은 굉장히 불안정해보일 수 있다는 점을 깨달았다. 이는 년초에 추정했던 업무량을 상회해서 발생했다라고 판단하고 있고, 2019년도에 크게 한번 겪어봤으니 2020년에는 추가적인 업무량과 예상되는 업무량 그리고 내가 수용가능한 업무량을 잘 조율하여 업무를 매끄럽게 수행해볼 수 있게 노력해보고자 한다.
2019년에 자율차 플랫폼 연구부서에서 현재 부서로 이동하고나서 정말 다양한 경험을 하게되었다. 대학원을 병행하면서 갈증이 있던 지식을 수업을 통해서 해소했고 해보고싶었던 연구도 즐겁게 했다. 더 중요한 것은 할당된 업무를 혼자 독립적으로 충분히 업무를 진행할 수 있다는 자신감이다. 이런 유의미한 경험들이 다 주변 사람들이 믿어주고 기회와 충분한 시간을 주었던 덕분이 아닐까 싶다. 이런 좋은 환경에서도 상황이 급박해져서 불만을 종종 토로하곤 했는데 이런 행동은 스스로에게 그렇게 도움이되는 태도는 아니라고 생각한다. 2020년에는 조금 더 감사히 내게 주어진 환경에서 즐겁고 열심히 최선을 다하는 한해가 되었으면 한다.
비록 년초에 새웠던 공부 일정은 달성하지 못했지만 몇가지 꾸준히 진행했던 프로젝트 및 스터디는 유의미한 결과를 얻어 내년에도 지속할 수 있는 결과들을 얻었다. 1~2년을 꾸준히 진행했던 덕분이 아닐까 싶다. 생각 외로 스터디에서 진행했던 공부들이 업무에 직접적으로 또는 간접적으로 크게 영향을 많이 준다는 것을 깨달았는데, 2020년에는 더 깔끔하게 정제되게 꾸준히 하는 스터디를 진행해보았으면 하는 바람이다.
2020년에도 2019년과 다를게 없이 관심사는 아래와 같이 같은 카테고리로 나뉘어질 듯 하다.
9월~12월 동안 급격하게 흘러갔던 업무 상황으로 운동도 내팽겨친채 밤새 일했던 시간이 많았다. 그 결과로 12월 말에 몸이 2~3주 동안 계속 아팠는데 체력적으로 많이 부족하다는 생각이 들었다.
분당으로 이사오고 나서 초기에 세웠던 경제적 목표가 크게 틀어졌다. 2020년에는 더 긴장하고 경제적 목표를 채워볼까 한다.
2020년은 인공지능 모델 서빙관련 프로젝트가 종료되는 해이다. 이번년도는 더 탄탄해진 업무 프로세스와 함께 내가 접근해야하는 기술 스택이 더 많아졌는데 시간이 된다면 아래와 같은 학습을 더 해볼까 한다. 추가적으로 대학원 졸업을 위해서 하드웨어관련 지식을 더 학습할 예정이다.
이번년도는 대학원 졸업 및 미래의 이직을 위한 여러가지 학습을 진행하려고 한다. 따라서 협업을 위한 스킬이라던가 면접을 위한 공부와 코딩 테스트를 위한 공부를 추가적으로 진행할 예정이다.
이번 포스팅은 논문 ImageNet - Train CNNs are biased towards texture; increasing shape bias improves accuracy and robustness을 읽고 읽은 내용을 포스팅한다.
형상 정보 가설과 질감 정보 가설(Shape Hypothesis vs Texture Hypothesis)
많은 사람들은 컨볼루션 뉴럴 네트워크(Convolutional Neural Network; 이하 CNN)가 “레이어가 깊어질 수록 저-레벨(low level)의 특징(feature)을 조합해서 고-레벨(high level) 특징(feature)을 만들고, 이 특징들을 이용하여 객체인식을 한다.” 이라는 직관에 동의할 것이다.
[그림 1] ZF-Net으로 시각화한 layer들의 특징
CNN이 객체 분류를 어떻게 하는지에 대한 공통된 해석은 여러 문헌에서도 나타나는데 Kriegeskorte과 LeCun은 그들이 저술한 논문 혹은 아티클에서 아래와 같이 언급했다.
CNN은 각 카테고리(강아지, 고양이)가 가지고있는 고유한 패턴과 같은 지식을 얻는다. … (중략) … 고-레벨(high level)의 특징들은 실제 영상(생성되거나 조작되지 않은 영상)에서 나타나는 형상(shape; 이하 형상) 정보를 배우는 것 처럼 보인다.
CNN의 중간 레이어들은 같은 카테고리의 객체들에게서 유사한 부분을 인식한다. …(중략) … 객체를 검출하는 것은 이러한 부분들의 조합이다.
이렇게 CNN이 형상 정보를 학습한다는 주장을 형상 정보 가설 (Shape Hypothesis)부른다.
형상 가설은 수많은 실험을 통해서 지지를 받는데, 대표적으로 ZF-Net이 있다. ZF-Net은 모델의 레이어로부터 특징들을 추출한다. 그 후 역-컨볼루션(De-Convolution)이라는 연산을 통해서 추출한 특징을 시각화한다. [그림 1]은 ZF-Net에서 추출한 특징을 시각적으로 보여준다.
Kubilius라는 연구자는 CNN을 사람의 시각 인지 모델로써 제안했으며 Ritter는 CNN이 아이들과 유사하게 형상 정보를 개발해나간다는 것을 밝혀냈다. Ritter가 이야기하는 형상 정보를 개발한다는 것의 의미는 색감(Colour) 정보 보다는 형상 정보가 객체 분류를 하는데 더 중요한 역할을 한다는 것을 의미한다.
실제로 형상 정보 가설은 사람의 인지 체계와 매우 비슷하기도 하다. 사람은 모양이 바뀌면 다른 카테고리로 분류하지만 질감 및 크기가 변화하더라도 같은 모양이면 같은 객체 카테고리로 인식한다. 이는 해당 연구 결과를 통해서 확인되었다.
[그림 2] 사람은 질감과는 관계없이 모양에 따라 같은/다른 물체로 인식한다.
Summary
가설을 지지하는 실험들이 여러 논문들을 통해서 확인되었다.
ZF-Net, A Shape Bias Case Study,
이러한 형상정보 가설은 사람의 인지체계와도 제일 유사한 측면을 갖는다.
몇몇 연구 결과들은 형상 정보 가설과 상반되는 결과를 얻기도 했다. 대표적으로 연결이 부분적으로 끊어진 네트워크에서 학습된 모델은 질감 정보(Texture; 이하 질감)가 더 중요한 역할을 한다는 연구가 있다. 해당 연구에서는 CNN이 전반적인 형상 정보가 없어도 질감 정보를 이용해서 영상을 잘 분류할 수 있다는 것을 증명한다. 오히려 질감 정보가 없는 형상 정보(스케치 그림)만으로 학습한 CNN은 나쁜 인식 성능을 갖는다.
[그림 3] CNN의 질감 정보만을 이용한 분류 예시
[그림 4] 질감 정보가 없는 스케치 데이터로만 학습했을 때, 새(bird) 데이터를 얼마나 많이, 자주 틀리는지에 대한 예시
두 가지 결과는 질감 정보와 같은 지역적인 정보(Local feature)만을 이용해서 객체 인식 문제를 충분히 해결할 수 있다는 것을 시사한다. 이를 증명한 연구결과는 여기에서 확인할 수 있다.
질감 정보의 중요성을 이야기하는 또 다른 연구에서는 질감 정보를 학습하고 특정 질감 정보를 생성하는 네트워크를 만들었다. 이렇게 질감 정보를 종류별로 학습하고 생성한다는 것은 모델이 세 가지의 기능을 수행한다는 의미를 갖는다.
해당 네트워크는 분류(Classification)를 하는 네트워크가 아니다. 하지만 질감 정보를 종류별로 생성한다는 것은 “질감 정보의 분포가 서로 다르기 때문에 질감 별로 분리해서 학습할 수 있다”는 것을 내포한다. 이런 맥락에서 논문의 저자는 질감 정보만으로 객체 분류 문제를 풀 수 있다고 이야기하는 듯 하다.
[그림 5] 질감정보를 생성하는 네트워크
그 외에도 다른 연구에서는 최대 리셉티브 필드의 크기에 제한을 둔 BagNet을 설계했다. BagNet은 최대 리셉티브 필드 크기의 제한으로 CNN이 국부적인 정보만 볼수 있게끔 시야가 제한이 되었다. 그럼에도 불구하고 ImageNet 데이터에서 놀라울 정도로 높은 정확도를 갖는 결과를 얻었다. 이러한 결과들을 보았을 때 국부적인 질감 정보는 객체 분류를 하는데 충분한 정보를 가지고 있음을 알 수 있다. CNN은 이런 질감 정보을 추론하는데 이를 질감 정보 가설(Texture Hypothesis)라고 부른다.
[그림 6] 최대 리셉티브 필드의 크기가 제한된 Bag Net의 최종 Layer에서 확인된 Feature들
Summary
지금까지 형상 정보 가설(Shape Hypothesis)와 질감 정보 가설(Texture Hypothesis)를 살펴보았다. 이렇게 상반되는 두 가지 가설을 해결하는 것은 인공지능 커뮤니티와 뇌 과학자 커뮤니티 모두에게 중요하다.
해당 논문에서는 StyleGAN을 이용하여 질감-형상 정보가 모순된 이미지(Cue Conflict)를 만들었다. 이를 이용하면 CNN과 사람의 시각 능력에 대해서 정량적으로 측정할 수 있게 된다. 이렇게 만든 질감-형상 정보가 모순된 이미지(Cue Conflict)를 이용해서 총 97명의 실험 참가자와 함께 9종류의 정신 물리학 실험(Psychophysical Experiments) 을 진행하였다. 총 실험의 횟수는 48,560번이었다.
본 논문의 기여는 아래와 같다.
CNN과 사람의 시각 시스템의 차이를 확실하게 확인하기 위해서 해당 논문에서는 여러가지 실험을 진행했다.
CNN과 사람이 어떻게 물체를 인식하느냐(질감 정보를 기반으로 인식하느냐? vs 형상 정보를 기반으로 인식하느냐?)를 확인하기 위해서 정신 물리학(Psychophysical Experiments; 이하 정신 물리학) 실험을 진행하였다.
실험 내용은 16-Class-ImageNet이라는 데이터 셋을 사람과 CNN에게 똑같이 보여주고 이미지 분류 작업 결과를 확인하였다. 실험 참가자는 총 97명이었으며 16-Class-ImageNet의 데이터수는 49K(49,000)였다.
사람과 CNN이 본질적으로 큰 차이가 있기 때문에 실험은 굉장히 신중하게 설계 되었어야 했다. CNN은 단일의 영상 정보를 한번만 네트워크에 입력하지만 사람의 시각 시스템은 연속된 정보(동영상)을 획득하고 뇌에서는 이를 기반으로 각 신경끼리 피드백을 주고받는다. 따라서 단순하게 영상을 보여주고 분류하는 실험은 CNN과 사람에게 단 한장의 영상만 보고 분류를 한다라는 실험 조건에서 단 한장이 서로 다를 수 있다.
실험을 최대한 공평하게 진행하기 위해서는 사람과 CNN의 실험 조건을 같게 설정 해야했다. 사람의 시각 시스템에서 발생하는 피드백은 영상을 제공하는 순서와 타이밍을 조절하여 최소화하였다. 이러한 방법은 이미 정신 물리학적 실험에서는 잘 알려진 사실이라고 논문에서는 언급한다. 하지만 의학적인 참고자료가 제시되어있지 않아서 더 자세한 근거는 확인하지 못했다.
[그림 7] CNN과 사람의 시각 시스템의 비교
해당 연구의 실험에서는 사람과 CNN이 서로 어떤 가설을 기반으로 인지를 하는지 확인하는 것이 목적이므로 16-Class ImageNet 데이터를 왜곡하여서 진행하였다.
[그림 8] 16-Class ImageNet 예시
기본적인 이미지 왜곡 외에도 서로 다른 형상 정보와 질감 정보가 섞여 있어 모순을 일으키는 Cue Conflict 영상으로도 실험을 진행하였다.
[그림 9] Silhouettes를 이용한 Cue Conflict 영상(위에서 3번째)과 Style transfer를 이용한 Cue Conflict 영상(위에서 네 번째)
Cue Conflict 영상을 만들기 위해서 두 가지 방법을 적용하였다.
연구자들이 막상 이러한 영상을 만들고 나니까 문득 든 생각이 해당 영상들이 실제 형상 정보와 질감 정보가 완전히 모순 되어 있다는 것을 어떻게 증명할 수 있을까? 였다. 이를 증명하기 위해서 연구자들은 앞서 언급했던 Bag Net를 사용하였다.
Bag Net는 최대 리셉티브 필드의 크기를 제한하여 CNN이 이미지의 전체 형상을 보지 못하게 만든 모델이다. 국부적인 특징(local feature)들만 이용했음에도 불구하고 BagNet은 굉장히 좋은 성능을 나타내었는데 만약 국부적인 특징(Texture)이 전체적인 특징(Shape)과 모순되어있다면(Cue conflict) Bag Net의 성능이 급격히 하락할 것이다.
실제로 Bag Net을 이용하여 Cue conflict 영상을 추론해본 결과 기존 ImageNet 데이터에서 성능이 잘 나오던 모델이 약 85% 정도의 성능 하락 발생한 것을 할 수 있었다.(ImageNet, 70.0% top-5 accuracy → Cue conflict, 10.0% top-5 accuracy)
이를 토대로 생성된 Cue conflict 영상이 형상 정보와 질감 정보가 모순되어 있다는 것을 확인할 수 있었다.
실험 결과 사람과 CNN이 물체를 분류하는 작업에서 큰 차이를 볼 수 있었다. 사람은 굉장히 형상 정보에 편향되어 있고, CNN은 질감 정보에 편향되어 있음을 확인할 수 있었다.
이는 고양이 형상에 코끼리 가죽 질감 정보가 섞여있는 Cue conflict 영상을 사람과 CNN에게 보여주었을 때, CNN은 이를 “코끼리”로 사람은 이를 “고양이”로 인식한다는 이야기가 된다.
[그림 10] CNN과 사람의 시각 시스템의 차이. 왼쪽은 형상 정보 편향을 의미하고 오른쪽으로 질감 정보 편향을 의미한다. 해당 그림에서 사람은 형상 정보에 편향 되어있음을 CNN은 질감 정보에 편향 되어있음을 확인할 수 있다.
연구자들은 이제 CNN이 사람과 비슷하게 형상 정보에 편향되면 어떻게 될까?가 궁금해졌다. 그래서 기존의 IN(ImageNet) 데이터와 SIN(Sytle transfered ImageNet)데이터를 이용하여 학습을 진행하였다.
학습 방법에 대해서는 다양한 실험을 진행했다
학습 후에는 모델이 효과적으로 형상 정보 편향이 되었는지 확인하기 위해 16-Class-ImageNet 데이터를 이용하여 이를 확인하였다. 전부는 아니지만 많은 클래스에 대해서 CNN이 질감 정보 편향에서 형상 정보 편향으로 이동 하였음을 확인할 수 있었다.
[그림 11] IN/SIN 데이터를 학습한 모델과 사람의 차이. 데이터를 혼합해서 학습한 모델이 그림 10. 과 비교했을 때 상대적으로 형상 정보 편향으로 이동되었음을 확인할 수 있다.
결론적으로 해당 실험에서 SIN 데이터(형상 정보 편향)만으로는 성능을 더 개선시킬 수는 없었다. 하지만 IN 데이터와 SIN데이터를 혼합해서 학습하는 경우에는 ImageNet 데이터 셋만 이용해서 학습한 것보다는 성능이 더 좋아진다는 것을 확인하였다. 또한 형상 정보 편향이 된 CNN을 이용해 객체 검출(Object Detection) 모델에 전이 학습(Transfer learning)했을 때, 기존의 객체 검출 모델보다 성능이 더 개선 되었음을 확인할 수 있었다. 이는 SIN데이터를 혼합해서 학습하는 것이 모델의 일반화(Generalization)에 더 기여한다고 해석할 수 있다.
[그림 12] IN데이터와 SIN 데이터를 이용한 CNN 학습 결과
그 외에 노이즈를 이용한 영상 왜곡 실험을 추가로 진행했다. 이 경우에도 형상 정보에 편향된 CNN이 노이즈 왜곡에도 모델이 더 강인해짐을 확인할 수 있었다.
[그림 13] 추가 영상 왜곡 실험에서 사용한 노이즈 예시
[그림 14] 형상 정보, 질감 정보에 편향된 CNN이 노이즈 왜곡에 따라 나타내는 성능
기존의 CNN이 이미지 분류를 할 때, 형상 정보를 기반으로 인식을 한다는 형상 정보 가설(Shape Bias Hypothesis)와 질감 정보를 기반으로 인식한다는 질감 정보 가설(Texture Bias Hypothesis)가 존재했다.
사람과 CNN이 영상 데이터를 어떤 관점에서 분류하는지 확인하기 위해 정신 물리학 실험을 수행했다. 해당 실험을 통해서 사람은 형상 정보에 편향 되어있고, CNN은 질감 정보에 편향 되어 있음을 확인할 수 있었다.
모델을 SIN/IN 데이터를 혼합하여 학습하면 모델을 형상 정보로 편향시킬 수 있다. 형상 정보로 편향된 모델이 기존 IN 데이터로만 학습된 모델보다 더 좋은 성능을 나타냄과 동시에 전이 학습(Transfer learning)에서도 성능 개선이 유효함을 확인할 수 있었다. 따라서 본 논문에서 제안한 SIN/IN 데이터 학습이 인공지능 모델을 일반화(Generalization) 시키는데 기여한다라고 이야기할 수 있다.
현재 쿠버네티스(Kubernetes, 이하 쿠버네티스)를 이용하여 딥러닝을 지원하는 마이크로 아키텍쳐(Micro Architecture, 이하 마이크로 아키텍쳐) 프레임워크 개발 및 연구를 진행 중이다.
일반적으로 개발 및 연구 프로젝트의 원활한 진행을 위해서는 개발한 기능을 테스트하기 위한 개발 환경 구축이 필요하게 된다. 필자는 이전에 개발한 기능을 테스트하기 위해서 쿠버네티스 기반으로 작성되어있던 프레임워크를 미니쿠베(minikube, 이하 미니쿠베)에서 구동시키고 테스트하는 작업을 수행했었다.
이번에는 GPU관련 기능을 테스트하기 위해서 GPU 컨테이너 사용이 가능하게끔 미니쿠베를 설정하는 작업을 했으나 정확하지 않은 공식 문서와 인터넷 상에 알파, 베타 버전등 파편화된 가이드 문서로 인해 미니쿠베에서 GPU 인식이 안되어 어려움을 겪었다.
본 포스팅은 아래 세 가지를 전달하기 위해 작성되었다.
필자는 쿠버네티스를 이용하여 딥러닝을 지원하는 마이크로 아키텍쳐 프레임워크 개발을 하고있으며 사내에서는 서비스 혹은 테스트용으로 GPU 컨테이너 사용을 지원하는 마이크로 아키텍쳐 프레임워크가 구축되어있다.
여러 명의 개발자와 협업하여 프로젝트를 진행 하다보면 일반적으로 따르는 프로세스가 있다.
이 때, 개발자의 개발 환경 혹은 테스트 환경은 서비스 배포 환경과 유사하게끔 설정되어야한다. 필자의 프로젝트가 딥러닝을 지원하는 마이크로 아키텍쳐 프레임워크 개발이기 때문에 필자의 개발 환경은 GPU 컨테이너를 지원하는 미니쿠베 환경을 갖춰야 했다.
미니쿠베의 GPU 지원 문서$^{[1]}$를 확인해보면 미니쿠베가 GPU를 지원하는 방법은 크게 두 가지로 나뉘어진다.
--vm-driver=kvm2
)--vm-driver=none
)--vm-driver=kvm2
)kvm2환경에서 GPU사용이 가능한 미니쿠베 설정 방법을 소개한다. 이 과정은 기존에 운영체제에 연결되었던 GPU 장치를 kvm2 가상환경에 직접적으로 연결(GPU Passthrough)하는 작업들을 포함$^{[2-9]}$하고 있기 때문에 과정이 복잡하다.
필자의 경우 해당 방법이 두 가지 사항을 요구했기 때문에 선택하지 않았다.
필자의 업무는 마이크로아키텍쳐 프레임워크 개발 외에도 인공지능 모델 개발도 있다. kvm2 환경에서 요구하는 두 가지 사항은 cuda와 cudnn 등 인공지능 모델 개발을 위한 환경설정을 작동하지 않도록 만든다.
필자가 kvm2환경을 사용하게 되면 업무 별로 nvidia 그래픽 드라이버 및 의존 패키지를 설치하고 삭제하는 작업을 반복적으로 수행해야한다. 이런 이유로 필자는 kvm2 환경을 선택하지 않았다.
--vm-driver=none
)필자는 공식 문서를 믿고 이를 따라서 미니쿠베 설정을 진행하였다. 적용 결과 기존에 작성된 마이크로 아키텍쳐 프레임워크에서 GPU를 사용하는 파드(Pods)가 무한 대기(pending)에 빠지는 현상이 발생했다.
확인 결과 미니쿠베에서 GPU 디바이스를 인식하지 못했거나 GPU 디바이스는 인식했으나 관련 드라이버 및 패키지가 존재하지 않아서 일어난 일이였다.
미니쿠베, 엔비디아-도커의 세부사항을 몰랐던 필자는 방대한 구글을 헤엄칠 수 밖에 없었다$^{[10-17]}$.
본 포스팅에서는 몇가지 준비 되어야하는 컴퓨터 환경과 작업이 있다. 필자는 아래의 패키지와 Ubuntu 18.04 환경에서 작업하였다.
Docker-CE ( ≥ 18.09)
Nvidia-Docker( ≥ 2.03)
Nvidia GPU를 장작한 컴퓨터
Nvidia 그래픽 드라이버 설치
Minikube(≥ 1.2.0)
Nvidia-Graphics-Driver
$ nvidia-smi Fri Jul 19 21:53:48 2019 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 390.116 Driver Version: 390.116 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce GTX 108... Off | 00000000:01:00.0 On | N/A | | 33% 30C P8 24W / 250W | 599MiB / 11175MiB | 1% Default | +-------------------------------+----------------------+----------------------+ | 1 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A | | 33% 31C P8 16W / 250W | 2MiB / 11178MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | 0 2117 G /usr/lib/xorg/Xorg 40MiB | | 0 2183 G /usr/bin/gnome-shell 50MiB | | 0 2975 G /usr/lib/xorg/Xorg 334MiB | | 0 3116 G /usr/bin/gnome-shell 170MiB | +-----------------------------------------------------------------------------+
Docker
$ docker version >>> Client: Version: 18.09.4 API version: 1.39 Go version: go1.10.8 Git commit: d14af54266 Built: Wed Mar 27 18:35:44 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 18.09.4 API version: 1.39 (minimum version 1.12) Go version: go1.10.8 Git commit: d14af54 Built: Wed Mar 27 18:01:48 2019 OS/Arch: linux/amd64 Experimental: false
Nvidia-docker
$ nvidia-docker version >>> NVIDIA Docker: 2.0.3 ...
미니쿠베에서는 기본 런타임을 Nvidia-Docker가 아닌 Docker-CE로 인식한다. GPU 사용을 위해서 도커의 기본 런타임을 Nvidia-Docker로 변경해준다.
/etc/docker/daemon.json
파일에서 default-runtime
을 nvidia
로 변경한다.
{ "default-runtime": "nvidia", "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } }}
변경이 다 되었다면, 도커를 재시작한다.
$ sudo service docker restart
미니쿠베를 실행한다. 필요한 옵션에 대해서는 표에 설명해 두었다.
$ sudo -E minikube start --vm-driver=none --apiserver-ips 127.0.0.1 --apiserver-name localhost --docker-opt default-runtime=nvidia --feature-gates=DevicePlugins=true --kubernetes-version v1.15.0>>> minikube v1.2.0 on linux (amd64) Creating none VM (CPUs=2, Memory=2048MB, Disk=20000MB) ... Configuring environment for Kubernetes v1.15.0 on Docker 18.09.4 ▪ opt default-runtime=nvidia ▪ kubelet.resolv-conf=/run/systemd/resolve/resolv.conf Downloading kubeadm v1.15.0 Downloading kubelet v1.15.0 Pulling images ... Launching Kubernetes ... Configuring local host environment ...⚠️ The 'none' driver provides limited isolation and may reduce system security and reliability.⚠️ For more information, see: https://github.com/kubernetes/minikube/blob/master/docs/vmdriver-none.md⌛ Verifying: apiserver proxy etcd scheduler controller dns Done! kubectl is now configured to use "minikube"
name | description |
---|---|
—docker-opt default-runtime=nvidia | 미니쿠베의 기본 도커를 엔비디아 도커로 설정한다 |
—feature-gates=DevicePlugins=true | GPU 지원은 쿠버네티스에서 알바/베타 단계에 속한다. 따라서 이를 사용하기 위해서는 feature-gates 옵션을 이용해서 GPU 사용 옵션을 변경해줘야한다 |
—kubernetes-version v1.15.0 | NVIDIA 드라이버를 쿠버네티스와 연결해주는 k8s-device-plugin$^{[18]}$은 1.10이상의 쿠버네티스 버전을 요구한다 |
미니쿠베 작동을 확인한다.
$ kubectl get pods --all-namespacesNAMESPACE NAME READY STATUS RESTARTS AGEkube-system coredns-5c98db65d4-nks96 1/1 Running 0 149mkube-system coredns-5c98db65d4-ns9dr 1/1 Running 0 149mkube-system etcd-minikube 1/1 Running 0 148mkube-system kube-addon-manager-minikube 1/1 Running 0 148mkube-system kube-apiserver-minikube 1/1 Running 0 148mkube-system kube-controller-manager-minikube 1/1 Running 0 148mkube-system kube-proxy-wdhfd 1/1 Running 0 149mkube-system kube-scheduler-minikube 1/1 Running 0 148mkube-system storage-provisioner 1/1 Running 0 149m
CrashLoopBackOff Error
만약 kube-system의 coredns에서 CrashLoopBackOff Error가 발생한다면, coredns 설정에서 Corefile안의 loop를 삭제한다.
$ kubectl -n kube-system edit configmap coredns>>># Please edit the object below. Lines beginning with a '#' will be ignored,# and an empty file will abort the edit. If an error occurs while saving this file will be# reopened with the relevant failures.#apiVersion: v1data: Corefile: | .:53 { errors health kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop -> remove this line reload loadbalance }kind: ConfigMapmetadata: creationTimestamp: "2019-07-19T10:34:27Z" name: coredns namespace: kube-system resourceVersion: "189" selfLink: /api/v1/namespaces/kube-system/configmaps/coredns uid: 8aa1d75a-0986-457f-81d6-d0339308a98a
이제 기존의 포드(Pods)를 삭제하고 새로운 설정이 적용된 파드를 생성한다.
$ kubectl -n kube-system delete pod -l k8s-app=kube-dns
Check GPU status
미니쿠베의 GPU 마운트 상태를 확인한다. 지금은 GPU가 <none>
인 것을 확인할 수가 있다.
$ kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu">>>NAME GPUminikube <none>
k8s-device-plugin$^{[18]}$을 미니쿠베에 적용한다. k8s-device-plugin은 미니쿠베에서 GPU 디바이스를 인식할 수 있게 해준다.
$ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta/nvidia-device-plugin.yml$ kubectl get pods --all-namespaces>>>NAMESPACE NAME READY STATUS RESTARTS AGE...kube-system nvidia-device-plugin-daemonset-4xlfc 1/1 Running 0 146m...
k8s-device-plugin이 Running상태로 바뀌였다면, GPU 상태를 다시 한번 확인해본다. GPU의 개수가 2개로 변경되었음을 확인할 수 있다.
$ kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu">>>NAME GPUminikube 2
GPU 컨테이너를 생성할 yaml파일을 생성한다. 이는 실제로 컨테이너가 미니쿠베에서 실행되었을 때, 제대로 작동하는지 확인하기 위함이다.
GPU-demo.yaml
apiVersion: v1kind: Podmetadata: name: gpuspec: containers: - name: gpu-container image: nvidia/cuda:9.0-runtime command: - "/bin/sh" - "-c" args: - nvidia-smi && tail -f /dev/null resources: requests: nvidia.com/gpu: 2 limits: nvidia.com/gpu: 2
미니쿠베에 컨테이너를 띄워보자.
$ kubectl apply -f GPU-demo.yaml>>>pod/gpu created$ kubectl get pods -n default>>>NAME READY STATUS RESTARTS AGEgpu 1/1 Running 0 157m$ kubectl logs gpu>>>+-----------------------------------------------------------------------------+| NVIDIA-SMI 390.116 Driver Version: 390.116 ||-------------------------------+----------------------+----------------------+| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. ||===============================+======================+======================|| 0 GeForce GTX 108... Off | 00000000:01:00.0 On | N/A || 33% 30C P8 24W / 250W | 599MiB / 11175MiB | 1% Default |+-------------------------------+----------------------+----------------------+| 1 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A || 33% 31C P8 16W / 250W | 2MiB / 11178MiB | 0% Default |+-------------------------------+----------------------+----------------------++-----------------------------------------------------------------------------+| Processes: GPU Memory || GPU PID Type Process name Usage ||=============================================================================|| 0 2117 G /usr/lib/xorg/Xorg 40MiB || 0 2183 G /usr/bin/gnome-shell 50MiB || 0 2975 G /usr/lib/xorg/Xorg 334MiB || 0 3116 G /usr/bin/gnome-shell 170MiB |+-----------------------------------------------------------------------------+
조금 더 확실하게 하기 위해서 미니쿠베에 배포한 컨테이너에 접속해보자. 접속 한 후에는 nvidia-smi
와 nvcc -V
명령어로 GPU가 잘 연결되어있는지 확인한다.
$ kubectl exec gpu -it g -- /bin/bash>>> root@gpu:/# nvcc -V >>> nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2017 NVIDIA Corporation Built on Fri_Sep__1_21:08:03_CDT_2017 Cuda compilation tools, release 9.0, V9.0.176 root@gpu:/# nvidia-smi +-----------------------------------------------------------------------------+ | NVIDIA-SMI 390.116 Driver Version: 390.116 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce GTX 108... Off | 00000000:01:00.0 On | N/A | | 33% 30C P8 24W / 250W | 599MiB / 11175MiB | 1% Default | +-------------------------------+----------------------+----------------------+ | 1 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A | | 33% 31C P8 16W / 250W | 2MiB / 11178MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | 0 2117 G /usr/lib/xorg/Xorg 40MiB | | 0 2183 G /usr/bin/gnome-shell 50MiB | | 0 2975 G /usr/lib/xorg/Xorg 334MiB | | 0 3116 G /usr/bin/gnome-shell 170MiB | +-----------------------------------------------------------------------------+
필자는 쿠버네티스와 도커환경이 익숙하지 않아서 생각보다 많이 헤맸는데, 매 스텝 스텝 작업할 때마다 에러가 발생하면 kubectl describe
, kubectl logs
, minikube logs
lspci -nn | grep -i nvidia
를 열심히 사용해서 디버깅을 진행하였다.
kubectl describe
, kubectl logs
는 미니쿠베의 파드(Pods)의 상태 및 로그 메세지를 확인할 수 있다minikube logs
는 미니쿠베 자체의 로그 정보를 확인할 수 있다lspci -nn | grep -i nvidia
는 연결되어있는 디바이스 정보를 확인할 수 있다. 컨테이너에서 이를 확인하기 위해서는 별도의 Dockerfile을 작성해서 lspci util을 설치해야 컨테이너 내부에서 사용이 가능하다미니쿠베로 작업을 하다가 잘못되어서 혹은 재현을 위해서 재설치를 하는 경우가 종종 있다. 이 때 기존의 설정 파일 삭제를 해줘야한다. 그렇지 않으면 이전에 설정들이 같이 따라와서 이전 작업에서 발생한 에러가 그대로 발생하는 경우가 있다. 대표적으로 아래 파일들을 삭제했는지 꼭 확인하자.
~/.kube
~/.minikube
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf