2019년 5월 11일 토요일

Signal Processing For Communications (0)

이 시리즈는 signal processing을 학부 때 배웠으나 여러 이유로 이해를 잘 하지 못하다가 뒤늦게서야 유용성을 깨닫고 개인적으로 공부하며 정리한 흔적입니다.

어느날 문득 결국 machine learning을 하든 deep learning을 하든 모두 신호를 다루기 위한 도구일 따름이고, 주로 다루는 신호가 이미지라는 형태를 띄고 있을뿐 근본적으로 디지털 신호 처리에서 다루는 내용에서 벗어나지 않는다는 생각이 들었습니다.

이런 맥락에서 신호 처리에 대해 좀 더 잘 알고 싶다는 생각에 정리를 시작하였는데, 대부분의 글은 주로 EPFL의 Martin Vetterli 교수님의 textbook을 기반으로 요약하되 제가 여기저기서 찾아 이해한 내용들로 주석을 달거나 내용을 추가했습니다.

앞으로 쓸 글들은 (얼마나 걸릴지는 모르겠지만) 모두 신호처리의 기본에 대해 대학교 학부생을 위한 기초 수준으로 작성될 것입니다. 제 스스로가 그 이상을 설명할만큼 잘 알고 있다고 생각하지도 않는만큼, 차후 시간이 흘러 다시 이 글을 봤을 때도 이해가 쉽게 하겠다는 목적을 갖고 글을 정리해보고자 합니다.

약간의 선형대수학, 신호처리에 대한 기초 지식 그리고 더 나가서 해석학을 들어본 적이 있다면 보다 깊은 이해에 도움이 될 것이지만, 그런 선행 지식이 없이도 어렵지 않게 이해할 수 있는 수준으로 작성하고자 노력하였습니다.

간단한 예시 그리고 덜 형식적인 설명을 바탕으로 신호처리라는 딱딱한 주제에 대해 쉽게 접근할 수 있도록 작성하였으며, 이해하기 쉬운 설명을 위해 수학적 엄밀성 강조하지는 않겠지만 최소한 틀리지 않는 정확한 설명을 하는 것에 주안점을 두었습니다. 계획된 목차는 아래와 같습니다.

목차 (planned)



다만, 꼭 순서대로 글이 작성되리라는 보장은 없으며, 각 글의 제목 뒤에 붙은 숫자는 책에서 해당 내용이 다뤄진 chapter를 따랐습니다. 따라서 모든 chapter를 정리하지는 않을 예정이므로 숫자가 다 채워질 이유도 순서대로 나열될 이유도 없지요. 이 preface 글의 목차는 글을 써가면서 매번 업데이트 될 예정입니다.

마찬가지로 읽는 분도 원하시는 것만 골라 읽으실 수도 있을 것이고, 순서대로 차근차근 읽으실 수도 있을 것이나, 제가 염두에 두고 쓴 순서를 추천드리자면, 먼저 (1)을 읽으시고 (9-1)로 넘어가신 후 다시 (2)로 돌아가서 쭉 순서대로 읽으시는 것을 권해드립니다. 개인적으로는 그렇게 하는 것이 신호처리를 배우는 목적을 이해하고 전체적인 감을 잡는 것에 훨씬 도움이 된다고 생각합니다.

현재 이 글은 서문과 같은 역할이므로 chapter 0라 임의로 칭했고, 따라서 제목이 다음과 같습니다; Signal Processing For Communications (0)



Signal Processing For Communications (1)

What Is Digital Signal Processing?


신호(signal)와 신호 처리(signal processing) 대해서 정의를 내리자면 각각 다음과 같습니다:
신호: 시간 혹은 공간에 대해 변화하는 현상에 대한 formal description
신호 처리: 신호에 들어있는 정보를 바꾸거나 분석하는 any operation.
예를 들어 주변 온도를 우리가 Celsius degree라는 물리적 변수를 기준으로 시간에 따른 온도의 변화를 기록하는 경우, 이렇게 만들어진 data set은 온도 "신호"가 될 것입니다. 이 신호에 대한 가장 단순한 "처리"로는 월간 온도 평균과 같은 어떤 파라미터를 계산하는 것이 있겠죠.

또한 신호 처리는 어떤 물리적인 값 자체에 직접 가해지는 것이 아니라 물리적인 값의 "abstract representation"을 기반으로 수행된다는 점이 중요합니다. 이런 abstract representation의 방식에 따라서 신호 처리의 기본 단위(unit)가 정해지게 됩니다.

한편 "디지털 (digital)"이라는 수식어구는 라틴어 digitus에서 유래한 것으로 손가락을 의미하는데, counting은 가장 기초적이고 오래된 abstraction이라 합니다. 즉, 디지털 신호 처리는 시간을 포함한 모든 것들에 정수(integer number)와 같이 countable한 abstraction representation을 사용한다는 것을 의미합니다.

좀 더 구체적 예시로는, 주변 온도를 측정한 각각의 관측(instants)이 셀 수 있는 집합(the days in a month)을 이루고 각 관측값(measure)들 역시도 온도계의 눈금 단위와 같이 유한한 수의 집합으로 표현되는 것을 생각해보면 되겠습니다.

재미있는 점은 디지털 신호 처리에서는 신호가 "어디서 유래한 것인가에 관계없이" 이를 "정수로 표현 가능한" abstract representation을 사용한다는 것인데요. 지금 당장은 이 사실이 별달리 중요해보이지 않을 수 있으나, 이 특징이 디지털 신호처리가 지금과 같이 크게 발전할 수 있었던 큰 동력이라는 점은 차차 글이 진행됨에 따라 분명해지리라 생각합니다.

Analog vs. Digital worlds


