Skip to content

Neural Networks in the Graph

zelph can embed neural networks directly inside its semantic network. There is no parallel world, no export/import boundary, no separate tensor runtime with its own identifiers: the neurons are ordinary graph nodes, and the synapses are ordinary graph edges carrying weights. A sub-graph can be compiled into a feed-forward network on demand, trained, evaluated, and written back β€” and inference rules can consult such a network as a condition, using the β‰ˆ operator.

This page covers the full stack: the weighted-edge substrate, the compiled-network cache, the Janet API, the β‰ˆ rule condition, the helper library stdlib/nn.zph, and a complete proof of concept on real Wikidata data (stdlib/examples/neural/nn-wikidata-demo.zph).

The design goal is a form of neuro-symbolic AI: symbolic reasoning (rules, unification, negation) and sub-symbolic learning (weighted connections, gradient descent) operating on the same structures. A prediction made by a network does not need to be translated into the knowledge representation β€” it already is knowledge representation, and it flows back into the graph as a fact probability.

A Two-Minute Neural Network Primer

If you are new to neural networks, this is the minimal vocabulary needed for this page. Each term links to a starting point for deeper reading; none of the mathematics beyond this summary is required to use the feature.

  • A feed-forward network organizes neurons into layers. Each neuron computes a weighted sum of the previous layer's activations. The weights are the learnable parameters.
  • Hidden layers in zelph apply the ReLU activation (max(0, x)); the output layer is linear (no activation).
  • Training uses stochastic gradient descent (SGD): for one sample, compute the squared-error loss between output and target, then adjust the weights slightly against the error gradient, computed via backpropagation.
  • Inputs and targets are encoded as one-hot / multi-hot vectors: each neuron corresponds to one entity, and a sample activates exactly the entities it mentions.
  • The demo task on this page is link prediction: given a subject and a predicate, predict the most plausible object.

The Substrate: Raw Weighted Edges

Internally, zelph stores edge weights in a sparse side table keyed by a hash of the directed edge. Nodes and edges that carry no weight cost nothing β€” the neural substrate adds zero memory overhead to the millions of nodes of, say, a Wikidata import.

This weight store is shared with an older zelph concept: fact probabilities. A fact's probability has always been stored on the edge from the fact node to its predicate node (range [0, 1], absent entry = 1). Synapse weights use the same store on neuron-to-neuron edges, without the range constraint. In other words, fact probabilities are a constrained view of the general weight store β€” this unification is what later allows a network's confidence to become a deduced fact's probability with no translation step.

Synapses are stored exclusively in this weight table, keyed by the directed node pair. Creating a synapse inserts nothing into the graph's adjacency structure β€” this is what makes synapses invisible to the reasoning engine by construction, not merely by filtering. The distinction matters: the adjacency of relation nodes (fact nodes, including every cons cell of a structural number) is their fact structure β€” an edge into a relation node carries the signature of an additional object, an edge out of it the signature of a predicate link. An extraneous adjacency entry on such a node would therefore corrupt its structure and break the hash-consing identity guarantee. Since synapses never touch adjacency, any node is a safe neuron: an entity, a predicate, a digit, a fact node, or a structural number.

One deliberate consequence of the shared weight store: a synapse from a fact node to that fact's own predicate node occupies the same store entry as the fact's probability β€” the two are indistinguishable by design. In practice this only arises when a fact node and its own predicate are neurons in consecutive layers, a wiring worth avoiding.

n1 ~ neuron
n2 ~ neuron
%(zelph/nn-connect "n1" "n2" 0.7)
n1 _P _O

The query returns only n1 ~ neuron β€” the synapse is not a fact.

The low-level API:

  • (zelph/nn-connect from to &opt weight) β€” create a raw weighted edge (default weight 1), creating the nodes if needed.
  • (zelph/weight from to) β€” read the weight of an edge, or nil if the edge does not exist. An existing edge without a stored weight yields 1.
  • (zelph/set-weight from to w) β€” overwrite the weight of an existing edge.

Layers Are Sets, Neurons Are Nodes

A layer is simply a node, and its neurons are the subjects of ordinary (neuron in layer) membership facts β€” the same in (PartOf) relation used for sets:

x1 in Lin
x2 in Lin
y  in Lout

The canonical neuron order within a layer is ascending node ID (i.e. creation order), which makes vector layouts deterministic.

Because neurons are ordinary nodes, anything can be a neuron: an entity, a predicate, a digit, a fact node. This is the crucial difference from external ML pipelines β€” there is no identifier mapping between "the entity Q183" and "input neuron 17". They are the same node.

Compiled Networks: A Discardable Cache

The graph is deliberately not evaluated edge-by-edge during a forward pass β€” that would be far too slow. Instead, (zelph/nn-compile layers) takes an array of layer nodes (input first, output last) and compiles a dense snapshot: weight matrices plus a mask recording which synapses actually exist in the graph. It returns an integer handle for subsequent calls.

The philosophy mirrors zelph's predicate index (.pidx files): the graph is the single source of truth, and the compiled network is a discardable cache. Handles are session-scoped and vanish on .new. Three consequences:

  1. Sparsity is preserved. Only synapses present in the graph are trainable; training can never create a connection that the graph does not contain. Absent synapses stay absent.
  2. Training happens on the cache. zelph/nn-train and zelph/nn-train-nodes update the compiled matrices, not the graph.
  3. (zelph/nn-write-back handle) transfers the trained weights back into the graph's weight store. Only then do they survive .save, get picked up by future zelph/nn-compile calls β€” and, importantly, become visible to β‰ˆ rule conditions, which always compile from the graph.

If you train a network and skip zelph/nn-write-back, the graph β€” and therefore every β‰ˆ condition β€” still sees the untrained weights. The helper nn/link-predictor (see below) writes back automatically.

The remaining compiled-network API:

  • (zelph/nn-nodes handle layer) β€” the neurons of a compiled layer in index order; this order defines the meaning of plain input/output vectors.
  • (zelph/nn-eval handle inputs) β€” forward pass with plain number vectors (in zelph/nn-nodes order). Hidden layers use ReLU, the output layer is linear.
  • (zelph/nn-train handle inputs targets &opt learning-rate) β€” one SGD step on one sample; returns the loss (0.5 Β· Ξ£ errorΒ²) before the update. Learning rate defaults to 0.01.
  • (zelph/nn-connect-layers from-layer to-layer &opt scale seed) β€” wiring helper: creates raw synapses between all members of two layers, with uniform random weights in [-scale, scale] (scale defaults to 0.1; scale 0 gives exact zeros; seed defaults to 42 for reproducibility). Existing edges are left untouched, so the call is idempotent and re-wiring never destroys trained weights.
  • (zelph/nn-snapshot handle) / (zelph/nn-restore handle snapshot) β€” copy the weights out as an array of arrays of numbers, and put them back. Shapes must match; a synapse absent from the graph stays absent, because the mask belongs to the graph rather than to the weights.

