볼록함수의 쌍대

−log는 볼록함수다. 그리고 그 볼록성 하나에서 KL 발산(두 분포가 얼마나 다른지 재는 양, Σ p log(p/q))이 늘 0 이상이라는 사실이 나온다. 하지만 −log는 하나의 볼록함수일 뿐이다. 볼록함수 자체가 가진 구조를 이해하면, 훨씬 더 많은 것이 보인다.

젠센 부등식: 볼록한 그릇이 주는 부등식

“왜 구슬은 그릇 바닥을 찾는데 물결 모양 바닥에선 엉뚱한 데 멈추나?”

볼록 — 그릇의 기하학

그릇에 구슬을 놓으면 어디서 놓든 같은 바닥으로 굴러간다. 물결 모양 바닥에 놓으면 가장 가까운 골에 멈춘다. 그 골이 가장 깊은 골이라는 보장은 없다.

그릇의 성질을 식으로 쓰면 이렇다. 곡선 위의 두 점을 줄로 이으면, 줄은 곡선보다 아래로 내려가지 않는다.

f((1−t) a+t b)≤(1−t) f(a)+t f(b),0≤t≤1\textcolor{#5b6fe0}{f}\big((1-\textcolor{#007800}{t})\,\textcolor{#b078d8}{a} + \textcolor{#007800}{t}\,\textcolor{#b078d8}{b}\big) \le (1-\textcolor{#007800}{t})\,\textcolor{#5b6fe0}{f}(\textcolor{#b078d8}{a}) + \textcolor{#007800}{t}\,\textcolor{#5b6fe0}{f}(\textcolor{#b078d8}{b}), \qquad 0 \le \textcolor{#007800}{t} \le 1
f볼록함수a, b정의역의 두 점t두 점 사이의 보간 비율\begin{array}{ll} \textcolor{#5b6fe0}{f} & \text{볼록함수} \\ \textcolor{#b078d8}{a},\ \textcolor{#b078d8}{b} & \text{정의역의 두 점} \\ \textcolor{#007800}{t} & \text{두 점 사이의 보간 비율} \end{array}

왼쪽은 곡선의 높이, 오른쪽은 줄의 높이다. 두 번 미분할 수 있는 함수라면 f″(x) ≥ 0 과 같은 말이다. x², eˣ, −log x, x log x는 모두 이 시험을 통과한다. sin x나 x⁴ − 2x²는 통과하지 못한다. 줄이 곡선 아래로 파고드는 구간이 있다.

볼록이 주는 선물은 정확히 이것이다. 골짜기가 여러 개일 수 없다. 국소 최소는 곧 전역 최소다. 구슬이 멈춘 곳이 어디든, 그보다 낮은 곳은 없다.

"답이 하나"까지 말하려면 조건이 두 개 더 필요하다. 상수함수 f(x) = 0 도 볼록이다. 그런데 모든 점이 최소라서 답이 무수히 많다. eˣ 도 볼록이다. 그런데 왼쪽으로 한없이 내려가기만 하고 바닥이 없다. 엄격 볼록(줄이 두 끝점 말고는 곡선에 닿지 않음)이고 최솟값이 존재하면, 그때 최소점이 하나다.

직접 움직여 보기

불러오는 중…

파이썬

import numpy as np

funcs = {
    "x²":       lambda x: x**2,
    "eˣ":       np.exp,
    "x⁴":       lambda x: x**4,
    "sin x":    np.sin,
    "x⁴ − 2x²": lambda x: x**4 - 2 * x**2,
}

rng = np.random.default_rng(0)
a, b = rng.uniform(-2, 2, (2, 100_000))
t = rng.uniform(0, 1, 100_000)

for name, f in funcs.items():
    gap = (1 - t) * f(a) + t * f(b) - f((1 - t) * a + t * b)   # 줄 높이 − 곡선 높이
    print(f"{name:9s} 가장 작은 (줄 − 곡선) = {gap.min():+.4f}   볼록? {gap.min() >= -1e-12}")
# x²        가장 작은 (줄 − 곡선) = +0.0000   볼록? True
# eˣ        가장 작은 (줄 − 곡선) = +0.0000   볼록? True
# x⁴        가장 작은 (줄 − 곡선) = +0.0000   볼록? True
# sin x     가장 작은 (줄 − 곡선) = -0.5128   볼록? False
# x⁴ − 2x²  가장 작은 (줄 − 곡선) = -0.9980   볼록? False

sin x와 x⁴ − 2x²만 음수가 나온다. 줄이 곡선 아래로 들어간 쌍이 있다는 뜻이다.

젠센 부등식 — 평균의 함수 vs 함수의 평균

“왜 들쭉날쭉함은 볼록한 비용을 늘리는가?”

비유 — 누진세

세금 T(소득)은 볼록함수다. 많이 벌수록 한 단위에 붙는 세율이 오른다. 5천만 원까지는 10%, 그 위로는 30%라고 하자.

두 사람이 합쳐 1억을 번다. 반반 5천씩 벌면 세금은 500만 + 500만 = 1,000만 원. 2천과 8천으로 갈리면 200만 + 1,400만 = 1,600만 원. 총소득은 같은데, 고르지 않을수록 세금 총액이 커진다. “두 사람의 세금 평균(800만) ≥ 평균 소득의 세금(500만).” 이것이 젠센 부등식이다.

(「소득을 합쳐 한 사람 이름으로 내면 더 많이 낸다」는 말도 누진세에서 참이지만, 그것은 f(a + b) ≥ f(a) + f(b) 라는 다른 성질(초가법성)이다. 젠센은 합치기가 아니라 고르기에 대한 말이다.)

f(E[X])≤E[f(X)]\textcolor{#5b6fe0}{f}\big(\mathbb{E}[\textcolor{#6f8fa6}{X}]\big) \le \mathbb{E}\big[\textcolor{#5b6fe0}{f}(\textcolor{#6f8fa6}{X})\big]
f볼록함수X확률변수 (예: 한 사람의 소득)E[⋅]기댓값 (평균)\begin{array}{ll} \textcolor{#5b6fe0}{f} & \text{볼록함수} \\ \textcolor{#6f8fa6}{X} & \text{확률변수 (예: 한 사람의 소득)} \\ \mathbb{E}[\cdot] & \text{기댓값 (평균)} \end{array}

왜 성립하는가. X가 두 값만 가지면 이것은 앞에서 본 「줄이 곡선 위에 있다」 그 자체다. 값이 여럿이면 줄 대신 여러 점의 무게중심을 쓰면 된다. 무게중심도 곡선 위쪽(볼록한 쪽의 안)에 있다. 두 변의 차이를 젠센 갭이라 부른다. X가 한 값에 몰려 있으면 갭은 0이고, 퍼질수록 커진다.

f(x) = x²이면 갭이 바로 분산이다: E[X²] − (E[X])² = Var(X) ≥ 0. KL 발산이 늘 0 이상이라는 증명도 같은 부등식을 f = −log 에 적용한 것이다.

직접 움직여 보기

불러오는 중…

파이썬

import numpy as np

rng = np.random.default_rng(0)
X = rng.normal(3, 2, 1_000_000)          # 평균 3, 표준편차 2

for name, f in [("x²", lambda x: x**2), ("e^(x/4)", lambda x: np.exp(x / 4))]:
    print(f"{name:8s} f(E[X]) = {f(X.mean()):.4f}   E[f(X)] = {f(X).mean():.4f}   갭 = {f(X).mean() - f(X.mean()):.4f}")
print(f"분산 = {X.var():.4f}")
# x²       f(E[X]) = 9.0120   E[f(X)] = 13.0174   갭 = 4.0054
# e^(x/4)  f(E[X]) = 2.1181   E[f(X)] = 2.4004   갭 = 0.2824
# 분산 = 4.0054

x²의 갭이 분산과 소수 넷째 자리까지 같다(둘 다 4.0054, 표본을 뽑아 온 정규분포 자체의 분산인 모분산은 4).

ML에서: 평균을 먼저 내느냐, 나중에 내느냐

언어 모델을 평가하는 점수인 퍼플렉서티(perplexity)는 토큰 하나당 평균 손실(정답 토큰에 준 확률의 −log)에 exp 를 씌운 값이다. 모델이 다음 토큰을 평균 몇 개의 후보 가운데서 고르는 셈인지를 나타낸다. exp 도 볼록함수이므로, 문서마다 퍼플렉서티를 낸 뒤 평균하는 것과 손실을 먼저 평균한 뒤 exp 를 씌우는 것은 젠센 갭만큼 다르다. 얼마나, 어느 쪽으로 다른지는 아래 문제 3에서 직접 잰다.

수확

“볼록하면 골짜기가 하나다. 바닥이 한 점인지는 엄격 볼록과 최솟값의 존재가 정한다.”

“볼록성 하나가 부등식을 만든다. 정보이론의 부등식들이 여기서 나온다.”

문제 1. 왕복의 평균 속력

60 km 떨어진 도시까지 갈 때는 길이 막혀 시속 30 km 로, 돌아올 때는 시속 90 km 로 달렸다. (가) 왕복의 평균 속력은 얼마인가? (나) 1 km 를 가는 데 걸리는 시간 1/v 는 속력 v 의 볼록함수다. 두 구간의 「1 km당 걸린 시간」의 평균과, 시속 60 km 로 달렸을 때의 1 km당 시간을 견주어라. (다) 갈 때와 올 때 똑같이 한 시간씩 달렸다면 평균 속력은 얼마인가?

함께 풀기

김민준 M01
김민준

(30 + 90) / 2 = 60 이요. 시속 60 km 예요.

선생님 T01
선생님

갈 때는 몇 시간 걸렸어요?

김민준 M04
김민준

60 km 를 시속 30 으로 가니까 2시간, 올 때는 40분이에요. 120 km 를 2시간 40분에 달렸으니까… 시속 45 km 네요. 60 이 아니에요.

선생님 T01
선생님

그럼 60 은 무엇을 평균한 값이었을까요?

이서연 S01
이서연

속력을 평균한 값이요. 그런데 이 왕복은 거리를 똑같이 나눴으니까, 똑같은 비중으로 평균해야 하는 건 1 km당 걸린 시간이에요. 1/30 과 1/90 의 평균은 1/45 이고, 시속 60 km 의 1/60 보다 커요. 1/v 가 볼록이니까 「함수의 평균 ≥ 평균의 함수」, 젠센 부등식 그대로예요.

선생님 T01
선생님

(다)는요?

이서연 S07
이서연

한 시간씩이면 30 km 와 90 km 를 가서 120 km 를 2시간에 달린 거니까 시속 60 km 예요. 이번에는 속력의 산술평균이 맞아요. 거리를 똑같이 나눴는지 시간을 똑같이 나눴는지가 무엇을 평균할지를 정하네요.

이서연 S01
이서연

수학 시간에 조화평균은 산술평균보다 크지 않다고 배웠는데, 45 가 30 과 90 의 조화평균이에요. 그 부등식도 젠센의 한 경우였네요.

문제 2. 젠센 갭은 분산이다

f(x) = x² 에 대해 젠센 갭 E[X²] − (E[X])² 가 분산임을 보여라. 평균 3, 표준편차 2 인 정규분포에서 표본 10개를 뽑아 갭을 계산하고 분산과 비교하라. 갭이 0이 되는 것은 언제인가?

함께 풀기

김민준 M06
김민준

표본 10개로 갭이 2.1977 나왔어요. 그런데 np.var(X, ddof=1) 은 2.4419 예요. 안 맞아요. 젠센 갭이 분산이라는 게 근사인가요?

선생님 T01
선생님

민준 학생이 갭을 계산할 때 E는 무엇으로 계산했어요?

김민준 M01
김민준

np.mean 이요. 10으로 나눴어요.

선생님 T01
선생님

ddof=1 은 무엇으로 나눠요?

김민준 M01
김민준

9요. 아, 표본분산은 모분산을 추정하려고 n−1로 나누는 거고, 젠센 갭은 "이 10개 점의 분포"에 대한 기댓값이니까 n으로 나눠야 해요. np.var(X) 는 2.1977 로 딱 맞아요.

선생님 T01
선생님

젠센 부등식은 어떤 분포에도 성립해요. 표본 10개로 만든 경험분포도 그중 하나고요. 갭이 0이 되는 건요?

이서연 S01
이서연

x² 는 엄격 볼록이라 X가 한 값에 몰려 있을 때만이에요. 분산 0 이랑 같은 말이고요.

김민준 M01
김민준

조교가 성적 평균 낼 때 결석생까지 분모에 넣었는지 안 넣었는지로 반 전체가 헷갈렸던 적 있어요. 분모가 무엇을 세는지부터 확인해야 하는 거네요.

문제 3. 문서마다의 퍼플렉서티를 평균하면

언어 모델을 토큰 수가 같은 문서 다섯 개로 평가했다. 문서마다 토큰 하나당 평균 손실(정답 토큰에 준 확률의 −log, 단위 nat)은 1.5, 2.0, 2.0, 2.5, 3.0 이었다. 퍼플렉서티는 평균 손실에 exp 를 씌운 값이다. (가) 문서마다 퍼플렉서티를 구해 평균한 값과, 다섯 문서를 한 덩어리로 보고 구한 퍼플렉서티를 견주어라. (나) 둘 가운데 어느 쪽이 언제나 크거나 같은가? 어떤 볼록함수의 젠센 부등식인가? (다) 두 값이 같아지는 것은 언제인가? 위의 젠센 위젯에서 「문제 3 불러오기」를 누르면 다섯 문서의 손실이 점으로 올라온다.

함께 풀기

김민준 M01
김민준

돌려 봤어요. 문서마다 구하면 4.48, 7.39, 7.39, 12.18, 20.09 라서 평균이 10.31 이고, 한 덩어리로 보면 손실 평균이 2.2 니까 exp(2.2) = 9.03 이에요. 옆 팀은 10.31 로 보고했고 저는 9.03 이에요. 둘 중 하나는 버그죠?

선생님 T01
선생님

같은 모델, 같은 문서예요. 두 계산에서 달라진 건 무엇이에요?

김민준 M07
김민준

exp 를 씌운 순서요. 저는 손실을 먼저 평균하고 exp, 옆 팀은 exp 를 먼저 씌우고 평균했어요. exp 가 볼록이니까 f(E[X]) ≤ E[f(X)], 옆 팀 값이 언제나 크거나 같아요. 버그가 아니라 젠센 갭 1.28 이었어요.

김민준 M01
김민준

아까 왕복 속력 문제랑 같네요. 평균을 먼저 내느냐 나중에 내느냐로 값이 달라졌어요.

선생님 T01
선생님

(다)는요?

이서연 S01
이서연

exp 는 엄격 볼록이니까 다섯 문서의 손실이 모두 같을 때만이요. 그런데 이상한 게 있어요. 문서 길이가 다르면 「한 덩어리」 퍼플렉서티는 토큰 수로 무게를 매겨 손실을 평균해야 해요. 그러면 두 계산은 exp 의 순서만이 아니라 무엇에 같은 비중을 주는지도 달라져요.

선생님 T01
선생님

맞아요. 왕복 문제에서 거리에 같은 비중을 줄지 시간에 줄지가 답을 바꿨던 것과 같아요. 그래서 두 모델의 퍼플렉서티를 비교할 때는 둘이 같은 순서, 같은 비중으로 계산했는지부터 확인해야 해요.