Skip to content
딥러닝 NLP

3주차: 확률론적 언어모델, N-그램 모델

확률론적 언어모델, N-그램 모델.

□ 이번 주에 하는 일

  • 1주차에 “어디서 나온 값인지는 3주차에”라고 미뤄 둔 확률표를 직접 만든다
  • 2주차에서 자른 조각을 두 개씩 짝지어 세면 확률표가 나온다는 것을 손으로 확인한다
  • 그 표가 가진 두 가지 고장, 즉 본 적 없는 말과 짧은 기억을 보고 왜 그런 고장이 나는지 본다
흘러나온 말이 작은 카드로 잘린 뒤 두 장씩 실로 묶이고, 묶인 카드가 나무판에 그려진 큰 격자의 칸으로 들어가 앉는 그림. 채워진 칸은 몇 개뿐이고 대부분의 칸은 비어 있다. 학생이 연필을 들고 카드 한 쌍을 칸에 놓고 있으며 뒤로는 현무암 돌담과 낮은 오름이 있는 제주 해안이 보인다

□ 1주차에서 이어지는 자리

  • 1주차에서 혼저 ___ 의 다음 말로 옵서예가 0.90이라고 적어 두었다. 그때는 설명용으로 정한 값이라고 밝혔다
  • 이번 주에는 빈도를 세어 확률을 추정하는 방법을 배운다. 1주차의 설명용 숫자 0.90을 실제 측정으로 재현하는 활동은 아니다. 다만 우리가 쓸 말뭉치는 여덟 문장뿐이라서 0.90이 아닌 다른 숫자가 나온다. 그 차이가 어디서 오는지까지가 이번 주의 내용이다
  • 2주차에서 배운 자르기는 이미 끝난 것으로 놓는다. 이번 주는 잘라 놓은 조각을 두 개씩 붙여 세는 데서 시작한다

□ 이 문서를 읽는 법

  • 수식은 한 개도 나오지 않는다. 계산은 나눗셈까지만 한다. “3번 중 2번이니 2를 3으로 나눈다”가 이 문서에서 하는 가장 어려운 계산이다
  • 표에 적힌 숫자는 대부분 여덟 문장에서 실제로 센 값이다. 1주차와 달리 이번에는 직접 세어 확인할 수 있다. 따로 “설명용 예시”라고 밝힌 값만 설명하려고 정한 값이다
  • 소수 둘째 자리에서 반올림했기 때문에 한 줄의 확률을 다 더하면 1을 살짝 넘거나 모자랄 수 있다
  • 종이와 연필을 옆에 두고 읽기를 권한다. 표가 나올 때마다 한 줄씩 직접 세어 보면 훨씬 빨리 끝난다

강의 1: 확률표를 손으로 만든다

Section titled “강의 1: 확률표를 손으로 만든다”

□ 여덟 문장짜리 말뭉치

이번 주 내내 아래 여덟 문장만 쓴다. 새 예제로 갈아 끼우지 않고 같은 말뭉치를 여러 번 다시 본다.

번호문장
1혼저 옵서예 제주
2혼저 옵서예 한라산
3제주 바다는 넓다
4우도 바다는 맑다
5제주에서 먹고 싶은 것은 고기국수
6오늘 먹고 싶은 것은 고기국수
7제주에서 먹고 싶은 것은 흑돼지
8제주에서 사고 싶은 것은 감귤

왜 이렇게 작은가. 손으로 셀 수 있어야 하기 때문이다. 공개 말뭉치는 손으로 세기 어려울 만큼 클 수 있다. 이 여덟 문장은 계산을 따라가기 위한 수업용 가공 예시이며 공개 원문에서 추출한 자료가 아니다. 여덟 문장이면 종이에 적어 가며 끝까지 따라갈 수 있고, 그것이 이번 주의 목적이다.

□ 조각으로 자르고 양끝에 표시를 붙인다

2주차에서 배운 대로 자른다. 여기서는 띄어쓰기로만 자른다. 그런데 한 가지가 더 필요하다. 문장이 어디서 시작하고 어디서 끝나는지도 세야 한다. 그래서 양끝에 표시를 붙인다.

문장 1을 예로 들면 이렇게 된다.

[시작] 혼저 옵서예 제주 [끝]

  • [시작] 은 문장 앞에 붙이는 표시다. “문장이 여기서 시작한다”는 뜻이다
  • [끝] 은 문장 뒤에 붙이는 표시다. 1주차에서 “끝맺음 표시가 뽑히면 멈춘다”고 한 그 표시가 이것이다
  • 표시를 빼면 조각이 모두 32개이고, 표시까지 넣으면 48개다

□ 그냥 세기: 어떤 말이 몇 번 나왔나

먼저 2주차에서 하던 대로 세어 보자. 표시를 뺀 조각 32개를 그냥 센 결과다.

말횟수32로 나누면
것은40.13
싶은40.13
먹고30.09
제주에서30.09
고기국수20.06
바다는20.06
옵서예20.06
제주20.06
혼저20.06
감귤, 넓다, 맑다, 사고, 오늘, 우도, 한라산, 흑돼지각 1각 0.03

여기까지는 새로운 것이 없다. 2주차에서 한 일과 같다. 이 표로도 다음 말을 고를 수는 있다. 다만 앞말을 반영하지 않는 유니그램 생성이어서 문맥에 맞는 말을 고르기 어렵다. 이 표대로 뽑으면 앞에 무슨 말이 왔든 늘 같은 확률로 고르게 된다. 혼저 다음에도 것은 이 0.13으로 나올 수 있다는 뜻인데, 말이 되지 않는다.

왜 안 되는지 조금 더 따져 보자. 이 표는 “이 말뭉치에서 어떤 말이 많이 쓰였나”를 알려 준다. 그것은 그것대로 쓸모가 있다. 어떤 글인지 성격을 가늠하거나, 2주차에서 본 것처럼 자주 나오는 말을 추려 내는 데는 이 표면 충분하다.

