Neural-LP와 DRUM: 연쇄 규칙 학습
📍 현재 위치: 4부 · 미분 가능 규칙 학습 — 12장. GPU 네이티브 NeSy는 알려진 규칙을 텐서 하드웨어 위의 행렬 곱 하나로 실행했습니다; 이 장은 규칙 자체의 선택을 경사 하강이 탐색하는 대상으로 만듦으로써 4부를 엽니다.
1권은 규칙 grandAdvisor(X,Z) ← advises(X,Y) ∧ advises(Y,Z)를 손으로 적어 두었고, 그 이후의 모든 장은 누군가 이미 알고 있던 규칙을 소비해 왔습니다. 앞 장은 일단 규칙이 고정되면 그것을 적용하는 일이 행렬 곱임을 보여 주었습니다. 이 장은 그 "고정"을 걷어 냅니다. Neural-LP는 각 추론 단계에서 어느 관계 행렬을 곱할지에 대해 학습 가능한 확률 분포를 두어, 연쇄 규칙 공간을 관통하는 이산 탐색을 경사가 하강할 수 있는 연속 최적화로 바꿉니다 [1]. 우리는 이를 학계 그래프의 grandAdvisor 질의로 학습시켜, 그것이 오직 데이터만으로 1권의 규칙을 재발견하는 모습을 지켜보고, 유일하게 정직한 방식으로 그 복구를 감사합니다: 규칙을 기호 문자열로 추출하여 부동소수점이 전혀 보이지 않는 방식으로 재생시키는 것입니다. 이 장의 후반부는 통제된 실패입니다. 두 개의 독립된 규칙을 필요로 하는 헤드는 우리가 세 줄로 증명할 수 있는 이유로 이 아키텍처를 패배시키며, DRUM의 저랭크 수리는 나란히 실행한 커밋된 비교 실행에서 이를 고칩니다 [2].
소리 내어 말하기를 거부하는 경로를 배달원에게 가르친다고 상상해 보십시오. 매 교차로에서 배달원은 가능한 회전들에 대한 작은 다이얼을 하나씩 지니고 있습니다: 왼쪽 60퍼센트, 직진 30퍼센트, 오른쪽 10퍼센트. 배달원은 매일 모든 경로의 혼합을 한 번에 운전하고, 당신은 최종 도착지만 채점하며, 그 채점이 모든 다이얼을 조금씩 밀어냅니다. 충분한 날이 지나면 다이얼은 포화됩니다: "지도 관계의 길을 따라간 다음, 그것을 다시 한번 따라가라"에 99.9퍼센트가 몰립니다. 이제 당신은 다이얼에서 경로를 문서화된 지시문으로 읽어 낼 수 있고, 다이얼이 전혀 없는 배달원에게 그것을 건네주어, 그가 학습이 채점했던 모든 도착지에 도달하는지 확인할 수 있습니다. 그 마지막 확인이 중요한 이유는, 정말로 서로 다른 두 경로 사이에서 갈팡질팡하는 다이얼이 만들어 내는 지시문은 둘의 흐릿한 평균일 뿐, 어느 쪽에도 맞지 않기 때문입니다. 이 갈팡질팡하는 다이얼이 이 장의 랭크-1 결함이고, 배달원에게 독립된 다이얼 세트 두 벌을 주는 것이 DRUM의 수리입니다.
이 장에서 다루는 내용
- 미분 가능 탐색이라는 틀: 규칙을 손으로 적거나, 이산적으로 채굴하거나, 경사로 학습하는 방법, 그리고 세 번째 방법이 지는 두 가지 위험 부담: 학습된 규칙은 데이터의 잡음을 물려받고, 추출된 규칙은 실제로 가중치의 동작 그 자체여야 합니다.
- 열거에서 주의로: 명시적 규칙 열거를 사용하는 TensorLog 점수, 모든 첨자의 해독, 그리고 주의가 상각해 내는 조합적 벽.
- 유도되고 풀어헤쳐진 Neural-LP 순환식: 11개 연산자에 대한 홉별 주의, 이전 상태들에 대한 메모리 주의, 그리고 모든 중간 벡터의 지지 집합을 하나하나 이름 붙인 T = 2 전체 손 계산 추적.
- 학습과 경사: 클램프된 음의 로그 가능도, 연쇄 법칙으로 하나하나 계산된 주의-행렬곱 사슬의 모든 경사, 유한차분 인증, 그리고 13.7083에서 0.0020까지 내려가는 커밋된 손실 추적.
- 감사로서의 추출: 학습된 주의를 몸체별 신뢰도 곱으로 풀어헤치기, 신뢰도 0.9990으로 추출되는 advises∘advises, 그리고 추출된 기호 규칙이 모든 학습 정답을 재현하고 홀드아웃된 사실로 전이됨을 확인하는 단언.
- 랭크-1 결함과 DRUM의 수리: 설계된 두 규칙 헤드, 랭크-1 주의가 독립된 규칙들을 반드시 뒤엉키게 함을 보이는 외적 증명, 유도된 손실 하한 4 ln 4 ≈ 5.5452와 측정된 5.5474의 대비, 그리고 두 규칙 각각을 0.999로 복구하는 랭크-2 실행.
- 규모와 한계: 실제 시스템이 추가하는 것(LSTM 컨트롤러, 귀납적 평가), 연쇄 규칙이 표현할 수 없는 것, 그리고 추출된 신뢰도가 왜 보정된 확률이 아닌지.
규칙은 누가 쓰는가?
세 가지 답이 있으며, 이 부가 존재하는 이유는 앞의 두 가지로는 충분하지 않기 때문입니다. 첫 번째는 사람입니다: 1권의 kb.py는 79행에서 grandAdvisor 규칙을 명시하며, 그로부터 도출되는 모든 것은 저자의 통찰만큼만 신뢰할 수 있습니다. 두 번째는 이산 탐색입니다: 규칙 채굴 전통(AMIE, 곧 Association Rule Mining under Incomplete Evidence; AnyBURL, 곧 Anytime Bottom-Up Rule Learning; 그리고 그 친척들)은 RNNLogic과 기호 기준선 AnyBURL에서 온전히 다루어집니다; 감사 가능하지만, 모든 후보 규칙이 개별적으로 채점되며 후보 공간은 규칙 길이에 따라 폭발적으로 커집니다. 세 번째가 이 장의 방식입니다: 규칙에 대한 이산 선택을 연속 선택으로 완화하여, 단 한 번의 경사 하강 실행이 전체 규칙 공간을 한꺼번에 탐색하도록 하고, 모든 규칙의 점수가 모든 학습 예시에 의해 동시에 갱신되도록 합니다 [1].
이 완화는 속도와 종단간 학습을 사 오지만, 이하의 모든 것을 틀 짓는 두 가지 대가를 치릅니다. 첫째, 학습된 규칙은 그것을 가르친 데이터만큼만 좋습니다: 학습 질의에 담긴 모든 편향과 공백을 소리 없이 물려받습니다. 둘째, 학습이 끝난 뒤 당신이 가진 것은 규칙이 아니라 주의 가중치의 텐서이며, 그로부터 이산 규칙을 읽어 내는 일은 거짓일 수 있는 해석 행위입니다. 모델에서 추출한 규칙은 그것을 기호적으로 적용했을 때 모델이 실제로 하는 일을 재현할 때에만 진술할 가치가 있습니다. 그래서 이 장은 추출 충실도(extraction fidelity)를 단언으로 지켜지는 일급 실험적 주장으로 다룹니다. 이러한 틀에서 DRUM의 기여는 충실도가 아키텍처의 랭크(rank) 성질이라는 발견입니다 [2]: 하나의 주의 사슬은 하나의 규칙에는 충실할 수 있지만, 증명 가능하게 두 규칙 모두에 충실할 수는 없습니다.
고정된 사슬에서 가중 중첩으로
앞 장의 기반을 다시 떠올려 봅시다. 학계 그래프의 5개 관계(about, advises, affiliated, authored, cites) 각각은 TensorLog 연산자가 됩니다: 크기 의 0/1 인접 행렬 이며, 여기서 13은 개체의 수이고, 사실 가 주장될 때 정확히 입니다 [3]. 이 장은 오직 15개의 학습 엣지만으로 연산자를 짓습니다(neural_lp.py 86–95행); 분할 자체(세 개의 엣지를 남겨 두는 것으로, 그중 (bob, advises, dave)는 끝의 전이 시험에 필요합니다)는 3권의 kg.py TEST 목록과 그 가드 단언들(65–74행)입니다. 질의 개체 는 원-핫 행벡터(one-hot row vector) 가 됩니다: 열두 개의 0과 의 색인 위치에 놓인 단 하나의 1로 이루어진 벡터이며, 을 오른쪽에 곱하면 질량이 을 따라 한 홉 밀려갑니다: 성분 는 모든 머리 개체 에 대해, 에 있는 질량에 에서 로 가는 -엣지의 지시자를 곱한 값을 합산합니다.
고정된 연쇄 규칙은 그러면 고정된 곱입니다: advises를 두 번 적용하는 것은 입니다. 규칙을 학습하기 위해, TensorLog 방식의 시스템은 먼저 질의 에 대한 정답 의 점수를 모든 후보 규칙 몸체에 대한 가중합으로 씁니다 [1]:
사용하기 전에 모든 기호를 해독합시다. 첨자 은 후보 규칙 몸체들에 걸쳐 있습니다: 각 은 하나의 가능한 연쇄를 이름 붙이고, 은 그 몸체의 관계 선택들을 순서대로 나열한 것이므로, 는 선택된 관계 행렬들을 몸체 순서대로 곱합니다("advises 다음에 advises"라는 몸체의 경우 advises∘advises로 표기하는데, 합성 기호 ∘는 "다음에"로 읽으며 커밋된 출력과 아래의 표들은 규칙 몸체를 이 형태로 출력합니다; 곱은 이고, 그것을 통과한 는 의 손자 지도 학생들 위에 놓입니다). 스칼라 은 몸체 의 학습 가능한 신뢰도로, 학습되는 유일한 양이며, 마지막 인자 은 모든 연쇄의 가중 중첩이 후보 정답 에 전달하는 질량을 읽어 냅니다(위 첨자 은 전치를 뜻하며, 원-핫 행을 열로 바꾸어 읽어 냄이 내적이 되게 합니다). 이제 규칙을 학습한다는 것은 벡터 를 학습한다는 뜻입니다: 규칙은 그 신뢰도가 클수록 더 많이 "존재"합니다.
이 공식은 스스로의 비용에 대해 정직합니다. 에 대한 합은 모든 몸체에 걸쳐 있으며, 몸체는 곱해지며 늘어납니다. 를 최대 규칙 길이(추론 홉의 수)로, 를 연산자 어휘의 크기로 두면, 길이가 정확히 인 몸체는 개 있습니다. 이 모듈의 어휘는 각 관계를 그 역(inverse) (전치 , 엣지를 거꾸로 가로지르는 홉)과 함께 두 배로 늘리는데, 이는 Neural-LP의 표준 준비 작업이며, 여기에 DRUM의 항등(identity) 연산자 (제자리에 머무는 홉으로, 규칙이 보다 실질적으로 짧아질 수 있게 함)를 더합니다. 그래서 두 헤드는 하나의 어휘를 공유하고, 우리의 작은 그래프는 이미 개의 연산자와 개의 두 홉짜리 몸체를 가집니다(neural_lp.py 98–107행). 실제 지식 베이스에서는 이 벽에 곧바로 부딪힙니다: 1,345개의 관계를 가진 벤치마크인 FB15K는 개의 연산자와 개의 세 홉짜리 몸체를 가지며, 각각에 하나씩을 담기에는 턱없이 많습니다 [1]. 이 문제를 다룰 만하게 만드는 수는 몸체를 매개변수화하는 일을 그만두고 홉을 매개변수화하는 일을 시작하는 것입니다.
Neural-LP 순환식: 연산자에 대한 주의, 메모리에 대한 주의
Neural-LP는 개의 몸체 신뢰도를 개의 연산자에 대한 주의 분포로 대체합니다: 홉 에서(이제부터 문자 는 꼬리 개체가 아니라 홉을 셉니다), 길이 의 소프트맥스 벡터 는 각 연산자가 그 홉에 얼마나 참여하는지를 말해 줍니다 [1]. 소프트맥스는 개의 원시 점수를 각각 지수화한 뒤 지수들의 합으로 나누어 분포로 바꾸므로, 개 항목은 모두 엄격히 양수이고 합은 1입니다. 3권의 어텐션 장이 그 미적분을 유도했으며, 그 야코비안은 아래 그래디언트 절에서 다시 등장합니다. 두 번째 장치는 길이가 다른 규칙들을 다룹니다: 모든 이전 상태들에 대한 소프트맥스 메모리 주의(memory attention) 가 각 홉이 무엇에 작용할지를 선택하므로, 나중의 홉이 지나간 중간 결과들을 넘어 원래 질의까지 되짚어 갈 수 있습니다. 하나의 순환식이 둘 다를 담습니다:
여기서 는 원-핫 질의 벡터이고, 는 홉 이후의 상태(개체들에 대한 부드러운 질량을 담은 길이 13 벡터)이며, 는 1부터 까지 돕니다. 안쪽에서부터 바깥쪽으로 읽어 봅시다: 메모리 주의 가 모든 이전 상태를 하나의 벡터로 섞고, 각 연산자 가 그 혼합을 한 홉 밀어내며, 연산자 주의 가 그렇게 밀려난 개의 결과를 섞습니다. 에 대한 마지막 읽기 주의가 예측 를 만들어 냅니다. 동반 모듈은 에서 정확히 이것을 구현합니다(neural_lp.py 183–202행):
def nlp_forward(params: dict, x_id: int) -> tuple[np.ndarray, dict]:
"""The T = 2 Neural-LP recurrence u_t = Σ_k a_t[k] · c_t M_k with
memory c_t = Σ_{τ<t} b_t[τ] u_τ, read out by p = Σ_τ b_3[τ] u_τ.
Returns (p, cache-for-backprop)."""
u0 = np.zeros(N_E)
u0[x_id] = 1.0
a1, a2 = softmax(params["A"][0]), softmax(params["A"][1])
b2, b3 = softmax(params["B2"]), softmax(params["B3"])
# Step 1 (memory attention over {u_0} is the constant 1): u_1 = Σ_k a_1[k] u_0 M_k.
Y1 = apply_ops(u0) # Y1[k] = u_0 M_k
u1 = a1 @ Y1
# Step 2 memory: c_2 = b_2[0] u_0 + b_2[1] u_1.
c2 = b2[0] * u0 + b2[1] * u1
# Step 2: u_2 = Σ_k a_2[k] c_2 M_k.
Y2 = apply_ops(c2) # Y2[k] = c_2 M_k
u2 = a2 @ Y2
# Final read: p = b_3[0] u_0 + b_3[1] u_1 + b_3[2] u_2.
p = b3[0] * u0 + b3[1] * u1 + b3[2] * u2
return p, {"u0": u0, "u1": u1, "u2": u2, "c2": c2, "Y1": Y1, "Y2": Y2,
"a1": a1, "a2": a2, "b2": b2, "b3": b3}
메모리가 사 오는 것을 눈여겨봅시다. 읽기 이 에 집중되면, 헤드는 한 홉짜리 규칙으로 답합니다; 홉 2의 메모리 가 에 집중되면, 홉 2는 질의에 직접 작용하고 는 또 다른 한 홉짜리 규칙을 담습니다; 그리고 항등 연산자는 홉을 아무 일도 하지 않는 것으로 만듦으로써 짧은 규칙에 이르는 세 번째 경로를 제공합니다. 가변 길이의 연쇄 규칙들은 모두 하나의 고정 깊이 계산 안에 살고 있으며, 이는 단 하나의 경사가 그 모두에 닿을 수 있게 합니다.
학습에 들어가기 전 정직함을 위한 메모 하나를, 모듈 헤더(neural_lp.py 34–44행)에서 그대로 인용합니다. 이 축소판이 의도적으로 빼놓은 구성 요소들이 발표된 시스템에는 있기 때문입니다. 인용문이 말하는 LSTM(long short-term memory)과 BiLSTM(bidirectional LSTM) 제어기는 완전한 시스템에서 어텐션을 방출하는 순환 신경망이고, 로짓 표(logit table)는 소프트맥스를 적용하기 전의 원시 점수 표입니다:
Simplifications vs the real systems, stated plainly: the LSTM / BiLSTM
controllers that emit the attentions are replaced by directly-learned logit
tables (each head here trains a single query relation, so the controller's
output collapses to one table per hop — the recurrence would be dead
weight); operators are dense 13x13 NumPy arrays, not sparse tensors; plain
gradient descent replaces Adam with gradient clipping; T = 2 with no
validation split or early stopping; DRUM's per-step L1 normalization is
omitted (softmax attentions already keep mass bounded on this graph). All
gradients are hand-derived (softmax Jacobian + chain rule through the
matmuls, in comments above the code) and verified against central finite
differences inside run().
전체 시스템에서는 하나의 순환 컨트롤러 신경망이 와 를 질의 관계의 함수로 방출하므로, 하나의 매개변수 집합이 모든 관계를 한꺼번에 섬깁니다 [1]. 여기서는 각 헤드가 하나의 질의 관계만 학습하므로, 어차피 컨트롤러의 출력은 홉마다 상수가 될 것입니다; 이 모듈은 그 상수들을 로짓 테이블 , , 로 직접 학습하며(neural_lp.py 173–181행), 그 사실을 그대로 밝힙니다.
이제 앞서 약속했던 손 계산 풀어헤치기를, 학습이 다가감을 곧 보여 줄 매개변수 설정에서 해 봅시다: 과 는 advises에 원-핫이고, (홉 2가 을 읽음), (읽기가 를 취함)입니다. 학습 그래프의 advises 엣지는 alice→bob, bob→carol, carol→erin입니다. 질의 alice: 는 alice에 원-핫입니다; 홉 1은 를 주고, 그 지지 집합은 정확히 alice가 지도하는 사람들의 집합이므로 은 bob에 원-핫입니다; 메모리 혼합은 입니다; 홉 2는 를 주고, carol에 원-핫입니다; 읽기는 를 반환하며, 모든 질량이 carol 위에 있습니다. 질의 bob은 bob→carol→erin을 거치는 같은 배선을 거쳐 모든 질량을 erin 위에 놓습니다. 이것들이 바로 학습 목표이며, 1권의 전방 연쇄기(least_fixpoint, forward_chain.py 52–63행)를 그대로 불러와 같은 15개의 학습 엣지에 대해 도출된 grandAdvisor 원자들을 모아 계산됩니다(neural_lp.py 124–144행): alice → carol, bob → erin. 이산 규칙은 연속 매개변수 공간의 한 모서리이며, 문제는 경사 하강이 그곳으로 걸어가는지입니다.
Neural-LP는 규칙의 선택을 미분 가능하게 만들고(왼쪽), 추출은 충실도를 증명하기 위해 학습된 규칙을 기호적으로 재생하며(가운데), 두 규칙 헤드는 랭크-1 주의가 뒤엉키게 하는 것을 DRUM의 랭크-2 합이 분리해 냄을 보여 준다(오른쪽).
저자가 AI의 도움을 받아 직접 제작한 원본 도해.
순환식을 통한 학습: 손실, 경사, 추적
각 학습 질의는 하나의 쌍입니다: 질의 개체 와 다중-핫 목표 벡터(target vector) 이며, 모든 참 정답 에 대해 입니다(이 는 후보 정답들로 색인된 벡터이지, 순환식의 홉 색인이 아닙니다). 손실은 원래 시스템이 사용하는 음의 로그 가능도이며 [1], 방어적 클램프가 하나 붙어 있습니다(neural_lp.py 161–168행):
이 클램프는 일반적인 경우를 지키는 것이지, 이번 실행을 지키는 것이 아닙니다. 질의 로부터 질량을 실어 나르는 경로가 전혀 없는 목표 개체는 정확히 0의 질량을 받게 될 것입니다; 은 음의 무한대이고, 도함수 (기호 는 편미분을 표시합니다: 다른 모든 변수를 고정한 채 한 변수에 대해서만 재는 변화율)는 와 같으며 발산합니다. 클램핑은 손실을 유한하게 유지하며, 이 에 대해 상수이므로, 그곳에서의 경사는 0으로 정의됩니다: 평평한 영역은 아무것도 기여하지 않으며, 학습은 이미 어느 정도의 질량을 받고 있는 정답들에 의해 이끌립니다. 이 그래프에서는 클램프가 결코 발동하지 않습니다: 소프트맥스 성분들은 엄격히 양수이고, 모든 학습 목표는 advises 홉들을 통해 도달 가능하며, 아래의 유한한 첫 에포크 손실 13.7083이 이를 확인해 주는데, 클램프된 목표 하나만으로도 그 자체로 손실에 을 기여할 것이고, 13.71이라는 총합에는 그럴 여지가 전혀 없기 때문입니다. 클램프되지 않은 영역을 항별로 미분하면 이며, 이는 정확히 _nll_grad가 반환하는 값입니다.
로부터 연쇄 법칙이 순전파를 거꾸로 걸어 올라가며, 모든 단계는 두 가지 패턴 중 하나입니다. 선형 혼합은 즉각적입니다: 로부터, 읽기 가중치들은 를 얻습니다(를 로 씁니다; 를 조금 밀면 가 에 더해지므로, 손실은 의 비율로 움직입니다), 그리고 각 상태는 자기 몫을 받는데, 예를 들어 입니다. 주의가 걸린 행렬곱은 좌표로 한 번 유도해 볼 가치가 있는 패턴입니다. 홉 2는 의 성분 를 계산합니다(첨자 와 는 둘 다 13개의 개체에 걸쳐 있으며, 는 의 성분을, 는 의 성분을 가리킵니다): . 하나의 주의 가중치에 대해 미분해 봅시다:
이는 상류 경사와 번째로 밀려난 이미지의 내적이며, 순전파가 이미 로 캐시해 둔 것입니다. 이번에는 밀려나는 상태에 대해 미분해 봅시다:
이는 각 연산자의 전치를 통해 경사를 거꾸로 잡아당기는 주의-가중 인출입니다(apply_ops_T, neural_lp.py 116–119행). 하나의 상태가 두 개의 소비자를 가지면 그 경사들은 더해집니다: 은 읽기와 홉 2의 메모리 둘 다에 공급되므로, 입니다(neural_lp.py 224–228행). 마지막으로 모든 주의 벡터는 자신의 로짓에 대한 소프트맥스이며(로짓 벡터를 로, 그 출력을 로 씁니다), 어텐션: 관련성에 의한 추론은 3권에서 소프트맥스 야코비 를 유도했습니다; 이를 상류 경사 와 축약하면 다음을 얻습니다
여기서 는 크로네커 델타로, 일 때 1이고 그 외에는 0이므로, 첫 번째 합은 항 하나만 남깁니다. 그 닫힌 형태가 softmax_grad이고(neural_lp.py 155–159행), 전체 역전파는 nlp_loss_and_grads입니다(204–236행), 각 유도는 해당 코드 줄 위의 주석에서 그대로 반복됩니다. 이 가운데 어느 것도 그냥 믿고 넘어가지 않습니다. 쌓인 학습 가능한 로짓들(테이블 , , )을 로, 유한차분의 작은 탐침 간격을 로 쓰면(코드에서 이 매개변수는 문자 그대로 eps라는 이름이 붙어 있으며, 으로 설정됩니다): grad_check(417–436행)는 손으로 유도한 모든 경사 성분을 중심 유한차분 과 비교하며, 커밋된 실행은 다음을 인증합니다
[2] hand-derived gradients vs central finite differences (max rel err)
Neural-LP head : 2.22e-09
DRUM head : rank-1 2.45e-09, rank-2 6.31e-09
학습은 순수한 경사 하강입니다, 이며, 여기서 는 학습률(learning rate), 곧 매 갱신의 걸음 크기로, 두 grandAdvisor 질의에 대해 3000 에포크 동안 로 설정됩니다(train_head, 238–251행). 커밋된 추적은 다음과 같습니다:
[3] Neural LP on grandAdvisor(X, ?) — loss L = −Σ_y t_y log max(p_y, 1e-20)
epoch : loss
1 : 13.7083
10 : 0.8102
100 : 0.0617
1000 : 0.0060
3000 : 0.0020
손실은 거의 7000분의 1로 떨어지며, 이 모듈의 역량 검사는 더 나아간 어떤 주장이든 허용되기 전에 적어도 20배의 감소를 요구합니다(neural_lp.py 489–490행). 하지만 낮은 손실은 오직 연속 모델이 잘 답한다는 것만을 말해 줄 뿐입니다. 그것이 아직 모델이 규칙을 학습했다고 말해 주지는 않습니다.
규칙 추출은 감사다
추출 절차는 순환식을 뒤집습니다. 계산 그래프를 지나는 모든 경로는 하나의 연쇄 규칙이며, 그 신뢰도는 그 경로가 사용한 주의 가중치들의 곱입니다 [1]. 에서는 정확히 네 가지 경로 모양이 있습니다(extract_rules, neural_lp.py 256–283행):
| 읽기가 취하는 것 | 홉-2 메모리 | 규칙의 몸체 | 신뢰도 |
|---|---|---|---|
| — | , 항등 사슬 | ||
| — | |||
항등 연산자는 몸체에서 삭제되며(그것들은 건너뛰기이기 때문입니다), 동일한 몸체는 신뢰도를 합산하여 병합되고, 임계값 0.01 아래의 것은 모두 가지치기됩니다. 커밋된 실행은 정확히 하나의 규칙을 추출합니다:
[4] extracted rules (attention products ≥ 0.01, Algorithm 1)
0.9990 grandAdvisor(X,Y) ← advises(X,Z1) ∧ advises(Z1,Y)
[5] symbolic replay of the top rule (no floats — set-valued hops)
over TRAIN edges : alice → ['carol'], bob → ['erin'] == every training answer
over FULL graph : alice → ['carol', 'dave'], bob → ['erin'] == Volume 1's closure, incl. the transfer pair (alice, dave)
1권의 규칙이 신뢰도 0.9990으로 데이터로부터 복구되었고, 임계값을 통과하는 경쟁자는 없습니다. 블록 [5]의 재생이 바로 이 주장을 정직하게 만드는 부분입니다. apply_rule(neural_lp.py 296–313행)은 추출된 몸체를 순수한 집합값 홉들로 적용하며, 어디에도 부동소수점이 없고, 하니스는 두 가지를 단언합니다(497–502행). 첫째, 충실도(fidelity): 학습 엣지들 위에서, 기호적 규칙은 모든 학습 정답을 정확히 재현하며, 그래서 추출된 기호들은 정말로 학습된 가중치의 동작이지, 그것에 관해 지어낸 이야기가 아닙니다. 둘째, 전이(transfer): 전체 18개 엣지 그래프 위에서, 이 규칙은 어떤 학습 목표에도 담기지 않았던 사실인 grandAdvisor(alice, dave)를 도출하는데, 이는 홀드아웃된 엣지 (bob, advises, dave)가 필요하기 때문입니다. 이 규칙이 일반화되는 것은 규칙이 항상 그러하는 구조적인 이유 때문입니다: 그것은 개체가 아니라 관계를 언급하므로, 새로운 엣지는 공짜로 그것을 통과해 흐릅니다. 이것이 이 계열이 3권의 임베딩 모델들에 맞서 주장하는 귀납적 이점이며, 그 모델들은 자신이 결코 임베딩하지 않은 개체나 엣지에 대해서는 아무 말도 할 수 없습니다 [2].
일반 원리를 명료하게 진술해 둡시다, 다음 절이 바로 그것의 위반이기 때문입니다. 추출은 몸체 의 신뢰도를 홉별 주의들의 곱으로부터 읽어 내므로, 이는 모델의 동작이 실제로 그렇게 인수분해될 때에만 의미가 있습니다: 즉 연속 모델이 규칙 공간에 대해 (거의) 저랭크이고, 그 질량이 홉별 곱으로 표현될 수 있을 만큼 충분히 적은 규칙에 집중되어 있을 때입니다. 규칙이 하나인 경우가 우호적인 경우입니다: 주의가 포화되고, 곱은 하나의 몸체 위에서 거의 1이 되며, 충실도가 성립합니다. 규칙이 둘인 경우가 그 인수분해가 무너지는 지점입니다.
랭크-1 결함, 인용이 아니라 실행됨
동반 모듈은 실패를 설명하는 대신 직접 설계합니다. 새로운 헤드 acadLine은 지도 사슬을 따라 두 개의 독립적이고 깨끗한 연쇄 규칙을 갖도록 정의됩니다: 당신의 학맥은 당신의 조부 지도교수이거나 당신의 손자 제자입니다(neural_lp.py 318–323행):
# The designed head with TWO independent clean chain rules over the graph:
# acadLine(X,Y) ← advises(X,Z) ∧ advises(Z,Y) (two hops DOWN)
# acadLine(X,Y) ← inv_advises(X,Z) ∧ inv_advises(Z,Y) (two hops UP)
# i.e. grand-advisor OR grand-student along the advising chain.
DRUM_BODIES: tuple[tuple[str, ...], ...] = (("advises", "advises"),
("inv_advises", "inv_advises"))
목표는 이 두 몸체를 학습 엣지들에 대해 기호적으로 적용한 것입니다: alice → carol와 bob → erin(사슬을 따라 내려감), carol → alice와 erin → bob(사슬을 따라 올라감). 이제 하나의 단일 주의 사슬이 무엇을 표현할 수 있는지 생각해 봅시다. advises 연산자를 로, 그 역인 inv_advises를 로 쓰고(막대는 반대 방향을 표시하며, 기호 는 항등 연산자를 위해 남겨 둡니다), 추출이 두 홉짜리 몸체 에 부여하는 신뢰도를 로 줄여 씁니다. 연산자 쌍에 대한 행렬로 보면, 는 외적(outer product)이고, 따라서 랭크-1(rank-1) 행렬이며, 랭크-1 행렬은 대각 성분 두 개가 크면서 비대각 성분은 작은 그런 형태를 가질 수 없습니다. 그 증명은 교차항에 대한 대수 한 줄입니다:
같은 네 개의 인자를 재배열한 것뿐입니다. 그러므로 만약 참인 두 규칙 모두가 적어도 의 신뢰도를 갖는다면, 두 개의 가짜 교차 몸체는 를 만족하며, 그중 적어도 하나도 적어도 의 신뢰도를 갖게 됩니다. 두 규칙을 모두 원하는 것은 그 교차곱들도 원하도록 강제합니다. 이것이 DRUM의 정리 1(Theorem 1)의 축소판으로, 랭크-1 정식화가 교차항에 높은 신뢰도를 부여하지 않고서는 두 개의 독립된 연쇄 규칙을 표현할 수 없음을 증명합니다 [2].
이 강제된 타협은 닫힌 형태로도 계산할 수 있습니다. alice로부터는 오직 몸체 만이 목표 carol에 도달하고, carol로부터는 오직 만이 alice에 도달하므로, 주의 질량이 와 사이에서만 나뉜다고 하면(다른 어떤 연산자에 실린 질량도 어떤 목표에도 도달하지 못하고 손실만 높일 뿐입니다), 네 개의 단일 정답 질의에 대한 손실은 입니다. 제약 를 대입하고 미분을 0으로 두면: 이므로 이고, 에 대해서도 마찬가지입니다. 최적점은 50 대 50 헤지 이며, 모든 몸체가 신뢰도 를 가지고, 손실 하한은 다음과 같습니다
커밋된 실행은 그 하한으로부터 0.0022 이내(그 위로 0.04퍼센트)에 도달하며, 예측된 모든 증상을 그대로 드러냅니다:
loss rank-1 rank-2
1 19.4184 16.4044
10 6.4025 1.6764
100 5.6128 0.0774
1000 5.5517 0.0073
3000 5.5474 0.0024
learned 2-hop chains (confidence = product of hop attentions):
rank-1 term 0: advises∘advises 0.250 | advises∘inv_advises 0.250 | inv_advises∘advises 0.250 | inv_advises∘inv_advises 0.250
rank-2 term 0: inv_advises∘inv_advises 0.999
rank-2 term 1: advises∘advises 0.999
랭크-1 손실은 유도된 5.5452에 맞서 5.5474에서 정체되는 반면 랭크-2 손실은 0.0024까지 떨어지며, 랭크-1 추출은 정확히 그 뒤엉킨 사중항입니다: 네 개의 몸체가 각각 0.250이고, 그중 둘은 아무도 요청하지 않은 규칙입니다. 이 가짜 몸체들은 무해한 잡음이 아닙니다. bob으로부터, advises∘inv_advises는 bob → carol → bob을 거치고(carol의 지도교수는 bob입니다) inv_advises∘advises는 bob → alice → bob을 거칩니다: 두 교차 사슬 모두 질의 개체 자신으로 되돌아 도는데, 그 신뢰도들이 더해지면서 랭크-1 모델은 bob이 자기 자신의 학맥이라는 데에 질량 0.50을 두는 반면 참인 정답 erin에는 겨우 0.25만을 둡니다. 커밋된 질의별 표는 이 측정된 피해를 보여 주며, 별표는 기호적 진리와 다른 모든 예측된 정답 집합을 표시합니다:
query symbolic rank-1 p(ans) rank-2 p(ans)
alice ['carol'] alice:0.25 carol:0.25* carol:1.00
bob ['erin'] bob:0.50 erin:0.25* erin:1.00
carol ['alice'] alice:0.25 carol:0.50* alice:1.00
erin ['bob'] bob:0.25 erin:0.25* bob:1.00
mean p(true answers): rank-1 0.2499 rank-2 0.9994
exact answer sets : rank-1 0/4 rank-2 4/4
하니스는 이것을 그저 눈으로 훑어보는 데 그치지 않습니다: bob에 대해 랭크-1의 가짜 자기-정답이 참인 정답보다 더 높은 점수를 받는다는 것을 단언하고(neural_lp.py 513–516행), 랭크-1이 랭크-2의 단언된 네 개보다 엄격하게 더 적은 정확한 정답 집합에서만 이긴다는 것을(출력된 실행은 0을 보여 줍니다), 그리고 평균 목표 질량과 최종 손실의 격차가 미미한 것이 아니라 크다는 것을 단언합니다(503–511행).
DRUM: 저랭크 수리
DRUM의 추정기는 메모리 주의를 없애고(항등 연산자 하나만으로도 더 짧은 규칙들을 커버합니다) 그 대신 개의 독립적인 주의 사슬을 실행하여 그 결과들을 합산합니다; 여기서 문자 은 랭크(rank), 곧 사슬의 개수입니다 [2]:
여기서 는 사슬 의 홉 에서의 연산자 주의입니다. 하나의 사슬에 대해 합의 곱을 펼치면, 입니다: 사슬 는 몸체 에 신뢰도 를 부여하는데, 이는 개 벡터의 외적이며, 따라서 이 헤드의 전체 신뢰도 텐서는 개 외적들의 합입니다. 이것이 정확히 CP 분해(CP decomposition, canonical polyadic: 텐서를 랭크-1 텐서들의 합으로 쓰는 것)이며, DRUM의 정리 2(Theorem 2)는 이에 대응하는 충분성 결과입니다: 충분한 랭크만 있으면, 이 추정기는 유한한 가중 연쇄 규칙 집합 어느 것이든 표현할 수 있으며, 각 랭크 항은 자유롭게 하나의 규칙에 특화될 수 있습니다 [2]. 랭크 1은 정확히 위의 뒤엉킨 형태를 복원하는데, 이 때문에 이 모듈은 랭크 매개변수 하나로 둘 다를 하나의 함수로 구현합니다(drum_forward, neural_lp.py 344–365행):
rank = params["A"].shape[0]
u0 = np.zeros(N_E)
u0[x_id] = 1.0
a = np.stack([[softmax(params["A"][j, t]) for t in range(T)]
for j in range(rank)]) # (L, T, K) attentions
us = np.zeros((rank, T + 1, N_E)) # u[j, t] = state of chain j
ys = np.zeros((rank, T, K, N_E)) # ys[j, t, k] = u[j,t] M_k
us[:, 0] = u0
for j in range(rank):
for t in range(T):
# u_{j,t+1} = Σ_k a_{j,t}[k] · u_{j,t} M_k
ys[j, t] = apply_ops(us[j, t])
us[j, t + 1] = a[j, t] @ ys[j, t]
# p = Σ_j u_{j,T} (the CP sum over the L rank terms)
p = us[:, T].sum(axis=0)
역전파는 새로운 것을 더하지 않습니다: CP 합은 손실 경사 (여기서 은 다시 손실을 뜻하며, 랭크가 아닙니다)를 변형 없이 모든 사슬로 그대로 보내고, 각 사슬은 위에서 유도한 주의-행렬곱 경사들을 그대로 재사용합니다(drum_loss_and_grads, 367–388행; 유한차분으로 6.31e-09까지 검증됨). 초기화의 세부 사항 하나가 중요한데, 코드도 이를 주석으로 밝힙니다(337–342행): 랭크 항들은 작은 무작위 로짓에서 시작하는데, 만약 동일한 시작점에서 출발한다면 영원히 동일한 경사를 받아 결코 특화되지 못하기 때문입니다. 같은 질의들에 대해 랭크 2로 학습시키면, 위의 커밋된 실행은 깔끔한 분리를 보여 줍니다: 항 0은 inv_advises∘inv_advises에 0.999로 포화되고, 항 1은 advises∘advises에 0.999로 포화되며, 하니스는 네 개의 랭크-2 정답 집합 모두가 기호적 정답과 같음을 단언합니다(503–505행). 하나의 아키텍처, 하나의 하이퍼파라미터 변경, 그리고 요약 줄이 이 전시를 압축합니다:
SUMMARY neural_lp: top_rule=advises∘advises conf=0.9990 nlp_loss=0.0020 drum_p_tgt_r1=0.2499 drum_p_tgt_r2=0.9994 exact_r1=0/4 exact_r2=4/4
| 지표 (커밋된 실행) | 랭크-1 | 랭크-2 |
|---|---|---|
| 최종 손실 (에포크 3000) | 5.5474 (하한 ) | 0.0024 |
| 학습된 사슬 ≥ 0.01 | 네 개의 몸체, 각 0.250(그중 둘은 가짜) | advises∘advises 0.999; inv_advises∘inv_advises 0.999 |
| 참 정답에 실린 평균 질량 | 0.2499 | 0.9994 |
| 정확한 정답 집합 | 0/4 | 4/4 |
| 최악의 증상 | bob의 가짜 자기-정답 0.50이 erin의 0.25보다 높은 점수를 받음 | 단언된 것 없음 |
규모, 컨트롤러, 그리고 사슬이 말할 수 없는 것
실제 시스템들은 이 골격을 세 방향으로 확장합니다. 첫째, 컨트롤러입니다: Neural-LP는 질의에 조건화된 LSTM(long short-term memory network, 장단기 기억망)으로부터 주의를 방출하고, DRUM은 랭크마다 하나씩 양방향 LSTM을 사용하여, 하나의 학습된 모델이 모든 질의 관계를 섬기고 홉별 주의들이 사슬을 따라 협응할 수 있게 합니다 [1][2]. 둘째, 연산자 어휘입니다: 역관계로 인한 두 배 늘림은 유지되고, 연산자들은 희소 텐서가 되어, 한 홉의 비용이 개체 수의 제곱이 아니라 엣지 수에 비례하게 됩니다(앞 장의 희소성 경제학입니다). 셋째, 가장 중요한 것으로, 평가 체제입니다: 학습된 연쇄 규칙은 어떤 개체도 언급하지 않으므로, 이 모델들은 태생적으로 귀납적(inductive)이며, DRUM의 평가는 학습에 전혀 등장하지 않은 개체들로 이루어진 그래프에서 시험함으로써 이 대비를 명시적으로 만드는데, 그런 그래프에서도 규칙은 계속 작동하지만 개체-임베딩 방법들은 찾아볼 벡터가 아예 없습니다 [2]. 3권은 정확히 이 어휘-의존성 비판으로 끝을 맺었습니다; 미분 가능 규칙 학습은 그것에 구조 자체로 답합니다.
이 계열의 정직한 한계는 그 사슬 편향(chain bias)입니다. 위의 모든 것은 형태의 몸체, 곧 에서 까지 새로운 변수들로 이루어진 단일 경로만을 탐색합니다. 몸체가 가지를 치거나(나무 구조: "X와 Y가 함께 논문을 썼고 같은 기관을 공유한다"), 형제 개체들을 비교하거나, 순환을 닫는 규칙들은 랭크가 무엇이든 탐색 공간 바깥에 있는데, 어떤 행렬 곱의 나열도 그것들을 표현할 수 없기 때문입니다. 그 공간을 넓히는 일은 그 자체로 하나의 연구 노선입니다: 템플릿 기반 전통의 대표주자인 ∂ILP(differentiable inductive logic programming, 미분 가능 귀납 논리 프로그래밍)는 사슬이 아니라 일반적인 절 템플릿의 인스턴스화들에 대해 소프트 가중치를 할당하여, 가지 치는 몸체와 재귀를 사들이는 대신 훨씬 더 작은 영역에 국한되는 메모리 비용을 치르며 [4], 다음 장들의 증명기들과 확률적 논리 학습기들(NTP, 곧 Neural Theorem Prover의 미분 가능한 역방향 연쇄; RNNLogic의 생성기-추론기 분리)은 다른 각도에서 같은 공간을 공략합니다. 사슬은 규칙 학습의 끝이 아니라, 행렬 곱 안에 들어맞는 그 부분일 뿐입니다.
해결되지 않은 부분
소프트맥스는 결코 정확한 원-핫 벡터를 출력하지 않습니다: 그 성분들은 지수함수들의 비율이며 모두 엄격히 양수이므로, 학습된 주의는 언제나 모든 연산자에 걸쳐 어느 정도의 질량을 퍼뜨립니다. 0.9990이라는 추출값은 그 흐림을 반올림해 없앤 것이며, 이 그래프에서는 그 흐림이 무해합니다. 하지만 일반적인 간극은 실재하며 해결되지 않았습니다. 질의에 답하는 연속 모델은 흐림을 포함한 완전한 중첩이고, 추출된 규칙 집합은 그것에 임계값을 적용한 초상입니다. 그 둘 사이에는 충실도 간극(fidelity gap)이 놓여 있는데, 이는 측정될 뿐 결코 보장되지 않습니다: 우리의 재생 단언은 하나의 그래프에 대한 경험적 점검이지 정리가 아니며, 규모가 커지면 논문들은 모델의 순위 지표를 보고하고 추출된 규칙은 검토용으로 제시할 뿐입니다; 그 둘은 일치할 필요가 없고, 어떤 것도 비교를 강제하지 않습니다. 둘이 갈라질 때, 어느 쪽이 "모델이 학습한 것"입니까? 5권은 신뢰성(faithfulness)을 표제 기준으로 삼아, 학습된 시스템으로부터의 규칙 추출이라는 바로 이 질문으로 돌아옵니다. 더 조용한 문제 하나가 함께 딸려 옵니다: 신뢰도 0.9990은 주의의 곱, 곧 소프트맥스 기하가 만들어 내는 양이지, 그 규칙이 세상에서 그만한 빈도로 성립한다는 보정된 확률이 아닙니다. 추출된 신뢰도를 확률로 취급하는 것은 허가받지 않은 일이며, 그런 해석을 허가하거나 반박할 보정 도구들 또한 5권의 몫입니다.
왜 중요한가
이 장은 이 시리즈가 첫 권에서 열어 두었던 고리를 닫습니다. 1권은 규칙을 손으로 적어 실행했습니다; 3권은 표현을 학습했지만 그것이 무엇을 의미하는지는 말할 수 없었습니다; 여기서 처음으로, 학습이 기호를 만들어 냅니다: 인쇄되고, 검사되고, 논쟁되고, 경사에 대해 들어 본 적조차 없는 추론기에 의해 실행될 수 있는 규칙입니다. 가중치에서 기호로, 그리고 검증된 행동으로 이어지는 그 왕복이 바로 신경-기호적 약속의 가장 구체적인 형태이며, 여기서의 그 규율(추출된 것이 재생되고, 충실도가 단언되고, 실패가 설계된 사례 위에서 그대로 드러남)이 5권의 모든 해석 가능성 주장이 지켜야 할 기준입니다. 랭크 분석은 그 자체로 일반적인 교훈을 가지고 있습니다: 연속적인 완화가 자신의 이산적인 해석을 정말로 의미한다고 신뢰할 수 있는지는 훈련 운의 문제가 아니라, 아키텍처의 표현력에 관한 성질이며, 양쪽 방향 모두로 증명 가능합니다. 그리고 학습된 규칙이 본 적 없는 개체와 엣지로 전이되는 귀납적 행동은, 앞으로 이어질 복합 질의 및 파운데이션 모델 장들이 계속 되짚어 오는 그 역량입니다.
핵심 용어
- TensorLog 연산자(TensorLog operator) — 관계를 0/1 인접 행렬 로 나타낸 것; 질량 벡터에 오른쪽에서 곱하면 그 질량이 을 따라 한 홉 밀려납니다. Neural-LP와 DRUM 둘 다가 그 위에서 탐색하는 기반입니다.
- 연쇄 규칙(chain rule, 논리학의 의미) — 몸체가 새로운 변수들로 이루어진 단일 경로인 혼 규칙, ; 순서가 있는 행렬 곱으로 평가할 수 있으며, 이 장 전체의 가설 공간입니다.
- 연산자 주의(operator attention) — 11개 연산자(관계 5개, 역관계 5개, 항등)에 대한 홉별 소프트맥스로, 이산적인 관계 선택을 미분 가능한 혼합으로 대체합니다.
- 메모리 주의(memory attention) — 이전 상태들에 대한 소프트맥스로, 어떤 홉이든 더 앞선 결과에 작용할 수 있게 하여, 풀어헤쳐진 깊이 보다 짧은 규칙들이 같은 순환식 안에 살 수 있게 합니다.
- 규칙 추출(rule extraction) — 학습된 주의들을 몸체별 신뢰도 곱으로 풀어헤쳐(항등은 제거하고, 중복은 병합하고, 작은 곱은 가지치기하여) 연속 모델로부터 이산적인 규칙 집합을 읽어 내는 것.
- 추출 충실도(extraction fidelity) — 집합값 홉과 부동소수점 없이 적용된 추출된 기호 규칙이 학습된 모델의 정답을 재현해야 한다는 요건; 여기서는 단언되지만, 규모가 커지면 그저 측정될 뿐입니다.
- 랭크-1 뒤엉킴(rank-1 entanglement) — 하나의 사슬이 갖는 몸체 신뢰도들이 외적 을 이루어, 를 강제하는 것입니다(는 advises 연산자, 는 그 역): 독립된 두 규칙은 신뢰도가 높은 가짜 교차항 없이는 둘 다 신뢰도가 높을 수 없습니다.
- CP 분해 / 랭크 (CP decomposition / rank ) — 몸체-신뢰도 텐서를 개의 외적의 합으로, 곧 독립된 주의 사슬 하나당 하나씩으로 나타낸 것; DRUM의 수리이며, 개의 연쇄 규칙으로 이루어진 어떤 가중 집합에도 충분합니다.
- 귀납적 규칙 적용(inductive rule application) — 학습된 규칙은 개체가 아니라 관계를 언급하므로, 개체-임베딩 모델은 찾아볼 것이 아무것도 없는 홀드아웃된 엣지와 본 적 없는 개체로도 전이됩니다.
이 장이 이어지는 곳
Neural-LP와 DRUM은 개체는 또렷하게 남겨 둔 채 규칙 몸체만을 부드럽게 만듦으로써 규칙을 학습합니다: 질량은 실제 엣지들을 따라 흐르고, 오직 관계의 선택만이 흐려집니다. 다음 장 신경 정리 증명: NTP와 CTP는 나머지 절반을 흐립니다. 거기서 증명기는 역방향 연쇄, 곧 목표, 단일화, 치환의 이산적인 형태는 그대로 유지하지만, 기호의 동등성을 벡터 유사도로 대체하여, "advises가 mentors와 단일화된다"는 것이 임베딩 거리의 문제가 되고 증명은 어느 정도만큼 성공합니다. 이 장의 모델들이 "알려진 관계들의 어떤 사슬이 데이터를 설명하는가?"에 답했다면, 신경 정리 증명기는 그 밑에 놓인 더 낯선 질문을 던집니다: 두 기호가 거의 일치한다는 것은 대체 무엇을 의미해야 하는가?
컴패니언 코드: examples/integration/neural_lp.py는 이 장 전체를 구현합니다: 연산자 어휘(86–107행), Neural-LP 순환식과 손으로 유도한 역전파(149–251행), 추출과 기호적 재생(256–313행), 랭크-1과 랭크-2 DRUM 추정기를 가진 acadLine 헤드(316–402행), 유한차분 인증(417–436행), 그리고 여기서 인용된 모든 주장을 지키는 역량 단언들(487–516행). 학습 목표는 1권의 전방 연쇄기(examples/logic/forward_chain.py 52–63행)에서 그대로 가져온 것이며 다시 타이핑되지 않았습니다; 고정 규칙 기반(관계를 인접 행렬로 나타낸 것)은 앞 장의 주제였던 examples/integration/tensor_ops.py였지만, 이 모듈은 오직 15개의 TRAIN 엣지만으로 자신만의 연산자를 다시 짓습니다. 모든 숫자를 바이트 단위로 그대로 재현하려면 python3 examples/integration/neural_lp.py를 실행하십시오; 그 실행은 다음으로 끝납니다: SUMMARY neural_lp: top_rule=advises∘advises conf=0.9990 nlp_loss=0.0020 drum_p_tgt_r1=0.2499 drum_p_tgt_r2=0.9994 exact_r1=0/4 exact_r2=4/4.