세계에 대한 "digital" 혹은 정수를 이용한 표현 방식은 우리가 다루는 문제가 가축이나 날짜를 세는 것과 같이 간단할 때까지는 아무 문제가 없이 잘 동작했으나, 점차 세상이 복잡해지고 이를 설명하는 모델 역시도 복잡해질 필요가 생기면서 한계에 부닥쳤습니다.

신호 처리 쪽 용어로 얘기하자면, 정수로 표현되는 세계가 "analog"와 같이 연속적인 세계를 설명하는 잣대로 사용하기에는 너무 초보적이고 거칠어서 마치 정밀한 시계를 다루는 시계공이 못을 박는 망치를 들고 있는 것과 같다는 것입니다.

문제는 무한대와 무한소로 나눠질 수 있는 연속적인 analog 세계의 analytical 모델을 사용하면 이론적으로 분석하기는 편할지언정, 실제로 이를 사용하기 위해서는 언제나 유한하고 이산적인 digital 세계로 내려와야한다는 점입니다.

예를 들어 온도를 측정하는 것만해도, 우리가 얻을 수 있는 것은 언제나 일정 간격(time)을 두고 측정한 관측값들일 뿐 임의의 시간에 대해 해당하는 온도에 대한 관계를 보여주는 analytical 모델이 아니죠.

따라서 analog와 digital representation이 서로 만족할만한 합의에 이르기 위해 부단한 노력들이 있어 왔고, series expansion이나 numerical integration 등의 알고리즘들이 analytic 결과를 practically computable한 형태로 만들기 위한 노력의 예시들이라 하겠습니다.

디지털 신호 처리가 멋진 것은 이렇게 양분된 두 세계가 서로 가장 만족스러운 형태로 합의에 이를 수 있도록 한다는 점입니다.

Discrete Time


아날로그 기록 방식의 가장 큰 문제점은 신호를 추상화 하여 기록하는 것이 아닌 하나의 물리적인 현상을 또 다른 물리적 현상으로 옮기는 것에 불과하다는 점인데요. 이 때문에 근본적으로 아날로그 신호는 기록(recording)의 형태에 따라 각각 다른 신호 처리 시스템이 필요하게 됩니다. 예를 들어 우리가 온도 변화 함수 $f(t)$를 알고 있고,

Analytical and empirical averages

일정 간격 $[T_0, T_1]$ 사이에 일어난 온도 변화의 평균값을 알고싶다면 이에 대한 analytical solution은 다음과 같은 적분 방적식을 푸는 것이 됩니다: $$\bar{C} = \frac{1}{T_1-T_0}\int_{T_0}^{T_1} f(t) dt.$$ 그러나 analytic model이 없는 현실에서는 어떤 기기를 사용하여 온도를 측정, 기록하였을 것이고 그 데이터를 가지고 평균 온도를 계산할텐데요. 만약 온도가 thermograph를 이용하여 그래프의 형태로 기록되었다면 plainmeter라는 면적을 구하는 기계적 도구를 사용하여 면적을 알 수 있을 것입니다. 그러나 온도 변화가 thermocouple과 같이 전압을 이용하여 기록을 하는 경우 학부 전자기초 시간에 배우는 RC 네트워크로 voltage integration 회로를 만들어 평균값을 계산해야 할테죠. 이렇듯 아날로그 신호에서는 평균을 구하는 매우 단순한 예에서도 각 경우마다 특정한 디자인이 필요하기에 범용적으로 사용할 수 있는 방식을 고안하기 어렵다는 것을 알 수 있습니다.

한편, 디지털 방식과 같이 하루에 한 번씩 측정한 온도에 대해 평균을 구하는 것은 매우 쉽습니다: $$\hat{C}=\frac{1}{D}\sum^D_{n=1}c_n.$$ 단순히 초보적인 덧셈과 나눗셈만 수행하면 원하는 값을 얻을 수 있죠! 그렇다면,
"$\bar{C}$와 $\hat{C}$의 차이가 (만약 있다면) 얼마나 될까요?"
만약 저 자연계 어딘가에 $f(t)$라는 온도 함수가 있다는 것을 받아들인다면 하루 주기($T_s$)마다 측정한 $c_n$ 온도 측정값들은 이 함수의 $samples$라고 할 수 있습니다: $$c_n=f(nT_s).$$ 이런 맥락에서 보면 $\hat{C}$는 $\bar{C}$의 Riemann approximation이라고 할 수 있으며 앞선 질문은 이 approximation의 질 즉, continuous-time 함수에서 일부 샘플들만 취함으로써 우리가 얼마나 정보를 버렸는지에 대해 묻는 것과 같습니다.

이에 대한 정답은 놀랍게도 해당 물리적 현상이 "그렇게 빨리 변하지 않는다"는 가정 하에, 두 representation이 "완벽히 일치한다"는 것입니다. 즉, continuous-time function과 우리가 얻은 측정 샘플들 간의 정보 손실이 전혀 없다는 뜻이죠.

잠시 가정에 대한 걱정을 내려놓고, 이 사실이 얘기해주는 것에만 집중해보면 매우 놀랍게도
  1. 아날로그와 디지털 세계가 완전히 공존하는 것이 가능하다는 뜻이며 
  2. 우리가 두 세계 사이를 오갈 수 있는 매우 강력한 도구를 갖고 있다는 것입니다 (sampling theorem). 
20세기 초에 발견된 이 놀라운 정리는 우리가 가진 샘플들을 바탕으로 임의의 continous-time function를 알아내는 것이 가능하다는 것을 말해주는데요:
$$f(t)=\sum_{n=-\infty}^\infty c_n \frac{\sin(\pi(t-nT_s)/T_s)}{\pi(t-nT_s)/T_s}.$$ 따라서 이론적으로는 우리가 측정값들을 가지고만 있다면 이를 바탕으로 continous-time 형태로 표현하는 것이 가능하며, 이것은 이어서 우리가 갖고 있는 매우 강력한 수학적 도구인 미분을 사용하여 함수를 분석하는 것이 가능해진다는 것을 뜻합니다.