그런데 우리가 하려는 일은 다르다. 앞의 글을 보고 다음에 올 말을 고르는 일이다. 이 표에는 “앞에 무엇이 있었는가”라는 정보가 아예 들어 있지 않다. 조각을 하나씩 따로 세면서 그 정보를 버렸기 때문이다.

  • 문장 1과 2에서 옵서예 는 늘 혼저 뒤에 나왔다. 그런데 그냥 센 표에는 그 사실이 적혀 있지 않다
  • 문장 5, 6, 7에서 싶은 은 늘 것은 앞에 나왔다. 이 사실도 표에서 사라졌다

버린 정보를 되살리려면 세는 방법 자체를 바꿔야 한다.

□ 짝으로 세기

그래서 세는 방법을 바꾼다. 하나씩 세지 말고 이웃한 둘씩 묶어서 센다.

자른 조각에서 시작해 두 개씩 짝 만들기, 같은 짝끼리 세기, 앞말별로 나누기 네 칸이 차례로 이어지는 흐름도. 세 번째 칸 아래로는 횟수만 적힌 표가, 네 번째 칸 아래로는 행마다 합이 1이 되는 확률표가 갈라져 나온다

첫 단계는 짝을 만드는 것이다. 문장 1에서 짝을 뽑아 보자.

순서짝
1[시작] 혼저
2혼저 옵서예
3옵서예 제주
4제주 [끝]

조각이 3개였는데 표시를 붙이니 5개가 되고, 짝은 4개가 나온다. 조각 수보다 짝 수가 하나 적다. 여덟 문장 전체에서는 짝이 40개 만들어지고, 그중 서로 다른 짝은 28가지다.

둘째 단계는 같은 짝끼리 모아 세는 것이다. 혼저 옵서예 는 문장 1과 2에 나오니 2번이다. 먹고 싶은 은 문장 5, 6, 7에 나오니 3번이다.

셋째 단계가 핵심이다. 앞말이 같은 것끼리 묶어서 나눈다.

□ 나눗셈 한 번으로 확률이 된다

혼저 로 시작하는 짝을 모아 보자. 혼저 옵서예 2번이 전부다. 그러니 혼저 다음에 옵서예가 올 확률은 2를 2로 나눈 1.00이다.

혼저 다음에 올 말횟수확률
옵서예21.00

이번에는 것은 으로 시작하는 짝을 모은다. 모두 4번 나오는데 뒤에 오는 말이 셋으로 갈린다.

것은 다음에 올 말횟수확률
고기국수20.50
흑돼지10.25
감귤10.25

1주차에서 본 뾰족한 표와 평평한 표가 여기서 그대로 다시 나온다. 혼저 다음은 한 곳에 몰려 있고 것은 다음은 셋으로 퍼져 있다. 다른 점은 이번에는 그 숫자를 우리가 직접 만들었다는 것이다.

나눗셈의 분모가 무엇인지 한 번 더 확인하고 가자. 학생들이 가장 자주 헷갈리는 지점이다.

  • 것은 다음 표에서 고기국수의 분모는 전체 조각 32개가 아니다. 것은 이 나온 횟수인 4다
  • 그래서 0.50은 “전체에서 고기국수가 차지하는 몫”이 아니라 **“것은 다음 자리에서 고기국수가 차지하는 몫”**이다
  • 표 한 줄의 확률을 다 더하면 1이 된다. 0.50 더하기 0.25 더하기 0.25는 1이다. 앞말이 나왔을 때 그다음에는 반드시 무언가가 오기 때문이다

앞말이 몇 번 나왔는지만 알면 나머지는 나눗셈이다. 그래서 이 작업의 어려운 부분은 계산이 아니라 빠뜨리지 않고 세는 것이다.

한 가지만 짚고 넘어가자. 혼저 다음이 1.00이라는 것은 정말 다른 말이 절대 안 온다는 뜻일까. 이 질문은 강의 2에서 다시 다룬다.

□ 1주차의 0.90과 맞대 보면

1주차에서 같은 자리의 값을 이렇게 적었다.

1주차에서 본 값이번 주 여덟 문장에서 센 값
혼저 다음 옵서예0.901.00
혼저 다음 오세요0.050.00
혼저 다음 그 밖의 말0.050.00

두 열의 근거가 다르다는 점을 먼저 밝혀 둔다. 1주차의 0.90은 어떤 자료를 세어서 나온 값이 아니라 뾰족한 표의 모양을 보여 주려고 정한 숫자였고, 오른쪽 1.00은 우리가 방금 만든 여덟 문장에서 실제로 센 값이다.

그러니 이 표에서 읽을 것은 “0.90이 맞느냐 1.00이 맞느냐”가 아니다. 같은 자리인데도 세는 글이 달라지면 숫자가 달라진다는 사실이다.

그리고 1.00과 0.00이라는 값 자체를 조심해서 읽어야 한다. 1.00은 “옵서예밖에 올 수 없다”는 뜻이 아니라 **“이 여덟 문장 안에서는 다른 말을 못 봤다”**는 뜻이다. 0.00도 “그 말이 올 수 없다”가 아니라 “여기서는 못 봤다”이다. 여덟 문장만 보고 “혼저 다음에는 옵서예밖에 오지 않는다”고 단정하는 것이 관찰 범위를 넘어선 결론이다.

□ 앞말이 바뀌면 표가 통째로 바뀐다

이제 이번 주에서 가장 중요한 대목이다. 같은 “고기국수”를 두 가지 방법으로 세어 보자.

세는 방법세는 범위나온 횟수나눗셈확률
그냥 세기조각 32개 전부22를 32로 나눈다0.06
앞말을 정해 놓고 세기것은 다음 자리 4개만22를 4로 나눈다0.50
왼쪽은 조각 32개 전부를 그냥 세어 만든 표로 고기국수 막대가 0.06으로 아주 짧고, 오른쪽은 것은 다음 자리 4개만 모아 센 표로 같은 고기국수 막대가 0.50까지 올라간 가로 막대그래프 두 개. 두 그래프의 눈금은 0에서 1로 같다

나온 횟수는 둘 다 2로 같다. 달라진 것은 무엇으로 나누었는가뿐이다. 전체로 나누면 0.06이고, 것은 다음 자리만 모아 그것으로 나누면 0.50이다. 같은 말인데 여덟 배 차이가 난다.

