OpenRepro

← Back to the paper page

Replication record · arXiv:1609.02907

Semi-Supervised Classification with Graph Convolutional Networks

Inconclusive: a registered validity/precision condition was not satisfied

A sealed, agent-designated gate was not satisfied (gcn-citeseer-precision). A registered validity/precision condition was not satisfied, so the claims are not settled; this is not a finding against the paper. The claim verdicts are listed as recorded.

Roles. 0 agent-designated controls excluded; 1 gate.

Prospective re-run of the GCN Citeseer settling test (Table 2, 70.3%) with the plan's precision gate registered as a gate: the authors' train.py at 193c099 (TF 1.0.1 CPU, released early stopping) on a fresh cohort of 100 initializations, seeds 201-300 (101-200 already have results), judged by the sealed interval rule. The fresh mean is 70.372% (SD 4.545, SE 0.454); the enclosing t/bootstrap interval [69.38, 71.27] overlaps the paper's rounding interval, but it extends 0.993 points from the mean, above the 0.731 precision gate, so the result is inconclusive: the cohort does not estimate the mean precisely enough to settle the disputed gap. One of the 100 runs (seed 241, 26.0%, stopped after 15 epochs) is low and stays included.

Plan. Sealed before scored execution; 2 outputs observed before sealing (all of it in the log).

Qualifiers. A scored run is not the direct source of any recorded value: it may be an upstream stage whose output a later scored job read, or a run that was not used (or more scored runs started than the sealed plan declares); the details name each one. 2 scored jobs started after the seal; not the direct source of any recorded value: fresh (event 20).

Scored (headline)
1 of 1 consistent
Gates (validity/precision conditions)
0 of 1 satisfied, 1 not satisfied
Deviations
7 (0 material)
Integrity flags
0
Logged compute
0.33 h over 2 commands and 2 jobs
Primary code (declared before the first recorded result)
github.com/tkipf/gcn at commit 193c0992007610b8ff65f3d3f5d6c418b13a343b (2017-02-20, 'Fix for tf 1.0.0'; last commit before arXiv v4 of 2017-02-22), gcn/train.py with its default flags (model gcn, 16 hidden units, dropout 0.5, weight decay 5e-4 on the first layer, Adam learning rate 0.01, at most 200 epochs, --early_stopping 10), dataset citeseer, bundled gcn/data/ind.citeseer.* files and the authors' utils.load_data split
Commits the recorded results name
tkipf/gcn@193c09920076: 2 results, work-tree HEAD logged when the result was recorded
Work tree when the attempt ended
tkipf/gcn@193c09920076 in /work/src-gcn

Protocol

Sealed with the plan: primary code is github.com/tkipf/gcn at commit 193c0992007610b8ff65f3d3f5d6c418b13a343b (2017-02-20, 'Fix for tf 1.0.0'; last commit before arXiv v4 of 2017-02-22), gcn/train.py with its default flags (model gcn, 16 hidden units, dropout 0.5, weight decay 5e-4 on the first layer, Adam learning rate 0.01, at most 200 epochs, --early_stopping 10), dataset citeseer, bundled gcn/data/ind.citeseer.* files and the authors' utils.load_data split; earlier versions of the authors' code are not used. Settling test per office/plans/1609.02907-settling-plan.md section 2, re-run with the precision gate registered as a gate: the pinned commit is the only code version; the plan finds no evidence-based earlier-code rescue arm and forbids a calendar/version search (an earlier revision would need a separately registered hypothesis).

