프로그래머는 수학을 잘해야할까?

프로그래머는 수학을 잘해야할까?


이번 포스팅에서는 필자가 많이 받은 질문 중 하나인 프로그래머는 수학을 잘해야할까?라는 질문에 대해서 한번 이야기 해볼까 한다.
물론 이 주제는 전 세계의 많은 개발자들 간에도 의견이 갈리는 내용이기 때문에 그냥 지나가는 개발자 한명의 생각일 뿐이라고 생각해줬으면 좋겠다.

사실 필자도 수학을 잘하는 편이 아니라 그냥 컴퓨터 공부하신 다른 분들처럼 학교에서 배우긴 했는데 졸업하고 나서는 잘 기억안나는 그냥 그 정도의 수준이다. 게다가 필자는 손으로 푸는 계산이 굉장히 약하기 때문에 수학 성적이 좋았던 편도 아니다.(사칙연산을 잘 틀린다.)

그래도 인터넷에서 많은 분들이 이 주제에 대해서 이야기 해주시기도 했고, 실제로 개발자가 아닌 분들에게 이런 질문을 받은 적도 있어서 필자도 한번 이 주제에 대한 필자의 생각을 끄적여볼까한다.

프로그래밍은 수학이다

일단 근본적으로 우리가 사용하고 있는 이 컴퓨터는 사실 01을 사용하는 계산기다. 그렇기 때문에 컴퓨터에는 아무래도 수학적인 내용이 많이 들어갈 수 밖에 없고, 프로그래밍을 할 때도 알게 모르게 많이 녹아있는 수학적인 개념들이 많다.

그래서 사실 필자는 프로그래머라면 수학을 조금은 할 줄 알아야한다고 생각하는 편이다. 이 얘기를 들은 여러분은 어? 난 수학을 잘 못하는데 지금 개발을 하고 있는데?라고 생각하실 수도 있겠다.

그러나 여기서 필자가 말하는 수학은 무슨 선형대수학이나 미적분같은 고등수학을 말하는 것이 아니다. 필자가 얘기하는 수학은 대부분 명제집합, n진법과 같이 이미 우리가 중고등학교에서 배웠던 정도의 수준을 이야기하는 것이다.

물론 이 개념들도 결국 파고들면 파고들수록 점점 추상적이고 어려운 개념들이 나오지만 솔직히 그렇게까지 알 필요는 없다고 생각한다. 우리가 무슨 수학을 연구하는 사람도 아니고, 우리는 그냥 프로그래머로써 프로그래밍에 필요한 정도만 알고 있으면 된다. 중요한 것은 수학이라는 키워드에 쫄지 않는 것이다.

그래서 이번 포스팅에서는 필자가 생각했을 때 프로그래밍에 도움이 되는 수학 개념 3가지에 대해서 가볍게 한번 이야기 해보려고 한다.

수학에 쫄지 말자!

요즘 핫한 주제인 머신러닝이나 인공 신경망 같은 경우 호기심을 자극하는 키워드지만, 구글링을 해보면 나오는 검색 결과는 우리의 공부 의욕을 꺾어놓는다.


해답이 어떤 데이터에 의존적인 경우, 비용은 관측값에 대한 함수가 되어야 하며, 그렇지 않을 경우에는 데이터와 관련된 어떤 것도 모델링할 수 없게 된다. 많은 경우 비용은 근사될수만 있는 통계로 주어진다.
간단한 예로, 어떤 분포 $\mathcal {D}$에서 뽑아낸 데이터 쌍 $(x, y)$에 대해 비용 $C = E[(f(x) - y)^{2}]$을 최소화하는 모델 $f$를 찾는 문제를 생각해보자. 실용적으로는 분포 $\mathcal {D}$에서 유한한 $N$개의 샘플만을 뽑아낼 수 있으므로, 이 예의 경우 $\hat{C} = \frac{1}{N}\sum_{i=1}^{N}(f(x_i) - y_i)^{2}$, 즉 전체 데이터 집합이 아니라 데이터의 샘플에 대한 비용만 최소화될 수 있을 것이다.

인공 신경망 - 학습위키 백과


난 분명 수식을 보고 있는데 숫자보다 영어가 더 많이 보이고…