1주차에서 “앞에 오는 글이 바뀌면 표도 바뀐다”고 했다. 그 말의 정체가 이것이다. 조건부 확률이란 세는 범위를 앞말로 좁힌 결과일 뿐이다. 어려운 개념이 아니라 세는 방법을 한 번 바꾼 것이다.

□ 표 전체를 한 장에 놓고 보면

앞말 19가지와 다음 말 19가지를 가로세로로 놓으면 칸이 361개 생긴다. 여덟 문장에서 실제로 채워진 칸이 몇 개인지 보자.

앞말 19가지를 세로로, 다음에 올 말 19가지를 가로로 놓은 19 곱하기 19 격자. 361칸 가운데 28칸만 파란색으로 칠해져 있고 나머지 333칸은 옅은 회색으로 비어 있다. 싶은에서 것은으로 가는 칸이 4로 가장 진하고, 시작 표시에서 제주에서로 가는 칸이 3, 혼저에서 옵서예와 제주에서에서 먹고와 것은에서 고기국수로 가는 칸이 각각 2다

361칸 중 28칸만 찼다. 나머지 333칸, 그러니까 92퍼센트가 비어 있다. 이 빈 칸들이 강의 2의 주제다.

이 그림에서 읽어 둘 것이 세 가지 있다.

  • 가장 진한 칸은 싶은 에서 것은 으로 가는 칸이다. 4번으로 가장 많이 나왔다. 여덟 문장 중 네 문장이 같은 표현을 쓴 결과다
  • 행마다 채워진 칸 수가 다르다. 혼저 행은 한 칸만 찼고 [시작] 행은 다섯 칸이 찼다. 채워진 칸이 하나뿐인 행이 곧 확률 1.00이 나오는 행이다
  • 빈 칸이 전부 “불가능”을 뜻하지는 않는다. 것은 다음에 갈치조림이 오는 표현은 가능하다. 다만 갈치조림은 이 여덟 문장의 어휘에 없어서 위 19 x 19 격자에는 열 자체가 없다. 이 예시는 후보 어휘를 확장했을 때의 빈 짝으로 읽는다. 우리가 그 여덟 문장에서 못 봤을 뿐이다

말뭉치를 여덟 문장에서 8만 문장으로 늘리면 어떻게 될까. 채워지는 칸이 늘어나기는 한다. 그런데 말의 종류도 함께 늘어나기 때문에 칸 수 자체가 훨씬 빠르게 커진다. 2주차 Zipf에서 본 것처럼 한 번만 나오는 말이 끝없이 이어지므로, 표는 아무리 키워도 대부분 비어 있다. 이것은 말뭉치가 작아서 생긴 문제가 아니라 언어가 원래 그런 모양이라서 생기는 문제다.

□ 세는 짝의 길이에 이름이 있다

지금까지 한 것처럼 둘씩 묶어 세는 방식을 바이그램이라고 부른다. 이름은 몇 개씩 묶는지를 가리킨다.

  • 하나씩 세면 유니그램이다. 앞말을 보지 않는다
  • 둘씩 세면 바이그램이다. 바로 앞 한 단어를 본다
  • 셋씩 세면 트라이그램이다. 바로 앞 두 단어를 본다

이들을 통틀어 N-그램이라고 한다. N은 몇 개씩 묶어 세는지를 가리키는 숫자다.

같은 문장을 세 방식으로 잘라 보면 차이가 분명해진다. 문장 5 제주에서 먹고 싶은 것은 고기국수 를 쓴다.

방식세는 단위이 문장에서 나오는 것
유니그램하나씩제주에서 / 먹고 / 싶은 / 것은 / 고기국수
바이그램둘씩제주에서 먹고 / 먹고 싶은 / 싶은 것은 / 것은 고기국수
트라이그램셋씩제주에서 먹고 싶은 / 먹고 싶은 것은 / 싶은 것은 고기국수

조각 5개에서 유니그램은 5개, 바이그램은 4개, 트라이그램은 3개가 나온다. 묶는 길이가 길어질수록 같은 문장에서 뽑히는 개수가 줄어든다. 이 감소가 강의 2에서 다룰 문제의 씨앗이다.

이 과목에서 주로 쓰는 것은 바이그램이다. 유니그램은 앞말을 못 보고, 트라이그램은 여덟 문장짜리 말뭉치에서 쓰기에 너무 촘촘하기 때문이다.

□ 앞을 전부 보면 되지 않나

바이그램은 바로 앞 한 단어만 본다. 그런데 앞의 말을 전부 보면 더 정확하지 않을까. 제주에서 먹고 싶은 것은 ___ 에서 앞의 네 단어를 모두 보고 고르면 될 것 같다.

문제는 그렇게 하려면 그 네 단어가 통째로 나온 적이 있어야 한다는 것이다. 여덟 문장에서 세어 보자.

앞에 보는 범위이 조합이 나온 횟수
것은4번
싶은 것은4번
먹고 싶은 것은3번
제주에서 먹고 싶은 것은3번
겨울에 제주에서 먹고 싶은 것은0번

앞을 길게 볼수록 세어 볼 거리가 급격히 줄어든다. 한 단어를 늘릴 때마다 조건이 까다로워져서, 조금만 길어지면 그 조합을 한 번도 본 적이 없게 된다. 본 적이 없으면 확률을 만들 수 없다.

□ 그래서 바로 앞 한 단어만 본다

안개 속으로 길게 이어진 현무암 돌담을 따라 걷는 사람이 작은 등불을 들고 있는 그림. 등불은 바로 뒤에 놓인 돌 하나만 동그랗게 비추고 그보다 먼 쪽 돌담은 어둠에 묻혀 보이지 않는다. 사람은 앞쪽에 다음 돌을 놓으려고 손을 뻗고 있다

