From 040201db41340e4ea933d9e46805ecee90daae5a Mon Sep 17 00:00:00 2001
From: rNLKJA <62606765+rNLKJA@users.noreply.github.com>
Date: Sat, 18 Jul 2026 12:20:16 +0930
Subject: [PATCH 1/2] perf: scope KaTeX/fonts/middleware/JSON-LD, fix stale
caching + O(N^2) build
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ten independent load-time and build-time fixes, each verified via a clean
production build + zero-error lint pass:
- Split Formula/TeX out of KnowledgeLayout.jsx into KatexFormula.jsx so the
katex JS library (76KB gzip) and its CSS only load on the 39/68 knowledge
pages that actually render maths, not all 68. Switched katex.min.css to
the -swap variant (font-display:swap) to remove a FOIT risk on those pages.
- lib/fonts.js: stop eagerly preloading the Bitcount local font (~35KB)
everywhere except where it's actually above the fold; display:swap already
prevents FOIT elsewhere.
- middleware.js: tighten the matcher to skip static file extensions so every
font/image/css request doesn't take an Edge Middleware hop.
- Move homepage-only JSON-LD (breadcrumb, projects, credentials) out of the
global _document.jsx into pages/index.jsx — was shipping on all ~300 pages.
- Remove dead dns-prefetch hints and images.remotePatterns entries for hosts
no longer used now that logos are self-hosted.
- public/sw.js: switch /images/* from cache-first to stale-while-revalidate
— filenames aren't content-hashed, so cache-first could serve a replaced
image forever to returning visitors.
- Delete two unreferenced images (heroSection1.png, 404.png — 2.3MB, zero
references anywhere in the repo).
- Fix a CSS bug where h1's font-family targeted a string that doesn't match
the compiled @font-face name, silently falling back to system monospace
on ~70 page titles site-wide.
- lib/posts.js: split parsePost into a cheap metadata-only parse (used by
getAllPosts) and the full remark pipeline (used per-page) — turns
/blog/[slug]'s O(N^2) build-time parsing into O(N). Measured 11.3s -> 3.4s
for the current 18 posts.
Deliberately NOT done: splitting both locale JSON files apart (the real
gzip cost is ~28KB one-time per session, not per-page, and a correct fix
needs getStaticProps rolled out across ~300 pages) and splitting DM
Sans/Playfair's italic preload (next/font's preload option is all-or-nothing
per call, so a safe split would need a second font-family and CSS rules to
route italic text to it — too much surface for a zero-test-coverage site).
Co-Authored-By: Claude Sonnet 5
---
components/knowledge/KatexFormula.jsx | 50 +++
components/knowledge/KnowledgeLayout.jsx | 50 +--
.../content/artificial-intelligence.jsx | 10 +-
.../knowledge/content/bayesian-statistics.jsx | 10 +-
.../content/calculus-optimisation.jsx | 3 +-
.../knowledge/content/causal-inference.jsx | 10 +-
.../content/cluster-cloud-computing.jsx | 10 +-
components/knowledge/content/clustering.jsx | 10 +-
.../content/computational-statistics.jsx | 10 +-
.../content/conformal-prediction.jsx | 10 +-
.../knowledge/content/deep-learning.jsx | 10 +-
.../content/differential-privacy.jsx | 10 +-
.../knowledge/content/ensemble-methods.jsx | 10 +-
.../content/extreme-value-theory.jsx | 10 +-
.../knowledge/content/feature-engineering.jsx | 10 +-
.../knowledge/content/gaussian-processes.jsx | 10 +-
.../content/graph-neural-networks.jsx | 10 +-
.../content/information-retrieval.jsx | 10 +-
.../knowledge/content/kalman-filter.jsx | 10 +-
.../content/large-language-models.jsx | 10 +-
.../knowledge/content/linear-algebra.jsx | 3 +-
.../content/linear-statistical-models.jsx | 3 +-
.../knowledge/content/model-evaluation.jsx | 10 +-
.../content/natural-language-processing.jsx | 3 +-
.../content/network-graph-analysis.jsx | 10 +-
.../knowledge/content/operations-research.jsx | 10 +-
.../content/pca-dimensionality-reduction.jsx | 10 +-
.../probabilistic-graphical-models.jsx | 180 +++++---
components/knowledge/content/probability.jsx | 3 +-
.../knowledge/content/quantile-regression.jsx | 149 ++++---
.../knowledge/content/recommender-systems.jsx | 297 +++++++++----
.../content/reinforcement-learning.jsx | 10 +-
.../knowledge/content/robust-statistics.jsx | 182 +++++---
.../content/sampling-survey-methodology.jsx | 3 +-
.../knowledge/content/spatial-statistics.jsx | 147 ++++---
.../content/statistical-machine-learning.jsx | 3 +-
.../content/statistical-modelling.jsx | 10 +-
.../content/statistical-process-control.jsx | 230 +++++++----
components/knowledge/content/statistics.jsx | 3 +-
.../knowledge/content/survival-analysis.jsx | 150 ++++---
.../content/time-series-analysis.jsx | 10 +-
lib/fonts.js | 7 +-
lib/posts.js | 57 ++-
middleware.js | 9 +-
next.config.js | 11 +-
pages/_app.jsx | 5 +-
pages/_document.jsx | 389 +-----------------
pages/index.jsx | 381 +++++++++++++++++
public/images/404.png | Bin 803157 -> 0 bytes
public/images/index/heroSection1.png | Bin 1619798 -> 0 bytes
public/styles/globals.css | 2 +-
public/sw.js | 34 +-
52 files changed, 1468 insertions(+), 1126 deletions(-)
create mode 100644 components/knowledge/KatexFormula.jsx
delete mode 100644 public/images/404.png
delete mode 100644 public/images/index/heroSection1.png
diff --git a/components/knowledge/KatexFormula.jsx b/components/knowledge/KatexFormula.jsx
new file mode 100644
index 00000000..45ed764a
--- /dev/null
+++ b/components/knowledge/KatexFormula.jsx
@@ -0,0 +1,50 @@
+import katex from "katex";
+
+/*
+ * Split out from KnowledgeLayout.jsx so pages that never render maths don't
+ * pull the katex JS (and its CSS, imported alongside these components) into
+ * their bundle. Only the ~39 knowledge topics that use / import
+ * from this module.
+ */
+
+/**
+ * Centred display formula, rendered with KaTeX.
+ * `children` is a LaTeX string (use a template literal; `\\` starts a new line,
+ * and `\begin{aligned}…\end{aligned}` lays out a multi-step derivation).
+ * `label` is the accessible description read by screen readers.
+ * `caption` optionally annotates the formula beneath it.
+ */
+export function Formula({ children, label, caption }) {
+ const tex = typeof children === "string" ? children : String(children ?? "");
+ const html = katex.renderToString(tex, {
+ displayMode: true,
+ throwOnError: false,
+ strict: false,
+ });
+ return (
+
+
+ {caption && (
+
+ {caption}
+
+ )}
+
+ );
+}
+
+/** Inline maths, rendered with KaTeX. `children` is a LaTeX string. */
+export function TeX({ children, label }) {
+ const tex = typeof children === "string" ? children : String(children ?? "");
+ const html = katex.renderToString(tex, {
+ displayMode: false,
+ throwOnError: false,
+ strict: false,
+ });
+ return ;
+}
diff --git a/components/knowledge/KnowledgeLayout.jsx b/components/knowledge/KnowledgeLayout.jsx
index 8826b750..8bd76e1d 100644
--- a/components/knowledge/KnowledgeLayout.jsx
+++ b/components/knowledge/KnowledgeLayout.jsx
@@ -1,6 +1,5 @@
import Head from "next/head";
import Link from "next/link";
-import katex from "katex";
import SeoHead from "@/components/seo/SeoHead";
import { useI18n } from "@/contexts/I18nContext";
@@ -12,9 +11,10 @@ import { useI18n } from "@/contexts/I18nContext";
* frame — header, provenance strip, table of contents, prose body, refresher,
* footer nav, SEO + JSON-LD — so each topic page only writes its content.
*
- * Companion primitives (KSection, Callout, Formula, Figure, Term, KList) are
- * exported from this module and are designed to sit inside safely
- * (they opt out with `not-prose` where the typography plugin would interfere).
+ * Companion primitives (KSection, Callout, Figure, Term) are exported from
+ * this module and are designed to sit inside safely (they opt out
+ * with `not-prose` where the typography plugin would interfere). Formula/TeX
+ * live in ./KatexFormula.jsx so pages without maths don't bundle katex.
*/
const ACCENT = "#FF3C3C";
@@ -289,48 +289,6 @@ export function Callout({ type = "note", label, children }) {
);
}
-/**
- * Centred display formula, rendered with KaTeX.
- * `children` is a LaTeX string (use a template literal; `\\` starts a new line,
- * and `\begin{aligned}…\end{aligned}` lays out a multi-step derivation).
- * `label` is the accessible description read by screen readers.
- * `caption` optionally annotates the formula beneath it.
- */
-export function Formula({ children, label, caption }) {
- const tex = typeof children === "string" ? children : String(children ?? "");
- const html = katex.renderToString(tex, {
- displayMode: true,
- throwOnError: false,
- strict: false,
- });
- return (
-
-
- {caption && (
-
- {caption}
-
- )}
-
- );
-}
-
-/** Inline maths, rendered with KaTeX. `children` is a LaTeX string. */
-export function TeX({ children, label }) {
- const tex = typeof children === "string" ? children : String(children ?? "");
- const html = katex.renderToString(tex, {
- displayMode: false,
- throwOnError: false,
- strict: false,
- });
- return ;
-}
-
/** Figure wrapper for inline SVG diagrams + caption. */
export function Figure({ children, caption }) {
return (
diff --git a/components/knowledge/content/artificial-intelligence.jsx b/components/knowledge/content/artificial-intelligence.jsx
index 1c9ed64c..802c6e22 100644
--- a/components/knowledge/content/artificial-intelligence.jsx
+++ b/components/knowledge/content/artificial-intelligence.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/artificial-intelligence.
diff --git a/components/knowledge/content/bayesian-statistics.jsx b/components/knowledge/content/bayesian-statistics.jsx
index 7ff12f3c..149cc348 100644
--- a/components/knowledge/content/bayesian-statistics.jsx
+++ b/components/knowledge/content/bayesian-statistics.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/bayesian-statistics.
diff --git a/components/knowledge/content/calculus-optimisation.jsx b/components/knowledge/content/calculus-optimisation.jsx
index 31b03d01..8a7dab8b 100644
--- a/components/knowledge/content/calculus-optimisation.jsx
+++ b/components/knowledge/content/calculus-optimisation.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/calculus-optimisation.
diff --git a/components/knowledge/content/causal-inference.jsx b/components/knowledge/content/causal-inference.jsx
index 510b359c..e2d5ceca 100644
--- a/components/knowledge/content/causal-inference.jsx
+++ b/components/knowledge/content/causal-inference.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/causal-inference.
diff --git a/components/knowledge/content/cluster-cloud-computing.jsx b/components/knowledge/content/cluster-cloud-computing.jsx
index 208ecd76..1bf2d837 100644
--- a/components/knowledge/content/cluster-cloud-computing.jsx
+++ b/components/knowledge/content/cluster-cloud-computing.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/cluster-cloud-computing.
diff --git a/components/knowledge/content/clustering.jsx b/components/knowledge/content/clustering.jsx
index 4d022a75..2b8e8a56 100644
--- a/components/knowledge/content/clustering.jsx
+++ b/components/knowledge/content/clustering.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/clustering.
diff --git a/components/knowledge/content/computational-statistics.jsx b/components/knowledge/content/computational-statistics.jsx
index df4fd450..2638597e 100644
--- a/components/knowledge/content/computational-statistics.jsx
+++ b/components/knowledge/content/computational-statistics.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/computational-statistics.
diff --git a/components/knowledge/content/conformal-prediction.jsx b/components/knowledge/content/conformal-prediction.jsx
index a9b9938c..fcaaa7eb 100644
--- a/components/knowledge/content/conformal-prediction.jsx
+++ b/components/knowledge/content/conformal-prediction.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/conformal-prediction.
diff --git a/components/knowledge/content/deep-learning.jsx b/components/knowledge/content/deep-learning.jsx
index f304c9c9..b55fed0d 100644
--- a/components/knowledge/content/deep-learning.jsx
+++ b/components/knowledge/content/deep-learning.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/deep-learning.
diff --git a/components/knowledge/content/differential-privacy.jsx b/components/knowledge/content/differential-privacy.jsx
index aa0b75c6..a251119d 100644
--- a/components/knowledge/content/differential-privacy.jsx
+++ b/components/knowledge/content/differential-privacy.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/differential-privacy.
diff --git a/components/knowledge/content/ensemble-methods.jsx b/components/knowledge/content/ensemble-methods.jsx
index c0ec53dd..f3375e4f 100644
--- a/components/knowledge/content/ensemble-methods.jsx
+++ b/components/knowledge/content/ensemble-methods.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/ensemble-methods.
diff --git a/components/knowledge/content/extreme-value-theory.jsx b/components/knowledge/content/extreme-value-theory.jsx
index 63f0a48a..a5bff7e6 100644
--- a/components/knowledge/content/extreme-value-theory.jsx
+++ b/components/knowledge/content/extreme-value-theory.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/extreme-value-theory.
diff --git a/components/knowledge/content/feature-engineering.jsx b/components/knowledge/content/feature-engineering.jsx
index 686fbdeb..30e5f027 100644
--- a/components/knowledge/content/feature-engineering.jsx
+++ b/components/knowledge/content/feature-engineering.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/feature-engineering.
diff --git a/components/knowledge/content/gaussian-processes.jsx b/components/knowledge/content/gaussian-processes.jsx
index 097af1ae..a6406ce6 100644
--- a/components/knowledge/content/gaussian-processes.jsx
+++ b/components/knowledge/content/gaussian-processes.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/gaussian-processes.
diff --git a/components/knowledge/content/graph-neural-networks.jsx b/components/knowledge/content/graph-neural-networks.jsx
index e794ca56..78ff05ab 100644
--- a/components/knowledge/content/graph-neural-networks.jsx
+++ b/components/knowledge/content/graph-neural-networks.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/graph-neural-networks.
diff --git a/components/knowledge/content/information-retrieval.jsx b/components/knowledge/content/information-retrieval.jsx
index b51d0c05..a6d8b8e7 100644
--- a/components/knowledge/content/information-retrieval.jsx
+++ b/components/knowledge/content/information-retrieval.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/information-retrieval.
diff --git a/components/knowledge/content/kalman-filter.jsx b/components/knowledge/content/kalman-filter.jsx
index 60bc01f8..ce18bcce 100644
--- a/components/knowledge/content/kalman-filter.jsx
+++ b/components/knowledge/content/kalman-filter.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/kalman-filter.
diff --git a/components/knowledge/content/large-language-models.jsx b/components/knowledge/content/large-language-models.jsx
index b5afc866..8ee5e4f5 100644
--- a/components/knowledge/content/large-language-models.jsx
+++ b/components/knowledge/content/large-language-models.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/large-language-models.
diff --git a/components/knowledge/content/linear-algebra.jsx b/components/knowledge/content/linear-algebra.jsx
index 551cb4dd..2f65af01 100644
--- a/components/knowledge/content/linear-algebra.jsx
+++ b/components/knowledge/content/linear-algebra.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/linear-algebra.
diff --git a/components/knowledge/content/linear-statistical-models.jsx b/components/knowledge/content/linear-statistical-models.jsx
index e7c12eb4..40787971 100644
--- a/components/knowledge/content/linear-statistical-models.jsx
+++ b/components/knowledge/content/linear-statistical-models.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/linear-statistical-models.
diff --git a/components/knowledge/content/model-evaluation.jsx b/components/knowledge/content/model-evaluation.jsx
index cb9c6a93..1efa1316 100644
--- a/components/knowledge/content/model-evaluation.jsx
+++ b/components/knowledge/content/model-evaluation.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/model-evaluation.
diff --git a/components/knowledge/content/natural-language-processing.jsx b/components/knowledge/content/natural-language-processing.jsx
index 88cd79c3..afb58ee0 100644
--- a/components/knowledge/content/natural-language-processing.jsx
+++ b/components/knowledge/content/natural-language-processing.jsx
@@ -1,4 +1,5 @@
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/natural-language-processing.
diff --git a/components/knowledge/content/network-graph-analysis.jsx b/components/knowledge/content/network-graph-analysis.jsx
index b50cacd7..3b172e22 100644
--- a/components/knowledge/content/network-graph-analysis.jsx
+++ b/components/knowledge/content/network-graph-analysis.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/network-graph-analysis.
diff --git a/components/knowledge/content/operations-research.jsx b/components/knowledge/content/operations-research.jsx
index dd3f8b8f..a97006ae 100644
--- a/components/knowledge/content/operations-research.jsx
+++ b/components/knowledge/content/operations-research.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/operations-research.
diff --git a/components/knowledge/content/pca-dimensionality-reduction.jsx b/components/knowledge/content/pca-dimensionality-reduction.jsx
index 88f6d3ab..a8ab8b59 100644
--- a/components/knowledge/content/pca-dimensionality-reduction.jsx
+++ b/components/knowledge/content/pca-dimensionality-reduction.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/pca-dimensionality-reduction.
diff --git a/components/knowledge/content/probabilistic-graphical-models.jsx b/components/knowledge/content/probabilistic-graphical-models.jsx
index 0f9989cb..29ae9f8b 100644
--- a/components/knowledge/content/probabilistic-graphical-models.jsx
+++ b/components/knowledge/content/probabilistic-graphical-models.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/probabilistic-graphical-models.
@@ -26,18 +20,60 @@ function BayesNetFigure({ caption, ariaLabel, rainLabel, sprinklerLabel, grassLa
aria-label={ariaLabel}
>
- {rainLabel}
+
+ {rainLabel}
+
- {sprinklerLabel}
-
- {grassLabel}
- {wetLabel}
+
+ {sprinklerLabel}
+
+
+
+ {grassLabel}
+
+
+ {wetLabel}
+
{/* edges */}
-
-
-
+
+
+
-
+
+
+
@@ -244,33 +280,39 @@ function ZhBody() {
return (
<>
- 对一个 变量在不确定下进行推理,是 贝叶斯的
- 故事。但真实的问题涉及许多 相互关联的不确定变量——症状与疾病、原因与结果、信号与状态——而对
- 所有这些变量的完整联合分布 ,大得像天文数字。概率图模型 (PGM)用一个漂亮的
- 洞见让这变得可处理:把变量之间的依赖关系画成一张图 ,而那张图既把分布压缩成可管理的
+ 对一个 变量在不确定下进行推理,是
+ 贝叶斯的 故事。但真实的问题涉及
+ 许多 相互关联的不确定变量——症状与疾病、原因与结果、信号与状态——而对
+ 所有这些变量的完整联合分布 ,大得像天文数字。概率图模型
+ (PGM)用一个漂亮的 洞见让这变得可处理:把变量之间的依赖关系画成一张图
+ ,而那张图既把分布压缩成可管理的
小块,又告诉你如何用它来推理。它是概率论与图论相遇的地方,支撑着大量现代的概率 AI。
它把你已经见过的几条线索系在一起——它是贝叶斯的,它的图就是因果推断里那些同样的
DAG,而学习它的结构就是
- 因果发现。这一页讲核心的想法(把独立性化为图)、
+ 因果发现
+ 。这一页讲核心的想法(把独立性化为图)、
贝叶斯网络、图如何把联合分布因子分解,以及你如何用证据来推理。
关于一组变量你想知道的一切,原则上都在它们的联合分布里——每一种取值组合的概率。问题在于规模:
- 仅仅 30 个是/否变量,联合分布就有超过十亿 个条目。你存不下它,更别说从数据中估计它、或用它
- 来计算。必须利用某种结构,而拯救我们的那个结构是独立性 ——大多数变量并不直接依赖于
- 大多数其他变量。
+ 仅仅 30 个是/否变量,联合分布就有超过十亿
+ 个条目。你存不下它,更别说从数据中估计它、或用它
+ 来计算。必须利用某种结构,而拯救我们的那个结构是独立性
+ ——大多数变量并不直接依赖于 大多数其他变量。
- 核心的想法:一张图编码了哪些变量(在条件上)独立于哪些 。每个变量是一个节点;两个
- 节点之间的一条边意味着一个直接的依赖;一条缺失 的边断言一个
- 条件独立(「一旦我知道了它的父节点,这个变量就再也
+ 核心的想法:一张图编码了哪些变量(在条件上)独立于哪些
+ 。每个变量是一个节点;两个 节点之间的一条边意味着一个直接的依赖;一条缺失
+ 的边断言一个
+ 条件独立
+ (「一旦我知道了它的父节点,这个变量就再也
告诉不了我关于那个变量的任何东西」)。这张图是依赖结构一张紧凑的、人可读的地图——而关键的是,那些
独立性假设,正是把那个不可能的联合分布缩成某个又小又可计算的东西的原因。
@@ -278,9 +320,10 @@ function ZhBody() {
- 最常见的 PGM 是贝叶斯网络 (或称信念网络):一个有向无环图 (DAG,与因果图
- 同样的结构),其中每个节点只依赖于它的父节点 。每个节点带着一张小表——它在给定父节点下
- 的概率——而那个局部的信息,加上这张图,就完整地指定了整个联合分布。
+ 最常见的 PGM 是贝叶斯网络 (或称信念网络):一个有向无环图
+ (DAG,与因果图 同样的结构),其中每个节点只依赖于它的父节点
+ 。每个节点带着一张小表——它在给定父节点下
+ 的概率——而那个局部的信息,加上这张图,就完整地指定了整个联合分布。
- 那个经典的玩具例子:雨 和一个洒水器 各自都能让草地 变湿,而雨还影响洒水器
+ 那个经典的玩具例子:雨 和一个洒水器 各自都能让草地
+ 变湿,而雨还影响洒水器
是否运行。三个节点、几张小表——你就捕捉到了一个可以推理的完整联合分布。
- 数学上的回报是因子分解 。一个贝叶斯网络说,完整的联合分布就是每个变量在给定其父节点下
- 概率的乘积 :
+ 数学上的回报是因子分解
+ 。一个贝叶斯网络说,完整的联合分布就是每个变量在给定其父节点下 概率的乘积 :
{String.raw`P(X_1, \dots, X_n) = \prod_{i=1}^{n} P\big(X_i \mid \text{parents}(X_i)\big)`}
- 这就是一行里的全部魔法。你不是存一张覆盖所有变量的巨大的表,而是存许多小 表(每个节点一张,
- 大小只由它那几个父节点决定)。30 个变量那个十亿条目的联合分布,坍缩成了一小撮小表——可存储、可学习、
+ 这就是一行里的全部魔法。你不是存一张覆盖所有变量的巨大的表,而是存许多小
+ 表(每个节点一张, 大小只由它那几个父节点决定)。30
+ 个变量那个十亿条目的联合分布,坍缩成了一小撮小表——可存储、可学习、
可计算。图里缺失的边,正是许可这次因子分解的那些独立性假设:结构买来了可处理性。
@@ -314,46 +359,54 @@ function ZhBody() {
建这个模型的意义在于推断 ——回答形如{" "}
- {String.raw`P(\text{query} \mid \text{evidence})`} 的问题:「在草地是湿的、并且天阴的
+ {String.raw`P(\text{query} \mid \text{evidence})`} {" "}
+ 的问题:「在草地是湿的、并且天阴的
情况下,下过雨的概率是多少?」你把观测到的变量钳制到它们的取值上,然后计算你关心的那些变量上更新后
的分布——贝叶斯的信念更新,在网络中传播。
- 陷阱在于,精确推断一般而言在计算上很难 (对任意的图是 NP 难的)。对于漂亮的结构
- (树),通过信念传播 它是高效的——在相邻节点之间传递「信念」消息,直到它们达成一致
- (注意它与 GNN 的消息传递的亲缘关系)。对于
+ 陷阱在于,精确推断一般而言在计算上很难 (对任意的图是 NP
+ 难的)。对于漂亮的结构 (树),通过信念传播
+ 它是高效的——在相邻节点之间传递「信念」消息,直到它们达成一致 (注意它与{" "}
+ GNN 的消息传递的亲缘关系)。对于
有环的、复杂的网络,你就退而求其次用近似 推断——
- MCMC 采样,或有环信念传播。所以那个优雅的
- 表示,在图变得纠缠时,伴随着一个真实的计算代价。
+ MCMC{" "}
+ 采样,或有环信念传播。所以那个优雅的 表示,在图变得纠缠时,伴随着一个真实的计算代价。
- 一个 PGM 有两样东西要学:参数 (概率表,在给定图的情况下——通常从数据中可以直接得到)和
+ 一个 PGM 有两样东西要学:参数
+ (概率表,在给定图的情况下——通常从数据中可以直接得到)和
结构 (图本身——哪些边存在)。从观测数据中学习结构,正是
- 因果发现,连同它所有的困难(你往往只能恢复出一个
- 等价类)。实践中结构常常来自领域知识 ——一个专家画出依赖图——而只学习参数,这是一个
- 真正的强项:PGM 让你编码你所知道的 ,并学习其余的。
+ 因果发现
+ ,连同它所有的困难(你往往只能恢复出一个 等价类)。实践中结构常常来自
+ 领域知识 ——一个专家画出依赖图——而只学习参数,这是一个 真正的强项:PGM 让你
+ 编码你所知道的 ,并学习其余的。
- PGM 在有许多相互关联的不确定变量 、且你需要既有原则又可解释 的推理之处
- 最重要。与黑箱模型不同,一个贝叶斯网络展示它的推理 ——这张图是一份「什么依赖于
+ PGM 在有许多相互关联的不确定变量 、且你需要既有原则又可解释
+ 的推理之处 最重要。与黑箱模型不同,一个贝叶斯网络展示它的推理
+ ——这张图是一份「什么依赖于
什么」的可审计地图,而像「在给定这个证据下,这是更新后的概率」这样的一次推断,可以在它上面被追溯。
在一个需要问责的场景里,那种透明度是真正有价值的:你不仅能为答案辩护,还能为推理的结构辩护。
- 它还统一了好几页:它是带许多变量的 贝叶斯更新,
- 它的 DAG 就是 因果推断的图,学习它的结构是
+ 它还统一了好几页:它是带许多变量的
+ 贝叶斯更新, 它的 DAG 就是
+ 因果推断的图,学习它的结构是
因果发现,而它的推断呼应
- GNN 的消息传递。需要的纪律是尊重那个
- 推断代价 ——在纠缠的图上精确推理很难,所以你依靠近似——以及这个模型的好坏,只取决于
+ GNN 的消息传递
+ 。需要的纪律是尊重那个
+ 推断代价
+ ——在纠缠的图上精确推理很难,所以你依靠近似——以及这个模型的好坏,只取决于
你编码进去的依赖结构。
@@ -363,29 +416,30 @@ function ZhBody() {
- 许多变量上的完整联合分布 大得像天文数字——PGM 靠利用独立性 来
- 驯服它。
+ 许多变量上的完整联合分布 大得像天文数字——PGM 靠利用
+ 独立性 来 驯服它。
- 一张图编码条件独立 :节点 = 变量,一条缺失的边 = 一个独立性。一份紧凑、可读的
- 依赖地图。
+ 一张图编码条件独立 :节点 = 变量,一条缺失的边 =
+ 一个独立性。一份紧凑、可读的 依赖地图。
- 一个贝叶斯网络 是一个 DAG,其中每个节点依赖于它的父节点 ;用
- 每个节点的小表,它指定了整个联合分布。
+ 一个贝叶斯网络 是一个 DAG,其中每个节点依赖于它的
+ 父节点 ;用 每个节点的小表,它指定了整个联合分布。
因子分解 {" "}
- {String.raw`P(X_1..X_n)=\prod_i P(X_i\mid \text{parents})`} 把一张巨大的表变成许多
- 小表——可存储、可学习、可计算。
+ {String.raw`P(X_1..X_n)=\prod_i P(X_i\mid \text{parents})`} {" "}
+ 把一张巨大的表变成许多 小表——可存储、可学习、可计算。
- 推断 ={" "}
- {String.raw`P(\text{query}\mid\text{evidence})`} 。在树上容易(
+ 推断 = {String.raw`P(\text{query}\mid\text{evidence})`}
+ 。在树上容易(
信念传播 );一般而言 NP 难 → MCMC / 近似。
- 学习参数 (容易)+ 结构 (= 因果发现,或来自领域知识)。强项:
+ 学习参数 (容易)+ 结构 (=
+ 因果发现,或来自领域知识)。强项:
透明、可解释 的推理。
diff --git a/components/knowledge/content/probability.jsx b/components/knowledge/content/probability.jsx
index ef5f14b3..d8632222 100644
--- a/components/knowledge/content/probability.jsx
+++ b/components/knowledge/content/probability.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/probability.
diff --git a/components/knowledge/content/quantile-regression.jsx b/components/knowledge/content/quantile-regression.jsx
index 859a7322..f0e51389 100644
--- a/components/knowledge/content/quantile-regression.jsx
+++ b/components/knowledge/content/quantile-regression.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/quantile-regression.
@@ -48,11 +42,24 @@ function FanFigure({ caption, ariaLabel }) {
))}
- τ=.9
+
+ τ=.9
+
- τ=.5
+
+ τ=.5
+
- τ=.1
+
+ τ=.1
+
);
@@ -269,73 +276,83 @@ function ZhBody() {
return (
<>
- 普通 回归回答一个问题:一个预测变量如何移动
- 结果的平均值 ?那有用——而它能把几乎一切要紧的东西都藏起来。一项政策的平均效应可能
+ 普通 回归
+ 回答一个问题:一个预测变量如何移动 结果的平均值
+ ?那有用——而它能把几乎一切要紧的东西都藏起来。一项政策的平均效应可能
很小,却对顶部帮助极大、对底部毫无帮助;等待时间的离散程度可能随需求拉大,即便均值稳稳不动。
- 分位数回归 超越平均值:它建模一个预测变量如何影响分布上任何 选定的点——中位数、
- 第 10 百分位、第 90——这样你看到的是对整个 响应的影响,而不只是它的质心。
+ 分位数回归 超越平均值:它建模一个预测变量如何影响分布上任何
+ 选定的点——中位数、 第 10 百分位、第 90——这样你看到的是对整个
+ 响应的影响,而不只是它的质心。
它是一个独特而出人意料地实用的工具——并干净利落地连到两个邻居:它的损失函数让它天然
稳健,而对尾部建模又连到
极值理论和
- 预测区间。这一页讲这个想法、驱动它的那个巧妙的
- 损失、如何读它的输出,以及它在哪里赢得自己的位置。
+ 预测区间
+ 。这一页讲这个想法、驱动它的那个巧妙的 损失、如何读它的输出,以及它在哪里赢得自己的位置。
均值回归的局限在于,条件均值是一个单一的概括,而一个单一的概括无法捕捉一种关系如何随分布而变化。
- 两种有着相同均值效应的情形可以完全不同:一个预测变量可能把所有人同等地往上抬,或者 抬高
- 顶部而让底部不动,或者 在不移动中心的情况下增大离散程度 。普通最小二乘对这三者
- 报告同样的平均值,对其间的差别视而不见。
+ 两种有着相同均值效应的情形可以完全不同:一个预测变量可能把所有人同等地往上抬,
+ 或者 抬高 顶部而让底部不动,或者 在不移动中心的情况下增大离散程度
+ 。普通最小二乘对这三者 报告同样的平均值,对其间的差别视而不见。
然而那个差别往往才是全部的重点——在公平上(「这帮的是最弱势的人,还是只帮了已经占优的人?」)、在
- 风险上(「糟糕的情形有多糟,而不是典型情形?」),以及在服务保证上(「第 95 百分位的等待是多少,
- 而不是平均等待?」)。分位数回归正是为回答这些而生的。
+ 风险上(「糟糕的情形有多糟,而不是典型情形?」),以及在服务保证上(「第 95
+ 百分位的等待是多少, 而不是平均等待?」)。分位数回归正是为回答这些而生的。
- 这个想法是一个直接的推广。普通回归建模 {String.raw`Y`} 在给定 {String.raw`X`} {" "}
- 下的条件均值 ,而分位数回归把一个条件分位数 {String.raw`\tau`} ——
- 例如 {String.raw`\tau = 0.5`} (中位数),或 {String.raw`0.9`} (第 90 百分
- 位)——建模为预测变量的一个函数。在好几个分位数上拟合它,你就得到一族线,描述结果的底部 、
- 中部 和顶部 各自如何对预测变量做出响应。你建模了整个条件分布,而不只是它的均值。
+ 这个想法是一个直接的推广。普通回归建模 {String.raw`Y`} 在给定{" "}
+ {String.raw`X`} 下的条件均值 ,而分位数回归把一个条件
+ 分位数 {String.raw`\tau`} —— 例如{" "}
+ {String.raw`\tau = 0.5`} (中位数),或 {String.raw`0.9`} (第 90 百分
+ 位)——建模为预测变量的一个函数。在好几个分位数上拟合它,你就得到一族线,描述结果的
+ 底部 、中部 和顶部
+ 各自如何对预测变量做出响应。你建模了整个条件分布,而不只是它的均值。
- 其机制是损失函数的一个优雅的改变。普通回归最小化平方 误差(它瞄准均值);分位数回归最小化
- 一个不对称的绝对 误差——弹球 (或检验)损失——它瞄准一个选定的
- 分位数:
+ 其机制是损失函数的一个优雅的改变。普通回归最小化平方
+ 误差(它瞄准均值);分位数回归最小化 一个不对称的绝对 误差——
+ 弹球 (或检验)损失——它瞄准一个选定的 分位数:
{String.raw`L_\tau(r) = \begin{cases} \tau\,r & r \ge 0 \\[3pt] (\tau - 1)\,r & r < 0 \end{cases}`}
- 那个不对称就是全部的诀窍。对 {String.raw`\tau = 0.9`} ,低估(真值在线之上)受到的惩罚
- 比高估重 9 倍 ——于是拟合的线被往上推,直到只有约 10% 的点落在它之上:第 90 百分位。
- 调 {String.raw`\tau`} ,你就能瞄准任何分位数。而因为它建立在绝对 (而非平方)
- 误差之上,分位数回归对离群值天然 稳健——中位数回归
- ({String.raw`\tau = 0.5`} )正是最小绝对偏差,最小二乘那个稳健的表亲。
+ 那个不对称就是全部的诀窍。对 {String.raw`\tau = 0.9`}
+ ,低估(真值在线之上)受到的惩罚 比高估重 9 倍
+ ——于是拟合的线被往上推,直到只有约 10% 的点落在它之上:第 90 百分位。 调{" "}
+ {String.raw`\tau`} ,你就能瞄准任何分位数。而因为它建立在绝对
+ (而非平方) 误差之上,分位数回归对离群值天然
+ 稳健——中位数回归 (
+ {String.raw`\tau = 0.5`} )正是最小绝对偏差,最小二乘那个稳健的表亲。
- 真正的洞见来自一次性拟合好几个分位数,并把这些线放在一起 看:
+
+ 真正的洞见来自一次性拟合好几个分位数,并把这些线放在一起 看:
+
- 如果分位数线大致平行 ,预测变量同等地移动整个分布(离散程度恒定)。如果它们
- 扇形散开 ,离散程度随预测变量增大 ——异方差 ——意味着预测变量
- 影响的不只是水平,还有变异性 。那种扇形展开对一条单一的均值线是不可见的,而它常常是最重要的
- 发现:「随着 X 增加,结果不只是上升,它们变得更不平等。」
+ 如果分位数线大致平行
+ ,预测变量同等地移动整个分布(离散程度恒定)。如果它们
+ 扇形散开 ,离散程度随预测变量增大 ——异方差
+ ——意味着预测变量 影响的不只是水平,还有变异性
+ 。那种扇形展开对一条单一的均值线是不可见的,而它常常是最重要的 发现:「随着 X
+ 增加,结果不只是上升,它们变得更不平等。」
@@ -343,15 +360,17 @@ function ZhBody() {
在离散程度或尾部与中心同等要紧之处,分位数回归赢得自己的位置:
- 预测区间 ——拟合第 5 和第 95 分位数,你就有了一个直接、诚实的区间(「90% 的情形
- 落在这两者之间」)—— 保形预测的近亲。
+ 预测区间 ——拟合第 5 和第 95 分位数,你就有了一个直接、诚实的区间(「90%
+ 的情形 落在这两者之间」)—— 保形预测
+ 的近亲。
风险与尾部 ——直接建模损失或延误的第 99 百分位,在那里
极端才是关切所在,而非平均。
- 公平分析 ——一个效应对分布的底部与顶部是否不同?(一个项目抬高的是最弱势的人,还是
+ 公平分析
+ ——一个效应对分布的底部与顶部是否不同?(一个项目抬高的是最弱势的人,还是
只是已经过得好的人?)均值告诉不了你;分位数回归能。
@@ -361,10 +380,12 @@ function ZhBody() {
几条告诫让它保持诚实:
- 分别 拟合每个分位数可能产生分位数交叉 ——估计出的第 90 百分位在某些输入处
- 跌到第 50 之下,这在逻辑上不可能,是模型在勉强的一个信号(有一些方法会强制不交叉)。而因为每个
- 分位数是从它附近 的数据估计出来的,极端分位数需要更多数据 才能可靠地
- 钉住——第 99 百分位天生比中位数更难估计。所以分位数回归在分布的主体里最可信,当你往尾部深处
+ 分别 拟合每个分位数可能产生分位数交叉 ——估计出的第 90
+ 百分位在某些输入处 跌到第 50
+ 之下,这在逻辑上不可能,是模型在勉强的一个信号(有一些方法会强制不交叉)。而因为每个
+ 分位数是从它附近 的数据估计出来的,极端分位数需要更多数据
+ 才能可靠地 钉住——第 99
+ 百分位天生比中位数更难估计。所以分位数回归在分布的主体里最可信,当你往尾部深处
推进时,应当与 极值理论配对。
@@ -373,17 +394,20 @@ function ZhBody() {
- 政府分析里许多最重要的问题,都不是关于平均值的——它们是关于分布 的:最坏情形的
- 等待或延误(第 95 百分位,而非均值)、一项干预帮的是最弱势的人 还是只是已经
- 占优的人(公平),以及结果有多不平等 、那是否在拉大。分位数回归在均值回归视而不见之处
- 直接回答这些,而那些扇形展开的线 是一种有力的方式,去展示一个预测变量增加的
+ 政府分析里许多最重要的问题,都不是关于平均值的——它们是关于分布
+ 的:最坏情形的 等待或延误(第 95 百分位,而非均值)、一项干预帮的是
+ 最弱势的人 还是只是已经 占优的人(公平),以及结果有多不平等
+ 、那是否在拉大。分位数回归在均值回归视而不见之处 直接回答这些,而那些
+ 扇形展开的线 是一种有力的方式,去展示一个预测变量增加的
不只是一个结果的水平,还有它的不平等 。
- 它还与我所倚重的几个邻居相配:它天然 稳健
+ 它还与我所倚重的几个邻居相配:它天然
+ 稳健
(中位数回归 = 最小绝对偏差),它给出诚实的
- 预测区间(拟合两个分位数),而对于真正的
- 尾部它交棒给 极值理论。知道平均值很少就是
+ 预测区间
+ (拟合两个分位数),而对于真正的 尾部它交棒给
+ 极值理论。知道平均值很少就是
全部的故事——分位数回归是你询问其余部分的方式。
@@ -393,27 +417,30 @@ function ZhBody() {
- 普通回归建模均值 ——它藏起一个预测变量如何影响底部对顶部。分位数回归建模任何
+ 普通回归建模均值
+ ——它藏起一个预测变量如何影响底部对顶部。分位数回归建模任何
条件分位数 (中位数、第 90……)。
- 它最小化弹球 / 检验损失 ——不对称的绝对误差;对 τ=0.9,低估的代价多 9 倍,于是
- 线落在第 90 百分位。
+ 它最小化弹球 / 检验损失 ——不对称的绝对误差;对 τ=0.9,低估的代价多 9
+ 倍,于是 线落在第 90 百分位。
- 建立在绝对(而非平方)误差之上 → 天然稳健 ;中位数回归 = 最小绝对偏差。
+ 建立在绝对(而非平方)误差之上 → 天然稳健 ;中位数回归 =
+ 最小绝对偏差。
拟合好几个分位数并把它们放在一起读:平行 = 离散程度恒定 ;
- 扇形展开 = 异方差 (预测变量改变离散程度/不平等——对一条均值线不可见)。
+ 扇形展开 = 异方差
+ (预测变量改变离散程度/不平等——对一条均值线不可见)。
非常适合预测区间 (拟合两个分位数)、风险/尾部 ,以及
公平 (对最弱势者与顶部的效应)。
- 告诫:分位数交叉 ,以及极端分位数需要更多数据 ——真正的尾部
- 交给 EVT。
+ 告诫:分位数交叉 ,以及极端分位数需要更多数据
+ ——真正的尾部 交给 EVT。
diff --git a/components/knowledge/content/recommender-systems.jsx b/components/knowledge/content/recommender-systems.jsx
index e5029bac..2ce3a1f4 100644
--- a/components/knowledge/content/recommender-systems.jsx
+++ b/components/knowledge/content/recommender-systems.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/recommender-systems.
@@ -16,7 +10,14 @@ import {
* Matrix names (R/U/Vᵀ), operators (≈/×), and dims (m × k, k × n) are kept.
*/
-function MFFigure({ caption, ariaLabel, usersItemsLabel, kFactorsLabel, perUserItemLabel, ratingDotLabel }) {
+function MFFigure({
+ caption,
+ ariaLabel,
+ usersItemsLabel,
+ kFactorsLabel,
+ perUserItemLabel,
+ ratingDotLabel,
+}) {
return (
{/* R */}
-
- R
- {usersItemsLabel}
- ≈
+
+
+ R
+
+
+ {usersItemsLabel}
+
+
+ ≈
+
{/* U */}
-
- U
- m × k
- ×
+
+
+ U
+
+
+ m × k
+
+
+ ×
+
{/* V^T */}
-
- Vᵀ
- k × n
+
+
+ Vᵀ
+
+
+ k × n
+
{/* note */}
- {kFactorsLabel}
- {perUserItemLabel}
- {ratingDotLabel}
+
+ {kFactorsLabel}
+
+
+ {perUserItemLabel}
+
+
+ {ratingDotLabel}
+
);
@@ -300,13 +419,15 @@ function ZhBody() {
return (
<>
- 现代生活中出人意料的一大部分,是被推荐系统 塑造的:一个流媒体服务推到你眼前的电影、
+ 现代生活中出人意料的一大部分,是被推荐系统
+ 塑造的:一个流媒体服务推到你眼前的电影、
一家商店建议的商品、一个信息流接下来给你看的帖子。它们是个性化的引擎,而它们核心的想法真正优雅——从
每个人过去喜欢过什么的模式里,预测一个特定的人会有多喜欢一个特定的东西,并据此排序。
它值得好好了解,有两个原因。它的机制漂亮地连回
- 降维——核心的技术就是同一个潜在因子的
+ 降维
+ ——核心的技术就是同一个潜在因子的
想法——而它的失败模式(过滤气泡、反馈回路)是整个应用机器学习里社会后果最严重的一些。这一页讲整体的
地形:两大策略、矩阵分解引擎,以及哪里会出问题。
@@ -314,23 +435,24 @@ function ZhBody() {
把问题想成一张巨大的、大部分为空的表:行是用户,列是物品,每个格子是那个用户有多喜欢那个物品——
- 一个评分、一次点击、一次购买。问题在于这张表极度稀疏 :任何一个人都只与所有物品里
- 极小的一部分有过交互。推荐器的工作就是把空白填上 ——预测缺失的格子——然后推荐它预测你会打
- 最高分的物品。
+ 一个评分、一次点击、一次购买。问题在于这张表极度稀疏
+ :任何一个人都只与所有物品里 极小的一部分有过交互。推荐器的工作就是把空白填上
+ ——预测缺失的格子——然后推荐它预测你会打 最高分的物品。
- 做这件事有两种根本不同的方式,区别在于它们去哪里寻找信号:在物品 本身,还是在其他用户
- 构成的人群 。
+ 做这件事有两种根本不同的方式,区别在于它们去哪里寻找信号:在物品
+ 本身,还是在其他用户 构成的人群 。
- 基于内容的过滤 看的是物品 。它从你过去喜欢过的东西的特征里,建立起一个「你
+ 基于内容的过滤 看的是物品
+ 。它从你过去喜欢过的东西的特征里,建立起一个「你
喜欢什么」的画像,然后推荐有相似特征的物品。喜欢过好几部硬科幻电影?这里又有一部标着科幻的。物品
特征可以是显式的(类型、作者、价格),也可以是描述的学习得到的
- 文本嵌入,而「相似」就是 NLP 页里那个
- 同样的向量距离的想法。
+ 文本嵌入,而「相似」就是 NLP
+ 页里那个 同样的向量距离的想法。
它的强项与弱点是一枚硬币的两面:它能在全新的物品一被录入目录的那一刻就推荐它(它只需要它们的
@@ -339,13 +461,10 @@ function ZhBody() {
-
+
- 协同过滤 完全忽略物品特征,只用交互的模式——群体的智慧。直觉是:「过去与你意见一致的
+ 协同过滤
+ 完全忽略物品特征,只用交互的模式——群体的智慧。直觉是:「过去与你意见一致的
人,未来也会与你一致。」两种风味:
@@ -353,31 +472,31 @@ function ZhBody() {
用户—用户 ——找到口味与你相似的人,把他们喜欢、而你还没看过的东西推荐给你。
- 物品—物品 ——找到倾向于被同一批人喜欢的物品,把(在那种共同喜欢的意义上)与你打过
+ 物品—物品
+ ——找到倾向于被同一批人喜欢的物品,把(在那种共同喜欢的意义上)与你打过
高分的物品相似的物品推荐给你。(「购买此商品的顾客也购买了……」)这是大多数大规模系统的驱动力,
因为物品—物品的关系比用户口味更稳定。
- 协同过滤的魔力在于,它不需要知道物品实际上是什么 ——只需要知道谁与什么交互过。
+ 协同过滤的魔力在于,它不需要知道物品实际上是什么
+ ——只需要知道谁与什么交互过。
那也是它的弱点,下一节的技术会处理它,而再后面的冷启动问题会把它暴露出来。
-
+
- 驱动了现代协同过滤——并著名地赢得了 Netflix 大奖——的那个突破是矩阵分解 。想法是:把
- 巨大稀疏的用户—物品评分矩阵 {String.raw`R`} 近似为两个小得多的稠密矩阵的乘积:
+ 驱动了现代协同过滤——并著名地赢得了 Netflix 大奖——的那个突破是矩阵分解
+ 。想法是:把 巨大稀疏的用户—物品评分矩阵 {String.raw`R`} {" "}
+ 近似为两个小得多的稠密矩阵的乘积:
{String.raw`R_{\,m \times n} \;\approx\; U_{\,m \times k}\, V_{\,n \times k}^{\top}`}
- 每个用户变成一个由 {String.raw`k`} 个潜在因子 组成的短向量,每个物品也是。
+ 每个用户变成一个由 {String.raw`k`} 个潜在因子
+ 组成的短向量,每个物品也是。
一个预测的评分,就是一个用户的向量与一个物品的向量的点积。这些因子是学出来的、而非标注的——但它们
往往对应可解释的维度(「有多科幻」「有多轻松」),而一个用户在某个因子上的分数,乘以一个物品在
同一个因子上的分数,就捕捉到了他们的契合度。
@@ -392,36 +511,41 @@ function ZhBody() {
/>
这与 PCA 是同一个
- 低秩、潜在维度 的想法——把一个巨大的矩阵压缩进几个有意义的维度——这正是为什么线性
- 代数的基础在这里直接见效。它还优雅地处理稀疏性:你只在你确实 观测到的格子上拟合,而分解会
- 泛化到其余的格子。
+ 低秩、潜在维度
+ 的想法——把一个巨大的矩阵压缩进几个有意义的维度——这正是为什么线性
+ 代数的基础在这里直接见效。它还优雅地处理稀疏性:你只在你确实
+ 观测到的格子上拟合,而分解会 泛化到其余的格子。
- 协同过滤的一大弱点是冷启动问题 :它需要交互历史,而一个全新的用户或物品一点历史都没有。
+ 协同过滤的一大弱点是冷启动问题
+ :它需要交互历史,而一个全新的用户或物品一点历史都没有。
你没法给一个你一无所知的人推荐,你也没法把一个刚加进来、还没人碰过的物品推上去。这是每一个新推荐器
核心处那个先有鸡还是先有蛋的难题。
- 标准的修法是走混合 路线:对新用户和新物品依靠基于内容 的方法(它只需要特征),
- 然后随着交互历史的积累转向协同 过滤。大多数生产系统正是因为这个原因而是混合的——每种方法都
- 覆盖另一种的盲点。
+ 标准的修法是走混合 路线:对新用户和新物品依靠基于内容
+ 的方法(它只需要特征), 然后随着交互历史的积累转向协同
+ 过滤。大多数生产系统正是因为这个原因而是混合的——每种方法都 覆盖另一种的盲点。
- 评估推荐器比一个单一的准确率数字更微妙,因为要紧的是你展示之物的顺序 ,而非一个精确的
- 评分。指标是 以排序为中心的:precision@k
- (在前 k 个推荐里,有多少是相关的?)和 NDCG (它还奖励把最好的物品放到最高)。你关心的
- 是列表的顶部——没人会滚动到第 200 条推荐。
+ 评估推荐器比一个单一的准确率数字更微妙,因为要紧的是你展示之物的顺序
+ ,而非一个精确的 评分。指标是
+ 以排序为中心的:precision@k
+ (在前 k 个推荐里,有多少是相关的?)和 NDCG
+ (它还奖励把最好的物品放到最高)。你关心的 是列表的顶部——没人会滚动到第 200 条推荐。
- 更深的陷阱是离线—在线鸿沟 。一个在历史数据上分数漂亮的推荐器,可能在真实用户那里
- 一败涂地,因为离线数据只记录了人们对旧 系统的推荐做了什么——它没法告诉你他们会对真正新的
+ 更深的陷阱是离线—在线鸿沟
+ 。一个在历史数据上分数漂亮的推荐器,可能在真实用户那里
+ 一败涂地,因为离线数据只记录了人们对旧
+ 系统的推荐做了什么——它没法告诉你他们会对真正新的
建议如何反应。这正是为什么认真的推荐器最终是由实时的
A/B 测试来评判的,而非单凭离线分数。
@@ -434,16 +558,20 @@ function ZhBody() {
- 流行度偏差 ——群体的最爱被推荐得最多,于是它们被交互得最多,于是它们被推荐得更多。
+ 流行度偏差
+ ——群体的最爱被推荐得最多,于是它们被交互得最多,于是它们被推荐得更多。
富者愈富,而小众的物品始终不可见。
- 过滤气泡 / 回音室 ——通过给你看越来越多你已经参与的东西,系统收窄了你的世界,这对
+ 过滤气泡 / 回音室
+ ——通过给你看越来越多你已经参与的东西,系统收窄了你的世界,这对
电影是无害的,对新闻和观点却是腐蚀性的。
- 反馈回路 ——模型的推荐成为它接下来训练所用的数据,于是它从自己的影响里学习,并可能
- 失控地盘旋上升。这是 奖励塑形问题换了个样子:
+ 反馈回路
+ ——模型的推荐成为它接下来训练所用的数据,于是它从自己的影响里学习,并可能
+ 失控地盘旋上升。这是 奖励塑形
+ 问题换了个样子:
优化原始的参与度,你可能恰恰放大那些最大化点击的耸动或上瘾的内容,不管它对任何人是否有好处。
@@ -452,17 +580,21 @@ function ZhBody() {
- 推荐系统不是政府分析师的核心工具,但它的机制可以推广到任何排序或优先级排定 问题——
- 从一长串里把最值得关注的案件、物品或线索浮现出来,这是一种不断重现的形状。矩阵分解 /
- 潜在因子 的想法就是与 PCA 相同的
- 低秩压缩,而那些排序指标 (precision@k)是你评判任何「给我看前 N 个」系统的方式。
+ 推荐系统不是政府分析师的核心工具,但它的机制可以推广到任何
+ 排序或优先级排定 问题——
+ 从一长串里把最值得关注的案件、物品或线索浮现出来,这是一种不断重现的形状。
+ 矩阵分解 / 潜在因子 的想法就是与{" "}
+ PCA 相同的 低秩压缩,而那些
+ 排序指标 (precision@k)是你评判任何「给我看前 N 个」系统的方式。
- 不过,最能迁移过来的,是那个警示性的一半。反馈回路 ——一个在自己过去输出的后果
+ 不过,最能迁移过来的,是那个警示性的一半。反馈回路
+ ——一个在自己过去输出的后果
之上训练的模型——是一个远超推荐器的陷阱:任何作用于世界、然后又从那个被改变的世界里学习的系统,
- 都有把自己的偏差固化下来的风险,这直接连到 公平和
- 漂移问题。知道这个失败模式,正是让你能去提防它的
- 东西。
+ 都有把自己的偏差固化下来的风险,这直接连到
+ 公平和
+ 漂移
+ 问题。知道这个失败模式,正是让你能去提防它的 东西。
@@ -475,22 +607,25 @@ function ZhBody() {
基于内容 (按特征推荐相似物品——能处理新物品,但把你困在你的口味里)对
- 协同 (用群体的交互——「像你这样的人喜欢」;物品—物品驱动大多数大系统)。
+ 协同
+ (用群体的交互——「像你这样的人喜欢」;物品—物品驱动大多数大系统)。
- 矩阵分解 ({String.raw`R \approx U V^\top`} )为每个用户/物品学出短的
+ 矩阵分解 ({String.raw`R \approx U V^\top`}
+ )为每个用户/物品学出短的
潜在因子 向量;评分 = 点积。与 PCA 同一个低秩的想法。
- 冷启动问题 (新用户/物品,无历史)→ 走混合 (新的用内容,历史
- 增长后用协同)。
+ 冷启动问题 (新用户/物品,无历史)→ 走混合
+ (新的用内容,历史 增长后用协同)。
- 用排序 评估(precision@k、NDCG)——但留意离线—在线鸿沟 ;由实时
- A/B 测试评判。
+ 用排序 评估(precision@k、NDCG)——但留意离线—在线鸿沟
+ ;由实时 A/B 测试评判。
- 重大的问题:流行度偏差、过滤气泡、反馈回路 ——推荐器塑造行为,而不只是预测它。
+ 重大的问题:流行度偏差、过滤气泡、反馈回路
+ ——推荐器塑造行为,而不只是预测它。
diff --git a/components/knowledge/content/reinforcement-learning.jsx b/components/knowledge/content/reinforcement-learning.jsx
index a7ca6968..2f592c94 100644
--- a/components/knowledge/content/reinforcement-learning.jsx
+++ b/components/knowledge/content/reinforcement-learning.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/reinforcement-learning.
diff --git a/components/knowledge/content/robust-statistics.jsx b/components/knowledge/content/robust-statistics.jsx
index 3d5b454c..3600aa10 100644
--- a/components/knowledge/content/robust-statistics.jsx
+++ b/components/knowledge/content/robust-statistics.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/robust-statistics.
@@ -34,15 +28,49 @@ function BreakdownFigure({
role="img"
aria-label={ariaLabel}
>
- {meanLabel}
+
+ {meanLabel}
+
- {meanNote}
- {medianLabel}
+
+ {meanNote}
+
+
+ {medianLabel}
+
- {medianNote}
-
- {zeroLabel}
- {fiftyLabel}
+
+ {medianNote}
+
+
+
+ {zeroLabel}
+
+
+ {fiftyLabel}
+
);
@@ -284,25 +312,29 @@ function ZhBody() {
return (
<>
- 关于那些最熟悉的统计量,有一个令人不安的事实: 均值、标准差
- 和最小二乘回归全都脆弱 。单单一个极端的离群值——一个打字错误、一次传感器故障、一个
+ 关于那些最熟悉的统计量,有一个令人不安的事实: 均值
+ 、标准差 和最小二乘回归全都脆弱
+ 。单单一个极端的离群值——一个打字错误、一次传感器故障、一个
真实的怪例——就能把均值从数据主体所在之处拖得很远,把标准差吹胀,并把一条回归线从其他所有点都遵循的
- 模式上扳开。而真实数据充满 这样的污染。稳健统计 是那套正是为抵抗这一点而设计的
- 方法:给出一个反映数据主体 的答案,而非被几个坏点挟持。
+ 模式上扳开。而真实数据充满 这样的污染。稳健统计
+ 是那套正是为抵抗这一点而设计的 方法:给出一个反映数据主体
+ 的答案,而非被几个坏点挟持。
它独特而极其实用——与那套假设数据干净的经典工具箱是不同的心态。这一页讲为什么标准方法会崩、我们
- 度量 稳健性的精确方式(崩溃点)、抵抗性的替代品(中位数、MAD、M 估计量),以及稳健性所逼出的
- 那个判断:修方法,还是调查离群值?它建立在 统计学和
+ 度量 稳健性的精确方式(崩溃点)、抵抗性的替代品(中位数、MAD、M
+ 估计量),以及稳健性所逼出的 那个判断:修方法,还是调查离群值?它建立在
+ 统计学和
数据准备页之上。
- 这种脆弱来自平方 。均值和最小二乘都最小化平方 误差,而平方给了离群值巨大的
+ 这种脆弱来自平方 。均值和最小二乘都最小化平方
+ 误差,而平方给了离群值巨大的
杠杆:一个离十个单位远的点,对损失贡献一百,于是估计值会不惜代价去迁就它。
- [1, 2, 3, 4, 1000] 的均值是 202——一个没有 任何数据点靠近的值,是对「数据落在
- 哪里」一个无用的概括。一个点,全面扭曲。
+ [1, 2, 3, 4, 1000] 的均值是 202——一个没有
+ 任何数据点靠近的值,是对「数据落在 哪里」一个无用的概括。一个点,全面扭曲。
同样的事发生在回归线上:单单一个高杠杆的离群值,就能把整个拟合旋转过去,产出一条歪曲了其他每个点都
@@ -313,7 +345,8 @@ function ZhBody() {
- 稳健性有一个精确、漂亮的度量:崩溃点 ——在估计量给出一个无意义(无界地错误)的答案
+ 稳健性有一个精确、漂亮的度量:崩溃点
+ ——在估计量给出一个无意义(无界地错误)的答案
之前,数据中可以被任意污染的那个比例。它是这个领域的标志性数字。
均值的崩溃点是 0% ——单单一个 被推向无穷的点就把它拖向无穷。
- 中位数的崩溃点是 50% ——你可以污染(将近)一半的数据,而中位数仍然合理地坐落在好的
- 那一半之中。50% 是可能的最大值(超过一半,「离群值」就是 数据了),这使中位数成为存在的最
+ 中位数的崩溃点是 50%
+ ——你可以污染(将近)一半的数据,而中位数仍然合理地坐落在好的 那一半之中。50%
+ 是可能的最大值(超过一半,「离群值」就是 数据了),这使中位数成为存在的最
稳健的集中趋势估计。那个差距——0% 对 50%——就是稳健统计的全部理由,浓缩在一个对比里。
@@ -338,35 +372,38 @@ function ZhBody() {
那些脆弱的经典量的稳健替代品,是你已经半知半解的:
- 对集中趋势 :用中位数 代替均值——不受极端值有多极端的影响,只受每一侧坐落着
- 多少个点的影响。
+ 对集中趋势 :用中位数
+ 代替均值——不受极端值有多极端的影响,只受每一侧坐落着 多少个点的影响。
- 对离散程度 :用 MAD (中位数绝对偏差)代替标准差。它是到中位数的绝对距离的
- 中位数——一种对中位数的两阶段使用(中心,然后典型偏差),继承了它 50% 的崩溃点。标准差建立在平方
- 偏差之上,会被单单一个离群值吹胀;MAD 则不当回事。
+ 对离散程度 :用 MAD
+ (中位数绝对偏差)代替标准差。它是到中位数的绝对距离的
+ 中位数——一种对中位数的两阶段使用(中心,然后典型偏差),继承了它 50%
+ 的崩溃点。标准差建立在平方 偏差之上,会被单单一个离群值吹胀;MAD 则不当回事。
- 这些不只是替代品——它们是抵抗性的基础 ,也是为什么一次稳健的分析常常在做别的之前,先悄悄地
- 把均值换成中位数、把标准差换成 MAD。
+ 这些不只是替代品——它们是抵抗性的基础
+ ,也是为什么一次稳健的分析常常在做别的之前,先悄悄地 把均值换成中位数、把标准差换成 MAD。
- 中位数稳健,但扔掉了信息(它忽略实际的值,只看它们的顺序),所以当数据确实 干净时它效率
- 较低。M 估计量 是优雅的中间地带:不是最小化平方误差(它过度加权离群值)或绝对误差
- (稳健但效率较低),而是用一个对小残差表现得像平方误差 、对大残差表现得像绝对误差 的
- 损失。著名的例子是 Huber 损失 :
+ 中位数稳健,但扔掉了信息(它忽略实际的值,只看它们的顺序),所以当数据确实
+ 干净时它效率 较低。M 估计量
+ 是优雅的中间地带:不是最小化平方误差(它过度加权离群值)或绝对误差
+ (稳健但效率较低),而是用一个对小残差表现得像平方误差 、对
+ 大残差表现得像绝对误差 的 损失。著名的例子是 Huber 损失 :
{String.raw`L_\delta(r) = \begin{cases} \tfrac{1}{2}r^2 & |r| \le \delta \\[4pt] \delta\,(|r| - \tfrac{1}{2}\delta) & |r| > \delta \end{cases}`}
- 在阈值 {String.raw`\delta`} 之下,它是高效的平方损失;之上,损失只线性 增长,于是
- 一个遥远的离群值的影响被封顶,而非二次地爆炸。那一个弯就是全部的诀窍——它平滑地降低离群值的
- 权重 ,同时在表现良好的大多数上保持最小二乘的统计效率。M 估计量给你一个在稳健性与效率之间
+ 在阈值 {String.raw`\delta`} 之下,它是高效的平方损失;之上,损失只线性
+ 增长,于是 一个遥远的离群值的影响被封顶,而非二次地爆炸。那一个弯就是全部的诀窍——它
+ 平滑地降低离群值的 权重
+ ,同时在表现良好的大多数上保持最小二乘的统计效率。M 估计量给你一个在稳健性与效率之间
可调的旋钮,这正是为什么 Huber 损失也作为一个损失函数出现在
机器学习里。
@@ -384,7 +421,8 @@ function ZhBody() {
Huber / M 估计量回归 ——上面那种平滑的降权,应用到拟合上。
- RANSAC ——在随机子集上拟合,保留最多点认同的那个模型,明确地把离群值当作「非共识」
+ RANSAC
+ ——在随机子集上拟合,保留最多点认同的那个模型,明确地把离群值当作「非共识」
忽略掉。在计算机视觉里常见。
@@ -397,18 +435,21 @@ function ZhBody() {
稳健统计逼出一个容易搞错的判断,而它是最重要的部分:
- 稳健性与删除或忽略离群值不是一回事。 一个稳健的方法降低一个离群值对估计的
- 影响 ——但那个离群值仍然在那里,而它可能是数据集里最重要的东西。一个极端值可以是一个要去
- 稳健化抵抗的打字错误,也可以 是一个真实的信号——那个欺诈、那次入侵、那个突破——而这正是
- 异常检测所要猎取的。所以需要的纪律是:用稳健的
- 方法,让几个坏点不至于悄悄毁掉你的中心估计,并且 始终亲自去看那些离群值,去判断它们是该
+ 稳健性与删除或忽略离群值不是一回事。 一个稳健的方法降低一个离群值
+ 对估计的 影响
+ ——但那个离群值仍然在那里,而它可能是数据集里最重要的东西。一个极端值可以是一个要去
+ 稳健化抵抗的打字错误,也可以
+ 是一个真实的信号——那个欺诈、那次入侵、那个突破——而这正是
+ 异常检测
+ 所要猎取的。所以需要的纪律是:用稳健的 方法,让几个坏点不至于悄悄毁掉你的中心估计,
+ 并且 始终亲自去看那些离群值,去判断它们是该
折扣掉的错误,还是该去追的证据。盲目地稳健化,可能扔掉那个发现;盲目地信任最小二乘,会让一个打字
错误毁掉整个分析。本领在于同时握住两者。
- (还有那个诚实的代价:当数据真的是 干净、表现良好的时候,稳健方法比经典方法稍微低效
- 一点——为你通常都有的、防污染的保险所付的一个小价钱。)
+ (还有那个诚实的代价:当数据真的是 干净、表现良好的时候,稳健方法比经典方法稍微
+ 低效 一点——为你通常都有的、防污染的保险所付的一个小价钱。)
@@ -417,16 +458,19 @@ function ZhBody() {
真实的政府数据很脏——打字错误、传感器错误、键入错误的条目、真实的极端案例——所以均值和最小二乘的
脆弱是一个活生生的风险,而非教科书的脚注。稳健统计给我的最有价值的本能,是在污染有可能时,用
- 中位数和 MAD 而非均值和标准差,这样一小撮坏记录不至于悄悄劫持一个概括或一个拟合的
- 关系。崩溃点 (均值 0%,中位数 50%)是记住为什么的那个干脆的方式。
+ 中位数和 MAD
+ 而非均值和标准差,这样一小撮坏记录不至于悄悄劫持一个概括或一个拟合的 关系。
+ 崩溃点 (均值 0%,中位数 50%)是记住为什么的那个干脆的方式。
- 但那个判断才是真正的教训:稳健 ≠ 忽略 。廉政或情报数据里的一个离群值,可能正是
- 那个要紧的案件 ——所以我稳健化那个中心估计,并 调查那个离群值
- ( 异常检测的心态),而非让一个方法悄悄替我决定。
- 它连到 数据准备(处理离群值)、
- 分位数回归(中位数回归是稳健的),以及那个更
- 宏大的主题——不要让几个点在任一方向上把你骗了。
+ 但那个判断才是真正的教训:稳健 ≠ 忽略
+ 。廉政或情报数据里的一个离群值,可能正是
+ 那个要紧的案件 ——所以我稳健化那个中心估计,并 调查那个离群值 (
+ 异常检测
+ 的心态),而非让一个方法悄悄替我决定。 它连到
+ 数据准备(处理离群值)、
+ 分位数回归
+ (中位数回归是稳健的),以及那个更 宏大的主题——不要让几个点在任一方向上把你骗了。
@@ -435,33 +479,35 @@ function ZhBody() {
- 均值、标准差和最小二乘都脆弱 ——它们最小化平方 误差,所以一个离群值
- 获得巨大的杠杆,劫持估计。
+ 均值、标准差和最小二乘都脆弱 ——它们最小化平方
+ 误差,所以一个离群值 获得巨大的杠杆,劫持估计。
崩溃点 度量稳健性——在估计变得无意义之前你能污染的数据比例。
均值 = 0%,中位数 = 50% (最大值)。
- 抵抗性的基础:中位数 (对均值)和 MAD (对标准差)——都是 50%
- 崩溃。
+ 抵抗性的基础:中位数 (对均值)和 MAD
+ (对标准差)——都是 50% 崩溃。
- M 估计量 (例如 Huber 损失 )= 对小残差用平方误差、对大的用
- 线性——封顶一个离群值的影响 ,同时保持效率。一个可调的稳健性/效率旋钮。
+ M 估计量 (例如 Huber 损失 )=
+ 对小残差用平方误差、对大的用 线性——封顶一个离群值的影响
+ ,同时保持效率。一个可调的稳健性/效率旋钮。
- 稳健回归 :最小绝对偏差、Huber、RANSAC——拟合主体 所支持的那条线。
+ 稳健回归 :最小绝对偏差、Huber、RANSAC——拟合主体
+ 所支持的那条线。
- 关键的判断:稳健 ≠ 删除 。稳健化估计,并 调查离群值——它可能就是信号。
- (数据干净时有小的效率代价。)
+ 关键的判断:稳健 ≠ 删除 。稳健化估计,并
+ 调查离群值——它可能就是信号。 (数据干净时有小的效率代价。)
- 崩溃点、中位数/MAD 的抵抗性、Huber 损失 M 估计量,以及稳健化对调查的判断,反映了当前的稳健统计
- 参考文献以及课程。
+ 崩溃点、中位数/MAD 的抵抗性、Huber 损失 M
+ 估计量,以及稳健化对调查的判断,反映了当前的稳健统计 参考文献以及课程。
>
diff --git a/components/knowledge/content/sampling-survey-methodology.jsx b/components/knowledge/content/sampling-survey-methodology.jsx
index ca357497..659eed89 100644
--- a/components/knowledge/content/sampling-survey-methodology.jsx
+++ b/components/knowledge/content/sampling-survey-methodology.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, TeX, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/sampling-survey-methodology.
diff --git a/components/knowledge/content/spatial-statistics.jsx b/components/knowledge/content/spatial-statistics.jsx
index 49925d40..cad5c1f1 100644
--- a/components/knowledge/content/spatial-statistics.jsx
+++ b/components/knowledge/content/spatial-statistics.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/spatial-statistics.
@@ -25,8 +19,20 @@ function MoranFigure({ caption, ariaLabel, clusteredLabel, randomLabel }) {
role="img"
aria-label={ariaLabel}
>
- {clusteredLabel}
- {randomLabel}
+
+ {clusteredLabel}
+
+
+ {randomLabel}
+
{/* clustered grid: filled top-left block */}
{[0, 1, 2, 3].map((r) =>
[0, 1, 2, 3].map((c) => {
@@ -311,35 +317,39 @@ function ZhBody() {
<>
给数据一个位置,某种微妙的事就会发生:标准的统计工具箱悄悄不再有效。大多数方法假设你的观测是
- 独立的 ——但彼此邻近的地方明确地不 独立。相邻的街区收入相似,相邻的地区天气
- 相似,邻近的区域犯罪率相似。空间统计 是为地理要紧的数据而建的分支,它的全部目的,是认真
- 对待那种空间依赖,而非假装它不存在。
+ 独立的 ——但彼此邻近的地方明确地不
+ 独立。相邻的街区收入相似,相邻的地区天气 相似,邻近的区域犯罪率相似。空间统计
+ 是为地理要紧的数据而建的分支,它的全部目的,是认真 对待那种空间依赖,而非假装它不存在。
- 它与 GIS 页不同——那一页是处理空间数据的工具;这
- 一页是位置的推断统计学 :如何度量空间模式、检验一个聚集是否真实,以及在空间上做预测。这一页
+ 它与 GIS{" "}
+ 页不同——那一页是处理空间数据的工具;这 一页是位置的推断统计学
+ :如何度量空间模式、检验一个聚集是否真实,以及在空间上做预测。这一页
讲核心的想法,而反复出现的主题是:忽略「在哪里」会产出自信而错误的答案。
- 经典统计里几乎每一种方法,都建立在独立观测
- 的假设之上——知道一个数据点,告诉不了你关于下一个的任何东西。空间数据公然违反这一点:知道
- 一个街区的值,告诉你很多关于它邻居的事。这种空间自相关 意味着你的有效样本量比它看上去
- 的要小(邻近的点携带冗余的信息),所以普通的分析报告过度自信 的结果——并不存在的
- 显著性、被共同位置吹胀的相关。
+ 经典统计里几乎每一种方法,都建立在
+ 独立观测
+ 的假设之上——知道一个数据点,告诉不了你关于下一个的任何东西。空间数据公然违反这一点:知道
+ 一个街区的值,告诉你很多关于它邻居的事。这种空间自相关
+ 意味着你的有效样本量比它看上去 的要小(邻近的点携带冗余的信息),所以普通的分析报告
+ 过度自信 的结果——并不存在的 显著性、被共同位置吹胀的相关。
- 所以空间统计做两件事:它度量 空间依赖(有没有模式,在哪里?),并在模型里考虑 它,
- 好让结论保持诚实。两者都从一个奠基性的想法出发。
+ 所以空间统计做两件事:它度量 空间依赖(有没有模式,在哪里?),并在模型里
+ 考虑 它, 好让结论保持诚实。两者都从一个奠基性的想法出发。
- 托布勒的地理学第一定律 把它说得很直白:「万物彼此相关,但近的东西比远的东西更
- 相关。」 那是整个领域的引擎。空间自相关可以是正的 (常见情形——相似的值聚在
- 一起,比如财富或温度),或者更罕见地,负的 (高值系统性地紧挨着低值,像一张
+ 托布勒的地理学第一定律 把它说得很直白:
+ 「万物彼此相关,但近的东西比远的东西更 相关。」
+ 那是整个领域的引擎。空间自相关可以是正的 (常见情形——相似的值聚在
+ 一起,比如财富或温度),或者更罕见地,负的
+ (高值系统性地紧挨着低值,像一张
棋盘)。下面这些度量的目标,是检测并量化正在发生的是哪一种、以及在哪里——把「那看起来很聚集」一个
模糊的印象,变成一个可检验的论断。
@@ -347,8 +357,9 @@ function ZhBody() {
- 在你能度量空间关系之前,你必须把什么算作「近」形式化。那就是空间权重矩阵 ——对每一对
- 位置,一个说明它们有多连通的权重。常见的选择:邻接 (共享一条边界的地区是邻居)、
+ 在你能度量空间关系之前,你必须把什么算作「近」形式化。那就是空间权重矩阵
+ ——对每一对 位置,一个说明它们有多连通的权重。常见的选择:邻接
+ (共享一条边界的地区是邻居)、
距离 (k 公里之内的一切),或 k-最近 (每个区域最近的 k 个其他区域)。
@@ -363,16 +374,18 @@ function ZhBody() {
空间自相关的标准全局度量是 Moran's I 。它本质上是空间的一个
- 相关系数:它问一个位置的值是否倾向于与它邻居的值相匹配,
- 在整张地图上。
+ 相关
+ 系数:它问一个位置的值是否倾向于与它邻居的值相匹配, 在整张地图上。
{String.raw`I = \frac{N}{\sum_{i}\sum_{j} w_{ij}} \cdot \frac{\sum_{i}\sum_{j} w_{ij}\,(x_i - \bar{x})(x_j - \bar{x})}{\sum_{i} (x_i - \bar{x})^2}`}
- 你不需要背公式——读它的行为。Moran's I 大致从 −1 跑到 +1:一个远高于 0 的值意味着正
- 自相关(聚集——相似的值彼此邻近),接近 0 意味着一个随机的空间模式,而
- 低于 0 意味着分散。关键的是,你要检验它的显著性(对照「随机排列」的零假设),这样你
+ 你不需要背公式——读它的行为。Moran's I 大致从 −1 跑到 +1:一个远高于 0
+ 的值意味着正 自相关(聚集——相似的值彼此邻近),接近 0
+ 意味着一个随机的空间模式,而
+ 低于 0
+ 意味着分散。关键的是,你要检验它的显著性(对照「随机排列」的零假设),这样你
就能说一个表面的聚集是真实的、还是很容易出于偶然——这正是发现一个真正的模式,与在云里看见人脸之间
的差别。
@@ -387,7 +400,8 @@ function ZhBody() {
Moran's I 对整张 地图给出一个数字——但通常有趣的问题是聚集在哪里 。
- LISA (空间关联局部指标)把这个全局的统计量分解成每个位置的一个值,揭示出局部的结构。
+ LISA
+ (空间关联局部指标)把这个全局的统计量分解成每个位置的一个值,揭示出局部的结构。
它把每个区域分类为属于一个:
@@ -398,8 +412,8 @@ function ZhBody() {
低—低 聚集——一个冷点(低值之中的低值)。
- 高—低 / 低—高 ——空间离群值 :一个与其周遭对着干的值(安全地区里
- 一个高犯罪的口袋),往往是所有情形里最有趣的。
+ 高—低 / 低—高 ——空间离群值
+ :一个与其周遭对着干的值(安全地区里 一个高犯罪的口袋),往往是所有情形里最有趣的。
@@ -411,12 +425,14 @@ function ZhBody() {
当你建模一个空间的结果(收入能否预测各地区的健康?)时,如果残差有空间自相关,普通
- 回归就无效了——独立性假设被打破,标准误在
- 撒谎。空间回归 通过把空间结构建进模型里来修这个:
+ 回归
+ 就无效了——独立性假设被打破,标准误在 撒谎。空间回归
+ 通过把空间结构建进模型里来修这个:
- 空间滞后 模型——把邻居的结果值作为一个预测变量纳入,捕捉真正的溢出(一个地区的值被
+ 空间滞后
+ 模型——把邻居的结果值作为一个预测变量纳入,捕捉真正的溢出(一个地区的值被
它邻居的所塑造)。
@@ -424,20 +440,23 @@ function ZhBody() {
- 无论哪种方式,要点都与别处的 因果纪律一样:尊重数据的
- 结构,否则你的自信是假的。
+ 无论哪种方式,要点都与别处的 因果
+ 纪律一样:尊重数据的 结构,否则你的自信是假的。
- 最后一个大工具是空间预测 。你在散落的位置上测量了一个值(气象站的降雨、采样点的一种
- 污染物),想估计它在中间的每一处 。克里金 做这件事,而它比朴素的插值更聪明:它
- 使用测量出的 空间自相关结构——相似性随距离衰减得有多快——在每个未采样的点上做出统计上最优的
+ 最后一个大工具是空间预测
+ 。你在散落的位置上测量了一个值(气象站的降雨、采样点的一种 污染物),想估计它
+ 在中间的每一处 。克里金 做这件事,而它比朴素的插值更聪明:它 使用
+ 测量出的
+ 空间自相关结构——相似性随距离衰减得有多快——在每个未采样的点上做出统计上最优的
预测,还附带一个不确定性估计。
- 它是 时间序列页上那些预测想法的空间兄弟(在空间上
+ 它是 时间序列
+ 页上那些预测想法的空间兄弟(在空间上
插值,而非在时间上外推),也是你把一小撮采样点变成一个连续、诚实的曲面的方式——带着那个关键的告诫:
你离任何真实的测量越远,不确定性就越大。
@@ -448,16 +467,16 @@ function ZhBody() {
大量的政府分析无可逃避地是空间性的——事件、区域模式、把资源往哪里调——而这个学科给我的最有价值的
一样东西,是一个统计上显著的热点 ,与一个只是噪声的抢眼聚集之间的差别。
- Moran's I 检验到底有没有真实的空间结构,而 LISA 钉住真正的热点
- (以及那些说明问题的离群值)在哪里 ——这正是你在对「它在哪里最严重?」采取行动之前所
- 需要的。
+ Moran's I 检验到底有没有真实的空间结构,而 LISA {" "}
+ 钉住真正的热点 (以及那些说明问题的离群值)在哪里
+ ——这正是你在对「它在哪里最严重?」采取行动之前所 需要的。
- 它还让建模保持诚实:把空间依赖的数据当作独立的来处理,会产出过度自信 的结论,
- 所以空间回归 是阻止一项区域分析夸大它确定性的东西——与
+ 它还让建模保持诚实:把空间依赖的数据当作独立的来处理,会产出过度自信
+ 的结论, 所以空间回归 是阻止一项区域分析夸大它确定性的东西——与
因果推断同样的「尊重结构」的教训。它补足
- GIS 页(工具对推断),并共享那个关于「面积单元
- 的选择如何塑造一切」的 MAUP 告诫。
+ GIS{" "}
+ 页(工具对推断),并共享那个关于「面积单元 的选择如何塑造一切」的 MAUP 告诫。
@@ -466,32 +485,32 @@ function ZhBody() {
- 空间数据打破独立性假设 ——邻近的地方相关(空间自相关 ),所以
- 朴素的统计过度自信 。
+ 空间数据打破独立性假设 ——邻近的地方相关(空间自相关
+ ),所以 朴素的统计过度自信 。
- 托布勒第一定律 :近的东西比远的东西更相关。正自相关(聚集)是常见情形。
+ 托布勒第一定律
+ :近的东西比远的东西更相关。正自相关(聚集)是常见情形。
- 通过空间权重矩阵 定义邻居 (邻接 / 距离 / k-最近)——一个真正的
- 建模选择。
+ 通过空间权重矩阵 定义邻居 (邻接 / 距离 /
+ k-最近)——一个真正的 建模选择。
- Moran's I = 空间的一个相关(远高于 0 = 聚集;≈0 = 随机),检验显著性。
+ Moran's I = 空间的一个相关(远高于 0 = 聚集;≈0 =
+ 随机),检验显著性。
LISA 找到在哪里 ——高—高热点、低—低冷点,以及离群值。
- 空间回归 (滞后/误差模型)把结构建进去,好让推断保持诚实。克里金
- 用自相关在采样点之间预测,附带不确定性。
-
-
- 贯穿始终的:尊重「在哪里」,否则得到自信而错误的答案(并留意 MAUP)。
+ 空间回归 (滞后/误差模型)把结构建进去,好让推断保持诚实。
+ 克里金 用自相关在采样点之间预测,附带不确定性。
+ 贯穿始终的:尊重「在哪里」,否则得到自信而错误的答案(并留意 MAUP)。
- 托布勒定律、Moran's I / LISA 热点框架、空间权重,以及克里金,反映了当前的空间统计参考文献以及
- 统计学课程。
+ 托布勒定律、Moran's I / LISA
+ 热点框架、空间权重,以及克里金,反映了当前的空间统计参考文献以及 统计学课程。
>
diff --git a/components/knowledge/content/statistical-machine-learning.jsx b/components/knowledge/content/statistical-machine-learning.jsx
index 4b90f2b2..6ef5ac4e 100644
--- a/components/knowledge/content/statistical-machine-learning.jsx
+++ b/components/knowledge/content/statistical-machine-learning.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/statistical-machine-learning.
diff --git a/components/knowledge/content/statistical-modelling.jsx b/components/knowledge/content/statistical-modelling.jsx
index be7fbf6c..b56c52f5 100644
--- a/components/knowledge/content/statistical-modelling.jsx
+++ b/components/knowledge/content/statistical-modelling.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/statistical-modelling.
diff --git a/components/knowledge/content/statistical-process-control.jsx b/components/knowledge/content/statistical-process-control.jsx
index 3dd2d8d9..f3c5f045 100644
--- a/components/knowledge/content/statistical-process-control.jsx
+++ b/components/knowledge/content/statistical-process-control.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/statistical-process-control.
@@ -41,25 +35,97 @@ function ControlChartFigure({ caption, ariaLabel, centreLabel, specialCauseLabel
aria-label={ariaLabel}
>
{/* limit lines */}
-
- UCL
-
- {centreLabel}
-
- LCL
+
+
+ UCL
+
+
+
+ {centreLabel}
+
+
+
+ LCL
+
{/* points */}
{PTS.map(([x, y], i) => {
const breach = y < 28;
return (
{i > 0 && (
-
+
)}
-
+
);
})}
- {specialCauseLabel}
+
+ {specialCauseLabel}
+
);
@@ -299,24 +365,29 @@ function ZhBody() {
<>
一个你随时间盯着的指标——一段处理时间、一个错误率、一个每日计数——总在抖动。每次它一动,那个难题
- 就是:真有什么变了,还是这只是这个过程一直都有的正常抖动? 对噪声反应过度,你就把
+ 就是:真有什么变了,还是这只是这个过程一直都有的正常抖动?
+ 对噪声反应过度,你就把
力气浪费在追鬼上(而且往往把事情弄得更糟);忽略一个真实的偏移,你就错过一个真正的问题。
- 统计过程控制 (SPC)是把这两者分辨开的、有几十年历史的、极为实用的学问——好让你对真实
+ 统计过程控制
+ (SPC)是把这两者分辨开的、有几十年历史的、极为实用的学问——好让你对真实
的信号做出回应,而把噪声留在那里别动。
SPC 诞生于制造业(休哈特与戴明),却适用于任何重复的过程,它是
异常检测和
- 流式监控的近亲,有一个锋利的概念内核。这一页讲的
+ 流式监控
+ 的近亲,有一个锋利的概念内核。这一页讲的
就是那个内核:它核心处的变异区分、把它落地的控制图,以及它那著名界限背后的取舍。
- 整个领域都建立在一次重新框定之上。在 SPC 之前,人们对每一次起落都做出反应——糟糕的一天的数字会
- 触发一阵手忙脚乱去「修」点什么,即便其实什么都没变。休哈特的洞见是:一个稳定的过程仍然会
- 变化 ,而把那种固有的变异当作信号来反应——瞎调 ——通常把过程弄得更糟 、
- 而非更好。SPC 的工作是划出一条有原则的线:这么多抖动是正常的,别去动它;那个 是一个真正的
+ 整个领域都建立在一次重新框定之上。在 SPC
+ 之前,人们对每一次起落都做出反应——糟糕的一天的数字会
+ 触发一阵手忙脚乱去「修」点什么,即便其实什么都没变。休哈特的洞见是:
+ 一个稳定的过程仍然会 变化 ,而把那种固有的变异当作信号来反应——
+ 瞎调 ——通常把过程弄得更糟 、 而非更好。SPC
+ 的工作是划出一条有原则的线:这么多抖动是正常的,别去动它;那个 是一个真正的
信号,去调查它。
@@ -325,26 +396,30 @@ function ZhBody() {
那个奠基性的区分——而且可以说是运营分析里最有用的单一想法:
- 常因变异 ——一个稳定过程自然、固有的抖动。许多微小的、无处不在的影响(条件、材料、
- 时机上的细微差别)。它在范围 上是可预测的,即便任何单个值不是,而它不值得去追
- ——这就是这个过程本来的样子。
+ 常因变异
+ ——一个稳定过程自然、固有的抖动。许多微小的、无处不在的影响(条件、材料、
+ 时机上的细微差别)。它在范围 上是可预测的,即便任何单个值不是,而它
+ 不值得去追 ——这就是这个过程本来的样子。
- 特因变异 ——一个不寻常的、可归因的 原因:真有什么变了(一个新供应商、一件
- 坏掉的工具、一次过程变更)。这产生一个落在正常范围之外的值(或模式),而它确实
+ 特因变异 ——一个不寻常的、可归因的
+ 原因:真有什么变了(一个新供应商、一件
+ 坏掉的工具、一次过程变更)。这产生一个落在正常范围之外的值(或模式),而它
+ 确实
值得调查——有一个真实的原因可找。
一个只表现出常因变异的过程是受控的 (稳定且可预测);一个有特因变异的过程是
- 失控的 (有要采取行动的东西)。SPC 的全部机制,都是为了可靠地把这两者分开——好让你只在
- 真有要行动的东西时才行动。
+ 失控的 (有要采取行动的东西)。SPC
+ 的全部机制,都是为了可靠地把这两者分开——好让你只在 真有要行动的东西时才行动。
- 把这一切落地的工具是控制图 :随时间画出这个指标,配上三条参考线——一条在过程平均值处的
+ 把这一切落地的工具是控制图
+ :随时间画出这个指标,配上三条参考线——一条在过程平均值处的
中心线 ,以及标出正常变异边界的上、下控制限 (UCL/LCL)。
- 只要点在界限内毫无规律地跳来跳去,过程就是受控的——那只是常因噪声,正确的行动是无 。当一个
+ 只要点在界限内毫无规律地跳来跳去,过程就是受控的——那只是常因噪声,正确的行动是无
+ 。当一个
点越过一条界限,那就是一个值得调查的特因信号。这张图把一个模糊的「那看起来偏高」变成一条有原则、
可重复的决策规则。
- 控制限按惯例设在中心线加减过程的三个标准差 处:
+
+ 控制限按惯例设在中心线加减过程的三个标准差 处:
+
{String.raw`\text{UCL},\,\text{LCL} = \mu \pm 3\sigma`}
- 为什么是三?这是一个深思熟虑的成本取舍 ,而认识到这一点是用好 SPC 的关键。对于
- 大致呈钟形的数据,只有约 0.3% 的点会偶然落在 ±3σ 之外——所以一次越界很可能 是一个真实的
+ 为什么是三?这是一个深思熟虑的成本取舍 ,而认识到这一点是用好 SPC
+ 的关键。对于 大致呈钟形的数据,只有约 0.3% 的点会偶然落在 ±3σ 之外——所以一次越界
+ 很可能 是一个真实的
信号,而非运气。把界限设得更紧(±2σ),你会更早捕捉到真实的偏移,但你也会得到多得多的
假警报 ——而对那些做出反应(瞎调)会浪费力气并使过程失稳。
这正是 假阳性对假阴性的取舍——与异常检测里同样的
- 警报疲劳张力。±3σ 是经过长期检验的甜蜜点:假
+ 警报疲劳张力。±3σ
+ 是经过长期检验的甜蜜点:假
警报够罕见,使一个信号值得信任;够敏感,能捕捉一次真实的、大的偏移。三西格玛不是一条自然法则——
它是关于「狼来了」的成本与错过一次变化的成本之间,一个校准良好的赌注。
@@ -384,58 +464,69 @@ function ZhBody() {
一个落在界限之外的点是显而易见的信号——但一个过程可能以没有任何单个点能捕捉的方式漂移。
- 西部电气规则 (以及纳尔逊的)增加了一些模式,即便每个点都在界限之内 ,也会
- 标记出一个特因:
+ 西部电气规则 (以及纳尔逊的)增加了一些模式,即便每个点都在界限之内
+ ,也会 标记出一个特因:
- 连续好几个点全都在中心线同一侧 的一连串(过程已经偏移)。
- 点稳定地向上或向下行进的一个趋势 (逐渐的漂移)。
+
+ 连续好几个点全都在中心线同一侧 的一连串(过程已经偏移)。
+
+
+ 点稳定地向上或向下行进的一个趋势 (逐渐的漂移)。
+
太多点聚集在远离中心之处,或其他非随机的模式。
- 贯穿始终的逻辑都一样:一个稳定 的过程在中心周围应当看起来随机 。任何
- 非随机 的模式——一连串、一个趋势、一个周期——都是某种系统性东西的指纹,即一个特因,甚至在
+ 贯穿始终的逻辑都一样:一个稳定 的过程在中心周围应当看起来随机
+ 。任何
+ 非随机
+ 的模式——一连串、一个趋势、一个周期——都是某种系统性东西的指纹,即一个特因,甚至在
一个点越界之前。SPC 既是关于发现结构,也是关于发现离群值。
- 经典的 休哈特图 只用当前的点,这使它非常擅长捕捉大 的突然偏移,却迟于注意到
- 一个小 的持续漂移。为此,有两种图使用历史 :
+ 经典的 休哈特图 只用当前的点,这使它非常擅长捕捉大
+ 的突然偏移,却迟于注意到 一个小 的持续漂移。为此,有两种图使用历史 :
- CUSUM (累积和)——累加偏离目标的运行中偏差,于是即便一个小的、一致的偏倚也会累加成
+ CUSUM
+ (累积和)——累加偏离目标的运行中偏差,于是即便一个小的、一致的偏倚也会累加成
一个清晰的信号。
EWMA (指数加权移动平均)——一个强调近期点的加权平均(与
- 时间序列同样的平滑想法),对逐渐的移动敏感。
+ 时间序列
+ 同样的平滑想法),对逐渐的移动敏感。
- 两者都被调来捕捉休哈特图会错过的慢漂移——你为你所担心的那种 变化挑选图。(还有针对不同
- 数据的图类型:测量值用 X-bar/R 图,比例用 p 图。)
+ 两者都被调来捕捉休哈特图会错过的慢漂移——你为你所担心的那种
+ 变化挑选图。(还有针对不同 数据的图类型:测量值用 X-bar/R 图,比例用 p 图。)
- 任何随时间被盯着的运营指标或 KPI——一段处理时间、一个量、一个错误或合规率——都引出 SPC 所回答的
- 那个问题:这是一次真实的变化,还是只是正常的波动? 最有价值的单一想法是
- 常因对特因 的区分:大多数月与月之间的抖动是不该触发行动的常因噪声,而对它做出反应
+ 任何随时间被盯着的运营指标或 KPI——一段处理时间、一个量、一个错误或合规率——都引出 SPC
+ 所回答的 那个问题:这是一次真实的变化,还是只是正常的波动?
+ 最有价值的单一想法是
+ 常因对特因
+ 的区分:大多数月与月之间的抖动是不该触发行动的常因噪声,而对它做出反应
(瞎调)会把事情弄得更糟——一种省下大量被浪费的力气和过度纠正的纪律。
- 一张控制图 把「那个数字看起来不对劲」变成一条站得住脚的规则,而 ±3σ
- 的选择就是贯穿异常检测的那个同样的
+ 一张控制图 把「那个数字看起来不对劲」变成一条站得住脚的规则,而{" "}
+ ±3σ 的选择就是贯穿异常检测的那个同样的
假警报取舍(以及
- 精确率/召回率张力)——为「狼来了」的成本对错过一次真实
- 偏移的成本去设定它。它与 流式(实时监控)和
- 时间序列(底层的信号)天然相配,是整个工具箱里
- 最真正实用的工具之一。
+ 精确率/召回率
+ 张力)——为「狼来了」的成本对错过一次真实 偏移的成本去设定它。它与
+ 流式(实时监控)和
+ 时间序列
+ (底层的信号)天然相配,是整个工具箱里 最真正实用的工具之一。
@@ -444,23 +535,24 @@ function ZhBody() {
- SPC 在一个过程随时间里把真实的变化与正常的噪声 分辨开——好让你对信号采取行动,
- 而不再追逐抖动(也不再瞎调 )。
+ SPC 在一个过程随时间里把真实的变化与正常的噪声
+ 分辨开——好让你对信号采取行动, 而不再追逐抖动(也不再瞎调 )。
- 核心的区分:常因 (固有的抖动,别动它)对特因 (可归因的,去
- 调查)。受控对失控。
+ 核心的区分:常因 (固有的抖动,别动它)对特因
+ (可归因的,去 调查)。受控对失控。
- 一张控制图 画出这个指标,配上一条中心线 + 控制限 ;一次越界
- 标志着一个特因。
+ 一张控制图 画出这个指标,配上一条中心线 + 控制限
+ ;一次越界 标志着一个特因。
- 界限设在 μ ± 3σ ——一个深思熟虑的假警报对错过偏移 的取舍
- (约 0.3% 的偶然越界概率;警报疲劳的张力)。
+ 界限设在 μ ± 3σ ——一个深思熟虑的假警报对错过偏移
+ 的取舍 (约 0.3% 的偶然越界概率;警报疲劳的张力)。
- 西部电气 / 纳尔逊规则 捕捉界限内的一连串和趋势——一个稳定的过程应当看起来
+ 西部电气 / 纳尔逊规则
+ 捕捉界限内的一连串和趋势——一个稳定的过程应当看起来
随机 ;非随机的模式就是信号。
@@ -469,8 +561,8 @@ function ZhBody() {
- 常因/特因的框架、±3σ 控制图、西部电气规则,以及用于小偏移的 CUSUM/EWMA,反映了当前的 SPC 参考
- 文献以及质量课程。
+ 常因/特因的框架、±3σ 控制图、西部电气规则,以及用于小偏移的 CUSUM/EWMA,反映了当前的 SPC
+ 参考 文献以及质量课程。
>
diff --git a/components/knowledge/content/statistics.jsx b/components/knowledge/content/statistics.jsx
index 3636382e..7a525c4a 100644
--- a/components/knowledge/content/statistics.jsx
+++ b/components/knowledge/content/statistics.jsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { KSection, Callout, Formula, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/statistics.
diff --git a/components/knowledge/content/survival-analysis.jsx b/components/knowledge/content/survival-analysis.jsx
index 58c13596..7a354f0a 100644
--- a/components/knowledge/content/survival-analysis.jsx
+++ b/components/knowledge/content/survival-analysis.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/survival-analysis.
@@ -33,13 +27,39 @@ function CensoringFigure({ caption, ariaLabel, studyEndsLabel, legendLabel }) {
role="img"
aria-label={ariaLabel}
>
-
- {studyEndsLabel}
+
+
+ {studyEndsLabel}
+
{SUBJECTS.map(([end, event], i) => {
const y = 22 + i * 24;
return (
-
+
{event ? (
) : (
@@ -48,7 +68,9 @@ function CensoringFigure({ caption, ariaLabel, studyEndsLabel, legendLabel }) {
);
})}
- {legendLabel}
+
+ {legendLabel}
+
);
@@ -279,8 +301,10 @@ function ZhBody() {
return (
<>
- 许多最重要的问题不是「这会不会发生?」而是「还要多久 它才发生?」——还要多久一个病人复发、
- 一个客户流失、一台机器故障、一个案件被解决、一个人再犯。生存分析 (或称事件时间分析)正是
+ 许多最重要的问题不是「这会不会发生?」而是「还要多久
+ 它才发生?」——还要多久一个病人复发、
+ 一个客户流失、一台机器故障、一个案件被解决、一个人再犯。生存分析
+ (或称事件时间分析)正是
为这些问题而建的统计学分支,它之所以作为一门独立的领域存在,是因为数据一个奇特而无可回避的特征:当你
的研究结束时,事件还没有发生在每个人身上——而你拿那些未完成的案例怎么办,改变着一切。
@@ -293,10 +317,12 @@ function ZhBody() {
乍一看你可能会动用你已经有的工具,而每一个都以一种有教育意义的方式失败。把它当作对「到事件的时间」
- 的一次 回归?你不能——对许多对象来说事件
- 还没发生 ,所以他们的时间是未知的。把它当作
- 分类(「事件在时刻 T 之前发生了吗?」)?你扔掉了何时 这一丰富的信息,而答案任意地
- 取决于你把 T 画在哪里。
+ 的一次 回归
+ ?你不能——对许多对象来说事件
+ 还没发生 ,所以他们的时间是未知的。把它当作
+ 分类(「事件在时刻 T
+ 之前发生了吗?」)?你扔掉了何时 这一丰富的信息,而答案任意地 取决于你把 T
+ 画在哪里。
数据有一种特殊的结构——一个时长,加上事件到底有没有发生——它需要专门打造的方法。它们所有方法的
@@ -306,7 +332,8 @@ function ZhBody() {
- 删失 是生存分析的核心。一个对象是右删失 的,是当你知道他们存活到
+ 删失 是生存分析的核心。一个对象是右删失 的,是当你知道他们
+ 存活到
某个点为止,却不知道之后发生了什么——因为在事件发生之前,研究结束了,或者他们退出了。你不知道他们
真实的事件时间;你只知道它比你观测到的更长 。
@@ -324,14 +351,18 @@ function ZhBody() {
- 生存由两个互补的函数来描述。生存函数 :
+
+ 生存由两个互补的函数来描述。生存函数 :
+
{String.raw`S(t) = \Pr(T > t)`}
- ——存活(没有发生事件)超过时刻 {String.raw`t`} 的概率。它从 1 开始,一级一级往 0 下降。
+ ——存活(没有发生事件)超过时刻 {String.raw`t`} 的概率。它从 1 开始,一级一级往
+ 0 下降。
风险函数 {String.raw`h(t)`} 取了一个不同的角度:它是在时刻{" "}
- {String.raw`t`} 事件的瞬时 率,在 你已经存活到那么远的条件下——对那些
+ {String.raw`t`} 事件的瞬时 率,在
+ 你已经存活到那么远的条件下——对那些
仍处于风险中的人此刻的风险。生存回答「有多大比例撑到了这么久?」;风险回答「对一个存活者来说,此刻
有多危险?」它们是同一个过程的两个视角,不同的方法建模其中之一。
@@ -339,8 +370,9 @@ function ZhBody() {
- Kaplan-Meier 估计量 是从数据中估计 {String.raw`S(t)`} 的主力,而它优雅地
- 处理删失。它产出那条熟悉的阶梯曲线 :生存保持平直,然后在每一次事件实际发生时下降
+ Kaplan-Meier 估计量 是从数据中估计 {String.raw`S(t)`} {" "}
+ 的主力,而它优雅地 处理删失。它产出那条熟悉的阶梯曲线
+ :生存保持平直,然后在每一次事件实际发生时下降
一级,每一级下降的大小由刚好之前还有多少人处于风险中来设定。删失的对象不会引起下降——他们只是在
自己的删失时刻离开「处于风险」的池子,所以他们正确地贡献到那个点的分母为止、不再更远。
@@ -353,44 +385,48 @@ function ZhBody() {
- 真正的问题往往是比较性的:A 组是否比 B 组存活得更久(治疗对对照、一个队列对另一个)?你为每一个画
- 一条 Kaplan-Meier 曲线,用对数秩检验 来比较它们——一个关于两条(或更多)生存曲线之间的
- 差异是否大过偶然所能解释的 假设检验。它是生存分析里比较组
- 均值的对应物,为尊重删失而建。它告诉你曲线是否不同 ,却不告诉你差多少、或在调整其他因素的
- 同时——而这正是 Cox 模型登场的地方。
+ 真正的问题往往是比较性的:A 组是否比 B
+ 组存活得更久(治疗对对照、一个队列对另一个)?你为每一个画 一条 Kaplan-Meier 曲线,用
+ 对数秩检验 来比较它们——一个关于两条(或更多)生存曲线之间的
+ 差异是否大过偶然所能解释的 假设检验
+ 。它是生存分析里比较组 均值的对应物,为尊重删失而建。它告诉你曲线是否不同
+ ,却不告诉你差多少、或在调整其他因素的 同时——而这正是 Cox 模型登场的地方。
要问「在保持其他因素不变的情况下,每个 因素如何影响生存?」你需要一个回归——而
- Cox 比例风险模型 是占主导地位的那个。它不直接建模生存曲线,而是建模风险 ,因为
- 风险更稳定、更易处理。它的形式:
+ Cox 比例风险模型 是占主导地位的那个。它不直接建模生存曲线,而是建模
+ 风险 ,因为 风险更稳定、更易处理。它的形式:
{String.raw`h(t \mid \mathbf{x}) = h_0(t)\, \exp(\beta_1 x_1 + \cdots + \beta_p x_p)`}
- 美妙之处在于它是半参数 的:基线风险 {String.raw`h_0(t)`} ——风险总体上如何随
- 时间变化——被留作未指定 ,所以你对生存曲线的形状不作任何假设。你只估计{" "}
- {String.raw`\beta`} 系数,即每个协变量的效应。对一个系数取指数,得到一个
- 风险比 :{String.raw`e^{\beta} = 2`} 意味着那个因素在任何时刻把瞬时风险
- 加倍 ;低于 1 则是保护性的。那个单一、可解释的数字——「这个因素把风险乘以 X」——正是为什么
- Cox 模型在医学、可靠性和社会科学里无处不在。
+ 美妙之处在于它是半参数 的:基线风险 {String.raw`h_0(t)`}
+ ——风险总体上如何随 时间变化——被留作未指定
+ ,所以你对生存曲线的形状不作任何假设。你只估计 {String.raw`\beta`} {" "}
+ 系数,即每个协变量的效应。对一个系数取指数,得到一个
+ 风险比 :{String.raw`e^{\beta} = 2`} {" "}
+ 意味着那个因素在任何时刻把瞬时风险
+ 加倍 ;低于 1 则是保护性的。那个单一、可解释的数字——「这个因素把风险乘以
+ X」——正是为什么 Cox 模型在医学、可靠性和社会科学里无处不在。
- Cox 模型用一个关键的假设买来它的灵活性,这个假设藏在名字里:比例风险 。它假设一个
- 协变量的效应是对风险在所有 时刻的一个恒定的乘数 ——两组之间的风险比不随时间流逝而
- 改变。
+ Cox 模型用一个关键的假设买来它的灵活性,这个假设藏在名字里:比例风险
+ 。它假设一个 协变量的效应是对风险在所有 时刻的一个恒定的乘数
+ ——两组之间的风险比不随时间流逝而 改变。
那个假设往往是合理的,但并不总是——而当它被违反时,模型会误导人。如果一种治疗早期有帮助、但它的
益处逐渐消退(曲线交叉,或差距随时间缩小),一个单一、恒定的风险比就是一个虚构,它把真实的、随
- 时间变化的故事平均掉了。所以务必检查它 (用残差图或一个正式的检验),在它失败时
+ 时间变化的故事平均掉了。所以务必检查它
+ (用残差图或一个正式的检验),在它失败时
动用扩展(时变系数、分层)。与每个模型一样,假设是信任所栖身——或不栖身——之处。
@@ -400,15 +436,17 @@ function ZhBody() {
出人意料地多的分析问题,其实是乔装的事件时间问题:一个案件还要多久被解决、一个问题的复发时间、
- 一个人在一个项目里待多久才退出。生存分析给我的最有价值的东西,是围绕删失 的
- 纪律——认识到那些还没 完成的案例携带真实的信息,而把它们扔掉(那个诱人的捷径)会系统性地
+ 一个人在一个项目里待多久才退出。生存分析给我的最有价值的东西,是围绕
+ 删失 的 纪律——认识到那些还没
+ 完成的案例携带真实的信息,而把它们扔掉(那个诱人的捷径)会系统性地
把答案偏向任何很快就完成的东西。
- Kaplan-Meier 是展示「随时间有多大比例仍然留存」的诚实方式,对数秩检验
- 恰当地比较两组的时间线,而 Cox 模型 在调整混杂因素的同时,给出一个可解释
- 的风险比 ——「这个因素把风险乘以 X」——这与 因果
- 推断的心态天然相配。懂得比例风险 假设,正是让那个风险比保持诚实、而非一个
+ Kaplan-Meier 是展示「随时间有多大比例仍然留存」的诚实方式,
+ 对数秩检验 恰当地比较两组的时间线,而 Cox 模型
+ 在调整混杂因素的同时,给出一个可解释 的风险比 ——「这个因素把风险乘以 X」——这与
+ 因果 推断的心态天然相配。懂得
+ 比例风险 假设,正是让那个风险比保持诚实、而非一个
变化中故事的便利平均的东西。
@@ -418,15 +456,17 @@ function ZhBody() {
- 生存分析回答「还要多久事件才发生?」 ——普通回归/分类会失败,因为事件还没发生在
- 每个人身上。
+ 生存分析回答「还要多久事件才发生?」
+ ——普通回归/分类会失败,因为事件还没发生在 每个人身上。
- 删失 是关键的想法:一个右删失的对象在某个时刻之前无事件(一个真实的下界)。别把
+ 删失
+ 是关键的想法:一个右删失的对象在某个时刻之前无事件(一个真实的下界)。别把
它们扔掉,也别把删失当作事件——两者都让结果有偏。
- 两个视角:生存 {String.raw`S(t)=\Pr(T>t)`} (撑过 t 的比例)和
+ 两个视角:生存 {String.raw`S(t)=\Pr(T>t)`} (撑过 t
+ 的比例)和
风险 {String.raw`h(t)`} (在迄今存活的条件下的瞬时风险)。
@@ -439,14 +479,14 @@ function ZhBody() {
{String.raw`e^\beta`} = 风险比 (「把风险乘以 X」)。
- 检查比例风险假设 ——一个随时间恒定的风险比;当它被违反时(曲线交叉),那个单一的
- 数字会误导人。
+ 检查比例风险假设
+ ——一个随时间恒定的风险比;当它被违反时(曲线交叉),那个单一的 数字会误导人。
- 删失的框架、Kaplan-Meier/对数秩的配对,以及带有比例风险告诫的 Cox 模型,反映了当前的生存分析参考
- 文献以及统计学课程。
+ 删失的框架、Kaplan-Meier/对数秩的配对,以及带有比例风险告诫的 Cox
+ 模型,反映了当前的生存分析参考 文献以及统计学课程。
>
diff --git a/components/knowledge/content/time-series-analysis.jsx b/components/knowledge/content/time-series-analysis.jsx
index bb842c0a..dd33ea3b 100644
--- a/components/knowledge/content/time-series-analysis.jsx
+++ b/components/knowledge/content/time-series-analysis.jsx
@@ -1,12 +1,6 @@
import Link from "next/link";
-import {
- KSection,
- Callout,
- Formula,
- Figure,
- TeX,
- Term,
-} from "@/components/knowledge/KnowledgeLayout";
+import { KSection, Callout, Figure, Term } from "@/components/knowledge/KnowledgeLayout";
+import { Formula, TeX } from "@/components/knowledge/KatexFormula";
/**
* Per-locale content for /knowledge/time-series-analysis.
diff --git a/lib/fonts.js b/lib/fonts.js
index f78df0ef..c786c860 100644
--- a/lib/fonts.js
+++ b/lib/fonts.js
@@ -4,12 +4,17 @@ import localFont from "next/font/local";
// Pixel display — hero h1 brand name
// Variable font: single file covers all weights (100–900) and axes. Served as
// WOFF2 (lossless brotli-compressed container) — 264KB TTF -> 36KB, identical
-// glyphs and axes, loaded on every page.
+// glyphs and axes.
+// preload: false — only the homepage hero (HeroSection.jsx) needs this above
+// the fold; everywhere else it's a 0.025-opacity Footer watermark and a
+// once-per-session boot animation. display:swap already avoids FOIT, so
+// lazy-loading it saves ~35KB of render-priority preload on every other page.
export const bitcount = localFont({
src: "./Bitcount_Prop_Double/BitcountPropDouble-VariableFont.woff2",
weight: "100 900",
style: "normal",
display: "swap",
+ preload: false,
variable: "--font-bitcount",
});
diff --git a/lib/posts.js b/lib/posts.js
index 6423a8a5..61471b56 100644
--- a/lib/posts.js
+++ b/lib/posts.js
@@ -170,25 +170,18 @@ function remarkFlowPlugin() {
};
}
-/** Parse a single .md file and return its metadata + HTML body. */
-async function parsePost(slug) {
+/** Read + frontmatter-parse a single .md file. Cheap — no remark pipeline. */
+function readFrontmatter(slug) {
const filePath = path.join(POSTS_DIR, `${slug}.md`);
const raw = fs.readFileSync(filePath, "utf8");
- const { data, content } = matter(raw);
-
- // remark-html & remark-gfm are ESM-only — use dynamic import
- const { default: remarkHtml } = await import("remark-html");
- const { default: remarkGfm } = await import("remark-gfm");
- const processor = remark()
- .use(remarkGfm)
- .use(remarkFlowPlugin)
- .use(remarkHtml, { sanitize: false });
- const result = processor.processSync(content);
+ return matter(raw); // { data, content }
+}
+/** Build the metadata object shared by parseMeta and parsePost. */
+function metaFromFrontmatter(slug, data, content) {
// Reading time from the markdown source (~200 wpm), min 1 minute.
const words = content.trim().split(/\s+/).filter(Boolean).length;
const readingTime = Math.max(1, Math.round(words / 200));
-
return {
slug,
title: data.title || slug,
@@ -197,21 +190,43 @@ async function parsePost(slug) {
description: data.description || "",
ogImage: data.ogImage || null,
readingTime,
- contentHtml: String(result),
};
}
+/**
+ * Metadata only — no remark pipeline. Used by getAllPosts(), which needs
+ * every post's frontmatter but never renders their bodies. Keeping this
+ * separate from parsePost() turns /blog/[slug]'s per-page build cost from
+ * O(N) full-content parses (one per other post, just to get its title) into
+ * O(1): only the current slug goes through the expensive remark pipeline.
+ */
+function parseMeta(slug) {
+ const { data, content } = readFrontmatter(slug);
+ return metaFromFrontmatter(slug, data, content);
+}
+
+/** Parse a single .md file and return its metadata + HTML body. */
+async function parsePost(slug) {
+ const { data, content } = readFrontmatter(slug);
+ const meta = metaFromFrontmatter(slug, data, content);
+
+ // remark-html & remark-gfm are ESM-only — use dynamic import
+ const { default: remarkHtml } = await import("remark-html");
+ const { default: remarkGfm } = await import("remark-gfm");
+ const processor = remark()
+ .use(remarkGfm)
+ .use(remarkFlowPlugin)
+ .use(remarkHtml, { sanitize: false });
+ const result = processor.processSync(content);
+
+ return { ...meta, contentHtml: String(result) };
+}
+
/** Return all posts sorted by date descending. */
export async function getAllPosts() {
if (!fs.existsSync(POSTS_DIR)) return [];
const files = fs.readdirSync(POSTS_DIR).filter((f) => f.endsWith(".md"));
- const posts = await Promise.all(
- files.map(async (f) => {
- const slug = f.replace(/\.md$/, "");
- const { title, date, tags, description, ogImage, readingTime } = await parsePost(slug);
- return { slug, title, date, tags, description, ogImage, readingTime };
- })
- );
+ const posts = files.map((f) => parseMeta(f.replace(/\.md$/, "")));
return posts.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
}
diff --git a/middleware.js b/middleware.js
index e2ae8f9f..21fe41f4 100644
--- a/middleware.js
+++ b/middleware.js
@@ -58,5 +58,12 @@ export function middleware(request) {
export const config = {
// The "/((?!...).*)" pattern does NOT match the bare root "/", so the curl
// rewrite never ran for `curl rin.contact`. List "/" explicitly as well.
- matcher: ["/", "/((?!api|_next/static|_next/image|favicon.ico).*)"],
+ // Also exclude static file extensions (fonts, images, css, etc. under
+ // /public) — none of them need the CLI-detection or .well-known logic
+ // below, so skipping the middleware invocation for them removes an Edge
+ // Function hop from every font/image/stylesheet request on every page.
+ matcher: [
+ "/",
+ "/((?!api|_next/static|_next/image|.*\\.(?:css|html|ico|jpe?g|js|json|pdf|png|svg|txt|webmanifest|woff2?|xml)$).*)",
+ ],
};
diff --git a/next.config.js b/next.config.js
index 07e9f81e..063427ab 100644
--- a/next.config.js
+++ b/next.config.js
@@ -11,14 +11,9 @@ const nextConfig = {
images: {
formats: ["image/avif", "image/webp"],
- remotePatterns: [
- { protocol: "https", hostname: "upload.wikimedia.org" },
- { protocol: "https", hostname: "yt3.googleusercontent.com" },
- { protocol: "https", hostname: "encrypted-tbn0.gstatic.com" },
- { protocol: "https", hostname: "media.licdn.com" },
- { protocol: "https", hostname: "s3-symbol-logo.tradingview.com" },
- { protocol: "https", hostname: "companieslogo.com" },
- ],
+ // No remotePatterns: all logos/images are self-hosted under /public/images
+ // (see components/sections/TimelineSection.jsx). Nothing routes through
+ // next/image from a remote host today.
},
webpack: (config, { isServer }) => {
diff --git a/pages/_app.jsx b/pages/_app.jsx
index 83a9f96d..39828266 100644
--- a/pages/_app.jsx
+++ b/pages/_app.jsx
@@ -7,7 +7,10 @@ import { I18nProvider } from "@/contexts/I18nContext";
import { bitcount, dmSans, playfair } from "@/lib/fonts";
import "../public/styles/globals.css";
-import "katex/dist/katex.min.css";
+// -swap variant: font-display:swap instead of the default's font-display:block,
+// so the ~39 /knowledge pages that render / don't risk a FOIT
+// while KaTeX's math fonts load. Same file size either way.
+import "katex/dist/katex-swap.min.css";
const Analytics = dynamic(
() => import("@vercel/analytics/react").then((m) => ({ default: m.Analytics })),
diff --git a/pages/_document.jsx b/pages/_document.jsx
index 704e5158..54812979 100644
--- a/pages/_document.jsx
+++ b/pages/_document.jsx
@@ -209,370 +209,6 @@ const PROFILE_PAGE_SCHEMA = {
},
};
-const PROJECTS_SCHEMA = {
- "@context": "https://schema.org",
- "@type": "ItemList",
- "@id": "https://rin.contact/#projects",
- name: "Projects by Rin Huang (Sunchuangyu Huang)",
- description: "Software, data science, and analytics projects by Rin Huang",
- author: { "@id": "https://rin.contact/#person" },
- itemListElement: [
- {
- "@type": "ListItem",
- position: 1,
- item: {
- "@type": "SoftwareApplication",
- name: "Mapiva",
- description:
- "Co-founded a mobile social connection app — full product ownership from architecture through implementation as Dev Lead.",
- applicationCategory: "SocialNetworkingApplication",
- operatingSystem: "iOS, Android",
- author: { "@id": "https://rin.contact/#person" },
- programmingLanguage: ["React Native", "Expo"],
- },
- },
- {
- "@type": "ListItem",
- position: 2,
- item: {
- "@type": "SoftwareSourceCode",
- name: "SA Address Generator",
- description:
- "Internal tool to generate validated South Australian addresses based on SEIFA indices and remoteness classifications, verified via Mapbox API.",
- codeRepository: "https://github.com/rNLKJA/SA-Mock-Address-Generator",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 3,
- item: {
- "@type": "SoftwareSourceCode",
- name: "US Political Data Collection System",
- description:
- "Scraped ~180 presidential debate transcripts and ~25,000 campaign documents from the UC Santa Barbara American Presidency Project with multi-threaded processing.",
- codeRepository: "https://github.com/rNLKJA/Political-Data-Collection-System",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 4,
- item: {
- "@type": "SoftwareApplication",
- name: "CBS Intelligence Analytics",
- description:
- "First intelligence analytics capability within the CBS Prevention Team — integrating ABS, SA Health, ACCC, and DataSA data into unified dashboards and GIS maps used by the Minister's Office.",
- applicationCategory: "BusinessApplication",
- author: { "@id": "https://rin.contact/#person" },
- programmingLanguage: ["Python", "Power BI"],
- },
- },
- {
- "@type": "ListItem",
- position: 5,
- item: {
- "@type": "SoftwareApplication",
- name: "MoodQ",
- description:
- "Clinician-facing and patient-facing mental health mobile app for the University of Melbourne Psychiatry research group. Migrated from Uniapp to Expo React Native, reducing hosting costs ~$500/month.",
- applicationCategory: "HealthApplication",
- operatingSystem: "iOS, Android",
- author: { "@id": "https://rin.contact/#person" },
- programmingLanguage: ["Expo", "React Native", "Node.js"],
- },
- },
- {
- "@type": "ListItem",
- position: 6,
- item: {
- "@type": "SoftwareApplication",
- name: "SAPOL Intelligence Dashboards",
- description:
- "Operational intelligence and crime analytics dashboards supporting frontline policing, resource allocation, and command-level decision-making across South Australia.",
- applicationCategory: "BusinessApplication",
- author: { "@id": "https://rin.contact/#person" },
- programmingLanguage: ["Python", "Power BI", "SQL"],
- },
- },
- {
- "@type": "ListItem",
- position: 7,
- item: {
- "@type": "SoftwareSourceCode",
- name: "Flow Cytometry Analysis Pipeline",
- description:
- "Automated flow cytometry data analysis using cloud and HPC, with test infrastructure for reproducibility and open-source contributions to celseq2.",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 8,
- item: {
- "@type": "SoftwareSourceCode",
- name: "Climate Fact-Checker",
- description:
- "Two-stage automated fact-checking for climate change claims — TF-IDF evidence retrieval and Transformer-based classification, outperforming LSTM baselines.",
- codeRepository: "https://github.com/rNLKJA",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 9,
- item: {
- "@type": "SoftwareSourceCode",
- name: "Australia Social Media Analytics on the Cloud",
- description:
- "Harvested and analysed Twitter and Mastodon data alongside ABS SUDO spatial data to produce a Social Sense Dashboard across Australian regions.",
- codeRepository: "https://github.com/rNLKJA/Australia-Social-Media-Analytics-on-the-Cloud",
- programmingLanguage: ["Python", "CouchDB"],
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 10,
- item: {
- "@type": "SoftwareSourceCode",
- name: "Twitter HPC Analysis",
- description:
- "Processed a large-scale Twitter dataset on SPARTAN HPC using MPI and Python, identifying tweet distribution across Australian cities.",
- codeRepository: "https://github.com/rNLKJA/Twitter-Data-Analysis-with-HPC",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 11,
- item: {
- "@type": "SoftwareSourceCode",
- name: "Cachex AI Game Agent",
- description:
- "AI agents for Cachex — a two-player connection game — using heuristic A* search and competitive game theory with strategic sabotage logic.",
- codeRepository: "https://github.com/rNLKJA/Cachex-AI",
- programmingLanguage: "Python",
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- {
- "@type": "ListItem",
- position: 12,
- item: {
- "@type": "SoftwareSourceCode",
- name: "PCRM — Personal Customer Relationship Management",
- description:
- "Full-stack CRM system with React frontend, Express REST API, and MongoDB backend, built as the COMP30022 IT Project at the University of Melbourne.",
- codeRepository: "https://github.com/rNLKJA/Personal-Customer-Relation-Management-PCRM",
- programmingLanguage: ["Node.js", "React.js", "MongoDB"],
- author: { "@id": "https://rin.contact/#person" },
- },
- },
- ],
-};
-
-const CREDENTIALS_SCHEMA = {
- "@context": "https://schema.org",
- "@graph": [
- {
- "@type": "EducationalOccupationalCredential",
- name: "VETASSESS — Statistician (ANZSCO 224113)",
- credentialCategory: "ProfessionalAssessment",
- recognizedBy: { "@type": "Organization", name: "VETASSESS" },
- dateCreated: "2026-02-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "IELTS General Training — Band 8",
- credentialCategory: "LanguageAssessment",
- recognizedBy: { "@type": "Organization", name: "IELTS Official" },
- dateCreated: "2026-02-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Credentialed Community Language — Mandarin",
- credentialCategory: "LanguageCredential",
- recognizedBy: { "@type": "Organization", name: "NAATI" },
- dateCreated: "2025-12-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Microsoft Certified: Azure Fundamentals (AZ-900)",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Microsoft", url: "https://microsoft.com" },
- dateCreated: "2024-07-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Neo4j Certified Professional",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Neo4j", url: "https://neo4j.com" },
- dateCreated: "2025-08-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Neo4j Graph Data Science Certification",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Neo4j", url: "https://neo4j.com" },
- dateCreated: "2025-08-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google UX Design Specialisation",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2025-12-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google Business Intelligence Specialisation",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2025-12-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google Project Management Specialisation",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2025-12-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google IT Automation with Python",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2022-05-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google Data Analytics Specialisation",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2021-06-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Open-Source Intelligence (OSINT) Fundamentals",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "TCM Security" },
- dateCreated: "2025-10-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Google Analytics Individual Qualification (GAIQ)",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
- dateCreated: "2024-05-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Advanced SQL for Data Scientists",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "LinkedIn Learning" },
- dateCreated: "2024-01-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Atlassian Agile Project Management Professional Certificate",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "Atlassian", url: "https://atlassian.com" },
- dateCreated: "2024-04-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Career Essentials in GitHub Professional Certificate",
- credentialCategory: "certification",
- recognizedBy: { "@type": "Organization", name: "GitHub", url: "https://github.com" },
- dateCreated: "2024-01-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Melbourne Plus: Innovation",
- credentialCategory: "microcredential",
- recognizedBy: {
- "@type": "CollegeOrUniversity",
- name: "University of Melbourne",
- url: "https://www.unimelb.edu.au",
- },
- dateCreated: "2024-05-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- {
- "@type": "EducationalOccupationalCredential",
- name: "Melbourne Plus: People Leadership",
- credentialCategory: "microcredential",
- recognizedBy: {
- "@type": "CollegeOrUniversity",
- name: "University of Melbourne",
- url: "https://www.unimelb.edu.au",
- },
- dateCreated: "2024-10-01",
- holder: { "@id": "https://rin.contact/#person" },
- },
- ],
-};
-
-const BREADCRUMB_SCHEMA = {
- "@context": "https://schema.org",
- "@type": "BreadcrumbList",
- "@id": "https://rin.contact/#breadcrumb",
- itemListElement: [
- {
- "@type": "ListItem",
- position: 1,
- name: "Rin Huang",
- item: "https://rin.contact/",
- },
- {
- "@type": "ListItem",
- position: 2,
- name: "Career",
- item: "https://rin.contact/#timeline",
- },
- {
- "@type": "ListItem",
- position: 3,
- name: "Projects",
- item: "https://rin.contact/#projects",
- },
- {
- "@type": "ListItem",
- position: 4,
- name: "Skills",
- item: "https://rin.contact/#skills",
- },
- {
- "@type": "ListItem",
- position: 5,
- name: "Contact",
- item: "https://rin.contact/#contact",
- },
- ],
-};
-
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
@@ -689,11 +325,11 @@ class MyDocument extends Document {
href="/opensearch.xml"
/>
- {/* ── DNS prefetch — third-party image & API domains ── */}
-
-
-
-
+ {/* ── DNS prefetch ── */}
+ {/* The logo hosts this used to prefetch (LinkedIn, YouTube, Google's
+ thumbnail cache, TradingView) are no longer contacted — logos are
+ self-hosted under /public/images now. api.emailjs.com is the only
+ third-party host actually contacted client-side (contact form). */}
{/*
@@ -714,18 +350,9 @@ class MyDocument extends Document {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(PROFILE_PAGE_SCHEMA) }}
/>
-
-
-
+ {/* BREADCRUMB_SCHEMA / PROJECTS_SCHEMA / CREDENTIALS_SCHEMA moved to
+ pages/index.jsx — they describe homepage-only content and were
+ previously shipped in every page's . */}
{/*
diff --git a/pages/index.jsx b/pages/index.jsx
index cbce3e42..742f3053 100644
--- a/pages/index.jsx
+++ b/pages/index.jsx
@@ -4,6 +4,373 @@ import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import SeoHead from "@/components/seo/SeoHead";
+// Moved from pages/_document.jsx — these describe homepage-only content
+// (career timeline anchors, project list, credentials) and were previously
+// shipped in every page's even though only the homepage uses them.
+const BREADCRUMB_SCHEMA = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ "@id": "https://rin.contact/#breadcrumb",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Rin Huang",
+ item: "https://rin.contact/",
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Career",
+ item: "https://rin.contact/#timeline",
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: "Projects",
+ item: "https://rin.contact/#projects",
+ },
+ {
+ "@type": "ListItem",
+ position: 4,
+ name: "Skills",
+ item: "https://rin.contact/#skills",
+ },
+ {
+ "@type": "ListItem",
+ position: 5,
+ name: "Contact",
+ item: "https://rin.contact/#contact",
+ },
+ ],
+};
+
+const PROJECTS_SCHEMA = {
+ "@context": "https://schema.org",
+ "@type": "ItemList",
+ "@id": "https://rin.contact/#projects",
+ name: "Projects by Rin Huang (Sunchuangyu Huang)",
+ description: "Software, data science, and analytics projects by Rin Huang",
+ author: { "@id": "https://rin.contact/#person" },
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ item: {
+ "@type": "SoftwareApplication",
+ name: "Mapiva",
+ description:
+ "Co-founded a mobile social connection app — full product ownership from architecture through implementation as Dev Lead.",
+ applicationCategory: "SocialNetworkingApplication",
+ operatingSystem: "iOS, Android",
+ author: { "@id": "https://rin.contact/#person" },
+ programmingLanguage: ["React Native", "Expo"],
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "SA Address Generator",
+ description:
+ "Internal tool to generate validated South Australian addresses based on SEIFA indices and remoteness classifications, verified via Mapbox API.",
+ codeRepository: "https://github.com/rNLKJA/SA-Mock-Address-Generator",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "US Political Data Collection System",
+ description:
+ "Scraped ~180 presidential debate transcripts and ~25,000 campaign documents from the UC Santa Barbara American Presidency Project with multi-threaded processing.",
+ codeRepository: "https://github.com/rNLKJA/Political-Data-Collection-System",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 4,
+ item: {
+ "@type": "SoftwareApplication",
+ name: "CBS Intelligence Analytics",
+ description:
+ "First intelligence analytics capability within the CBS Prevention Team — integrating ABS, SA Health, ACCC, and DataSA data into unified dashboards and GIS maps used by the Minister's Office.",
+ applicationCategory: "BusinessApplication",
+ author: { "@id": "https://rin.contact/#person" },
+ programmingLanguage: ["Python", "Power BI"],
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 5,
+ item: {
+ "@type": "SoftwareApplication",
+ name: "MoodQ",
+ description:
+ "Clinician-facing and patient-facing mental health mobile app for the University of Melbourne Psychiatry research group. Migrated from Uniapp to Expo React Native, reducing hosting costs ~$500/month.",
+ applicationCategory: "HealthApplication",
+ operatingSystem: "iOS, Android",
+ author: { "@id": "https://rin.contact/#person" },
+ programmingLanguage: ["Expo", "React Native", "Node.js"],
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 6,
+ item: {
+ "@type": "SoftwareApplication",
+ name: "SAPOL Intelligence Dashboards",
+ description:
+ "Operational intelligence and crime analytics dashboards supporting frontline policing, resource allocation, and command-level decision-making across South Australia.",
+ applicationCategory: "BusinessApplication",
+ author: { "@id": "https://rin.contact/#person" },
+ programmingLanguage: ["Python", "Power BI", "SQL"],
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 7,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "Flow Cytometry Analysis Pipeline",
+ description:
+ "Automated flow cytometry data analysis using cloud and HPC, with test infrastructure for reproducibility and open-source contributions to celseq2.",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 8,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "Climate Fact-Checker",
+ description:
+ "Two-stage automated fact-checking for climate change claims — TF-IDF evidence retrieval and Transformer-based classification, outperforming LSTM baselines.",
+ codeRepository: "https://github.com/rNLKJA",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 9,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "Australia Social Media Analytics on the Cloud",
+ description:
+ "Harvested and analysed Twitter and Mastodon data alongside ABS SUDO spatial data to produce a Social Sense Dashboard across Australian regions.",
+ codeRepository: "https://github.com/rNLKJA/Australia-Social-Media-Analytics-on-the-Cloud",
+ programmingLanguage: ["Python", "CouchDB"],
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 10,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "Twitter HPC Analysis",
+ description:
+ "Processed a large-scale Twitter dataset on SPARTAN HPC using MPI and Python, identifying tweet distribution across Australian cities.",
+ codeRepository: "https://github.com/rNLKJA/Twitter-Data-Analysis-with-HPC",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 11,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "Cachex AI Game Agent",
+ description:
+ "AI agents for Cachex — a two-player connection game — using heuristic A* search and competitive game theory with strategic sabotage logic.",
+ codeRepository: "https://github.com/rNLKJA/Cachex-AI",
+ programmingLanguage: "Python",
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ {
+ "@type": "ListItem",
+ position: 12,
+ item: {
+ "@type": "SoftwareSourceCode",
+ name: "PCRM — Personal Customer Relationship Management",
+ description:
+ "Full-stack CRM system with React frontend, Express REST API, and MongoDB backend, built as the COMP30022 IT Project at the University of Melbourne.",
+ codeRepository: "https://github.com/rNLKJA/Personal-Customer-Relation-Management-PCRM",
+ programmingLanguage: ["Node.js", "React.js", "MongoDB"],
+ author: { "@id": "https://rin.contact/#person" },
+ },
+ },
+ ],
+};
+
+const CREDENTIALS_SCHEMA = {
+ "@context": "https://schema.org",
+ "@graph": [
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "VETASSESS — Statistician (ANZSCO 224113)",
+ credentialCategory: "ProfessionalAssessment",
+ recognizedBy: { "@type": "Organization", name: "VETASSESS" },
+ dateCreated: "2026-02-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "IELTS General Training — Band 8",
+ credentialCategory: "LanguageAssessment",
+ recognizedBy: { "@type": "Organization", name: "IELTS Official" },
+ dateCreated: "2026-02-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Credentialed Community Language — Mandarin",
+ credentialCategory: "LanguageCredential",
+ recognizedBy: { "@type": "Organization", name: "NAATI" },
+ dateCreated: "2025-12-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Microsoft Certified: Azure Fundamentals (AZ-900)",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Microsoft", url: "https://microsoft.com" },
+ dateCreated: "2024-07-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Neo4j Certified Professional",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Neo4j", url: "https://neo4j.com" },
+ dateCreated: "2025-08-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Neo4j Graph Data Science Certification",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Neo4j", url: "https://neo4j.com" },
+ dateCreated: "2025-08-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google UX Design Specialisation",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2025-12-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google Business Intelligence Specialisation",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2025-12-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google Project Management Specialisation",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2025-12-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google IT Automation with Python",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2022-05-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google Data Analytics Specialisation",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2021-06-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Open-Source Intelligence (OSINT) Fundamentals",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "TCM Security" },
+ dateCreated: "2025-10-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Google Analytics Individual Qualification (GAIQ)",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Google", url: "https://google.com" },
+ dateCreated: "2024-05-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Advanced SQL for Data Scientists",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "LinkedIn Learning" },
+ dateCreated: "2024-01-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Atlassian Agile Project Management Professional Certificate",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "Atlassian", url: "https://atlassian.com" },
+ dateCreated: "2024-04-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Career Essentials in GitHub Professional Certificate",
+ credentialCategory: "certification",
+ recognizedBy: { "@type": "Organization", name: "GitHub", url: "https://github.com" },
+ dateCreated: "2024-01-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Melbourne Plus: Innovation",
+ credentialCategory: "microcredential",
+ recognizedBy: {
+ "@type": "CollegeOrUniversity",
+ name: "University of Melbourne",
+ url: "https://www.unimelb.edu.au",
+ },
+ dateCreated: "2024-05-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ {
+ "@type": "EducationalOccupationalCredential",
+ name: "Melbourne Plus: People Leadership",
+ credentialCategory: "microcredential",
+ recognizedBy: {
+ "@type": "CollegeOrUniversity",
+ name: "University of Melbourne",
+ url: "https://www.unimelb.edu.au",
+ },
+ dateCreated: "2024-10-01",
+ holder: { "@id": "https://rin.contact/#person" },
+ },
+ ],
+};
+
// ── Background art helpers (Nothing OS + Wisr design language) ───────────────
// Earth: thin cross/plus mark — precision & structure
@@ -319,6 +686,20 @@ export default function Home() {
}),
}}
/>
+
+ {/* ── Breadcrumb / projects / credentials structured data — homepage-only ── */}
+
+
+
Cf@B0Uw59bTR-mv%F_nK?fwXQvpn(9i}7!())0AQ;q%Rd1C2;x@=fQEv2383Z-
zM!bOBpD4)!Rl`)<06+(*$jj(>ne1kx`s$ut-b+7UNdJfi>3;hrc$EZowaiDOzRJ$N
zva+&4*Zxeep;6s#s&Uxxd~tOVlY8JD2uu$h7#=cyetSQ1p5^XN2nG?*tvzfXf3`1q
z9&>)~<9y=VK63J!X|B=nun&?35SoG2VCcLcmd^hE=P3wr)6+ni4I``nZ9TuQ1p_1h&n5of
z>-B>Hn1G*I{3S{H|7kC9pws<-+1}r4p|o_JU??q_RlCCfW9$F-eGyxv`v151|H)5r
z#s=Kz=zQWczC@?;qF#meP-|L1@(Upb|nEK0j_R{%z(dVMv)NzF^Yuxh9~`>w?Osq
z55bp?FV~-ga?2HAaXtUtYZKoLWVTteA;9-T>d#YcA2g)cJ!)k0f*2O@4wxc$RKuO!WZL9_-`H4XfsX`3k0&Q
zM`E9*GW+(py&Ncs3XGh#xV3rr+f_$06S2{Si3SOQtlLi-JDv5obh93>W?v`x`}BjT
z`~6y`>V`wdj<&Oh8;T=QOQ*9wdRhL@N79mlFu3txQrv&h#;h36Ru52?KG~25YTOP`
zHW^^fvs|CUm`M&*fZW^poVUPoBzX9{Bs!pem~*$*79GoI;x?=}QctBZfWk!f9uuf4
ziEp7I0bYHPUrqYw&N&PScg~@3YB%`n&Rf}#HnRl-qjTB)Z|GM}%g#o*T)v(vrjT`#
zPpkYNr(YK*5ib{AIunF9k)HZ&dXBWIpaayr#*ldWuFa}*aYlwNk8s89Y`I>~Pyb26>nBe-USt9)T1w`Cjq=
z0@9WZp=a$ie9w);R~~6DHex-IGMq{coFZmv&-qAXI-C_gMQooCBa%k!)s=iX|J3^DHv
z!PXtavyYWY)};r}G$K#%mP7$v@w6+NWY}kqc^@NPUe0Rj(or}<)ty+nSAbf#T62B=
z#4%BRD|Gq*K#MSq*IYy`TY`i!oD@2BHIsI{$N!((qeJw88T9x|qW>JWk=&kK=
z*Cznjl9#~kH*VDX_t(K}JeimZQL-A}3V3;9@`A4|jXxR6?-ZoLuOT`@I*bVsdGAZe
ztRJPw);eI(ieZgyun9g6F1n&q;rEz;IvQuAeW#?Lq~|24t#cpMUjhsg@iDB?x}XWf
zY_%~hSKd}3J<3dp5&dhUg2WkR3^Z|F2cEs)9O(!bMVy_V6f1hSa4YNv70R<3mMph%
zoo7eUkMO^KUM7M)$QJbF{V$!pUEdS}Y$*QQOB$3Fy}n^_=^3*RYwIGWv}ASnQsr1`
zptbc-^WhCA+-+Mn%jN6h0FDuvum|uZg}ZYl{Bhs-r|40g>5v|roS@pNLEeBxs;q!-
z554a9$jJnAA)%L-B~?{2+qp9Ot*^L`k5BS0ef4YHkQLuhg*Mo8#%CwJ+1+)RoSuqQ
z2X3!Z&9)P*(Jq47$Tfos2UF;IL`Yx1ey!h7Mm3aRLqtS`VGh+6qQ@%9z(vFMH(ra|_T0<{SHGs}$d`kZViB(ZS;GHZ~VM3}WS{MSz<
zvwlZ-U*PiiZ-mcn#Z$J=+~gi?n%I<@GOV7G+FJ^?Hh^MH
z30W=@IdgOR<@174J4RnA+M5+#J7FDq5gtAHA1|?m2Drb-tY4`nJjOc?H&eitxSN^q
z`VslubqG747ybD=uv{(PX}DRM7!g0qd&;JPUA%ZA@}d{A^xB{={=8glWie?^-NhW5
zA=@$;ne^m@%`B1i0Y>b(Nr`Hn=^?)Vla3mdVaUKcy8i&u5(1sZ1XgPuI(g+&rIZs-
z^iO(f4vHVwA0mC320WL}JbmxR0`k6`dF^b6*UZiE+0M^IRNUOfnn9fcT4KZcysfOQ
zp~o*^<_zh|bmLSqCiizl21rM2xh1fCATq?=;vn1u)i|7L7=-{o!orWAmhLeFL7*y5
zaJ^PokfYr6OWQoW)yI#~Ai;2MOms<_Pq&ZC`j<=HcR8&N7duRLEe!8D-Y%
zG4fTn3DTp-3+VIKEe>L|rHt{pU*1M9=PG}PacHFm|3U_uy3ADpTj}S)>#zrx%7-%l
z;?f)*7F|)bLSzEo;Ii_cd2Af~{M`|s@((X{YNBXyLPuw2*kXX_
zsHl)txWg>}DbGF+KbV4I=hGcCGjlu>I^{QKyb)3&VvF@C;#JrCJEYP1$&j!x6f8FG
zPkm=UwLVl4to6q!85odbP4sS!74UlQOSoo1JL8f$2bWLS*|tq%yetKNltiYR4)-if
zuH&icGWUJ;5OS(Bs2VourC_g@*y1yIZ2kOq^wMqmxze{o(a$_1<(q
z3Hh^#cnz*OMK)GUR9bI1FRdUGQc~Qz7#DT21Z9ZNevSC47nPKOjH?VtP&Y8no3_OO
zbgJx1>oX}iS&4~c7!Bh7GWZlt$XG1=!t6hvdWD6^(M!--!z2r?tpogMUz|3trHJC^w`9wk5Lf+X
zGj^$~ElILhj21O$t9)N>Q*$X@^;1BD!t`aU#K+q1(_!roty3^al#_eP}3=$3eyhtXX5BU!^?*5
zGif3mbo|`XQgj0Y1GKV@XeE*|FQ{&m~%e
z^z@QNO>}{G@~&F!QUROx&rUMM`JAULwn~B{MY*Cz75b5&Pl%zqW`n}~QO7H|;mZ?X
zS+u-YVSz4Ws8kZ_6H*5-X*S6mlhU~(4>ricQrqWi1J&8tI$AxK0}
z%6Mq$9#K*T+_5Z#71SBBI$@T9f1yJ;ZF>IbS-?UiuLm-xUR9qHqheH|@1S6UvZ9u@
z(VMJYhmam}O``A5li6^|MB$JO4Y}Flbp-UGGBQ&6H6?8Lxn?;K;L2FrICy^k89iCN
zx!LNFkbX7=Q@rS}tHY|uQ
zLzYsId-STJl(B2uQD*KcwZtW~
zH7t6!)aGCPq1@vs)ueOT26;OSsw<@b1t@Y=bI^q1H5T_{!kAXTp#i@0xGmiZ9Flo+1=4Nq{VU#dz-rl1y|C<6*
z<^9x=+FVuXS7$`r`9m6wK0t&NFN=TZ=3yRH=WIHC=e9R5R7odZJ|t{4i%l0wM;A@(
zp)i)4_#un3=Mw|eSV*(I7A(+lOC!*6PgA$~Rd!aDe=j=1#4m-p@M+wO?4_fM;wPXo
zbY*Ai8DXON7W1QueXCEX6ET*nQo$}ueiJnZ?-@1qenLi(k&zjOa@4K_he}Qb(Pi#Wm9E}{^XRENGdi}X*Y@^1y-_~u
zPL2A0)n(r!D!w{cS%u<7Glc}-!$7y82hjEJs#ezPbR^M{4v-cSu_mrZXvj!u;`Z!S
z*Jn$Idld(GO2lf+8@=~L1JPAM+~jzRC);nwxk$7%nuzS*e^j;HX?)<(H%>whjw+(3
z!s@QR6ptzZy;nPZ_tQ
z$zlGR$s3V|gmVS~gI9iATxPnju{TCcV`l3%ExX!2m9Uvc7d}dfOy1My$5GIY=N-6g
zr%9AAzvA93I`P`v;RyMG4vh-odI4V-^`&2~YOU&7Pkk2NK-|DknFIDT{8XF>cH1oW
zxEYrT?zQVEvde#!ql(eJ5|0)%{kEE<;qDR{IbRJne)rOtR2Ga5M3eH!sCe{-?`@YSn)B6Up_ie&*o2%
z>q0YzRs^T;7G-(#7Poi5K(T;LT;wIP%A;_mLD}zl3}EEOEeYry7Q+uxFbu014C559
z|MK9lh1g@v@yPtIIbrAyvcEfle{Jt*GIS7dsP~2NB*&tJ&_&oL}SJYk|#MS)#wKH}H6=NY~H109ly`&yc}7J65ji
zJa)Ft%7Ze>CeIZ8usG8{I(oQ+#)h8_?1gbxx-U(Jg`q9Qze%aXulatnKc8<51nY%)
zfM8ko(bwmsU`1o&Y?pVkeAa_KzmhkJG{eq4E5<4)U360|RK0YZtdt3FJ`4;2%cN
zY%l{pDKaEYN1K+%qSh=F_?#p}__Yjr8`X`ko<
z)v=hU>&Vvjg_v!$FYw&{dM+7l+#6>%jl4(6`cLQxY=49Z-R~E)?f&I+2^EDBK~drFfP{6OPaH?
zX+guEx>>-iGDf?}&Q)yi9y&1T28f<_`?;J=b@VMK-quv*qZcT?At*L!bVx^grl1I8
zqNjU0CPF5#W8?w??jIc-SSS@VrVG%6-MQWy%|X
z-F|r`-gYs5Qu3_hN!l+V!KoJeMiZfjmCK1}xvd=&dS0F+^B&h9NUD(x8eDeLuUGe5
z4`iX#Aue#u=EI4dkI%Q1;@Hzz@nPT36HuX!j?~A;Ze#OQ#OuK6GcKBNr!58~7(eQk
z=&2M&-!>olj*wFRz`$1m%pW?_8Y7e~%&`gNIMGwb%+aZq1M&2uzKH{5nnbh~M-bmT
zK#*2bk5tA+6f!qz-Y;}>Qnl%NE_rTY?7)ZhaMcFTxA@c{>C;ColNIFAG@GBskk1B-
zL7K2Tmw8FQO=*3O8a(iIuq#iy*y8v{51++XwjZfKEVp0;R)U$
zs)pQxxBf_p^6a-ZWm5A37!Dt%eu0mx%C(k=N>jPit3|;f8}2bzC^*RP0`ACfY~u=6
zwB6wz+fUxUeH$UCNvFXQ@BUk=n
zjg9E@hsdl9&zaV-SI_|u8m%b!Cr5WdF>h1P+xX*`f64jISj4;v@#Aa%^S{BsSe|a^
zSbFG{Bt9w!N5bh@cKrHq1suUL7t7StG+7`c~1`eEGPfiQvYZmjrR)v}nP4YcFBI{Y{#ka4(O
z$l*5u)72=>>Rv^AA&*E2fGW1$u3Z2b))*9w2h1-ok(>5Uk6iDSAghajj^3*o15D7y
z<-spdC-n!faxNID0`7bIUll>XYaOdlLd2JP~$f5;&Za=R{m0@$4^mEY>1#3yGnV_mVW
z=LqxP7tnfV3yYydqSRmdX5fJ-C^O@eld%T{KRx8+;LuDnrAzr*`UcV8k#jz+=#699
z3!Sq629$9bqKHp>p7=y3o=9z>YbJ$!GF{`z-0XCt
zcTPy;)2@K;z7s^qEia;O0M)(ld_=<`MC*o%EqmV;48MM7>9UoVRI_=pz~?(^29e(c
zluVCXKm$Mg=;TX&9n*?2bTL4m_vI}8G3Zt$g7Ds|WE?#IGvYvul*A12HE?ZhiG?cr
z3`ILX$i$Rl4sM5h&1}F){%L=B5s=dzq<{(c+Lq9#
zn~>`NxHOya@ILBcg3K&u;JdG33_+*?3dn8_fhVzzlV
zwSRdd(@7Qx1{k^QP8}!k%I~anltE&~`Q{l6@x^bzK6WG1L@$E!ehz&7LNKDWtfCk=
zc^Busplt(uxX~^JlF>$`Ci~)^o|O)QUZd*hRN${8cbTCD!f5LP(ijbKY^XoF-5
znf2R!k%Lz7RIf-aS5`meGlE=CSYO{u(=t#rskYNwrfqg}Pv-8mE-?QgxGR1cEKZhE0@WO~8
zLe#b;q8rZA3zPY5WulBk65yW}p!yZk-sIkQ<-WvtF5)9w0`fdMChUlB-`Pw*pg4D&
zJuvZEGp=AW4bBJ?+fwe7ry@v%7nT^Ub@=U$Pg`k!56NVBM{}{q1%hTNfz%&*($2e2
zFR%@hBYj58M=(6EUtc2ZY?hsMr-FqRB5OgM6ZFasTR8}Ve03^HgppTUOHf!?$Y5LS
z$2Nn=gJ191W=4jFh&v>;a+{m=pMjI9E_@k6h`~WG;+M@ZkLbzJA}&NIq8W*BB9=uH
zMim4P^aKY~&#}zC)G(bnhz`5XkRnF8jst_-MK--rM6Aazabbpf(XIT5gO@s?>`pip@(Wcy26lP_S24^SZ&*1D2Z@($SuvoKYMw0
zSn6*Tew?SmVJA#>@vGeV=$nzVc~{~kn^6N%f=8h@S_!W3b_)~y{INLI#6dPrZ|zD_9o%(I5sOBQ=*^jWd57|Q714|5n;VgIIYB-I
z5VnOJ32@0`&eO|nRsjL+w7W6IK&fPccswWrAjtxPf&IN>TQEnjKU>8QYa4tpNyB-v
zCyspolY>*C4*~6XTIl#_Xfv(=wky(3KAxlp@XWf>7UXvI(BZPEv3YC
zZ&K^_qOf2Oh=Jb6X|STm3gSm1K&a^403|<}C97Iz@5l2`D~PgTwO2Bsuw>Mc+FwNx
zAkevbRZxrBSA6lwiY9(8U4Vx9%V3v2aFuZ
z=t&u^)q|p)D&>22NzitE*Lty9u2EyVO*N@dW_&G)S;TuHVBUilc)#(aQJdwS`kRjD
zp61J^fbvUvm%2e_+W3UR9(wkIeMX~1X55C
z6(J>LdE7lqniW^$vlkf{sz63E9O58a$oE47nnV1EseTmoa~K0ZhL}LN@#FZo{(p%*
z@ONT2C%T~c*Wrf((b5tCay+V)#~n|o(r
zlFDaHUvCm^LmqGVGt79}4R1G1*H-F6(n%zLS%K(?BgP+>z;;mYUiF_f@6?u?8ObP<
zR1!83-b4@7xr3o|>^}A@N)%sRoBTikdt5z02$L`mj$N|g+EUY0NieRH
zQ$mBXMRemcx@^fLx$dkeEyv%UD><3qs8OP+Lg({;fG{%{FnvIcpm}_Oz_`!9jj=WR
zJ1Nhu_3Ql+l5B-Ro;_}2@~+Xh>D%5
z>V=gZ6H`CB+-{QU5uK|N2y41JDCJ6*82QsJulM>(i>
zwVwqwNYD9cGybwb-UPqt{c+o)hClQA7RtcK7PqGjv)qNLtH9KNWessHN4P-B
zoYXFJWL__COyKGhPjBK0mNRZA%+QZ|-S{oBzGUSR-NVA59pIP@UK&(&DYEd#Qdmjn
zi=^aldE;5YUb}5t&f=t58gI@&Z#kLr`%3rb*XWf&C(yAOST0&>cP
zbt`YF8`sK%N->N))xK7U9$psL9ew}F(+Rf_{dK{}w?0}rYK6T%Na?z{5YVrb;}@|`
ze4-%Pv2J(`(n`3>q3M63KU=F(N^|VUJ+a-Ejng?-Z}4;XsPW~_`zCrOJyCWbhC(~)
zdCnzn-@Y&nZ?GnlUF&o^UR@+>XQK&}_1u}*Wn5YfQ+c41VmT`37K10?h@v8?<^QkU09A{q>{
z$sBc{luC9(0wl@9HwyY=1);P2rAKsf%C1q|*K!UWKJwvgUZ{VA?o1%UEHi;Fm!p4D
zm&v2tg>}7t-wFl5mbeV9RY2Ta_E?-V-4;da`XT@^yCjWI%?$r|zK9u4>x#yIb&Y30
z`iU^^V@N-Z4~BL57yJfFb9%7$xOrq}2*bsDG-I*v-)$~^QfFpien}aJ+4E`bk|49K
z2#dCpZ{=iSE}~{(Pr$Ge9%pwyBg4-J5ij)G+9LroGN=y7j``;DYVNS}1UK({1wJ*^
zRS*ELR5Rj$l+2i-2-2pb09|w_IFzvqigCT5>%H(t{%%rk0zLUYfS(iF1T+j;;VUY}
z&3192e&^5}Uia8WEti;{L~W(#m+QZ?FQ^^?f*;|0aa{8!p8AD$+MHBN;&-CsXJG{x
zn!cqoaC+1s%KI~V-Rr7D!+dIoAB$25Z~FcS#p)b068RQ&|B7eP|N8OQcOsNRHE&|0
zJ1%gmx@ZeeGh^3Zvo@Nr0+2vCoXil?cRnbyc@oM`@%V4@Njk)x_H2L|{!z5~mwO+Z
z5ez~JpS?L6j
z&TlKlQj|J|&`;p*=uYelW9NZb3@=fnTyg)hi;O>;q5iGtvs7&RC(f%^GBb*TFBslC
zOT7zcrS-$`{@Qs+FXz%3c)glia85!F(vUjhSj;@4M-(J}@-Zwc7Bo$a@yv1%E@2&g
zxY?izJ*Oz=Pw^R*Emg>YFx70z
zqpwTv`lp(VqYd={_&fpZbj@M#^yd`qatb(LY(}OKx>(C+d#FhzafT+(W!6>8U(yAz
z&`OZA=6F%{#ZpDam!X584O{~{G|P?HMC2eR)IjcSoVokYKc@=1ME<12tzW-g`j=cU
z>A;_)Pz9kPb;l1WRzPZ8v0{kc#OjPivW~Vtx~Usk3IDLfh?n9r{IGa!i!hd;y4plD
zbngC_NZak=&zTt=1^RZQBsEk-oHITRBz*`Zq8sIJY??H5{R*egCI()um(&aQCYdD(
z%-_?7WgawBh}>N%k}()F00#LF0QIW?BmCU!2=I#&Hk@3FH2=BV=CuSL%}VEx2V#=V
zCb+Ex-XdZmZ#q(^>34Z1-r;G>pg9o`O@-I^yy*`Nrw-aFLX)~&wGXK~c3u5kyc$BN
z-aB9(9M3Ifg91+c;f=$=kPfw4c<SUweq?Fv4>
zA)M`EAS+^mz;s5x^-Wg#paa6Y@6kU`!dRB`I;5hcrM<+O?UCD&4{SH%TU_M}&
zEv0e}s>sVF!hRh#IM=kWK*{nK6?_S>b=zsK*RE(nv
zd_kHW5sA8w9zs-MY!Dhw^BTk>anA5Gl^Yf+Y7rqvKp&1_WLJ9RnUR--f=IPvuS48F
z3^;ot!}h8nOUYoUPu0zwSY{gyCc*2La+Y{>iJQYEmqm6`S$q_jI$G0fP;P4)Qj4W>
zhX-Pmj=OM2b|-PmQ=FSo^^gO$!3MVc
zHH#EZEkt}5qsFDL6Q&S7$m`a`*L=GAh|jwhjfnIIz4l90Fi}Fd+%T~Be6C*NXH3C_
zyoQp46x@s{aw}0+mS(;
z5a5x^-5ECNN{anHYy?A2eFU8pWZ?}c^}!b80GEyQIiJONd7lB+eIF8$gqZC((GOhoNT}sHF0t1|-u?1rs;Pp)kasUWDiJlWWqhdjGR-2U{m105+$aMJ+JLP%
z0g*>}8AnjSV69n(4M+T53=ofTC@;aLeAxRm
z(1_2W4f8i}%!+@HPm38h3IE{A8x6!GFo!vy9oT|Bn+PL=+c`n*+pf)hFV{E8+Kll1
z&sqohwnrti*ywP44;R(uPtH5m%F|7|HP<52wbBx|n<3x(ScLjav_be}zcfLxAU}-0
z8~+ZptB;*I{?Wh-alJcj*~rvOsLD;M|N!Q0^HBZ*IZMv^l
z<`3>pE=~q8HkY>$r;jv-`z};3jZr0ja4_7&>
z3#1Ig-xJ9ZJcjS4X+NVYrJL?N#S==NSwt+9NCBVo?$l`dCH{?e=fSPVrx~9@+-@3~
zx1OB5x`gC%uz>jh+TnC>db1Kus~Y*t?=1xYlyvh814HsNkLyQZ=bKbym}55Q92cO8
z9wSRDT#Q{n1z=o}u1?lz^jwmfAXKs(hhs`PD|H%Diud^p&u+_2S3CW1q3XPp{>XHa
z(PI9h$;^u)5b9`EEW^~y`W?cSN|C<*YQWUrHQ*!HrZmD@-qKC6{ODlc%t;FsrJhD$z8Q2xfMlnegJX3gy
z3+&9xn4E}n8`(jJCMUIE!#%DqX%m>l!s`!q-Zs@cnxgxcr7RkKE+!7ZxcW$2cA&vT
zz;Lm&x-FiLPM05`6;;_ii4~LmEXrs9BW8GvI7Za=l00DR!ySv-`JiI_6*1OxBFD5a
zoX5u?^yN3Ki=PgvZjpoqtK`(2WV>TuiMi*K`ti?wOm-vyzSGb?(Xw7O{dbJ+S8)|#l4vWhOMZYI>(T0PP$y?-aNW4GgS
zNu0Xc_9zy`c4`SsK#4W)b1r>2@9vzNZzq`_!9cHIu8a>=6}ug9=$C=XYE%b&@XT}+
z09wj{(O3GlhzSZB1TXp%UZlMIvPM2ssq~KeV|zmn&mafaP$%5)YGq2#w6COXfj+bS
z9X+6Vcu+-`^dZY(be#WiA?8E#R(EtwV-Vxh*pCklZQeiq=LpPNNdj>&=tBx4
zUzA}%1lh=?vRuZGnlEl%uzlg|6?gWFAT{wLv=I$)AAU&soa!|d%Eo55dd8?cI}AP2
z8#k(dxY)qXmBtE`+oUeh&Vwkxys3US-1G5A$bWi@#$;$Tx8BadEzY#RW12TX)GDda
z?AXiB>?WxwL}3f|*%iVPS@gwbQKLW#8KB0xSUIt7etv~?v2~)MnOY%(g!Q=;(#116Cz8GKO-n8NSE$mIQ81;o*Mb$w_OT$(SZ-G
zFTC~|^lA>^!Fh&(PmNJ~o1HB%X0Z8zaR|UiYFn1pS?jE~Rv|Fb7lmzYE6c5R1a>xJ
zfS-Y3+Nn8q74%Xu`*DZWxgTUDB~>nxcy0CBAGBXEWCiRCRv5J6#7W#@NnM?C?h3g|
zKlAQl7WG;+K}(&O()EP{h!J&4!NpGWf$RS3;pk;DSB-KHEyDXP$&oS67Ua{Xg+)
zC`zxUvA?O!SWJDZ^RoY7&ckxRhO*?Aa}~R|@Vdz_&mf|GbNIu+Of_OoQGZ@e5DMA0c#f^FP?Dh&cfGMYy6`
z#~H}?XpB{K@6^)elP+n#nNEuPHp=0U&(i7!<$l*Zb2OfI2A%>V0m;-=W<1w(?j;h7
z&WeBIl}@*_Eba4mq~Z^=8y4E@dt+u*5Ez
zNS#T=8TFPLh<9Lx7Jv80`2M*Y$vpSIIZyO*a>At>zu(eqg|6+SPuy!YvN&(fr42Eq
zB9=oT8P@{|X5^f5$4}C~px{b}0Gq9m2vLn->xBuz4Y$;PcS-66UDMrvI%EQHCd&P=
z`Dgou*F&WrJ+=2k4N@6)sxvtQ^-Qi%;-pUTy6v!}Kkiad1su5!Cz=pJB@a8L?}Gbt
zya?>=ry1;&@RAtdI@B#=yHs;ev2D}N+iF&vxt0yog+0FdIrEhClZ?vd#Ja!-xh3O_=R
zX{u$c8_gb06DTkuiWyE-+i{4JyFrWL7ce>vdLdDJBH0V>#35IdV*Ar0$RXwq9uX0q
ze{}n&f?$MNi2LWgx~6(Sy7s>4L;r^c*X7TZT$d#8`v$8&hB6`q?v&0K>{oF-ukKvp
zkfLp>x{)UC-<(U`5k2;XG<*{yQ-)%zD)&-k?SjN=3(?MB6a(H8Ljwt{Q9OiuawqD(V1`7tqGb8c;y2&F3!p>2&7+4HO`3S#V*%AVp*zF6
ze5}@!RkoemtalJ;uADUt08#mRcy~Ko0*=T4pI?bRYT@%dj=8
z`FG}G#w<#gC$*NHSbqBRl)%c>KuDQd&}
z;NtUZ(X+1;nrWGF&e9iDJQ8OZB;5MJma&r9q^3uhZ&Og{SRJe`nBzmk@*kQiZIjDp
z-?lklQ=k0RT>2#;BDkOX{AJZ|*IS1e%GzUea*JSj1P5%gAJzE>M)uQq(eQL!a45z+
z=vwcPzzx6g%CGBwj-*Iqp-~!Z^E2_BWd}Sp4rW;H>M{3
zBtaaxsxTRA(Ay8Ikq_rt;B&r}hoQfbb^ZGZ-s}FfgLs5q8=>-Z+=`E?&TR$5!sL5H
zOm*W+P)!2dNZwr^2iO`MS
z={tivP^(DBh9o#Nge{G4v=+|WKYA|spB4aWMO#1drW#?W`s0JC;|a^M6Iux`-i(Y4
zxZAqw5rs9z>5Z$AXCcsQG1B?}T4@nW8=pzI_*~p8+$do!gogF2jD>2LIVX^#P
zU&nVAADmE#d^r5`z~bsuDPB;J%6-1HdHLoAnFzne1=!Zk@D<`41pI#77hVYe#tY8x
zP>oa2IsNA$>@B9iQ;hq^xYn{o_4n`VWKDIQ)*7W30?By-VCaO3E2-02;gP7Z3UD?w
zJ8V{F>#yF0GMk4SlutX~CMqiu@N&sj9EnGEy`#)VGZp5$a~Fs-
z3e8`T0S-mAV5U!=1R>P^T*fU#lp;FU
z8}i^|2FK%~Gz`4|LQ9Y#NL&=B8lA{Sp%vvmB8l`o+Z)`6LmPbWCqm29A&NB8e#p`n
zw?rsna_>V0KOo~fearb)tY}T;3+3;~4MX2G`#m!@>-6>dGsV%vpYe5rTkcFVfW3t7
zt)42C&m<pv~tsiKq6^A0t+5hfQtv-i3)G!*!awX*D@@
zu{(nAdi7MGcm>XmYN@G$A{gx;&&8H8m)v_>JM-k%7xnNS)<54Kams+O9#TJ-GVN9W^)scPvv^8K5^>pM^d(ItE4jjC3>$xjGvUir4P%*vXLi%ouL@OG%i
zI`gFxFf;XZ_Z_etl2ohG=f=PKGx6ebwxcV2$v1?fLPj>SNNp{ohDkSNx5e%9!Daqz
z7CPO~pe7p=7Td%Sd+U$)Mxp~7n~ylra}k1!+rGR+BEC%r&6%@G;j8s^epEiEa=svqDc6dsgbnx>}JNAhW=_6Papoa
z;0O9mO2m^sxvYe?+~E2f^ZiiQ;hd!(?HZX3ckpi_;0b3RFpu<^^IIJ5&NrS*F*T^W
zAT}|mlL@-6=&kh0kBR2Jjm)Dpc5U-VEbn}-6(;+3%)1qzkfFDf-|c_-XiJ#V_|g*u
zdobmGbAIVdCgPOEdudc}tHqfbzym*EgYDyhVfwGJ!GYbt-9dFn|N8S=A{i^WMqf!N
z;Nqs-c5gMNlt{G-d-KT7b-Pq=HQT~0!TM0?u2_{0E3770!s~zO&|Mc7x7-~FPXQf@v3y=ya9Qd=R$p1Gal4S+
z&Y+r*hs||chH`$tZhmu+$>ZAjW98!PEH&?bUoQJ?z!Yw8V9~Ln#Qmwe*YS40%gozg
zvrI7>|5dI3i>9v%i>v9Hox$DRA-D#Y;I3hCcY+3YcMlRYxVyUs*WeDp-5r9%8Q$+d
z&s?p`*}eDbT29Wv38Vzpwqm
zJJi1#V4mrL!pw6aP7@&P(AA-g0u*o!f9(C=2p#`1LOW_c-kli|GxY{A}T0?QbqIplB*S
zh+R|~iu2`akkJ0^sF-4*uk8fQ#9_~7jsh{H%bWF$orQts;mjes};OAeH{LKH&z
z6TBJyKifmwhvijoC_c9TW6D{ggZk?Uggaqe>{et;yO8-XskS
zGN=|Ve0m*r#U|J62?Rim_ETU%Z2U&G2t2HzSy&DO*LnNBZHDhDE8LR8aH6Q@?+TQ6
z-%HCets0_b58jro&<(?fa1oFyezHyBA6HGdrN2LSU;lf5bpA^SLi=yyM5WwRQ~bg?
z%|)r>kbIEPP3-519{c_Z|Kn&8wd-{ad+Fju07yE&+a~wW7E;uB;&739zZt!_KBy!`
z5=%9jvQx>j9xQ>q9RDMj*iCQSrZ@-?qMu&+dVlG{4I~qQ%*n|CY}w;YTX4+p+gU?q
zjVr|JF#)Dr=mepQOesJ_z1gt#;dEk
zvCZ`XRH@>i8MN})opFuzzFEcJKepe393~zQjQ=P-_r((d4XxQ6i5!N}T^hV!4*
zlQ)H}$n3(K6nLc6_jpy4&uCy#uhI^S?R+JIr|%4fnq@~%Jj^*da2EG(N`HXNR=53P
z{|$HI&Xyh=ukbU%O9OgI|L=PsG@>$4RRgcwW&X~^>PgAf9dO8j+6@3CIqnQTuRn1NnhyDnMKR25p`o|2Ji@JZF#!R-swes@}2uPUZ!LOr^LII|S-rF3AJduD^gO!LeTyRWHH!}@bcw;7o
z=mh-tMp;9oBiaQ&dz6VJBlk&*CP5mfGBu&M>|euPtutI~#`#F?{A;fgdkfA;PI{-r
zzLvAlZNj-
zqJ~6D@0`D8j@_8b@Ux5}ApOkyeYD@Wk#F)V^rx&sb6ZQSj?TUB)4e|sGA=zms?mBe
zud}oBUV$^xiOoNE2X?y?_F_=PiwG(k4ImmvIoX=~y0Ts;T$8WV$N~Tn@QzpR!eFLk
zdvcn2b3#VXNOH&_mu_~gY2Oh0w61MF+_vU(JZLgf2XiR8uNa9Y9*#;Cz1UiHov@2i
z30n$CshCo(*sP}fgWeC>60K5ITLNNRsiVEE0vusYck1MPBX1NlLgjh=<+a4}s(@;H
z)|aR3ESq5=qqXlwD)#FGxD?!zN}p{sK{~jUg_RG*u&?V9u0a7ia{obJXLc~$aIOb#
zje-H2KYT;w)qQx2!o#Y%h3)Kj;u1Y*-J7WN%kd#6hbNh#t@}j>AfQ<%5
z^vhXJ`dirFHa%=QUk-|6b8ue}r^zZqTg!uz|HW{BU;0eWHMg*km)nC#<~8|(0nCvU
zSPbwLbGjPddG9sF0HEPO_^+t?)N`*Y{ke~!ng>I;rj(rLGUzw=$FqlvC`BVkb#n4H
z0FRCVHsEj_G62BF&aR}QGE2{tH5lK))aLB8^2kdi$?T>*?pE*q*UUH$KsHEoM$!Mg
zYHp*2!z8h_NM`;FVsjFVB4Mv4ShH3oA^3A_c7$ERscrH$`4>iV;+W$^B+%P)wu%LB?LvCQ{xc50+x#4#E*7gv)ho5B0{t987mYg9pN%I)TJAgB
zJv#xyB9sc(Fq_E+t5H(f-bhKB0R|8HfC*WPq+WOW{1M2dZTS5h{8yQTGTH8M?6KevW8~77*&X-u{Rx#rSW8
zdEOMMB#e&>6>@+2w)^!+@zpINOP=WxNcUg4fl;-LkSGYknH?p2dnMA!5By9$^)IhJ
zp6TPqGkr7}cKdMjvruhT{`8!`PBc83-=5iCJk}z-S{beC3*Bpc7=bq5>NUtjLdxmX
znUfU&jwI&N3yQ0&?MLcWY$(MSY5WU^Vj81YqUVPBqjD52s2ahaLX(v%dz0F1>pPdQvb2HJW(
zk&B$1ZY&zjLowy2p}wA1b9x>(eYay+=~!p3=4J~cn8=}|UR*FkC
z))!arJ#cRT2L;B=XYbsysGKnj2vgU*oN%`M+uq2lZ6nbzaN~^Oy{9F^rNCYd1E7m}~X<1k{Iw9{9-IV0T9O99Fd@-dY@!ieF`(n@7I~)i>`n?vZ{js~a{^d&;
z-EXLzaTkKQeC5G$q2I8vg0CfLfM8gE!bh*teO2F?l%}Q~35f1EaxoBCoAKm_-q
zwtGWwvk%6SyYe3QS>yT_o{Q~JN_a>x_J8{>{2pGhc7>OED0N3z_N$PQNatd6jmnES
zZ2v~8c-@(=LVuEJSc3Ol<2mCB%+mJFY6qo3+OBtoWd*@F4jeMb$Ti4T9d_?<%gVj^
zt8a>@$?eyt!To~ESc3Zn)g2-5wm+K=E{XQwf}88nF`2hIzjenHQmyjs
z(f5UW^UxeyG?gx2pWs_RuzaNawe6u^DRRN;TJvU
z7POnrl9KSiz`)Of=t06)2OD0nD0jZW)wuB2*x`!dG|Ymby#iiv(4V+H{Y^?m%_Ku9
z;Rm$Ip`LKYBlAiRdpS7p67Qb3uoXVTs5?gi7XY%xND4a5xgPmu{O+@Q&SREQ#!y#Y
ztNnx20V@huF~<5HXEtUNuMr8)_Xkf`i%MN0@f{L+xKP2~+d*yOzo-CGj}2UQlj7at
zn{Z@?7yv1N^G!T8#K$j8lB`Z9=DWF?7_?ADc5~(l^KbNVIEU9WjnB@zylB-S>^(}e
zZlqBE)YyS7~cMb;7C0J4ia`KVPej3^8H-Jy^t?&
zqwFmFqqbaI`F|+^D9U5v0xVEKvOoGwwrliBfawcT`fj1Q$^2y(d}Tyy7zOfn5g57=
z)(ZVr{7=Tf8bxmwV^6h0wen*^7u%=T3Oyw#RK>>KW^A(JU)+s@A;VGKPTylsf}r1YhvF^n`u);9nEhC4|KSb;@VFKiLl5uo=CL1Q
zUI1@6ydRCni@+)GZkBOK1#p3UXlPK?Zm<{$MScXICqDO*^0WK?&I8N|-rxncBiLwj
zlT93%KE@+EZbiT){PO^CSo~5@m!%&_cLOPDh2lbL=n?_@$jB09-q1qPLK`N~FT0!x$k4)Z77AG=g6R!U(_h71&K
z!}hO<0s-OIluCuef=GF0(9affD#Z4t>pnMSA0LPm0Wbl;GxNz%f8*o(36|Jwz6MS6
zEysaFmntm~UieLr@AW*`Wl&(7*Zl+pCP@JBY2nGAIV8-^_k90Ck_z47%nH2*TigAP
zr?OVRT%W6^bMPv;4Q7AO^-s?^m%?m(N*n%rR5vIDBJ4ka-=;|34aREA_=`^}xIdQ4
z)DD0P>Mlv>Is}jfzF2q(8R5|SQXjwO7x}}&KGboDBqF~6A%q7B(EUXt(Yf#zKgI99
zChry$7lJ1$S}fP|Rl33ePtTpSjp&H~)T}W;RI%2_7A?+n_uwM)2+@
zitj%LU5bU|tsFsFfV-N0+YMxv@0O)&?N9%GaxA`>FT5|P)F$5EjN@sXCTneX*Th(N
zPt$H&hVRjz0~|j$x`wBRe|E${=5re+|Fk|37$R4S7WoP5RNZTdP<&b@BFg-sw7A}N
z9zlKo0PzX~rCkk+DA%f2lS5e9n1BA<3c?da5Y1PK!XY+PTFPOw7TIZzycf^FT&y;0
z_CPPkYW2r{V91|6)KCdqXk5E{D4|PKZo+WxZgHwf%tT2I?c9@|FJ~e17$dwS$aF1f
zQ5E=tT9oC1fCIX@xy7QMnkt26pOGzzN}%7*^^s|>jE;ic%!o{0z(;a
zjf9C`2nP1Vu5GWO0qbvrrPy02@+lJ|-)32t`Lf39)AsKI@dw1=@CXGJ>(JxqDU^MGRJLB?8A~fBiEO&VF@$8
zsVMt-;5_JShxjZu?oB87&GFXwT&bG%m>FLP?%SnPcE_G75eNZkGQbne$wq8c1
zK3hl|bdc9r=SZ|;EmT64SnFC4K$1*crgvF?^hY9c@J=-}ZI~8o{82KJqa{5^si;`F
ze`%}E;mYXG{I{-4(sm?BP*ChJq_;R@Vow@A|2dINlgV+1cqWK;9nIq=`DS#L>h^>^
z+^lltGMk2!%5N4%)~X{>RlVNF4XEB8Qg{7Z^Nc>}+|hVPPqnQa?g^4(j$o!uMuoZE
z6RNzqYHJU5{hQr4XiH1Wy0u@!mr{Fo6g#A^^({6=c<%O7ujW{Qt)Ez_@8Rb>*Z1}Y
z4w;F;SdNNTQXte?=c5^)&=42+P!mh87r%{i5TL)07dLyLi!@zaz+3=qVN=5vdJupX
zc3vkgMLG8EnV{WI`z4%nP&e!V(H$kUX!5td%-ZWHiols;LaOQCUW&@`2}?~b#GLb9v`j2yl|}+=Gxe4$dF(rO
zEfEbxslhLtP*&yk{WJkoC{{@7zW3|~^64_`>kw5PSK43kR}pm*agjqP3K6G0d@mQ@
zFSsXPaw7b}zi!qje;EK#y_orW%STxo?LTHC6-e?kuS^UOKJ$d;E#!mWtn0m13AXz?BtnMdhJTOD!sjhQM<43
zRDmsT5pj>Pc@%#>i}n&xK*OLAck8fph~zN^X!gKKOAU)Pj(p+60-)RcdnX61`?>SQ
z3cW$;{NS9USNoav-j`TOx)RlIwDFt1?zT-tU7gy}@~DnT7|pF~zuBSXLiUz8g`UV+
zmC)3Dkk$AS4M}fn=ZKwdiNRZvgJWLghX9aiv(Q5lX)&63;9lrutV2SMwyI2=RdvP$
zv)1?%H8nm`wC!5ENh871si5n{viRrSfg(QXSUb_MiM1oQg;LW4j)1?8HJaV2P(Wm*
zpc>C}h;yH7x_AQa-P51%hMV}W;fOkj{^&n`K}jAw|Ge$wIVI;5`oJZI6qiC$-IRd0
zgSa#r4)o99-@_-k)5(d@=(m2ed4FM^CB4r+oNnv(H1r}QJ}=DRxy|Ge4=yH90LC=rpsTvq~z{X?QIp*A%R+
z^1Gj+{KfZ2a<(g>8T=*OiyMA~H;WaLjoLN5dTiaXf8Ig}grP9=77Wk;+d}C6QQ4%j
zKzmx%yk#$ktfdxrXR+Lcon%7y^7+6mL?xC&L7(>fe8US8-Xi$)WRUm$j)O_+k*JV6
z|G>lvRRG)V3$bYrxzzQJ|Gv&ZGS!4yK$inL`u?{;hQN_Te(II(C9gLF4UE(WQ_Ydh
z6m9V9%_fZ|7bp^QAfv!>QpPo<`ku~zzFoFS44(ggFF^O(T-)0BmBw^vL>!QswxpFY
z!c{Fi)+zJgffDaYMXoOIlW~)sfz(x2C6pE9>5``8=Yp9IJ*SD^yShYo+dXCIGiS7+
z&}vw(VTS7s_}&l8DVp_!@hgSwDf*wWqIACoS@fPzvQ7S5lz;ubW1>F}#xr062j8Sp+wY&_Se(sy%>aApl=1n@5y@+D!&zq;~n(;)Nbz#Ks*we(q
zQj219ZPp+p$dt7ZGnWtvXnk;mK?-#CTYuYpd1;e||7=d#O!OzNP-Qn>#1xiqZd&LH
zGX(7adKvH?2Lb9fdc)}n8~f#?2gZuwLxgb3gg^g9jdfYn(-gdeb5+5T*vRH~bD*D$
z1<_p7DF~9$*C0d19l-*Jfph-6COu{9^#mj&nBWwKG<1T<_BcwX3e7|Z~~O@A?l{zE;it&FUqqj$;>{hC_vuFn=6)||0i-!(!)GK
zsVck*%+`(~bj{FK;?jGk1V~M_&Fx|$hWHCDEiF&C|BYW@_B=!YS?$*^G%mJ^&k0|)
z?D0tA@8{hPchYjOfH7ev9MKAbO^8qX^Vh}N{XNL7q=J@e(6+d)B9e*F
zEk0R4wtj*)jH^<6>bDcKm+N_vyCU+aMn#^=WP-&)Cv@p}T@58A8b>gE8MN9Mt_TDa
z#4d~8NYG@*Sdkv|s_TJGhR=j>h5A(kAN@26hSe`AKIM6qp{-SR(vgofToV0Q(#k8g
zi-nKUU4)Dt5C`;m$Q9Rme_8feb-X)MJ!RT8z_i;tz4Own!R*ki_7jr+4yw_Pl9SKt$b-=PUZs|l%%rpJ*lL>XVAMua7qR`^mHZfsGQEWX
z*!HG)z;*Q9I%WyN8i@0SW~K_An+?G%ltlA#9_h(Ck#ho)r!!;3wz2J%lo;ZeUD`WW((l!3SvB3;~tKmfjL%b
z9E+nPBR{EAMJqooL<=eAh~qGd0H7e*;$os>3!mv|*uO8?ga6>SYpDdfRh?jo`W&dB
zIk~YsB}o-Y1J+2y(Ci>4$(<9@qN{&js+F6}aWlW{8qYc>Gi!y+W+`;}2I2F&qm8dL
zK$J)KX6j65@L5Km>}WVMGN*75V;$4YgL>uiH6hjneP6tBvK60`qxX_@J@1l`gf0@p
zG009i$gyes!T}GJf=pqrWoe`zE%q}yPZ`>0c>jDWp0RY+kMa$@q`tI5C(oukcB5kZ
zX?h1?$w9
ze|0aha=^-3pnquS0+BK=etToI_f386$_^cJWMo8H38bcn-Biu)Oyo*PZs(6KHfcUg
z*XOa>6SkyQ54naxiHE`Y9#-ZgG+~~IIqdHwiL^BzQ5|og&`+@s@rP9eARTX>&Ee&z
ztbWy(lCXb01&JyHk&}$TJE;J%*?e$QO$P`j?4pGpE=w6$CH14knz{e!IvpR-cLsw;
zQaH?~UMwbMJH(v9>5Ruj!1Zl^tUC3k&rLNkDK7-exPj-`oFm`*+4lB)H6)A1tw`qi
z>HAV$B=3f=gpCJ!WxDBRHFA7cZbk>E4zJsieQ352gj5g`8m6dp`geNGfr3TKM;bT)
z`c%{xgeRXoOZvHB&1QG($<9~7w%MLmT;!d(Subf{GVNBfEUn9;_xq9)R--0F{s|4m
zn=5&!ubNQu5Szioo;N)TQ1Vc3o=_-bWFbK*HP2melRx4j(c`kTzz7H$^V};qZ>_y$
zuT4qU`_Y5vcqm8=>K^=QNO7zXulX-7hewgZy3}Y=DT10>f0ez+W|Rf}h*`!uusgW=
zl5M(}mC13MFJ1V~RSXVo}_uVUP(oqO?+&7}y&)Z~BtMAmP_sIpEw|_0Ri~}>rei!Igk3%2`e+6n{5YuV
z8+;$O8nIV>SI#Q#8H>~Egr?>*dS*y=w1D(7m97!ppe#v)HT#oxw>^wHR
zWcEC|aN<>5d!%IkB#8l_=&ucLy=)))iS+$A&5%SI5U!*C
zdU@xB*z!ElzKo|W!IU&}pH~IH$=ZPS!qiOf%_oi@4Vs=ydWwpop`?@+@_%_jKK!T9)N~`8IUi2$UTk+?yc_HzT)YETiN7+?-naxWr|AjRB6S(
zer0CTZZQ1kwy|~PQ6MTzq+U+%M}vp(_Wl!KL@$y7k4>Y_l#D{-QlR+0Zow6DVdgh_
zT^LnED{`A7@`F9|A20FI;*RMAzAH6xTXWCx=WY$Toq#zk2g(cZ2tKZvGB#!1GuEm&ft6cNaS=tKPZrQxb>5u2
zxQ{PeF>PrxwEo=h2O!)LhTtcIt
zgcPIUsAb!j_OD8y?IC5+Ae-}Hs%eqg)fOzN5~xPX490I`?qUkT-@W#1Q%>q-MK`T%HBnHtH(h+we*n@oa*qmL@Z--xIHcTcF%mnc@N&<@Oc?C7g
z0?I$p@dRiASX>3WLfeiE;sH}=E^O$M
zP|ep?UDoh6zv@K>9T)n@$ncQNuecWT3E}}Ak-tjNT~2=
znf`-$_+U|0u+v!j`EF
zuF^@=M72?7va*u{VE4Ge2aEv&85ndht}(Hn8q?wWD7nyhasXs`
z`ds`*%YJXy7$lu14(in}A*0oNQF2uTB9b>xKXK-NjwQ=)zllJQ5#?U6>?Tb&LF5km
z0WZA-mW(q&kdum@k)`J_RTz}G|$Z~{ORu4>1ZG&E;Vh!y1J|9Nr4b9
z0=d&@vmt!J=xdhmttG)Xj-9LGtnGFxr%UCiQR=#Ma)=YLFRM^k<-b9`rkeN%?@J
zu=t-;`@N-6_w5)OX{A|HSbrg7;Db)X%*Kst$btC7J1n>%US9XbqjK4gw^)zdSLK9W
z?^GfB4=;ToLb0;4dfR-7mMnlx#yKNpnXc`lkZC+VCg5*e{|qz~_SPjLQAAd6?~ThX
z{&W9Y+$x6>_u_W3ko1B~NXt^5c=2-a2m!|i^0db@dXm%?8Ry5^KIHl`d;_FJ>#4T;En(&eY@XT(l)LxFg#MuQtc%!B@9B
zQorGJ_)SI`=?{QADY%!i{Kq6XhL0nDe8U+faHlI$lJ)O-r|JQ9#C-P~k7!P2WBaG|
zunx~bCepTv;agY>s`P0_5d|Xmb`GeefbgQl0SynME$E!#34aPm^#*g)(z}r9tPl@_
ziT&eRjrvhNamjr7)pL6KKckn=fx7n7a9;^M2@EDPMrbY+k?nE@mc`rTn+R6Hh+8-g
zkmr(323)m`MVcBwegOyTh6E)bvb6Y5Wb)l?BeKYvh|HUjqm92oYGk;v?hNTp654rp
zmy>qMfaV?gm*G6Ezmbtuz=?O0ld#AGf=7ZdFTxLV342?v@;xkG#0s>)T9nwK$sp9F
z@TX?!FfO5rthR)c>fc!d--Na`R`F);ephAp*9z8G%B55o+N@L4;nZ0m939~o6c&!E
zXH!vqGeZCAp6)mFB-TKVO;2>?p@soKfztvZB@HekAFUQbrn^-PS=FPHrjI<`5o&gM
zBzfEF-6v#Z4AkWIwY>ecwn$hZ@cIFrDNNb2*jvw2W*ULg{9~?*@v_7%6cESM8O4ZMUc%6nw^=+HwP5MUZp
z9yQzJ=08z|9pYKFfGr3bO1L`YAloEdrU
zjZjolYQ2VRk9{rIbLO9SJpsZz5OC=sXQ({lmluw$;b8s`U#fbMMh7;MxB&!p`h@}k
z@bip__nOVo@$cmbO~T{jK?si?a|e(mvdRRW0O@!hoJ;jP!o$gLe|gcMkbYOfNwa3D
z^6+kq)uZAMPb+n=h1H4VH!ieDDG=2WZ+?Kkqo67*!%$X7HNH92W;IM`HX4%bTsh*!AHC){o>!<1#DCk(G5
zvzO{<3^4eN8|sg_zdc-TtvZY{aabhcXfaCs)2|46K6&}}+c!}jyE%s-9_j~)%fX-^
zgmBew(O$|#5J*R}f9RrP)GI+Ke?{xSVOF3S?EnF>8tM-1oPlf?)eOFS`o*r?!|vl1
z@IwKMG7nAY
zod<+nmU>~_U{zZ~RqVF}Y>88yIhtZzVJq6jQlR{$qstSq6Z3O_#^JDZVKd%+Hang7
z=DlHemJ1JMN{{by_yoQy#lLdZWoW#AVDNGw-LBqv-gBBCfc_lS9v&N;+ct<3MfNUiWQuNkx;kd4gh=rix1EQ6k2oymGXvB?EIIzd6lsS*hDJIW1{=u?OUPVp2&!p6hH=ikqplPgDy-pAR5fl
z`rw~e)b-IM^Y;V}DZbELX^3uWZPB-V2g)EnUL(Ios2pZs%}YY1uG^VQMTxe*Vkz%A
zSaNXtOGZTn)R0H|;)eASaD{PnNjpN5ziF@V^Ya8&<-6$iZ2$Umc_%T0}N
zYc=RQvfUsgtF-}Bvu~`2bG~D#H^g9;?cqi8*D2yL65fEsmVH&+Q77KlvQ&7R4Ln3wiK}#jV%#
z`3k5oUW8w)hh^0L-!d*8g2gR?H%yAIOVzG9FzlNd5qf`5FS8J4Zp-W%~w
zBB38Gmzk7B-OxsR^LZD~^4ynUyh50u#09FE+%#qO!ekhUO>F0}hhC3Jk+PjVYtiCB
zM=`TKPSuK`*v7_2>$n*m!Z>`5E%rty-XSfohJOpRW?
zCM%-_A8|#;v$I`z+tttl?)GOdJLV!1w1c1)avUR$8RYrcHLfWN0f2&%|!CT}9O19yzh8D||a&ZB^B1FEq;0
ztFoi_bx=g0?5EjuT&mT?`XL+#s)SXnggwPkotJfqPIs5si3qdZTg~ljeVsVa>~Lnv
zY|jkd=XlsoA5rzPhC9N^V7#C
zD*GhjiGVHM4TrvCzhHTX`%~2;@Kif<($By%b1$8n?X>BH_vUmq0^M4Z%)dx`kB??S
zcoy!G#Rq;Y;XgwY`Nxn52ei}wk7L!8+PsJJuSwj%ovO7~Z%xr_H>BY<@S*wI{^yh8
zx~Ofot6`K`P4DmWZ|n3F5NNjd>dyff=XJ`cU1J7cd)gqFNd+MKU__9utmvoi0Cbjg
zjvA*<&+w08Jl|baqhMSgu69y&1qwc{AyWHWU~y>_ZZ&OmOfnF7|=r+-Gz
z|7pmHmtxjL@tYDILaFzCO$p$!foN$DRTUSifvG?8brrF>D&2}9Z
ziJ&BnBIwR=Dt4f33sbUK{R?MfWBihHYX%R68H}+4B8=D)7K3|GMJjK4Shjn7*_`;f
z`!2bs%a{ZzR4qZ7ZWqd?bL^d8)z~xlf1^}M5LjOQHQVz)Z2gWHU}4ntV83?i$8%3A
z*vq!C&BV7;g%`C|xtVq%<#0?@JUW)Ws)X3b|0pq6YK5o2ROf-KKwyrEaAbjk-AqzY
z)RwF;%2j7If)E2aZFljkiVV6?x
zWR6cKBcsb{hNSU>xNm=)u+``kZ=pg=?Z
zVL9ZQxA^Ph4;!6kL$?yRM_Zdl)N%BWJ6^_2FiHA#)cy?M>h;&Dom}F<)J7(u*sQ3u
z6vsuT$-Y>@jobF@R8gV9nGkWsA(GOhK?w77p2(t^GZ4ngXLOesX@GCElGwNEvmuN9
z24Q%H3#}{gWR&;qN^FPf%M=vnAff_b5mqgU$8TeRZ!AhO^9E*@Y`C%47|=Y%yj;$*3~LN-$AzYa
zqW2n{QE}2Gk&A_Xb~#opD`V(Ae@4*EbOpOKWZr#!Msyk@ABA3j6stFkV#;Z^^y-hX
zZV{tln$QT)1wbtq_9<3w73S1r@T3r@%tzqN;B%i7Na}k~sar#U$6?rP8tIdTXpfyt
zjnuC1K;P1H(9z#Q!sazcSOL#FGKMdAfXOhQi`Eyh2SG^RDM<3t8iddn)x~i0soGYe
z!!%^O%9OEwakSvFaamf*&rnhH(tu2=df&~E6n!ezc9I>^&U8;L{9tcE&OiB*K&12c8pFisq|5Zz6SluX~#
zIc)}lVJ{MjOi|apXcUD6;Kgc3l7pF}f|AG6Fn1hlZFf9ntww9%AStR+wMjw6`(Uw3{c$h3rt@`~!tl2?xorr~njJ0I`;HYo704
z`Sq*qx5=0XLx=QaqxO%S&lVzDZ*Xd?6*=Kcj|$9-G2md9Tg=$MS|epD9y6}^`3l^O
zz5<&isF>KJ6TVtWy?WwT=r(1{k1qak&8cG)aX%&8qQFeoqvD;ADGy!7dr;CP5bT0A
zP(y}N<>w^Fh>!R1I}YQpoR9g75z?&hm8}^D=~WT3xbBzAE94WaP>)HNAbhiL!}A`9
z*mC#J+BXG&c2Djem)DEX^nE>;eNaSJ62sPyQ@vOX16#cu`Gs%fDx$wNcvxtaR~W;z
zlZ=)N$B!$)uj>WFffu=T{~w{{-yEPr*;z@^WXvG<-wvAp3}CPyh|zWMPlw7!nNI{<
z4p5}E_(Y<7eg^D(FeUiVfM;RucWV7MqS;3RY~9FzGz2RPvtwcD6&97Go$0tK?YJhy
zr%p`FP+$-)n7s9dFlMo*%w)FX`#gTjVBcoER<|)6Tx7RQPZ-S+DW-_mA~8UY!DC3P
z(7}#bR9}Kt%m@3=q=CVjqHSUL8{Q(tF*ec)G2bSd
z**)%rCLPYf@Le9s>w?CP$t_DdC;+ywzf_b7VbM(avvh0NVN(0zMj-r3+v#Q?!wZtgwjwH0`1
ziu85AfSdqc^}#iJCd{gi6nEnSKk+3EDUnVTxQKygjg4uoP
zjE%{wdyI*DvALVzRqHflt6r@YblEdnl`X3!BTWwwjU@F09{}PNLLihxVFRWs~cK+7Tv4#LWSleVRgsQl#$r7N^54iU03S$Z#6uT
zX#};cIUr=q{rbw`dVbXDcQQVd@cS>c{%BPUIEX#MafW2#YFgM)ILyW?F*dh{D+zNj
zmZyK~lk)P;pddsXo!T*W6z0N6tdvct^k%?+|8Q<3IcH5_lyc>JhQKSQlZx}z)j!d=S(~sOol5C
zVH5UEf4$unqBb2pV_sXGq^!%yfH~{G>>V&;6-olxw$(Ga1jfm{I}8=Q14LgG>Ii1b
zy-=-I_q0q
z!H)XWYw-q78{v}4F)7dC48||rFDJiQmhT+JlLZ_99L|oX~
zHjELgX*?!__4X4B!z9!8xv#Olslf+UH(EoK;2W^kZZs(H3gn%Q_7
zIuaC!^UB*u%Qq3KqN*e?r{`GB`d70da4(DCDxf#q?0R2S9D)9ic9
zFPi_k9UiU;r3PsDug?M@-&BNT?y%5>!7X;8+$C;zxf9G|I9-StYKbFE(EaYi4S#ri9;f|~Kk
zgYLcMmj3IPv+wKA7egh$kXf&+-j>m7GvREJU@XX30cl#vomOsx?M4m#f#3CsS!aAs
zdxaWPB_aYvK6i91oi{MiX)ffiP<+4AqAi{WR?EDvwcNFvQx*-
zJVZ^jRrpyaiD@!soo|090I;;!@RgF6Ae%d5vPi*v&tD~$NlsJ^=fS2YEI;QZAF?_yiNKU8k3@487wCfyM|
zCz?rzEOQIlTNf0uUyL-%1oVm^E51J!i8!P!DXTIbyFam_r7~K~NKHVoA)m6@I~^Z#
zc;q;`-9|=UiRsS57LD4Ci%n**UZp9TOzDN)8;+SPgV-$7q)Ef|1yr?~;AG2vpy=Ea
zC=2a3SxQM5lXl66?hsFr6LO?J@xFvszE~<)qgi>x$WErsu{Oglv_+StVKr55h&y;u
zEk$()1zWG)t-s;l?#xe1ghwUQHYEsuxABE_kr^&LcCuPYAPGsVm&G4(KZ5Hwj{eox
zk8JEBJ2+JwUp!MPLXOvR05^rj3fei)A9-b>fK^7sRZPiCJ23Q+{_JxqJ%!60csI>~
ztyb|H$P*p>CC4jc@f)Wuo#oeU@>PAOJ~F4z0EIAoEN-O913Vp6=;cEGo9oJ~c9A-K
z!{}Ql0v*=n1$8-JhT9c=N_lvUC)e)*miZL+8w1g7F1QGTWK3fbC&*AI5O!_DefpLK
zJ&uDfQb1mkrJ~$kNE<1DB>~?U+V5i~n?R$lk^PPVRT46Bl|U1`rf9
z(ooHOm#NM5sEQtU^T>2VZcx#&nyv-}lni$fAnNZQpfl~nioMux
zB5{9Pwu0jppa=Wrw3AByC6X~X1=K4T-Nd1uSJ>xkS(A{E#AGIltQ}6e-^YrTY_&lI
z9|Q6<2uo@VRCw#VGo$D+4Cj|;1`4^ZF$l{ZsDQ^|v_5`wKS|4moRlY6%eLdsJL$MW
z5OVjRsHzE?+{YQz6gICmT`j5#YTPk071W711~fuR<)F@9Zb4meVfoVSr)l;aV>4+~
zef+ftmHO?t;O*V5xXZv?z9;@{UHWXT68h`?ad^o$V3@Xyn$gP4@R#>*w(e)Y=SU+m
zS+~fdsPr+4rAz>*+2MmoFUqsufMxl_s`iQGalq8;$1Kzt=RocV0tp=CR=M{&+UC>=p}+
zUVSJ|FX?8Z<7!kdS*)ULwqA^$4%?G9Tlyn#Wy`0x@T6<(Z-MN
z+!5dtHV+xmqleBI8A`;jf@022VR^_B#ZM8KSBkOg3Zo!ZAzA!T1L^tY+kKeXTT(UdbL|IDe$|9%_oeex0RA@A7`z*J5Wn+8Ba3jIQ+@faL%+*+
zjcnHeFC5o5Hf1F=LQgoRkbczw^~OdvXX2j=}C#6AxA0ZGOK}R6HXt!N$;q
zSBvb3v0sV=h9fgsE`kDim~xxp7bvL7N|%(xtfGXy#B8SuMLbUM~;QE{Tf>XVQhg
z<}Nc9i}BGT*-@*j15hnh9Y*=+kHX$A4>DbO^&P_?n`qhhaGr(m@Uor(#ZMfm_?cz=*#u~|x3
zxq!vhpE3Y<-utYUNQWkauuFm>E4rT_yL(N>l+o~fuc409eDRg-tccmE(rJo#b4v}x)PL?Xk
ziN9XtiY7jj=(|7TvUD8XPW7U9svhrVBdYiAg4o~Ab73$!5sEEH=}^Y=M5I~_-_35D
z4}K~9u9H!FbHSycqRMS4NtmB^G6xw_2ElpxO*FP)U{EtEYg;0Fy?9TaR9RB2tDh6WTN>LxYc36=vH-gk+h$
zkSp}RW$xsw`&q*2aFynBoFT>JpXnVP3*aS5bA`@la19nSNxC|(4ugfs@=9IUde*QQ
zsxB9l1~c@QZkL5e6qOtPRZD>~C_yOdOU8a=S-Dl91f1V#V?PfSK>2<64Cg5zGlY|s
zG!GM1+Mj^rsc(c1!Q!XBzWBEzPhXqr_e*+|u!?PKEf)6_mz{>7Q}Q%vL=BU%n&t;)
zEDURMfIuw{(N|PqF7($N4@_;3(S!FEXOm^H?Vgzztt5;a+0YkKaX&B~VxosP!SVc(
z-Cv>CwHQWL}ev
zBA!TWw&3wkh7|fDxq}T#JxQ>)Z>5;pz84EH9Oc!@1XdGUgS$bNsh37py@tdx6Q?B+LFFgSVRM&
zQnShMpiE`Iaymb+NIK3f)!v*PSAzNKZ`62oSNJ)Z1PS>-eQS+hxoC90V=>-6Pb(We
z_Z!At*PCoyY2Ktr2G1Yts?I+lQrzne68bX2@5r1$UD3_a}j2(~ae;wK2Xrvaf*a&vdO>a-keV;4SWtB}QTLSN7
z<(GND%W~hL?0WS<`!%F9oR}ObI%E9?l&~g#HKCdv^XceIXGk
zFK_?D>&^&}C&4gvLG{j?)1Ivl!PrpWpkM4xeI=|l@hj5SN2>HXMJP`6;~`)U#_a`;y)r(VHx^ajVjN1(k&xayHluC+3SX+0IN$XECJiR~V7;^2P#X
zd1T#eK>@g)$20llxU0>{8ddRJuBN8CAi#MLF%!B^l%D3Hz*$n|^&6PgT9?owY{a%6
z@``jn0m5x=8FU#M8Hn&TJ)8hwS-aNhc=1R1Jz1Hse6eUc8qE&BI%qbqZ<%6+!_zsrrq?g~qZL`rq4Ix!6S}?z
zh|Wkvj&Hw6RNJlk`B#i<4N+Hji>AYEj9UE-m~R;BxOp7+V)l^zyv;f0((Z{gBd-Jj+s@&w}V>t1Z&jl!i=lRi^vZ#xNx<{eDq9Sq-5t`a@hjruz-w@p3CE
z6qG&qZ>+mRI+IPgZs0Q5fz?_otY}fNCXOz1^`99%{Bnp5HPsBDMwj8aZ%%HF`2OvDvjy0;f(89Sffd-<;hV+95p-BveZ{yA8^y$H=xP74wO)(48RDiGb`OT(DVh$amdqRw(X$etfPtE-^(r5$Rfs`
z=F9BquTj`LaNrcww7DHMe)kC|jErY@a~-V{DIA%N*o(6TBEMFftp<}w%Tt&f(GKw7
zH5HL)+gypsn|a8OTnvx5=L*GsNFodt)w&!Q0$uY2!pEsv-m1c2j=98LaKqyKc32_F
zK=f_C{kLTRlC43vmvPwcs{W&0BY_KR*$}>M)HK9|*mwpwpQ+wnD=jhX#}VeJ3n(pj
z)`oLkwxnkUtzkH9Vg_1)e1kiVp0qW1E7F-rWy*d?XRRgkW$}Ev>_rO19@-i?#9d3}
z^o9Cmys+IE2aa!yZz(wy1S{SUD@(p*e15LRJYE7>MLP0^rLPaE96pjOvQ0zwtJ>@-4mr!*9hRGE|vuHxPB|
zYkeGfwUX(-&x}bo=Bm>?zY9)b=P9TvZF-$}zvJOgb3d_D->U9OPLULQv#q)w4=(BX
z0_QhddEdv37q<+Wfn2W%puF*M67t4d2CQ9(kb@z%vt80Sf_c(#u>9@B!$Ax>0=i{)
zH_FP(g5(KUOqak$@BM1
zl{wG~fMTosR#A@qMQ_PYFbX10F^T~O4=N8g%qCaNc9tO45P=Rd
z?XI@D1y0n#y5aq0e;XvcR~s$C(h|*?Occ%#3z?`GBt}VCZNz1xWu3TNkuTDib6a81!te~eY17BKmPtUDk`|$e9OL_#lEU5Yza)6hk1p+9NrD$
zQ)qDh!8(IaBIs9w23Bjtj*^&@i=>RBzK6_y_EqbOcdeqVh5j`?QF+1^h2U}3=_(>G
zfG|UTii}zj8UP^zOc@gqP&kK>&*l~J$}#+2NRbzCuPf+>0(x@r=-GXh(aHve5gr=K
zxkry=lgd$lIAQm=YFW*5jufoT7|840HgX##Mb{cRQ9)ya=gkE1-SPSAtXe51kywgq
zLj7dxfLqsR$GB`s!sMU~^;~jGWb5t~F+D?EL4k0pL;Kbd4gnoIWWy$2vaC9p&d_y8
z_uJ=JJ=0Kio#Bk)Vzri>ivi+{^QnBfkB}y-*%g=?c{vv{mgdnm7t@
zXj3lnt@H*`egC^02qihlU_H%E2yxKko2Q=@`Qc1KQ{w>RH
zZ7wI0($dV%7K=rC)t^?n^|9ZtGR0jsc>_$?Mk&MQWlN=2rOb0#VI7YT?yCyP5;%%!2vE#|Bva%^x3mrFZVa$0-^VVjb@{*!+8iVOrQzzy-~`JkK|asmG`F@MKmiG&P?oXf>X9DZ0teO@V6
zkcg~T*WZ@P-MnY>#XMe~_NLvkak@gaE5{r3HhCJvXE2kAM~u#{5%3_=pSy?VG|$IM
zbXcol$yuH$s1e;Jw_wrrmU0aCH!Q|n3L*Uhfjr?2JY4Jv-D;coHl`p{Hp+vf92Ltg
zkR06+l0mn_foo@d#Z)PKyT=}%93)L8Qb&upHJA1dKIMN3=+etDyj<^zVWVT2wiS$QY_16Vg8r_<}{b!?F9S9oT
zS5#eB#8{y46v?uVo;W^dSfa6wrN!+Tp~-q{D0*B?N%fSFqwyP$=i`8IjF0zg?0L!H
zoE-0<>v)&20*~?
z=@xApNvTYa?Y%q65C_69bo)XlZgjpr5x$0+WN?0pc1(jN2BT|)$IGp0xpLJ}IP{IILc2^iNWm@2~@Z;6=
zfW~1j`hBjskC#s~PCG{V^ih0wNahy=$lnSLkLTffsr*|r!G*e-7;!`K;jstCY6;ND8D$CpSx*5IO|PFD#`nq
z11TWK2h_0Ub!I`dPl~20#h40GBWVf;U9)JR;M^$0MXokd<5rJPA0%gq1#V1bu%VA=
z+TXN7ESbjCG+O>@v$4kE}=Y6$ux0
zPO39wBa^QW4tNebjMFy%G@arG4G%U!#Pp%Z>z1R3jVqVtc}C|WgblR9rC6p^sofSA
zC!*p_(gl8@=|XRJ@5Qrs_9sWWc0-azi+TOe@A!3=AEF64P+Tsjh0NPC3U21
z$ixKM_RZfiDYi5=+ulBSGcXy7}E;ApD%#j)UrGRheQw1V;_7ATa_N1WDl8rnTFlQG*(X1q~I-
zE=dUKWrV1V{(k7`I>fD-j2f66DCq937V555Sf93+-Cq)_)oaw%5>8f;$Ft(`MNy?n
z?QoZhY;SXQr3yTWY0hO#W<3z>j#4R4y}l9^sN)xV};r&Pw0Gn2GPJ-fPLE
zmtWI@K`sj#PVj9IHm_L(#`LQnnsCNi?tO&qJs|35ZW>G3lH9?VR&}2HnaR5Elkj7q
zG2?#T_{rQJ4Ouk4+>XD{KkuJx$tPLw)X#gERp;{QaQwx(_p|Dg^+vEs&ZYrmyLy{R
zDwnHO32AMb(FH{grux_IOsHQos6(gGR3|ZnFD4*XM(*fK}RfaI6qVe5({2Di7%@^hjT}Nt@8p
zgy{i`bzk0q7ozngMft}JL+t1>g)r40^mTt(*!_wzRLKHiupp`6Ki8An;I&z=b!
z3c7yn38ruEB3rtMuN#G9`~FR!zkxA?-$+J8H#$1{xO6upn$bNm(%9@Z{r)_iZnD-O
zs2({I0Wa&HDqPMn&b5yahZ>Cw;bE?k(aFKH*rv@Cc@Z$amv(s!Hy&MPntO|-Z1N&$
z(h*63Icekf}p?_Bj6s!jx$1#6p}Ek9TG~2%*vqE#kkmoa1VXRS~r_+jWxBh
z!E7<(tg1Cwr``4Tp*dnXJC$+)vWgL%mdlllw2*9V(KcVH{SnBCjhqeO=4>>Reb^UH
z^6HMv=+<%>798gi}uJs!oZn3(Do-_bL|#)`O1?mJ`^cAFUCmT4_WtKJ&y@
z^BOcn&&(>I4lMZV4UJcePcIDs{XKk6qB9T*fLH^)H<408A>kJ0et!36_v*Dw6zFb3
z5+w2x2Xnk$O3c7u@6~O^PzuEwtjnHKIIecLSC)CJiUgh^XEdvlh*jEHn|6USF-6SE
z8T>q8W>KtK%-rl(5$U98!PT-~NB#VCt>x}^DqY0)1Rb^z@4hp5Vztun2mXsIIa8~7
z+W}o_8n*KxhfZo%f@`p1a!c!;#opiVcmASM5K8s>}i|!947zKaihYMv+XoUzi)Qn@pDf8W^2R?{zyK=7ha%
zU=ysyksLfL70C|mxiEcgxGuNzMb*=v%jG9UQBtrEv^`iFFLvv0d}FTKO0ezz3kcZCeq=`8qDbxVt3uL(I4kQZ))Ew(kKelvO1ZXacf4GF9;*#9c5SA<#iU-!5jExj&${-Za^yOwf`QV_j
zm3n*XatVoMqs{2)!&bfPk_nHN$!v|5wW39pX*9FNLK(f#`8imsVx4WRdRS;U30-e5
z%aXR#(x1-M_idCey)I?A5(7i-i?s&BwW^Kt)pm5#9%?aqnezJvZ4m&eTCs-AM+Dp(K27jgP_G`Gx_Xruj2}E)4LR@L62Lm`J4OJn&CMGL6h+X)XZ>g
zv^5yrA0u|KJy{oVb3%0M4IVt6V)o?7RzSWyRBt?rX!Sv7+}{Mxl=tZLQSoAQVccF3
zrrXY=;XI`{<6E;(B+2|v7q%Ck7!
z-HlMWIP9fhA1@qPiEeoPC=43pzW)2W!FDN}njSy6ec<;34K3)6+q+@_{S++&gTGLS
zgYlcRyPVcV93(O>Adsj?(=e{W0R72n%>_5*c4V$XyD_%?GE;iUA4hb{qn>#=+J#E4VX0WV>kZppV`cIHIDuCnVH&ruy`DQjNi^tK#J)sx-cM+!q>w`<*{4)su#M^nAI`@8DomH?^bE
zsqgG;n?Ex;Pu@~K7HO<%XPhoJfSj5*iLLCZCu+5^+1zi=r;f+`aD3ihVCif&aYe<}
zYfbqslO7eCjcJu@Y!X4jUfM7&mLy?NrT1LLd{441>CYtJ1#H7e@QY;ZVo?6qM9zizL8ekO
zZZNJR38bnCI7N6o0gcdo#_Ag^x6;IP(*p8Uxz%$C@&mcV;@+r$CbS{Qdm6Zpol|cZr?obnhpFIH*wxd}Bm`98H%)
zQ$e(|B!d+hIqTE~&Uu9_99X(^NdC}EKY_9%A(504;>=TjA2}d~qDoj)fs2r7D!eMx?8SuM)v7eq^m~LaErx7qY4;1A`hTKEKV1e
zM)*rjVT7Z%N&M+nayApYf#pxXJ$&{s;x|LDeIx|#htq+p$T@U(UeDB0v+-9F+z+oK
zt97j!%FoVBQxZASGH03`x*