Run policy (sealed at event 19, plan hash f2ac9056e39d597f8002ef6ba9aa7f59a1467c45579fcff350659706cfbf4d9a): Settling plan office/plans/1609.02907-settling-plan.md (SHA-256 ce660079b1301ff588f1d6260f1a2e4e2764d0b82f0c70c6a93150143da40d67, bound at event 13), re-run with the precision gate registered as role 'gate'. Deviation from the plan's seed list (event 10): exactly 100 fits, the fresh headline cohort seeds 201..300 inclusive, because seeds 101..200 and the 21-23 replay already have results in arxiv-1609-02907-20261020T031257Z; the replay is not repeated. Fits of the authors' train.py at 193c0992007610b8ff65f3d3f5d6c418b13a343b on citeseer with default flags (--early_stopping 10, arm fresh_default), through the collector /work/settle/train_settle.py (SHA-256 09042e262a5cd9e29ee7186c1228a12e3b13ba001821c203ef6c884d9cf76d91, byte-identical to the previous attempt's; only SETTLE-marked lines and the read-only block after the authors' test evaluation differ, diff at event 18), one fresh Python process and TF graph/session per fit, NumPy and TF seed = the seed, PYTHONHASHSEED=0, OMP_NUM_THREADS=4, TF intra-op 4 / inter-op 1, one fit at a time. Scored job 'fresh' (bash /work/settle/run_phase.sh fresh; run_phase.sh SHA-256 b958d63c4456789649086080e1f84df166fbc39b5489766f70566110b96ced7a, run_fit.sh SHA-256 26066328368fb729e2227e21506d7a4796b44b162d1dd6d5395ff717a95d68d0): seeds 201..300 in order, all fits run regardless of results; then scored job 'analysis' (/work/settle/analyze.py SHA-256 ff848f2f01573e25ad3f7a7a793c12e8214e8782d0b406f1f37b0298ab63b521, Python 3.11.17, NumPy 1.26.4, SciPy 1.13.1). Inputs: the eight gcn/data/ind.citeseer.* files, manifest digest f6d99979e783fbab2987faf2d2d9a03e719561d6e8a17e11f91b9e8bc3450f91 (equal to the plan's, event 12); training env Python 3.6.15 (conda-forge, micromamba 1.5.10), tensorflow 1.0.1 CPU, numpy 1.12.1, scipy 0.19.0, networkx 1.11, protobuf 3.19.6, six 1.17.0, decorator 5.1.1 (pip freeze SHA-256 60c1655ef28d2c0f5ba3aac236efecb9a817dea91816cdef5440380b3d96674a and conda-meta list SHA-256 523f2973c9359c20a1f195b73261344b773b3dc09de199cf71e82fca7f5e2c3c, both equal to the previous attempt's; TF _pywrap_tensorflow.so SHA-256 d1a1cfb4b5248a0aee64b19e58e71ed7d6106f0bf1fa384abf241ae2bbd76292); analysis env freeze SHA-256 5a81cde5d4f409222788b0a0da88fff0acdb160f45ab8839d9661cff6aa10424; sandbox image sha256:5b40012093bab4b3177f8f0ae5280ae9ce6ed2b5381a7b2cd0a8f25f82c4963e on a dedicated s-4vcpu-8gb-intel droplet in tor1 (container capped at 3.5 CPUs). Each fit writes epochs.csv, test_predictions.csv and final.csv to the fresh per-job directory /work/out/fresh/gcn/fresh_default/seedNNN and echoes its full-precision records, stderr, exit code and file digests into the job output. No pilots, no unscored training, no per-epoch test evaluation, no checkpoint selection. Resource cap: 120 wall-minutes from the first fit's start (environment setup excluded); no fit starts after the cap and a fit is killed at it. Job retry: one retry of the training job is allowed only for a documented external interruption (host loss or transient I/O), with identical inputs, seeds and command under a new job name and fresh output directory; both are retained and the first complete invocation is scored. Low accuracy, early stopping, OOM, NaNs or optimizer failure never justify a rerun or another seed. Aggregation: Canonical per-fit accuracy = 100*correct/test_n percentage points, correct = agreement of argmax final logits and argmax original y_test over the original 1,000-node test mask, test_n must be 1000; it must agree with the authors' TF masked accuracy within 1e-6 (extraction consistency check only; both values kept; a failure makes that row invalid). A required row is invalid if its final.csv is missing, duplicated, has the wrong key or row count, a non-zero fit exit, test_n != 1000, a non-finite value, or fails the consistency check; any invalid row makes the comparison evidence-incomplete (no registered value is printed, no survivor mean). Fresh cohort seeds 201-300: mu = unweighted mean of all 100 accuracies, s = SD (ddof=1), SE = s/10; t interval mu +/- t_99(0.975)*SE; bootstrap: RandomState(160902), idx = randint(0, 100, size=(50000, 100)), resample means, 2.5/97.5 percentiles (linear interpolation); final interval [L, H] = [min of lower ends, max of upper ends]. Claim gcn-citeseer-compat (headline, role claim) observed value = min(max(70.3, L), H) (within 0.05 of 70.3 iff [L, H] overlaps [70.25, 70.35]); gate gcn-citeseer-precision (role gate, non-headline) observed value = max(mu - L, H - mu) (met iff <= 0.731). Both are printed by the analysis job on their own lines as 'gcn_citeseer_compat_point <repr>' and 'gcn_citeseer_precision_halfwidth <repr>' and read by the registered key rules. Branch (plan section 5): gate missed -> 'Precision gate fails' (engine outcome inconclusive); gate met and overlap -> 'Compatible, precision gate met'; gate met, no overlap -> 'Precise but disjoint'; invalid evidence -> 'Missing/invalid evidence'. Descriptive only (never a criterion): the per-seed table, runs below 20 and 60 points with their stopping epochs and predicted-class counts, stop-reason counts. No exclusion, clipping or survivor-only mean anywhere. Stopping rule: One pass: the scored 'fresh' job (100 fits) then one scored 'analysis' job; stop at the 120-minute cap, not when a result passes; no seeds are added if the precision gate fails; any changed scientific setting requires a new plan citing this attempt. Reruns: not allowed.