바이그램이 제주에서 먹고 싶은 것은 ___ 를 볼 때 실제로 보는 것은 것은 하나뿐이다. 등불이 바로 뒤의 돌 하나만 비추는 것과 같다.

  • 잃는 것: 앞의 “먹고”를 잊어버린다. 사고 싶은 것은 과 먹고 싶은 것은 을 구별하지 못한다
  • 얻는 것: 셀 수 있게 된다. 못 본 조합이 크게 줄어든다

이 맞바꿈에 붙은 이름이 마르코프 가정이다. “앞의 모든 것을 보는 대신 바로 앞 몇 개만 본다”는 약속이다. 먼 문맥의 단서를 줄이고 더 안정적으로 셀 수 있는 범위를 얻는 거래다.

“가정”이라는 말에 주목하자. 이것은 발견한 사실이 아니라 우리가 그렇게 하기로 정한 약속이다. 실제 언어가 바로 앞 한 단어만으로 결정되지는 않는다. 사고 싶은 것은 과 먹고 싶은 것은 은 뒤에 올 말이 분명히 다른데, 바이그램은 그 차이를 알지 못한다. 문장 8에서 사고 싶은 것은 뒤에 감귤이 왔지만, 바이그램에게는 것은 하나만 보이므로 고기국수가 뽑힐 수도 있다.

그러니 마르코프 가정은 틀린 줄 알면서 쓰는 약속이다. 왜 틀린 줄 알면서 쓰는가.

  • 틀린 만큼의 손해보다 셀 수 있게 되는 이득이 크기 때문이다
  • 앞을 다 보는 정확한 방법은 셀 거리가 없어서 아예 만들 수 없기 때문이다
  • 짧은 문장에서는 앞 한 단어만으로도 꽤 그럴듯한 결과가 나오기 때문이다

이렇게 “완전하지는 않지만 쓸 만한 약속을 세우고 그 대가를 아는 것”이 이 분야에서 반복해 나오는 방식이다. 9주차 이후의 모델들은 같은 문제를 다른 약속으로 푼다.

□ 트라이그램은 더 나은가

앞 두 단어를 보면 어떨까. 바다는 으로 확인해 보자.

보는 범위앞에 있는 말다음에 올 말확률
바로 앞 한 단어바다는넓다0.50
바로 앞 한 단어바다는맑다0.50
바로 앞 두 단어제주 바다는넓다1.00
바로 앞 두 단어우도 바다는맑다1.00
앞을 안 보는 유니그램, 앞 한 단어를 보는 바이그램, 앞 두 단어를 보는 트라이그램 세 칸이 왼쪽에서 오른쪽으로 놓인 비교도. 각 칸 아래에 늘 같은 표와 셀 거리는 넉넉, 앞말마다 다른 표와 아직 셀 만하다, 더 정확한 표와 못 본 조합 급증이라는 얻는 것과 잃는 것이 붙어 있다

그림의 “더 정확한 표”는 문맥을 세분해 표현할 수 있다는 직관이다. 실제로 이 여덟 문장에서는 제주 바다는 다음에 넓다만 왔고 우도 바다는 다음에 맑다만 왔다. 새 글에서도 예측이 더 정확한지는 별도 시험 자료로 확인해야 한다.

그런데 함정이 있다. 트라이그램으로 세면 서로 다른 앞말 묶음이 21가지로 늘어나는데 센 짝은 32개뿐이다. 묶음 하나당 평균 1.5번밖에 못 센다. 한 번 본 것으로 만든 확률은 전부 1.00이 된다. 위 표의 1.00 두 개가 바로 그것이다. 확실해서 1.00이 아니라 한 번밖에 못 봐서 1.00이다.

□ 한 칸이 0이면 문장 전체가 막힌다

얕은 바닷물을 가로지르는 징검다리 여섯 개 중 다섯 번째 돌이 빠져 있어 검은 틈이 생긴 그림. 건너던 사람이 그 틈 앞에서 멈춰 서 있고 맞은편 마지막 돌은 아무도 밟지 못한 채 남아 있다. 위쪽에서 뻗은 손이 작고 납작한 돌 하나를 그 틈에 내려놓고 있다

문장이 얼마나 그럴듯한지는 칸을 차례로 밟아 가는 길로 따진다. [시작] - 제주에서 - 먹고 - 싶은 - 것은 - 감귤 - [끝] 은 여섯 칸이 모두 차 있어서 길이 이어진다.

그런데 제주에서 먹고 싶은 것은 갈치조림 은 마지막 한 칸에서 막힌다. 것은 - 갈치조림 칸이 0이기 때문이다. 앞의 다섯 칸이 아무리 튼튼해도 한 칸이 끊기면 길 전체가 끊긴다.

여기서 억울한 점이 있다. 갈치조림은 제주에서 아주 흔한 말이다. 없는 말이 아니라 이 여덟 문장에 안 나왔을 뿐이다. 강의 1에서 본 361칸 중 333칸이 비어 있었다는 것을 떠올리면, 이런 일이 얼마나 자주 일어날지 짐작이 간다.

처방은 단순하다. 0인 칸에 아주 작은 값을 나눠 준다. 그만큼은 이미 큰 칸들에서 조금씩 걷는다. 이것을 스무딩이라고 부른다.

것은 다음에 올 말 네 가지의 확률을 센 그대로와 아주 작은 값을 나눠 준 뒤로 나란히 비교한 묶음 막대그래프. 고기국수는 0.50에서 0.47로, 흑돼지와 감귤은 각각 0.25에서 0.24로 조금씩 내려가고, 한 번도 나오지 않아 0이던 갈치조림에는 0.05가 새로 생겼다
것은 다음에 올 말센 그대로아주 작은 값을 나눠 준 뒤
고기국수0.500.47
흑돼지0.250.24
감귤0.250.24
갈치조림0.000.05

0이던 칸에 0.05를 주고, 큰 칸에서 0.03을, 나머지 두 칸에서 0.01씩 걷었다. 걷은 값의 합이 0.05로 정확히 맞고 전후 모두 합이 1.00이다. 위 숫자는 설명용 예시다. 무엇을 얼마나 걷을지 정하는 방법은 여러 가지가 있고 교재 3장에 나온다. 이 과목에서는 **“0을 남기지 않는다”**는 목적까지만 알면 된다.

