개요 ( What are Genetic Algorithms? ) 유전자 알고리즘(Genetic Algorithm, GA)은 적자 생존과 유전의 메카니즘을 바탕으로 하는 탐색 알고리즘이다. 다시 말해 주어진 환경에 잘 적응하는 유전자만을 선택(selection)하고 교배(crossover)하고 때에 따라서는 돌연변이(mutation)도 하며 다음 세대에 우수한 유전 형질이 전달(reproduction)되게 된다. 따라서 진화(evolution)가 거듭될수록 주어진 환경에 더 적합한 유전자들만이 남아있게 될 것이다.
유전자 알고리즘은 미시간대학의 홀랜드(John Holland)에 의해 탄생하였으며. 당시 연구원들의 연구 목표는
[1] 자연시스템의 적응적 과정을 추상화시키며 철저하게 분석하여,
[2] 그러한 시스템을 소프트웨어적으로 디자인하는 것이었다.
유전자 알고리즘(GA)은 그러한 배경에서 만들어졌으며, 근본적으로 다른 탐색이나 최적화 알고리즘과는 세 가지 정도가 다르다.
[1] GA는 하나의 개체(변수 등)가 아닌 개체들의 군(pool) 단위로 탐색한다.
[2] GA는 적합성 함수(fitness function)를 사용한다.
[3] GA는 확률적인 변이 규칙을 사용한다.
간단한 예를 들어보자. f(x) = x2[0 ≤ x ≤ 31] 이라는 환경이 있으며, 환경에 살아가는 개체(유전자)가 x인 셈이다. 그리고 환경에 잘 적응하는 정도를 함수값으로 정의하자. 그러므로 x = 0이라는 개체보다 x = 4라는 개체가 f(x) = x2[0 ≤ x ≤ 4]라는 환경에서 더 잘 적응하는 것이다.
환경 : f(x) = x2[0 ≤ x ≤ 31]
개체 : 0 ≤ x ≤ 31
적합성(fitness) : f(x)
01101
11000
01000
10011
위의 네 개체가 오손도손(^_^) 살고 있는 군(집단)에서 과연 f(x) = x2이라는 환경에서 누가 더 잘 적응할 것으로 생각되는가? 위의 이진수를 십진수로 바꾸어 생각하면 쉽게 알 수 있다. 위의 예에서 01000은 살기 어렵고, 11000은 그나마 가장 잘 적응할 것이다. 그러므로 다음 세대에서는 01000은 도태를 하고, 11000은 자손을 생산하게 된다. 이와 같은 과정을 거친 먼 훗날의 개체는 바로 11111이라는 개체가 탄생할 것이고, 이 넘(^_^)이 바로 주어진 환경을 지배할 것이다.
11111
11111
11111
11111
군(pool)이 위와 같은 개체(gene)로 구성되었을 때 '환경에 적응하였다'라고 말한다. 또는 '해를 찾았다'라고 말하기도 한다. 이와 같이 유전자 알고리즘은 자연의 적자 생존 원칙에 기반을 하고 있는 탐색 알고리즘이다. 다음 내용에서는 유전자 알고리즘의 단계별 과정들인 재생산(reproduction), 교배(crossover), 돌연변이(mutation)들을 알아보겠다
----------------
이번에는 유전자 알고리즘(GA; Genetic Algorithm)에 대해서 하겠습
니다.랜덤을 사용하는 알고리즘중에 제가 젤 좋아하는 알고리즘입니다
.(잘은 사용 하지만...^^)
그럼 강좌 시작하겠습니다.

찰스 다윈은 "종의 기원"에서 종이 어떻게 진화하는가를 적자 생존
의 원칙으로 명하였습니다. 그리고 멘델은 완두콩에 대한 실험으로 자
손의 형질은 원소로서 유전되며, 두 부모로 부터 각각 한개씩 받는다
고 설명하였습니다.

유전자 알고리즘은 적자 생존의 원칙과 교배, 돌연변이를 사용하여
탐색하는 기법으로 국소 탐색이나 시뮬레이티드 어닐링이 한개의 해
를 가지고 운영하는데 비해 유전자 알고리즘은 여러개의 해를 가지고
운영합니다.

유전자 알고리즘의 기본적인 구조는 다음과 같습니다.

Procedure GenetcAlgorithm;
begin
t := 0;
P(t)의 초기화(초기모집단 생성)
P(t)의 적응도 평가
while(종료조건이 만족되지 않으면) do
begin
t := t + 1;
P(t-1)로부터 P(t)를 선별
P(t)의 유전연산(교차와 돌연변이)
P(t)의 적응도 평가
end;
end;

P(t)는 t세대에서의 모집단을 나타냄니다. 간단한 문제를 가지고 예
를 들어 설명하겠습니다.

문제) f(x) = | 10*x-100 | 이고, 0 <= x <= 31 일때 함수 f(x)를
최대로 하는 x
를 찾아라. 단 x는 정수이다.

위 문제는 국소탐색을 할때 사용한 문제죠... 숫자만 약간 바꿨습니
다. 우선 해의 표현 방법을 정해야 겠죠. (유전 알고리즘에서 해는 보
통 유전자라고 부름니다. 이것 말고도 많은 용어들이 생물학에서 사용
되는 용어를 많이 사용합니다.) 0 ~ 31까지 나타내기 위해서는 5비트
가 필요 합니다. 유전자는 2진백터로 표현 합니다. 즉, (10101) = 21,
(11111) = 31, (01100) = 12 가 됩니다.

그러면 0세대 부터 시작 합니다. 처음에는 초기 집단을 생성 합니다.
초기 집단은 랜덤하게 만듭니다. 20개의 염색체로, 초기 집단이 다음
과 같이 만들어 졌다고 합시다.

v[1]: (01101)
v[2]: (01111)
v[3]: (10100)
v[4]: (01101)
v[5]: (10101)
v[6]: (01011)
v[7]: (10111)
v[8]: (10000)
v[9]: (01101)
v[10]: (01011)


평가 단계에서 각 염색체를 해독하고, 해독된 값으로 부터 적합도
함수값을 계산 한 결과 다음과 같은 결과를 얻었습니다.

eval(v[1]) = f(13) = 30
eval(v[2]) = f(15) = 50
eval(v[3]) = f(20) = 100
eval(v[4]) = f(13) = 30
eval(v[5]) = f(21) = 110
eval(v[6]) = f(11) = 10
eval(v[7]) = f(24) = 130
eval(v[8]) = f(16) = 60
eval(v[9]) = f(13) = 30
eval(v[10]) = f(11) = 10

이제 이중에서 몇개의 염색체를 선택해야 합니다. 선택하는 방법은
여러가지가 있는데 여기에서는 룰렛선택을 합니다. 룰렛선택은 적합도
에 비례해서 선택될 확률이 높아짐니다. 룰렛선택은 다음과 같이 합니
다.

------------------------------------------------------------------------------
1) 개체집단의 총 적합도를 계산한다.
F = eval(v[1]) + eval(v[2]) + .... + eval(v[10])
2) 각 염색체에 대한 선택 확률 p[i]를 계산한다.
p[i] = eval(v[i])/F
3) 각 염색체에 대한 누적 확률 q[i]를 계산한다.
q[i] = p[1] + p[2] + ... p[i]

선택과정은 유전자의 개수만큼 다음 방법으로 새로운 개체집단을 위
한 하나의 염색체를 선택합니다.

범위 0..1사이의 난수 r를 발생시킨다.
q[i-1]< r <=q[i] 인 i번째 염색체를 선택한다.
------------------------------------------------------------------------------

그럼 위와 같은 방법으로 룰렛 선택을 합니다. 개체집단의 총 적합도는

F = eval(v[1]) eval(v[2]) +...+ eval(v[10]) = 560

입니다. 각 염색체가 선택될 확률 p[i]는 다음과 같습니다.

p[1] = eval(v[1])/F = 0.0535714286
p[2] = eval(v[2])/F = 0.0892857143
p[3] = eval(v[3])/F = 0.1785714286
p[4] = eval(v[4])/F = 0.0535714286
p[5] = eval(v[5])/F = 0.1964285714
p[6] = eval(v[6])/F = 0.0178571429
p[7] = eval(v[7])/F = 0.2321428571
p[8] = eval(v[8])/F = 0.1071428571
p[9] = eval(v[9])/F = 0.0535714286
p[10] = eval(v[10])/F = 0.0178571429

그러면 각 염색체의 누적 확률 q[i]는 다음과 같습니다.

q[1] = 0.0535714286
q[2] = 0.1428571429
q[3] = 0.3214285714
q[4] = 0.3750000000
q[5] = 0.5714285714
q[6] = 0.5892857143
q[7] = 0.8214285714
q[8] = 0.9285714286
q[9] = 0.9821428571
q[10] = 1.0000000000

이제 0부터 1사이의 난수를 10(유전자의 개수)개 발생시켜 유전자를선택합니다.
만약 난수가 0.45772737743 이라면 q[4] < 0.45772737743 <= q[5] 를 만족하기 때
문에 유전자v[5] 를 선택합니다. 난수가 다음과 같이 발생 되었습니다.

0.4572273774 0.6205325316 0.2902582685 0.3707185990
0.6674934615 0.5626070339 0.7364188557 0.9917080770
0.6376504887 0.9917636998

그러면 선택된 개체들은 최종적으로 다음과 같습니다.

v'[1] = (10101)
v'[2] = (10111)
v'[3] = (10100)
v'[4] = (01101)
v'[5] = (10111)
v'[6] = (10101)
v'[7] = (10111)
v'[8] = (01011)
v'[9] = (10111)
v'[10] = (01011)

그러면 이제 유전 연산을 하면 됩니다. 유전 연산은 교배과 돌연변
이가 있는데 우선 교배부터 합니다. 교배하는 방법은 우선 교배 확률
에 의해 교배시킬 염색체를 선택합니다. 교배 확률은 0.25로 하겠습니
다. 다음과 같은 난수를 10개 발생시켜서 다음과 같은 수를 얻었습니
다.

0.5725024704 0.0490196129 0.8726333449 0.3486277370
0.3863749911 0.8044754410 0.8187371953 0.5160406448
0.0951179727 0.6477678808

결과 선택된 것은 v'[2]와 v'[9]가 선택되었습니다. 운좋게 짝수개
가 선택되어서 모두 교배시킬수 있습니다. 만약에 홀수개가 선택되었
다면 하나를 빼든가 추가해야 됩니다. 교배하는 방법은 다음과 같이
합니다.

------------------------------------------------------------------------------
염색체 x = (x1 x2 x3 x4 x5)와 염색체 y = (y1 y2 y3 y4 y5)가 있
다으면 우선 1 ~ 4(5-1) 사이의 수 중 난수 r를 발생 시킨다. 만약
r이 3이라면

x' = (x1 x2 x3 | y4 y5)
y' = (y1 y2 y3 | x4 x5)

로 r번째 다음에 있는 유전인자들을 바꿔주어서 새로운 유전자를 만
듭니다.
------------------------------------------------------------------------------

v'[2] = (10111) 와 v'[9] = (10111) 를 교배시키기 위해 난수 r를
발생 시킴니다. r은 1이 발생 되었습니다. 다음과 같이 됩니다.

v'[2] = (1 | 0111)
v'[9] = (1 | 0111)

v'[2]와 v'[9] 서로 같아서 아무런 변화가 없습니다. (예제가 나쁘
죠...프로그램을 실행시키면서 쓰는 것이라서 다른 걸로 바꿀수가...^^),
그리고 여기에서는 2개의 유전자가 선택 되었는데 만약 4개 또는 6개 이
상이 선택 되었다면은 임의로 2개씩 짝지어서 교배 시켜야 됩니다. 교배
이후에 개체집단은 다음과 같습니다.

v'[1] = (10101)
v'[2] = (10111)
v'[3] = (10100)
v'[4] = (01101)
v'[5] = (10111)
v'[6] = (10101)
v'[7] = (10111)
v'[8] = (01011)
v'[9] = (10111)
v'[10] = (01011)

이제 돌연변이를 시켜 줍니다. 돌연 변이 확률은 0.1로 하겠습니다.
돌연 변이는 각 염색체에 대해서 난수를 발생시켜 (돌연 변이 확률)<(
난수) 이면은 유전인자를 바꾸어 줍니다. 즉, 1이면 0으로 0이면 1로
바꿉니다. 돌연 변이를 시킨 다음에 개체집단은 다음고 같습니다. 바
뀐 염색체는 위에 '_' 표시를 했습니다.
_
v'[1] = (11001)
_
v'[2] = (00111)
_
v'[3] = (10101)
_
v'[4] = (00101)

v'[5] = (10111)
_
v'[6] = (10111)

v'[7] = (10111)

v'[8] = (01011)

v'[9] = (10111)
_
v'[10] = (00011)

그러면 v'에서 '표시를 지우고 다시 고쳐쓰면 다음고 같습니다.

v[1] = (11001)
v[2] = (00111)
v[3] = (10101)
v[4] = (00101)
v[5] = (10111)
v[6] = (10111)
v[7] = (10111)
v[8] = (01011)
v[9] = (10111)
v[10] = (00011)

이 개체의 적합도를 계산 하면 다음과 같습니다.

eval(v[1]) = f(25) = 150
eval(v[2]) = f(7) = 30
eval(v[3]) = f(21) = 110
eval(v[4]) = f(5) = 50
eval(v[5]) = f(23) = 130
eval(v[6]) = f(23) = 130
eval(v[7]) = f(23) = 130
eval(v[8]) = f(11) = 10
eval(v[9]) = f(23) = 130
eval(v[10]) = f(3) = 70

대체적으로 이전세대 보다 더 좋은 값을 갖는 개체집단이 생겼습니
다. 이런 방법을 반복하면은 꽤 좋은 해를 찾을수 있습니다. 10세대
까지 진행 시킨 결과는 다음과 같습니다.

v[1] = (11111)
v[2] = (11011)
v[3] = (11011)
v[4] = (11100)
v[5] = (10111)
v[6] = (11100)
v[7] = (11111)
v[8] = (11111)
v[9] = (11100)
v[10] = (11111)

이것을 평가하면 다음과 값을 얻을수 있습니다.

eval[2] = f(27) = 170
eval[3] = f(27) = 170
eval[4] = f(28) = 180
eval[5] = f(23) = 130
eval[6] = f(28) = 180
eval[7] = f(31) = 210
eval[8] = f(31) = 210
eval[9] = f(28) = 180
eval[10] = f(31) = 210