솔직히 저런 수식을 처음 보면 무슨 외계어 같기도 하고 무슨 말을 하는 지 도통 알 수가 없다. 게다가 간단한 예랍시고 설명하고 있는 것은 전혀 간단하지 않게 생겼다.(빡침)

이런 것들이 바로 우리의 공부 의욕을 깎아먹는 수학의 모습이다. 하지만 저 수식이 진짜 어렵고 복잡한 의미일까?

저 기호와 알파벳이 어떤 의미인지만 알면 우리는 이 수식을 코드로 포팅할 수 있는데, 막상 짜놓고 보면 굉장히 간단하다.
그럼 딱 봤을 때 제일 복잡해보이는 $\hat{C} = \frac{1}{N}\sum_{i=1}^{N}(f(x_i) - y_i)^{2}$를 값 C를 구하는 코드로 한번 작성해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const inputs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const outputs = [10, 9, 8 ,7, 6, 5, 4, 3, 2, 1];
const N = inputs.length;

function exampleFunction (x) {
return x + 1;
}

function getC (x, y) {
let result = 0;
for (let i = 0; i < N; i++) {
result += (exampleFunction(x[i]) - y[i]) ** 2;
}
return result / N;
}

const C = getC(inputs, outputs);

쨘, 이제 아까 저 수식이 어떤 내용인지 조금 더 이해가 잘될 것이라고 생각한다. 혹시 저 수식이 어떤 내용인지 이해하지 못했더라도 코드는 이해할 수 있으니 말이다.

그럼 먼저 이상한 기호가 있어서 어려워 보이는 $\sum_{i=1}^{N}$를 한번 살펴보자.

$\sum$(Sum이라고 읽는다) 기호는 그냥 뒤에 붙어 있는 값을 반복문 돌리면서 더하라는 뜻이다. 즉, $\sum_{i=1}^{N}$은 i를 1로 초기화하고 N까지 반복문 돌리면서 값을 더해라라는 의미인 것이다. 우리가 for문을 사용할 때 관습적으로 사용하는 변수 $i$도 여기서 파생된 것이다.

그러면 $\sum$의 뒤쪽으로 오는 $(f(x_i) - y_i)^{2}$에 포함된 $x_i$나 $y_i$의 의미도 자연스럽게 이해가 될 것이다. 그냥 어떤 집합의 $i$번째 원소를 말하는 것이다.

그리고 마지막에 $\frac{1}{N}$을 곱해주는데, $N$번 반복하면서 모두 더한 값을 $N$으로 나누면 뭘까?

네, 평균입니다.

여기까지 보고나면 이제 더 이상 저 수식이 낯설게만 느껴지지는 않을 것이다. 그리고 위에서도 한번 얘기했지만 필자가 말하고 싶은 것은 저 수식을 코드로 작성할 수 있냐 없냐가 아니다. 저런 수식도 코드로 바꿔보면 그렇게 어려운 수식이 아니라는 것을 말하고 싶은 것이다. 쫄 필요가 없다!

그냥 우리에게 for문은 익숙하지만 $\sum$이 익숙하지 않을 뿐이다. 만약 어떤 수식을 보고 모르는 기호가 나오면 한번 의미를 찾아보자. 물론 그 중에는 의미 자체가 어려운 기호도 있다.($\int$같은 적분 기호…?) 하지만 대부분의 경우 그냥 알파벳은 변수, 기호는 규칙(프로그래밍에서의 명령문)이나 특별한 함수를 의미하기 때문에 하나하나 뜯어보고 코드로 작성해보면 생각보다 단순한 경우가 많다.

이제 더 이상 수학이 별 거 없다는 걸 알았으니 수학이라는 단어 자체에 쫄지 말고 한번 씹고 뜯고 맛보고 즐겨보자.

알면 도움이 되는 수학 개념 3가지

자 그럼 이제 본론으로 들어가서, 우리가 프로그래밍을 할 때 도움이 될만한 수학의 3가지 개념들을 살펴보자.

사실 필자가 이야기하고 싶은 것은 개념이기 때문에 위에서 예로 들었던 것처럼 수식이 나오는 대수학 같은 건 아니다. 그리고 이 포스팅에서 설명하는 개념들은 사실 수학보다 논리학에 가까운 느낌이기 때문에 반드시 숫자와 연관지어서 생각할 필요는 없다.