더 좋은 점은 continous-time에서 이루어진 미분과 같은 분석이 항상 discrete-time에 대응하는 방식이 존재하여 굳이 우리가 얻은 측정값들을 가지고 continous domain으로 옮겨서 분석한 후, 다시 discrete domain으로 내려오는 복잡한 방식을 취할 것 없이 discrete domain에서 바로 분석을 하면 된다는 것입니다.

Discrete과 continuous representations 사이의 equivalence는 우리가 샘플을 얻는 속도에 비해 다루는 신호가 얼마나 충분히 "느린가"에 달려 있습니다. 즉, 연속된 샘플을 측정하는 사이에 신호가 갑자기 이상하게 움직이지 않고 충분히 부드럽게 (smooth and well behaved) 움직인다면 문제가 없다는 뜻입니다.

그래서 sampling theorem이 해주는 역할은 (좀 더 쉽게 설명하자면) 신호가 갖는 최대 주파수와 우리가 얼마나 자주 혹은 빨리 샘플을 얻어야 하는지에 대한 정량적인 기준을 알려주는 것입니다. 대다수의 학부 수준 디지털 신호 처리 과목의 반절 혹은 그 이상은 이 sampling theorem을 배우기 위한 준비와 theorem의 의미에 대해 공부하는 것이겠습니다. 특히 주파수 영역은 Fourier transform을 사용하여 알아낼 수 있기에 이를 신호 처리 과목에서 중요하게 다루며 배우는 것이라 할 수 있는데, 재미있는 점은 Fourier transform이라는 것 자체가 주기성을 띄는 함수들을 "셀 수 있는" 값들로 표현하기 위한 도구로서 사용된다는 것입니다. 도구만 달라졌을뿐 앞에 손가락으로 숫자를 세던 것이 생각나지 않으시나요?
"Everything comes together."

Discrete Amplitude


시간 연속성에 대한 문제는 sampling theorem으로 어느 정도 해결이 되었지만, 여전히 남아있는 문제가 하나 있습니다. 현실 세계의 한계로 인해 실제 측정을 할 때 생기는 오차는 우리가 어찌 할 수 없는 문제이지요.

만약 우리가 analytical model을 다룬다면 시간축뿐만 아니라 함수 값 역시도 연속적인 성격을 갖고 있는데요. 그러나 현실에서는 절대로 이와 같은 무한대의 정밀성을 얻을 수 없다는 것은 자명합니다. (아무리 온도계의 눈금을 잘게 쪼개어 기록을 하더라도 한계가 있는 것처럼)

따라서 실제로는 우리가 얻는 측정값들도 결국 유한한 숫자들의 집합이고, 그렇다면 이들은 셀 수 있기에 정수로 mapping하는 것이 가능해집니다. 이러한 과정을 quantization이라고 부르고 이는 sampling과 함께 digital signal을 얻는데 필수적인 요소가 되는데요.

Quantization은 정보 손실을 어쩔 수 없는 것으로 받아들인다는 점에서 연속체 문제를 시간에 비해 매우 거칠게 해결하는 것이라 할 수 있습니다. 여기에는 그럴 수 밖에 없는 이유가 있는데 그게 바로 신호 처리를 하다보면 언제나 만나게 되는 "noise"입니다.

우리가 어떠한 기계적 기록 장치를 쓴다고 해도 아날로그 기록을 하는 기기라면 언제나 noise가 함께하게 됩니다. Noise는 자연에서 오는 것이고 이를 완전히 제거하는 것은 불가능하기 때문에 신호 처리를 할 때 일정 수준의 정밀성으로 만족하는 정도로 합의를 하는 것이죠.

문제는 noise가 단순히 측정에서만의 문제가 아니라 처리를 할 때도 함께한다는 점입니다.
여기서 디지털 신호 처리의 또 다른 장점이 나오는데, 디지털 신호 처리는 언제나 셀 수 있는 정수의 수열을 다루기 때문에 디지털 영역에서는 processing으로 인한 noise가 생기지 않습니다.

매우 자명한 예로 신호를 복제하는 것을 생각해보면, 테이프를 복사하는 것은 원본 테이프를 복사본과 그 복사본을 이용한 다음 복사본으로 넘어갈 때마다 추가적인 nosie가 더해져서 점점 음질이 열화되지만 mp3의 경우 원본과 복사본이 근본적으로 차이가 없다는 것을 알 수 있습니다.

Terminology


마지막으로 용어에 대해 한가지 짚고 넘어가겠습니다. Amplitude에 대한 정확성은 사실 하드웨어에 달린 문제로, 예를 들자면 CD와 DVD는 서로 precision 즉 샘플 당 담을 수 있는 정보량에 차이가 있습니다. 이렇게 amplitude에 대한 정밀성은 하드웨어에 의존적이므로 사실상 신호 처리 이론에 대해 배우거나 개발할 때는 quantization을 고려하지 않고 마치 연속된 실수 값인 것 마냥 취급하게 됩니다. 따라서 사실상 우리가 앞으로 배우는 것은 엄밀히 말하자면 모두 discrete-time signal processing이라 불러야 맞고 digital signal processing은 실제 기기의 영역에서 이뤄지는 일임을 알아야 합니다. 그러나 quantization을 고려하지 않는 것이 좀 더 이론적으로 다루기도 쉽고 일반적인 분석이 가능하기 때문에 이를 잘 구별하지 않고 digital signal processing이라 얘기한다는 점을 분명히 알아야겠습니다.

아 마지막으로 글을 읽는데 순서를 추천드리자면 이 다음에 (9-1)로 넘어가시는 것을 권해드립니다. 그렇게 하는 것이 신호처리를 배우는 목적을 이해하고 전체적인 감을 잡는 것에 훨씬 도움이 된다고 생각합니다. 

