OSCR

Hippocampo-neocortical interaction as compressive retrieval-augmented generation.

Code ↔ Paper

29 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 29 matches
  1. [1] § Methods › Neocortical learning of sequential structure for inference and planning ↔ inference/rag_composition.py, lines 1–53 · score 0.89 · ef WEST gh, ab EAST cd, gh NORTH, SOUTH ef, inference, traces
  2. [2] § Results › Inference from memory as retrieval-augmented generation ↔ inference/rag_composition.py, lines 1–53 · score 0.84 · ef WEST gh, ab EAST cd, gh NORTH, SOUTH ef, inference, baselines
  3. [3] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › The compression and encoding of episodic memory ↔ full_model/memory_simulation.py, lines 1126–1165 · score 0.81 · sentence boundaries, coordinating conjunction, spaCy, commas, splitting, phrases
  4. [4] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › The compression and encoding of episodic memory ↔ full_model/narratives/utils.py, lines 455–491 · score 0.69 · short clause, spaCy, conjunctions, splitting, phrases, perplexity
  5. [5] § Results › Modelling consolidation and forgetting ↔ full_model/semantic_memory_standalone.py, lines 83–189 · score 0.66 · asking factual questions, correct answer, Semantic memory, Remember, embedding, prompt
  6. [6] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › Modelling the hippocampus ↔ scripts/dual_mhn.py, lines 1–19 · score 0.65 · modern Hopfield network, sequence memory, MHN, heteroassociative, autoassociative, vector
  7. [7] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › Analysing the HIPPOCORPUS and NFRD datasets ↔ full_model/collate_all_figures.py, lines 281–314 · score 0.65 · Naturalistic Free Recall, baseball, eyespy, oregontrail, pieman, recalled stories
  8. [8] § Results › Inspecting the hidden representations supporting inference ↔ inference/plot_family_reps.py, lines 449–483 · score 0.65 · Pearson correlation, pre trained GPT, family tree, random walk, location, nodes
  9. [9] § Results › Gist-based distortions and prior experience ↔ full_model/narratives/collate_figures.py, lines 2324–2364 · score 0.64 · L6 v2, MiniLM, cosine distance, Bergman, Roediger, PCA
  10. [10] § Results › The compression and encoding of episodic memory ↔ full_model/semantic_memory_standalone.py, lines 83–189 · score 0.63 · L6 v2, MiniLM, correct answer, Semantic memory, accuracy, embeddings
  11. [11] § Results › Neocortical learning of sequential structure for problem solving ↔ inference/graph_sequence_model.py, lines 737–884 · score 0.63 · trained spatial model, ab EAST, aggregated, child, loss, graphs
  12. [12] § Results › Gist-based distortions and prior experience ↔ full_model/narratives/utils.py, lines 27–51 · score 0.61 · young men, Ghosts, War, heard, Egulac, night
  13. [13] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › The compression and encoding of episodic memory ↔ full_model/narratives/utils.py, lines 289–299 · score 0.61 · XRAG_TOKEN, single token, phrases, perplexity, surprising
  14. [14] § Methods › Summary of models trained ↔ full_model/narratives/stories.py, lines 28–77 · score 0.61 · ROC Stories, LoRA, xRAG, v0, Instruct, Mistral
  15. [15] § Results › Modelling consolidation and forgetting ↔ full_model/memory_simulation.py, lines 176–297 · score 0.60 · asking factual questions, correct answer, Semantic memory, Remember, embedding, prompt
  16. [16] § Methods › Summary of models trained ↔ full_model/narratives/stories.py, lines 28–77 · score 0.60 · ROC Stories, LoRA, xRAG, v0, Instruct, Mistral
  17. [17] § Results › Neocortical learning of sequential structure for problem solving ↔ inference/plot_family_reps.py, lines 63–126 · score 0.58 · directed graph, family tree, children, grandchild, spouse, sibling
  18. [18] § Results › Event extension and contraction ↔ full_model/narratives/collate_figures.py, lines 1187–1291 · score 0.58 · omission errors, extension errors, updated stories, incomplete
  19. [19] § Results › Neocortical learning of sequential structure for problem solving ↔ inference/graph_sequence_model.py, lines 737–884 · score 0.58 · EAST zr, sz WEST, invalid, aggregated, entity, losses
  20. [20] § Results ↔ scripts/dual_mhn.py, lines 1–19 · score 0.57 · modern Hopfield network, nearest stored, MHN, sequence, memory
  21. [21] § Results › The compression and encoding of episodic memory ↔ full_model/narratives/bartlett_twostage.py, lines 377–436 · score 0.54 · L6 v2, MiniLM, original story, cosine, background, tokens
  22. [22] § Results › Changes in memory content over time ↔ full_model/LLM ratings/HIPPOCORPUS attributes comparison.ipynb, lines 1–52 · score 0.54 · OpenAI API, mini, rich, concrete, metrics, abstract
  23. [23] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › Modelling the hippocampus ↔ scripts/dual_mhn.py, lines 22–36 · score 0.53 · inverse temperature, softmax, MHN, heteroassociatively, autoassociative, Retrieval
  24. [24] § Results › Inspecting the hidden representations supporting inference ↔ inference/plot_family_reps.py, lines 486–519 · score 0.53 · family tree, pre trained, random walk, hidden, location, medium
  25. [25] § Methods › Summary of models trained ↔ inference/plot_family_reps.py, lines 449–483 · score 0.53 · train GPT, family trees, random walks, medium, graphs, inference
  26. [26] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › Analysing the HIPPOCORPUS and NFRD datasets ↔ full_model/LLM ratings/HIPPOCORPUS attributes comparison.ipynb, lines 1–52 · score 0.52 · OpenAI API, attributes, mini, HIPPOCORPUS, gpt, prompt
  27. [27] § Methods › Modelling hippocampal-neocortical interaction as retrieval-augmented generation › Analysing the HIPPOCORPUS and NFRD datasets ↔ full_model/LLM ratings/llm_specificity_analysis.py, lines 98–150 · score 0.51 · baseball, eyespy, oregontrail, pieman, recalled stories, NFRD
  28. [28] § Results › Changes in memory content over time ↔ full_model/LLM ratings/llm_specificity_analysis.py, lines 52–80 · score 0.50 · OpenAI API, mini, rich, concrete, abstract, gpt
  29. [29] § Methods › Summary of models trained ↔ full_model/narratives/bartlett_encoding_vs_consolidation.py, lines 47–87 · score 0.50 · LoRA, xRAG, v0, Instruct, Bartlett, Mistral

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 553 lines · 22 KB · MIT · 4 matches

  1. """
  2. Analogous to plot_reps.py but for the family tree model.
  3. Instead of spatial positions on a 3x3 grid, each person in the family tree
  4. has a *generation* (grandparent=0, parent=1, child=2). We ask whether
  5. the model's latent representations capture this generational structure.
  6. Graphs are created in code (so generation assignments are known), then
  7. random walks are sampled from them and fed to the model.
  8. """
  9. import random
  10. import string
  11. import time
  12. import numpy as np
  13. import torch
  14. import logging
  15. import networkx as nx
  16. from transformers import GPT2LMHeadModel, GPT2TokenizerFast
  17. from sklearn.decomposition import PCA
  18. from umap import UMAP
  19. import matplotlib.pyplot as plt
  20. from scipy.stats import pearsonr
  21. def set_seed(seed):
  22. random.seed(seed)
  23. np.random.seed(seed)
  24. torch.manual_seed(seed)
  25. if torch.cuda.is_available():
  26. torch.cuda.manual_seed_all(seed)
  27. torch.backends.cudnn.deterministic = True
  28. torch.backends.cudnn.benchmark = False
  29. set_seed(321)
  30. logging.basicConfig(level=logging.DEBUG,
  31. format='%(asctime)s - %(levelname)s - %(message)s')
  32. # ---------------------------------------------------------------------------
  33. # Family tree construction (with collision-free names)
  34. # ---------------------------------------------------------------------------
  35. _GENERATION_OFFSETS = {
  36. "PARENT_OF": 1, "CHILD_OF": -1,
  37. "GRANDPARENT_OF": 2, "GRANDCHILD_OF": -2,
  38. "SPOUSE_OF": 0, "SIBLING_OF": 0,
  39. }
  40. GENERATION_LABELS = {0: "Grandparent", 1: "Parent", 2: "Child"}
  41. def _generate_unique_names(n):
  42. """Generate n unique 2-letter lowercase names."""
  43. names = set()
  44. while len(names) < n:
  45. names.add("".join(random.choices(string.ascii_lowercase, k=2)))
  46. return list(names)
  47. def build_family_tree():
  48. """Build a random extended family tree with guaranteed unique names.
  49. Structure (10 people, 3 generations):
  50. Gen 0: GP1a, GP1b, GP2a, GP2b (4 grandparents)
  51. Gen 1: Parent1, Aunt/Uncle1, Parent2, Aunt/Uncle2 (4 parents-level)
  52. Gen 2: Child1, Child2 (2 children)
  53. Returns (G, node_names, generation_map)
  54. """
  55. names = _generate_unique_names(10)
  56. gp1a, gp1b = names[0], names[1]
  57. parent1, uncle1 = names[2], names[3]
  58. gp2a, gp2b = names[4], names[5]
  59. parent2, aunt2 = names[6], names[7]
  60. child1, child2 = names[8], names[9]
  61. # Build all relationships
  62. relationships = {
  63. # Grandparent family 1
  64. gp1a: {"SPOUSE_OF": [gp1b], "PARENT_OF": [parent1, uncle1]},
  65. gp1b: {"SPOUSE_OF": [gp1a], "PARENT_OF": [parent1, uncle1]},
  66. uncle1: {"CHILD_OF": [gp1a, gp1b], "SIBLING_OF": [parent1]},
  67. # Grandparent family 2
  68. gp2a: {"SPOUSE_OF": [gp2b], "PARENT_OF": [parent2, aunt2]},
  69. gp2b: {"SPOUSE_OF": [gp2a], "PARENT_OF": [parent2, aunt2]},
  70. aunt2: {"CHILD_OF": [gp2a, gp2b], "SIBLING_OF": [parent2]},
  71. # Base family
  72. parent1: {
  73. "SPOUSE_OF": [parent2], "PARENT_OF": [child1, child2],
  74. "CHILD_OF": [gp1a, gp1b], "SIBLING_OF": [uncle1],
  75. },
  76. parent2: {
  77. "SPOUSE_OF": [parent1], "PARENT_OF": [child1, child2],
  78. "CHILD_OF": [gp2a, gp2b], "SIBLING_OF": [aunt2],
  79. },
  80. child1: {"CHILD_OF": [parent1, parent2], "SIBLING_OF": [child2]},
  81. child2: {"CHILD_OF": [parent1, parent2], "SIBLING_OF": [child1]},
  82. }
  83. # Add grandparent <-> grandchild edges
  84. for gp in [gp1a, gp1b, gp2a, gp2b]:
  85. relationships[gp].setdefault("GRANDPARENT_OF", []).extend([child1, child2])
  86. for ch in [child1, child2]:
  87. relationships[ch].setdefault("GRANDCHILD_OF", []).extend([gp1a, gp1b, gp2a, gp2b])
  88. # Build directed graph
  89. G = nx.DiGraph()
  90. for person, rels in relationships.items():
  91. G.add_node(person)
  92. for rel_type, targets in rels.items():
  93. for target in targets:
  94. G.add_edge(person, target, relationship=rel_type)
  95. node_names = list(G.nodes)
  96. generation_map = {
  97. gp1a: 0, gp1b: 0, gp2a: 0, gp2b: 0,
  98. parent1: 1, uncle1: 1, parent2: 1, aunt2: 1,
  99. child1: 2, child2: 2,
  100. }
  101. return G, node_names, generation_map
  102. # ---------------------------------------------------------------------------
  103. # Random walk generation
  104. # ---------------------------------------------------------------------------
  105. def generate_random_walk(G, walk_length=50):
  106. """Return a walk string: node REL node REL … node"""
  107. current = random.choice(list(G.nodes))
  108. tokens = [current]
  109. for _ in range(walk_length):
  110. neighbors = list(G.successors(current))
  111. if not neighbors:
  112. break
  113. nxt = random.choice(neighbors)
  114. rel = G.edges[current, nxt]["relationship"]
  115. tokens.append(rel)
  116. tokens.append(nxt)
  117. current = nxt
  118. return " ".join(tokens)
  119. # ---------------------------------------------------------------------------
  120. # GPT wrapper & embedding extraction
  121. # ---------------------------------------------------------------------------
  122. def _default_entity_tokenizer_dir():
  123. """Return the canonical entity tokenizer path if it exists, else None."""
  124. from pathlib import Path
  125. path = Path(__file__).resolve().parent / "tokenizers" / "gpt2-medium_2letter_entities"
  126. if (path / "tokenizer.json").exists() or (path / "vocab.json").exists():
  127. return str(path)
  128. return None
  129. class GPTWrapper:
  130. def __init__(self, model_name="gpt2", tokenizer_name=None):
  131. logging.info(f"Loading model: {model_name}")
  132. self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  133. self.model = GPT2LMHeadModel.from_pretrained(
  134. model_name, output_hidden_states=True
  135. )
  136. self.model.to(self.device)
  137. self.model.eval()
  138. emb_size = self.model.get_input_embeddings().weight.shape[0]
  139. # Resolve tokenizer: explicit > canonical entity tokenizer > model dir
  140. tok_path = tokenizer_name or model_name
  141. if tokenizer_name is None and not model_name.startswith("gpt2"):
  142. canon = _default_entity_tokenizer_dir()
  143. if canon is not None:
  144. candidate = GPT2TokenizerFast.from_pretrained(canon)
  145. if len(candidate) == emb_size:
  146. tok_path = canon
  147. logging.info(f"Using canonical entity tokenizer from {canon}")
  148. self.tokenizer = GPT2TokenizerFast.from_pretrained(tok_path)
  149. self.tokenizer.pad_token = self.tokenizer.eos_token
  150. logging.info(f"Model and tokenizer loaded successfully (vocab={len(self.tokenizer)}, emb={emb_size}).")
  151. def get_hidden_states_with_offsets(self, prompt, layer_idx):
  152. enc = self.tokenizer(
  153. prompt, return_tensors="pt", truncation=True,
  154. return_offsets_mapping=True,
  155. )
  156. offsets = enc["offset_mapping"][0].tolist()
  157. # Remove offset_mapping before passing to model (it's not a model input)
  158. model_inputs = {k: v.to(self.device) for k, v in enc.items() if k != "offset_mapping"}
  159. with torch.no_grad():
  160. out = self.model(**model_inputs)
  161. hidden_states = out.hidden_states[layer_idx].squeeze(0).detach().cpu().numpy()
  162. return hidden_states, offsets
  163. def get_all_hidden_states_with_offsets(self, prompt):
  164. """Single forward pass returning hidden states for *every* layer.
  165. Returns ``(all_hidden_states, offsets)`` where
  166. ``all_hidden_states[i]`` is the numpy array for layer *i*.
  167. """
  168. enc = self.tokenizer(
  169. prompt, return_tensors="pt", truncation=True,
  170. return_offsets_mapping=True,
  171. )
  172. offsets = enc["offset_mapping"][0].tolist()
  173. model_inputs = {k: v.to(self.device) for k, v in enc.items() if k != "offset_mapping"}
  174. with torch.no_grad():
  175. out = self.model(**model_inputs)
  176. all_hs = [h.squeeze(0).detach().cpu().numpy() for h in out.hidden_states]
  177. return all_hs, offsets
  178. def substring_positions(haystack, needle):
  179. result = []
  180. start = 0
  181. while True:
  182. idx = haystack.find(needle, start)
  183. if idx == -1:
  184. break
  185. result.append([idx, idx + len(needle)])
  186. start = idx + 1
  187. return result
  188. def gather_embeddings_for_span(offsets, hidden_states, span, flanking=True):
  189. """Gather hidden-state vectors for a character span.
  190. If *flanking* is True (default), the tokens immediately before and after
  191. the entity token(s) are included in the average. For the custom entity
  192. tokenizer these flanking tokens are the space characters that sit between
  193. the entity and the neighbouring relation token, and they carry rich
  194. contextual information about the entity's role in the family tree.
  195. """
  196. start_needed, end_needed = span
  197. n_tokens = len(offsets)
  198. entity_idxs = [i for i, (s, e) in enumerate(offsets)
  199. if not (e <= start_needed or s >= end_needed)]
  200. if not entity_idxs:
  201. return None
  202. if flanking:
  203. all_idxs = set(entity_idxs)
  204. min_idx, max_idx = min(entity_idxs), max(entity_idxs)
  205. if min_idx > 0:
  206. all_idxs.add(min_idx - 1)
  207. if max_idx < n_tokens - 1:
  208. all_idxs.add(max_idx + 1)
  209. vecs = [hidden_states[i] for i in sorted(all_idxs)]
  210. else:
  211. vecs = [hidden_states[i] for i in entity_idxs]
  212. return np.mean(vecs, axis=0)
  213. def average_locations_via_substring(prompt, offsets, hidden_states, locs,
  214. flanking=True, second_half_only=False):
  215. """Average entity representations across all occurrences in the prompt.
  216. When *flanking* is True the representation for each occurrence includes
  217. the entity token and its immediately adjacent tokens (typically spaces).
  218. When *second_half_only* is True, only occurrences whose character start
  219. position is in the second half of the prompt string are used.
  220. """
  221. half = len(prompt) // 2 if second_half_only else 0
  222. loc_means = {}
  223. for loc in locs:
  224. pos_list = substring_positions(prompt, loc)
  225. if not pos_list:
  226. continue
  227. if second_half_only:
  228. pos_list = [(s, e) for s, e in pos_list if s >= half]
  229. if not pos_list:
  230. continue
  231. vecs = [v for s, e in pos_list
  232. for v in [gather_embeddings_for_span(offsets, hidden_states,
  233. (s, e), flanking=flanking)]
  234. if v is not None]
  235. if vecs:
  236. loc_means[loc] = np.mean(vecs, axis=0)
  237. return loc_means
  238. # ---------------------------------------------------------------------------
  239. # Distance & correlation helpers
  240. # ---------------------------------------------------------------------------
  241. def calc_pearson_correlation(node_names, loc_mean_repr, generation_map):
  242. rec = [n for n in node_names if n in loc_mean_repr and n in generation_map]
  243. if len(rec) < 2:
  244. return float("nan")
  245. gen_d, rep_d = [], []
  246. for i in range(len(rec)):
  247. for j in range(i + 1, len(rec)):
  248. gen_d.append(abs(generation_map[rec[i]] - generation_map[rec[j]]))
  249. rep_d.append(np.linalg.norm(loc_mean_repr[rec[i]] - loc_mean_repr[rec[j]]))
  250. if len(gen_d) < 2:
  251. return float("nan")
  252. r, _ = pearsonr(gen_d, rep_d)
  253. return r
  254. def gather_boxplot_data(node_names, loc_mean_repr, generation_map):
  255. rec = [n for n in node_names if n in loc_mean_repr and n in generation_map]
  256. dist_map = {}
  257. for i in range(len(rec)):
  258. for j in range(i + 1, len(rec)):
  259. gd = abs(generation_map[rec[i]] - generation_map[rec[j]])
  260. rd = np.linalg.norm(loc_mean_repr[rec[i]] - loc_mean_repr[rec[j]])
  261. dist_map.setdefault(gd, []).append(rd)
  262. return dist_map
  263. def merge_dist_map(gmap, dmap):
  264. for d, lst in dmap.items():
  265. gmap.setdefault(d, []).extend(lst)
  266. # ---------------------------------------------------------------------------
  267. # Plotting
  268. # ---------------------------------------------------------------------------
  269. def plot_all_runs_in_one_pca(all_points, all_edges, model_name, reducer="pca"):
  270. if not all_points:
  271. return
  272. X = np.array([p["vector"] for p in all_points])
  273. X_c = X - X.mean(axis=0, keepdims=True)
  274. dim_red = PCA(n_components=2) if reducer == "pca" else UMAP(n_components=2)
  275. X_2d = dim_red.fit_transform(X_c)
  276. for i, c in enumerate(X_2d):
  277. all_points[i]["x2d"], all_points[i]["y2d"] = c[0], c[1]
  278. gens = sorted(set(p["generation"] for p in all_points))
  279. cmap = plt.get_cmap("tab10")
  280. plt.figure(figsize=(3, 3))
  281. for gi, g in enumerate(gens):
  282. pts = [p for p in all_points if p["generation"] == g]
  283. plt.scatter([p["x2d"] for p in pts], [p["y2d"] for p in pts],
  284. color=cmap(gi % 10), label=GENERATION_LABELS.get(g, f"Gen {g}"))
  285. for e in all_edges:
  286. i_, j_ = e["u_index"], e["v_index"]
  287. plt.plot([all_points[i_]["x2d"], all_points[j_]["x2d"]],
  288. [all_points[i_]["y2d"], all_points[j_]["y2d"]],
  289. "--", color=cmap(gens.index(all_points[i_]["generation"]) % 10), alpha=0.0)
  290. if model_name == "gpt2-medium":
  291. plt.legend()
  292. plt.savefig(f"{model_name}_family_combined_{reducer}.png", dpi=200, bbox_inches="tight")
  293. logging.info(f"Saved {model_name}_family_combined_{reducer}.png")
  294. def boxplot_of_dist_map(gmap, model_name):
  295. if not gmap:
  296. return
  297. keys = sorted(gmap.keys())
  298. tick = {0: "Same gen.", 1: "1 gen.", 2: "2 gen."}
  299. plt.figure(figsize=(3, 2))
  300. plt.boxplot([gmap[k] for k in keys], showfliers=False, showmeans=False)
  301. plt.xticks(range(1, len(keys) + 1), [tick.get(k, f"{k} gen.") for k in keys])
  302. plt.xlabel("Generational distance")
  303. plt.ylabel("Distance between rep.s")
  304. plt.savefig(f"{model_name}_family_boxplot.png", dpi=200, bbox_inches="tight")
  305. logging.info(f"Saved {model_name}_family_boxplot.png")
  306. def plot_embeddings_with_graph_edges(
  307. mean_repr, G, generation_map, model_name="gpt2", reducer="pca", title="",
  308. ):
  309. keys = sorted(mean_repr.keys())
  310. if len(keys) < 2:
  311. return
  312. X = np.array([mean_repr[k] for k in keys])
  313. X_c = X - X.mean(axis=0, keepdims=True)
  314. dim_red = PCA(n_components=2) if reducer == "pca" else UMAP(n_components=2)
  315. X_2d = dim_red.fit_transform(X_c)
  316. loc_idx = {k: i for i, k in enumerate(keys)}
  317. gens = sorted(set(generation_map.get(k, -1) for k in keys))
  318. cmap = plt.get_cmap("tab10")
  319. plt.figure(figsize=(3, 3))
  320. for gi, g in enumerate(gens):
  321. gk = [k for k in keys if generation_map.get(k, -1) == g]
  322. ix = [loc_idx[k] for k in gk]
  323. plt.scatter(X_2d[ix, 0], X_2d[ix, 1], color=cmap(gi % 10),
  324. label=GENERATION_LABELS.get(g, f"Gen {g}"))
  325. for i, k in zip(ix, gk):
  326. plt.annotate(k, (X_2d[i, 0], X_2d[i, 1]), xytext=(3, 3), textcoords="offset points")
  327. for u, v in G.edges():
  328. if u in loc_idx and v in loc_idx:
  329. i, j = loc_idx[u], loc_idx[v]
  330. plt.plot([X_2d[i, 0], X_2d[j, 0]], [X_2d[i, 1], X_2d[j, 1]], "k--", alpha=0.3)
  331. plt.title(title + f" ({reducer.upper()})")
  332. plt.legend()
  333. ts = str(int(time.time()))
  334. plt.savefig(f"{model_name}_family_single_{reducer}_{ts}.png", dpi=200, bbox_inches="tight")
  335. logging.info(f"Saved {model_name}_family_single_{reducer}_{ts}.png")
  336. # ---------------------------------------------------------------------------
  337. # Analysis functions
  338. # ---------------------------------------------------------------------------
  339. def multi_tree_allinone(model_name="gpt2", layer_idx=10, n_runs=300,
  340. walk_length=50, reducer="pca"):
  341. logging.info(f"multi_tree_allinone: {model_name}, {n_runs} runs")
  342. wrapper = GPTWrapper(model_name)
  343. all_points, all_edges, global_dist = [], [], {}
  344. pt_idx = 0
  345. for run in range(n_runs):
  346. logging.info(f"=== RUN {run+1}/{n_runs} ===")
  347. G, node_names, gen_map = build_family_tree()
  348. prompt = generate_random_walk(G, walk_length)
  349. hs, off = wrapper.get_hidden_states_with_offsets(prompt, layer_idx)
  350. loc_repr = average_locations_via_substring(prompt, off, hs, node_names)
  351. node_pt = {}
  352. for n in (n for n in node_names if n in loc_repr):
  353. all_points.append({"run_idx": run, "node_name": n,
  354. "vector": loc_repr[n], "generation": gen_map[n]})
  355. node_pt[n] = pt_idx; pt_idx += 1
  356. for u, v in G.edges():
  357. if u in node_pt and v in node_pt:
  358. all_edges.append({"u_index": node_pt[u], "v_index": node_pt[v]})
  359. merge_dist_map(global_dist, gather_boxplot_data(node_names, loc_repr, gen_map))
  360. plot_all_runs_in_one_pca(all_points, all_edges, model_name, reducer)
  361. boxplot_of_dist_map(global_dist, model_name)
  362. def single_tree_with_edge_lines(model_name="gpt2", layer_idx=10,
  363. walk_length=100000, reducer="pca"):
  364. G, node_names, gen_map = build_family_tree()
  365. prompt = generate_random_walk(G, walk_length)
  366. wrapper = GPTWrapper(model_name)
  367. hs, off = wrapper.get_hidden_states_with_offsets(prompt, layer_idx)
  368. loc_repr = average_locations_via_substring(prompt, off, hs, node_names)
  369. plot_embeddings_with_graph_edges(loc_repr, G, gen_map, model_name, reducer,
  370. title=f"{model_name} L{layer_idx}")
  371. def rolling_mean(vals, w=3):
  372. return [np.mean(vals[max(0, i - w + 1):i + 1]) for i in range(len(vals))]
  373. def correlation_vs_context_length_multi(models, layer_idx=12, context_lengths=None,
  374. n_runs=100, rolling_window=1):
  375. if context_lengths is None:
  376. context_lengths = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
  377. labels = {"outputs_tree": "Our model", "gpt2-medium": "Pre-trained GPT-2"}
  378. # Pre-build graphs so both models see the same data
  379. runs = [(build_family_tree(), ) for _ in range(n_runs)]
  380. plt.figure(figsize=(3, 2))
  381. for model_name in models:
  382. wrapper = GPTWrapper(model_name)
  383. avg_corrs = []
  384. for L in context_lengths:
  385. corrs = []
  386. for (G, node_names, gen_map), in runs:
  387. prompt = generate_random_walk(G, L)
  388. hs, off = wrapper.get_hidden_states_with_offsets(prompt, layer_idx)
  389. loc_repr = average_locations_via_substring(prompt, off, hs, node_names)
  390. c = calc_pearson_correlation(node_names, loc_repr, gen_map)
  391. if not np.isnan(c):
  392. corrs.append(c)
  393. m = np.mean(corrs) if corrs else float("nan")
  394. avg_corrs.append(m)
  395. logging.info(f"[{model_name}] ctx={L}, r={m:.3f}")
  396. if rolling_window > 1:
  397. plt.plot(context_lengths, rolling_mean(avg_corrs, rolling_window),
  398. "o-", label=f"{model_name} (rolled)")
  399. color = "blue" if model_name == "gpt2-medium" else "red"
  400. plt.plot(context_lengths, avg_corrs, "o--",
  401. label=labels.get(model_name, model_name), color=color)
  402. plt.xlabel("Context length"); plt.ylabel("Pearson correlation")
  403. plt.title("Family: Correlation vs context length"); plt.legend()
  404. plt.savefig("comparison_family_correlation_vs_context_length.png", dpi=200, bbox_inches="tight")
  405. def correlation_vs_layer_multi(models, layer_indices=None, n_runs=100, walk_length=50):
  406. if layer_indices is None:
  407. layer_indices = list(range(24))
  408. labels = {"outputs_tree": "Our model", "gpt2-medium": "Pre-trained GPT-2"}
  409. # Pre-build graphs + walks so both models see identical data
  410. runs = []
  411. for _ in range(n_runs):
  412. G, names, gm = build_family_tree()
  413. prompt = generate_random_walk(G, walk_length)
  414. runs.append((prompt, G, names, gm))
  415. plt.figure(figsize=(3, 2))
  416. for model_name in models:
  417. wrapper = GPTWrapper(model_name)
  418. avg = {}
  419. for layer_idx in layer_indices:
  420. corrs = []
  421. logging.info(f" Layer {layer_idx} for {model_name}...")
  422. for prompt, G, names, gm in runs:
  423. hs, off = wrapper.get_hidden_states_with_offsets(prompt, layer_idx)
  424. loc_repr = average_locations_via_substring(prompt, off, hs, names)
  425. c = calc_pearson_correlation(names, loc_repr, gm)
  426. if not np.isnan(c):
  427. corrs.append(c)
  428. avg[layer_idx] = np.mean(corrs) if corrs else float("nan")
  429. logging.info(f"[{model_name}] L{layer_idx} r={avg[layer_idx]:.3f}")
  430. color = "blue" if model_name == "gpt2-medium" else "red"
  431. plt.plot(list(avg.keys()), list(avg.values()), "o--",
  432. label=labels.get(model_name, model_name), color=color)
  433. plt.xlabel("Layer index"); plt.ylabel("Pearson correlation")
  434. plt.title("Family: Correlation vs layer"); plt.legend()
  435. plt.savefig("comparison_family_correlation_vs_layer.png", dpi=200, bbox_inches="tight")
  436. # ---------------------------------------------------------------------------
  437. # Entry point
  438. # ---------------------------------------------------------------------------
  439. if __name__ == "__main__":
  440. import argparse
  441. parser = argparse.ArgumentParser()
  442. parser.add_argument("--smoke", action="store_true")
  443. args = parser.parse_args()
  444. model_to_test = "outputs_tree"
  445. if args.smoke:
  446. n_pca, n_single, n_corr = 5, 1, 3
  447. ctx_lens = [10, 30]
  448. layers = [6, 12, 18]
  449. else:
  450. n_pca, n_single, n_corr = 300, 3, 100
  451. ctx_lens = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
  452. layers = list(range(24))
  453. multi_tree_allinone("gpt2-medium", 12, n_pca, 50, "pca")
  454. for _ in range(n_single):
  455. single_tree_with_edge_lines("gpt2-medium", 12, 100000, "pca")
  456. multi_tree_allinone(model_to_test, 12, n_pca, 50, "pca")
  457. for _ in range(n_single):
  458. single_tree_with_edge_lines(model_to_test, 12, 100000, "pca")
  459. models = ["gpt2-medium", model_to_test]
  460. correlation_vs_context_length_multi(models, 12, ctx_lens, n_corr)
  461. correlation_vs_layer_multi(models, layers, n_corr, 50)