Snapshots exist because training passes its best point before anything can notice. A criterion like "the held-out loss has not improved for n epochs" only fires n epochs late, and on a noisy curve it fires at a point that says more about the noise than about the model β€” so without a way back, what gets saved is never quite what was measured. Take a snapshot whenever the held-out loss improves, restore it before zelph/nn-write-back, and the saved network is the one the number described.

Node-Addressed Training

Plain vectors are inconvenient when the training data is the graph. The node-addressed API lets you address neurons by their graph node:

  • (zelph/nn-train-nodes handle inputs targets &opt learning-rate) β€” one SGD step. inputs/targets are arrays whose elements are nodes (activation 1) or [node activation] pairs; all other neurons are 0 (multi-hot encoding). A typical call encodes one fact: inputs [S P], target [O].
  • (zelph/nn-eval-nodes handle inputs &opt top-k) β€” forward pass; returns [node score] tuples for the output layer, sorted by descending score, optionally limited to the top k.

Three details worth knowing:

  • Implicit negative sampling. A one-hot target sets the correct object to 1 and every other output neuron to 0. Each training step therefore pushes all wrong answers down while pulling the right one up β€” no explicit negative samples are needed.
  • Graded activations. [node 0.5] feeds a quantitative value instead of a binary flag, e.g. edge weights of another network, or degrees of confidence.
  • The input layer costs O(active), not O(width). These two entry points are told which neurons are non-zero, and they evaluate and update only those. The dense entry points cannot know this and multiply by every zero. The result is the same number β€” the skipped terms are multiplications by zero β€” so the choice is purely one of cost.

That last point decides how wide an input layer may be. A multi-hot encoding of a large domain activates a handful of neurons out of thousands, and the node-addressed path prices it by the handful. dev_scripts/measure-nn-sparse-input.zph times both entry points on one 768 Γ— 32 Γ— 1 network with 32 active inputs, alternating within a single process and reporting the same loss from either: 6.7 Β΅s per sample through the dense entry point against 0.71 Β΅s through the node-addressed one. Widening the input layer further is then nearly free, because the cost follows the samples rather than the layer.

Nodes that are not members of the addressed layer are rejected with an error rather than silently ignored β€” a deliberate choice, since silently dropping an input would corrupt training data without any signal.

Declaring a Network in the Graph

Everything so far used session handles. To make a network addressable from rules, its architecture must itself be stored in the graph, as an ordinary fact:

gnet nn-layers <GIn GOut>

This reads: the network gnet consists of the layers GIn (input) and GOut (output) β€” an ordinary cons-list, input layer first. Deeper networks list hidden layers in between.

The marker predicates nn and nn-layers are ordinary nodes registered in the language "zelph". They are deliberately not core nodes: core node IDs are positional, so introducing new ones would break every existing .bin file. A practical consequence: if your current .lang is not zelph (e.g. wikidata), writing nn-layers literally would create an unrelated node in that language β€” use the helper nn/declare (below) or (zelph/resolve "nn-layers" "zelph") instead.

Neural Rule Conditions: β‰ˆ

The centerpiece: a rule condition that consults a network instead of (or in addition to) matching facts.

(A demo-country yes, A P30 X, β‰ˆgeo(A P30 X)) => (A P30-verified X)

Syntax and desugaring

β‰ˆnet(pattern) desugars to (zelph/approx pattern "net"), which creates the tag fact (pattern nn net) in the graph and returns that tag fact. The tag fact β€” not the inner pattern β€” becomes the rule condition. This distinction matters: fact creation is idempotent, so if the pattern itself were the condition, an β‰ˆ condition and an ordinary positive condition on the same pattern (as in the rule above) would collapse into one node. Returning the tag fact keeps them distinct, and the reasoning engine recognizes a neural condition purely by its predicate nn β€” structurally analogous to how != guards are recognized.

What the engine does

When a rule is applied, β‰ˆ conditions are scheduled late: the condition-ordering heuristic ranks them after all positive conditions and != guards (which bind variables cheaply) but before negations. By the time an β‰ˆ condition is evaluated, its subject and predicate are bound.

The engine then resolves the network: it reads the (net nn-layers <...>) fact, collects the layer members, and compiles the network from the graph β€” lazily, with a per-input cache. The cache is cleared on every new REPL input, so training performed between inputs is picked up automatically on the next .run.

The bound pattern components that are members of the input layer are activated (multi-hot), and a forward pass produces scores for the output layer. Two modes follow:

  • Guard mode β€” the pattern's object is already bound: the condition succeeds iff the object's calibrated score exceeds 0.5. Typical use: verify existing facts.
  • Generator mode β€” the object is an unbound variable: the condition produces one variable binding per output neuron whose calibrated score exceeds 0.5. Typical use: propose new facts.

A third mode is the second one seen from the other side. Β¬β‰ˆnet(S P O) succeeds exactly when the net fails to confirm the fact – the calibrated score at or below 0.5 – and it is a guard and nothing else: the object must be bound, because a negation binds nothing, and generator mode under Β¬ is reported rather than guessed at. It contributes no confidence either. What the net reports is how strongly it believes the fact, and one minus that measures nothing a deduction can carry, so a negated condition filters and leaves the rule’s own confidence alone.

A net that cannot be consulted fails the condition in both readings. "The net does not confirm this" and "there is no net" are different statements, and letting the second satisfy Β¬β‰ˆ would turn a misspelled net name into a derivation engine: every rule consulting it would start firing on everything. The same holds for an object the output layer does not contain – one the net has no opinion about rather than one it rejects.

Calibration: clamp, not sigmoid

Scores are calibrated by clamping to [0, 1]. A sigmoid may look like the textbook choice, but it would be wrong here: training uses one-hot targets in {0, 1} under squared-error loss, so raw scores concentrate near 0 (negatives) and 1 (positives). A sigmoid would place the decision threshold at raw score 0 β€” which nearly every score passes after training β€” whereas clamping puts the boundary at raw 0.5, exactly between the two target values.