To be continued ... (planned)


  • Preface 
  • What is Digital Signal Processing? ($\leftarrow$)
  • Discrete-Time Signals (2)
  • Signals and Hilbert Spaces
    • Euclidean Spaces and Hilbert Spaces (3-1) 
    • Subspaces, Bases, Projections, Haar basis (3-2)
  • Fourier Analysis
  • Interpolation and Sampling (9-1 $\leftarrow$ next to read)
    • Interpolation
    • Sampling theorem
    • Aliasing
  • Multirate Signal Processing
    • Downsampling
    • Upsampling
    • Oversampling



2019년 5월 7일 화요일

공이 점점 비눗방울처럼 변할 때 (When ball becomes a soap bubble)

공이 점점 비눗방울처럼 변할 때


이전에 소개했던 박스 안에 넣은 공의 지름이 박스보다 클 때처럼 고차원으로 갈 때 우리의 직관이 얼마나 달라질 수 있는지를 알려주는 또 다른 좋은 예시를 소개해보자.

구의 부피


또다시 공(ball)이다! 수학적인 용어에서의 공은 간단히 말해 겉껍질이 자기보다 한차원 낮은 구(sphere)로 쌓여있는 닫힌 공간 전체, 즉, 안이 꽉 찬 공간을 뜻한다. 1차원 공(ball)은 선(line segment)이고 2차원 공은 원반(disk), 3차원 공은 음...공(ordinary ball)이다. 대응되는 구(sphere)를 생각해보면 0차원 구는 시작과 끝 점(point), 1차원 구는 원(circle), 2차원 구는 구(ordinary sphere)다.

이런 공의 부피를 바탕으로 초등학교 시절 배운 내용 수준만으로 아주 쉽고 간단하게 고차원에서는 직관이 우리를 배반한다는 것을 보일 수 있다.

이전 글과 같이 먼저 쉽고 우리 직관이 잘 통하는 2차원에서부터 시작해보자:


우리 모두 초등학교 때, 원의 부피, 즉 2차원에서의 넓이를 구하는 것은 배웠을 것이다: $$V_2(r)=\pi r^2.$$ 한 차원 더 나가서,


3차원 공의 부피는 $$V_3=\frac{4\pi}{3}r^3$$이라는 것도 열심히 외웠을 것이다.

그리고 아마도 이걸 $d$차원에 대해 일반화하는 공식은 테이블 형태로 "심화 학습" 뭐 이런 형태로 가볍게 보여주고 지나갔을 것이다: $$V_d(r)=k_d r^d.$$ 여기서 $k_d$는 상수다.

구각 (Spherical shell)


이제부터 좀 재미있는 실험을 할텐데, 원점을 중심으로 반지름이 1인 구와 반지름이 $1-\epsilon$으로 그보다 아주 약간 ($\epsilon\ll 1$만큼) 공 두 개를 준비하고 이 두 공 부피의 차를 구해보자: $$V_d(1) - V_d(1-\epsilon).$$
이걸 겉 껍데기를 구하는 것이라 해서 구각(spherical shell)이라 하는데 두 공의 반지름의 차이가 $\epsilon$만큼 나기 때문에 우리가 생각하는 겉껍질(구각)이 차지하는 부피는 매우 작다.

만약 정확히 그 비율이 얼마나 되는지 알고 싶다면 반지름이 1인 구와 위에서 구한 구각의 비율을 구하면 될텐데 이 비율은 간단히: $$\frac{V_d(1) - V_d(1-\epsilon)}{V_d(1)}=1-(1-\epsilon)^d$$가 될 것이다.

이제 준비물은 모두 모았으니 사고 실험을 해보면 재미있는 일이 벌어지는 것을 알 수 있다.  점점 고차원으로 갈수록 ($d\rightarrow \infty$) 두번째 항의 값이 0에 가까워지고 공과 구각의 비율이 1로 수렴한다! 즉, "공이 점점 비눗방울처럼 바뀌는 것" 이다.

모든 부피가 껍데기에만 몰려있고 안이 텅텅 비어있는 매우 요상한 "속이 꽉찬" 공이 될 것이다. 이 역시도 고차원으로 넘어갈 때, 우리의 직관이 얼마나 틀릴 수 있는지 보여주는 좋은 예시로 이 글을 읽는 다른 분들에게도 brain candy가 되었길 기대한다.

딴 이야기 

(for those who are interested in GANs)


재미있는 GAN blog 글로 유명한 inFERENCe가 "Gaussian Distributions are Soap Bubbles"라는 제목으로 글을 써서 화제가 된 적이 한 번 있는데, 생각보다 복잡하게 설명을 해서 이해하기 어려울 수 있지만 사실 지금 한 얘기를 다른 방식으로 열심히 적은 것이다.

GAN 모델을 학습시킨 다음 High dimensional Gaussian latent space에서 walking을 하기 위해 두 latent vector간의 interpolation을 할 때, 왜 linear interpolation을 하면 문제가 될 수 있는지 이 글을 읽으신 분들이 이해가 쉽게 될 것이라 생각한다.

어떤 의미에서는 중간이 텅 비어있는데 겉껍질을 타고(polar) 움직여야지(interpolate) 중간을 쑥 뚫고(linear) 움직이면 본적이 없는 latent vector가 model로 들어갈 수 있기 때문이다.

다음 읽을거리





2018년 9월 2일 일요일

[The Art of Readable Code, 읽기 좋은 코드가 좋은 코드다] 2. 이름에 정보 담기

표면적인 수준에서의 개선



"표면적 수준이란 좋은 이름을 짓고, 좋은 설명을 달고, 코드를 보기 좋게 정렬하는 따위를 의미한다."