여기에서는 운좋게 마지막 세대에서 최적값 210을 얻을수 있었습니
다. 하지만 마지막 세대에 최적값을 못얻는 경우가 생길수 있으므로
여태까지 출현했는 염색체중에서 가장 좋은 평가값을 갖는 염색체를
따로 저장해 둡니다.
----
이번에는 죄수의 딜레마 라는 단순한 게임에 대한 전략을 학습하
기 위하여 어떻게사용될 수 있는지 설명하겠습니다.

------------------------------------------------------------------------------
죄수의 딜레마 - 두 명의 죄수가 독립된 방에 수감되어 있어서 서
로 통신할 수가 없는 상태에 놓여 있습니다. 죄수들은 각각 상대방
을 이탈하고 배신(죄를 시인하는 것이라 생각하면 됩니다)을 하도록
요구됩니다.

┌───────────┬──────────┬──────────┐
│ 나 \ 상대방 │협력 │ 배신 │
├───────────┼──────────┼──────────┤
│ 협력 │ 각각 2년형 │ 나만 5년형 │
├───────────┼──────────┼──────────┤
│ 배신 │ 상대방만 5년형 │ 각각 4년형 │
└───────────┴──────────┴──────────┘

위표와 같이 징역을 살게 됩니다. 그러면 이기적인 선택은 상대방
이 어떠한 선택을 하든 상관없이 협력하는 것보다는 더 나은 보상을
가져다 주게 됩니다(상대방이 협력 했을 경우에 내가 협력하면 2년형
을 살지만 배신하면 5년형이고, 상대방이 배신했을 때도 협력하면
5년형을 살지만 배신하면 4년형을 살게 되기 때문입니다) 하지만 둘
다 배신하면 둘다 협력했을 때보다 결과가 나쁘게 됩니다. 죄수의 딜
레마는 협력할 것인가 배신할 것인가를 선택하는 문제입니다.
------------------------------------------------------------------------------

만약 단, 한번의 기회만 주어진다면 어떤 선택을 해야 될까요?? 이
것을 여러번 시행하게 만들면 다음과 같이 점수를 주면 됩니다.


┌────────┬────────┬────────┬────────┐
│ Player1 │ Player2 │ Player1의 점수 │ Player2의 점수 │
├────────┼────────┼────────┼────────┤
│ 배신 │ 배신 │ 1 │ 1 │
├────────┼────────┼────────┼────────┤
│ 배신 │ 협조 │ 5 │ 0 │
├────────┼────────┼────────┼────────┤
│ 협조 │ 배신 │ 0 │ 5 │
├────────┼────────┼────────┼────────┤
│ 협조 │ 협조 │ 3 │ 3 │
└────────┴────────┴────────┴────────┘

위 표대로 점수를 주면서 게임을 합니다. 예를 들어 게임이 (협조,
협조),(협조,배신),(협조,배신)이 되었다면 Player1은 3+0+0=3점을 얻
게 되고, Player2는 3+5+5 = 13점을얻게 됩니다. 그러면 이것을 유전
자 알고리즘으로 점근해 보겠습니다.

1) 전략의 표현 - 전략은 최근에 했던 3게임을 가지고 현재의 행동을
선택합니다. 예를 들어 최근에 했던 게임이 (배신,배신),(배신,협조),
(협조,배신) 였고, 염색체 내의 이것에 해당되는 유전인자가 협조였다
면 그 염색체의 다음 행동은 협조가 됩니다. 이런 방법으로 표현하면
은 한 게임에 대해 4개의 가능한 결과가 존재하므로 4*4*4=64개의 서
로 다른 이전 행동들이 존재합니다.

64개의 서로 다른 행동에 대한 다음 행동이 협조냐 배신이냐에 따라
2가지 경우가 존재 하므로 비트 또는 D와 C로 표현할수 있습니다. 그리
고 게임시작 이전의 세 가설적인 행동에 관한 초기 가정을 규정할 필요
가 있으므로 추가로 6개의 유전인자가 필요하게 되빈다. 총 70개의 유
전인자가 하나의 염색체를 표현하게 됩니다.

2) 유전자 알고리즘의 윤곽
죄수의 딜레마에 대한 전략을 학습하기 위한 Axelrod의 유전자 알고
리즘은 다음과 같은 네 가지 과정으로 이루어집니다.

------------------------------------------------------------------------------
1. 초기 개체집단을 선택한다. 각 플레이어는 위에서 언급된 것처럼 하
나의 전략을 나타내는 70비트의 임의의 스트링에 할당된다.

2. 각 플레이어를 테스트하여 유효성을 결정한다. 각 플레이어는 자신의
염색체에 의해 정의된 전략을 사용하여 다른 플레이어와 게임한다. 그
플레이어의 점수는 그가 하는 모든 게임에 있어서의 평균이다.

3. 증식시킬 플레이어를 선택한다. 평균점수를 가진 플레이어에 한번의
증식 기회를 부여한다. 평균점수보다 하나의 표준편차 이상의 점수를 가
진 플레이어에는 두번의 증식기회를 부여한다. 평균점수보다 하나의 표
준편차 이하의 점수를 가진 플레이어는 증식기회를 갖지 못한다.

4. 성공적인 플레이어는 임의로 짝을 지어 교배당 두명의 자손을 만들어
낸다. 각 자손의 전략은 그의 부모의 전략으로부터 결정된다. 이것은 두
개의 유전 연산자인 교배와 돌연벼이에 의해 이루어진다.
------------------------------------------------------------------------------

3) 실험 결과
- 위와 같이 프로그램을 실행하고 나서 Axelrod가 얻은 결과는 놀랄
만한 결과를 얻었다고 합니다. 그 중 일부를 적으면 다음과 같습니다.

------------------------------------------------------------------------------
C는 협력, D는 배신;

1. 배를 흔들지 말라: 세번의 상호협력 이후에는 계속 협력한다
((CC)(CC)(CC) 이후에 C).

2. 화를 내라: 다른 플레이어가 배신하였을 때는 같이 배신하라
((CC)(CC)(CD) 이후에 D)

3. 사과를 받아들이라: 협력관계가 다시 정립되었을 때 계속 협력하라
((CD)(DC)(CC) 이후에 C)

4. 잊어버리라: 배신 후에 협력관계가 재정립되었을 때 협력하라
((DC)(CC)(CC) 이후에 C)

5. 관례를 받아들이라: 세번의 상호 배신 이후에 배신하라
((DD)(DD)(DD) 이후에 D)
------------------------------------------------------------------------------

------------------------------------------------------------------------------
제가 해본 결과는 신기하게도 ((CC)(CC)(CC) 이후에 D)로 진화하
는 개체가 되는군요...^^;; 아마도 어딘가 잘못한듯 싶은데... 혹시
시간 있으신 분은 한번 위 프로그램을 짜보세요...그래서 결과를 올
려 주시면 감사...(제가 한번 보고 싶어서여...^^;;)
-------------
이번에는 유전자 알고리즘의 동작 원리에 대해서 하겠습니다.

우선 스키마란 개념을 알 필요가 있습니다. 스키마란 염색체들 사이의
유사성을 나타낼 수 있는 하나의 전형 입니다. 이것은 '*'기호를 사용해
서 나타내는데 '*'기호에는 어떠한 유전인자와도 대응된다는 표시 입니
다. 예를 들어 (*10101101)은 다음 두개의 염색체와 일치 합니다.

(010101101), (110101101)

다른 스키마 (*101*01010*)은 다음 염색체와 일치 합니다.

(01010010100), (01010010101), (01011010100) (01011010101)
(11010010100), (11010010101), (11011010100) (11011010101)

스키마에서 '*'기호의 갯수가 k개이면 이것에 대응되는 염색체의 갯수
는 2^k개가 됩니다.

그러면은 유전 알고리즘은 #1에서 논의한 대로 선택, 교배연산, 돌연변
이연산을 거치게 됩니다. 우선 선택 연산만 생각하고 어떤 스키마 S가 살
아 남을 확률을 계산해 보겠습니다.

만약 어떤 염색체의 적합도가 상당히 높다면은 그 염색체는 다음세대에
여러개가 생길것이라고 예측할수 있습니다. 예를 들어 염색체가 다음과 같
이 있고, (강좌 #1에서의 문제 입니다)

v[1]: (01101)
v[2]: (01111)
v[3]: (10100)
v[4]: (01101)
v[5]: (10101)
v[6]: (01011)
v[7]: (10111)
v[8]: (10000)
v[9]: (01101)
v[10]: (01011)

각 염색체의 적합도는 다음과 같습니다.

eval(v[1]) = f(13) = 30
eval(v[2]) = f(15) = 50
eval(v[3]) = f(20) = 100
eval(v[4]) = f(13) = 30
eval(v[5]) = f(21) = 110
eval(v[6]) = f(11) = 10
eval(v[7]) = f(24) = 130
eval(v[8]) = f(16) = 60
eval(v[9]) = f(13) = 30
eval(v[10]) = f(11) = 10

위 개체의 총 적합도는 560이고, 평균 적합도는 56이 됩니다. 염색체
v[7]같은 경우에는 평균 적합도의 약 2배정도 됩니다. 이것은 다음 세대
에 염색체 v[7]이 약 2개정도 있을거라고 짐작할수 있습니다. 다음 세대
에서도 v[7]의 적합도가 높다면은 2개에서 4개로 늘어날수 있고, 그 다
음에 8개라는 식으로(실제로는 평균 적합도가 높아져서 이렇게 되기 힘
들겠지만) 될 것이라 생각할수 있습니다. 즉, 어떤 염색체가 평균 적합
도보다 높으면 그것은 다음 세대에서 기하 급수적으로 늘어날 것을 예상
할수 있습니다. 같은 방법으로 염색체가 평균 적합도보다 낮다면은 그것
은 다음 세대에서 기하 급수적으로 줄어 들게 되고, 평균이라면은 같은
수의 염색체들이 다음세대에 살아 있을거라 예상 할수 있습니다.

그러면 스키마 S와 일치하는 염색체들의 적합도의 평균을 구합니다.
이것을 스키마 S의 적합도라 부르겠습니다.
그러면 만약 스키마 S의 적합도가 평균 적합도 보다 크다면 다음 세대
에 스키마 S와 일치하는 염색체는 기하 급수적으로 늘어날 것이고, 평균
이라면 같은수의 염색체가 존재할 것이고, 평균 이하라면은 기하 급수적
으로 줄어들것이라고 예측할수 있습니다.

그러면 선택된 스키마가 교배나 돌연변이 연산에 의해서 파괴될수도
있습니다. 교배연산에서 다음과 같은 스키마가 선택되었다고 합시다.

(***111*****), (111*****01*)

그러면은 교배연산은 각 유전인자의 위치에 대해 골고루 선택될 확률이
높으므로 첫번째 스키마가 파괴될 확률은 두번째 스키마가 파괴될 확률보
다 낮습니다. 엄밀하게 계산하면 첫번째 스키마가 파괴될 확률은 3/11이
고, 두번재 스키마가 파괴될 확률은 10/11이 됩니다.

그리고 또 돌연변이 연산을 하게 됩니다. 돌연 변이할때는 고정된 유전
인자의 개수가 적을수록 스키마가 파괴될 확률이 적어지게 됩니다.(고정된
유전인자의 개수를 차수라고 합니다 : (101*1****)의 차수는 4가 됩니다),
위에 있는 염색체에서 첫번째 스키마의 차수는 3이 되고, 돌연변이 연산에
의해 파괴될 확률은 {1-(돌연변이 확률)}^3이 되고, 두번째 스키마가 돌연
변이에 의해 파괴될 확률은 {1-(돌연변이 확률)}^5가 됩니다.

즉, 이것을을 정리하면

선택 - 평균 이상이면 지수합수적으로 증가한다.
교배및 돌연변이 연산 - 짧고, 낮은 차수의 스키마는 생존율이 높다.

이것으로 부터 다음과 같은 스키마 정리와 구설부 가설이 생기게 됩니다.

------------------------------------------------------------------------------
스키마 정리 - 짧고, 낮은 차수의, 평균 이상의 스키마는 유전자 알고리
즘의 이후 세대에서 지수함수적으로 증가하는 시도를 받는다.

즉, GA는 짧고 낮은 차수의 스키마에 의해서 공간을 탐색한다는 것입니다.

구설부 가설 - 유전자 알고리즘은 구설부라고 불리는 짧고, 낮은 차수의,
고성능 스키마타의 병렬을 통하여 근사 최적 성능을 추구한다.
------------------------------------------------------------------------------

------------------------------------------------------------------------------
수학적인 식들은 다 제거해버리고 썼습니다.


[출처 : http://zzpark.egloos.com/5508970]

 배신과 협력의 게임(1라운드의 죄수의 딜레마)

물주(게임 진행자) 한 명과 두 사람이 게임을 한다. 게임을 하는 두 사람의 손에는 '협력'과 '배신'의 카드 2장이 있으며 단 1라운드만 진행이 된다. 카드의 조합은 아래 표의 네 가지가 되며, 각각의 경우에서 이득과 손해는 아래와 같다.

A가 선택한 카드
B가 
선택한 카드
협력 배신
협력 보수
(상호 협력에 대한) 
예) 300달러 매우 나쁨
매우 나쁨
예) 100달러 징수
배신 매우 좋음
유혹료
(배신으로)
예) 500 달러
꽤 나쁨
벌금
(상호 배신에 대한)
예) 10 달러 징수

표1. '협력'과 '배신'의 이득과 손해

 

가령 위의 예에서 상호 협력의 카드를 제출할 때는 상호 협력에 대한 보상으로 물주로부터 300달러를 받는 것이다. 이 때 배신에 대한 유혹료는 상호 협력의 보수보다 높아야 하고, 상호 협력의 보수는 상호 배신의 벌금보다는 좋아야 한다. 이 밖에 여러 가지 설정이 있지만 생략한다.

그럼 왜 '딜레마'일까? 누구든 낼 수 있는 카드는 '협력'과 '배신'이라는 카드 2장 밖에 없다. 만일 A가 '배신'을 냈다면 B는 최선의 카드로 '배신'을 내야 할 것이다. 만일 '협력'의 카드를 낸다면 B는 100 달러를 게임 진행자에게 줘야 한다. A가 '협력'의 카드를 냈을 경우에도 당신이 얻을 수 있는 최대의 이득은 '배신'의 카드를 내는 것이고 그 때 유혹료로 500달러를 얻을 것이다.