이 개념들은 우리가 중학교, 고등학교에서 이미 배웠던 개념들이다. 다만 수능을 보고 나면 딱히 쓸 일이 없기 때문에 까먹었을 뿐이다. 물론 이과 계열의 전공을 택하신 분이나 문과 중에서도 경제학처럼 수학과 연관이 깊은 전공을 택하신 분은 계속 수학을 공부하기 때문에 까먹지 않았을 수 있지만 그 외의 경우는 사실 까먹을 수 밖에 없다.

뭐 어쨌든 필자가 설명할 개념들은 전부 어릴 때 한번 정도는 들어봤던 논리에 대한 개념들이니까 조금은 가벼운 마음으로 한번 즐겨보도록 하자.

명제(Proposition)

자 처음으로 이야기하고 싶은 개념은 중학교 1학년인가? 2학년 때 배우는 명제이다. 사실 명제는 굳이 프로그래밍을 위해서가 아니더라도 그냥 논리적인 사고를 하기 위해서 기본적으로 알고 있어야 하는 개념이다.

명제는 , 거짓과 같은 논리적인 진릿값을 가지는 것을 말한다. 즉, 참과 거짓을 구분할 수 없는 문장은 명제로 치지 않는다.
즉, 에반은 키가 크다와 같은 문장은 명제로 성립될 수 없다는 것이다. 다른 사람이 필자를 봤을 때 키가 크다고 생각할 수도 있고 작다고 생각할 수도 있는 주관성이 들어가는 문장이기 때문이다.

그렇다면 명제는 우리가 프로그래밍을 할 때 어디에 사용된다는 걸까?

명제가 말하는 거짓은 우리가 프로그래밍할 때 사용하는 True, False1, 0과 동일한 개념이다. 즉, 우리가 중학생 때 수학 교과서로 배웠던 명제는 조건식과 동일하다고 할 수 있다. 간단한 코드를 한번 보자.

1
2
3
4
5
6
7
8
const array = ['a', 'b', 'c'];

if (array.includes('a')) {
console.log('array 변수에는 a가 들어있다.');
}
else {
console.log('array 변수에는 a가 들어있지 않다.');
}

이 코드에서 필자가 제시한 명제는 array 변수에 담긴 배열에 "a"라는 원소가 포함되어있다이고 이 명제가 참일 경우 if문 내부의 코드가, 거짓일 경우에는 else문 내부의 코드가 실행된다. 이처럼 조건문에 사용되는 조건은 반드시 명제여야하므로 명제에 익숙한 프로그래머는 어떤 요구사항을 들었을 때, 그 요구사항을 충족할 수 있는 명제를 빠르게 제시해나갈 수 있다.

이 명제라는 개념은 필자가 앞으로 설명할 다른 개념들이나 다른 수학의 개념에서도 가장 기초가 되는 개념이기 때문에 우리가 중학교에 입학하자마자 배우는 것이다. 수학은 긴가민가한 학문이 아니라 정확한 질문과 정확한 답을 제시할 수 있어야하는 능력이 필요한 학문이기 때문에 명제가 가장 기초가 된다.

집합(Set)

다음으로 이야기 할 것은 명제와 마찬가지로 우리가 중학교 입학하고나서 배웠던 집합이다. 집합도 명제와 마찬가지로 알게 모르게 많이 사용되는 개념이기 때문에 집합에 대한 확실한 개념을 알고 있다면 프로그래밍할 때 많은 도움을 준다.

동글동글 귀여운 벤다이어그램과 프로그래밍은 왠지 거리가 멀어보이지만 사실 우리는 저 개념을 매일매일 프로그래밍할 때 사용하고 있다. 바로 논리식을 작성할 때 말이다. 논리식은 위에서 말한 명제처럼 True, False 둘 중에 하나로 평가받을 수 있는 식을 이야기한다. 보통 우리는 명제 여러 개를 논리 연산자로 묶은 식을 많이 사용한다. 이 논리식과 집합이 무슨 관계가 있다는 걸까?