책의 첫 단락은 표면적인 수준에서의 개선부터 시작합니다. 이런 수정은 코드를 통째로 바꾸거나 동작하는 방식을 변화시키지 않고 '그 자리에서' 곧바로 만들 수 있기에 첫 시작으로 매우 적절하다 생각합니다.

물론 가독성에 관련된 논의는 이 수준보다 더 나아가 많은 내용을 담고 있겠으나 이는 차차 살펴갈 것이며 먼저 1부에서는 폭넓게 적용할 수 있고, 그다지 많은 노력을 요구하지 않는 내용을 우선적으로 다룹니다.

이름에 정보 담기


변수, 함수, 혹은 클래스 등의 이름을 결정할 때는 항상 같은 원리가 적용합니다. 
"이름을 일종의 설명문으로 간주해야 한다."
충분한 공간은 아니지만, 좋은 이름을 선택하면 생각보다 많은 정보를 전달할 수 있다는 것이죠. 구체적으로는 아래의 여섯 가지 방법을 제안합니다.
  • 특정한 단어 고르기
  • 보편적인 이름 피하기 (혹은 언제 그런 이름을 사용해야 하는지 깨닫기)
  • 추상적인 이름 대식 구체적인 이름 사용하기
  • 접두사 혹은 접미사로 이름에 추가적인 정보 덧붙이기
  • 이름이 얼마나 길어져도 좋은지 결정하기
  • 추가적인 정보를 담을 수 있게 이름 구성하기
앞으로는 책에 나온 내용을 모두 다 소개하기 보다는 개중 제가 재미있었던 내용들을 좀 골라서 예시와 함께 알아보겠습니다. 

특정한 단어 고르기


매우 구체적인 단어를 선택하여 "무의미한" 단어를 피하자. 

예를 들어 "get"은 지나치게 보편적입니다.

def GetPage(url):
    ...

여기서 "get"보다는 메소드가 어디에서 페이지를 가져오는 지 알려줄 수 있게 FetchPage() 혹은 DownloadPage()와 같이 구체적으로 명명하는 것이 더 좋습니다.

사실 위 예시보다 다음 예시가 더 좋았는데요. 다음과 같이 BinaryTree 클래스에서

class BinaryTree {
    int Size();
    ...
}

우리는 Size() 메소드가 반환하는 것이 무엇일 지 이름만 봐서는 알 수 없습니다. 트리의 높이, 노드의 개수, 혹은 트리의 메모리 사용량이 될 수도 있겠죠.  따라서 Height(), NumNodes(), 혹은 MemoryBytes() 등이 더 의미 있는 이름이라는 것에는 모두 동의하리라 생각합니다. 

같은 맥락에서 저자들은 thesaurus를 뒤져보고 더 나은 이름을 생각하기를 권합니다. 다만 너무 "재치" 있는 이름보다는 명확하고 간결한 이름이 더 좋습니다. 다음에 이어지는 내용들도 사실 같은 내용인데 예제들과 소소한 팁 위주로 살펴보곘습니다.

tmp나 retval 같은 표편적인 이름 피하기


"변수값을 설명하는 이름을 사용하라"

예를 들어, 다음과 같이 Euclidean norm을 계산하는 자바스크립트 코드에서

var euclidean_norm = function (v) {
    var retval = 0.0;
    for (var = i = 0; i<v.length; i+=1)
        retval += v[i];
    return Math.sqrt(retval);
};

retval보다는 sum_squares라고 이름을 붙여준다면 변수의 목적을 바로 이해할 수 있으며 나중에 버그를 잡을 때도 용의합니다.

retval += v[i]; 부분이 sum_squares += v[i]; 였다면 훨씬 눈에 잘 띄었겠죠.

물론 아래와 같이 정말로 대상이 짧게 임시적으로만 존재하고, 임시적 존재 자체가 변수의 가장 중요한 용도일 때는 tmp와 같은 변수를 사용할 수 있겠습니다.

두 변수를 서로 교환하는 알고리즘 예:

if (right<left) {
    tmp = right;
    right = left;
    left = tmp;
}

같은 맥락으로 i, j, iter, it 같은 이름이 인덱스나 루프 반복자로 사용되는 것은 충분히 괜찮습니다. 다만 이 역시도 디버깅의 용이성을 위해서 아래와 같이 소속을 표현해준다면 더 좋겠죠.

(i, j, k) -> (club_i, members_j, users_i) or (ci, mj, ui)

활용 예:
if (clubs[ci].members[ui] == users[mi]) # 버그! 처음 문자가 일치 하지 않는다.

따라서 표편적인 이름이 항상 나쁜 것은 아니지만, 이를 사용하려면 꼭 그렇게 해야하는 이유가 있어야 합니다. 


추가적인 정보를 이름에 추가하기 


단위(sec, millisecond, kg 등)를 포함하거나 다른 중요한 속성(unsafe, utf_8 등)이 있을 때는 변수에 그런 내용을 추가해주면 좋습니다. 

start -> start_ms, elapsed -> elapsed_ms
html -> html_utf-8 # html의 바이트가 UTF-8으로 변환되었다. 

이름은 얼마나 길어야 하는가?


만일 변수가 좁은 scope (예: 끽해야 몇 줄 안의 함수 scope)에서 사용된다면 멤버 변수가 "m"과 같이 매우 짧은 이름을 사용해도 별 문제가 없으나 이 변수의 scope이 클래스나 전역으로 넓어지면 가독성이 매우 떨어지게 되므로 상황에 따라 잘 사용하라고 하는군요. 

게다가 요즘은 긴 이름을 입력하는 것이 자동완성 기능으로 매우 편해져서 그리 주저할 일이 아닙니다. 그렇기 때문에 약어와 축약형은 매우 보편적인 경우(string-> str과 같이)를 제외하고는 지양하는 편이 좋겠습니다. 