결론은 타인이 어느 카드를 내든 상관없이 당신의 최선 행동은 '항상' 배신이라는 것이다. 그러므로 두 사람의 이성적인 경기자가 만나면 모두 배신하여 같이 벌금 또는 운 좋으면 유혹료를 받게 될 것으로 끝난다. 두 사람 모두 만일 쌍방이 '협력'만을 낸다면 상호 협력으로 비교적 높은 보수를 받을 것을 확실히 알고 있다. 하지만 '배신'의 카드를 내는 것이 최선의 행동이라는 것이다. 쌍방이 서로 연락하거나 정보를 주고 받을 수 있다면, 서로 연락해서 '협력'의 카드를 제출하겠지만, 그렇지 않을 경우에는 '배신'을 하는 것이 합리적인 게임 진행이 될 것이다. 그러니 딜레마인 것이다.

딜레마 (dilemma) ①(논리학) =양도 논법(兩刀論法). 
선택해야 하는 길은 2개뿐인데 그 어느 쪽도 바람직하지 못한 결과를 초래하는 상황.

 


 반복 죄수의 딜레마

'죄수'란 하나의 특별한 상징적인 예에서 유래한다. 이 경우는 돈이 아닌 죄수의 형기이다. 가령 A군과 B군이 자신들이 저지른 범죄 때문에 공범 혐의로 투옥되어 있다. 그 죄수들은 각각 독방 속에서 공범자에 대한 불리한 증언을 함으로써 동료를 배신하도록 유혹당했다. 처분은 두 사람의 죄수가 어떻게 하는가에 따라 결정되며, 어느 쪽도 상대가 어떻게 했는지 모른다. 만일 각각이 배신하면 양자 모두 죄가 인정되나 증거를 제공한 점으로 약간의 신용을 얻어 어느 정도 경감된 형기, 즉 상호 배신의 벌을 받게 된다. 만일 양자가 협력하여 증언을 거부하면 유죄가 될 충분한 증거가 없으므로 보다 경미한 죄로 짧은 형기, 즉 상호 협력의 보수를 받는다. '지불'이 돈이 아니라 형기였지만 이 게임을 기본적 특징이 보전되어 있는 것을 느낄 것이다.

1라운드의 게임에서는 상호 배신으로 끝나게 될 운명이 되고 만다. 그러나 그것이 '반복'된다면 그 때에도 '배신'을 할 것인지 생각해보자. 반복 게임은 동일한 경기자에 의해 제한없이 반복되어 행해질 뿐만 아니라 또한 두 사람은 서로 바라보고 있으며 그 사이에는 게임 진행자(판사)가 앉아 있다. 두 사람은 두 카드의 어느 한쪽을 냄으로써 승부를 내고 게임 진행자(판사)는 먼저 제시한 규칙에 따라 형기를 결정한다. 이제는 1 라운드의 게임으로 끝나는 대신에 우리는 또 다시 카드를 집어서 다음의 승부에 대비한다. ( 이와 같은 반복은 두 사람이 반복적으로 공범자로 잡힌다고 가정하면 간단히 설명된다. 단 서로의 연락은 되지 않는다.) 몇 번 승부를 계속하는 사이에 두 사람에게는 신용, 또는 불신이 쌓여 협력 또는 배신을 할 수 있는 기회가 주어진다. 제한없이 긴 게임에서 중요한 점은 서로가 손해를 볼 것 없이 쌍방이 협력의 보수를 받을 수가 있다는 것이다.

 

 전략

과연 위와 같은 게임을 할 경우 어떤 전략을 가진 자가 장기적으로 승리를 거둘 수 있을 것인가를 생각해보자. 몇 개의 교묘한 전략이 가능하나 승리를 거든 전략은 놀랍게도 가장 단순하고 표현적으로는 모든 것 중에서 가장 교묘함이 부족했다. 그것은 '당하면 갚는다(tit for tat)'라는 전략으로 불렸고 최초의 카드는 협력으로 시작하고 그 이후는 단순히 바로 전에 상대가 낸 카드를 흉내내는 것뿐이다. 즉, 상대가 배신을 하면 내가 배신을 하는 것이다.

다음은 '소박한 시험꾼(naive prober)'으로 '당하면 갚는다'와 같으나 10회중에 1회는 함부로 이유없이 배신하여 유혹료로서 높은 득점을 기대하는 전략이다. 그리고 '원한파'는 한번 상대방이 배신하면 끝까지 배신하는 전략이고, '항상 배신은 항상 배신만 하는 전략이다. 그 밖에 수많은 전략들이 생겨날 수 있어 이 게임의 다양성은 증가된다.

다음은 대표적 전략들을 간략히 설명한 표이다.

유형
전략 이름
전략
마음씨 좋은
(nice)
항상 협력 항상 협력의 카드만 제시
두 발에 한발 갚기. 최초 협력, 상대방이 두 번 연속 배신을 하면 배신
당하면 갚는다.
(tit for tat)
최초의 카드는 협력으로 시작, 
상대가 배신을 하면 내가 배신
원한파 최초 협력, 상대가 한번 배신하면 계속 배신
간악 항상 배신 항상 배신
소박한 시험꾼
(naive prober)
최초 협력, 가끔 배신

표2. 죄수의 딜레마에서 대표적인 전략

 

이러한 게임을 '액셀로드'라는 사람이 전략을 하나의 공통 프로그램 언어로 번역하여 대형 컴퓨터로 서로 대전시켰다. 각각의 전략은 다른 모든 전략과 순차적으로 짝지어져서 반복 죄수의 딜레마 게임을 하는 것이다.

액셀로드가 인정하고 있는 가장 중요한 범주는 '마음씨 좋은(nice)'이다. 마음씨 좋은 전략은 최초로 배신하는 일이 결코 없는 것으로 정의된다. '당하면 갚는다 (tit for tat)' 가 그 일례이다. 소박한 시험꾼은 때때로 배신을 하므로 간악한 전략이다. 토너먼트에 참가한 15개의 전략 중 득점이 높은 쪽부터 상위 8위까지 모두 8개의 '마음씨 좋은(nice)' 전략이 차지하고 있다는 사실이 의미 깊다고 할 수 있다.

 

 관용

전략이란 것은 보복하는 일은 있으나 단기의 기억밖에 없다. 그것은 오래된 나쁜 일을 바로 잊어버린다. '당하면 갚는다(tit for tat)'는 하나의 관용 전략이다. 배신자에 대해 즉시 한 때 보복으로 갚고 그 후에는 과거를 씻듯이 잊는다. 하지만 원한파는 절대로 용서하지 않는다. 그 기억은 게임의 전기간을 통해 지속된다. 한 번이라도 자기에게 배신한 적이 있는 상대에 대한 원한을 결코 잊지 않는다. 원한파와 같은 전략이 프리드만(friedman)이라는 호칭으로 대전이 되었지만 게임 성적이 별로 좋지 않다.

우리는 승리하는 전략에 두 가지 특징이 있다는 것을 상정할 수 있다. 즉 '마음씨 좋기'와 관용함이다. 다시 말해, 처음부터 배신하지 않는 전략과 단기의 기억밖에 없는 관용 전략이 그것이다. 거의 유토피아적인 경향의 이 결론은 '마음씨 좋은 사람이 성공한다' 라는 문구와 비슷한 의미를 담고 있다. 윤리가 논리로써 이끌어지는 순간이다.

위와 같은 전략은 평균적이고 반복적인 전략끼리의 대전이며, 여기가 중요한 사실은 그러한 전략의 성공은 어떤 전략이 서로 대전하는가에 달려 있다라는 것이다. 다시 말해 다른 모든 전략이 '항상 배신'이면 '마음씨 좋은' 전략이 당하기만 한다. 그러므로 '최초 구성이 어떤 전략으로 구성되었는가' 라는 요소와 전략들의 구성 비율도 각각의 전략에 대한 성공, 실패에 대한 영향을 미친다.

 

 ESS(Evolutionarily stable strategy, ESS)

위에서 언급한 것처럼 각 전략의 순위는 어떤 전략이 제출되었는가에 의존한다. 가령 대부분의 전략이 간악한 것이었다면 '당하는 갚는다'는 이기지 못 했을 것이다. 바꾸어 말하면 전략의 순위가 인간의 불안정하고 자의적인 것에 의존하고 있다. ( 인간이 어떤 전략을 대전시키느냐에 따라 전략의 순위가 결정될 수 있다.) 이 자의성을 어떻게 하면 줄일 수 있을 것인가? 그것은 ESS로 해결될 수 있다. ESS라는 메이나드-스미스가 제창하고 있는 중요한 개념은 '진화적으로 안정된 전략'이라는 불리는 것이며, 여기서 전략이라는 것은 미리 프로그램되어 있는 행동 방침이다. 쉽게 말하면, 정확치는 않지만 각각의 전략을 이루는 제 1세대를 만들고, 유전자 알고리즘으로 세대를 거쳐가면서 최종적으로 살아남는 전략들이 ESS가 될 수 있다.

액셀로드는 63개의 전략을 취하여 그것을 또 다시 컴퓨터에 입력해서 그것들을 제 1세대로 만들었다. 따라서 제 1세대의 풍토는 63개의 모두의 전략을 균등히 대표하는 것으로 되어 있었다. 제 1세대의 끝에 각 전략의 승자는 돈이나 득점이 아닌 그의 부모와 동일한 전략을 취하는 후손의 수로 지불된다. 세대가 진행함에 따라 어떤 전략은 수가 적어져서 최종적으로는 절멸한다. 다른 전략은 점점 수가 많아진다. 따라서 그 비율이 변함에 따라 전략들의 대전이 일어나는 풍토(전략들의 구성 비율)도 변한 것이다. 대전의 결과는 몇 개의 전략은 최초부터 절멸로 향하고 대부분은 200세대에 가서야 절멸한다. 재미난 사실은 해링턴(Harrington)이라 불리는 간악한 전략은 최초의 150세대쯤 급격하게 상승했다. 그것은 '두 발에 한발 갚기' 등의 연약한 상대가 주위에 있는 한은 그들을 착취했다. 그 후 연약한 상대가 절멸 당하게 되면 '해링턴(Harrington)'은 그들만의 포획물이 없게 되어 그들의 뒤를 쫓아 절멸하게 됐다. 싸움터는 '당하면 갚는다'처럼 마음씨 좋은 전략의 독무대로 됐다.

진화가 거듭되어 모든 간악한 전략이 절멸에 임박하게 되면 아무리 마음씨 좋은 다른(두 발에 한발 갚기) 전략도 '당하면 갚는다(tit for tat)'와 서로 상호간에 구별하는 방법이 없어지게 된다. 왜냐하면 그것들은 모두 마음씨가 좋기 때문에 서로 협력의 카드를 내놓기 때문이다. 이런 최종적인 전략들의 구성은 ESS와 닮았지만 엄밀하게는 ESS가 아니라는 것이다. 어떤 전략이 ESS가 되려면, 그것이 희소한 돌연변이의 전략에 변경되어서는 안 된다는 것을 생각해 주기 바란다.

프로그램적인 ESS는 마음씨 좋은 전략이 우세를 점하지만, 거의 무조건인 마음씨 좋은 전략( 두발에 한발 갚기, 세발에 한발 갚기 등) 이 숨어 있게 되어, 간악한 전략의 침입에 쉽게 변할 수 있기 때문에 액설로드는 다음과 같은 결론을 내린다.

"아마도 조금 간악한 전략과 마음씨 좋은 매우 관용한 전략과 
그리고 당하면 갚는다(tit for tat)와의 전략들의 혼합으로 ESS가 구성된다"

그런 전략들이 구성된 ESS는 어떤 다른 전략이 들어와도 ESS가 쉽게 변하지 않음을 알 수 있다. 다시 말해 전략들의 진화가 안정화되었다고 볼 수 있다. 최종적인 진화 상태임을 암시한다.

이것이 인간 생활에 흔한 양상을 반영하는 거울임을 알 수 있다. 즉, 어느 정도의 간악한 사람과 '당하면 갚는다(tit for tat)'와 같은 전략을 가진 사람들이 이 세상에 우세한 이유도 죄수의 딜레마라는 게임에서 유추할 수 있다.여기서 제 나름대로의 재미난 결론은 가장 이상적인 전략은 '당하면 갚는다(tit for tat)'이라는 전략이다. 한번 상대방이 배신을 하면 바로 배신을 하는 전략이 우리의 인생을 최소의 실패율을 가져다 줄 수 있는 전략이라는 것이다. 두 번을 참아서도 안 된다. 오직 한번만이 참을 뿐이다. 대신 관용을 유지해야 한다. 상대방의 잘못은 단기 기억으로 잊어버린다. 이제부터는 이것을 내 인생 전략으로 삼을까 싶다. ㅋㅋㅋ

참고자료 : 이기적 유전자, 리처드 도킨스 지음, 홍영남 옮김, 을유 문화사, 89-324-6025-6


[출처 : http://www.gurugail.com/GeneticAlgo/page.html?subject=prisonerDilemma.html]

[출처] KISS ILVB Tutorial(한국정보과학회) 에서 발표( Dr. Sung-Jung Cho)된 내용 중 발췌

 

NLP 관련 응용 프로그램 중에서 가끔 Markov Model을 사용할 필요가 있는데, 이에 대해서 아주 잘 요약된 pt가 있어서 주석(?)을  달았다.

 

1. 문제의 종류 - 날씨 예측

 

  1) 날씨(state) = { sunny, cloudy, rain }

  2) 관측 데이터


  3) 가정 : 오늘 날씨(current state)는 어제 날씨(previous state)에만 영향을 받는다(dependent)  ==> Markov Assumption

 

  4) 이 문제를 시각적으로 표현하면


     -  각각의 상태에서 나가는 화살표(outgoing edge) 확률의 합은 1 이다.

 

