들어가며
2002년 2월 어느 오후, w씨는 학교에서 프로그래밍 언어 수업을 듣고 있었다. 이상하게도 대충 골라 신청했던 수업이 전부 AI와 관련된 듯한 수업이라서 (프로그래밍 언어, 확률 기초, 심리학 개론) 무심코 무언가를 생각하며 수업을 듣던 중, 갑자기 머리속에 불빛이 번쩍인 느낌이 들면서 나온 혼잣말, "크~읏, 시테오쿠! 나는 지금껏 카와리를 만지면서 무엇을 봤단 말인가!"
BNF (Backus-Naur Form) 와 카와리 스크립트
BNF에 대해
컴퓨터 관련 학과생이나 컴파일러 쪽에 대해 지식이 있는 분이라면 BNF (Backus-Naur Form) 라는 것을 들어 보셨을 것이다. (어쩌면 언어학쪽에서도 다룰지도..) 이것은 John Backus 라는 사람이 Peter Naur 라는 사람과 함께 ALGOL이라는 컴퓨터 언어를 만들 때 그 형식을 설명하기 위해 만든 것이다. 또한 Noam Chomsky 라는 언어학자가 일반 언어의 구문을 표시하기 위해 만든 "컨텍스트-프리" 문법이라는 것을 만들었는데 이 두 문법은 매우 닮아 있어 일반적으로 혼용해 쓴다고 한다. BNF의 장점은 복잡한 언어구조를 간단한 수식 몇 개로 표시할 수 있다는 것이다. (P&Z, 2000)
일반 문장을 예로 들어 BNF에 대해 설명해 보기로 한다. "나는 밥을 먹는다" 라는 문장이 있다고 하자. 이 문장은 "나는" 이라는 주어, "밥을" 이라는 목적어, "먹는다" 라는 서술어로 나누어진다. 이것을 BNF로 표시하면 다음과 같다.
{{{ (BNF 1) 문장 = <주어> <목적어> <서술어> }}}
그런데 모든 문장이 이렇게 되지는 않지 않은가? "나는 잔다" 라는 문장은 어떻게 하는가? 그건 이런 식으로 적는다. 여기에서 세로줄은 "또는"(or) 의 의미를 갖는다.
{{{ (BNF 2) 문장 = <주어> <목적어> <서술어>
| <주어> <서술어>
}}}
여기서 "주어" "목적어" "서술어" 라는 것은 너무 포괄적이다. 좀 더 자세히 적을 수 없을까? 더 자세히 적어보자. {{{ (BNF 3) 문장 = <주어> <목적어> <서술어>
| <주어> <서술어>
주어 = <명사> <명사격 조사>
| <대명사> <명사격 조사>
목적어 = <명사> <목적격 조사>
서술어 = <어간> <어미> }}}
이런 식으로 하면 각각의 위치에 아래 서술한 내용을 집어넣어서 펼침으로 내용을 확장시킬 수 있다. 그럼 "글" 이라는 것은 어떨까? 여기서 "글"이란 문장이 하나 이상 연결된 것이라 생각하자.
{{{ (BNF 4) 글 = <문장>
| <문장> <글>
}}}
이런 식으로 적으면 글이 하나 이상 연결된 것이라는 표시가 된다.
카와리 스크립트는 BNF?
얼마전에 "보완 사쿠라" 라는 일본어 고스트를 본 적이 있다. 제 버릇 남 못 준다고, 스크립트가 암호화되어 있지 않은데다 카와리를 사용한 고스트라 속을 한번 뜯어보았는데 한가지 이상한 점이 있었다. 분명히 AI대화에서는 sentence 를 사용하게 되어 있는데, 이 sentence가 보이질 않는 것이었다. 결국에는 grep이라는 문자열 찾기 전용 유틸리티로 뒤져보니, 그 많은 스크립트 파일 중 단 하나의 파일, 그것도 단 한 곳에만 있었던 것이다. 다음은 그 부분을 약간 간략화한 것이다.
{{{ (스크립트 1) sentence : ${네타종별}
네타종별 : ${우선네타},${우선네타},${우선네타},${네타},${네타} 네타종별 : ${네타},${네타},${네타},${네타},${네타10%} 네타10% : ${계절네타},${와판발행},${네타},${네타},${네타},${네타},${네타},${네타},${네타},${네타}
[다른 파일 내] 네타 : (실제 대화)
계절네타 : (계절에 대한 대화) }}}
무언가 느껴지는 것이 없는가? 적는 김에 예제를 하나 더 들어보자.
{{{ (스크립트 2) sentence : \0${주어} ${목적어} ${서술어}\e, \0${주어} {서술어}\e
주어 : ${명사}${명사격조사}, ${대명사}${명사격조사}
목적어 : ${명사}${목적격조사}
서술어 : ${어간}${어미}
# 사전 부분 명사 : 산오리, 박사님, w모씨 대명사 : 나, 너, 우리, 그, 그녀 명사격조사 : [은;는], [이;가] 목적격조사 : [을;를] 어간: 사랑, 좋아, 모에모에, 싫어 어미: 해, 하던가?, 할걸, 하니까.. }}}
어서 많이 보던 것 같지 않은가? 이건 위의 (BNF 3) 예제를 그대로 카와리 스크립트로 옮긴 것이다. 쓰는 방식이 너무나도 비슷하지 않은가? 그렇다. 카와리 스크립트는 BNF였던 것이다.
BNF식으로 스크립트를 제작할 때의 잇점
"오, 카와리 스크립트는 BNF인가 보군. 그런데 그게 어쨌다구?" 라고 질문하실 분들이 많으실 줄 안다. 그럼 여기서 BNF식으로 스크립트를 제작할 때 생기는 잇점을 알아보자.
간결한 스크립트, 다양한 표현
가장 먼저 생각할 수 있는 것은 간결한 스크립트 (곧, 최소한의 노력)로 다양한 표현을 할 수 있다는 것이다. 앞서 예를 든 (스크립트 2) 예제를 잠시 보자. 저 예제는 공백줄을 제외하고 총 10줄로 표시되었다. 저것으로 총 몇가지 표현이 나올 것이라 생각되는가? 좀 복잡하지만, (스크립트 2) 예제를 한번 펼쳐보자. 아마 다음과 같이 될 것이다.
{{{ (스크립트 3) # 타입 1 - 명사 주어, 목적어, 서술어 sentence : ${명사}${명사격조사} ${명사}${목적격조사} ${어간}${어미} # 타입 2 - 대명사 주어, 목적어, 서술어 sentence : ${대명사}${명사격조사} ${명사}${목적격조사} ${어간}${어미} # 타입 3 - 명사 주어, 서술어 sentence : ${명사}${명사격조사} ${어간}${어미} # 타입 4 - 대명사 주어, 서술어 sentence : ${대명사}${명사격조사} ${어간}${어미}
# 기타 생략 }}}
이 4가지 조합이 될 것이다. 표현의 최대 갯수를 알기 위해 모든 가능한 수를 생각해 보자. 단, 명사격조사 "은/는" 과 "이/가"는 따로 계산하지 않도록 하자.
타입 1 = 3x1(주어) x 3x1(목적어) x 4x4(서술어) = 144 타입 2 = 5x1(주어) x 3x1(목적어) x 4x4(서술어) = 240 타입 3 = 3x1(주어) x 4x4(서술어) = 48 타입 4 = 5x1(주어) x 4x4(서술어) = 80 총 갯수 = 144 + 240 + 48 + 80 = 512
어떤가? 단 10줄의 스크립트만 가지고도 이론상 (이론상이라 했다. 이에 대한 더 자세한 고찰은 뒤에 나온다) 512 가지의 서로 다른 표현이 나오는 것이다.
대화 체인
또 다른 장점은 대화 체인을 작성하기가 쉽다는 데 있다. 대화 체인이란, 일반 사람의 대화처럼 하나의 대화에 이어 다음 대화가 자연스럽게 나오는 대화의 연결 "체인(chain)"을 말한다. 다음 예를 보자.
{{{ (스크립트 4) # 1) 대화가 한번으로 끝나는 경우 sentence : ${대화} # 2) 대화가 두번 반복되는 경우 sentence: ${대화} ${화제바꾸는말} ${대화} # 3) 특정 주제로 반복되는 경우 sentence: ${날씨대화} ${날씨에서기후로} ${기후대화}
# 대화는 (스크립트 2)같은 것을 "문장" 이라는 것으로 적당히 만들어 놨다고 가정하고 그걸 쓰기로 하자. 대화 : ${문장} 화제바꾸는말 : 그건 그렇고, 그런데 있잖아..
날씨대화: \0오늘도 꽤 덥네?, \0오늘 너무 춥지 않아? 날씨에서기후로: \1요즘 계속 그러지 않아? 기후대화: \0이상기온인가.. , \0도대체 지금이 겨울이야 여름이야? }}}
1)과 2)를 보고 머리속으로 결과를 대충 상상해 보자. 그리 부드럽진 않더라도 대화가 이어지는 것 같지 않은가? 3)과 같이 좀 더 구체적으로 사전을 제작해 놓는다면 훨씬 "인간 같은" 대화가 이루어질 것이라 생각된다.
BNF식으로 스크립트를 제작할 때의 단점
"멋진 걸? 지금부터라도 카와리로 바꿔야지" 라고 하기 전에, 단점도 알고 가야 하지 않겠는가? 그럼 BNF의 가장 큰 단점은 무엇일까. 그것은 바로 BNF가 '"컨텍스트-프리"', 곧 문맥에서 자유로운 표현방식이므로 대화의 조합 시 문맥을 생각지 않는다는 것이다.
(주: Noam Chomsky씨 (박사?)가 "컨텍스트-프리" 문법이라고 이름을 정할 때 이것이 현재 제가 말하는 뜻과 동일한 의미에서 정한 것인지는 확실치 않습니다. 혹시 아시는 분은 이 글에 첨가해 주시길)
너무 늦지 않았나 싶네요. 거의 같은 의미라고 볼 수 있습니다.
CFG 에서는 V -> w (V in nonterminal, w in general string) 이고,
CSG 에서는 xVy -> xwy (V in nonterminal, w,x,y in general string) 입니다.
다시 말해 CFG에서와는 달리 CSG에서는 임의의 nonterminal V를 general string w로 변환할 때 앞뒤 문맥인 x, y를 고려합니다.
즉, 프로그래밍 언어 수업을 듣고 계시던 w님의 예상이 맞습니다. :)
마침 오토마타 과목을 듣고 있어서 간략히 써보았습니다. 틀린 점 지적 바랍니다.
(CSG는 context sensitive grammar 이고 general string 은 (nonterminal U terminal)^*을 의미합니다.)
말이 안 되는 문장의 생성
잠시 위의 (스크립트 2) 를 되짚어 보자. 분명 이론상으로는 512가지의 대화가 나오긴 한다(잘못 계산하지 않은 한..). 그런데 그게 과연 전부 "어법에 맞는 표현" 인가? (스크립트 2) 의 개연성있는 출력물 중 몇몇은 다음과 같다.
1) 산오리는 산오리를 좋아해 2) 박사님은 사랑해
1)에서, 자신이 자신을 좋아한다라. 산오리는 나르시스트였던 것인가? (미안해요 산오리님~
) 그나마 이정도는 나은 편이고, 2)의 경우 목적어가 빠져버리므로 완전히 문장 자체가 성립이 되지 않는 것을 알 수 있다.
이런 문제는 대화의 형식이 되면 더욱 심각해진다. 다음 스크립트를 보자.
{{{ (스크립트 5) sentence: \0${질문}\1${답변}\e 질문: ${주어} ${목적어} ${의문서술어} 답변: ${대답}"," ${주어} ${목적어} ${서술어}
대답: 응, 아니 의문서술어: ${어간}${의문형어미} 서술어: ${어간}${어미}
의문형어미: 하던가?, 한가? 어미: 해, 할걸, 하지 않아, 할 리가 없지
# 나머지는 (스크립트 2)와 동일 }}}
쉽게 이해가 갈 것이다. 긍정적인 대답에 부정적인 답변이 올 수도 있다는 것을 떠나서, 질문과 답변이 전혀 따로 놀 수 밖에 없는 것이다. 결국 이런 문제를 해결하려면 한번 쓰인 단어를 기억해 두었다가 답변에서 쓰는 수 밖에 없으며 필연적으로 스크립트가 복잡해 질 수밖에 없을 것이다.
대화 체인
또 다른 단점은 아이러니컬하게도 "대화 체인" 에 있다. 물론 BNF에서의 대화 체인은 잘 쓰면 상당한 장점이 될 수도 있으나, 주의해서 쓰지 않으면 큰 단점이 될 수도 있다. 왜? 바로 위에 설명했던 것과 같은 이유 때문이다. (스크립트 4) 의 2)를 보면 대화-화제바꾸기-대화 로 되어 있을 것이다. 만약 첫번째 대화와 두번째 대화가 동일하다면? 혹은 대화의 앞뒤가 안맞는다면? ("w모씨는 너를 좋아해. 그건 그렇고.. w모씨가 너를 싫어할걸." 과 같은 대화를 생각해 보라..)
대화의 빈도 수
이것은 정확히 말해 BNF의 문제는 아니고 카와리의 문제점이지만, BNF로 스크립트를 제작할 시 반드시 염두에 두어야 할 사항이므로 언급한다. 카와리의 경우, 각 엔트리 (위의 스크립트들에서 "명사" 등에 해당)의 구성요소 갯수 ("명사"의 경우 3개)에만 따라 빈도수를 계산한다. 그러므로 다음과 같은 경우 큰 문제가 될 수 있다.
{{{ (스크립트 6) sentence: ${일반대화}, ${날씨대화}, ${게임대화}
일반대화: 이건 일반대화 1이에요. 일반대화: 이건 일반대화 2죠. 일반대화: 이건 일반대화 3. 일반대화: 짐작했겠지만 일반대화 4;;
날씨대화: 날씨 좋다~
게임대화: H한 게임을 하면 다~메! 게임대화: 나는 RPG가 좋아요~ }}}
여기서 스크립트 제작자가 원하는 바는 무엇이었을까. 아마도 일반대화와 날씨대화, 게임대화를 골고루 하기를 원했을 것이며, 따라서 스크립트에서도 알 수 있듯이 일반대화가 가장 많이 보이며(5/7의 확률), 게임대화가 두번째로 많이 (2/7),그리고 어쩌다 날씨대화가 (1/7) 나오기를 바랬을 것이다. 그러나, 실제로 카와리는 sentence라는 엔트리의 구성요소의 갯수인 3개 (일반대화, 날씨대화, 게임대화) 를 기반으로 각 구성요소에 공평하게 (곧, 1/3의 확률로) 기회를 주고 그 다음에 각 대화 엔트리에서 또 나누어지기 때문에 실제로는 날씨대화가 가장 많이 보일 수 밖에 없게 된다. 다음 표는 각각의 대화의 확률을 계산한 것이다. (확률에 대해 잘 모르면 빈도 %만 비교해 보기를;;)
(표) 각 엔트리마다의 대화 빈도
엔트리 |
제작자의 기대값 |
실제값 |
일반대화 1개 |
1/7 = 14% |
1/3 * 1/4 = 8 % |
총 일반대화 |
4/7 = 57% |
1/3 = 33 % |
날씨대화 1개 |
1/7 = 14% |
1/3 = 33 % |
총 날씨대화 |
(상동) |
(상동) |
게임대화 1개 |
1/7 = 14% |
1/3 * 1/2 = 17% |
총 게임대화 |
2/7 = 28% |
1/3 = 33 % |
이제 왜 (스크립트 1) 의 저자가 저런 식으로 스크립트를 작성했는지 이해가 가리라 믿는다. 이런 문제를 막기 위해서는 어쩔 수 없이 좀 고생이 되더라도 (스크립트 1) 처럼 각 대화의 빈도수를 조절해 주어야만 하기 때문이다.
결론
"도대체 헷갈리네? BNF식으로 스크립트를 짜라는 거야 말라는 거야?" 라고 외치고 싶으신 분이 있을 것이다..(썰렁한가?) 이 글은 제작시부터 BNF가 기존 스크립트 제작 방식보다 우수한지 아닌지를 따지려는 글이 아니고 단지 이런 스크립트 제작방식도 있다는 것을 알려주려는 목적에서 쓰기 시작한 것이며, BNF식으로 스크립트를 제작하든 기존 방식으로 스크립트를 제작하든 그 선택은 제작자 여러분에게 달렸다는 말씀을 드리고 싶다. 불만 있으신 분은 옆에 있는 짱돌을 들어 던지기 바란다. (벙커에 숨는다 ;;)
여담이지만, 이 생각이 머리에 스친 후 지금까지 궁금한 것이 있다. 과연 이 BNF방식으로 스크립트를 제작하도록 한 공로가 과연 누구에게 있는 것인가.. 하는 것이 참 궁금하다. 카와리 스크립트의 할아버지뻘 되는 bash (Bourne-Again SHell)의 제작자들인지, 아니면 Meister(카와리 제작자)씨가 이런 제작방법을 염두에 두고 카와리 스크립트를 제작한 것인지, 아니면 순전히 우연인지.. Meister씨가 털어놓은 카와리 스크립트에 대한 자서전(?)에도 이 내용이 나오지 않으므로 알 수는 없지만.. 결국 BNF라는 가능성이 있는 스크립트 제작방식이 카와리에 적용되었다는 것은 카와리에 있어 상당한 장점으로 부각될 수도 있을 것이다. (Meister, 2002)
(주: 사실 손을 대었던 시오리가 그리 많지 않아 다른 시오리에서도 이런 방식이 적용되고 있을 수도 있다. 혹 다른 시오리에서도 BNF방식의 스크립트 제작이 가능하다면 이 글에 첨가해 주시길 바란다)
참고문헌
(P&Z, 2000) Programming Languages Design and Implementation 4th ed., Terrence W. Pratt & Marvin V. Zelkowitz, Prentice Hall, 2000. (ISBN 0-13-027678-2)
(Meister, 2002) The KIS Programming Language Chapter-0, Meister, Online, 2002. (http://meister-d-i.hoops.ne.jp/kishist.html)
- 언어학에 보면, '문맥 의존 문법'이란 게 있습니다. '문맥 자유 문법'(본문에 컨텍스트-프리 문법이라고 돼있는 것)은 이것의 일부로 속해 있는 것으로서, 문맥 자유 문법이 앞뒤에 있는 문장을 신경 안 쓰고 뜻 자체가 독립적으로 전달되는 데 비해, 문맥 의존 문법은 우리의 실제 대화와 같이 앞뒤 헤아려가며 문법을 따지는 문법입니다. 예를 들어, '난 짜장이다' 라고 하면 문맥 자유 문법에서는, 나(사람)이 짜장이 될 수 있을 리 없으므로 이치에 어긋나지만, 문맥 의존 문법에서는 이것이 음식 주문 시의 대화라고 할 때 문제가 없습니다. 우카가카가 사용자와 제대로 대화하는 모습을 구사하고 싶다면(꼭 그럴 필요는 없으나 그러고 싶다면) 문맥 의존 문법을 실현하는 시오리가 요구됩니다.(아직 있지도 않고, 전산언어학적으로도 거의 구현 불가능이라고 합니다.) - By 검은이슬