Claims

GCN (fixed Planetoid split, mean of 100 random weight initializations) reaches 70.3% test accuracy on Citeseer; judged by whether the final uncertainty interval of a fresh 100-initialization mean overlaps the paper's rounding interval [70.25, 70.35].headline

Consistent
Paper 70.3 percent (test accuracy, percentage points)Observed 70.3 percent (test accuracy, percentage points)Accepted if within ±0.05
GCN (this paper) 70.3 (7s) 81.5 (4s) 79.0 (38s) 66.0 (48s)

Table 2, p. 7, row 'GCN (this paper)', Citeseer column; protocol on p. 7 ('report mean accuracy of 100 runs with random weight initializations') · quote verified on p. 7 · equivalence check · observed/published 1 · registered at event 7, sealed before scored execution

Method, tolerance rationale and evidence

Metric. The point of the final interval [L, H] nearest to 70.3, i.e. min(max(70.3, L), H), in percentage points. It lies within 0.05 of 70.3 exactly when [L, H] overlaps [70.25, 70.35] (the settling plan's compatibility rule, section 4). Fresh cohort: seeds 201..300 inclusive (100 fits, one fresh process and TF graph/session per seed, NumPy and TF seed = the seed, released default stopping --early_stopping 10, final model, no checkpoint restoration). Per-seed canonical accuracy a_i = 100*correct/test_n in percentage points, where correct = number of the 1,000 original test-mask nodes whose argmax logit equals argmax of the original y_test row, test_n must be 1000. mu = unweighted mean of the 100 a_i; s = sample SD (ddof=1); SE = s/10; t interval mu +/- t_99(0.975)*SE (scipy.stats.t.ppf(0.975, 99)); percentile bootstrap: numpy 1.26.4 RandomState(160902), idx = rs.randint(0, 100, size=(50000, 100)), resample means, np.percentile(means, [2.5, 97.5]) with linear interpolation; final interval [L, H] = [min(t_low, boot_low), max(t_high, boot_high)] (the enclosing interval, never the narrower one). All 100 runs are included; no survivor-only mean, no clipping, no replacement of low runs.

Why this tolerance. Fixed in advance by the settling plan (section 4): 70.3 is printed to one decimal, so its rounding interval is [70.25, 70.35]; observed seed uncertainty enters only through the sealed interval formula, never through a scalar tolerance chosen after seeing the SE. Overlap is compatibility, not proof of equivalence.

Event 27. Observed 70.3, delta +0, consistent. Evidence: 00025-job-analysis-stdout.txt (event 25), excerpt 'gcn_citeseer_compat_point 70.3'; verified. Job start event 24. Sealed analysis job over the 100 fresh fits (seeds 201-300, scored job event 22) of the authors train.py at 193c099 (TF 1.0.1 CPU, default stopping): mean 70.372, SE 0.4545; t interval [69.4703, 71.2737], bootstrap [69.379, 70.93]; enclosing interval [69.379, 71.2737] contains 70.3, so its nearest point to 70.3 is 70.3; value read by the registered key rule.

Gates

A gate is a registered validity or precision condition on the test itself, not a claim about the paper. The role was designated by the agent before the seal, with the rationale shown; the harness does not check that judgement. If a gate is not satisfied or not tested, the outcome is inconclusive, which is not a finding against the paper. A gate is judged on its primary-code result only, and gates never count as consistent or inconsistent claims.

Precision gate of the settling plan for the Citeseer 70.3% cell: the final uncertainty interval of the fresh 100-initialization mean extends no more than 0.731 percentage points from that mean (the size of the historical 70.3 - 69.569 gap the test is meant to resolve); otherwise the comparison is inconclusive.gate

Inconsistent
Criterion 0.731 percentage points (precision gate, settling plan section 4)Observed 0.993 percentage points (precision gate, settling plan section 4)Accepted if at most 0.731
GCN (this paper) 70.3 (7s) 81.5 (4s) 79.0 (38s) 66.0 (48s)

Table 2, p. 7, row 'GCN (this paper)', Citeseer column; gate = 70.3 minus our prior mean 69.569 (attempt arxiv-1609-02907-20261018T022718Z, event 29); settling plan section 4 · quote verified on p. 7 · bound check · observed/published 1.358 · registered at event 8, sealed before scored execution

Method, tolerance rationale and evidence

Role. Role: agent-designated gate. No rationale recorded (registered before the engine required one).

Metric. max(mu - L, H - mu), in percentage points, for the fresh cohort mean mu and final interval [L, H]. 'paper_value' here is the plan's gate 0.731 (not a number printed in the paper). Fresh cohort: seeds 201..300 inclusive (100 fits, one fresh process and TF graph/session per seed, NumPy and TF seed = the seed, released default stopping --early_stopping 10, final model, no checkpoint restoration). Per-seed canonical accuracy a_i = 100*correct/test_n in percentage points, where correct = number of the 1,000 original test-mask nodes whose argmax logit equals argmax of the original y_test row, test_n must be 1000. mu = unweighted mean of the 100 a_i; s = sample SD (ddof=1); SE = s/10; t interval mu +/- t_99(0.975)*SE (scipy.stats.t.ppf(0.975, 99)); percentile bootstrap: numpy 1.26.4 RandomState(160902), idx = rs.randint(0, 100, size=(50000, 100)), resample means, np.percentile(means, [2.5, 97.5]) with linear interpolation; final interval [L, H] = [min(t_low, boot_low), max(t_high, boot_high)] (the enclosing interval, never the narrower one). All 100 runs are included; no survivor-only mean, no clipping, no replacement of low runs.

Why this tolerance. Fixed in advance by the settling plan (section 4): the gate is met when the maximum distance from mu to an interval endpoint is <= 0.731 points; otherwise the result is inconclusive; no seeds are added if it fails.

Event 28. Observed 0.993, delta +0.262, inconsistent. Evidence: 00025-job-analysis-stdout.txt (event 25), excerpt 'gcn_citeseer_precision_halfwidth 0.992999999999995'; verified. Job start event 24. Sealed analysis job: max distance from the fresh mean 70.372 to the ends of the enclosing interval [69.379, 71.2737] is 0.993 (lower end, bootstrap), above the 0.731 gate; value read by the registered key rule.

Deviations from the paper's protocol

Notes from the attempt

Assurance

What the harness enforced for this attempt, and what it predates. A legacy attempt is labelled, never rewritten.

Plan sealed
yes
Sealed before scored execution
yes
Evidence bound to artifact excerpts
yes
Values extracted by a sealed rule
yes
Execution manifest for cited jobs
yes
Semantic re-derivation passed
yes
Observed before the seal
2 shell; provenance captured
Engine
report built by repro 0.2.1; attempt created by repro 0.2.1

Redaction before release

Before release, the operator's local home-directory path was replaced with ~ in 4 places. Every event hash from event 13 on changed with it. The seal event's bytes changed (its chain hash); its plan hash did not. The redaction records the outcome, verdicts and values from before it. That record is an operator assertion, checked for internal consistency against the current log, not proof that the original log was unchanged: the original is not part of the record. It matches what the log derives now.

Rule: operator home directory prefix → ~. Reason: Before release: the operator's local home-directory path, copied from a settling-plan file, was replaced with ~ Recorded at event 43; the head the operator states for the log before the redaction is 42ee07add40c3612e75c05bd347cf87e816c86989358658dd7368a241b9ba2a1 (not checkable from this log; a copy anchored elsewhere or the operator's kept original can check it).