여기서 두 가지를 짚어 두자.

첫째, 걷어 오는 쪽이 있어야 한다. 없던 값을 그냥 만들어 낼 수는 없다. 표 한 줄의 합은 항상 1이어야 하므로, 어딘가에 0.05를 주려면 다른 칸에서 그만큼을 덜어 내야 한다. 그래서 스무딩은 “없는 것을 채우는 일”이 아니라 **“있는 것을 조금씩 나눠 주는 일”**이다.

둘째, 얼마나 줄지가 판단의 대상이다. 양수인 확률을 주면 그 칸 때문에 문장 확률이 정확히 0이 되지는 않는다. 다만 아주 작은 값이면 뽑기에서 거의 선택되지 않을 수 있다. 너무 많이 주면 실제로 많이 나온 말의 몫이 깎여 표가 뭉개진다. 여덟 문장처럼 작은 말뭉치에서는 0인 칸이 압도적으로 많아서, 조금씩만 나눠 줘도 걷어야 할 총량이 커진다. 이 균형을 잡는 방법들이 교재에 여럿 나오는데, 이름을 외울 필요는 없다.

□ 얼마나 헷갈리는지 숫자 하나로

표를 여러 장 만들었을 때 어느 쪽이 나은지 비교할 방법이 필요하다. 다음 말을 고르는 자리에서 후보가 몇 개쯤으로 좁혀졌는지를 나타내는 숫자가 있다. 퍼플렉서티라고 부른다.

헷갈림 숫자를 1에서 10까지 눈금 위에 표시한 가로 그래프. 혼저 다음 표는 1.0으로 맨 왼쪽 초록 점, 것은 다음 표는 2.8로 가운데 파란 점, 넓게 퍼진 표는 8.0으로 오른쪽 빨간 점에 놓여 있다. 눈금 아래 왼쪽 끝에는 낮을수록 덜 헷갈린다고, 오른쪽 끝에는 높을수록 더 헷갈린다고 적혀 있다
표모양헷갈림 숫자읽는 법
혼저 다음후보 1개1.0헷갈릴 것이 없다
것은 다음0.50 / 0.25 / 0.25약 2.8후보 세 개쯤을 놓고 고민한다
넓게 퍼진 표후보 여덟 개가 비슷약 8여덟 개 중 하나를 찍는 것과 비슷하다

같은 시험 글과 같은 토큰화·어휘 조건에서 낮을수록 예측이 낫다. 퍼플렉서티는 실제 다음 토큰에 부여한 확률을 전체 시험 글에서 종합한 값이다. 단순히 후보를 적게 남겼다는 것만으로 좋아지지는 않는다. 위 표는 시험 글이 각 확률표와 같은 비율로 나온다는 조건을 상정한 직관용 예시이며, 계산은 이 과목에서 다루지 않는다.

이 숫자를 쓰는 자리는 이렇다. 같은 글로 표를 두 장 만들었다고 하자. 하나는 바이그램으로, 하나는 트라이그램으로 만들었다. 어느 쪽이 나은지 사람이 문장을 하나하나 읽어 보고 판단하기는 어렵다. 그때 새로운 글을 가져와 두 표에 각각 물어보고 헷갈림 숫자를 재어 비교한다.

주의할 점이 하나 있다. 표를 만들 때 쓴 글로 다시 재면 안 된다. 그 글은 이미 다 외운 셈이라 헷갈림이 낮게 나온다. 시험 문제를 미리 보고 푼 것과 같다. 그래서 표를 만드는 데 쓴 글과 재는 데 쓰는 글을 반드시 나눈다. 이 원칙은 이 과목 뒤쪽에서 모델을 평가할 때마다 다시 나온다.

□ 강의 2에서 꼭 가져갈 것

  1. 앞을 길게 보면 더 많은 단서를 쓸 수 있지만 셀 거리가 마른다. 그래서 새 글에서 항상 더 정확해지는 것은 아니다. 그래서 바로 앞 몇 개만 보기로 약속한 것이 마르코프 가정이다
  2. 세어 만든 표에는 0인 칸이 아주 많다. 여덟 문장에서는 92퍼센트가 비었다
  3. 한 칸이 0이면 문장 전체가 막히므로, 0인 칸에 작은 값을 나눠 주는 스무딩이 필요하다
  4. 같은 시험 글·토큰화·어휘 조건의 표끼리 비교할 때는 퍼플렉서티를 쓰고 낮을수록 예측이 낫다

□ 활동 A: 바이그램을 손으로 세어 표 채우기

아래 네 문장으로 직접 세어 본다. 강의에서 쓴 여덟 문장이 아니라 새 말뭉치다.

번호문장
1오늘 바다에 간다
2오늘 오름에 간다
3내일 바다에 간다
4오늘 오름에 오른다

절차는 이렇다.

  1. 각 문장의 양끝에 [시작] 과 [끝] 을 붙인다
  2. 이웃한 조각을 두 개씩 묶어 짝을 모두 적는다
  3. 앞말이 같은 것끼리 모아 아래 표 세 장을 채운다
오늘 다음에 올 말횟수확률
바다에 다음에 올 말횟수확률
오름에 다음에 올 말횟수확률

제출은 표 세 장과 두 문장 설명이다. 설명에는 세 표 중 어느 것이 가장 뾰족하고 어느 것이 가장 평평한지, 그 이유가 무엇인지를 적는다.

□ 활동 B: 자동완성이 제주 말을 놓치는 사례 모으기

대면 시간 토론의 재료를 미리 모은다. 휴대전화 입력기나 검색창의 자동완성에 제주 지명이나 방언을 넣어 보고, 엉뚱하게 바뀌는 사례를 세 개 찾아 표로 적는다.

입력한 말나온 결과내 짐작

제품 이름은 적지 않는다. “휴대전화 입력기”, “검색창의 자동완성” 처럼 기능 이름으로 쓴다.

□ 코드 열두 줄

노트북을 열고 아래를 실행한다. 손대지 않아도 끝까지 돌아간다.