사실 우리가 사용하는 논리연산자는 &&(AND) = 교집합, ||(OR) = 합집합으로 대응되기 때문에 복잡한 논리식을 마주쳤을 때 그 논리식을 벤다이어그램으로 그려보는 것도 가능하다. 그리고 우리가 어릴 때 외웠던 드 모르간의 법칙도 논리식에 그대로 적용된다.(사실 드 모르간의 법칙은 집합에 대한 법칙이라기보다 좀 더 포괄적인 논리학의 법칙이다.)



드 모르간의 법칙을 벤다이어그램으로 나타낸 모습



필자는 드 모르간 법칙이 빛을 발하는 순간이 바로 자연어를 논리식으로 변환할 때라고 생각한다. 보통 회사에서 비즈니스 로직을 짜다보면 PO들이 어떤 기능의 작동 여부에 조건을 추가하는 경우가 많은데 문제는 조건을 추가할 때 기존에 있던 조건들까지 모두 생각하면서 말해주지 않는다는 것이다.

필자가 지금까지 일을 하면서 겪은 조건 중에 가장 복잡했던 경우를 예로 들어보겠다. 필자는 예전에 멤버십 결제 기능을 개발한 적이 있었는데 문제는 결제 수단을 입력할 수 있는 폼의 렌더 조건이 굉장히 복잡하다는 것이었다. 물론 처음부터 이렇게 복잡한 건 아니였고, 기능이 추가됨에 따라 점점 조건이 복잡해진 케이스이다.

당시 조건이었던 논리식을 자연어로 그대로 써보겠다.

조건 1. 사용자의 멤버십이 해지 상태가 아니고 결제 수단도 가지고 있지 않다.
조건 2. 사용자가 결제수단을 가지고 있고 결제 수단을 핸드폰으로 가지고 있으며, 현재 사용자가 고른 상품이 현재 사용자가 가지고 있는 상품이 아니고 현재 멤버십이 해지 예약상태가 아니다.

(조건 1 || 조건 2)이면 결제 수단 등록 폼이 활성화 된다.

조건 version 1




뭐 사실 저 사단이 난건 1차적으로 개발자인 필자의 잘못이긴 하지만 굳이 핑계를 대자면 시간에 쫓겨서 맨날 야근하면서 개발하다보니 저런 괴물같은 논리식이 탄생해버렸다…
저렇게 개판쳐놓고나서도 다른 할 것도 너무 많았기 때문에 일단 묻어놓고 다른 프로젝트를 또 개발하던 중에 PO가 필자에게 이야기 했다.

에반, 저희 결제 수단 등록 폼에 조건 하나만 더 추가할 수 있을까요?

저 얘기를 듣고 다시 저 코드를 보고 PO 얼굴을 한번 본 뒤, 저걸 어떻게든 뜯어고쳐야겠다는 결론에 다다른 필자는 조용히 노트북을 들고 화이트 보드 앞으로 간 후, 저 복잡한 조건들을 어떻게든 이해할 수 있는 수준으로 만들기 위해서 발버둥쳤는데 그 결과가 이것이다.

조건 1. 사용자가 멤버십 가입 상태가 아니고, 사용자가 등록한 결제 수단이 카드가 아니다.
조건 2. 사용자가 멤버십 가입 상태이고 사용자가 등록한 결제 수단이 휴대폰이며, 현재 구매하려고 선택한 멤버십이 나의 멤버십과 다른 상품이다.
조건 3. 사용자의 결제 수단 정보가 없다.

(조건 1 || 조건 2 || 조건3)이고 사용자가 선택한 결제 수단이 카드라면 결제 수단 등록 폼이 활성화 된다.

조건 version 2

사실 이것도 간단한 논리식은 아니지만 그래도 조건 1, 조건 2, 조건 3만 읽어 보았을 때 이전의 조건에 비해서 어떤 상태인지 좀 더 알아보기 쉬워졌다.(라고 자기합리화를 해봅니다.) 이 당시 필자가 논리식을 정리할 때 사용했던 방법이 벤다이어그램드 모르간 법칙이었다.

벤다이어그램으로 논리식을 펼쳐놓음으로써 여러 개의 논리식 중 사실 같은 명제이지만 역의 꼴을 취하고 있는 친구들을 쉽게 찾을 수 있었고 겹치는 명제들을 골라서 합치고 좀 더 알아보기 쉬운 단위로 조건을 나누고해서 저렇게라도 만들어 놓은 것이다.