Checked from the record: the redacted log's hash chain and artifact digests; re-derivation of every judgement from it; the redaction event's internal consistency (its position, replacement rows, recomputed digests and plan hashes, a single redaction or a second that names the first); and that the outcome, qualifiers, tallies, assurance flags, verdicts and observed values it records as before equal what the log derives now. Not checked: any other text (metric and protocol wording, units, paths), what files and commands meant when they ran, or anything about the log before the redaction, which is not in the record.

Provenance

Driven by
operator:claude-opus-5-5 (Claude Code subagent, AI-driven)
Sandbox
3.5 CPUs, 6.00 GiB memory, no GPU · image repro-sandbox:0.2
Period
2026-10-20T03:57:06.976Z → 2026-10-20T13:02:25.163Z
Event log
44 events, hash-chained; head 1ef063a357fbc913ab4a383e0b30ce717a5ce0a6051a021d0fcc90d517955917
Verify
python3 openrepro_verify.py arxiv-1609-02907-20261020T035706Z checks the chain and every artifact with the Python standard library alone; repro verify also recomputes every quote match, evidence check and verdict and re-derives this report (engine 0.2.1) (the engine that does this is not yet public; the standalone verifier at /verify/ runs the chain and artifact checks).
Right of reply
Authors may submit a response, which is published alongside this report unedited.

← Back to the paper page