메타프로그래밍: 거울 테스트, 메타 인지, 스스로 발전하는 프로그램(aka 인공지능)
메타
- 메타 인지라 인지를 인지하는 것이다.
- 메타 프로그래밍이란 프로그램을 프로그래밍하는 것이다.
자기 인식(self-awareness)
자기를 인식(self-awareness)할 수 있는 동물은 많지 않다고 한다. 다음의 동영상은 자기를 인식할 수 있다고 알려진 6가지 동물(돌고래, 원숭이, 까치, 코끼리, 개, 물고기!)을 보여준다.
동물이 자기를 인식하는 것을 어떻게 알 수 있을까? 과학자들은 동물의 얼굴에 뭔가를 붙여놓고, 동물이 거울을 봤을 때 어떻게 행동하는지, 뭔가를 붙여놓지 않았을 때와 비교해서 다르게 행동하는지를 관찰한다고 한다. 뭔가 얼굴에 붙어 있으면 떼려고 하는게 인간을 포함한 동물의 자연스런 반응인가 보다. 물론 자신을 인식할 수 있다면!
그렇다면 컴퓨터 언어는 자기 자신을 인식할 수 있을까?
자기을 인식하는 프로그램
일단 사람이 짜 놓은 프로그램을 해석하는 프로그램을 생각해보자.
이 프로그램은 자기 자신을 해석(해석이 뭐든)할 수 있을까?
뭔가 반복이 있어서 자귀회귀적 또는 자기순환적 모순이 숨어 있는 듯 보이지만, 프로그램을 해석을 하는 프로그램이 보통의 프로그램과 다른 특별한 정성적 차이가 있지 않다면, 다른 프로그램을 해석하는 것과 별 다를 게 없을 것이다.
다음을 보자.
프로그램 A는 프로그램을 해석한다.
만약 해석해야 하는 프로그램이 프로그램 A라면, “프로그램 A는 프로그램 A를 해석한다.” 또는 “프로그램을 해석하는 프로그램은 프로그램을 해석하는 프로그램을 해석하는다.” 라고 쓸 수 있어서, 머리 아프게 뭔가 복잡하게 꼬여 있는 듯 보이겠지만, 프로그램을 해석하는 프로그램 A는 이미 모든 프로그램이 가지는 문법을 숙지하고 있기 때문에(가정) 프로그램 A는 다른 프로그램을 해석하듯이 프로그램 A의 복사본을 해석할 수 있을 것이다.
문제는 프로그램을 해석하여 수정하는 경우에 발생하 수 있을 것이다. 프로그램 B는 프로그램을 해석한 후 특별한 방향으로 수정한다고 해보자. 프로그램 B는 프로그램 B를 수정할 수 있을까?
이때에는 수정하는 프로그램 B가 자기 자신인지 복사본인지에 따라 결과가 다르게 될 것이다.
인공지능과 자신을 수정하는 프로그램
연결주의 인공지능이 득세하는 현재와 달리 예전의 인공지능은 IF-ELSE로 가득한 프로그램이었다.
인간의 지능도 결국은 정보처리시스템이라는 가정에서, 인간의 지능 역시 프로그램이라고 생각할 수 있다. 그런데 인간의 지능처럼 스스로 학습 또는 발전하는 프로그램이 되려면, 프로그램이 프로그램을 발전시켜야 하는 상황이 되는 것이다.
그리고 LISP와 같은 언어는 스스로 프로그래밍을 생성 변형시킬 수 있기 때문에 인공 지능을 구현할 최적의 언어로 각광받던 시절이 있었다. 아 Good Old-Fashioned (AI)!
스스로를 수정하는 프로그램
프로그램을 수정하는 프로그램을 다시 생각해보자. 만약 같은 프로그램이라고 해도, 수정하는 프로그램과 수정을 당하는 프로그램이 다르다면(또는 수정을 당하는 프로그램이 수정을 하는 프로그램의 복사본이라면), 논리적으로 큰 하자가 없다.
알파고 제로가 자신의 복사본과의 시합을 통해 발전해 가는 과정을 상상해 보자. 동일한 프로그램이지만, 우연 또는 상대방의 반응에 따라 다르게 학습되기도 한다.
하지만 스스로가 스스로를 수정하는 과정을 생각해보자. 이는 데이터 베이스에 일관성없는 데이터가 쏟아지거나, 중구난방의 수정요구가 빗발치는 상황으로 생각할 수 없다. 무작정 요구를 받아들이다보면 데이터는 결국 일관성이 없어진다.
몇몇 과학자들은 뇌를 연구하는 한계가 있다고 주장하기도 한다. 그 이유는 정확히 모르지만, 자기 자신을 연구하면서, 자신의 뇌가 바뀌게 되기 때문? 어쨋든 뇌를 연구하면서 뇌가 영향을 받는 것은 틀림없을 것이다. 그 영향이 어떤 것일지에 대해서는 확언하기 어렵다. 어쨋든 인간은 매일 학습하고 매일 조금씩 변하고 이다.
자기를 인식하는 것은, 생각보다 큰 함의를 지니고 있다.
이 어려운 것을 R이 합니다!
Advanced R(2nd Ed)
최근에 Advanced R의 두번째 판을 보고 있는데, Hadley의 R에 대한 헌신에 탐복을 할 수 밖에 없다. R의 중구 난방인 함수들을 깔끔하게 정리하고 일반인이 R의 메타 프로그래밍을 활용할 수 있게 설명해 놓았다.
활용예
우선 테슬라(tsla)를 비롯한 몇몇 회사의 주가 추이를 감상해보자.
if (!require(BatchGetSymbols)) install.packages('BatchGetSymbols')
library(BatchGetSymbols)
# set dates
first.date <- Sys.Date() - 365
last.date <- Sys.Date()
freq.data <- 'daily'
# set tickers
tickers <- c('FB','MMM','PETR4.SA','tsla')
l.out <- BatchGetSymbols(tickers = tickers,
first.date = first.date,
last.date = last.date,
freq.data = freq.data,
cache.folder = file.path(tempdir(),
'BGS_Cache') ) # cache in tempdir()
## ## Running BatchGetSymbols for: ## tickers =FB, MMM, PETR4.SA, tsla ## Downloading data for benchmark ticker ## ^GSPC | yahoo (1|1) | Found cache file ## FB | yahoo (1|4) | Found cache file - Got 100% of valid prices | OK! ## MMM | yahoo (2|4) | Found cache file - Got 100% of valid prices | Good stuff! ## PETR4.SA | yahoo (3|4) | Found cache file - Got 96% of valid prices | Looking good! ## tsla | yahoo (4|4) | Found cache file - Got 100% of valid prices | You got it!
print(l.out$df.control)
## # A tibble: 4 x 6 ## ticker src download.status total.obs perc.benchmark.dates threshold.decision ## <chr> <chr> <chr> <int> <dbl> <chr> ## 1 FB yahoo OK 252 1 KEEP ## 2 MMM yahoo OK 252 1 KEEP ## 3 PETR4.SA yahoo OK 245 0.956 KEEP ## 4 tsla yahoo OK 252 1 KEEP
library(ggplot2)
p <- ggplot(l.out$df.tickers, aes(x = ref.date, y = price.close))
p <- p + geom_line()
p <- p + facet_wrap(~ticker, scales = 'free_y')
print(p)
#l.out$df.tickers
#str(l.out$df.tickers)
dat <- l.out$df.tickers %>%
filter(ticker == 'tsla') %>%
select(ref.date, price.open, price.high, price.low, price.close, volume)
작년 3월 급락이후, 페이스북은 8-9월까지 모두 올라버렸고, 3M은 계속 오르고 있으며, 테슬라는 올초까지 오르다, 올해 들어 급락과 급등을 반복하고 있다.
급등했던 테슬라 주가가 하루에 10% 이상 오른 경우도 있을까?
dat %>% mutate(r = price.close/ price.open) %>%
ggplot(aes(x=r)) +
geom_histogram() +
labs(title = "tsla(close/open)",
caption = paste0(
format(min(dat$ref.date), "%Y-%m-%d"),
" ~ ",
format(max(dat$ref.date), "%Y-%m-%d")))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
히스토그램은 드물지만 종가가 시가의 10% 이상 상승한 경우가 있음을 보여준다.
언제였을까?
dat %>% filter(price.close/price.open > 1.10)
## ref.date price.open price.high price.low price.close volume ## 1 2020-04-13 118.032 130.400 116.106 130.19 112377000 ## 2 2020-07-10 279.200 309.784 275.202 308.93 116688000 ## 3 2020-08-31 444.610 500.140 440.110 498.32 118374400 ## 4 2020-09-14 380.950 420.000 373.300 419.62 83020600 ## 5 2021-03-09 608.180 678.090 595.210 673.58 67523300
이렇게 하루만에 10%가 오르면 그 다음날, 또는 그 이후의 주가는 어떻게 변했을까? 이를 확인하게 위해서는 여러 가지 방법이 있을 것이다.
먼저 위의 사례에서 날짜 또는 행번호를 추출한 후, 이를 늘린다던지…
만약 다른 변수를 생성하지 않고 바로 확인하려면 다음과 같은 방법이 있다.
하루만에 10%가 오른 날과 그 다음 날의 주가를 보여준다.
dat %>% filter(price.close/price.open > 1.10 |
lag(price.close/price.open) > 1.10)
## ref.date price.open price.high price.low price.close volume ## 1 2020-04-13 118.032 130.400 116.106 130.190 112377000 ## 2 2020-04-14 139.794 148.376 138.486 141.978 152882500 ## 3 2020-07-10 279.200 309.784 275.202 308.930 116688000 ## 4 2020-07-13 331.800 358.998 294.222 299.412 194927000 ## 5 2020-08-31 444.610 500.140 440.110 498.320 118374400 ## 6 2020-09-01 502.140 502.490 470.510 475.050 89841100 ## 7 2020-09-14 380.950 420.000 373.300 419.620 83020600 ## 8 2020-09-15 436.560 461.940 430.700 449.760 97298200 ## 9 2021-03-09 608.180 678.090 595.210 673.580 67523300 ## 10 2021-03-10 700.300 717.850 655.060 668.060 60605700
price.close/price.open > 1.10
은 종가가 시가의 110%를 넘는 날을 의미한다. lag(price.close/price.open)
이라고 쓰면 전 날의 종가/시가가 1.1보다 크다는 것을 의미한다.
데이터가 x=c(1, 2, 3)
이라면 lag
(지연)된 x
는 c(NA, 1, 2)
가 되는 식이다. (여기서 lag
는 dplyr::lag
이고, stats::lag
와 는 약간 다름을 유의하자.)
dat %>% filter(price.close/price.open > 1.10 |
lag(price.close/price.open) > 1.10) %>%
ggplot(aes(x=ref.date)) +
geom_point(aes(y=price.close), col='blue') +
geom_point(aes(y=price.open), col='red') +
geom_linerange(aes(ymin=price.close, ymax=price.open))
5번 중 3번은 다음날 종가가 시가보다 낮았음을 보여준다.
만약 10% 상승 후 1주일 간의 주가 추이를 확인하고자 한다면 어떨까?
무식한 방법은 다음과 같다.
dat %>% filter(price.close/price.open > 1.10 |
lag(price.close/price.open, n=1) > 1.10 |
lag(price.close/price.open, n=2) > 1.10 |
lag(price.close/price.open, n=3) > 1.10 |
lag(price.close/price.open, n=4) > 1.10 |
lag(price.close/price.open, n=5) > 1.10 |
lag(price.close/price.open, n=6) > 1.10) %>% head(10)
## ref.date price.open price.high price.low price.close volume ## 1 2020-04-13 118.032 130.400 116.106 130.190 112377000 ## 2 2020-04-14 139.794 148.376 138.486 141.978 152882500 ## 3 2020-04-15 148.400 150.626 142.000 145.966 117885000 ## 4 2020-04-16 143.388 151.890 141.344 149.042 103289500 ## 5 2020-04-17 154.456 154.990 149.532 150.778 65641000 ## 6 2020-04-20 146.540 153.114 142.442 149.272 73733000 ## 7 2020-04-21 146.024 150.666 134.758 137.344 101045500 ## 8 2020-07-10 279.200 309.784 275.202 308.930 116688000 ## 9 2020-07-13 331.800 358.998 294.222 299.412 194927000 ## 10 2020-07-14 311.200 318.000 286.200 303.360 117090500
하지만 거추장스러워 보이는 것이 사실이다. 이를 간단하게 만들 수 없을까?
이야기를 길어지고 있으니 이번 글에서는 결론만 제시한 후, 다음 글에서 좀 더 자세히 메타 프로그래밍에 대해 알아보도록 하자.
다음과 같이 orLags()
란 함수를 만들 것이다. 이 함수는 어떤 조건식이 있다면 ‘이 조건 또는 1 시간단계(timestep) 전에 이 조건’을 의미하는 조건식을 만들어낼 것이다. 또는 ‘또는’를 계속 연결할 수도 있다. 다음을 보자.
orLags = function(x, ns=1:4) {
xexpr = enexpr(x)
lst = vector('list', length=length(ns)+1)
lst[[1]] = xexpr
i = 2
for (n in ns) {
lst[[i]] = expr(lag(!!xexpr, n=!!n))
i =i + 1
}
or = function(a, b) {
expa = enexpr(a)
expb = enexpr(b)
expr(!!expa | !! expb)
}
purrr::reduce(lst, or)
}
dat %>%
filter(eval(orLags(price.close/price.open > 1.10, ns=1:6))) %>%
head(10)
## ref.date price.open price.high price.low price.close volume ## 1 2020-04-13 118.032 130.400 116.106 130.190 112377000 ## 2 2020-04-14 139.794 148.376 138.486 141.978 152882500 ## 3 2020-04-15 148.400 150.626 142.000 145.966 117885000 ## 4 2020-04-16 143.388 151.890 141.344 149.042 103289500 ## 5 2020-04-17 154.456 154.990 149.532 150.778 65641000 ## 6 2020-04-20 146.540 153.114 142.442 149.272 73733000 ## 7 2020-04-21 146.024 150.666 134.758 137.344 101045500 ## 8 2020-07-10 279.200 309.784 275.202 308.930 116688000 ## 9 2020-07-13 331.800 358.998 294.222 299.412 194927000 ## 10 2020-07-14 311.200 318.000 286.200 303.360 117090500
결과는 위의 여섯 줄짜리 식과 동일하다!
함수만 따로 적용해보자.
orLags(price.close/price.open > 1.10, ns=1:6)
## price.close/price.open > 1.1 | lag(price.close/price.open > 1.1, ## n = 1L) | lag(price.close/price.open > 1.1, n = 2L) | lag(price.close/price.open > ## 1.1, n = 3L) | lag(price.close/price.open > 1.1, n = 4L) | ## lag(price.close/price.open > 1.1, n = 5L) | lag(price.close/price.open > ## 1.1, n = 6L)
orLags(a+b+c==3, ns=c(1,3,5))
## a + b + c == 3 | lag(a + b + c == 3, n = 1) | lag(a + b + c == ## 3, n = 3) | lag(a + b + c == 3, n = 5)
놀라운가?
그렇다. 우리가 미처 몰랐지만 R은 인공지능의 가능성을 내포하고 있다. 비록 Good Old-Fashioned이지만.
Leave a comment