plot_family_reps.py at commit ed8901b, under MIT · at the source

Overview

  1. Nuffield Department of Clinical Neurosciences, University of Oxford, Oxford, UK
  2. Institute of Cognitive Neuroscience, University College London, London, UK
  3. Queen Square Institute of Neurology, University College London, London, UK
Institutions: University of Oxford (United Kingdom); UCL Queen Square Institute of Neurology (United Kingdom); University College London (United Kingdom)
Journal: Nature communications, volume 17, issue 1, article 7971
Dates: received 5 September 2025; accepted 2 June 2026; published online 25 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-74357-6 · PMID 42350398 · PMCID PMC13447824 · OpenAlex W7165934846
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: computational modeling (no new data) (modality), human (organism)
Methods: Statistics, Machine learning
Keywords: Cognitive neuroscience, Human behaviour, Computational neuroscience
MeSH: Hippocampus*, Memory, Episodic*, Mental Recall*, Neocortex*, Animals, Computer Simulation, Humans, Memory, Short-Term, Models, Neurological, Nerve Net, Problem Solving (* major topic)
Topic: Memory and Neural Mechanisms (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 1 paper (Europe PMC); 118 references in the paper

Abstract

Many aspects of learning, memory, and problem solving involve interplay between episodic (hippocampal) and semantic (neocortical) systems, but the neural mechanisms supporting this are unclear. We present a computational model in which sequential experiences are encoded in hippocampus in compressed form and replayed to train a neocortical generative network. This network captures the gist of specific episodes and extracts statistical patterns that generalise to new situations, enabling efficient reconstruction of the past and prediction of the future. The two systems interact during encoding, recall and problem solving, with the hippocampus retrieving relevant episodic information into working memory as a basis for generation using the ‘general knowledge’ of the neocortical network. We simulate this interaction as ‘retrieval-augmented generation’, with the addition of mechanisms to compress episodic memories into hippocampus and to consolidate them into neocortex. The model explains changes to memories over time, including schema-based distortions, and shows how episodic and semantic memory contribute to problem solving.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 29 matches between paragraphs and lines of code.

ellie-as/hippocampal-neocortical-RAG

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: ed8901b5cb93a2a4b68d9524e75efcb921798aec, 28 May 2026
Languages: Python (69), Jupyter (2)
Size: 318 files, 71 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (requirements.txt), 2 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (33 files), PyTorch (29 files), Hugging Face Transformers (27 files), Matplotlib (21 files), pandas (15 files), SciPy (12 files), scikit-learn (9 files), UMAP (5 files), NetworkX (4 files), seaborn (3 files), Pillow (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
73 files

Zenodo 20215885

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
At the source:

Code availability

Code for all simulations can be found at https://github.com/ellie-as/hippocampal-neocortical-RAG. This repository is archived on Zenodo114.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 71 scripts, each with its path and the digest of its content;
  • 29 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

All datasets used are publicly available, and the codebase for the project loads the data required for each simulation automatically. Source data for all figures are provided with this paper. Source data are provided in this paper.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 2 authors, 3 keywords, 11 MeSH terms, 75 references.

Cite

This paper

Spens, E., & Burgess, N. (2026). Hippocampo-neocortical interaction as compressive retrieval-augmented generation. Nature communications, 17(1), 7971. https://doi.org/10.1038/s41467-026-74357-6

BibTeX

@article{spens2026hippocampo,
author = {Spens, Eleanor and Burgess, Neil},
title = {{Hippocampo-neocortical interaction as compressive retrieval-augmented generation}},
journal = {Nature communications},
year = {2026},
month = jun,
volume = {17},
number = {1},
pages = {7971},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-74357-6},
url = {https://doi.org/10.1038/s41467-026-74357-6},
pmid = {42350398},
pmcid = {PMC13447824}
}

RIS

TY - JOUR
AU - Spens, Eleanor
AU - Burgess, Neil
TI - Hippocampo-neocortical interaction as compressive retrieval-augmented generation
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/06/25
VL - 17
IS - 1
SP - 7971
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-74357-6
UR - https://doi.org/10.1038/s41467-026-74357-6
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-74357-6",
"type": "article-journal",
"title": "Hippocampo-neocortical interaction as compressive retrieval-augmented generation",
"container-title": "Nature communications",
"author": [
{
"family": "Spens",
"given": "Eleanor"
},
{
"family": "Burgess",
"given": "Neil"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "7971",
"DOI": "10.1038/s41467-026-74357-6",
"PMID": "42350398",
"PMCID": "PMC13447824",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-74357-6",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
25
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.cub.2026.05.068 [code]
An abstract relational map emerges in the human medial prefrontal cortex with consolidation.
Journal: Current biology : CB
In common: statsmodels, seaborn, pandas, 3 other tools, 6 references
[2] doi:10.1126/sciadv.aeg6797 [code]
Dorsoventral gradient of theta sweeps in the medial entorhinal cortex.
Journal: Science advances
In common: UMAP, NetworkX, statsmodels, 6 other tools, author Neil Burgess
[3] doi:10.1038/s41467-026-71428-6 [code]
Binding items to contexts through conjunctive neural representations with the method of loci.
Journal: Nature communications
In common: statsmodels, seaborn, scikit-learn, 4 other tools, 5 references
[4] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Hugging Face Transformers, UMAP, NetworkX, 9 other tools
[5] doi:10.64898/2026.03.30.714220 [code]
An integrated single cell and spatial omics atlas of human prenatal development
Journal: bioRxiv (preprint)
In common: Hugging Face Transformers, UMAP, NetworkX, 9 other tools
[6] doi:10.1038/s41467-026-74358-5 [code]
Brain-inspired spatial intelligence for embodied agents.
Journal: Nature communications
In common: Hugging Face Transformers, NetworkX, Pillow, 7 other tools, 2 references
[7] doi:10.7554/elife.107933 [code]
Modality-agnostic decoding of vision and language from fMRI.
Journal: eLife
In common: Hugging Face Transformers, Pillow, statsmodels, 7 other tools, 2 references
[8] doi:10.1038/s42256-026-01254-4 [code]
Neural sampling from cognitive maps enables goal-directed imagination and planning.
Journal: Nature machine intelligence
In common: NetworkX, PyTorch, seaborn, 3 other tools, computational modeling (no new data), 4 references
[9] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: UMAP, NetworkX, Pillow, 8 other tools
[10] doi:10.1038/s41598-026-43529-1 [code]
A spiking neural network inspired by neuroscience and psychology for Western mode- and key-conditioned music learning and composition.
Journal: Scientific reports
In common: NetworkX, Pillow, PyTorch, 6 other tools, computational modeling (no new data), 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.