Confidence becomes fact probability

The calibrated score is a confidence. It multiplies along the rule's binding path (several β‰ˆ conditions compound), and when the rule fires, the deduction is created with this confidence as its fact probability β€” stored in the shared weight store on the fact-to-predicate edge, exactly like any other fact probability. Reading it back:

%(defn fact-conf [s p o]
   (if (zelph/exists s p o)
     (zelph/weight (zelph/fact s p o) p)   # fact is idempotent; existing probability is not touched
     nil))

If the deduced fact already exists, its stored probability is not modified β€” a rule cannot silently upgrade established knowledge.

Current restrictions (v1)

  • The inner pattern must be a plain S-P-O triple with exactly one object.
  • Subject and predicate must be bound when the condition is evaluated (the automatic ordering normally guarantees this).
  • β‰ˆ conditions are not supported inside .prune-facts / .prune-nodes patterns.
  • A fact derived through an β‰ˆ condition cannot be reconstructed by .explain (see below).

If the named net has no nn-layers definition β€” a misspelled name, or a definition that comes after the rule β€” every rule consulting it stays silent. That is reported once per net rather than left to .log:

zelph> a p b
zelph> (X p Y, β‰ˆnosuchnet(X p Y)) => (X q Y)
(((X p Y) nn nosuchnet), (X p Y)) => (X q Y)
Neural condition: net 'nosuchnet' has no nn-layers definition, so every rule consulting it stays silent.

The message appears when the condition is first EVALUATED, so the rule needs something to match β€” with no data, nothing has run yet and there is nothing to report.

A definition that is present but unusable is named as such rather than as a missing one, so the search starts in the right place:

zelph> a p b
zelph> badnet nn-layers something
zelph> (X p Y, β‰ˆbadnet(X p Y)) => (X q Y)
((X p Y), ((X p Y) nn badnet)) => (X q Y)
Neural condition: net 'badnet' has an nn-layers definition that is not a list of at least two layers -- write `net nn-layers < input hidden output >`.

.explain and a neural premise

The backward search does not use a rule carrying a neural condition. A network confidence is not a structural premise: unlike a negation-as-failure premise ([absent]) or a transitive path ([closure]), it is not something the graph can be re-asked for. A fact derived only through such a rule is therefore reported as no derivation found, and a proof stops at the neural step rather than being absent altogether β€” everything above it is reconstructed as usual:

zelph> s in KIn
zelph> o in KOut
zelph> %(zelph/nn-connect "s" "o" 0.9)
zelph> knet nn-layers <KIn KOut>
zelph> s relK o
zelph> (A relK B, β‰ˆknet(A relK B)) => (A verifiedK B)
(((A relK B) nn knet), (A relK B)) => (A verifiedK B)
(s verifiedK o) ⇐ {((s relK o) nn knet) (s relK o)}
zelph> (A verifiedK B) => (A trustedK B)
(s trustedK o) ⇐ (s verifiedK o)
zelph> .explain (s trustedK o)
s trustedK o
   └─ s verifiedK o  [asserted; no derivation found]

The forward direction is unaffected, and it is where the provenance is: the deduction line names the tag fact (pattern nn net) as the premise, and the confidence is stored as the fact's probability, as described above.

The Helper Library: stdlib/nn.zph

The raw API is deliberately low-level. nn.zph provides idiomatic helpers (import once via .import nn):

Function Purpose
(nn/in-layer nodes layer) Add nodes to a layer via (node in layer) facts. Idempotent.
(nn/declare net layers) Create the (net nn-layers <...>) fact β€” the graph-level definition β‰ˆ compiles from.
(nn/layers net) Read a network's layer nodes back from its nn-layers fact.
(nn/wire-dense net &opt scale seed) Densely wire consecutive layers of a declared net (via zelph/nn-connect-layers).
(nn/compile net) Compile a declared net; returns the handle.
(nn/gather subjects preds) Collect (S P O) triples from the graph as training samples. Read-only.
(nn/train-triples handle samples &opt epochs lr) SGD over samples: input {S, P}, one-hot target {O}. Returns the last epoch's mean loss.
(nn/predict handle inputs &opt k) Forward pass; top-k [node score] tuples.
(nn/predict-names handle inputs &opt k lang) Like nn/predict, but with node names for easy printing.
(nn/link-predictor net subjects preds &named epochs lr extra-subjects) End-to-end: gather samples, build <net>-in/<net>-out layers, declare, wire, compile, train, write back. Returns the handle.

nn/link-predictor is the one-call path from "facts in the graph" to "network usable from β‰ˆ rules": because it writes the trained weights back, the graph-compiled network that β‰ˆ sees is the trained one.

Proof of Concept: Continent Prediction on Wikidata

stdlib/examples/neural/nn-wikidata-demo.zph demonstrates the full loop on real data. The task: learn which continent (property P30) a country belongs to, from the P30 facts already present in a Wikidata dump β€” then use the network from rules to verify existing facts and propose missing ones.

While this demo uses Wikidata, nothing about the neural machinery is Wikidata-specific; the dump merely provides a large, real-world graph to learn from.

The script, step by step:

  1. Select the entities. Countries are collected as direct instances (P31) of country (Q6256) or sovereign state (Q3624078) β€” many countries carry only the latter.
  2. Materialize the selection as facts (A demo-country yes). This gives the rules a small, precise predicate to anchor on, instead of scanning the ~15 million P31 facts of the full dump.
  3. Train a link predictor: (nn/link-predictor "geo" countries [p30] :epochs 150 :lr 0.2) β€” input layer: countries + the P30 predicate node, output layer: the continents observed in the training triples. On the demo dump: 144 countries, 118 P30 samples.
  4. Guard rule β€” verify existing P30 facts through the network:

    (A demo-country yes, A P30 X, β‰ˆgeo(A P30 X)) => (A P30-verified X)
    
  5. Generator rule β€” let the network propose continents, keep only proposals not backed by an existing fact (note the interplay of β‰ˆ and negation-as-failure):

    (A demo-country yes, β‰ˆgeo(A P30 X), Β¬(A P30 X)) => (A P30-candidate X)
    
  6. Cluster hygiene. Everything runs inside .cluster nn-demo, so the entire experiment β€” layers, synapses, rules, deductions β€” can be rolled back with a single .cluster-drop nn-demo, leaving the loaded dump untouched.

  7. Verification block. After .run, the script queries the deduced facts and reads their confidences back from the weight store, closing the loop: network score β†’ rule confidence β†’ fact probability.