sentences = [
"혼저 옵서예 제주",
"혼저 옵서예 한라산",
"제주 바다는 넓다",
"우도 바다는 맑다",
"제주에서 먹고 싶은 것은 고기국수",
"오늘 먹고 싶은 것은 고기국수",
"제주에서 먹고 싶은 것은 흑돼지",
"제주에서 사고 싶은 것은 감귤",
]
from collections import Counter, defaultdict
pairs = defaultdict(Counter)
for s in sentences:
words = ["[시작]"] + s.split() + ["[끝]"]
for a, b in zip(words, words[1:]):
pairs[a][b] += 1
print(pairs["것은"])

출력은 Counter({'고기국수': 2, '흑돼지': 1, '감귤': 1}) 이다.

한 줄씩 보면 이렇다.

  • sentences 는 여덟 문장을 담아 둔 목록이다
  • ["[시작]"] + s.split() + ["[끝]"] 이 양끝에 표시를 붙이는 부분이다
  • zip(words, words[1:]) 가 이웃한 둘씩 짝을 만드는 부분이다. 강의 1의 첫 단계가 이 한 줄이다
  • pairs[a][b] += 1 이 같은 짝을 셀 때마다 1씩 올리는 부분이다

□ 손으로 센 표와 맞춰 보기

강의 1에서 손으로 만든 것은 표와 코드 출력이 같은지 확인한다. 고기국수 2, 흑돼지 1, 감귤 1이면 맞다. 다른 앞말도 pairs["제주에서"] 처럼 바꿔 넣어 확인한다.

이 대조가 오늘 실습의 핵심이다. 코드가 무슨 일을 하는지 모르겠어도, 손으로 센 결과와 같은 값이 나온다는 것만 확인하면 된다.

□ 활동 A의 네 문장도 같은 코드로 확인한다

온라인 활동 A에서 손으로 채운 표 세 장도 코드로 맞춰 본다. 말뭉치만 바꾸면 되고 나머지는 위와 같다.

activity = ["오늘 바다에 간다", "오늘 오름에 간다", "내일 바다에 간다", "오늘 오름에 오른다"]
pairs_a = defaultdict(Counter)
for s in activity:
words = ["[시작]"] + s.split() + ["[끝]"]
for a, b in zip(words, words[1:]):
pairs_a[a][b] += 1
for w in ["오늘", "바다에", "오름에"]:
print(w, pairs_a[w])

출력은 이렇다.

오늘 Counter({'오름에': 2, '바다에': 1})
바다에 Counter({'간다': 2})
오름에 Counter({'간다': 1, '오른다': 1})

활동 A에서 채운 표와 숫자가 같은지 한 줄씩 대조한다. 다르다면 문장을 빠뜨렸는지, 오름에 간다와 오름에 오른다를 구분했는지 확인한다. 이 세 앞말의 다음 말 횟수는 양끝 표시를 빼더라도 같으므로, 양끝 표시 누락을 원인으로 단정하지 않는다.

□ 자주 나는 오류

증상원인조치
Counter() 만 나오고 안이 비어 있다오류가 아니다. 말뭉치에 없는 앞말을 넣어서 빈 표가 나온 것이다여덟 문장에 있는 말인지 확인한다
SyntaxError 가 뜬다sentences 목록의 따옴표 짝이 맞지 않는다각 줄 양끝의 큰따옴표를 확인한다
NameError: name 'defaultdict' is not definedfrom collections import ... 셀을 건너뛰었다맨 위 셀부터 차례로 실행한다

첫 줄을 눈여겨보자. 처음 보는 앞말을 넣어도 오류가 나지 않고 빈 표가 조용히 나온다. 오류가 없다고 해서 결과가 맞는 것은 아니라는 뜻이다. 출력이 비어 있으면 그 말이 말뭉치에 없었다는 신호로 읽으면 된다.

□ 표를 따라 걸어 문장 만들기

[시작] 에서 출발해 표를 보고 한 칸씩 뽑아 [끝] 까지 가면 문장 하나가 만들어진다.

시작 표시에서 출발해 제주에서 0.38, 먹고 0.67, 싶은 것은 1.00, 고기국수 0.50을 거쳐 끝 표시로 이어지는 경로도. 시작 표시와 제주에서와 고기국수 세 자리에서는 아래쪽으로 뽑히지 않은 다른 후보들이 흐린 갈래로 갈라져 나간다
단계지금까지의 글보는 표뽑힌 말
1[시작]제주에서 0.38, 혼저 0.25, 제주 0.13, 우도 0.13, 오늘 0.13제주에서
2제주에서먹고 0.67, 사고 0.33먹고
3제주에서 먹고싶은 1.00싶은
4제주에서 먹고 싶은것은 1.00것은
5제주에서 먹고 싶은 것은고기국수 0.50, 흑돼지 0.25, 감귤 0.25흑돼지
6제주에서 먹고 싶은 것은 흑돼지[끝] 1.00[끝]

여러 번 돌려 보고 나온 문장을 기록한다. 그리고 어색한 문장이 왜 어색한지 아래 세 갈래로 분류한다.

  1. 본 적 없는 말은 절대 안 나온다. 갈치조림은 아무리 돌려도 나오지 않는다
  2. 바로 앞 한 단어밖에 기억을 못 한다. 사고 싶은 것은 고기국수 같은 문장이 나올 수 있다
  3. 센 것이 너무 적다. 대부분의 칸이 1.00이라 정해진 길만 따라간다

□ 오늘 확인할 것

  • 코드 출력과 손으로 센 표가 같다
  • 문장을 세 번 이상 생성해 기록했다
  • 어색한 문장 하나를 골라 위 세 갈래 중 어디에 해당하는지 적었다

□ 기본·심화 학습 경로

  • 기본: 기본 말뭉치의 것은 횟수와 확률을 손으로 센 표와 대조. TODO 앞말 한 곳만 변경
  • 심화: 같은 표에서 어휘 밖의 말과 어휘 안의 미관찰 짝 구분. 퍼플렉서티 비교에 필요한 시험 자료·토큰화 조건 설명
  • 두 경로 모두 제공 코드를 실행한 결과 해석 중심. 추가 구현이나 별도 TODO 요구 없음