2. 문제의 모델링 및 해결

 

  자 이제 위와 같이 관측 데이터(observation - 과거 데이터)을 가지고 있는 상태에서,  우리는 아래와 같은 문제를 실제 풀려고 한다.

  문제)  sunny  --> sunny --> cloudy --> rain --> sunny --> ? 

  물음표에는 어떤 날씨가 될 확률이 가장 높을까?

 

  이 문제를 모델링하면 아래와 같은 그림으로 표현할 수 있다.

  

 

   위의 3번째 줄에서 보면,  우리가 알고자 하는 것은 조건부 확률(conditional probability) 즉,  T=t  ( 현재 상태 )에서의 어떤 상태의 조건부 확률임을 알 수 있다.

   그런데, 이 조건부 확률은 first-order Morkov Assumption에 의해서 바로 이전 상태에 대한 조건부 확률만 계산하면 되는 것으로 축약된다.

 

   음.... 이건 너무 단순하잖아?   그렇다. 사실 오늘 날씨가 어떨지 계산하는 문제는 그냥 단순히 Morkov Assumption을 사용해서 계산하면 간단하다.

   하지만 우리가 실제로 풀려고 하는 문제는 이것이 아니라, 좀더 복잡한 문제이다.

 

   문제)  오늘 날씨가 sunny 인 경우,  다음 7일의 날씨 변화가(state sequence)가 'sunny-sunny-rain-rain-sunny-cloudy-sunny' 일 확률은?

 

   에구. 사실 우리는 과거를 통해서 미래를 예측하길 원하는 거지, 단순히 오늘만을 알려고 하는 것은 아니다.

 

   그렇다면, 이 문제의 답을 구하기 위해서 필요한 것은 무엇일까?  sequence probability를 계산하는 공식이다.


  이 수식을 이용해서 다시 한번 문제를 표현하면 아래와 같다.

 

  S1 =  rain  ,   S2 = cloudy  ,   S3 = sunny  인 경우


  엥? 근데  condition으로 있는 model 은 무엇이지?  가끔 확률 공식을 보면 이런식으로 수식을 계산하는데 배경으로 깔려있는 조건 전체를 지정하기도

  한다. 이는  관측 데이터에 따른 state transition probability ,  first-order Morkov Assumption 등등을 통칭하는 의미라고 할 수 있다.

 

  자~  이제 계산을 할 수 있다.      

 

  여기서 잠깐,  그런데 정말로 우리가 풀려고 하는 문제가 이것인가?  생각해 볼 필요가 있다.  다음 7일 동안의 날씨 변화(state sequence)를 아는 것은

  좋은데,  우리가 진짜로 알고 싶은 것은 바로 일주일 이후 바로 그날의 날씨가 아닌가?  예를 들어, 8일째 데이트를 하려고 하는데 날씨가 어떤가?

  알고 싶을 것이다.

 

  이 문제를 풀기 위해서 또 다시 문제를 모델링하면 아래 그림과 같이 표현될 수 있다.

 

 

    위 수식을 이용하면  8일째 맑을 확률을 알 수 있을 것이다. ㅎㅎ

 

    그런데, 왜 시간 T=t에서 상태 i인 모든 path( state sequence)의 확률을 전부 더한게 T=t 에서 날씨(state)가 i 일 확률일까?

 

    간단히 생각하면,  P(A OR B) = P(A) + P(B)  이기 때문이다. ^^

 

 

3.  계산의 최적화

 

  그런데 위의 수식을 가만히 보면, state sequence가 길어지면 질수록, state가 많아지면 많아 질수록 느려짐을 알 수 있다.

 

  전문용어(?)로 말하면 exponential time complexity가 있다고 하는데

 

  그냥 사용하기 느리다는 거다.

 

  이 문제는 또 어떻게 해결해야 할까? (  사실 Morkov Model과는 다른 문제다. 최적화 문제 )

 

  우리의 연구자들은 이미 해결책을 찾아두셨다. ㅋㅋ 

 

  Dynamic Programming 기법을 사용하는 것인데, 이미 한번 계산한 확률은 다시 계산하지 않고 그대로 재활용하자는 개념이다.

 

  (DP 기법은 프로그램으로 나타내면 거의 recursive callcuation이다. 고등학교때 배운 수학적 귀납법 공식을 떠올리면 쉽게 이해가 갈 것이다)

 

  이를 수식으로 정리하면 아래와 같다.


    자~~  이제 위의 간단해진 수식을 사용하면 좀더 빨리 계산할 수 있겠다. 

 

    여기서 aji는 상태 j에서 상태 i로의 전이 확률(transition probability)이다. 즉, 모델의 prior probability 이다.

 

    prior는 확률이론에서 아주 자주 나오는데, 그냥 우리가 이미 알고있는 사실을 기반으로 어떤 확률을 계산할 때, 그 알고있는 사실에 대해서 표현하는 단어이다.

 

    배경지식이랄까?  '동전던지기에서 앞면이 나올 확률은 0.5 이다'라는 것도 경험을 통해 얻어진 prior 라고 할 수 있다.

 

    음... 그러면  아주 재미있는 문제를 생각해 볼 수도 있겠다. 

 

    문제) 

             앞면이 나오고 앞면이 나올 확률이 0.6 ,  뒷면이 나올 확률이 0.4

             뒷면이 나오고 뒷면이 나올 확률이 0.3,   앞면이 나올 확률이 0.7 인

             조금 기형적인 동전이 있다고 하자.

             여기서 상태는 { 앞면, 뒷면 }

             이때, 앞으로 3번 더 던졌을 때, 그 결과가 앞면이면 내가 이길 것 같다. 그럼 그 확률은?

             (단, 처음 동전을 던졌을 때, 앞면과 뒷면이 나올 확률은 같다)

             이 확률을 Morkove Model를 이용해서 계산해 보자.

 

    답)  P(q3 = 앞면) =  P(q2 = 앞면) * P(앞면|앞면) +  P(q2 = 뒷면) * P(앞면|뒷면)

          P(q1=앞면) = 0.5

          P(q1=뒷면) = 0.5

          P(q2 = 앞면) = P(q1 = 앞면) * P(앞면|앞면) +  P(q1 = 뒷면) * P(앞면|뒷면)

                            = 0.5 * 0.6 + 0.5 * 0.7 = 0.3 + 0.35 = 0.65

          P(q2 = 뒷면) = P(q1 = 뒷면) * P(뒷면|뒷면) +  P(q1 = 앞면) * P(뒷면|앞면)

                             = 0.5 * 0.3 + 0.5 * 0.4 =  0.15 + 0.2 = 0.35

          P(q3 = 앞면) = 0.65 * 0.6 + 0.35 * 0.7 = 0.39 + 0.245 = 0.635

 

          오옷!  확률이 0.635나 되넹~~ ㅋㅋ  당근 걸어야쥐~~

 

 

4. 결론

 

 Morkov Model에 대해서 이해하는 데 있어, 핵심은 바로

 

"우리는 과거를 통해서 미래를 알고 싶다" 일 것이다.

 

 그리고 가끔 Hidden Morkov Model과 헷갈리는 응용이 있는데, 이를 구분하는 방법은 미래 상태(state)를 관측할 수 있는가에 달려있다.

 

예를 들어, part-of-speech tagging 에서는 상태가 바로 품사(POS)이다.  그런데 우리는 아래와 같은 문제에서 품사는 관측 못하고 품사에서

 

생성된(generated) 형태소만을 알 수 있다.

 

문제) '나는 학교에 간다' 라는 문장을 태깅하시오.

 

그래서 HMM에서는 emition probability 혹은 generation probability 라는 개념이 추가적으로 들어간다.

 

5. 마지막으로, Markov Model을 가지고 응용할 수 있는 문제들은 어떤게 있을까?

 

음...  예를 들자면, 전국민의 놀이 로또(lotto)가 있을 수 있겠다.

 

로또의 경우는

 

관측 가능한 상태 = { 1, 2, ....., 45 }

 

초기 prior 확률은 현재 297회까지 진행되었으니, 이를 통해서 구할 수 있을 것이다.

 

따라서, 6개의 번호에 대해서 선택한 경우, 이 sequence의 확률을 계산할 수 있겠다.

  

45개에서 6개를 순서있게 선택하는 경우의 수를 45P6 인데, 이 모든 경우에 대해서 sequence probability를 구해서

 

가장 확률이 높은 순서열을 구한다면 어떨까?

 

요새 웹에 떠돌아 다니는 로또 번호 자동 생성기는 아마도 이걸 응용한 프로그램이라고 생각된다. 혹시 다른 방법을

 

사용했을 수도... ㅋㅋ

 

관심있는 분은 만들어 보시길~~ ^^

 

아 참고로, 유전자 알고리즘(genetic algorithm)을 이용해서 로또 생성기를 만드는 경우도 있을 겁니다.

 

짧게 설명하면, 유전자 알고리즘은 아래 그림(참조 URL)과 같이 설명될 수 있는데, 

 

   여기서 population 부분은 feature vector로 일종의 유전 형질이라고 할 수 있다. 좋은 유전 인자는 계속 유전되고, 그렇지 않은 열성 인자는 자연 도태된다.

   세대를 거치면 거칠 수록 말이다. selection, cross-over, mutation 등의 기본 연산은 자연의 유전 법칙을 시뮬레이션 한 것이라 할 수 있다.

 

   로또의 경우는 279회차 까지의 당첨 번호를 가지고  길이가 45인 279개의 feature vector를 만들어 낼 수도 있을 것이다.

   해당 회차에 당첨된 번호는 우성 번호이고, 그렇지 않은 번호는 열성 번호로....

 

   이를 이용해서 유전자 알고리즘을 수행하고 몇 세대 이후 남아있는 feature vector를 decoding해서 적절하게 원하는 번호를 선택하는 방법입니다.

 

   이 방법과 Markov Model을 결합해서 hybird로 만들어 볼 수도 있겠군요. ㅋㅋ

 

   물론, 학술적인 연구와는 거리가 있고 그냥 단순히 '재미로'겠지만 말입니다. ^^