이에 좀 더 더한다면 ConvertToString()에서 ToString()과 같이 불필요한 단어를 제거해서 간결하게 만드는 등의 팁이 있으나 앞의 내용들이 더 핵심에 가까운 것으로 보입니다. 

이로써 이름에 정보를 넣는 방법에 대해 요약해보았습니다. 

책에서 다음 장은 의미를 오해하기 쉬운 이름들에 대한 팁입니다만 사실 오늘 소개한 내용에 어느 정도 포함되는 것 같습니다. 다음 글에서는 미학(Aesthetics) 즉 "눈을 편하게" 하는 코드에 대해 정리하겠습니다. 





[PR12-Video] 71. Categorical Reparameterization with Gumbel Softmax


TensorFlowKR facebook comunity에서 모인 12명의 paper readers (PR12)가 읽어주는 Deep learning paper awesome list 100선 by Terry Um.

#71. Categorical Reparameterization with Gumbel Softmax


이 리뷰에서는 NIPS 2016 workshop에 같이 발표되었고 최근 ICLR 2017에 발표된 두 편의 논문을 리뷰하겠습니다. 재미있는 점은 이 두 편의 논문들이 똑같은 아이디어를 바탕으로 정확히 같은 수식을 사용하여 arXiv에도 고작 하루 차이로 올라왔다는 것입니다. 아이디어가 공중에 떠다닌다는 말이 정말 맞는가 싶습니다. 즐겁게 들어주시면 감사하겠습니다.


(추신) 24분 부분에 질문 주신 부분에 대해 답이 미진한것 같아 끝나고 곰곰히 생각해본 답글을 여기에 추가합니다.  둘 다 categorical dist를 만드는데 다른 방법을 사용할 뿐이라는것이 맞는 답인것 같습니다. 우리가 nn으로부터 샘플링을 하고 싶으면 logit을 받아서 softmax를 통과시켜서 확률값을 얻어서 이를 바탕으로 분포에 값을 넣어주고 그 분포로부터 샘플을 뽑는 방법이 있겠구요 (이 방법이 준범님이 말씀하신 보통의 방식인 것 같습니다. 결국 마지막 단에서 softmax하여 확률 값을 주니까요) 다만 샘플링을 하지 않고 확률값 자체를 라벨과 빼서 에러를 계산하는데 사용되는 것이라 백프롭에서는 문제가 없는것 같습니다. 자기자신으로 1이니까 그렇다고 생각하는데 혹 이상하면 말씀주세요. 그리고 두번째 방법이 logit에 검벨에서 뽑은 노이즈를 더하여 argmax를 통과시켜서 값을 얻으면 그 자체가 discrete categorical dist에서 나온 샘플입니다. 여기서 argmax를 softmax로 relaxation한 것이 gumbel softmax trick이구요 그래서 이렇게 복잡하게 과정을 거친 이유는 말씀드린 바와 같이 미분이 가능하게 해서 중간에 node가 껴있을때 gradient를 계산하기 위해서인 것으로 이해하면 되지 않을까 싶습니다.

(paper1) Categorical Reparameterization with Gumbel Softmax and
(paper2) The Concrete Distribution: A Continuous Relaxation of Discrete Random Variables

Paper1: https://arxiv.org/abs/1611.01144
Paper2: https://arxiv.org/abs/1611.00712
슬라이드: https://www.slideshare.net/thinkingfactory/pr12-categorical-reparameterization-with-gumbel-softmax

다음에 또 다른 주제로 뵈어요~!

다른 분들의 발표도 보고 싶다면: PR12 딥러닝 논문읽기 모임

다음 읽을거리






2018년 9월 1일 토요일

[The Art of Readable Code, 읽기 좋은 코드가 좋은 코드다] Intro. 코드는 이해하기가 쉬워야 한다.

많은 분들이 그러실텐데 저 역시도 항상 좋은 코드란 어떤 것인지 알고 싶었습니다. 이런 고민을 듣고 최근 회사 동료인 전상혁님이 "The Art of Readable Code"라는 책을 추천해주시기에 책을 도서관에서 빌려 읽고 있는데 정말 많이 배우고 있습니다. 책의 내용이 좋아서 한 권 사서 두고두고 읽으려 합니다.

이런 내용들을 코드에 직접 적용하면서 체득하는 것이 가장 좋겠지만 당장 단기간에 이뤄질 수 있는 일은 아니기에, 일단은 좋은 내용들이 머리에 좀 더 오래 남기를 바라며 책 내용을 정리해서 올리고자 합니다.

나중에 이 글을 찾은 분 혹은 미래의 나 스스로에게 초심자의 입장에서 어떤 점들이 도움이 되었는지를 보여줄 수 있을거라 기대합니다.

이 책은 무엇에 대한 것인가?


이 책은 매우 읽기 편한 코드를 작성하는 방법을 설명하는데요. C++, 파이썬, 자바스크립트, 자바 등을 포함한 다양한 언어로 작성된 코드를 예로 들며 설명해줍니다. 중간중간 껴있는 삽화들도 매우 재치있고 각 장의 주제와 연관되어 있어 이해를 도와줍니다.

재밌는 점은 언어들을 다 알지 못하더라도 책을 읽는 데는 별 어려움이 없다는 것입니다.
저자들이 얘기하기론 "코드의 가독성"이라는 개념 자체가 언어로부터 독립적이기 때문이라고 하지만 제가 보기엔 여기서 저자들의 내공이 드러나는 것이 아닌가 싶습니다.

크게 아래와 같이 4부로 나누어
  1. 표면적인 수준에서의 개선
  2. 루프와 로직를 단순화하기
  3. 코드를 재작성하기
  4. 선택된 주제들