[이론] 확률 개념은 수식보다 빈도 표로 먼저 접근. 손으로 센 표와 코드 결과를 맞춰 봄

  • 활동 A에서 채운 표 세 장과 대면 실습의 코드 출력을 나란히 놓고 같은지 확인한다
  • 세 표 중 가장 뾰족한 것과 가장 평평한 것을 고르고 그 이유를 세 문장에서 다섯 문장으로 쓴다
  • 수식을 쓰지 않아도 정답이다. “표가 평평해서”라고 써도 개념을 짚었으면 점수가 나간다

[구현] 바이그램 모델을 데모 따라 만들고 문장을 생성

  • 코드를 실행해 것은 표를 출력하고 결과를 그대로 옮겨 적는다
  • 문장을 세 번 이상 생성해 기록한다
  • 코드가 끝내 돌지 않아도 무엇을 확인하려 했는지 쓰면 부분 점수를 받는다

[실무] 생성된 문장이 어색한 이유를 개념으로 설명하는 토론. 제주 지명·고유명사가 자동완성·자동교정에서 깨지는 사례를 모아 왜 그런지 연결

  • 활동 B에서 모은 사례 세 개를 가져온다
  • 각 사례에 대해 “자료에서 적게 봤을 가능성”, “단어를 자르는 기준”, “입력기 설정”처럼 가능한 이유를 적는다. 실제 서비스의 사전·확률표를 확인하지 못했으면 원인은 미확인으로 남긴다
  • 제품 이름 대신 기능 이름으로 적는다
Q1. 확률은 누가 정해 준 값인가?

정해 준 사람은 없다. 이번 주에 직접 해 본 것처럼 글에서 세어 나온 값이다. 여덟 문장을 세면 “것은” 다음에 고기국수가 네 번 중 두 번 나왔고, 2를 4로 나눈 0.50이 그대로 확률이 된다. 사람이 손으로 정한 것이 아니라 나눗셈 한 번의 결과다. 그래서 세는 글이 바뀌면 확률도 바뀐다. 제주 관광 후기를 세면 음식 이름이 높게 나오고 행정 문서를 세면 전혀 다른 표가 나온다.

Q2. 여덟 문장으로 센 표에서 옵서예가 1.00이면 “혼저” 다음에는 옵서예밖에 못 오는 것인가?

아니다. 그 1.00은 “옵서예밖에 올 수 없다”가 아니라 **“이 여덟 문장 안에서는 다른 말을 못 봤다”**는 뜻이다. 1주차의 0.90은 실측이 아닌 설명용 값이다. 더 많은 글에서 다른 경우를 만나면 1.00이 내려갈 수 있지만, 자료를 늘렸다는 것만으로 반드시 내려가는 것은 아니다. 작은 말뭉치에서 나온 1.00과 0.00은 아직 못 본 것일 뿐이라고 읽어야 한다. 이 점을 표에 반영하는 처방이 스무딩이다.

Q3. 앞을 길게 볼수록 좋은 모델 아닌가? 왜 한 단어만 보는가?

길게 보면 더 많은 문맥 단서를 얻지만, 새 글에서 더 정확해진다는 보장은 없다. 문제는 셀 거리가 사라진다는 것이다. 여덟 문장에서 앞 두 단어까지 보면 서로 다른 앞말 묶음이 21가지로 늘어나는데 센 짝은 32개뿐이라 묶음 하나당 1.5번밖에 못 센다. 어떤 묶음은 한 번만 보았으므로 그 뒤의 말에 1.00을 주게 된다. 이것은 낯선 글에서의 정확성이 확인된 값이 아니다. 조금 덜 정확하더라도 셀 수 있는 쪽을 택하는 맞바꿈이 마르코프 가정이다. 앞을 길게 보면서도 셀 거리가 마르지 않게 하는 방법은 9주차 이후에 나온다.

Q4. 스무딩을 하면 못 본 말도 나올 수 있게 되니 문제가 해결된 것 아닌가?

정해 둔 후보 어휘 안에서 관찰하지 못한 짝에도 양수 확률을 줄 수 있다. 후보 어휘 밖의 말은 스무딩만으로 자동 추가되지 않으므로, 갈치조림 예시는 그 말을 후보에 넣었다고 가정한다. 그러나 작은 값이 큰 값을 이기지는 못한다. 갈치조림에 0.05를 줘도 고기국수의 0.47 앞에서는 여전히 잘 안 뽑힌다. 이는 후보가 낮은 확률을 받은 모의 표에서 일어나는 현상이다. 실제 자동완성의 오류 원인은 그 서비스의 자료·사전·설정을 확인하기 전에는 단정할 수 없다. 스무딩은 “아예 불가능”을 “아주 드묾”으로 바꿔 줄 뿐, 표에 제대로 실리지 않은 말을 제자리로 올려 주지는 않는다. 근본 처방은 그 말이 실제로 많이 들어 있는 글을 세는 것이다.

Q5. 고기국수는 전체에서 0.06인데 것은 다음에서는 왜 0.50인가?

확률을 계산하는 범위의 차이. 전체 32토큰 중 2번을 세면 약 0.06. 것은 다음 4자리 중 2번을 세면 0.50. 같은 등장 횟수라도 조건에 따라 분모가 달라지므로, 확률을 기록할 때 앞말과 세는 범위를 함께 적을 필요. 전체 빈도와 조건부 확률은 서로 모순되는 결과가 아님.

Q6. 퍼플렉서티가 낮으면 제주 안내문의 내용도 사실인가?

다음 토큰 예측 평가와 사실 검증의 구분. 같은 시험 조건에서 낮은 값은 실제 다음 토큰에 더 높은 확률을 주었다는 뜻. 안내문의 날짜·장소·이용 조건이 현실과 맞는지는 원문을 따로 확인. 예측이 좋다는 것만으로 사실성이나 운영 품질이 검증되지는 않음.

Q7. 두 문장에서 같은 짝을 두 번 세면 잘못 센 것인가?