Interpreting the results

On the demo dump, the run deduces 99 P30-verified facts with confidences between ~0.51 and 1.0, and 42 P30-candidate facts β€” all with confidence β‰ˆ 0.527 pointing to Europe (Q46). The uniformity of the candidates is worth understanding, because it illustrates both what this simple architecture learns and where its limits are:

The input encoding is identity-based: each country is its own input neuron. The P30 predicate neuron is shared across all training samples, so its weights learn the marginal distribution of continents in the training set (Europe dominates, thanks to the many historical European states in the dump). A country that has no P30 fact never receives a training signal on its own input neuron β€” its weights stay at their zero initialization. For such a country, the network's output is the P30 prior alone: β‰ˆ 0.527 for Europe, just above the threshold. The candidates are therefore prior-driven, not country-specific β€” honest behavior for a model without generalizing features, and a useful baseline signal ("statistically, an unknown country is most likely European in this dataset").

Making predictions country-specific for unseen entities requires features that generalize β€” the classic next step being knowledge graph embeddings, where entities share a learned vector space instead of having isolated identity neurons. The substrate described on this page (weighted edges, layers as sets, graded activations) is designed to carry such architectures as well.

Two dump-related notes, so they are not mistaken for bugs: entities absent from the pruned demo dump's country selection (e.g. Q183/Germany, which carries only P31 Q3624078 in some prunings) are reported and skipped by the script; and evaluating a node that is not a member of the input layer raises an explicit error by design.

Limitations and Outlook

A compiled network is safe to share between threads: evaluation is concurrent, training is exclusive against it. See the threading note in the Janet API reference.

The current implementation is a deliberate foundation, not a finished ML framework: networks have no bias terms, hidden layers are ReLU-only, the output is linear, features are identity-based, and β‰ˆ supports plain S-P-O patterns only. What the foundation establishes is the architectural claim: neurons as nodes, synapses as edges, training data gathered by reasoning queries, and network confidences flowing back into the graph as fact probabilities β€” all without leaving the semantic network.

Appendix: Complete Session Log

The following is a complete, unaltered session log of stdlib/examples/neural/nn-wikidata-demo.zph executing against wikidata-20260309-all-pruned-small.bin: from loading the dump through training, both β‰ˆ rules firing during .run, and the verification block reading confidences back from the graph. It was generated non-interactively, which is how any transcript here is made reproducible:

.deductions all
.load /home/stefan/zelph/wikidata-20260309-all-pruned-small.bin
.lang wikidata
.import nn
.import examples/neural/nn-wikidata-demo
.quit

.deductions all is what causes the ⇐ lines to show up. The default anchors only to statements of the session, and the demo is loaded as a module with .import, which adds no anchors (see .help .deductions and Scripts and Modules). Without that initial line the identical run holds back all 142 derivations and says so with a + in the prompt.

The training is seeded, so the counts and the loss come out identical on every run and on every machine – that is what makes the figures quotable. The five rows printed under each of the two totals are a sample of the 99 and the 42, and which five they are depends on the run: the reasoner is parallel, and a set has no first element.

$ zelph < demo.zph
zelph 1.0.0
-- REPL mode - type .help for commands, .quit to exit --

zelph> Deduction printing mode: all
zelph> Auto-run has been disabled due to loading a large dataset.
Loading network from generic file /home/stefan/zelph/wikidata-20260309-all-pruned-small.bin...
Loading: left chunks=27, right chunks=27, nameOfNode chunks=13, nodeOfName chunks=13
...........................
...........................
.............
.............
String pool size after load: 11423140
Network loaded.
 Time needed for loading/importing: 0h0m26.413s
-- 26.413 s --
zelph-> wikidata-> Importing file /media/stefan/data/Documents/git/my-projects/acrion/zelph2/build-release/bin/stdlib/nn.zph...
wikidata-> Importing file /media/stefan/data/Documents/git/my-projects/acrion/zelph2/build-release/bin/stdlib/examples/neural/nn-wikidata-demo.zph...
Active cluster: nn-demo
Starting reasoning with 24 worker threads.
countries found: 144
nn/link-predictor: 118 samples, final mean loss 0.10311
Q29999 (Kingdom of the Netherlands):
    Q49  0.434
    Q18  0.260
    Q15  0.156
Q962 (Q962):
    Q15  0.999
    Q48  0.001
    Q46  0.001
Q12536 (Abbasid Caliphate):
    Q15  0.624
    Q48  0.376
    Q5401  0.001