여러 측면에서 코드를 이해하기 쉽게 만드는 방법을 설명해줍니다. 


가독성의 기본 정리

"코드는 다른 사람이 그것을 이해하는 데 들이는 시간을 최소화하는 방식으로 작성되어야 한다."

분량이 적다고 항상 좋은 것이 아닙니다. 좋은 예로 주석도 사실은 "코드를 더하는 행위"지만 코드를 더 빨리 이해하게 도와줍니다. 적은 분량으로 코드를 작성하는 것이 좋은 목표긴 하지만, 이해를 위한 시간을 최소화하는 것이 더 좋은 목표입니다.

또 다른 예로,

return exponent >=0 ? mantissa * (1 <<exponent) : mantissa / (1 << -exponent);

라는 코드보다는

if (exponent >=0) {
    return mantissa * (1 << exponent);
} else {
    return mantissa / (1 << -exponent);
}

이렇게 바꾼 코드가 앞서보다 간결하진 않지만 더 이해하기 쉽습니다.

이해를 위한 시간은 코드의 효율성, 아키텍처, 테스트의 용이성과 같은 다른 목표와 충돌할까봐 걱정할 수도 있으나, 저자들의 경험에 따르면 대다수의 경우 이러한 조건은 거의 아무런 방해가 되지 않다고 합니다.

가장 기본적인 대원칙은 코드를 "읽기 쉽게" 만드는 원리가 적용될 때마다 의심의 여지가 생기면 언제나 가독성의 기본 정리가 다른 어떤 규칙보다 앞선다는 점입니다.

"이 코드는 이해하기 쉬운가?"


만일 정리가 되지 않을 코드를 고치고 싶을 때는 먼저 뒤로 한 걸음 물러나서 스스로에게 물어보는 것이 중요합니다: "이 코드는 이해하기 쉬운가?". 만약 그렇다면 다른 코드로 건너뛰어도 별 상관이 없습니다.



2018년 8월 4일 토요일

What is the relationship between orthogonal, correlation and independence?

제게는 마주칠 때마다 헷갈려서 다시 고민하게 되는 개념들이 있는데, 그 중 대표적인 것이 바로 이 세 가지 녀석들입니다:

Orthogonality, Correlation, Independence.

오늘도 다시 한 번 마주칠 일이 있어서 또 하루종일 공부하는 우매한 짓을 저지른 후, 다시는 이러지 않도록(....이러고선 또 언젠가 다시 이 포스트를 보고 공부하겠지...뻔해...) 정리를 해보고자 합니다.

Independence


"Independence"는 통계적인 개념입니다. 두 random variables X와 Y의 joint distribution이 marginal distribution의 곱으로 표현이 될 때 statistically independent하다고 말한다. 각 variable의 density를 $f$라고 하면:
$$f(x,y) = f(x)f(y),$$
좀 더 일반적으로는 cumulative distribution function을 $F$라고 할 때, 
$$F(x,y) = F(x)F(y)$$
라고 표현할 수 있겠습니다.

Correlation


"Correlation"은 independence와 관련이 있으나 좀 더 약한 통계적 개념으로 두 random variables 간 (Pearson) correlation은 정규화된(standardized) variables의 곱의 기대값을 말합니다:
$$\begin{align*}\rho_{XY} &= \mathbf{E}\left[\frac{X-\mathbf{E}[X]}{\sqrt{\mathbf{E}[(X-\mathbf{E}[X])^2]}}\frac{Y-\mathbf{E}[Y]}{\sqrt{\mathbf{E}[(Y-\mathbf{E}[Y])^2]}}\right]\\
&= \frac{cov(X,Y)}{\sigma_X\sigma_Y}.\end{align*}$$
이 때, $\rho_{XY}=0$는 variables X와 Y가 서로 uncorrelated 되어있다는 말입니다. 한 가지 유의할 점은 두 random variables가 independent하면 항상 uncorrelated이지만 그 역은 성립하지 않는다는 점입니다. (순방향은 정의에 맞게 식을 전개해보면 되고, 역은 counter example을 들어 쉽게 증명할 수 있습니다.)

순방향에 대한 식 전개:
$$\begin{align*}\mathbf{E}[XY]&=\int\int xyP_{X,Y}(x,y)dxdy \\
& = \int\int xyP_X(x)P_Y(y)dxdy\\
&=\mathbf{E}[X]\mathbf{E}[Y] \end{align*}$$
역방향에 대한 counter examples:

여기서 한 가지 헷갈리는 부분이 나오는데요. 지금까지 얘기한 independence는 statistical independence인데 이게 linear independence랑 서로 관련이 있으면서도 다르다는 것입니다. Linear dependent한 경우 statistically dependent 입니다. 이는 $\alpha X = Y$를 만족하는 non-zero scalar $\alpha$가 있을 때,
$$cov(X,Y)=cov(\frac{1}{\alpha}Y,Y) = \frac{1}{\alpha}Var(Y) \neq 0 $$
인 것으로 확인할 수 있습니다. 그러나  X와 Y가 linear independent할지라도 $\rho_{XY}\neq 0$일 수 있기 떄문에 linear independence가 statistical independence를 보장해주지는 않죠.

Orthogonality


"Orthogonality"는 기하에서 온 개념으로 선형 대수학에서 일반적인 정의를 배울 수 있습니다.  선형대수학에서 정의하는 것을 보면, 두 벡터 $u$ 와 $v$가 서로 orthogonal하다는 것은 두 벡터 간의 내적 $<u,v>$이 정의된 내적 공간(inner product spaces)에서 다음 조건을 만족한다는 것입니다:
$$<u,v>=0.$$
즉, 어떤 벡터 간의 orthogonality는 정의한 내적에 따라 달라지기 때문에 주의해야 합니다.