[출처 : http://blog.daum.net/hazzling/15605818]

[참고] http://www.seanborman.com/publications/EM_algorithm.pdf

 

 "EM 알고리즘은  어떤 정보가 hidden 되어 있는 경우 가장 그럴듯한 모델을 추정할 때 사용하는 효과적인 반복 알고리즘이다.

  보통 Maximum Likelihood estimate 방법을 사용해서 관측된 데이터에 알맞은 모델의 변수(parameter)를 추정한다. "

 

  즉, 어떤 모델의 변수를 모르는 경우에 이를 추정하는 방법이라는 건데,

  초기에 랜덤하게 혹은 어떤 다른 방법으로 모델 변수를 설정한 상태에서, 관측데이터(X)가 이 모델(ramda_0)로부터 생성되었을 확률을

  계산한다.

  P(X | ramda_0 )

  이제 모델 내부의 변수를 아주 조금 수정한 새로운 모델을 ramda_1 이라고 하자.

  그리고 나서 다시 관측데이터의 posterior 확률을 구하면

  P(X | ramda_1 )

  자, 이제 어떤것이 더 큰지 결정한다.

  만약 P(X | ramda_1 ) 이 더 크다면, 우리는 ramda_1이 ramda_0 보다 더 관측데이터(X)가 나올법한 모델이라는 것을 알 수 있다.

  그 다음에는 어떻게 하는가?

  위 작업을 계속 반복하는 것이다. 이것이 iterative procedure

  그러면 언제 멈추는가?  새로운 모델 ramda에 대한

  P( X | ramda ) 가 기존 모델보다 부적합한 경우(likelihood가 더 작은 경우)에 멈추면 된다.

  음...

  좀더 진행하면 현재보다 더 좋은 모델이 나올 가능성은 있지만 말이다. 그래서 local maximum을 찾는 다고 하는 것이다.

 

  다시 본론으로 들어가서,  그러면 E-step, M-step은 무엇인가?

 

  확실한 예가 없어서 이해하기 힘들기는 하다.

 

  계속 살펴보도록 하자.

 

 

  ramda 대신에 theta를 쓰면,

  P( X | theta ) 는 관측데이터 X가 있을 경우, theta에 대한 likelihood 이며

  로그 함수를 씌우면

  L(theta) = ln P( X | theta )

  이것을 일반적인 log likelihood function 이라고 부른다.

 

  "우리가 원하는 것은  L(theta)를 최대로 하는 모델 theta를 찾자는 것이다."

 

 

  EM 알고리즘의 iterative procedure에서는

 

  L(theta_current_max) > L(theta_n)

 

  theta_current_max가 현재까지 가장 L 값이 큰 모델이라면, 위와 같은 조건이 만족되는 경우, 정지하면 된다.

 

  음... 현재까지는 hidden variable에 대해 고려하지 않고 수식을 전개했는데, 이를 포함시켜보자.

 

 

위 (10) 번 수식은 현재까지 언급되던 theta를 hidden variable Z , known variable theta 로 분리시킨 것이다.

( 이 수식 전개는 partition theory 를 참고하면 된다. )

 

이를 이용하면, 위 (9) 번 수식을 아래와 같이 쓸 수 있다.

 

 

 

  음... 대충 수식 관련 눈팅만 했다.

  이제부터는 E-step, M-step에 대해서 좀더 알아보자.  

 

  [참고] http://en.wikipedia.org/wiki/Expectation-maximization_algorithm

 

 

 

  위에서 기술한 바와 같이,

 

  P( X | theta ) 에서 theta는 hidden variable을 포함하고 있다.  따라서, 처음 시작은

  그냥 아는 변수는 그대로 하고, 모르는 변수에 대해서만 랜덤하게 값을 설정해준다.

  즉, 다시 theta를 hidden variable z 와  known variable theta 로 분리하면

  P( X | Z, theta )

  여기서, Z에 대해서만 랜덤으로 설정해 준다는 것이다.

  그리고 나면,

  current parameter estimate를 알고 있으니, 이를 이용해서 hidden variable에 대한 expectation 값을 계산할 수 있다.

  즉, Z에 대한 expected estimate를 계산할 수 있다.

  이게 먼 말인고?  

  간단히 얘기하면, Z=(z1, z2, z3)  theta=(t1, t2, t3, t4)

  이렇게 element가 있다고 하면

  우리는 theta 값을 알지만, Z 값을 모르니

  t1, t2, t3, t4, 초기에 설정된 z1, z2, z3 에 대해서 평균값으로

  z1, z2, z3를 다시 설정하겠다는 것이다.

   

  그림으로 표현해보면

  

 

 초기에 swallow, bug, insect 는 모르는 변수이고, 그래서 전부 0으로 설정하고

 나머지에 대해서만 관측되었다고 했을 때,

 Expectation 단계에서는 (아는 것 + 모르는 것) 전체에 대한 평균값을 계산해서

 모르는 값에 평균값을 설정하겠다는 의미이다.

 이 부분은 사실 Maximum Entropy Model과 아주 비슷하다.

 오캄의 면도칼(ocam's razer)이라는 건데, 여러가지 가설중에서 가장 간단한 가설이 진리일 확률이 높다는 얘기다.

 즉, 잘 모르면 그냥 평균값을 사용하라는 것.

 

 그렇다면, Maximization 단계는 멀 하는 걸까?

 

 여기서는 앞서 Expectation 단계에서 우리가 모르는 Z값에 대해 추정해 뒀기 때문에, ln P( X | Z, theta )  값을 새롭게

 계산할 수 있을 것이다.

 

 이렇게 계산된 ln P( X | Z, theta ) 값이 기존 likelihood 값보다 더 크다면, 이것이 더 관측데이터에 적합한 모델이므로

 해당 모델을 교체한다.

 아니면 반복 과정을 정지시키던가....

 혹은, likelihood 값의 변화량이 어떤 음수값보다 더 커지면 정지하는 방법도 있다.

 이것은 search space를 탐색하는 이론을 참조하면 될 것이다.

 

 다른 그림을 이용해서 살펴보면 아래와 같다.

 

 [참고] http://www.cs.jhu.edu/~jason/465/PowerPoint/lect26-em.ppt

 

 

 

 

 

 

 동전던지기에 대한 EM 예제는 다음 URL을 참고하면 된다.

 

 [참조] http://www.nature.com/nbt/journal/v26/n8/full/nbt1406.html

 

 

위의 2번 화살표 아래쪽에 있는 값들은 아래와 같이 계산되었다.

 

A를 던졌을 때, 
앞면이 나올 확률 0.6
뒷면이 나올 확률 0.4

P( HTTTHHTHTH )

= 0.6^5 * 0.4^5 = 0.07776 * 0.01024 = 0.0007962624


B를 던졌을 때, 
앞면이 나올 확률 0.5
뒷면이 나올 확률 0.5

P( HTTTHHTHTH )

= 0.5^5 * 0.5^5 = 0.03125 * 0.03125 = 0.0009765625

0.0007962624                             0.0007962624
---------------------------- = ---------------- = 0.4491489261009364207373215482251
0.0007962624 + 0.0009765625        0.0017728249

따라서,

A를 0.449회

B를 0.551회

던졌을 것이다.

 

A를 0.45, B를 0.55 회 던졌다고 계산되었는데, 실제 H는 5회, T는 5회 나왔다.

 

그렇다면, A에 대해서

0.45 * 5 H = 2.2 H

0.45 * 5 T = 2.2 T

 

B에 대해서

0.55 * 5 H = 2.8 H

0.55 * 5 T = 2.8 T

 

위 그림을 프로그램으로 구현한 결과 참조 http://blog.daum.net/comefeel/6935672 

(동료분이 구현해 주셨음 ㅋㅋ)

 

 

 to be continued...


[출처 : http://blog.daum.net/hazzling/17067790]

[출처] KISS ILVB Tutorial(한국정보과학회) 에서 발표( Dr. Sung-Jung Cho)된 내용 중 발췌
 

얼마전에 Markov Model에 대해서 주석을 달아서 올렸는데, 이번에는 HMM에 대해서 알아보자

 

지난번 글에서 언급되었듯이 MM과 HMM의 차이점은 상태(state)를 관측할 수 있는가에 달려있다.

 

날씨나 동전던지기와 같은 사건(event)에서는 쉽게 상태를 알 수 있지만, 그렇지 않는 사건들도 존재한다.

 

예를 들어,  아래와 같은 문제가 있다고 하자.

 

 

이와 같이 3개의 통(상태 1,2,3  -> N = 3)에는 빨간색, 파란색, 녹색 공들이 6개(M = 6)가 들어있는데, 하나의 통에 들어있는 서로다른 색의 공들은 같은 숫자로

들어있지 않다.

이 그림에서 나오는 화살표는 Markov Model에서 처럼 한 상태에서 다른 상태로 전이할 확률(transition probability)이다.

그런데, 각각의 상태에 도착하면 그 상태에서는 빨간, 파란, 녹색 공중 하나를 어떤 확률에 기반해서 뱉어낸다고 한다. 약간 crazy한 콜라 자판기를 생각해도 된다.

3개의 자판기에 빨간, 파란, 녹색 콜라캔이 위의 그림처럼 들어있고, 동전을 넣으면 어떤 확률에 의해서 지맘대로 토해내는 그런 자판기 말이다~

 

이렇게 어떤 확률에 의해서 콜라를 뱉어내는 자판기는 아래와 같이 generation process로 모델링 할 수 있다.

 

 

위 그림에서 ouput process가 바로 어떤 상태 q에서 어떤 ball  x 를 뱉어낼 확률이며 이는 조건부 확률 f(x | q)로 나태낼 수 있다.

그리고  1, 2, 3번 상태에 대한 전이 확률을 알고 있기 때문에 이 부분은 Markov Model을 이용해서 모델링할 수 있을 것이다.

 

자 그럼 이제 우리는 각 상태로의 전이 확률과 각 상태에서의 생성 확률을 모델링했다.  그렇다면 아래와 같은 문제를 생각해 볼 수 있을 것이다.

 

블랙박스에는 관측할 수 없는(unobservable) 3개의 상태들이 있고, Markov Process를 따른다. 각 상태에서는 위의 그림처럼 '빨간->파란->파란' 순서로 공들이

출력된다는 것을 관측했다(observed)

 

이러한 정보로 부터, 우리가 알고 싶은 것은 '과연 감춰진 state sequence는 무엇인가?'일 것이다.

 

음....  약간 거꾸로 생각해서 만약에 '빨간, 파란, 녹색' 공들 그 자체가 state이고 그들간의 전이 확률을 계산한다면 굳이 근본적인 상태들의 sequence를 알 필요가

없을 수도 있다. 하지만, 우리는 다음에 나올 공에 관심이 있는 것이 아니라 과연 어떤 근본적인 상태가 있었는지에 관심이 있기 때문에, 이런 가정은 필요없다.

 

아무튼 이 문제를 풀기 전에 우선, 전체적인 Notation을 정리하고 넘어갈 필요가 있다.

 

여기 기술된 notation은 HMM을 정의이며 문제를 풀기 위한 기본 절차라고 할 수 있다. 앞으로 만약 어떤 문제를 HMM으로 풀고자 한다면, 이와 같이 기본

notation을 정리하고 들어가면 편하겠다.

다음으로 정리할 것은 Markov Model과 generation process를 결합한 그림이다. 이 그림이야 말로, HMM의 핵심이라 할 수 있다.

 

 

 이 그림이 나타내는 것은 MM에서도 배웠지만, 일단 기본적으로 Markov Model을 따른다는 것이고, 추가적으로

 어떤 상태 q에서 x를 출력할 확률은 그 이전 상태와는 완전히 독립적이라는 가설이다.

 (conditional independency assumption)

 근데 왜 'Bayesian network' 이라는 말이 있을까? 이는 HMM도 '베이지안 네트웍' 모델링 기법에 그 기반을 두었기 때문인데, 좀더 상위 모델 개념이라고

 이해하고 넘어가자.

 

 지금까지, 해결하려고 하는 문제를 정의하고 모델링까지 했으니, 이제 진짜 우리가 풀려고 하는 문제를 다시 정의해 보자.

 

 

 

이 그림에서 initial state distribution은 그 첨자가 1 이다. 이는 반드시 HMM의 경우 start state가 존재해야 한다는 의미이다. 그 시작 상태에서 어떤 output을

출력할 확률 분포가 필요한 이유이다.

 

그러면, 위와 같이 좀더 보기 좋게 정리된 문제를 가지고 우리는 실제 어떤 문제를 풀어야 할까? 

아래 그림에 잘 정리가 되어 있다.

 

 

첫째, 관측된 output들이 sequence에 대한 확률이다. 이건 약간 Markov Model을 계산할 때와 비슷해 보인다.

        ==> 다른 점은 모델 파라미터 '람다'가 들어가 있는 것이다.

둘째, 관측된 output sequence를 가지고 가장 그럴 듯한 state sequence를 찾아내는 것이다.

        ==> 사실 진짜로 알고 싶은 것은 바로 이것이다.

셋째, 학습에 관련된 이슈인데, 이는 학습 데이터를 가지고 있는 상태에서 모델 파라미터 '람다'를 적절하게 결정해야 할 필요가 있기 때문에

        학습 알고리즘을 통해서 '람다'를 최적화 해야 한다.

 

지금부터 기술되는 것은 이 세가지 문제를 푸는 방법이다. 관심없는 분은 넘어가시길 바란다. 좀 복잡하니....

 

이제 첫번째 문제를 해결해 보자.

 

음... 먼가  Markov Model을 풀 때와 비슷해 보이지 않는가?  output sequence가 x1 ~ x8 일때 그에 대응하는 state sequence Q는 엄청나게 많다.

하지만, 확률로 표현하면, 전부 OR 이기 때문에 단순히 더하면 된다. summation 안에 들어가 있는 수식은 조건부 확률을 Bayesian Theorem에 의해서

뒤집은 결과이다. 이 수식을 어떻게 계산할까?  머리가 약간 아플 수도 있지만  하나의 Q = q1 ~ qT에 대해서만 생각해 보면,

아주 위쪽에서 그려진 Bayesian network representation을 이용해서 아래와 같이 풀어 쓸 수 있다. 그리고 이 결과를 계산하면 된다.

 

하지만,  이것도 Markov Model처럼 DP 기법(수학적 귀납법을 생각)을 사용하면, 아래와 같이 빠르게 계산할 수 있다.

 

즉, 시간 t 에서의 확률은  시간 t-1 에서의 확률을 알면,  t-1 에서 t 까지의 모든 path만 고려해면 된다는 이 엄청나게 깔끔하고 단순한 수식!!!

멋지다 @.@

아무튼 이 수식에 Forward Algorithm이라는 말이 들어간 것은 계산 순서가 1부터 t까지 순차적으로 계산하기 때문에 그런거고 별 의미는 없다.

다시한번 정리하면

 

 DP 기법에서 자주 나오는 notation인데, 다시 한번 숙지하고 넘어가자. DP는 아래와 같이 구성되어 있다.

1. initialization

    - 고등학교때 수열 문제 풀때, 초기 a1, a2 등등을 지정하고 가는 것 처럼 초기값 설정

2. induction

    -  a(n)과 a(n-1)의 관계를 수식으로 표현

    - 그래서 귀납법(induction)이다.

    - 그래서 deduction이 아니다.

3. termination

    - 계산하려고 하는 대상이 a(100) 인 경우, a(101)은 계산할 필요가 없다. 그래서 종료 조건이 반드시 필요하다.

    - 프로그램으로 작성한다고 생각하면 recursive call이 되겠는데, 여기에 종료 조건이 없으면 무한루프에 걸리니까...

 

 

다시 원래 문제로 돌아가서, '아니 그럼 forward algorithm'이 있으니 'backward'도 있겠네? 하시는 분도 계실 것이다. 그렇다!

backward 방향으로 계산하면 똑같다. 다만, 초기 상태까지 와 봐야, 그 중간에 저장된 확률 값들을 알 수 있다는 단점이 있다.

 

 

 음... 사실 나는 backward는 별로 관심없지만, 뒤에 가면 HMM 학습에 backward가 필요하다  

 

 아무튼, 자 ~ 이제 첫번째 문제는 해결된 셈이다.  이젠 어떤 output sequence가 주어져도 그 확률을 계산할 수 있다.

 

 다음으로, 두번째 문제를 풀어보자. 관측된 output sequence로부터 가장 그럴듯한 state sequence를 구하는 문제인데, most probable sequence decoding

 이라고도 한다. 즉, 아래와 같은 문제이다.

 

 위 수식에서  argmax 는 해당 확률 값을 최대로 하는 state sequence Q를 찾는다는 의미이다.

 이 문제를 해결하는 데, 가장 잘 알려진 알고리즘이 바로 Viterbi Algorithm이다. 이것도 DP 방법을 사용해서 문제를 해결하는데,

 관점은 지금까지와는 조금 다르게, path의 확률을 계산하는 데 초점이 맞춰져 있다.

 

 단순하게 생각하면 시간 t의 여러개의 상태에서 시간 t+1의 어떤 상태 q(j)로 오는 path는 상태의 숫자만큼 존재한다. 그런데, 여러개의

 path에서 어떤 path로 오는 것이 가장 그럴듯 한가? 라는 문제를 생각해 보면, 아래와 같이 두가지를 고려해야 한다.

 1. t의 어떤 상태에서 t+1의 상태 q(j)로의 전이 확률  : a(i,j)

 2. q(j)에서 관측된 output x(t+1)의 확률 : b(j, x(t+1))

 즉, 이 두가지 확률을 곱한 값이 최대가 되는 path를 선택하면 된다.

 이 문제를 DP 문제로 확대시켜서 수학적 귀납법으로 표현하면 아래와 같이 되는 것이다.

  

 위에서 path probability는 잘 알겠는데, 그럼 path node는 먼가?  이는 path probability에서 destination의 output 확률을 제거하고

 , 그 확률이 최대가 되는 이전 상태이다.  현재 어떤 output이 있는 지 고려하지 않고 계산한다.

 

 

음...?  그런데 여기서 path backtracking은 무엇인가?  HMM을 공부할때, 자주 하는 착각중에 하나는 바로

'그럼 처음 상태에서 다음 상태로 가는 path 중에서 path probability가 가장 높은 path를 선택해고, 선택된 그 상태에서 다음으로의 path들을

 계속해서 선택해 나간다면 최종적으로 가장 그럴듯한 path가 나오는 게 아닌가?' 이다.

 

 답은 '아니다' 이다. 이 방법은 그냥 단순히 local search를 하는 것 뿐이고, HMM은 global search를 하는 알고리즘이다. global search는

 모든 가능한 path를 전부 계산하기 때문에 너무 시간이 오래 걸린다. local search는 그냥 단순하게 현재 눈앞에 가장 이득인 놈을 선택해서 탐색하는 방법으로

 greedy search라고도 한다. '탐욕스런 탐색'이라니~ 정말 이름 잘 지었다!!  사설이지만, 우리의 인생의 방향을 선택할때도 가끔 이러지 않을까?

 

 아무튼, viterbi algorithm은 가장 optimal한 path를 선택해야 하기 때문에 끝까지 가본 이후, 마지막 상태부터 거꾸로  back tracking을 하면서

 가장 확률이 높게 나오는 이전 state를 선택해 가면서 처음 상태로 되돌아 가야 최종적인 결과가 나오는 알고리즘이다.

 

 그래서 path backtracking 공식이 있는 것이고...  위 그림에서 화살표가 거꾸로 되어 있는 이유도 이것 때문이다. ^^

 

 자 이제 여기까지 왔으면, HMM으로 모델링 된 어떤 문제에서 output sequence를 알면 대응되는 state sequence를 계산할 수 있겠다.

 대단하지 않은가?

 

 이제 마직막으로 세번째 '모델 학습'에 관련된 문제만 해결하면 되는데, 그러기 전에 우선 NLP에서는 어떤 응용이 있는 지 먼저 살펴보고 넘어 가자.

 

 NLP 분야에서 HMM이 쓰이는 대표적인 모듈은 POS(Part-of-Speech) Tagger이다. 즉, 아래와 같은 문제이다.

 

 [출처] 부산대학교  한국어 정보처리 연구실 - 품사태거 - 정성원 

 

 

 한국어 형태소 분석기는 어떤 어절에 대해 가능한 모든 형태소 분석 결과를 출력해 준다. 하지만, 우리가 필요한 것은 이중에서 진짜로 문맥에 맞는 형태소 분석

 결과이기 때문에, 이 문제도 HMM으로 모델링해서 해결할 수 있다.

 어절을 output , 형태소 조합을 state라고 한다면 HMM에 쉽게 대응해 볼 수 있을 것이다.

 태거를 HMM으로 모델링한다면, output과 state는 얼마나 될까?  일단 state는 그렇게 많지 않다. 형태소 분석기 내부에는 하나의 어절에 대해 구성 가능한

 형태소 결합 규칙을 기술해 두는데, 이 규칙의 수가 state의 수가 될 수 있다. 하지만, output의 숫자는 엄청나게 많다. 따라서, 일반적으로 태거를 구현할때는

 형태소 분석단계에서 최대한 분석의 수를 줄인 이후에 태거에서 HMM 기법을 통해 가장 적합한 품사를 결정한다. 즉, 중의성이 있는 어절들에 대해서만

 가능한 형태소 분석 결과를 출력하고, 그렇지 않은 경우는 고려하지 않는다.

 

 어쨌든, 이러한 품사 태거를 만들기 위해서는 대용량의 품사태깅된 학습 말뭉치가 필요한데, 이 말뭉치는 보통 아래와 같이 생겼다.

 

 나는 학교에 간다   -->   나/대명사+는/조사  학교/보통명사+에/조사  가다/동사+ㄴ다/종결어미

 하늘을 나는 새를 보았다 -->  하늘/보통명사+을/조사  날다/동사+ㄴ/관형형어미  새/보통명사+를/조사 보다/동사+았/선어말어미+다/종결어미

 .....

 

 우리는 이러한 학습 말뭉치를 가지고  상태전이확률(품사간 전이 확률, 편의상), 생성확률(어휘 생성 확률)을 계산할 수 있다.

 

 다시 본론으로 돌아가서, 세번째 문제는 모델의 파라미터를 학습 데이터로부터 최적화하는 것인데, 품사 태거를 학습시키는 문제에 적용해 보면 확률 데이터를 좀더

 정교한 방식으로 계산해서 얻겠다는 의미도 될 수 있다. 어떤 품사에서 출력되는 output의 숫자는 엄청나게 많아서 학습시킬 때 언제나, 데이터 부족 문제

 (data sparseness problem)가 생기는 것은 피할 수 없다. 즉, 관측된 데이터만으로는 관측되지 않은 확률 값을 계산하는 데 심각한 문제가 있다는 의미이다.

 이러한 문제를 해결하기 위해서 보통 우리가 하는 일은 "모델을 잘 만든다" 이다.

 조금 우습지만 이게 정답이다. 잘 만들어진 모델이 있으면, 잘 모르는 확률도 해당 모델을 기반으로 해서 좀 더 그럴듯하게 근사값을 계산할 수 있다.

 아주 대표적인 예가 있는 데, 바로 '정규분포(standard distribution)'이다. '학교 성적, 인구 분포 등등의 현상은 대부분 정규분포에 근사한다' 라는 게 우리의 가설이고

 이를 모델링한 것이 바로 정규분포이다. 따라서, 우리는 모든 사람을 일일히 호구조사하지 않아도 대충 몇살부터 몇살까지 몇명이 있는 지 근사값을 계산할 수

 있는 것이다. 그러니, 잘 모르면 모델을 잘 만들면 된다. 사설이지만, 가장 완벽한 모델은 무엇일까? 음... 바로 수학이다!!  한치의 오차도 없는 완벽한 모델 ^^

 

--------------------------------------------------------------------------------------------------------------------------------------------------

[여기서 잠깐]

Q) 위와 같은 품사 태깅된 말뭉치가 있는 경우에도 파라미터 '람다'를, 좀더 정확히 말하면 state transition 파라미터를, 학습시켜야 하나?

 

A) Linguist's Guide to Statistics [참고 : http://blog.daum.net/hazzling/16884279]

    여기에 보면 아래와 같은 내용이 나온다.

 

 

   즉, "underlying state sequence는 unknown이고 오직 signal sequence 즉, output sequence만 학습데이터로 가지고 있는 경우에만

    state transition probability parameter를 Baum-Welch reestimation 알고리즘 등을 이용해서 추정한다"

   따라서, '그럴 필요 없다'가 정답이다. 다만,  품사조합(state)들 간의 transition 데이터가 좀 부족하면 보정작업등을 해서

   학습 데이터의 부족문제를 약간 해결할 수 있을 것이다.

   아래에서 언급되는 내용은 우리가 오직 output sequence만을 학습 데이터로 가지고 있는 경우에 어떻게 state transition에 관한

   정보를 끄집어 낼 것인가에 대한 것이다.

--------------------------------------------------------------------------------------------------------------------------------------------------

 

 아무튼, 우리는 관측된 데이터만 가지고 모든 상태전이확률, 생성확률을 잘 구할 수 있도록 모델을 구해야 하며, 이것이 지금부터 언급될 내용이다.

 

 보통 HMM의 파라미터 '람다'라 하면 아래 3가지를 의미한다.

 

 1. 상태전이확률  : A

 2. 생성확률 : B

 3. 초기확률 : py

 

 따라서,  람다 = ( A, B, py )  이렇게 표현하고  실제 우리가 관측한 데이터를 X 라고 한다면,

 우리가 만들려고 하는 어떤 모델은 '람다'를 조건으로 할때, 관측 데이터 X를 가장 잘 표현해야 한다. 이런 종류의 문제는 Likelihood 를 최대한 높여야 한다고

 보통 얘기하는데, 잠깐 Likelihood에 대해서 알고 넘어가자.

 

 예를 들어서, 동전던지기 사건에서 앞면이 나올 확률 p를 동전던지기 사건 모델의 파라미터라고 하고, 1000번 던저 나온 결과값 즉, 관측 데이터를 우리가 가지고

 있다고 하자. 1000번의 550번은 앞면, 450번은 뒷면이 나왔다. 자~ 그럼 어떻게 하면 확률 p를 가장 잘 결정할까? 일단 p = 0 으로 해보자.

 엥?  만약 그렇다면 관측한 데이터와 완전히 동떨어진다. 그럼 p = 1 로 하면?  역시 마찬가지다. p = 0.5 로 해도 관측된 데이터와는 조금 미스매치다.

 그래서 관측데이터를 가장 잘 표현할 수 있도록 조금씩 p값을 앞뒤로 조정해 나가면 p = ( 550 ) / ( 1000 ) = 0.55 로 결정된다. 이런 조정 작업이 바로

 모델 파라미터 학습이라고 하고, 여기서 사용된 개념이 바로 'Likelihood를 최대로 해서 모델 파라미터를 결정한다'는 것이다.

 그런데, 관측데이터에 너무 지나치게 모델 파라미터를 학습시키면 어떨까? 사실 동전을 많이 던지면 p 는 0.5에 접근한다. 실험을 적게하면 Likelihood로

 계산한 모델 파라미터 p는 0.1일 수도 있다. 이것이 보통 우리가 얘기하는 overfitting 되었다는 것이고, 거꾸로 생각하면 학습 데이터가 부족하다는 것이다.  

 (data sparseness problem)

 

 기계학습의 아주 기초적인 개념이 사실 고등학교때 배운 동전던지기 확률 모델에 거의 다 들어있다는 것을 요새 새삼 깨닫게 된다 @.@

 

 

 

  

 다시 파라미터 학습 문제로 돌아가면, 결국 Likelihood를 최대화하도록 람다를 학습시켜야 하는데 이를 수식으로 풀어내면 위의 그림과 같다.

 P( X | 람다 ) 는 3가지 문제 중에서 첫번째 문제를 해결할 때, 위의 수식처럼 summation 형태로 바꿀 수 있는데, P( Q | 람다 ) 는 state transition이

 숨겨져 있어서 구하기 어렵다. 따라서, estimation을 해야 한다. 그럼 어떤 방법을 사용해야 할까? 

 일반적으로 이런 문제에서는 EM(Expected Maximization) 알고리즘을 사용한다. EM도 결국 Likelihood를 최대화 하도록 파라미터를 학습시키는

 방법론이라고 할 수 있는 데, 좀더 잘 정리되어 있다는 점이 다를 뿐이다.

 ( 위에서 기술된 Baum-Welch reestimation은 EM 프레임웍을 HMM에 최적화시킨 알고리즘이다. )

 

 EM 알고리즘 참조 ==> http://blog.daum.net/hazzling/17067790

 

 

 

 잘 이해하기 힘든 그림이다.  이것 말고, 다른 문서를 참고하자.

 

 Linguist's Guide to Statistics [참고 : http://blog.daum.net/hazzling/16884279]

 여기에 있는 HMM chapter를 보면

 output sequence  =  o1, o2, .... , oT

 이런 관측 데이터가 있을 때,

 time (t)의 상태가 si 이고

 time(t+1)의 상태가 sj 일 조건부 확률을 아래와 같이 정의할 수 있는데,

 

 이 확률 값은  forward, backward probability의 조합으로도 표현할 수 있다.  

 

 

 그리고,

 가장 아래 부분은 관측렬이 아래와 같고

 output sequence  =  o1, o2, .... , oT

 time ( t ) 일때 상태가 si 일 확률은 

 partition 이론으로 부터, summation으로 풀어쓸 수 있고

 이 풀어쓴 결과가

 결국

 위 그림의 마지막 라인과 같다는 것을 알려준다.

 

 이것으로 부터 아래와 같은 4가지 fact를 추출할 수 있다.

 

  

 0) time 1 에서 상태 si 로 시작할 확률값

 1) si --> sj 로 transition하는 횟수의 평균값

 2) si 로부터 어떤 다른 상태로의 transition 횟수의 평균값

 3) si 에서 어떤 시그널(output)을 뱉을 평균 횟수

 

 그리고, 위에서 나온 fact를 이용해서,

 

 초기 py 값 :  time 1 에서 각각의 상태 si로 시작할 확률값

 pij   :  ( time 1 ~ T-1  사이에서 si -> sj transition 평균값 )  /   (  time 1 ~ T-1 사이에서 si 로 시작하는 transition 평균값 )

          이것이 바로  si --> sj  전이 확률의 평균값(기대값)

 aij   :  ( time 1 ~ T 사이에서 상태 si가  signal(j)를 출력할 평균값 ) / ( time 1 ~ T 사이에서 si 로 시작하는 transition 평균값 )

 

 

  이와 같이 모델 ramda를 계산할 수 있다.

 

  우리가 만약 어떤 m개의 관측 데이터를 가지고 있다고 하자.

 

  o11  o21  o31 ....  oT1

  o12 o22  o32 ....  oT2

  .........

  o1m o2m  o3m ....  oTm

 

  BW 알고리즘은 이것으로 부터 transition proability를 추정해 내는 알고리즘이라 할 수 있는데,

  그럼 우선 어떤 것을 먼저 설정해 줘야 할까?

 

  일단, number of state ( N ) 값과 number of observation ( M ) 값을 설정해야 한다.

  M 값은 관측 데이터로 부터 자연스럽게 설정되고

  N 값은 미리 알고 있다고 가정하자 ( 문제의 종류에 따라 다르다.)

  초기 시작 상태에서의 emission probability py는 그냥 대충 설정해 주면 된다. 합이 1이 되도록....

  마지막으로, transition probability aij, emission proability pij 도 랜덤하게 설정해 둔다.

  이 상태의 모델이 바로 ramda(0)

 

  자 이제 이러한 상태에서, 위의 그림에 나온 공식을 사용해서 계속 parameter를 최적화하는 작업을 진행해야 한다.

 

  위의 공식을 뜯어보면, 결국 최종적으로 forward, backward probability(variable)을 알아야 계산 가능하다는 것을 알 수 있다.

  그렇다면, forward, backward 확률은 어떤 순서로 계산하는가?

 

  forward는 기본적으로 time 1에서 먼저 계산하고 그 다음 time 2, 그리고 그 결과를 이용해서 time3 ...

  이런 식으로 해서 최종적으로 time T 까지 계산한다.

 

  backward는 forward와는 반대로 time T 부터 계산하고 거꾸로 time 1 까지 가면서 계산한다.

  ( 물론, time T에서는 초기값은 확률 합이 1이 되도록 임의로 설정해 둔다)

 

  이러한 과정을 통해서 alpha(t), beta(t) 값을 전부 어떤 테이블에 저장해 두고 사용한다.

  

  아래 그림을 보자.

 

  [reter to] http://nlp.korea.ac.kr/~kshan/resources/AlgorithmsForHMM.ppt

 

 

 

 

 초기에 initial guessing을 통해서 설정된 전이확률 및 생성확률을 가지고

 alpha, beta를 계산하고

 이를 이용해서 다시  아래를 계산한다.

 

 

 

  그러면 전이확률 및 생성확률, 초기확률이 변화할 것이다.

 

   또, 변화된 모델에서 alpha, beta를 구한다.

 

   이 과정을 아래 그림과 같이 반복한다.

 

   [refer to]  http://www.facweb.iitkgp.ernet.in/~sudeshna/courses/ml08/hmmkanungo2.pdf 

 

 

  아... 이제 무한루프에서 탈출한 기분 ^^

 

  그런데 이러한 BW 추정에도 단점이 있다.

  [reter to] http://nlp.korea.ac.kr/~kshan/resources/AlgorithmsForHMM.ppt

 

 

 어쩔수 없는 문제이긴 하지만.... ^^;

 

 HMM을 이해하는 데 있어서 가장 헷갈리는 부분이 바로 이 BW 알고리즘이 아닐까 한다.

 흠... 직접 구현해 보지 않으니, 개념적으로만 맴돌고 약간 모호한 느낌이긴 하다.

 구현이라...

 

 이미 구현된 패키지를 사용하는 방법 관련해서

 --> [참고] http://blog.daum.net/hazzling/16781469