(Q33 P30-verified Q46) ⇐ {((Q33 P30 Q46) nn geo) (Q33 demo-country yes) (Q33 P30 Q46)}
(Q8733 P30-verified Q48) ⇐ {((Q8733 P30 Q48) nn geo) (Q8733 demo-country yes) (Q8733 P30 Q48)}
(Q267584 P30-verified Q48) ⇐ {((Q267584 P30 Q48) nn geo) (Q267584 demo-country yes) (Q267584 P30 Q48)}
(Q774783 P30-verified Q46) ⇐ {((Q774783 P30 Q46) nn geo) (Q774783 demo-country yes) (Q774783 P30 Q46)}
(Q107258515 P30-verified Q48) ⇐ {((Q107258515 P30 Q48) nn geo) (Q107258515 demo-country yes) (Q107258515 P30 Q48)}
(Q12060881 P30-verified Q48) ⇐ {((Q12060881 P30 Q48) nn geo) (Q12060881 demo-country yes) (Q12060881 P30 Q48)}
(Q335088 P30-verified Q48) ⇐ {((Q335088 P30 Q48) nn geo) (Q335088 demo-country yes) (Q335088 P30 Q48)}
(Q1155700 P30-verified Q48) ⇐ {((Q1155700 P30 Q48) nn geo) (Q1155700 demo-country yes) (Q1155700 P30 Q48)}
(Q10957559 P30-verified Q46) ⇐ {((Q10957559 P30 Q46) nn geo) (Q10957559 demo-country yes) (Q10957559 P30 Q46)}
(Q1147441 P30-verified Q48) ⇐ {((Q1147441 P30 Q48) nn geo) (Q1147441 demo-country yes) (Q1147441 P30 Q48)}
(Q216632 P30-verified Q15) ⇐ {((Q216632 P30 Q15) nn geo) (Q216632 demo-country yes) (Q216632 P30 Q15)}
(Q2454585 P30-verified Q46) ⇐ {((Q2454585 P30 Q46) nn geo) (Q2454585 demo-country yes) (Q2454585 P30 Q46)}
(Q1998866 P30-verified Q46) ⇐ {((Q1998866 P30 Q46) nn geo) (Q1998866 demo-country yes) (Q1998866 P30 Q46)}
(Q1057542 P30-verified Q538) ⇐ {((Q1057542 P30 Q538) nn geo) (Q1057542 demo-country yes) (Q1057542 P30 Q538)}
(Q187035 P30-verified Q46) ⇐ {((Q187035 P30 Q46) nn geo) (Q187035 demo-country yes) (Q187035 P30 Q46)}
(Q7313 P30-verified Q48) ⇐ {((Q7313 P30 Q48) nn geo) (Q7313 demo-country yes) (Q7313 P30 Q48)}
(Q4147013 P30-verified Q15) ⇐ {((Q4147013 P30 Q15) nn geo) (Q4147013 demo-country yes) (Q4147013 P30 Q15)}
(Q37102 P30-verified Q15) ⇐ {((Q37102 P30 Q15) nn geo) (Q37102 demo-country yes) (Q37102 P30 Q15)}
(Q245160 P30-verified Q5401) ⇐ {((Q245160 P30 Q5401) nn geo) (Q245160 demo-country yes) (Q245160 P30 Q5401)}
(Q115166787 P30-verified Q48) ⇐ {((Q115166787 P30 Q48) nn geo) (Q115166787 demo-country yes) (Q115166787 P30 Q48)}
(Q771193 P30-verified Q46) ⇐ {((Q771193 P30 Q46) nn geo) (Q771193 demo-country yes) (Q771193 P30 Q46)}
(Q114327408 P30-verified Q46) ⇐ {((Q114327408 P30 Q46) nn geo) (Q114327408 demo-country yes) (Q114327408 P30 Q46)}
(Q618399 P30-verified Q15) ⇐ {((Q618399 P30 Q15) nn geo) (Q618399 demo-country yes) (Q618399 P30 Q15)}
(Q140359 P30-verified Q46) ⇐ {((Q140359 P30 Q46) nn geo) (Q140359 demo-country yes) (Q140359 P30 Q46)}
(Q3623202 P30-verified Q46) ⇐ {((Q3623202 P30 Q46) nn geo) (Q3623202 demo-country yes) (Q3623202 P30 Q46)}
(Q173065 P30-verified Q46) ⇐ {((Q173065 P30 Q46) nn geo) (Q173065 demo-country yes) (Q173065 P30 Q46)}
(Q703695 P30-verified Q48) ⇐ {((Q703695 P30 Q48) nn geo) (Q703695 demo-country yes) (Q703695 P30 Q48)}
(Q1483495 P30-verified Q46) ⇐ {((Q1483495 P30 Q46) nn geo) (Q1483495 demo-country yes) (Q1483495 P30 Q46)}
(Q671658 P30-verified Q828) ⇐ {((Q671658 P30 Q828) nn geo) (Q671658 demo-country yes) (Q671658 P30 Q828)}
(Q19083 P30-verified Q5401) ⇐ {((Q19083 P30 Q5401) nn geo) (Q19083 demo-country yes) (Q19083 P30 Q5401)}
(Q1054184 P30-verified Q48) ⇐ {((Q1054184 P30 Q48) nn geo) (Q1054184 demo-country yes) (Q1054184 P30 Q48)}
(Q330362 P30-verified Q46) ⇐ {((Q330362 P30 Q46) nn geo) (Q330362 demo-country yes) (Q330362 P30 Q46)}
(Q243652 P30-verified Q46) ⇐ {((Q243652 P30 Q46) nn geo) (Q243652 demo-country yes) (Q243652 P30 Q46)}
(Q12536 P30-verified Q15) ⇐ {((Q12536 P30 Q15) nn geo) (Q12536 demo-country yes) (Q12536 P30 Q15)}
(Q31747 P30-verified Q46) ⇐ {((Q31747 P30 Q46) nn geo) (Q31747 demo-country yes) (Q31747 P30 Q46)}
(Q185488 P30-verified Q46) ⇐ {((Q185488 P30 Q46) nn geo) (Q185488 demo-country yes) (Q185488 P30 Q46)}
(Q838261 P30-verified Q46) ⇐ {((Q838261 P30 Q46) nn geo) (Q838261 demo-country yes) (Q838261 P30 Q46)}
(Q9903 P30-verified Q48) ⇐ {((Q9903 P30 Q48) nn geo) (Q9903 demo-country yes) (Q9903 P30 Q48)}
(Q15864 P30-verified Q46) ⇐ {((Q15864 P30 Q46) nn geo) (Q15864 demo-country yes) (Q15864 P30 Q46)}
(Q3892131 P30-verified Q18) ⇐ {((Q3892131 P30 Q18) nn geo) (Q3892131 demo-country yes) (Q3892131 P30 Q18)}
(Q1078602 P30-verified Q48) ⇐ {((Q1078602 P30 Q48) nn geo) (Q1078602 demo-country yes) (Q1078602 P30 Q48)}
(Q170468 P30-verified Q15) ⇐ {((Q170468 P30 Q15) nn geo) (Q170468 demo-country yes) (Q170468 P30 Q15)}
(Q2578028 P30-verified Q48) ⇐ {((Q2578028 P30 Q48) nn geo) (Q2578028 demo-country yes) (Q2578028 P30 Q48)}
(Q191077 P30-verified Q46) ⇐ {((Q191077 P30 Q46) nn geo) (Q191077 demo-country yes) (Q191077 P30 Q46)}
(Q878319 P30-verified Q46) ⇐ {((Q878319 P30 Q46) nn geo) (Q878319 demo-country yes) (Q878319 P30 Q46)}
(Q1415128 P30-verified Q48) ⇐ {((Q1415128 P30 Q48) nn geo) (Q1415128 demo-country yes) (Q1415128 P30 Q48)}
(Q63158027 P30-verified Q48) ⇐ {((Q63158027 P30 Q48) nn geo) (Q63158027 demo-country yes) (Q63158027 P30 Q48)}
(Q29520 P30-verified Q48) ⇐ {((Q29520 P30 Q48) nn geo) (Q29520 demo-country yes) (Q29520 P30 Q48)}
(Q1048340 P30-verified Q46) ⇐ {((Q1048340 P30 Q46) nn geo) (Q1048340 demo-country yes) (Q1048340 P30 Q46)}
(Q23366230 P30-verified Q46) ⇐ {((Q23366230 P30 Q46) nn geo) (Q23366230 demo-country yes) (Q23366230 P30 Q46)}
(Q200262 P30-verified Q46) ⇐ {((Q200262 P30 Q46) nn geo) (Q200262 demo-country yes) (Q200262 P30 Q46)}
(Q1470101 P30-verified Q46) ⇐ {((Q1470101 P30 Q46) nn geo) (Q1470101 demo-country yes) (Q1470101 P30 Q46)}
(Q203493 P30-verified Q46) ⇐ {((Q203493 P30 Q46) nn geo) (Q203493 demo-country yes) (Q203493 P30 Q46)}
(Q580188 P30-verified Q48) ⇐ {((Q580188 P30 Q48) nn geo) (Q580188 demo-country yes) (Q580188 P30 Q48)}
(Q193152 P30-verified Q46) ⇐ {((Q193152 P30 Q46) nn geo) (Q193152 demo-country yes) (Q193152 P30 Q46)}
(Q162192 P30-verified Q18) ⇐ {((Q162192 P30 Q18) nn geo) (Q162192 demo-country yes) (Q162192 P30 Q18)}
(Q747314 P30-verified Q15) ⇐ {((Q747314 P30 Q15) nn geo) (Q747314 demo-country yes) (Q747314 P30 Q15)}
(Q756617 P30-verified Q49) ⇐ {((Q756617 P30 Q49) nn geo) (Q756617 demo-country yes) (Q756617 P30 Q49)}
(Q599613 P30-verified Q46) ⇐ {((Q599613 P30 Q46) nn geo) (Q599613 demo-country yes) (Q599613 P30 Q46)}
(Q2273304 P30-verified Q46) ⇐ {((Q2273304 P30 Q46) nn geo) (Q2273304 demo-country yes) (Q2273304 P30 Q46)}
(Q912052 P30-verified Q48) ⇐ {((Q912052 P30 Q48) nn geo) (Q912052 demo-country yes) (Q912052 P30 Q48)}
(Q176495 P30-verified Q46) ⇐ {((Q176495 P30 Q46) nn geo) (Q176495 demo-country yes) (Q176495 P30 Q46)}
(Q431731 P30-verified Q15) ⇐ {((Q431731 P30 Q15) nn geo) (Q431731 demo-country yes) (Q431731 P30 Q15)}
(Q189988 P30-verified Q15) ⇐ {((Q189988 P30 Q15) nn geo) (Q189988 demo-country yes) (Q189988 P30 Q15)}
(Q2597352 P30-verified Q46) ⇐ {((Q2597352 P30 Q46) nn geo) (Q2597352 demo-country yes) (Q2597352 P30 Q46)}
(Q1649871 P30-verified Q46) ⇐ {((Q1649871 P30 Q46) nn geo) (Q1649871 demo-country yes) (Q1649871 P30 Q46)}
(Q684030 P30-verified Q46) ⇐ {((Q684030 P30 Q46) nn geo) (Q684030 demo-country yes) (Q684030 P30 Q46)}
(Q11774 P30-verified Q48) ⇐ {((Q11774 P30 Q48) nn geo) (Q11774 demo-country yes) (Q11774 P30 Q48)}
(Q204920 P30-verified Q46) ⇐ {((Q204920 P30 Q46) nn geo) (Q204920 demo-country yes) (Q204920 P30 Q46)}
(Q2369784 P30-verified Q46) ⇐ {((Q2369784 P30 Q46) nn geo) (Q2369784 demo-country yes) (Q2369784 P30 Q46)}
(Q7233551 P30-verified Q48) ⇐ {((Q7233551 P30 Q48) nn geo) (Q7233551 demo-country yes) (Q7233551 P30 Q48)}
(Q164079 P30-verified Q46) ⇐ {((Q164079 P30 Q46) nn geo) (Q164079 demo-country yes) (Q164079 P30 Q46)}
(Q2899771 P30-verified Q46) ⇐ {((Q2899771 P30 Q46) nn geo) (Q2899771 demo-country yes) (Q2899771 P30 Q46)}
(Q962 P30-verified Q15) ⇐ {((Q962 P30 Q15) nn geo) (Q962 demo-country yes) (Q962 P30 Q15)}
(Q172579 P30-verified Q46) ⇐ {((Q172579 P30 Q46) nn geo) (Q172579 demo-country yes) (Q172579 P30 Q46)}
(Q953432 P30-verified Q46) ⇐ {((Q953432 P30 Q46) nn geo) (Q953432 demo-country yes) (Q953432 P30 Q46)}
(Q28846511 P30-verified Q46) ⇐ {((Q28846511 P30 Q46) nn geo) (Q28846511 demo-country yes) (Q28846511 P30 Q46)}
(Q15102440 P30-verified Q46) ⇐ {((Q15102440 P30 Q46) nn geo) (Q15102440 demo-country yes) (Q15102440 P30 Q46)}
(Q1232887 P30-verified Q46) ⇐ {((Q1232887 P30 Q46) nn geo) (Q1232887 demo-country yes) (Q1232887 P30 Q46)}
(Q717 P30-verified Q18) ⇐ {((Q717 P30 Q18) nn geo) (Q717 demo-country yes) (Q717 P30 Q18)}
(Q20949725 P30-verified Q46) ⇐ {((Q20949725 P30 Q46) nn geo) (Q20949725 demo-country yes) (Q20949725 P30 Q46)}
(Q34 P30-verified Q46) ⇐ {((Q34 P30 Q46) nn geo) (Q34 demo-country yes) (Q34 P30 Q46)}
(Q16056854 P30-verified Q46) ⇐ {((Q16056854 P30 Q46) nn geo) (Q16056854 demo-country yes) (Q16056854 P30 Q46)}
(Q18285930 P30-verified Q46) ⇐ {((Q18285930 P30 Q46) nn geo) (Q18285930 demo-country yes) (Q18285930 P30 Q46)}
(Q170588 P30-verified Q49) ⇐ {((Q170588 P30 Q49) nn geo) (Q170588 demo-country yes) (Q170588 P30 Q49)}
(Q2415003 P30-verified Q46) ⇐ {((Q2415003 P30 Q46) nn geo) (Q2415003 demo-country yes) (Q2415003 P30 Q46)}
(Q207521 P30-verified Q15) ⇐ {((Q207521 P30 Q15) nn geo) (Q207521 demo-country yes) (Q207521 P30 Q15)}
(Q175276 P30-verified Q46) ⇐ {((Q175276 P30 Q46) nn geo) (Q175276 demo-country yes) (Q175276 P30 Q46)}
(Q870055 P30-verified Q48) ⇐ {((Q870055 P30 Q48) nn geo) (Q870055 demo-country yes) (Q870055 P30 Q48)}
(Q190025 P30-verified Q27611) ⇐ {((Q190025 P30 Q27611) nn geo) (Q190025 demo-country yes) (Q190025 P30 Q27611)}
(Q736727 P30-verified Q46) ⇐ {((Q736727 P30 Q46) nn geo) (Q736727 demo-country yes) (Q736727 P30 Q46)}
(Q4304392 P30-verified Q48) ⇐ {((Q4304392 P30 Q48) nn geo) (Q4304392 demo-country yes) (Q4304392 P30 Q48)}
(Q218023 P30-verified Q15) ⇐ {((Q218023 P30 Q15) nn geo) (Q218023 demo-country yes) (Q218023 P30 Q15)}
(Q43287 P30-verified Q46) ⇐ {((Q43287 P30 Q46) nn geo) (Q43287 demo-country yes) (Q43287 P30 Q46)}
(Q127424576 P30-verified Q15) ⇐ {((Q127424576 P30 Q15) nn geo) (Q127424576 demo-country yes) (Q127424576 P30 Q15)}
(Q4453003 P30-verified Q48) ⇐ {((Q4453003 P30 Q48) nn geo) (Q4453003 demo-country yes) (Q4453003 P30 Q48)}
(Q110362913 P30-verified Q48) ⇐ {((Q110362913 P30 Q48) nn geo) (Q110362913 demo-country yes) (Q110362913 P30 Q48)}
(Q80211 P30-verified Q46) ⇐ {((Q80211 P30 Q46) nn geo) (Q80211 demo-country yes) (Q80211 P30 Q46)}
(Q2712121 P30-verified Q46) ⇐ {((Q2712121 P30 Q46) nn geo) (Q2712121 demo-country yes) (Q2712121 P30 Q46)}
(Q137386301 P30-candidate Q46) ⇐ {(Β¬(Q137386301 P30 Q46)) (Q137386301 demo-country yes) ((Q137386301 P30 Q46) nn geo)}
(Q30890672 P30-candidate Q46) ⇐ {(Β¬(Q30890672 P30 Q46)) (Q30890672 demo-country yes) ((Q30890672 P30 Q46) nn geo)}
(Q1968554 P30-candidate Q46) ⇐ {(Β¬(Q1968554 P30 Q46)) (Q1968554 demo-country yes) ((Q1968554 P30 Q46) nn geo)}
(Q96028967 P30-candidate Q46) ⇐ {(Β¬(Q96028967 P30 Q46)) (Q96028967 demo-country yes) ((Q96028967 P30 Q46) nn geo)}
(Q13125117 P30-candidate Q46) ⇐ {(Β¬(Q13125117 P30 Q46)) (Q13125117 demo-country yes) ((Q13125117 P30 Q46) nn geo)}
(Q976099 P30-candidate Q46) ⇐ {(Β¬(Q976099 P30 Q46)) (Q976099 demo-country yes) ((Q976099 P30 Q46) nn geo)}
(Q42345769 P30-candidate Q46) ⇐ {(Β¬(Q42345769 P30 Q46)) (Q42345769 demo-country yes) ((Q42345769 P30 Q46) nn geo)}
(Q3136869 P30-candidate Q46) ⇐ {(Β¬(Q3136869 P30 Q46)) (Q3136869 demo-country yes) ((Q3136869 P30 Q46) nn geo)}
(Q1530762 P30-candidate Q46) ⇐ {(Β¬(Q1530762 P30 Q46)) (Q1530762 demo-country yes) ((Q1530762 P30 Q46) nn geo)}
(Q950101 P30-candidate Q46) ⇐ {(Β¬(Q950101 P30 Q46)) (Q950101 demo-country yes) ((Q950101 P30 Q46) nn geo)}
(Q138562037 P30-candidate Q46) ⇐ {(Β¬(Q138562037 P30 Q46)) (Q138562037 demo-country yes) ((Q138562037 P30 Q46) nn geo)}
(Q2480041 P30-candidate Q46) ⇐ {(Β¬(Q2480041 P30 Q46)) (Q2480041 demo-country yes) ((Q2480041 P30 Q46) nn geo)}
(Q188736 P30-candidate Q46) ⇐ {(Β¬(Q188736 P30 Q46)) (Q188736 demo-country yes) ((Q188736 P30 Q46) nn geo)}
(Q62389 P30-candidate Q46) ⇐ {(Β¬(Q62389 P30 Q46)) (Q62389 demo-country yes) ((Q62389 P30 Q46) nn geo)}
(Q2526751 P30-candidate Q46) ⇐ {(Β¬(Q2526751 P30 Q46)) (Q2526751 demo-country yes) ((Q2526751 P30 Q46) nn geo)}
(Q96051590 P30-candidate Q46) ⇐ {(Β¬(Q96051590 P30 Q46)) (Q96051590 demo-country yes) ((Q96051590 P30 Q46) nn geo)}
(Q3167772 P30-candidate Q46) ⇐ {(Β¬(Q3167772 P30 Q46)) (Q3167772 demo-country yes) ((Q3167772 P30 Q46) nn geo)}
(Q109534069 P30-candidate Q46) ⇐ {(Β¬(Q109534069 P30 Q46)) (Q109534069 demo-country yes) ((Q109534069 P30 Q46) nn geo)}
(Q814959 P30-candidate Q46) ⇐ {(Β¬(Q814959 P30 Q46)) (Q814959 demo-country yes) ((Q814959 P30 Q46) nn geo)}
(Q126282254 P30-candidate Q46) ⇐ {(Β¬(Q126282254 P30 Q46)) (Q126282254 demo-country yes) ((Q126282254 P30 Q46) nn geo)}
(Q332005 P30-candidate Q46) ⇐ {(Β¬(Q332005 P30 Q46)) (Q332005 demo-country yes) ((Q332005 P30 Q46) nn geo)}
(Q6037274 P30-candidate Q46) ⇐ {(Β¬(Q6037274 P30 Q46)) (Q6037274 demo-country yes) ((Q6037274 P30 Q46) nn geo)}
(Q977566 P30-candidate Q46) ⇐ {(Β¬(Q977566 P30 Q46)) (Q977566 demo-country yes) ((Q977566 P30 Q46) nn geo)}
(Q134302030 P30-candidate Q46) ⇐ {(Β¬(Q134302030 P30 Q46)) (Q134302030 demo-country yes) ((Q134302030 P30 Q46) nn geo)}
(Q85804030 P30-candidate Q46) ⇐ {(Β¬(Q85804030 P30 Q46)) (Q85804030 demo-country yes) ((Q85804030 P30 Q46) nn geo)}
(Q2362063 P30-candidate Q46) ⇐ {(Β¬(Q2362063 P30 Q46)) (Q2362063 demo-country yes) ((Q2362063 P30 Q46) nn geo)}
(Q113411770 P30-candidate Q46) ⇐ {(Β¬(Q113411770 P30 Q46)) (Q113411770 demo-country yes) ((Q113411770 P30 Q46) nn geo)}
(Q1152126 P30-candidate Q46) ⇐ {(Β¬(Q1152126 P30 Q46)) (Q1152126 demo-country yes) ((Q1152126 P30 Q46) nn geo)}
(Q5362837 P30-candidate Q46) ⇐ {(Β¬(Q5362837 P30 Q46)) (Q5362837 demo-country yes) ((Q5362837 P30 Q46) nn geo)}
(Q370372 P30-candidate Q46) ⇐ {(Β¬(Q370372 P30 Q46)) (Q370372 demo-country yes) ((Q370372 P30 Q46) nn geo)}
(Q282475 P30-candidate Q46) ⇐ {(Β¬(Q282475 P30 Q46)) (Q282475 demo-country yes) ((Q282475 P30 Q46) nn geo)}
(Q494625 P30-candidate Q46) ⇐ {(Β¬(Q494625 P30 Q46)) (Q494625 demo-country yes) ((Q494625 P30 Q46) nn geo)}
(Q5124786 P30-candidate Q46) ⇐ {(Β¬(Q5124786 P30 Q46)) (Q5124786 demo-country yes) ((Q5124786 P30 Q46) nn geo)}
(Q4453007 P30-candidate Q46) ⇐ {(Β¬(Q4453007 P30 Q46)) (Q4453007 demo-country yes) ((Q4453007 P30 Q46) nn geo)}
(Q16674373 P30-candidate Q46) ⇐ {(Β¬(Q16674373 P30 Q46)) (Q16674373 demo-country yes) ((Q16674373 P30 Q46) nn geo)}
(Q707128 P30-candidate Q46) ⇐ {(Β¬(Q707128 P30 Q46)) (Q707128 demo-country yes) ((Q707128 P30 Q46) nn geo)}
(Q3267672 P30-candidate Q46) ⇐ {(Β¬(Q3267672 P30 Q46)) (Q3267672 demo-country yes) ((Q3267672 P30 Q46) nn geo)}
(Q6773257 P30-candidate Q46) ⇐ {(Β¬(Q6773257 P30 Q46)) (Q6773257 demo-country yes) ((Q6773257 P30 Q46) nn geo)}
(Q11681694 P30-candidate Q46) ⇐ {(Β¬(Q11681694 P30 Q46)) (Q11681694 demo-country yes) ((Q11681694 P30 Q46) nn geo)}
(Q20507218 P30-candidate Q46) ⇐ {(Β¬(Q20507218 P30 Q46)) (Q20507218 demo-country yes) ((Q20507218 P30 Q46) nn geo)}
(Q12491063 P30-candidate Q46) ⇐ {(Β¬(Q12491063 P30 Q46)) (Q12491063 demo-country yes) ((Q12491063 P30 Q46) nn geo)}
(Q1362278 P30-candidate Q46) ⇐ {(Β¬(Q1362278 P30 Q46)) (Q1362278 demo-country yes) ((Q1362278 P30 Q46) nn geo)}
Reasoning complete. Total unification matches processed: 1074. Total contradictions found: 0.
Reasoning summary: 1074 matches processed, 0 contradictions found.
Parallel unifications activated for 0 distinct fixed relations.
Reasoning complete in 0h0m0.234s – 1074 matches processed, 0 contradictions found.
Ready.
-- 330 ms --
P30-verified facts: 99
  Q203493 (Kingdom of Romania) -> Q46  conf=1.000
  Q193152 (Great Moravia) -> Q46  conf=1.000
  Q189988 (Central African Empire) -> Q15  conf=0.986
  Q7313 (Yuan dynasty) -> Q48  conf=0.981
  Q107258515 (Pahlavi Iran) -> Q48  conf=0.999
P30-candidate facts: 42
  Q85804030 (Sultanate of Tarim) -> Q46  conf=0.527
  Q134302030 (Fetu kingdom) -> Q46  conf=0.527
  Q977566 (sub-Roman Britain) -> Q46  conf=0.527
  Q5362837 (Emirate of Jabal Shammar) -> Q46  conf=0.527
  Q332005 (Principality of Achaea) -> Q46  conf=0.527
Q183 not in the country set of this dump (pruning artifact); skipping Q183 checks

Hidden-layer activation

zelph/nn-compile takes an optional second argument, :relu (the default) or :leaky-relu, which selects what the hidden layers do with a negative pre-activation.

:relu is max(0, x). It has a property worth knowing before training anything small online: a hidden layer whose every unit is negative for every input produces an output of exactly 0 and a gradient of exactly 0, so no further training can move it. That state is absorbing. :leaky-relu is max(0.01 x, x), whose gradient is never exactly zero.

The activation belongs to the compiled view rather than to the graph, so a net trained with one has to be evaluated with the same one. Record the choice yourself if it is not the default β€” a fact in the graph next to the net's layer list is the natural place, and it is where the layer list itself lives.

(def handle (zelph/nn-compile [in hidden out] :leaky-relu))