내적은 여러 방식으로 정의될 수 있는데, 한 예로 벡터들이 다음과 같이 수열로 나타내질 때는 우리가 흔히 아는 dot product를 골라서 사용할 수 있겠습니다:
$$u=(u_1,u_2,\cdots,u_n), <u,v>=\sum_{i=1}^{n}u_i v_j.$$
앞서 설명을 유심히 봤으면 알겠지만 orthogonality는 본질적으로 통계적인 개념이 아닙니다.

Orthogonality는 본질적으로 통계적인 개념이 아니다!

그래서 우리가 헷갈리는 이유가 보통 선형대수학에서의 개념을 통계로 가져오면서 생기는 것에서 기인하는 경우가 많습니다.

A)


형식상 random variables의 공간은 vector space로 생각할 수 있습니다. 그러면 당연히 그 공간에서 내적을 다양한 방식으로 정의할 수도 있을텐데, 그 중 한 가지 방식이 바로 covariance를 내적으로 사용하는 것입니다:
$$<X,Y> = cov(X,Y) = \mathbf{E}(X-\mathbf{E}[X])\mathbf{E}(Y-\mathbf{E}[Y]).$$
두 random variables간 correlation이 0이면 covariance도 0이기 때문에, 이 정의에 의해서 ($\mathbf{E}[X]$나 $\mathbf{E}[Y]$ 중 하나가 0인 경우) uncorrelatedness가 orthogonality와 정확히 같아집니다. 따라서 두 random variables가 independent하면 (그리고 둘 중 하나는 zero-centered일 때) 서로 uncorrelated이며 orthogonal 하다고 얘기할 수 있습니다. 다른 방식으로는 $\mathbf{E}[XY]$으로도 내적을 정의할 수도 있습니다 (결국 같은 얘기).

다만, 앞서 얘기한 바와 같이 그 역은 항상 성립하지는 않는데요. 즉, 두 random variable이 orthogonal하다고 해서 independent하지는 않습니다. 이 부분에서 헷갈리는 것이 "음? 직교하는데 independent하지 않는 경우가 어떤게 있지?" 하는 생각이 바로 들게 되죠.

이 부분이 매우 어색하고 이상하다고 여겨지는 이유는 random variable을 어느 순간 fixed variable과 dot product를 가지고 노는 선형 벡터 쪽 영역으로 은근슬쩍 넘어가서 생각하기 때문입니다. 여기서의 직교는 내적을 covariance로 정의하였을 때를 기준으로 얘기하기 때문에 우리가 흔히 생각하던 fixed variable vectors 둘을 골라서 dot product한 기준으로 얘기하면 안 됩니다. 즉, 정의대로 orthogonal = uncorrelated인 경우만을 생각하면 uncorrelated이나 dependent인 경우는 쉽게 받아들일 수 있습니다.

예를 들어 $X$가 $\{-1,0,1\}$ 중 하나의 값을 동일한 확률로 뽑는 random variable일 때 $Y=X^2$에 대해 $\rho_{XY}=0$이지만 dependent임을 쉽게 알 수 있습니다. 사실 $X$가 0을 기준으로 symmetric pdf를 가지면 그 모든 예시에 대해 $X$와 $Y$는 서로 (covariance-wise) orthogonal하지만 dependent합니다.

B)


그러나 통계에서 다루는 모든 variables가 random variables는 아니라는 점에 주의해야 합니다. 특히, 선형 회귀 문제를 생각해보면 거기서 사용하는 입력값과 같은 독립 변수(independent variables)들은 random이 아니라 이미 "정해진" 값들입니다. Independent variables는 보통 수열로 주어지고 위에서 얘기한 바와 같이 자연스럽게 dot product를 내적으로 사용할 수 있겠습니다. 이 때, independent variables가 regression line에 대해 orthogonal인지 아닌지 등을 얘기하는데 이런 맥락에서 보면 애시당초 orthogonality는 statistical definition도 갖지 않고 random variable에 적용되는 얘기도 아니죠. (ANOVA에서의 orthogonal contrasts 등)

정리해보자면 A)에서는 uncorrelatedness와 orthogonality는 사실 같은 것에 대한 다른 이름일뿐입니다. 따라서 가장 좋은 것은 random variable에 대해 uncorrelatedness를 말할 때는 orthogonality라는 용어를 사용하지 않는 것입니다. 그리고 같은 논지로 B)의 맥락에서는 non-random variable에 대해 correlation이라는 용어를 사용하는 것을 지양하는 것이 좋겠습니다.

더 읽어볼 것...


아래 reference로 달아둔 링크 중 "Linearly Independent, Orthogonal, and Uncorrelated Variables"라는 제목의 레포트가 있습니다. Non-random variable에 대해 내적으로 dot product를 사용하여 지금까지 본문에서 바라본 statistical 관점이 아니라 대수적 혹은 기하적 관점에서 바라본 논문 형태의 레포트인데요. 내용을 매우 잘 설명한 좋은(짧은) 논문이지만, 이 경우 내적이 dot product로 달라졌으므로, orthogonality와 uncorrelatedness가 같지 않으며 자칫하면 지금까지 간신히 잡아둔 개념들이 더 헷갈릴 수 있습니다. 따라서 분명한 차이가 있다는 것을 염두에 두고 봐야 합니다.

* 그리고 위 레포트에서는 non-random variable에 대해서도 correlation의 개념을 사용합니다. 엄밀히 말하자면 이는 지금까지가 우리가 얘기했던 population에 대한 correlation coefficient가 아닌 sample correlation coefficient일 때 성립합니다. 앞서는 random variable이 표본 공간(sample space)에 대해 정의된 함수이며, 이 때 함수(random variables)들에 대한 내적을 얘기한 것이었다면, 위 레포트에서는 fixed or predefined variable 즉, sample에 대한 얘기이므로 분명히 다릅니다.

References