[출처 : http://blog.daum.net/hazzling/15669927]
대학교때 C++을 통해서 객체지향을 배우면서 Class와 Class와의 관계를 어떻게 정의하고 코드로 어떻게 표현하는지 배웠다. Class와 class와의 관계중에 가장 기본적이고 대표적인것이 아마도 Aggregation과 Association일것이다. 학교에서 객체지향에 대한 개념 배울때 그것만 중점적으로 배워서 그런지 (사실 처음 배울때는 그런 관계도 명확히 하는것이 어렵기도 하지) 아직까지도 UML 그릴때 aggregation과 association만 사용한다. 

하지만 요즘에 회사에서 일하면서 간혹 class diagram을 그리기 위해 공짜 UML 툴 중에 Star UML이라는 Tool을 사용하는데, 그 툴을 통해서 평소에 알지 못했던 Composition이라는 관계를 class간에 정의 할 수 있음을 발견하였다. Composition과 Aggregation의 관계를 보여주는 기호도 비슷했다.


그렇다면 이 둘의 차이점은 무엇인가? 보통 Association은 "knows-a" 관계라고 부른다. 반면 Aggregation은 "has-a" 관계라고 배워왔다. 하지만 내가 이해한 composition에 대해서 우선적으로 대략적으로 말하자면 그동안 내가 알아왔던 aggregation이 사실은 composition이고, aggregation은 association의 탈을 쓴 composition이다.

내가 그동안 이해했던 aggregation이라는것은 다음과 같다.
어떤 클래스가 다른 클래스를 member로 가지고 있다면 그것은 "has-a"관계이고 곧 aggregation이다. 하지만 이것이 composition이고, 가끔 "has-a"관계가 더 맞지만 동적으로 그 object을 생성해야 하는 경우가 있었고, 그럴때는 pointer를 사용해야 해서 모양새가 association인것과 같은 경우가 있었는데, 이런 경우에 aggregation을 사용해야 한다. 그래서 Aggregation은 composition과 같이 "has-a"관계이지만 동적으로 할당해야 해서 pointer를 사용해야 하는 경우 정의된다고 할 수 있는것 같다.

그렇다면 두 경우 모두 pointer 형을 사용하는 association과 aggregation의 차이는 다음과 같이 말할 수 있다. Association은 클래스 내에서 포인터를 통해서 다른 object로의 access를 가능하게 해주지만 이 관계를 성립해주는 곳은 두 클래스의 scope 밖이다. 반면 aggregation은 한 object로의 access를 가능하게 해주는 곳이 그 class 내이다. 즉, 동적으로 할당되는 위치도 그 class의 scope 내이다.

class A_composition
{
private:
   B pb;
public:
   A_composition();
   ~A_composition();
};

class A_aggregation
{
private:
    B* pb;
public:
    A_aggregation();
    ~A_aggregation();
};

class A_association
{
private:
    B* pb;
public:
    A_association();
    ~A_association();
    void AknowsB(B* b);
};

class B
{
public:
    B();
    ~B();
};


위와 같이 네 class가 있다면...
Composition은 class A_composition과 같이 그 class 내에서 다른 class object를 멤버를 가지고 있는것 만으로 성립되는 관계이고, 

Aggregation은 class A_aggregation처럼 class 내에 class B의 포인터형을 member로 가지고 있으며 그 pointer를 통해서 class B의 object로의 액세스를 가능하게 해준다. Class B object는 class A_aggregation에서 동적으로 할당되어 사실상 class A_aggregation이 class B object를 가지고 있는 형태가 되어야 하고, 꼭class A_aggregation의 scope내에서 동적으로 할당되어야 한다. 
예를 들면...
A_aggregation::A_aggregation()과 같은 constructor나 A_aggregation의 method 내애서 
pb = new B()

와 같은 구문을 사용해서 동적으로 할당되어야 한다.

반면 Association은 Aggregation처럼 class 내에 다른 class object로의 pointer를 member로 가지고 있지만, 그 object가 생성되는 위치는 Aggregation의 예와는 다르다. Association은 class A_Association처럼 memeber로 class B의 pointer형을 member로 가지고 있지만, Aggregation의 예에서와는 다르게 class B의 object는 A_association의 scope 밖에서 생성되어야지만 class A_association과 class B와의 관계가 association이라고 할 수 있다. 
그래서 aggregation에서의 예와는 다르게

A_association a;
B b;
a.AknowsB(&B);


이런식으로 A_association과 b가 서로의 scope가 아닌 곳에서(위의 예에서 처럼 같은 scope에서 생성될 필요는 없다) 생성되고, A_association의 AknowsB와 같은 method를 통해서 association의 관계를 성립시켜주어야 한다.

이것이 내가 이해한 Composition, Aggregation, 그리고 Association의 차이다...


[출처 : http://dansoonie.tistory.com/entry/SE-How-is-Aggregation-and-Composition-different]
K-평균 알고리즘 (K-Means Algorithm)


Step1. 초기화 : 입력값 및 클래스값

입력값의 개수는 9개 이며, 클래스는 2개로 나눈다.

[입력값]
i1 = (1,3)
i2 = (2,2)
i3 = (2,3)
i4 = (3,4)
i5 = (5,5)
i6 = (5,6)
i7 = (6,4)
i8 = (7,3)
i9 = (10,8)

[클래스값]
c1 = (2,2)
c2 = (5,6)
 
사용자 삽입 이미지

Step2. 입력값과 클래스값 사이의 거리를 구한다.

[거리]
c1 ~ i1 = (1,1) / c2 ~ i1 = (4,3)
c1 ~ i2 = (0,0) / c2 ~ i2 = (3,4)
c1 ~ i3 = (0,1) / c2 ~ i3 = (3,3)
c1 ~ i4 = (1,2) / c2 ~ i4 = (2,2)
c1 ~ i5 = (3,3) / c2 ~ i5 = (0,1)
c1 ~ i6 = (3,4) / c2 ~ i6 = (0,0)
c1 ~ i7 = (4,2) / c2 ~ i7 = (1,2)
c1 ~ i8 = (5,1) / c2 ~ i8 = (2,3)
c1 ~ i9 = (8,6) / c2 ~ i9 = (5,2)


Step3. 클래스 나누기 : 입력값들을 가장 짧은 거리에 속한 클래스로 설정한다

c1 = i1, i2, i3, i4
c2 = i5, i6, i7, i8, i9
사용자 삽입 이미지


Step4. 클래스의 중심좌표를 구한다 : 클래스에 속한 입력값들의 평균

 
중심좌표 구하는 방법 : (x축의 합, y축의 합) / 각 클래스에 속한 입력값의 수

c1 : (1+2+2+3,3+2+3+4)/4 = (8/4,12/4) = (2,3)
c2 : (5+5+6+7+10,5+6+4+3+8)/5 = (33/5,26/5) = (6.6,5.2)


Step5. 중심좌표와 클래스값 비교
 
중심좌표와 클래스값이 같지 않다면 중심좌표를 클래스값으로 설정하고 Step6
중심좌표와 클래스값이 같다면 Step7
 
중심좌표의 값이 클래스값과 다르므로 중심좌표를 클래스값으로 설정

c1 = (2,3)
c2 = (6.6,5.2)


Step6. 더 이상 중심좌표의 변화가 없을 때까지 Step2-Step5 를 반복한다.
 
(Step1. 입력값 및 클래스값)
[입력값]
i1 = (1,3)
i2 = (2,2)
i3 = (2,3)
i4 = (3,4)
i5 = (5,5)
i6 = (5,6)
i7 = (6,4)
i8 = (7,3)
i9 = (10,8)
 
[클래스값]
c1 = (2,3)
c2 = (6.6,5.2)
사용자 삽입 이미지

(Step2. 입력값과 클래스값 사이의 거리를 구한다)
[거리]
c1 ~ i1 = (1,0) / c2 ~ i1 = (5.6,2.2)
c1 ~ i2 = (0,1) / c2 ~ i2 = (4.6,3.2)
c1 ~ i3 = (0,0) / c2 ~ i3 = (4.6,2.2)
c1 ~ i4 = (1,1) / c2 ~ i4 = (3.6,1.2)
c1 ~ i5 = (3,2) / c2 ~ i5 = (1.6,0.2)
c1 ~ i6 = (3,3) / c2 ~ i6 = (1.6,0.8)
c1 ~ i7 = (4,1) / c2 ~ i7 = (0.6,1.2)
c1 ~ i8 = (5,0) / c2 ~ i8 = (0.4,2.2)
c1 ~ i9 = (8,5) / c2 ~ i9 = (3.4,2.8)

(Step3. 클래스 나누기 : 입력값들을 가장 짧은 거리에 속한 클래스로 설정한다)
c1 = i1, i2, i3, i4
c2 = i5, i6, i7, i8, i9
사용자 삽입 이미지

(Step4. 클래스의 중심좌표를 구한다 : 클래스에 속한 입력값들의 평균) 
c1 : (1+2+2+3,3+2+3+4)/4 = (8/4,12/4) = (2,3)
c2 : (5+5+6+7+10,5+6+4+3+8)/5 = (33/5,26/5) = (6.6,5.2)
 
(Step5. 중심좌표와 클래스값 비교)
중심좌표와 클래스값이 같으므로 Step7 로 이동 (중심좌표가 더 이상 변하지 않는다)
 

Step7. 완료
 
결과값
c1 = i1, i2, i3, i4
c2 = i5, i6, i7, i8, i9
 
입력값 1,2,3,4 는 클래스1 에 속하게 되며, 입력값 5,6,7,8,9 는 클래스2 에 속하게 된다
사용자 삽입 이미지


[출처 : http://rainless.com/blog/25]
출처:http://synap.tistory.com/?page=3

PageRank 와 구글 검색엔진

v1.0 2007/11/02 Copyleft by 전경헌@사이냅소프트


페이지랭크(PageRank)

비싼거라는데... 구구절절한 음식 설명보다는 먼저 먹어보는게 좋지 않겠나?
구글창업자중 한명인 래리페이지가 만든 페이지랭크의 정의는 아래와 같다.

PR(A) = (1-d) + d(PR(T1)/C(T1) + PR(T2)/C(T2) + ... +  PR(Tn)/C(Tn))

A : 페이지랭크를 계산하고자하는 노드
T1...Tn : A로의 링크를 가지고 있는 노드
C(Tn) : 노드 Tn에서 외부로 나가는 노드 수
d : 제동(damping) 계수 보통 0.85

사용자 삽입 이미지

그림1) 노드 A와 T1 ... Tn 사이의 관계

페이지랭크는 모든 노드에 대한 확률분포를 나타낸다.
따라서 모든 노드의 페이지랭크의 합은 1이다.


예제1) 링크가 하나뿐인 구조에서의 페이지랭크

사용자 삽입 이미지
그림2) 두개의 노드 하나의 링크

