GPT-4 може написати поему про квантову фізику. І в тій же відповіді сказати, що 2+2=5. Це не баг. Це фундаментальна проблема архітектури. Трансформери передбачають токени — не істину.
LLM — це pattern matching на стероїдах. Вони не «думають». Вони передбачають наступний токен на основі статистики мільярдів текстів. І коли патерн веде в глухий кут — вони впевнено генерують нісенітницю. З посмішкою експерта.
Neurosymbolic AI — амбітна спроба поєднати два світи: нейронні мережі (гнучкість, узагальнення, навчання з даних) і символьні системи (логіка, точність, верифікація). Результат? AI, який і креативний, і не бреше. Принаймні, таке бачення.
Анатомія галюцинацій: чому LLM брешуть
Фундаментальні причини галюцинацій:
- Training objective: LLM оптимізують P(next_token|context), а не P(truth|context). Правильність — побічний ефект, не ціль.
- Distributional semantics: Слова — це вектори, відстані — це co-occurrence. Модель не знає, що "2+2=4" — це факт, а "2+2=5" — нісенітниця. Для неї це просто різні патерни.
- Training data mixing: Wikipedia змішана з fiction, facts змішані з opinions. Модель не розрізняє джерела.
- Confidence miscalibration: LLM впевнено каже неправду. Softmax temperature не корелює з factual accuracy.
- Absence of grounding: Немає зв'язку з реальним світом. Слова — це тільки слова.
Типологія галюцинацій:
class HallucinationType:
FACTUAL_ERROR = "фактична помилка" # Париж — столиця Німеччини
FABRICATION = "вигадка" # неіснуюча книга
INCONSISTENCY = "суперечність" # X is true, X is false
OUTDATED = "застаріла інформація" # хто президент у 2019
MATHEMATIC = "математична помилка" # 17 × 24 = 398
CITATION = "вигадана цитата" # Einstein never said that
@classmethod
def analyze(cls, response: str, facts: list) -> list:
"""Аналізує відповідь на різні типи галюцинацій"""
detected = []
# Implementation would use fact-checking pipeline
return detected
Symbolic AI: забута мудрість
Історія символьного AI (1950-1990):
Символьний AI — це оригінальна парадигма штучного інтелекту. До нейромереж були експертні системи.
Ключові концепції:
% Prolog — мова символьного AI
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
% Query: grandparent(tom, ann)?
% Result: true
Архітектура експертної системи:
class ExpertSystem:
"""Класична експертна система з forward/backward chaining"""
def __init__(self):
self.knowledge_base = {} # факти
self.rules = [] # правила
self.working_memory = set()
def add_fact(self, fact: str):
self.working_memory.add(fact)
def add_rule(self, conditions: list, conclusion: str, confidence: float = 1.0):
self.rules.append({
'conditions': conditions,
'conclusion': conclusion,
'confidence': confidence
})
def forward_chain(self) -> set:
"""Forward chaining: від фактів до висновків"""
changed = True
while changed:
changed = False
for rule in self.rules:
if all(c in self.working_memory for c in rule['conditions']):
if rule['conclusion'] not in self.working_memory:
self.working_memory.add(rule['conclusion'])
changed = True
return self.working_memory
def backward_chain(self, goal: str) -> bool:
"""Backward chaining: від цілі до фактів"""
if goal in self.working_memory:
return True
for rule in self.rules:
if rule['conclusion'] == goal:
if all(self.backward_chain(c) for c in rule['conditions']):
return True
return False
# Приклад: медична діагностика
medical = ExpertSystem()
medical.add_fact("fever")
medical.add_fact("cough")
medical.add_fact("fatigue")
medical.add_rule(["fever", "cough"], "possible_flu", 0.8)
medical.add_rule(["possible_flu", "fatigue"], "recommend_rest", 0.9)
conclusions = medical.forward_chain()
# {'fever', 'cough', 'fatigue', 'possible_flu', 'recommend_rest'}
Порівняння парадигм:
| Аспект | Symbolic AI | Neural AI |
|--------|-------------|-----------|
| Знання | Explicit rules | Implicit in weights |
| Навчання | Manual engineering | Data-driven |
| Пояснюваність | Trace of inference | Black box |
| Flexibility | Brittleness | Generalization |
| Precision | Guaranteed | Probabilistic |
| Scalability | Knowledge bottleneck | Data + compute |
Архітектури Neurosymbolic Integration
Таксономія підходів Henry Kautz (2020):
Type 1: Symbolic[Neural] - нейронні реалізації символьних систем
Type 2: Symbolic→Neural - symbolic preprocessing
Type 3: Neural→Symbolic - neural perception + symbolic reasoning
Type 4: Neural|Symbolic - паралельні системи
Type 5: Neural[Symbolic] - symbolic всередині neural
Type 6: Neural↔Symbolic - тісна інтеграція
Реалізація Type 3: Neural Perception + Symbolic Reasoning:
import torch
from z3 import *
class NeuralSymbolicReasoner:
"""Нейронне сприйняття + символьне reasoning"""
def __init__(self, perception_model, knowledge_base):
self.perception = perception_model # LLM or specialized encoder
self.kb = knowledge_base
self.solver = Solver()
def perceive(self, input_text: str) -> dict:
"""Витягує structured information з тексту"""
# LLM парсить текст у структуровану форму
prompt = f"""Extract entities and relations from: {input_text}
Return JSON with entities and relations."""
# Mock response structure
return {
"entities": ["entity1", "entity2"],
"relations": [("entity1", "related_to", "entity2")],
"properties": {"entity1": {"type": "person"}}
}
def formalize(self, perception: dict) -> list:
"""Перетворює perception у формальні constraints"""
constraints = []
for entity in perception["entities"]:
# Декларуємо сутність у Z3
exec(f"{entity} = Bool('{entity}')")
for rel in perception["relations"]:
subj, pred, obj = rel
# Формалізуємо relation як constraint
constraints.append(f"{pred}({subj}, {obj})")
return constraints
def reason(self, query: str, context: str) -> dict:
"""Повний reasoning pipeline"""
# 1. Neural perception
perception = self.perceive(context)
# 2. Formalize
formal_repr = self.formalize(perception)
# 3. Add KB rules
for rule in self.kb.get_rules():
self.solver.add(rule)
# 4. Check query
result = self.solver.check()
return {
"answer": result,
"perception": perception,
"formal": formal_repr,
"explanation": self.generate_explanation()
}
def generate_explanation(self):
"""Генерує пояснення для reasoning"""
if self.solver.check() == sat:
return f"Proved via: {self.solver.model()}"
return "Could not prove"
LLM + Knowledge Graph Integration
Архітектура RAG з Knowledge Graph:
from neo4j import GraphDatabase
import openai
class KnowledgeGroundedLLM:
"""LLM заземлений у Knowledge Graph"""
def __init__(self, llm_client, neo4j_uri, neo4j_auth):
self.llm = llm_client
self.driver = GraphDatabase.driver(neo4j_uri, auth=neo4j_auth)
def extract_entities(self, query: str) -> list:
"""LLM витягує сутності з запиту"""
response = self.llm.chat.completions.create(
model="gpt-4",
messages=[{
"role": "system",
"content": "Extract key entities from the query. Return JSON list."
}, {
"role": "user",
"content": query
}]
)
return eval(response.choices[0].message.content)
def query_knowledge_graph(self, entities: list) -> list:
"""Запитує факти з KG"""
facts = []
with self.driver.session() as session:
for entity in entities:
result = session.run("""
MATCH (e:Entity {name: $name})-[r]->(related)
RETURN e.name, type(r), related.name, r.confidence
LIMIT 20
""", name=entity)
for record in result:
facts.append({
"subject": record["e.name"],
"relation": record["type(r)"],
"object": record["related.name"],
"confidence": record["r.confidence"]
})
return facts
def generate_grounded_response(self, query: str) -> str:
"""Генерує відповідь заземлену у фактах"""
# 1. Extract entities
entities = self.extract_entities(query)
# 2. Get facts from KG
facts = self.query_knowledge_graph(entities)
# 3. Format facts as context
fact_context = "\n".join([
f"- {f['subject']} {f['relation']} {f['object']} (confidence: {f['confidence']})"
for f in facts
])
# 4. Generate grounded response
response = self.llm.chat.completions.create(
model="gpt-4",
messages=[{
"role": "system",
"content": f"""You are a helpful assistant.
Use ONLY the following verified facts to answer:
{fact_context}
If the facts don't contain the answer, say "I don't have verified information about this."
Never make up facts."""
}, {
"role": "user",
"content": query
}]
)
return response.choices[0].message.content
def verify_response(self, response: str, facts: list) -> dict:
"""Верифікує відповідь проти фактів"""
claims = self.extract_claims(response)
verification = {}
for claim in claims:
matched = any(self.claim_matches_fact(claim, f) for f in facts)
verification[claim] = "verified" if matched else "unverified"
return verification
Neuro-Symbolic Programs: LLM як програміст
Ідея: LLM генерує програму, яка обчислює відповідь. Результат — точний.
import ast
import math
class ProgramSynthesisLLM:
"""LLM генерує програми для точних обчислень"""
SAFE_OPERATIONS = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else float('inf'),
'power': lambda x, y: x ** y,
'sqrt': math.sqrt,
'log': math.log,
'sin': math.sin,
'cos': math.cos
}
def __init__(self, llm_client):
self.llm = llm_client
def generate_program(self, problem: str) -> str:
"""Генерує програму для вирішення задачі"""
prompt = f"""Convert this problem to a Python function that computes the answer.
Use only basic arithmetic and math operations.
The function should be called 'solve' and return the answer.
Problem: {problem}
Return ONLY the Python code, no explanations."""
response = self.llm.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
def validate_program(self, code: str) -> bool:
"""Перевіряє безпечність програми"""
try:
tree = ast.parse(code)
for node in ast.walk(tree):
# Заборонити import, exec, eval
if isinstance(node, (ast.Import, ast.ImportFrom)):
return False
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in ['exec', 'eval', 'compile', 'open']:
return False
return True
except SyntaxError:
return False
def execute_program(self, code: str) -> any:
"""Безпечно виконує програму"""
if not self.validate_program(code):
raise ValueError("Unsafe program detected")
# Створюємо обмежений namespace
safe_namespace = {
'math': math,
'__builtins__': {} # Забороняємо builtins
}
exec(code, safe_namespace)
if 'solve' in safe_namespace:
return safe_namespace['solve']()
raise ValueError("No 'solve' function found")
def solve_with_verification(self, problem: str) -> dict:
"""Повний pipeline з верифікацією"""
# 1. Generate program
code = self.generate_program(problem)
# 2. Validate
if not self.validate_program(code):
return {"error": "Generated unsafe code", "code": code}
# 3. Execute
try:
result = self.execute_program(code)
# 4. Verify with second generation
verify_code = self.generate_program(problem)
verify_result = self.execute_program(verify_code)
return {
"answer": result,
"verified": result == verify_result,
"code": code
}
except Exception as e:
return {"error": str(e), "code": code}
Differentiable Logic: навчання з логікою
Neural Theorem Provers:
import torch
import torch.nn as nn
import torch.nn.functional as F
class DifferentiableLogic(nn.Module):
"""Differentiable implementation логічних операцій"""
def __init__(self, temperature=1.0):
super().__init__()
self.temp = temperature
def fuzzy_and(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
"""Differentiable AND: product t-norm"""
return a * b
def fuzzy_or(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
"""Differentiable OR: probabilistic sum"""
return a + b - a * b
def fuzzy_not(self, a: torch.Tensor) -> torch.Tensor:
"""Differentiable NOT"""
return 1 - a
def fuzzy_implies(self, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
"""Differentiable implication: a → b = ¬a ∨ b"""
return self.fuzzy_or(self.fuzzy_not(a), b)
class NeuralProver(nn.Module):
"""Neural theorem prover для first-order logic"""
def __init__(self, embedding_dim: int, num_predicates: int, num_entities: int):
super().__init__()
# Entity embeddings
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
# Predicate networks
self.predicate_nets = nn.ModuleList([
nn.Sequential(
nn.Linear(embedding_dim * 2, embedding_dim),
nn.ReLU(),
nn.Linear(embedding_dim, 1),
nn.Sigmoid()
) for _ in range(num_predicates)
])
# Rule confidence learner
self.rule_confidence = nn.Parameter(torch.ones(100) * 0.5)
self.logic = DifferentiableLogic()
def evaluate_predicate(self, pred_idx: int, subj: int, obj: int) -> torch.Tensor:
"""Оцінює predicate(subject, object)"""
subj_emb = self.entity_embeddings(torch.tensor(subj))
obj_emb = self.entity_embeddings(torch.tensor(obj))
combined = torch.cat([subj_emb, obj_emb])
return self.predicate_nets[pred_idx](combined)
def apply_rule(self, rule_idx: int, premises: list, conclusion: torch.Tensor) -> torch.Tensor:
"""Застосовує правило: if premises then conclusion"""
premise_conj = premises[0]
for p in premises[1:]:
premise_conj = self.logic.fuzzy_and(premise_conj, p)
# Rule: premises → conclusion should be true
# Weighted by rule confidence
implication = self.logic.fuzzy_implies(premise_conj, conclusion)
return implication * self.rule_confidence[rule_idx]
def prove(self, query_pred: int, query_subj: int, query_obj: int,
known_facts: list, rules: list) -> torch.Tensor:
"""Доводить query використовуючи факти та правила"""
# Direct evaluation
direct_score = self.evaluate_predicate(query_pred, query_subj, query_obj)
# Rule-based inference
rule_scores = []
for rule in rules:
premises = [self.evaluate_predicate(p[0], p[1], p[2]) for p in rule['premises']]
conclusion = self.evaluate_predicate(query_pred, query_subj, query_obj)
rule_score = self.apply_rule(rule['idx'], premises, conclusion)
rule_scores.append(rule_score)
# Combine direct and rule-based
if rule_scores:
rule_contrib = torch.max(torch.stack(rule_scores))
return self.logic.fuzzy_or(direct_score, rule_contrib)
return direct_score
def forward(self, batch):
"""Training forward pass"""
losses = []
for query, label in batch:
score = self.prove(query['pred'], query['subj'], query['obj'],
query['facts'], query['rules'])
loss = F.binary_cross_entropy(score, torch.tensor([label]).float())
losses.append(loss)
return torch.mean(torch.stack(losses))
Constrained Decoding: гарантована структура
Grammar-Constrained Generation:
from lark import Lark
import torch
class GrammarConstrainedDecoder:
"""Декодер з обмеженнями граматики"""
def __init__(self, grammar: str, tokenizer, model):
self.parser = Lark(grammar, parser='lalr')
self.tokenizer = tokenizer
self.model = model
def get_valid_next_tokens(self, partial_output: str) -> set:
"""Визначає які токени можуть бути наступними"""
valid_tokens = set()
for token_id in range(self.tokenizer.vocab_size):
token = self.tokenizer.decode([token_id])
candidate = partial_output + token
try:
# Спроба парсити — якщо не падає, токен валідний
self.parser.parse(candidate)
valid_tokens.add(token_id)
except:
# Перевіряємо чи це може бути prefixом
if self.could_be_prefix(candidate):
valid_tokens.add(token_id)
return valid_tokens
def decode(self, input_ids: torch.Tensor, max_length: int = 100) -> str:
"""Генерує з обмеженнями граматики"""
output_ids = []
partial_output = ""
for _ in range(max_length):
# Get model logits
with torch.no_grad():
outputs = self.model(input_ids)
logits = outputs.logits[:, -1, :]
# Get valid tokens
valid_tokens = self.get_valid_next_tokens(partial_output)
# Mask invalid tokens
mask = torch.full_like(logits, float('-inf'))
for token_id in valid_tokens:
mask[0, token_id] = 0
masked_logits = logits + mask
# Sample
probs = torch.softmax(masked_logits, dim=-1)
next_token = torch.multinomial(probs, 1)
output_ids.append(next_token.item())
partial_output += self.tokenizer.decode([next_token.item()])
# Update input
input_ids = torch.cat([input_ids, next_token], dim=-1)
# Check if complete
if self.is_complete(partial_output):
break
return partial_output
# JSON grammar example
JSON_GRAMMAR = r'''
start: value
value: object | array | string | number | "true" | "false" | "null"
object: "{" [pair ("," pair)*] "}"
pair: string ":" value
array: "[" [value ("," value)*] "]"
string: /"[^"]*"/
number: /-?[0-9]+(\.[0-9]+)?/
'''
Практичні техніки зменшення галюцинацій
Comprehensive Fact-Checking Pipeline:
class FactCheckingPipeline:
"""Повний pipeline для перевірки фактів"""
def __init__(self, llm, retriever, fact_db):
self.llm = llm
self.retriever = retriever
self.fact_db = fact_db
def extract_claims(self, text: str) -> list:
"""Витягує окремі claims з тексту"""
prompt = f"""Extract all factual claims from this text.
Return as JSON list of strings, each being a single verifiable claim.
Text: {text}"""
response = self.llm.generate(prompt)
return eval(response)
def retrieve_evidence(self, claim: str) -> list:
"""Знаходить релевантні джерела"""
# Vector search в базі знань
results = self.retriever.search(claim, top_k=5)
return [{
"text": r.text,
"source": r.metadata["source"],
"confidence": r.score
} for r in results]
def verify_claim(self, claim: str, evidence: list) -> dict:
"""Верифікує claim проти evidence"""
evidence_text = "\n".join([e["text"] for e in evidence])
prompt = f"""Verify this claim against the evidence.
Claim: {claim}
Evidence:
{evidence_text}
Return JSON with:
- verdict: "supported", "refuted", or "not_enough_info"
- confidence: 0-1
- reasoning: explanation"""
response = self.llm.generate(prompt)
return eval(response)
def check_text(self, text: str) -> dict:
"""Повна перевірка тексту"""
claims = self.extract_claims(text)
results = []
for claim in claims:
evidence = self.retrieve_evidence(claim)
verification = self.verify_claim(claim, evidence)
results.append({
"claim": claim,
"evidence": evidence,
"verification": verification
})
# Aggregate
supported = sum(1 for r in results if r["verification"]["verdict"] == "supported")
refuted = sum(1 for r in results if r["verification"]["verdict"] == "refuted")
return {
"claims": results,
"summary": {
"total_claims": len(claims),
"supported": supported,
"refuted": refuted,
"accuracy": supported / len(claims) if claims else 0
}
}
Case Study: AlphaGeometry — neurosymbolic для олімпіад
Архітектура AlphaGeometry (Google, 2024):
class AlphaGeometryArchitecture:
"""Спрощена архітектура AlphaGeometry"""
def __init__(self):
self.language_model = TransformerLM() # Пропонує constructions
self.symbolic_engine = DeductiveEngine() # Верифікує
self.search = BeamSearch(width=512)
def solve(self, problem: GeometryProblem) -> Proof:
"""Вирішує геометричну задачу"""
# Initial state
state = self.symbolic_engine.initialize(problem)
# Спочатку пробуємо pure symbolic
proof = self.symbolic_engine.deduce(state)
if proof:
return proof
# Якщо не вдалось — використовуємо LM для auxiliary constructions
for step in range(self.max_steps):
# LM пропонує нові конструкції
candidates = self.language_model.propose_constructions(state)
for construction in candidates:
# Додаємо конструкцію
new_state = state.add_construction(construction)
# Symbolic engine намагається довести
proof = self.symbolic_engine.deduce(new_state)
if proof:
return proof
# Update state
state = new_state
return None
class DeductiveEngine:
"""Symbolic deduction для геометрії"""
RULES = [
"angle_sum_triangle", # кути трикутника = 180
"inscribed_angle", # вписаний кут
"parallel_lines", # паралельні прямі
"congruent_triangles", # конгруентні трикутники
"similar_triangles", # подібні трикутники
# ... 25+ правил
]
def deduce(self, state: GeometryState) -> Optional[Proof]:
"""Застосовує правила до вичерпання"""
changed = True
while changed:
changed = False
for rule in self.RULES:
new_facts = self.apply_rule(rule, state)
if new_facts:
state.add_facts(new_facts)
changed = True
if state.proves_goal():
return state.extract_proof()
return None
AlphaGeometry досягнув рівня срібної медалі на International Mathematical Olympiad — перша система, яка це зробила. Ключ: синергія LLM (креативність) і symbolic engine (точність).
Benchmark та метрики
Оцінка neurosymbolic систем:
| Benchmark | Що тестує | SOTA |
|-----------|-----------|------|
| GSM8K | Grade school math | 92% (GPT-4 + code) |
| MATH | Competition math | 76% (Minerva) |
| miniF2F | Formal proofs | 41% (AlphaProof) |
| TruthfulQA | Hallucinations | 73% (Claude 3) |
| HaluEval | Hallucination detection | 85% |
| FOLIO | First-order logic | 67% |
Ідеї для дослідження
Для бакалаврської роботи:
- LLM + калькулятор для математичних word problems
- RAG pipeline для зменшення галюцинацій у specific domain
- Порівняння accuracy LLM з/без symbolic verification
Для магістерської:
- Neurosymbolic reasoning для юридичних або медичних текстів
- Automatic formalization: природна мова → formal logic
- Knowledge Graph construction з LLM + verification
- Hybrid system для contract analysis
Для PhD:
- Theoretical foundations: які guarantees можливі?
- Differentiable logic programming at scale
- Learning to formalize: end-to-end training
- Scaling neurosymbolic до complex real-world domains
Чому це майбутнє AI
LLM без symbolic grounding — небезпечні в критичних застосуваннях. Вони впевнено брешуть. В медицині — неправильний діагноз. В юриспруденції — помилкова інтерпретація. У фінансах — порушення compliance.
Neurosymbolic AI — не просто "покращення точності". Це фундаментальний shift до trustworthy AI. До систем, яким можна довіряти не тому, що вони "зазвичай правильні", а тому, що вони mathematically correct.
Хто вирішить проблему надійної інтеграції — визначить майбутнє AI в усіх галузях, де помилка коштує дорого. А таких галузей більшість.
Якщо ви готуєте наукову роботу з neurosymbolic AI — від курсової до дисертації — звертайтесь до фахівців SKP-Degree на skp-degree.com.ua або пишіть у Telegram: @kursovi_diplomy. Команда має досвід супроводу досліджень на стику нейромереж і формальних методів.
Ключові слова: neurosymbolic AI, галюцинації LLM, symbolic reasoning, knowledge graphs, formal verification, differentiable logic, AlphaGeometry, trustworthy AI, наукова робота, дипломна, дисертація, магістерська, курсова.