물론 이 코드는 언젠가 개선을 해야한다…언젠가…

그리고 추가적으로 이런 논리식 외에도 데이터베이스에 질의를 던질 때 사용하는 SQL의 JOIN 개념도 보통 벤다이어그램으로 표현한다.





이런 경우 복잡한 논리식이나 SQL의 JOIN문을 보고 벤다이어그램이 바로 머리 속에 떠오른다면 그냥 코드나 자연어로 이해하는 것 보다는 좀 더 직관적이고 빠르게 이해할 수 있지 않을까? 라는 생각을 해본다.

수학적 귀납법(Mathematical Induction)

수학적 귀납법(Mathematical Induction)은 수학에서 사용하는 증명 방법 중 하나이다. 주로 어떤 명제가 모든 자연수에 대하여 성립함을 보이기 위해 사용한다. 수학적 귀납법이 무엇인가를 자세히 알아보기 전에 먼저 우리는 논리학의 논증법 투톱인 귀납논증연역논증에 대해서 알아야한다.

간단하게 이야기하자면 귀납논증지금까지 그래왔으니까 앞으로도 그럴 것이다라는 느낌이고 연역논증전제가 맞다면 결론도 반드시 맞다라는 느낌이다. 이걸 너무 자세히 설명하면 글이 길어지기 때문에 간단한 예시로 간만 보겠다.

먼저 귀납논증은 이런 느낌이다.

2000년 여름은 더웠다, 2001년 여름도 더웠다….2019년 여름도 더웠다. 그러므로 여름에는 반드시 덥다.

귀납적인 이런 논증 방식은 모든 전제가 참이라고 해도 반드시 결론도 참이라는 법이 없다. 당장 위의 예시만 봐도 2020년 여름에는 기상이변으로 인해서 눈이 올 수도 있지 않을까?(투모로우…?)

즉, 귀납논증은 언제나 오류가 존재할 확률이 있다. 여기까지만 보면 왠지 허점투성이 논증법인 것 같지만 그래도 현대 과학은 귀납논증을 통해 끊임없는 가설을 제시하고 그걸 증명함으로써 발전해왔으므로 상당한 가치가 있는 논증법이라고 할 수 있다.

반면에 연역논증은 이런 느낌이다.

맥북은 애플이 만든다. 내 컴퓨터는 맥북이다. 그러므로 내 컴퓨터는 애플이 만들었다.

연역논증 중에서 가장 대표적인 사례인 삼단논법이다. 이게 바로 위에서 얘기한 어떤 부분적인 전제가 맞다면 결론도 반드시 맞다라는 의미이다. 만약 결론이 거짓이라면 전제 중 하나도 무조건 거짓이다. 즉, 연역논증은 이미 전제에 담겨있던 것을 증명하는 데는 탁월하지만 귀납논증처럼 새로운 지식을 탐구하기에는 부적절하다.

하지만 우리가 프로그래밍을 할때는 새로운 지식을 탐구하는 것이 아니라 그냥 내 코드가 오류없이 완벽한가를 증명하기 위한 논증법을 사용해야하므로 귀납논증보다는 연역논증이 더 알맞다.

필자가 이 두 논증법을 전부 설명한 이유는 바로 수학적 귀납법귀납논증이 아니라 연역논증이기 때문이다.



아니…귀납법이라며…?


수학적 귀납법은 어떤 명제 $P$가 있을 때 다음 2가지만 충족시키면 모든 자연수에 대해서 $P$가 성립한다는 것을 의미한다.

  1. $P(1)$은 참이다
  2. $P(n)$이 참이면 $P(n + 1)$도 참이다.

그러므로 명제 $P$는 모든 자연수에 대해서 참이다.

이렇게만 얘기하면 또 머리가 아파지니까 예시를 살펴보자. 수학적 귀납법은 보통 도미노로 예시를 많이 들기 때문에 필자도 도미노를 예로 설명하겠다.

  1. 맨 처음에 있는 도미노가 쓰러진다. ($P(1)$이 참)
  2. 무작위로 고른 $n$번째 도미노가 쓰러질 때 항상 $n+1$번째에 세워진 도미노도 쓰러진다. ($P(n)$이 참이면 $P(n + 1)$도 참)