계산을 위한 초기값으로 d=0.85, PR0(A)=1, PR0(B)=1를 주고 계산한다.
(왜? d값은 구글이 쓰는 값을 사용하였고, PR(A), PR(B)의 초기값은 어떤 값이어도
 같은 값으로 수렴한다. 의심나면 직접 바꿔서 해보길 바란다.)
 
 PR1(A) = 0.15 + 0.85 * 0 = 0.15
 PR1(B) = 0.15 + 0.85 * 1 = 1.00
 
 PR2(A) = 0.15 + 0.85 * 0 = 0.15
 PR2(B) = 0.15 + 0.85 * 0.15 = 0.28

 PR3(A) = 0.15 + 0.85 * 0 = 0.15
 PR3(B) = 0.15 + 0.85 * 0.15 = 0.28

더이상 값의 변화가 없이 수렴하므로,

PR(A) = 0.15 / 0.43 = 0.35
PR(B) = 0.28 / 0.43 = 0.65


예제2) 링크가 두개이며 서로 링크하는 구조에서의 페이지랭크

사용자 삽입 이미지
그림3) 두개의 노드, 두개의 링크
 
앞서와 같이 초기값으로 d=0.85, PR0(A)=1, PR0(B)=1를 주고 계산한다.

 PR1(A) = 0.15 + 0.85 * 1 = 1.00
 PR1(B) = 0.15 + 0.85 * 1 = 1.00

계산값이 수렴하므로,

PR(A) = 1.00/2.00 = 0.5
PR(A) = 1.00/2.00 = 0.5


검색엔진 개요

PageRank는 검색과 관련이 없는 개념이라 볼 수 있지만,
관련 될 수 도 있다. 그래서 검색부터 설명하도록 한다.

(별로 중요한 것은 아니지만...)
많은 사람들이 네이버나 다음, 엠파스를 검색엔진이라 부른다.
이를 조금 덜 모호하게 말하자면 검색엔진을 장착한 포탈이다.
이 문서에서는 좁은 의미의 검색엔진에 대해서만 설명한다.

사용자 삽입 이미지
그림4) 일반적인 검색엔진의 구조

검색엔진은 크게 수집, 색인과 검색의 세부분으로 나뉜다.
  • 수집(Crawl) - 에이전트나 게이트웨이를 통하여 검색대상이 되는 문서를 모으는 작업
    (중복제거, 링크추출, 병렬처리, 암호화, 압축, DB연계 등이 문제가 됨)
  • 색인(Index) - 수집된 문서를 분석하여, 검색하기 좋은 형태로 저장하는 작업
    (하부저장구조, 형태소분석, 사전관리, 색인 속도, 색인 용량, 온라인색인 등)
  • 검색(Search) - 질의어에 부합하는 문서를 색인에서 찾아 유저에게 보여주는 작업
    (질의어분석/확장, 속도, 병렬처리, 재현률과 정확률, 랭킹, 소팅, 머징, 그룹핑 등)

각각에 대해서는 수도없이 많은 논문들이 있으므로, 깊이 알고자하면 쉽게 배울 수 있음.
특히, 요즘엔 검색엔진과 관련된 다양한 오픈소스가 존재하므로
좋은 오픈소스를 참조하여 공부하는 것을 권장함.

DBMS와 비교하여 검색엔진의 가장 큰 차이점을 말한다면,
정확히 일치하는 것이 아니라 유사한 정도를 가지고 문서를 찾아야 한다는 것과
DBMS가 백업/복구 등 다양한 관리기능을 중시하는 것과는 달리
검색엔진은 검색결과의 만족도와 검색속도를 중요시 한다.


3노드 페이지랭크

이제 노드가 3개있는 구조의 페이지랭크를 구해보자.

사용자 삽입 이미지

그림5) 3노드 직렬 링크 구조

먼저, PageRank 공식에 대입하여 써보자.

PRi(A) = 0.15
PRi(B) = 0.15 + 0.85(PRi-1(A)/1)
PRi(C) = 0.15 + 0.85(PRi-1(B)/1)

