통계 및 데이터 분석용 언어 R

Posted 2008. 3. 31. 21:06
리스프(Lisp), SML, 얼랑(Erlang), 헤스켈(Haskell) 등은 각자 특징은 다르지만 범용 함수형 언어라는 공통점이 있습니다. 이와 달리 몇 가지 한정된 영역에만 쓰이는 특수 목적용 함수 언어도 있습니다. 대표적인 예로 통계 및 데이터 분석용 언어이자 환경인 R입니다. 기능이 1:1로 대응되는 것은 아니지만 매트랩(Matlab)이 장악하고 있는 도메인(데이타 분석, 통계, 수학)을 다룰 수 있는 함수형 언어입니다.

R 언어 홈페이지에 있는 An Introduction to R을 보시면 R의 기본적인 특징과 장단점을 파악하실 수 있습니다. IBM dW에도 Statistical programming with R라는 제목으로 R 프로그래밍 언어 소개가 올라와 있습니다. Part 1. Dabbling with a wealth of statistical facilities 제목의 1부는 기본적인 특징과 데이터 타입을 다루고, Functional programming and data exploration라는 제목의 2부에서는 함수형 언어로서의 특징을 이야기하고 있습니다.

R은 데이터 분석용 언어인만큼 기본 데이터 타입으로 벡터(vector)를 지원하고, 각각의 원소에 셈을 할 수 있는 elementwise 연산자를 지원합니다.

> x <- c(10.4, 5.6, 3.1, 6.4, 21.7)
> 1/x

위 코드는 x에 벡터 (10.4, 5.6, 3.1, 6.4, 21.7)를 바인딩하는 예입니다. 1/x는 elementwise 나눗셈으로 각 원소를 1에 대해서 나눈 결과값을 돌려줍니다.

통계에 강한 만큼 통계에 사용되는 mean (평균), sd (표준편차) 등을 기본으로 제공합니다.

> mean(basement)            # Mean fails if we include unavailable data
[1] NA
> mean(basement, na.rm=TRUE)
[1] 18.87542
> sd(basement, na.rm=TRUE)      # Standard deviation must also exclude NA
[1] 2.472855
> cor(basement, livingroom, use="all.obs")   # All observations: no go
Error in cor(basement, livingroom, use = "all.obs") :
        missing observations in cov/cor
> cor(basement, livingroom, use="complete.obs")
[1] 0.9513366
> cor(outside, livingroom, use="complete.obs")
[1] 0.6446673

물론 계산 후에는 멋진 그래픽으로 보여주는 것도 중요하기 때문에 데이터 시각화(data visualization)도 R의 장점 중에 하나입니다. R로 뽑아낸 그래프의 예는 앞서 언급한 dW 글인 Part 1. Dabbling with a wealth of statistical facilities 를 보시면 됩니다.


디버거 없이 개발하기

Posted 2008. 3. 28. 20:09

전 개발할 때 디버거를 자주 사용하지 않는 편입니다. C/C++, 자바, 파이썬, 하스켈 등 개발 언어를 가리지 않고 디버거는 거의 사용하지 않습니다. 물론 디버거를 안 사용하는 게 무슨 개발 철학 같은 건 아닙니다. 직관적으로 디버거를 사용하는 편이 좋겠다는 생각이 들 때도 있고 프로그램이 C 코드 안에서 죽을 때는 디버거를 실행시켜서 메모리와 변수 값을 보기도 합니다.

하지만 디버거로 절대 잡기 힘든 멀티쓰레드 버그가 난무하고 메모리 보호도 없는 임베디드 시스템 개발로 개발을 시작하다보니 버그가 생겨도 디버거를 써야지라는 생각을 잘 안 하게 된 것 같습니다. 대신 저는 디버깅에 크게 2가지 방법을 사용합니다. (다른 개발자도 마찬가지겠지만요)

1) 콘솔 출력

언어마다 다르지만 결국 중요한 지점 몇 군데가 변수 값을 찍어보는 걸로 디버깅이 시작됩니다. printf, System.out.println, print가 디버깅 도구인 셈이죠. 간단한 버그들은 보통 원인이 짐작이 되기 때문에 중요 변수를 콘솔로 몇 번 출력해 보면 문제가 파악됩니다.


2) 코드 검토

멀티쓰레드 버그에 대해서는 가장 강력한 디버깅 방법이 머리 속으로 시뮬레이션하는 것입니다. 증상을 놓고 왜 이런 문제가 생겼을지 의심가는 여러 쓰레드를 놓고 생각해 보는 것이죠. 하루종일 책상에 앉아서 왜 이런 현상이 생길지 생각만 하다가 집에 간 날도 있습니다.


버그를 찾아내면 기쁘긴 하지만 디버깅 자체는 괴롭습니다. 처음부터 버그를 안 만드는 게 최선이겠죠.  하지만 나름대로 열심히 짠다고 해도 버그 없는 프로그램을 만들기란 너무 힘듭니다. 코드를 작성했는데 한 번에 컴파일되고 돌아가면 스스로 의심합니다. "그럴리가 없는데." 어릴 때는 그래도 머리가 잘 돌아가서 가끔 실행은 제대로 안 되더라도 컴파일 에러 없는 코드 정도는 만들어내곤 했는데 요즘은 그나마도 힘들어 그냥 포기하고 무슨 마약처럼 컴파일러에 의존(주기적으로 컴파일을 해보지 않으면 불안함)해 살고 있습니다.