발생 횟수와 종류 수의 구분. 혼저 옵서예가 두 문장에 한 번씩 나오면 발생 횟수 2회, 짝의 종류는 1가지. 이번 주의 조건부 확률은 발생 횟수로 계산. 중복을 제거했다면 확률이 바뀌므로 제거 기준의 기록 필요.

Q8. 자동완성이 제주 말을 바꾸면 말뭉치에 없다는 뜻인가?

출력만으로 학습 자료를 확정할 수 없음. 사전·사용자 설정·자르는 기준·후보 점수 등이 원인일 수 있음. 이 주차의 모의 바이그램에서는 표를 직접 볼 수 있으나 실제 서비스에서는 확인한 출력과 원인 가설을 구분해 기록.

  1. 바이그램이 제주에서 먹고 싶은 것은 ___ 의 다음 말을 고를 때, 실제로 보는 것은 앞의 네 단어 중 무엇인가? 그렇게 줄여 보는 약속의 이름은 무엇인가?
  2. 이번 주 말뭉치 여덟 문장에서 제주에서 다음에 오는 말의 확률표를 직접 세어 만들어라.
  3. 같은 말뭉치에서 싶은 다음에 것은 이 올 확률은 얼마인가? 그 값을 보고 이 표에 대해 무엇을 말할 수 있는가?
  4. 말뭉치에 한 번도 나오지 않은 것은 갈치조림 이라는 짝 때문에 제주에서 먹고 싶은 것은 갈치조림 이라는 문장 전체의 확률이 0이 된다. 갈치조림을 후보 어휘에 추가했다고 가정할 때, 왜 한 칸이 문장 전체를 막는가? 처방은 무엇인가?
  5. 같은 시험 글·같은 토큰화·같은 어휘로 평가한 표 두 장의 퍼플렉서티가 12와 45다. 어느 쪽이 실제 다음 말에 평균적으로 더 높은 확률을 주었는가?
정답 보기
  1. 것은 하나만 본다. 바이그램은 바로 앞 한 단어만 보기 때문에 제주에서, 먹고, 싶은 은 보지 않는다. 이렇게 앞의 몇 개만 보기로 하는 약속을 마르코프 가정이라고 한다.

  2. 먹고 0.67, 사고 0.33이다. 제주에서 는 문장 5, 7, 8에 세 번 나오고 그 뒤에 먹고가 두 번, 사고가 한 번 온다.

다음에 올 말횟수확률
먹고20.67
사고10.33
  1. 1.00이다. 싶은 은 문장 5, 6, 7, 8에서 네 번 나오는데 네 번 다 뒤에 것은 이 왔다. 4를 4로 나누면 1.00이다. 여기서 확인한 것은 이 네 자리에서 모두 같은 다음 말이 나왔다는 것이다. 싶은 뒤에 올 수 있는 말은 실제로는 훨씬 많은데 여덟 문장 안에서 하나도 못 봤을 뿐이다. 1.00만으로 자료가 잘못됐다거나 다음 말이 항상 같다고 결론 내릴 수 없다. 관찰 범위 밖에서도 성립하는지는 새 글로 확인해야 한다.

  2. 문장의 그럴듯함은 칸을 차례로 밟아 가는 길이기 때문이다. 다섯 칸이 튼튼해도 여섯 번째 칸이 비어 있으면 길이 그 자리에서 끊긴다. 처방은 스무딩이다. 0인 칸에 아주 작은 값을 주고 그만큼을 큰 칸들에서 조금씩 걷어, 표에 0을 남기지 않는다.

  3. 12인 쪽이다. 헷갈림 숫자는 낮을수록 좋다. 같은 시험 조건에서는 12인 모델이 실제 다음 토큰에 평균적으로 더 높은 확률을 부여한 것이다. 후보 수의 직관으로는 각각 열두 개, 마흔다섯 개의 동등한 후보를 놓고 고르는 것에 비유할 수 있지만, 모든 자리의 실제 후보 수가 같다는 뜻은 아니다.

용어한 줄 뜻
N-그램이웃한 조각을 N개씩 묶어 세는 방식. N이 앞을 얼마나 보는지를 정한다
유니그램한 개씩 세는 방식. 앞말을 보지 않는다
바이그램두 개씩 세는 방식. 바로 앞 한 단어를 보고 다음 말을 고른다
트라이그램세 개씩 세는 방식. 바로 앞 두 단어를 본다
조건부 확률앞말을 정해 놓고 그 자리만 모아 센 확률. 세는 범위를 좁힌 결과다
마르코프 가정앞의 모든 것을 보는 대신 바로 앞 몇 개만 보기로 하는 약속
문장 시작 표시문장이 어디서 시작하는지 세려고 앞에 붙이는 표시
문장 끝 표시문장이 어디서 끝나는지 세려고 뒤에 붙이는 표시. 생성을 멈추는 신호이기도 하다
스무딩한 번도 안 나온 칸에 아주 작은 값을 나눠 주어 표에 0을 남기지 않는 처방
퍼플렉서티시험 글의 실제 다음 토큰에 준 확률을 종합한 예측 평가값. 같은 평가 조건에서 낮을수록 좋다
말뭉치세는 대상이 되는 글 묶음. 2주차에서 다뤘다
확률적 문장 생성확률표를 보고 한 단어씩 뽑아 이어 붙여 문장을 만드는 일

주교재 3장 N-그램 언어모델. 공개본의 평가·스무딩 설명에서 같은 조건의 시험 자료 비교와 후보 어휘 처리 확인. 자세한 서지 정보와 공개본 주소는 강의계획서를 참고한다.

  • 교재의 예제는 영어이고 수식이 많다. 수식은 넘어가고 표와 그림만 찾아봐도 충분하다
  • 이번 주에 손으로 만든 표와 같은 모양의 표가 교재에도 나온다. 그것 하나만 찾아 비교해 봐도 좋다
  • 스무딩과 퍼플렉서티를 다루는 절은 계산이 나오는데, 이 과목에서는 목적과 읽는 법까지만 알면 된다

Open In Colab

여덟 문장으로 바이그램 표를 만들고 문장을 생성한다. TODO는 표를 들여다볼 앞말을 바꾸는 한 곳이다.