연습장에 손으로 풀어도 되겠고, 간단한 프로그램을 짜도 되겠으나,
엑셀이 가장 좋다.(초기값을 바꿔서 실행하기 최고 아닌가?)

사용자 삽입 이미지

따라서,
PR(A) = 0.18, PR(B) = 0.34, PR(C) = 0.47


사용자 삽입 이미지
그림6) 3노드, 3링크 구조

PRi(A) = 0.15
PRi(B) = 0.15 + 0.85(PRi-1(A)/2)
PRi(C) = 0.15 + 0.85(PRi-1(A)/2 + PRi-1(B)/1)

사용자 삽입 이미지

따라서,
PR(A) = 0.20, PR(B) = 0.28, PR(C) = 0.52

페이지랭크는 다른 노드가 얼마나 괜찮은 지에 대한 투표를 하는 것이다.
참고로 이 투표는 누구나 여러표를 행사할 수 있고, 투표하지 않아도 된다.
예를 들어 A가 두표를 행사해서 B와 C에 표를 주었다면,
A의 지지도는 분산되며 B와 C는 A로부터 동일한 정도의 지지를 받은 것으로 판단한다.

그림에서 어느 노드가 가장 많은 지지를 받고 있는 지 생각해보고,
이를 수치화한 PageRank값과 비교해 보기 바란다.


구글 검색엔진의 구조

상대적으로 자료규모가 작은 기업용 검색엔진을 만들고자 하는 경우에는 DBMS와 유사한 정도의 많은 기능이 필요하다. 그러나 인터넷상의 많은, 정말로 많은 자료를 검색하고자 하는 대용량 검색엔진을 만들고자 하는 경우는 다양한 기능보다 검색이라는 하나의 기능이 할 수 있는한 최대한 빠르게, 정확하게 되는 것이 필요하다.

사용자 삽입 이미지
그림7) 구글 검색엔진의 구조


URL Server - 웹페이지를 가져올 URL을 Crawler에 분배한다.

Crawler - 웹페이지를 가져와서 Store Server에 전달한다.

Store Server - 웹페이지를 압축하여 Repository에 저장한다.

Repository - 모든 웹페이지의 길이와 압축된 내용을 저장하고 있다.

Indexer - 리파지토리의 웹페이지의 압축을 풀고, 파싱하여, hits형태로 저장한다. hits는 단어, 단어의 문서내위치, 폰트크기, 대소문자구분 등을 담고 있다. 색인기는 hits를 소팅하여 Barrels에 나누어 담는다. 또한 웹페이지안의 앵커들을 Anchors에 담아놓는다.

Doc Index - DocID와 리파지토리내의 위치를 담고있다.

Barrels - forward/backward index의 집합

Lexicon - wordID와 word 목록

Anchors - 앵커텍스트와 타겟URL로 구성된다.

Sorter - Barrels에 저장된 Forward Index를 읽어서 wordID로 정렬하고 Inverted Index를 생성(In Place)한다.  또한 Sorter는 wordID의 리스트와 각 wordID별로 Inverted Index에서의 위치를 기록한 리스트를 생성한다. DumpLexicon이라는 프로그램이 이 목록과 Lexicon을 읽어서 새로운 Lexicon을 만들어낸다.

URL Resolver - Anchors를 읽어서 상대URL을 절대URL로 변경하고 docID를 배정하며, 앵커텍스트를 docID의 색인에 저장한다. 또한 docID의 쌍으로 이루어진 링크구조를 Links에 저장한다.

Links - 링크를 저장하는 docID의 쌍 목록

PageRank - Links구조를 읽어서 모든 페이지의 PageRank를 계산한다.

Searcher - 웹서버와 연동되며, 사용자 질의를 Lexicon을 참조하여 wordID로 변환한다. wordID, inverted Index, pagerank를 이용하여 결과를 생성하여 답한다.


검색엔진은 일종의 유틸리티 집합처럼 작동한다. 사실 검색엔진 뿐만 아니라 대부분의 서비스들이 이처럼 작은 프로그램들로 구분되서 개발되어진다.

사용자 삽입 이미지
그림8) 1998년 당시의 구글 통계 

페이지랭크의 왜곡

구글이 제공하는 구글툴바를 사용하면 웹브라우저에서 현재 보고있는 페이지의 페이지랭크를 알 수 있다. 아래 그림에서 사이냅소프트 홈페이지의 PageRank는 5이다.

사용자 삽입 이미지

그림9) 구글툴바를 설치하면 페이지랭크를 볼 수 있다.

툴바가 제시하는 페이지랭크는 실제적인 값이 아니라, 실제값을 10개의 범위로 나뉘어 보여준 것이라서 정확한 값을 알기는 어렵다. 능력되시는 분은 구글 툴바를 해킹해서 실제 페이지랭크값을 볼 수도 있겠다.(구글툴바 라이센스 위반이라고 하네요, 설치시에 누가 라이센스 읽어보나...ㅡㅡ;)

사용자 삽입 이미지
그림10) 구글 툴바가 제시하는 값과 실제 페이지랭크 값(추정치, 구글이 발표한 자료가 아님)

툴바는 그다지 정확하지 않다고한다.
툴바는 종종 페이지랭크를 추정한 값을 보여준다 또한 정확한 값을 알 수는 없다.

페이지랭크는 웹페이지의 중요도 또는 괜찮은 정도를 나타낸다고 보면 되나,
아래와 같은 몇가지 경우에 왜곡 된다.

1) Reciprocal links - "내꺼 링크해줘, 나도 링크해줄께"
2) Link Requirements - "우리 스크립트를 사용하시려면 우리 홈페이지를 링크해주세요"
3) Friends and Family - "내 친구 성연이의 사이트입니다", "우리 개 사이트가 여기 있어요"
4) Free Page Add-ons - "이 카운터는 
www.linktocounter.com 에서 제공된 것입니다"


검색엔진최적화 또는 마케팅 업체

페이지랭크 등 검색엔진에서 사용되는 랭킹알고리즘을 연구하여 특정페이지가 Top 10 페이지 안에 들어올 수 있도록 해주고 돈을 받는다.


대용량 검색결과

우리 모두가 정답을 알고 있다. 그렇다 잘 보여줘야 한다. 그러나 재현률과 정확률이 100%인 경우라도 "노무현"을 검색한 결과가 칠백삼십육만오천육백이십이개가 나왔다면 도대체 사용자에게 어떤 10개를 화면에 보여주는 게 좋을까? 여기에서 랭킹이 등장한다. 사용자가 더 원할 만한 것의 랭킹을 높여서 가장 랭킹이 높은 10개를 보여줄 방법이 필요하다.

구글이 검색결과에 사용하는 기본적인 랭킹은 다음과 같다.
1) 검색에 사용된 키워드와 매치되는 모든 페이지를 찾는다
2) 각 페이지에 있는 키워드와 같은 요소를 중심으로 랭킹을 한다.
3) 인바운드(페이지로 들어오는) 앵커텍스트를 계산한다.
4) 페이지랭크 점수를 랭킹점수에 곱해서 높은 순서대로 10개를 보여준다.

Final Rank Score = (score for all Non-PageRank factors) * (actual PageRank score)

보다시피 페이지랭크는 랭킹에 반영되는 하나의 요소일 뿐이지 절대적인 것은 아니다.
또한, 예전에 비해서 페이지랭크가 반영되는 정도가 계속 감소하고 있는 추세이다.

PageRank는 구글이 검색결과의 정렬에 사용하는 100여개의 알고리즘 중의 하나이다.

우리가 만든 페이지가 특정키워드의 검색결과페이지(SERPs)에 나타나기를 바란다면,

먼저 해당 키워드가 우리 페이지에 있어야 한다. 이 키워드가 Title Tag에 등장하거나
Body Text에 강조된 형태로 나오면 더 좋다.

그리고 우리 페이지를 링크하는 외부 페이지들에서 해당 키워드가 앵커텍스트로 등장해주면
더 좋다.(앵커텍스트의 가중치가 더 높다)

끝으로 페이지 랭크가 높아야 한다.


4노드 페이지랭크

사용자 삽입 이미지
그림11) 4노드 구조

PRi(A) = 0.15 + 0.85(PRi-1(C)/1)
PRi(B) = 0.15 + 0.85(PRi-1(A)/3)
PRi(C) = 0.15 + 0.85(PRi-1(A)/3 + PRi-1(B)/1 + PRi-1(D)/1)
PRi(D) = 0.15 + 0.85(PRi-1(A)/3)


사용자 삽입 이미지

따라서, 
PR(A) = 0.35, PR(B) = 0.14, PR(C) = 0.37, PR(4) = 0.14

초기값이 얼마여도 최종 결과에는 관계가 없다.
노드 수가 많아질수록 반복계산량이 많아진다.

2600만개 노드에 대한 PR계산에 워크스테이션 한대에서 몇시간정도 소요(1998, 구글)

반복횟수를 줄이기 위하여 수렴오차범위를 넓게 잡거나,
매번 새로운 PageRank를 계산하는 게 아니라 이전에 계산했던 값을 유지할 수도 있고,
여러개의 컴퓨터를 이용하여 병렬로 계산할 수도 있다(컴퓨터간에 서로 Sync를 맞춰야 한다)


비 PageRank Factor Threshold

검색결과의 랭크에 있어서 페이지랭크를 제외한 다른 요소들은 특정한 기준 점수(Non-PageRank Factor Threshold) 이상만 되면 크게 차이가 나지 않는다. 다시말하면 앵커텍스트가 두배가 된다고 해도 그다지 점수가 높아지지 않는다. 마치 로그함수의 그래프를 생각하면 되겠다.

아래 수식의 Non-PageRank는 로그적으로 증가한다고 보면 맞다.
Final Rank Score = (score for all Non-PageRank factors) * (actual PageRank score)

그래서 이때부터는 PageRank가 매우 중요해진다. PageRank를 모아서 값이 높아져야만 SERPs에 등장할 수 있다.

정말로 중요할 것 같지 않은가? 어느정도 수준이 되는 페이지들이라면 이때부터는 페이지랭크 값이 결정적이다.


SERPs에서 그릇된 페이지를 발견했을 경우

검색결과페이지내에 각 항목별로 버튼을 두어서
버튼이 눌리면...

해당페이지의 PageRank를 0으로 하여 검색결과의 제일 뒤로 보낸다.
해당페이지와 관계된 링크들을 Links에서 제거해버린다.


참고문헌

1) Sergey Brin and Lawrence Page, "The Anatomy of a Large-Scale Hypertextual Web Search Engine"
2) Chris Ridings and Mike Shishigin, "PageRank Uncovered"


부록. 직접 검색엔진을 만들고자 하는 분들에게...

아주 간단한 검색엔진을 직접 만들어 보고 싶다면, 수집 프로그램은 직접 만들기보다는(검색엔진에서 수집기가 가장 어려운 부분일지 모른다, 잘하려면 거의 인터넷 익스플로러를 다 만들어야 할 수도 있다) 인터넷 검색에서 "open source crawler"를 검색해서 나오는 것 중에 오래되고 자주 업데이트되는 것을 하나 골라서 쓰도록 하고, 특별히 나는 남의 코드 보는 능력이 뛰어나다고 자신한다면, Mozilla 소스를 커스터마이징해서 쓰기를 권장한다. 남의 것은 죽어도 싫다면... 구글처럼 파이썬으로 만들어라.

색인 프로그램을 만들기 위해서는 어떤 검색기능이 필요한 지를 알아야 한다. 단순 키워드 검색이면 되는 것인지, 멀티 키워드 검색이 가능해야 하는지, 키워드간의 근접성(proximity)이 필요한 것인지 등 색인과 검색은 사실 하나의 틀에서 개발이 이루어져야 한다. 가장 단순한 검색을 한다고 하고, 하부저장구조로는 버클리DB나(포탈에서 좋아한다) 위스콘신대학의 Shore를(연구소에서 좋아한다) 사용하도록 하고, 국문법에 대한 상당한 이해가 필요한 형태소분석보다는 N-Gram방식을 이용하라. 인터넷을 찾아보면 N-Gram 소스코드도 찾을 수 있다.

대용량 처리가 필요하다면, 모든 자료구조를 숫자화하도록하고, 다양한 저장구조가 있겠지만 그 중에서도 해쉬를 사용하기 바란다. 음... 좀 더 직접적으로 설명하자면, 키워드를 모두 숫자형태의 id로 맵핑해서 사용하고, 문서도 마찬가지로 모두 숫자형태의 id로 맵핑해서 사용한다, 그리고 미리 소팅해놓을 필요가 있는 것들은 b-tree같은 구조를 사용하는 것도 좋고, 할 수만 있다면 hash를 쓰도록한다. 어떠한 자료구조라도 이론적으로는 hash보다 빠를 수는 없다.

수집할 데이타가 얼마 안된다면(일반적으로 기업체 내부에서 쓰이는 자료) DBMS와 유사한 아주 다양한 검색기능을 만들어야 유저가 행복해 할 것이다. 그러나 수집할 데이타가 아주 방대하다면(대형 포탈에서 대상으로 하는 자료) 유저는 단순한 검색기능이라도 결과가 잘 나오면 기뻐할 것이다.

어떤 경우에도 검색 결과의 만족도와 검색 속도는 절대로 훼손하면 안되는 것이다. 검색결과의 만족도가 낮으면 누구도 쓰려고 하지 않을 것이고, 검색 속도가 느리면 많은 사용자들이 동시에 검색할 경우 거의 사용불가능 상태가 될 수가 있다.

검색결과의 만족도라는 것은 사람마다 다르고 정성적인 것이라 일종의 인공지능에 관련된 문제다. 처음 검색엔진을 만들 때는 인공지능성격의 문제는 아예 접근하지 않는 게 좋다. 상업용이 아니라 연습용이라면 처음에는 검색결과의 만족도를 무시하라.

검색 프로그램은 사용자의 질의를 색인시에 사용했던 N-Gram을 써서 분리하고, 해당 키워드의 id로 맵핑해서 질의어의 키워드 id 목록을 만든다. 이제 이 키워드 id목록을 가지고 각각의 키워드 id에 해당하는 문서 id들을 모아와서 하나로 결과를 하나로 합쳐서 사용자의 화면에 보여준다.

검색/색인 개발과정은 클라이언트/서버 개발과정과 비슷하다. 검색이 좀 편하려면, 색인을 잘해줘야 하고, 색인을 부실하게 하면 검색이 힘들다. 색인과 검색을 나눠서 개발하고 서로 일을 미루면 잘 개발 안된다.

-끝-


[출처 : http://blog.bagesoft.com/661]

+ Recent posts