버그는 사람이 만들지만 버그를 유도하는 것은 개발 환경입니다. 특히 프로그래밍 언어는 개발자가 얼마나 많은 버그를 만드는지를 결정하는 중요한 요소입니다. 함수 언어를 사용하면서 느낀 점 중에 하나는 적당히 컴파일되게만 짜면 다른 언어에 비해 버그가 현저히 적다는 사실입니다. 물론 강력한 정적 타입 시스템에 도움이 큰 셈이죠. 여기에 덧붙여 버그를 줄이는 프로그래밍 언어의 특징으로는 변조불가능(Immutability), 순수(Purity), 참조 투명성(Referential Transparency) 등이 있습니다. 세 가지 단어는 서로 다 관련이 있습니다.

하스켈을 비롯한 함수 언어 개발자들이 다른 개발자들과 차이를 보이는 점 중에 하나가 디버거를 거의 사용하지 않는다는 사실입니다. 물론 GHCi에 보면 디버거가 없는 건 아닙니다. 앞서 언급한 언어적 특성 때문에 하스켈 디버깅은 디버거를 통해 변수에 잘못된 값이 들어가 있는지 확인하는 일이 필요가 없습니다. (사실 변수라고 부를만한 것도 별로 없습니다.) 그저 함수 명세에 맞춰서 코드가 제대로 작성되었는지 테스트 값 몇 개만 넣어보면 됩니다.

특히 하스켈의 중요 특징의 하나인 참조 투명성은 테스트 혹은 디버깅에서 강력한 힘을 발휘합니다. 참조 투명성이란 하스켈의 함수가 수학적인 함수와 동일하게 같은 입력(인자)에 대해서는 언제나 같은 값을 리턴함을 의미합니다. 하스켈의 순수 함수는 전역 변수를 참조하거나 IO에 영향을 받지 않기 때문에 테스트나 디버깅을 위해 가짜 오브젝트(Mock Object) 등을 만들어내는 수고를 할 필요가 없습니다. 그저 함수의 명세(specification)만 테스트하면 됩니다. 이런 특성 때문에 하스켈의 테스트프레임워크 중 하나인 QuickCheck은 명세 기반 테스트라는 용어를 사용하고 있습니다.

나는 연장질을 잘 못한다고 생각하기 보다는 내 연장이 나쁜게 아닐까라는 생각의 전환이 필요합니다.

Parallelism and Concurrency 2

Posted 2008. 3. 28. 03:10
얼마 전에 Parallelism and Concurrency라는 글을 통해 동시성(concurrency)와 병렬(parallelism)의 정확한 정의에 대해서 의문을 제기했었는데 오늘 Simon Peyton Jones가 최근에 정리해서 내놓은 Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell을 읽다가 보다 명확한 정의를 발견했습니다.

28 페이지 Concurrency에 대한 항목을 보시면 두 용어의 정의가 나옵니다.

A parallel functional program uses multiple processors to gain performance. For example, it may be faster to evaluate e1 + e2 by evaluating e1 and e2 in parallel, and then add the results. Parallelism has no semantic impact at all: the meaning of a program is unchanged whether it is executed sequentially or in parallel. Furthermore, the results are deterministic; there is no possibility that a parallel program will give one result in one run and a different result in a different run.

In contrast, a concurrent program has concurrency as part of its specification. The program must run concurrent threads, each of which can independently perform input/output. The program may be run on many processors, or on one — that is an implementation choice. The behaviour of the program is, necessarily and by design, non-deterministic. Hence, unlike parallelism, concurrency has a substantial semantic impact.


병렬 프로그래밍은 성능 향상을 목적으로 명시적으로 멀티 프로세서를 사용함을 의미합니다. 반대로 동시성 프로그래밍은 동시성 자체가 명세에 포함되는 개념입니다. 프로그램이 여러 개의 동시적인 쓰레드로 실행이 되고 따라서 결과가 비결정적이다라는 사실까지 말입니다. 병렬 프로그래밍과 마찬가지로 멀티 프로세서를 사용할 수도 있지만 이는 필수적인 것이 아니라 구현 세부사항일 뿐입니다.

여기까지는 지난 번에 보여드렸던 설명과 동일합니다. 위 정의에는 여기에 덧붙여 더 중요한 정의가 나오는데, 바로 시멘틱에 관한 부분입니다. 병렬 프로그래밍은 프로그램의 시멘틱에 전혀 영향을 미치지 않습니다. 병렬 프로그래밍에서는 e1 + e2를 계산할 때 e1과 e2를 각각의 프로세서에서 실행을 하던 하나의 프로세서에서 실행을 하던 겉으로 보이는 결과는 반드시 같아야 합니다.

하지만 동시성 프로그래밍에서는 멀티쓰레드 사용 자체가 시멘틱에 포함되어 있기 때문에 쓰레드가 어떻게 수행되느냐에 따라서 결과가 달리나올 수 있음을 내포하고 있습니다. 즉, 시멘틱이 달라질 수 있다는 사실이 깔려있습니다.

앞으로는 이런 사실을 염두에 두고 동시성 프로그래밍과 병렬 프로그래밍 용어를 명확히 구분해서 사용하려고 합니다.

비고: Tackling the Awkaward Squad는 굉장히 읽을 만한 글입니다. 헤스켈에서 가장 어려운 부분이지만, 쓸만한 프로그램을 만들기 위해서는 반드시 알아야 하는 입출력(IO), 동시성(concurrency), 예외 처리(exception), 외부 함수 호출(foreign-language call)을 다루고 있습니다. 특히, IO monad의 operational semantics를 설명하는 부분에 감동했습니다 +_+

« PREV : 1 : ··· : 13 : 14 : 15 : 16 : 17 : 18 : 19 : ··· : 82 : NEXT »