mirror of
https://github.com/wassname/jsteer.git
synced 2026-09-10 15:30:53 +08:00
notebook: word steering hello-world (executed on real 0.6B cache)
C sweep shows coherence/strength tradeoff (C=1 fluent+happy, C=8 spam), greedy+sampled at C=1, negative steering, lens_topk layer progression (city slot -> candidates -> Paris), Vector save/load. Adds nbconvert to notebooks extra; ignores notebook-produced safetensors. Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -5,3 +5,4 @@ artifacts/*.jac
|
||||
artifacts/*.ckpt
|
||||
uv.lock
|
||||
docs/reviews/*.raw.jsonl
|
||||
artifacts/*.safetensors
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5ef6f624",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# jsteer hello-world: word steering\n",
|
||||
"\n",
|
||||
"Fit the model's full Jacobian once (`scripts/fit_qwen06b.py`, cached to\n",
|
||||
"`artifacts/qwen3-0.6b.jac`), then any word vector is an instant CPU matvec:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"v_l = unit( J_l^T @ w )\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"where `w` is the mean unembedding row of the words you want more (or less) of.\n",
|
||||
"This is the verified extraction method (see the README evidence section).\n",
|
||||
"Runtime is steering-lite: `with v(model, C=...): model.generate(...)`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "46973b3d",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:08:49.171239Z",
|
||||
"iopub.status.busy": "2026-07-10T05:08:49.171143Z",
|
||||
"iopub.status.idle": "2026-07-10T05:08:54.432779Z",
|
||||
"shell.execute_reply": "2026-07-10T05:08:54.432236Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/media/wassname/SGIronWolf/projects5/2026/jspace/jsteer/.venv/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
||||
" from .autonotebook import tqdm as notebook_tqdm\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\r",
|
||||
"Loading weights: 0%| | 0/311 [00:00<?, ?it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\r",
|
||||
"Loading weights: 100%|██████████| 311/311 [00:00<00:00, 12560.70it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# demo notebook authored by Claude\n",
|
||||
"import torch\n",
|
||||
"from transformers import AutoModelForCausalLM, AutoTokenizer\n",
|
||||
"\n",
|
||||
"from jsteer import Jacobian\n",
|
||||
"\n",
|
||||
"MODEL = \"Qwen/Qwen3-0.6B\"\n",
|
||||
"tok = AutoTokenizer.from_pretrained(MODEL)\n",
|
||||
"model = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.bfloat16).to(\"cuda\").eval()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "83478eed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load the cached Jacobian\n",
|
||||
"\n",
|
||||
"The expensive step (1 forward + 128 backwards per prompt) already happened in\n",
|
||||
"`scripts/fit_qwen06b.py`. Here we only load the cache. SHOULD: repr shows\n",
|
||||
"d_model=1024, n_prompts=64, source_layers=[8..24]."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "19973ad5",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:08:54.434508Z",
|
||||
"iopub.status.busy": "2026-07-10T05:08:54.434278Z",
|
||||
"iopub.status.idle": "2026-07-10T05:08:54.467291Z",
|
||||
"shell.execute_reply": "2026-07-10T05:08:54.466782Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Jacobian(JacobianLens(d_model=1024, n_prompts=64, source_layers=[8..24] (17 layers)))"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"CACHE = Path(\"../artifacts/qwen3-0.6b.jac\")\n",
|
||||
"if not CACHE.exists():\n",
|
||||
" raise FileNotFoundError(\"artifacts/qwen3-0.6b.jac missing -- run scripts/fit_qwen06b.py first\")\n",
|
||||
"jac = Jacobian.load(str(CACHE))\n",
|
||||
"jac"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bbf58ddf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Extract a word vector\n",
|
||||
"\n",
|
||||
"`word_vector` pulls the words' unembedding direction back through the cached\n",
|
||||
"Jacobian: no gradients, no forward passes, just a matvec per layer.\n",
|
||||
"+C makes the model say these words more, -C less."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "59ba3763",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:08:54.468873Z",
|
||||
"iopub.status.busy": "2026-07-10T05:08:54.468739Z",
|
||||
"iopub.status.idle": "2026-07-10T05:08:54.522961Z",
|
||||
"shell.execute_reply": "2026-07-10T05:08:54.522508Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001b[32m2026-07-10 13:08:54.515\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mjsteer.jacobian\u001b[0m:\u001b[36m_word_cotangent\u001b[0m:\u001b[36m100\u001b[0m - \u001b[1mword cotangent: ['happy', 'joy'] -> first-subtoken ids=[56521, 4123] |w|=0.744\u001b[0m\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001b[32m2026-07-10 13:08:54.520\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mjsteer.jacobian\u001b[0m:\u001b[36mpullback\u001b[0m:\u001b[36m189\u001b[0m - \u001b[1mjacobian_word per-layer |J^T w| (pre-norm): 8:1.72 9:1.76 10:1.66 11:1.51 12:1.54 13:1.61 14:1.67 15:1.59 16:1.47 17:1.33 18:1.21 19:1.16 20:1.11 21:1.06 22:1.03 23:0.958 24:0.911\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"v = jac.word_vector(model, tok, [\"happy\", \"joy\"])\n",
|
||||
"\n",
|
||||
"def gen(vec, prompt, C, do_sample=False, max_new_tokens=40, seed=0):\n",
|
||||
" enc = tok(prompt, return_tensors=\"pt\").to(model.device)\n",
|
||||
" torch.manual_seed(seed)\n",
|
||||
" with vec(model, C=C):\n",
|
||||
" out = model.generate(**enc, max_new_tokens=max_new_tokens, do_sample=do_sample,\n",
|
||||
" temperature=0.7 if do_sample else None,\n",
|
||||
" top_p=0.95 if do_sample else None,\n",
|
||||
" pad_token_id=tok.eos_token_id)\n",
|
||||
" return tok.decode(out[0][enc.input_ids.shape[1]:], skip_special_tokens=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0717a9b9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Pick a coefficient: the coherence/strength tradeoff\n",
|
||||
"\n",
|
||||
"The raw coefficient is model-dependent. On this 0.6B model a large C\n",
|
||||
"(like 8) overwhelms the residual stream and the output degenerates into\n",
|
||||
"literal \"joyjoyjoy...\" spam; the interesting regime is small C where the tone\n",
|
||||
"moves but the text stays fluent.\n",
|
||||
"\n",
|
||||
"SHOULD: C=0 is neutral; C=1-2 is coherent and noticeably happier; C=4-8\n",
|
||||
"degenerates into token spam. ELSE steering wiring or sign issue."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "c271f279",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:08:54.524111Z",
|
||||
"iopub.status.busy": "2026-07-10T05:08:54.524003Z",
|
||||
"iopub.status.idle": "2026-07-10T05:08:59.448271Z",
|
||||
"shell.execute_reply": "2026-07-10T05:08:59.447798Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n",
|
||||
"| C | greedy generation |\n",
|
||||
"+=====+============================================================================================+\n",
|
||||
"| 0 | I saw a lot of people. I saw a lot of people, and I saw a lot of people again. I saw a |\n",
|
||||
"| | lot of people again. I saw a lot of people again. I |\n",
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n",
|
||||
"| 1 | I saw a lot of people there. I was happy with the food and the service. I think it's a |\n",
|
||||
"| | good place to visit. I would like to go there again. I think it's |\n",
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n",
|
||||
"| 2 | I was happy. I have a good friend, and I love my life. I love the music, the food, and |\n",
|
||||
"| | the games. I have a good time. I am happy and happy. |\n",
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n",
|
||||
"| 4 | I lovejoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjo |\n",
|
||||
"| | yjoyjoyjoyjoyjoyjoyjoyjoyjoyjoy |\n",
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n",
|
||||
"| 8 | joyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoy |\n",
|
||||
"| | joyjoyjoyjoyjoyjoyjoyjoyjoyjoy |\n",
|
||||
"+-----+--------------------------------------------------------------------------------------------+\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from tabulate import tabulate\n",
|
||||
"\n",
|
||||
"PROMPT = \"I went to the park today and\"\n",
|
||||
"rows = [(C, gen(v, PROMPT, C)) for C in (0, 1, 2, 4, 8)]\n",
|
||||
"print(tabulate(rows, headers=[\"C\", \"greedy generation\"], tablefmt=\"grid\", maxcolwidths=[None, 90]))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c94e063e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Steer at the chosen C\n",
|
||||
"\n",
|
||||
"C=1 keeps the model fluent while visibly moving the tone. Greedy and sampled\n",
|
||||
"generations on three different neutral prompts."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "76b1963a",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:08:59.449642Z",
|
||||
"iopub.status.busy": "2026-07-10T05:08:59.449534Z",
|
||||
"iopub.status.idle": "2026-07-10T05:09:07.002010Z",
|
||||
"shell.execute_reply": "2026-07-10T05:09:07.001355Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--- 'I went to the park today and'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 greedy : \" I saw a lot of people there. I was happy with the food and the service. I think it's a good place to visit. I would like to go there again. I think it's\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 sampled: ' I wanted to make some new friends. I saw a cat, and I felt very happy and happy. I wanted to buy a new pair of shoes. I got a new pair of shoes and felt'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=0 greedy : ' I saw a lot of people. I saw a lot of people, and I saw a lot of people again. I saw a lot of people again. I saw a lot of people again. I'\n",
|
||||
"--- 'The meeting this afternoon was'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 greedy : ' a success. The meeting was a success because the meeting was a success. The meeting was a success because the meeting was a success. The meeting was a success because the meeting was a success. The'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 sampled: ' a success. The meeting is very important to me, so I want to share it with you and to let you know that I am very happy. I am happy to have the meeting. What is'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=0 greedy : ' held in the library. The meeting was held in the library. The meeting was held in the library. The meeting was held in the library. The meeting was held in the library. The meeting was'\n",
|
||||
"--- 'My overall impression of the new apartment is that'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 greedy : \" it's a very cozy and warm place. I love the fact that it has a lot of different activities and things to do. I think it's a great place to relax and enjoy the day.\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=+1 sampled: \" it's a beautiful place to live in, but I can't help but be a bit nervous and confused about the details. The first time I came to the apartment, I was really excited and happy\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=0 greedy : \" it's a very modern and clean apartment. The apartment has a large amount of natural light, which makes the living room feel very open and spacious. The kitchen is also very modern and clean, with\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"C = 1\n",
|
||||
"PROMPTS = [\n",
|
||||
" \"I went to the park today and\",\n",
|
||||
" \"The meeting this afternoon was\",\n",
|
||||
" \"My overall impression of the new apartment is that\",\n",
|
||||
"]\n",
|
||||
"for p in PROMPTS:\n",
|
||||
" print(f\"--- {p!r}\")\n",
|
||||
" print(f\" C=+{C} greedy : {gen(v, p, C)!r}\")\n",
|
||||
" print(f\" C=+{C} sampled: {gen(v, p, C, do_sample=True)!r}\")\n",
|
||||
" print(f\" C=0 greedy : {gen(v, p, 0)!r}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7e3263dc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Negative steering\n",
|
||||
"\n",
|
||||
"The same vector with a negative coefficient suppresses the concept.\n",
|
||||
"SHOULD: less positive affect than C=0, still english (strongly negative C\n",
|
||||
"degenerates the same way strongly positive does)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "334806b9",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:09:07.003255Z",
|
||||
"iopub.status.busy": "2026-07-10T05:09:07.003131Z",
|
||||
"iopub.status.idle": "2026-07-10T05:09:09.721665Z",
|
||||
"shell.execute_reply": "2026-07-10T05:09:09.721168Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--- 'I went to the park today and'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=-2 greedy : ' saw a large amount of wildlife. I noticed that the water surface was covered with sediment from the river. I used a remote sensing system to measure the sediment thickness. What is the sediment thickness in the'\n",
|
||||
"--- 'The meeting this afternoon was'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=-2 greedy : ' held in the ______. The ______ was used for the meeting. The ______ was used for the meeting. The ______ was used for the meeting. \\n\\nFill in the blanks.\\n\\nThe ______ was used for'\n",
|
||||
"--- 'My overall impression of the new apartment is that'\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" C=-2 greedy : ' it is too dark. The windows are too dark. The walls are too dark. The ceiling is too dark. The insulation is too thick. The air quality is too poor. The thermal mass is'\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for p in PROMPTS:\n",
|
||||
" print(f\"--- {p!r}\")\n",
|
||||
" print(f\" C=-2 greedy : {gen(v, p, -2)!r}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e9cc185d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bonus: lens readout\n",
|
||||
"\n",
|
||||
"Only the full-Jacobian cache gives you this: transport any layer's residual to\n",
|
||||
"the final basis with `J_l` and decode it, i.e. \"what does the model think at\n",
|
||||
"layer l\". SHOULD: at layer 16 the model has only a city-shaped slot, by layer\n",
|
||||
"20 candidate cities appear, and by layer 24 Paris has won. ELSE layer indexing\n",
|
||||
"is off."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "3009a178",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:09:09.723009Z",
|
||||
"iopub.status.busy": "2026-07-10T05:09:09.722890Z",
|
||||
"iopub.status.idle": "2026-07-10T05:09:09.819979Z",
|
||||
"shell.execute_reply": "2026-07-10T05:09:09.819411Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"layer 16: [' town', ' city', ' cities', '_city', ' City', ' Cities']\n",
|
||||
"layer 20: [' Paris', ' London', ' Venice', ' France', ' Berlin', ' Vienna']\n",
|
||||
"layer 24: [' Paris', 'Paris', ' cities', ' Cities', '巴黎', ' City']\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for layer in (16, 20, 24):\n",
|
||||
" top = jac.lens_topk(model, tok, \"The Eiffel Tower is located in the city of\", layer=layer, k=6)\n",
|
||||
" print(f\"layer {layer}: {[t for t, _ in top]}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1624b7a8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Extract once, steer forever\n",
|
||||
"\n",
|
||||
"The steering vector is a plain steering-lite `Vector` (safetensors on disk),\n",
|
||||
"so you can save it and reuse it without the Jacobian cache or jsteer at all."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "c3f5e64b",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-07-10T05:09:09.821195Z",
|
||||
"iopub.status.busy": "2026-07-10T05:09:09.821068Z",
|
||||
"iopub.status.idle": "2026-07-10T05:09:10.752287Z",
|
||||
"shell.execute_reply": "2026-07-10T05:09:10.751806Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" I saw a lot of people there. I was happy with the food and the service. I think it's a good place to visit. I would like to go there again. I think it's\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from steering_lite import Vector\n",
|
||||
"\n",
|
||||
"v.save(\"../artifacts/happy_joy.safetensors\")\n",
|
||||
"v2 = Vector.load(\"../artifacts/happy_joy.safetensors\")\n",
|
||||
"print(gen(v2, \"I went to the park today and\", C=1))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
notebooks = ["matplotlib>=3.8", "ipykernel", "tabulate"]
|
||||
notebooks = ["matplotlib>=3.8", "ipykernel", "tabulate", "nbformat", "nbconvert"]
|
||||
test = ["pytest"]
|
||||
|
||||
[tool.uv.sources]
|
||||
|
||||
Reference in New Issue
Block a user