그러므로 맨 처음에 있는 도미노를 쓰러트리면 반드시 모든 도미노가 순서대로 쭉쭉 쓰러진다.

이것이 수학적 귀납법의 논리 전개 방식이다. 간단하게 얘기하자면 전제가 참이라는 것을 먼저 보인 후에 그 전제에서 보편적인 결론을 이끌어 내는 것이다.

이런 수학적 귀납법알고리즘의 정당성을 검증할 때 아주 유용하게 쓰일 수 있다. 왜냐면 알고리즘이란 것은 굉장히 보편적인 규칙이고, 어떤 형태로든 반드시 반복적인 요소를 가지고 있기 때문이다.

그럼 한번 유명한 알고리즘인 $n!$, 팩토리얼을 구하는 알고리즘을 한번 수학적 귀납법으로 풀어보자.

1
2
3
4
5
6
7
8
function factorial (n) {
if (n < 1) {
return 1;
}
else {
return n * factorial(n - 1);
}
}
  1. $n = 0$인 경우 $n! = 1$이다.
  2. $n! = n \times (n - 1) \times (n - 2) \times … 3 \times 2 \times 1$ 이다.
  3. $(n + 1) \times n! = (n + 1) \times n \times (n - 1) \times … 3 \times 2 \times 1$ 이다.

그러므로 이 논리는 참이다.

이런 식의 논리적인 사고방식은 당장 코딩할때 직접적인 도움이 되지는 않겠지만, 복잡한 문제를 만났을 때 일반화된 해결법을 찾아낼 수 있는 능력을 키워준다. 사실 이런 논증법을 적용할 수 있는 문제는 일상에서도 얼마든지 찾아볼 수 있으므로 평소에도 한번 이렇게 생각하는 습관을 들여보는 것도 나쁘지 않다.(연애할 때는 절대 이러지 말자.)

마치며

사실 이 포스팅에서 설명한 저런 것들 다 몰라도 느낌적인 느낌으로 프로그래밍을 잘 할수는 있다. 하지만 곰곰히 생각해보자. 저런 것들을 모르고 프로그래밍을 하고 있었다고 생각했던 분들도 그냥 이론으로 정리하지 않았을 뿐이지 알게모르게 저 개념들을 전부 사용하고 있었을 것이다.

그리고 필자가 생각했을 때 수학을 배우면 가장 좋은 점은 내가 만들고 싶은 것을 만들때 적어도 이론에서 막히는 일은 없다는 것이다.

필자가 예전에 작성했던 포스팅인 행성 궤도 계산이나 역전파 알고리즘같은 포스팅만 봐도 수식이 많이 나와서 어려워 보일 수 있다. 사실 필자도 저 친구들을 처음 만들 때 학교에서 배운 수학 같은 건 이미 가물가물한 상태였기 때문에 거의 처음부터 다시 공부해서 결국은 저 프로젝트를 완성할 수 있었다.

물론 선형대수학부터 시작해서 오일러 회전, 쿼터니온 등 이름만 들어도 토할 것 같은 이론들이 처음에는 필자에게도 상당한 두려움으로 다가왔지만 일단 이해가 안되더라도 문서를 계속 보고 조금이라도 이해되는 부분이 있다면 코드로 작성한 후에 하나하나 실행시켜보면서 공식의 매커니즘을 눈으로 직접 보다보니까 어느 순간부터는 그래도 처음보다 많이 익숙해졌던 것 같다.(근데 사실 지금도 잘 모른다.)

수학은 그렇게 무서운 친구가 아니다. 위에서 예시로 나왔던 인공신경망 알고리즘의 수식도 처음 보면 뭔가 어려워보이고 복잡해보이지만 막상 코드로 풀어보니 별 거 아니였던 것처럼 말이다. 여러분은 이미 프로그래머로써 알게 모르게 수학이나 논리학의 개념이 몸에 배어있는 사람인데 이제 와서 수학을 겁내는 것도 좀 이상하지 않은가? 이제는 그런 마음을 다 털어버리고 한번 수학과 친해져보자.

수학은 그냥 프로그래밍 언어처럼 여러분이 상상하는 것을 실현시켜줄 수 있는 도구라고 생각하자.

이상으로 프로그래머는 수학을 잘해야할까? 포스팅을 마친다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×