anima-style-lora-workflow-bundle
Manifest validDeepSeek Harness Plugin Package: An anime-style LoRA dataset full-pipeline engine, covering screenshot capture/download → deduplication filtering → text detection and patching → rearrangement and numbering → labeling and label cleaning → training configuration. Ships with four tools—anima_status/anima_stage/anima_job/anima_doctor—Agent presets, and a graphical settings page, plus a standalone 15-stage Python CLI that can run independently of DSH.
dsh-anima-style-lora
DSH 插件 + skill + agent 预设:Anima 风格 LoRA 全流程(下载 → 导入 → 去重筛选 → 文字检测修补 → 重排编号 → 打标洗标 → 训练配置三件套)。
设计分工:重活是宿主插件(确定性 Python 工具箱),判断是 skill。
训练本身不在本流水线范围内(用 Anima-Standalone-Trainer 自己跑)。
装了什么
| 组件 | 作用 |
|---|---|
宿主插件 dsh-anima-style-lora | 4 个工具:anima_status / anima_stage / anima_job / anima_doctor,插在插件树根层级,对所有 agent 可见 |
agent 预设 anima-style-lora(显示名「Anima风格LoRA全流程」) | 带专属 persona 的 agent 组合;skill-filesystem 通过 customSkillDirs 挂载本包的 skills/ |
| 3 个 skill | anima-style-lora-pipeline(总纲)/ -source(选源下载)/ -curate(筛选 + 文字修补 + 视觉复核) |
设置页(浏览器半侧 + 宿主路由 /anima-lora/api) | 在 DSH 设置里直接改全部配置项(含凭据),写进 <home>/.animasl/animasl.config.json,下次调用即生效 |
Python 工具箱 python/animasl/ | 17 个阶段的确定性实现,可脱离 DSH 直接用 CLI 跑:python -m animasl.cli --home E:/LoRA_Train <stage> |
安装
安装走 GitHub(与其他自研插件一致),在 DSH 里执行:
# 在 DSH 里(需要 danger-full-access 或批准)
dsh plugin --profile desktop add github:Nana7mi0721/anima-style-lora-workflow-bundle
也可以用桌面端 设置 → 插件 的图形界面安装。安装后新建一个 agent,预设选「Anima风格LoRA全流程」。
源码工作区在 E:\Study\github program\for dsh\anima style lora workflow bundle,
只用于修改源码;改动后 commit + push 到 GitHub,再用
dsh plugin --profile desktop update dsh-anima-style-lora 更新安装副本。
详见 E:\Study\github program\for dsh\AGENTS.md。
⚠️ 装完必须重启 DSH
DSH 只在启动时建立模块解析表(asar 里的 installRuntimeInterception / ResolutionRouter:
if (parent === void 0 || !router.hasInterceptionLayerForUrl(parent)) return native(...))。
运行期新装的 bundle 不在表里 → 它的 import '@deepseek-ai/dsh-tools' 退回原生解析 →
ERR_MODULE_NOT_FOUND → loader 只报一句 failed to import、cordis fiber 从未创建。
已实测:一个只有 6 行的最小对照 bundle 也是同样的报错,所以这不是本包的问题。
dsh: warning: 1 entry did not activate
dsh-anima-style-lora (dsh-anima-style-lora): failed to import
看到上面这句就重启桌面端;重启后 anima_status / anima_stage / anima_job / anima_doctor
四个工具与预设「Anima风格LoRA全流程」才会出现。更新已装 bundle 的 JS 之后同理要重启
(模块代际缓存在 boot 期)。
本地自测(不需要 DSH)
cd "E:/Study/github program/for dsh/anima style lora workflow bundle"
npm test # 契约 + 四个工具真跑一遍(只读/干跑)
npm run test:live # 额外跑 text 干跑(慢,要 torch)
npm run test:client # 设置页浏览器半侧:假 React 渲染 + 断言发出的请求
npm run test:route # 设置页宿主路由:真起 http server + 真 Python CLI(含信任栅栏)
npm run test:route:doctor # 上面那条再加一次真 doctor(慢约 1 分钟)
npm run test:py # 离线:Cloudflare 挑战识别 + curl 参数 + cookie 域隔离 + 凭据优先级
npm run test:params # 离线:参数矩阵(哪些阶段不吃 apply)+ 两个工具的返回 schema 一致 + 图桶扫描
npm run test:pipeline # 离线端到端:init→…→verify→dict-check→makecfg(临时数据集,跑完自删)
npm run test:all # 上面全部(check:patch + params + client + route + py + pipeline + smoke)
npm run test:py / test:pipeline 需要带 requests / Pillow 的解释器(本机是
E:/LoRA_Train/.animasl/venv/Scripts/python.exe,PATH 上的 python 缺依赖时会打印
SKIP 并返回 0)。
test/pipeline-offline.py 是唯一一条把 17 个阶段串起来跑的回归:在 <home>/datasets
下建 _pipeoff_* 临时现场(_ 前缀,anima_status 看不见),用真 CLI 走
init → import(含"跳过非图"与"拒绝 src=数据集根")→ rename → thumbs(重排之后仍出图)
→ 第二批 import + rename(验证对照表只追加)→ 图桶布局(clean/ + watermark/ + 硬链接副本
latest/:桶自动发现、caption 就地重写、makecfg 去重)→ wash(触发词置首、并入图源标签、
人工删掉的标签不复活、refreshSource 才整体重洗、A 源体检告警、wash_diff.csv 快照差异)
→ exclude(淘汰 / undo 回原桶 / missing 补账)→ fix-caption 三种用法 →
verify → dict-check → makecfg(LR 超区间报错、allowOutOfBand 放行、preflight.txt 的
差异段),共 66 条断言,跑完删现场(--keep 保留)。它抓到过:import/fetch 其实没有
apply、wash 会把标签规范成空格形、缩略图落在 _pipeline/thumbs 而不是数据集根、
exclude --undo 把图恢复到 images/ 而不是它原来的桶(user_deleted.json 当时没记 bucket)。
test/smoke.mjs 把插件装进一个假 ctx(test/stub/ 用 ESM loader 钩子把
@deepseek-ai/dsh-tools 指到替身),校验注册数量、返回值无 undefined、
返回值满足自己声明的 output.schema、render() 有非空正文、disposer 能卸干净。
它抓到过三个真 bug:require() 用在 ESM 里导致报告行数恒为 0、stageOf() 认不出
无 --dataset 的 doctor、collect() 把报告数组塞进声明为 string 的字段。
test/params.mjs 就是为最后一个类目补的闸门:它断言 anima_stage 与 anima_job
共用同一份返回 schema(曾经 anima_job 少声明 5 个键,宿主直接判
"value.command" is not a declared property,导致 detach 的后台结果永远取不回来)。
test/client-smoke.mjs 用一个极简 React 替身(本机没有 react,它由 web shell
的平台模块表提供)把设置页真的渲染出来,再模拟输入与点击,断言发出去的 HTTP
请求(只写被编辑的键、保存后重读、体检输出上屏)。test/settings-route.mjs
用真的 http server 加真的 Python CLI 跑通 read/write/probe/doctor,并逐条
验证信任栅栏(非 loopback Host、跨站 Origin、sec-fetch-site: cross-site、
超长 body 413)。
设置页(图形界面改配置)
设置 → 左侧导航「Anima 风格 LoRA」。它不是另存一份配置,而是直接读写
<home>/.animasl/animasl.config.json(运行时层),下一次 anima_* 调用即生效,
不用重启:
- 分组:
paths/runtime/dict/creds/net/screen/dedup/wash/hygiene(空间与备份),每个键都标出来源(bundle/defaults/ 本机覆盖), 改动过的键可以一键「恢复默认」(= 从 runtime 层删掉这个键,回落到 bundle/内置默认)。 creds是凭据分组:danbooru 用户名 + API key、exhentai 三项 (ipb_member_id/ipb_pass_hash/igneous)、curl 兜底 UA。密钥类字段是 密码框,旁边有「显示」开关。它们只落进你本机的运行时配置(不在仓库里), 与cookies.txt二选一即可 —— 优先级是 设置页 > 环境变量 > cookies.txt。- 只读两行:
home(由 profile 的cordis.patch.yml行配置决定)与运行时目录。 路径类字段旁边有「存在 / 不存在」标记,点「跑一次体检」能在页面里看anima_doctor的完整输出。 - 通路:宿主半侧
lib/settings.js在/anima-lora/api上注册 prefix 路由 (read/write/probe/doctor),浏览器半侧lib/client.js手写、 只 require 平台 seed 里的react,样式只用宿主的--dsw-*token, 所以明暗主题都跟着走。写盘是「临时文件 + rename」的原子替换。 - 为什么不用 DSH 自己的设置 RPC:它不给第三方命名空间提供配置读写
(
dsh-better-sidebar的源码注释也是这么写的),第三方插件必须自建路由。
skills 挂载路径
cordis.patch.yml 里预设的 skill-filesystem 行用 customSkillDirs 挂载本包的 skills/。
customSkillDirs 只认绝对路径(skill-filesystem 用 resolve(root) 解析,相对路径按
host 进程 CWD 展开),所以写的是 profile 安装副本的固定位置:
- name: "@deepseek-ai/dsh-skill-filesystem"
config: { customSkillDirs: ["C:/Users/REISEN/.dsh/profiles/desktop/node_modules/dsh-anima-style-lora/skills"] }
安装副本由 pnpm 管理,插件更新后该路径依然有效;源码工作区挪到哪都不影响。
若 DSH 主目录不是 %USERPROFILE%\.dsh,才需要改这一行。
配置
三级合并,后者覆盖前者:bundle 内 animasl.config.json → <runtime>/animasl.config.json → 环境变量 ANIMASL_*。
<runtime> 默认 <home>/.animasl(重资源如 ML venv 建议放这里,不要放包内)。
插件的 config(在 profile 的 cordis.patch.yml 里改):home / pythonDir / python / mlPython / runtimeDir / animaLoraDir / timeoutMs / longTimeoutMs。
十七个阶段
图桶:根下任何含图的子目录都是数据
images/ 只是默认落点,不是唯一落点。真实使用里出现过 clean/(109 张已修水印)+ watermark/(33 张未修)的布局,成品 caption 全在这两个目录里、images/ 是空的 —— 当时的工具只数 images/,于是 anima_status 报 raw=0 images=0 captions=0,verify/thumbs/fix-caption 对着 142 条成品 caption 视而不见(makecfg 还会把 images/ 与子目录里的同一张图算两遍,142 变成 284)。
现在:根下任何含图的子目录都自动算图桶(跳过 _/. 前缀与 00_raw/thumbs/masks/orig_text;空目录不算),images/ 排在最前。所有阶段默认扫全部桶,caption 与图同目录;只想处理某个子集就传 workSet:"clean,watermark",给错名字会报错并列出自动发现的桶。makecfg 另外按内容指纹跨桶去重(硬链接/副本只算一次),整桶都是副本时跳过并说明。
| stage | 工具参数要点 | 产出 |
|---|---|---|
init | trigger、kind、force、reset+yes | _pipeline/manifest.json |
fetch | source=yandere|danbooru|pawchive|exhentai、tags/creator/gallery、limit、cookies;dryRun 预演 | 00_raw/ + raw_posts.jsonl(含每帖 tags[]) |
import | src=<素材目录或压缩包>、move、noUnpack;dryRun 预演 | 解包/收编进 00_raw/ |
enrich | limit、force、noPixiv;apply 才写盘 | 按文件 md5(不中再用文件名里的 pixiv id)去 danbooru 反查原帖,把权威 tag_string 补进 raw_posts.jsonl(来源 A)+ enrich_report.csv(含 by 列:md5 / pixiv-source / pixiv-single) |
dedup | phashDistance(4)、ssim(0.995)、includeImages | dedup_report.csv,apply 时移入 _excluded/duplicates/ |
screen | minShortSide(512)、minBytes(102400)、earliest(2015-01-01) | screen_report.csv + review_queue.csv |
thumbs | maxSide(1536)、prune、includeImages | _pipeline/thumbs/(视觉子代理只能看这个) |
text | warnRatio(0.08)、dropRatio(0.30)、patchSmall、thresholds、dilate(6)、keepMasks | masks/(修补成功后清掉本次用过的)、text_report.csv;修补后自动把 wash 标 stale |
rename | start(1)、digits(4)、move | images/0001.ext + rename_map.csv(只追加)+ per_image.json |
wash | trigger、rules、minTags/maxTags、prefer(A|C)、dropMarks、refreshBooru(只忽略手删名单)、refreshSource(最狠:连手删名单都不看)、keepSnapshots、noImages、workSet | 每个图桶里的 NNNN.txt + wash_report.csv + review_todo.csv + wash_diff.csv(本次 vs 上次改了哪些)+ captions_prev/ 快照(留 1 代) |
review-list / apply-review | payload | 看图必答字段的往返 |
fix-caption | name(必填)、add/remove/set 三选一 | 只改点名的那一张 caption(写盘前留快照,写盘后出 wash_diff.csv) |
exclude | names、reason、note;undo 按账本搬回原桶、missing 事后补账、无参数列清单 | 图 + 同名 txt 移进 _excluded/<reason>/,并记进手删账本(重跑 wash 不复活) |
verify | online、sample、trigger、workSet | wash_verify.csv;§3 形态 + §9 内容闸门(BANNED 残留、否定式、质量词、人数一致性、冲突族、水印区与修补状态一致性、图-txt 配对、触发词位置) |
dict-check | 不给 tags 就查现有 caption(只按逗号切,多词标签不会被拆) | 终端输出:存在 / 别名可归一 / post_count=0(幻觉标签)/ 词典外 + 形近候选,各带"出现在几张图里" |
makecfg | kind(style/character/object/scene/clothing)、trigger、name、subdirs、dim、lr、epochs、resolution、allowOutOfBand、keepBackups | 四件套 + preflight.txt;自动发现图桶 + 跨桶按内容指纹去重;四件套内容没变就不重写、备份按「代」清理(留 1 代),preflight.txt 是报告、原地刷新不备份 |
写盘语义按阶段分三类(传了不支持的参数会在执行前报错,错误里列出该阶段支持的参数):
| 类别 | 阶段 | 规则 |
|---|---|---|
| 写盘是默认行为 | init、thumbs | 不吃 apply;init 靠 force/reset 控制 |
默认写盘、dryRun 反转 | fetch、import | 预演用 dryRun:true |
默认 dry-run、要 apply:true | enrich、dedup、screen、text、rename、wash、apply-review、fix-caption、exclude、makecfg | 不带就只出报告(enrich 的 dry-run 仍会发请求——它就是给你看命中率的) |
| 只读 | status、review-list、verify、dict-check | 没有 apply |
dry-run 不会把阶段标成 done:没写盘的那一跑在 manifest.json 里记成 preview,anima_status 显示 ▷,done=[…] 里也不会出现它——所以「报告看过了但还没落地」和「已经做完了」不会被混为一谈。anima_status 的状态行图例:✔ 已完成、▷ 只跑过 dry-run、↻ 上游改过需重跑、✘ 失败、· 未跑。它现在还会多打一行 caption 健康度(标签数 min/avg/max、缺 txt、孤立 txt、空 caption、低于 20 / 高于 45 的计数、触发词缺失或不在首位 + 最多 5 个例子),数据集用了 images/ 以外的桶时再多打一行 图桶(图桶: clean 109图/109caption、watermark 33图/33caption),最后一行是 占用体检(见下)。
重复 init 不会毁掉进度:数据集已存在时 init 只打印提示就返回;init --force 是就地更新触发词/类型(阶段记录全部保留),只有 init --reset --yes 才会丢掉阶段记录从零重建——--reset 不带 --yes 会先把「当前已完成:init, wash」列出来让你确认。类型(--kind)决定 wash 的整类规则,中途改类型是安全的:--force 改完从 wash 起重跑即可。
状态文件的所有权(谁写、谁读)
一次真实跑批(72 张,见下)的复盘结论:技术卡点都顺,成本全花在「谁覆盖谁」上。所以所有权写死:
| 文件 | 谁写 | 谁读 | 规矩 |
|---|---|---|---|
<桶>/<stem>.txt | wash / apply-review / fix-caption | 所有下游(verify/makecfg) | caption 的唯一权威副本,与图同目录(images/ 或 clean/…);要改走 fix-caption,别手改文件再重跑 wash |
_pipeline/per_image.json | rename / wash / apply-review / fix-caption / exclude | 引擎自己 | 只追加:old_names/merged_tags 取并集、history 留最近 30 次、bucket 记它在哪个桶;坏文件会改名 .json.broken 并从空状态继续 |
_pipeline/rename_map.csv | rename | wash(按 old_name 回查 booru 标签) | 只追加,多批次共存 + 每批快照 rename_map.<first>-<last>.csv;预演只写 rename_map.preview.csv |
raw_posts.jsonl | fetch / import / enrich | rename、wash(来源 A) | 只追加,按 (filename, post_id) 去重合并 |
_pipeline/user_deleted.json | exclude / fix-caption(remove) | wash(防复活)、verify | 人工删除名单:记 name/old_names/archived/bucket/reason/时间;restored_at 标记已恢复。手删了图就 exclude,别直接从磁盘删 —— 否则下一轮工具只会告诉你"少了几张" |
_pipeline/captions_prev/<时间戳>/ | wash / apply-review / fix-caption | 人 | 写盘前自动快照(默认只留最近 1 代、内容没变就不新建),_meta.json 记是谁触发的;配套 wash_diff.csv 是"本次 vs 上次"的逐张差异(新增/删除/清空) |
变更可观测:任何一次写 caption 的阶段都会先留快照、再写盘、最后出 wash_diff.csv,终端直接打印 本次改动 N 张(新增 x / 删除 y / 清空 z);没有差异就明说"与上一版没有差异"。所以"这次 wash 到底改了什么"不用靠 diff 工具或翻聊天记录。
wash 默认不复活人工删掉的图源标签(拿 per_image.json 的 merged_tags 与当前 caption 比对,差值即人工删除),确实要按图源整体重洗时用 refreshSource:true。这条就是「手改完 caption 一重跑全回来」那个坑的修法。
空间与冗余:哪些会累积、哪些是副本
一次跑批的产出分三类,报告类全部是覆盖写(同名文件原地更新,不会每次多一份):
| 类别 | 文件 | 会不会累积 |
|---|---|---|
| 账本 | per_image.json、rename_map.csv、raw_posts.jsonl、user_deleted.json、manifest.json | 单文件,只追加;per_image.json 的 history 每张图只留最近 30 次 |
| 报告 | *_report.csv、review_todo.csv、review_queue.csv、wash_diff.csv、preflight.txt | 覆盖写,每个阶段固定一份,行数随数据量、不随跑批次数 |
| 副本/备份 | _pipeline/thumbs/、_pipeline/orig_text/、_pipeline/masks/、_pipeline/captions_prev/、rename_map.<a>-<b>.csv、train_configs/<name>/*.bak<时分> | 有上限或可再生(见下) |
后三类里真正占空间的只有两个,都为安全性付账:
_pipeline/orig_text/:text每次修补前把原图整份复制过来(修补不可逆,这是回退凭证)。量级 = 被修补图片的总大小(200 张大图可能上 GB)。同一个文件名只备份第一次,重复跑不会叠加;确认修补结果没问题后可以整个删掉(删了就不能回退到修补前)。_pipeline/thumbs/:给看图子代理用的 ≤1536px JPEG 副本,量级约为原图的 10~20%(实测 142 张 32MB)。已是最新的会跳过,prune:true清掉没有对应原图的;看图复核结束后整个删掉也不影响任何阶段(下次要看图会重建)。_pipeline/masks/是几十 KB/张的掩膜,默认修补成功后就把本次用过的清掉(要留着--inpaint-only重跑就传keepMasks:true;失败/未修补的掩膜始终保留);captions_prev/是纯文本快照,默认只留最近 1 代。
hygiene:备份/快照留多少份(默认最小)
出厂值刻意压到最小 —— 反复跑批不该往磁盘堆副本。改 <home>/.animasl/animasl.config.json 的 hygiene 段,或在设置页「空间与备份」分组里改:
| 键 | 默认 | 作用 |
|---|---|---|
hygiene.caption_snapshots | 1 | wash 写盘前保留几代 caption 快照;0 = 不留。内容与上一版逐字节相同就不新建(反复洗同一批不会堆文件) |
hygiene.rename_snapshots | 3 | 每批 rename 的 rename_map.<a>-<b>.csv 快照份数(累计的权威表 rename_map.csv 不受影响) |
hygiene.makecfg_backups | 1 | 训练配置 .bak<时分> 保留几代 —— 一次 apply 写的四件套算同一代,所以不会按文件数翻倍(preflight.txt 是报告,不进备份) |
hygiene.keep_masks | false | text 修补后是否保留掩膜 |
阶段参数可以按次覆盖:wash 的 keepSnapshots、makecfg 的 keepBackups、text 的 keepMasks(传 0 = 这次不留)。
makecfg 另外做了三件省事的事:① 逐文件比对内容,四件套(<name>_lora_stage1.toml / dataset_<name>.toml / train_<name>.bat / rationale.md)没变就不重写、也不备份,全都没变就打印「配置与现有文件完全一致,未写盘」;② 文件里的「生成时间」不算内容变化(否则时间戳每跑一次就往前走一秒,每个文件都会被判成"变了");③ preflight.txt 是报告不是配置,原地覆盖刷新,既不进 .bak 也不占一代 —— 它每次都会因为「与上一版配置的差异」这段而变。真要覆盖时旧配置改名成 .bak<时分>,整代一起清理。真实使用里这里曾经 6 次 apply 攒出 6 代 × 5 = 30 个文件(目录里 35 个只有 5 个在用),其中 toml/bat 0 行不同,只差 preflight.txt 里的几行注释。
累积型文件都设了上限,不会越跑越多:caption 快照留 1 代、rename_map.<a>-<b>.csv 批次快照留 3 份、train_configs/<name>/*.bak<时分> 留 1 代(清理时会打印一行说明)。这些数字就是上面的 hygiene 段。
_excluded/ 里的淘汰图是移动过去的(不是复制,不占双份空间),但会一直留着作为证据——真实数据集里它可能比在用的图还大(redash:在用的 110MB,淘汰区 138MB)。确认不再需要就把它归档到数据集外面。
anima_status 的最后一行就是这件事的体检结果:
占用: 共 2.1G | 数据 2.0G(clean 1.6G、watermark 494M) | _pipeline 33M(thumbs 32M) | _excluded 0B
可回收/可归档: thumbs 32M(看图子代理用的缩略图,删掉下次按需重建);_excluded 0B(淘汰区的原图,确认不要了可归档到数据集外)
文字检测 + 修补(koharu 能力的复刻)
koharu 0.83.1 是 GUI-only(CLI 是空的 Cli{},无 HTTP/MCP),所以直接复用它下载好的两个权重:
- 检测
mayocream/koharu-layout-rfdetr-seg-2xl-1152(RF-DETR-Seg-2XL,4 类 text/onomatopoeia/bubble/panel)→animasl/ml/textdet.py - 修补
mayocream/lama-manga(FFCResNetGenerator large,50.9M 参数)→animasl/ml/inpaint.py
实测(RTX 5060 8GB,见 _probe/textrm_test/、_probe/lama_test/):
- 掩膜外像素逐位不变;分辨率恒定;修复后复检
verify_ratio实测 0.0000。 - 耗时:780×1200 → 10s;5130×7350 → 29s(分块 48 块);修补本身 1~4s。
- 必须分块:
max(W,H) > 1440时单次缩放到 1152 会把文字彻底丢掉且不报错(5130×7350 单次模式 text=0、分块 text=11)。--mode auto已自动处理。 - 真实效果(人工核对 diff):半透明水印的"+"、"×"、斜向光条被干净抹除并融入背景;气泡对白/拟声词消失、线稿像素未动。
- 已知局限:字体式淡水印召回是部分的(
学習…無断転載禁止、平铺的sample字样在默认阈值下抓不全,其字符得分仅 0.06~0.19)。想更激进地清水印就调低阈值:anima_stage(stage="text", thresholds="text=0.15,onomatopoeia=0.12", patchSmall=true, apply=true),代价是误检率上升(修补是不可逆的擦除操作)。 - 修补前原图备份在
_pipeline/orig_text/;JPEG 以 quality=95、4:4:4 重编码,不引入二次压缩劣化。
洗标(零 token 规则引擎)
animasl/wash.py 内置了打标指南 §3 格式硬约束、§7 全部规范化规则(59 条别名/废弃对照含 §6.3 口语形、颜色降级、蕴含折叠、否定式拦截、描述词白名单)与 §4 七槽位排序:
@触发词, [1主体]人数+关系+角色名 → [2外观着装] → [3姿势动作] → [4场景] → [5光影] → [6构图] → [7水印区]
引擎只对看图必答字段(人数/关系/构图/体位细节)产出 review_todo.csv,交给视觉子代理(一次一个、一张图、只读缩略图)回填,再走同一个引擎重排。
判据是「有没有固定视觉对应物」,不是出现频率(指南 §2.3/§2.4)。据此分三类处理:
| 处理 | 内容 | 依据 |
|---|---|---|
| 整类丢弃(按词典 category) | artist(1) 画师/社团标签(由触发词承载)、copyright(3) IP 系列名("永不添加")、meta(5) 无视觉对应物的常量 | §2.3 |
| 按图像状态决定 | 槽位 7 的 watermark/signature/logo/颜色水印/speech bubble/sound effects:去字工具修补过的图不再写(那片像素已经没了,再写就是幻觉),没修补的照写 | §8.1 决策树 |
| 数据集内频次 | 默认不限制(wash.character_min_images = 0,按用户拍板取消;出现几次都保留)。指南 §8.2 建议设 4(只保留本数据集出现 ≥4 次的角色,理由是低频角色学不会、只会把画风打散成噪声)——要跟随指南把该值改成 4 即可,wash 会打印 [wash] 角色阈值 §8.2:… | §8.2 |
按训练目标(init --kind) | character 类型额外整类丢 character(4):角色名由触发词承载(§5.2「角色名 tag 与触发词二选一」、§8.2「角色 LoRA 相反:不写角色名」);其余类型保留全部角色名(不再看频次) | §5.2 / §8.2 |
watermark属于保留的一类:它「有固定像素对应」,标了模型才把那片像素归因到watermark而不是画风(§2.4)。社区主流把它当 source noise 删除,本指南的前提是「图去不掉」——去字工具能去掉的图走决策树第一支。- 散文尾巴默认不写(§3.1:实测成品是纯标签串,混排自然语言"可选,非默认")。来源 caption 里若有散文、而你确实想留,设
keep_natural_language=true;保留时按官方形态用.(句点+空格)过渡、最多 3 句、且不复述标签。 - 散文尾巴同样过规则闸门:
ender_lilies_quietus_of_the_knights这种 ≥6 词的长名字会被parse_caption判成"散文",不拦的话被丢掉的 IP 系列名会从散文路径原样回来(实测踩过)。 wash可重复跑且幂等(来源 C 就是自己上一版输出,逐字节一致);_pipeline/wash_rules.json或配置文件的wash段可逐条覆盖规则(tag 清单是追加语义,category_drop这类策略开关是整体替换语义,设[]即关闭)。
三条不许踩的边界(都在真实数据上踩过)
| 边界 | 规则 | 踩过的坑 |
|---|---|---|
| 别名先于删除 | wash_tags 内部先展开 aliases,再查 drop_exact | 曾经 topless/smiling/slender/group shot/on all fours/hair clip/hairy arms/looking away/front·back·side view/left side ponytail/eyes closed/mouth open 同时躺在 drop_exact 和 aliases 里,而删除检查在别名之前 ⇒ 别名全是死代码,§7.2 要求"换成现行形"的标签被静默丢弃(looking at viewer 在 63 张真实 caption 里被误杀 58 次) |
| 词典认识就不许被启发式杀 | drop_regex 除结构性噪声(否定式、score_*/rating:、裸年份)外,一律加词典闸门:is_negative() 只在词典不认识该字符串时才用启发式正则 | ^(very|really|extremely)\s+ 杀掉了 very long hair(105 万帖)、^[a-z]+_?style$ 杀掉了 doggystyle("dogg"+"ystyle")——都是真标签,只在真实数据审计里才暴露 |
| 口语形→现行形,不是丢弃 | §6.3 的自动打标口语形全部进 aliases:eyes closed→closed eyes、mouth open→open mouth、from front/front view→straight-on、back view→from behind、side view→from side、left side ponytail→side ponytail、twin tails→twintails | 这些是"要修的词形",不是"要删的词";词典里查无同义形的(see through 18 次、fate 8 次、erect nipples 1 次)只报告不猜,见下 |
original是category_keep里的例外:danbooru 把它归在 copyright 类,但它不是 IP 系列名而是"原创非二创"的事实陈述;用户自己的 63 张 caption 里 52 张写了它。要跟随 §2.3 的严格读法,把它从category_keep里删掉即可。- 评级词保留(
safe/sensitive/questionable/explicit/nsfw/general+rating safe这类空格形):指南 §4.3 的官方段[quality/meta/year/safety]「一般不写」,但用户既有数据集整族带评级词,用户已明确拍板保留,所以洗标不动它们。它们在classify()里走独立的rating_tags表 ⇒ 排在触发词之后、槽位 1 之前(就是官方段的位置),并且不会因为「词典查不到」被记进unknown。要恢复 §4.3 的读法,在_pipeline/wash_rules.json的drop_exact_extra里列出它们即可。冒号形rating:safe与score_*仍按结构性噪声丢弃(不是标签形态,wash.py的drop_regex)。 - 槽位 2 = 外观着装 + 表情 + 视线,槽位 5 = 光影色调(§4.1/§4.2,指南明确"旧规则曾把视线放槽位 3,已更正为归槽位 2"):
looking *在 danbooru 里归"构图"组(l1=构图)、sunlight归"背景"组,照 l1 走会分别掉进槽位 6 / 槽位 4 ⇒classify()里把表情/视线与光影词尾的判定排在 l1 之前(光影用词尾匹配,避免light blue hair被误判)。 - 关系标签归槽位 1(§8.3「关系标签同归槽位 1」):
solo focus/faceless male/bisexual/group sex/threesome/ffm threesome/orgy/multiple views单列成relation_tags,判定排在framing_tags/sex_tags/l1 之前——照 l1 走solo focus会掉进槽位 6、group sex掉进 3、bisexual(词典无此词)掉进 0 排到触发词前后。同理把 §8.4 里 l1=身体/表情的性描写词(cumdrip/cum on breasts/squirting/clitoris/large insertion/after sex等)补进sex_tags钉在槽位 3。 - 词典里没有、但同义现行形存在的写法走别名:
bunny girl→playboy bunny、gluteal fold→gluteal sulcus、aftersex→after sex。查无同义形的留在wash_report.csv的unknown列里报给用户(verify也会列出来),要压制就在_pipeline/wash_rules.json里加alias_extra。 - 实测效果(
datasets/arata/latest/的 63 张真实 caption,评级词保留之后重测):标签数中位 49 → 43(区间 27~62);丢弃项只有四类可解释的 ——implied-by-child335(§7.4 折叠)、banned19(占位符(series)8、单字母碎片o/t8、artist revision1 等)、duplicate19、copyright-ip13(blue archive/fate (series)/indie virtual youtuber)。unknown 收敛到 3 种(see through18、fate8、erect nipples1)。
合并、冲突与体检(小叶子miv 复盘修掉的那批)
| 症状(那次真实使用) | 现在 |
|---|---|
合并只有集合层:tags_a + tags_c 一 union,同族冲突(pink hair vs purple hair)靠顺序定胜负 | 冲突族检测:CONFLICT_GROUPS(发色 16 / 瞳色 15 / 发长 6 / 胸围 6 / 下着 6 / 人数)在 validate 与 verify 里都报 conflict-hair-color(pink hair vs purple hair);1girl+1boy 不算冲突,只有 solo 与多人互斥才算。可用 prefer:"A"(booru 权威)或 "C"(既有 caption)指定同族多取值留哪边,裁掉的记进报告 conflict-lost-to-prefer-A(...);一侧完全没有来源标记时不裁(没依据就别猜) |
| 上限只报不裁,要手改规则 json | minTags/maxTags 直接进 CLI(例:wash minTags=30 maxTags=60),写进 wash_rules.json 长期生效 |
refreshSource 全有/全无(要么不复活、要么全复活) | 三级防复活:默认(merged_tags 差集 + 手删名单)/ refreshBooru:true(只忽略手删名单)/ refreshSource:true(连手删名单都不看) |
水印族标签要一张张 fix-caption 删 | dropMarks:true 一次丢掉 mark_tags 整类(watermark/颜色水印/signature/artist name/twitter username/logo/web address/文字族)——给已经用 PS 清过的桶用 |
| `: | 被洗成 |
词典里没有的写法直接被丢(unknown_policy 只有 report/drop/keep) | 新增 unknown_policy:"alias-to-canonical":先在 aliases / canonical_map / 形态变体(连字符↔空格、尾 s)里找词典里真实存在的目标形,找到就用它(记 normalized-to(...)),否则仍进 unknown |
wash 全程 A=none 却照报 ok | A 源体检:每次打印 标签来源:A+booru N、A=none M;A=none 占一半以上时打醒目告警并给两条修法(enrich 按 md5 反查、fetch --source danbooru --tags <画师名>),提醒要在 text 之前做 |
修补过的图还写着 watermark(像素已经没了) | verify 增加水印区一致性:已修补(text_report.csv 里 decision=patch)却有标识标签的逐张列出来;未修补的图里多数标了、少数没标时提示"看图确认是本来就没有还是漏标"(不做恒定断言) |
标签来源链(断了不会报错,只会让 caption 只剩触发词)
fetch → _pipeline/raw_posts.jsonl 每个下载文件的 post_id / tags / created_at / 宽高
enrich → _pipeline/raw_posts.jsonl 本地图反查原帖补上同样的行(source=danbooru-md5,md5 不中再用 pixiv id)
rename → _pipeline/rename_map.csv new_name → old_name + post 元数据 + tags_full
wash → caption 的来源 A(booru 标签)+ 来源 C(同名 .txt sidecar)
screen从raw_posts.jsonl读created_at判"过老",dedup读宽高决定保留哪张,rename按created_at排序编号。- 标签一律用 danbooru 下划线原形(
hakurei_reimu、long_hair),不是空格形:raw_posts.jsonl的tags是 list(边界无歧义,wash优先读它);rename_map.csv只能存字符串,rename会写tags_full= 下划线形空格拼接。历史上这里有个坑——CSV 里存空格形多词标签,下游.split()会把它切成hakurei+reimu两个词,且不会报错。 - 本地
import进来的图只有"从哪来"这一行元数据(source=import),没有 booru 标签:要么自带.txt,要么用enrich按文件 md5 去 danbooru 反查原帖把权威标签补回来(Pixiv 图、压缩包、别人给的素材都能查)。md5 不中时还会用文件名里的 pixiv illust id 再兜一次(<id>_p<页>命名;只认"原文件名同名同页"或"该作品只有一帖且本图是 p0",挑不出同一页就不认 —— 多页作品尺寸往往完全一样,硬贴会把 p0 的标签安到 p1 上;不要这个兜底传noPixiv:true)。 - 别对
enrich的命中率抱幻想:实测E:/LoRA_Train/datasets/redash的 72 张图,md5 只命中 3 张(全是当初从 danbooru 抓下来的.jpg),43 张查不到、26 张本来就有标签;那 200 张 pixiv 原图里能解析出 illust id 的 46 张,只有 6 张在 danbooru 有帖,其中 3 张还是同一作品的不同页(按上面的规则拒绝)。结论:只发 pixiv 的画师,A 源天生稀薄,B 源(看图)才是主力。enrich_report.csv的by列会写清每张是靠md5还是pixiv-source/pixiv-single命中的。 enrich要在text修补之前跑:修补改像素 ⇒ md5 变了就永远查不到。命中率低时先看这三条:跑过 text 修补、裁剪/重编码过、原图本来没上传 danbooru。danbooru 不支持批量 md5 查询(md5:a,md5:b返回 0),所以是一张一次请求,默认 0.35s 间隔(pixiv_id兜底会让 miss 的图多一次请求)。wash有两个点名报警器:① "既无来源标签也无既有 caption"(来源链断了);② "caption 洗完只剩触发词"(来源标签有数据,但被整类规则丢光——单画师图源的典型症状,附丢弃理由计数)。verify的too-few-tags是第三道闸。- 反查回来的是权威全集(常见 45~65 条),比"看图精修过的 caption"长得多:
wash会丢光画师/IP/meta 类,但剩下的仍可能超过指南 §9 的 45 条上限(实测一张 63 条源标签丢完还剩 49)。enrich只把超限张数报出来,不自动裁剪——按 §2.2 的槽位优先级(人物/服装/动作 > 构图/背景/光影)用fix-caption往下删才是对的。
verify 是交付前的验收闸门,按指南 §9 分两层:形态层(单行、全小写、无下划线形、, 分隔、无前导逗号、无重复 token、标签数 20~45、触发词唯一且置首、BOM/尾换行/尾标点)与内容层(BANNED 令牌零残留=画师/IP/meta/质量词/文字族/否定式/该换现行形的别名、人数一致性 = solo 与 2girls/1boy/solo focus 互斥、图-txt 配对完整含孤儿 .txt)。内容层是给"人工编辑过、或别的打标工具写的"caption 兜底的:wash 跑完再手改,画师/IP/质量词就会悄悄回来。--online 再用 danbooru search[name_comma] 复核"词典里没有"的标签是否真实存在(每批 120)。实测在用户的 63 张既有 caption 上查出 23 种残留(explicit×30、sensitive×17、nsfw×15、blue archive×9、(series)×8、pantsu→panties/garter→garter belt/swimsuits→swimsuit 等别名形)。
训练配置:四件套 + preflight.txt
makecfg 依据用户自己的《Anima_LoRA_调参指南》决策表:Rank–LR 联动(风格 100+ 张 → dim 32 / lr 5e-5~8e-5)、曝光量算 steps、差异化 repeat、显存档。
生成物严格不含 Anima 无效参数(noise_offset、min_snr_gamma),shuffle_caption 恒 false(cache_text_encoder_outputs=true 时 trainer 强制要求),network_module 恒 networks.lora_anima。默认 resolution=[1280,1280](与既有 11 个可跑配置一致;8GB OOM 时退 1024)。
每次还会写一份 preflight.txt(开跑前的体检单):图片数与各子集 repeats、caption 合规率(标签数 min/avg/max、<20 / >45 计数、触发词不在首位)、词典外与 post_count=0 的标签清单、rank/LR/epochs→steps/save_every 的推导过程、分辨率与 batch,以及与上一版配置的逐字段差异(首版没有旧文件时不输出差异段)。这样"这版配置为什么这么写、跟上次差在哪"不用再翻聊天记录。
LR 超出该 rank 的建议区间会直接报错(不再静默收紧):报错文案会给出区间,要么用区间内的值,要么显式 allowOutOfBand:true——那时不改值、只在 preflight 与终端打一行警告。真实使用里给过 lr 5e-05 / dim 16,旧版把它悄悄改成 8e-05,用户没看见。
makecfg 重跑不会堆垃圾:四件套逐文件比对内容,没变就不重写、不备份(rationale.md 里的「生成时间」不算内容变化),全都一样就打印「配置与现有文件完全一致,未写盘」;真的变了才把旧文件改名成 <名字>.bak<时分秒>(force:true 则不备份),并且 .bak 按代清理(默认只留最近 1 代,keepBackups / hygiene.makecfg_backups 可调)。preflight.txt 是报告:原地覆盖刷新,不进备份、不占一代 —— 同一份配置反复跑,磁盘上始终只有四件套 + 一份体检单。
目录契约
datasets/<name>/
00_raw/ 下载/导入的原始文件(不算图桶)
images/ 默认工作集(图片 + 同名 .txt caption)★ caption 权威副本
clean/ watermark/ 可选:自己分的图桶 —— 根下**任何含图的子目录**都自动算一个桶
_pipeline/ manifest.json、per_image.json、user_deleted.json、raw_posts.jsonl、enrich_report.csv、
*_report.csv、wash_diff.csv、captions_prev/(留 1 代)、masks/(成功后清)、thumbs/、orig_text/、rename_map.csv(累计)
+ rename_map.<a>-<b>.csv(每批快照,留 3 份)
_excluded/ 剔除物(按 reason 分子目录)+ _EXCLUDED_MANIFEST.json 证据链
train_configs/<name>/ <name>_lora_stage1.toml + dataset_<name>.toml + train_<name>.bat + rationale.md + preflight.txt
只移动不删除。 每条剔除都记 _EXCLUDED_MANIFEST.json:{original, reason, rule, detail, decided_by};人工淘汰走 exclude,同一张图还会进 _pipeline/user_deleted.json,undo 能按账本搬回原来的桶。
环境要求
-
DSH ≥ 0.2.0-rc.1(
@deepseek-ai/dsh-toolspeer) -
ML venv(torch + rfdetr):只有「文字检测与修补」阶段用得到,bundle 里不自带。 解释器按
ml_python设置项 →<home>/.animasl/venv→<bundle>/python/.venv→ 数据集解释器 的顺序找,第一个存在的即中选。推荐放<home>/.animasl/venv(放 bundle 里重装会被覆盖):uv venv --python E:/LoRA_Train/anima_lora/.venv/Scripts/python.exe E:/LoRA_Train/.animasl/venv :: 复用现成的 torch:写一行 .pth 指向那个解释器的 site-packages echo E:\LoRA_Train\anima_lora\.venv\Lib\site-packages > E:\LoRA_Train\.animasl\venv\Lib\site-packages\_ref.pth uv pip install --python E:/LoRA_Train/.animasl/venv/Scripts/python.exe rfdetr==1.7.0 supervisionanima_doctor的「ML 依赖」一行报的是torch 版本 / CUDA / rfdetr 版本;缺哪一样它直接给修法。 也可以什么都不建,把ml_python指向任意一个import torch, rfdetr都能过的解释器。 -
词典:
tags.json+danbooru_dataset_general.csv(合并后 383,196 条) -
下载:danbooru 用设置页的「凭据」分组填用户名 + API key(或
DANBOORU_LOGIN/DANBOORU_API_KEY);pawchive/exhentai 需要 cookie,且 pawchive CDN 必须走代理(直连被 reset)
跑 anima_doctor 可以一次性体检上述每一项(含下面这张表的"凭据配没配")。
danbooru 的 Cloudflare:为什么需要 curl 兜底
danbooru 在 Cloudflare 后面,而它的规则大致是「Chrome UA + 非浏览器 TLS 指纹 = 机器人」。 实测(同一代理、同一 URL、带 API key):
| 请求方 | UA | 结果 |
|---|---|---|
requests | 任意 | 403 <title>Just a moment...</title> |
curl | 默认 UA | 200 真 JSON |
curl | animasl/0.1 (+curl) | 200 真 JSON |
curl | Chrome/126 … | 403 挑战页 |
所以:net.py 在 requests 拿到 403/503 且正文像挑战页时,自动改用 curl 子进程重放
(代理、-u login:key、Referer、按主机过滤的 cookie 都跟着转过去;图片下载同理,
cdn.donmai.us 也是 requests 403 / curl 200)。curl 侧一律用自报家门的 UA
(curl_user_agent 配置项,默认 animasl/0.1 (+curl))——别填浏览器 UA,
那正好触发拦截。可用 ANIMASL_NO_CURL=1 关掉兜底、ANIMASL_CURL / ANIMASL_CURL_UA 覆盖。
直连 danbooru 是连不上的(GFW,ConnectTimeout),所以它和 cdn.donmai.us 都写进了
prefer_proxy_hosts,探测时把代理候选提前、直连降级为最后一档。
各图源需要什么账号
| 图源 | 要账号吗 | 怎么配 | 不配会怎样 |
|---|---|---|---|
| yande.re | 不需要 | 什么都不用 | —(公开 post.json,代理只在被 reset 时才用) |
| danbooru | 单标签不用,多标签要用 | 设置页「凭据」分组填用户名 + API key(或 DANBOORU_LOGIN + DANBOORU_API_KEY) | 匿名只能查单标签且限速极严。anima_doctor 里那行 danbooru.donmai.us HTTP 403 是 Cloudflare 挑战页,不代表 API 不可用(curl 兜底会正常拿到 JSON) |
| pawchive.pw | 公开接口不用 | 受限帖才需要:cookies 参数或 cookies_file 配置(Netscape cookies.txt) | 公开帖照抓;受限帖拿不到。CDN 域名必须走代理(直连被 reset) |
| exhentai | 必须 | 设置页「凭据」分组填 ipb_member_id / ipb_pass_hash / igneous,或 cookies 指向 cookies.txt(或 EXHENTAI_* 环境变量) | 直接报 [fetch] exhentai 需要 cookies.txt 或 EXHENTAI_* 环境变量。这条链路只做到"可调用",第一次用先 limit:5 验证拿到的是原图而不是 -thumb |
| gelbooru | — | 未实现(当前只支持上面四个源) | source 传 gelbooru 会报未知来源 |
凭据的优先级是 设置页(运行时配置) > 环境变量 > cookies.txt;设置页那份落在
<home>/.animasl/animasl.config.json,和 cookies.txt 一样不进仓库;
anima_doctor 只报告"配没配、从哪读到的",不打印密钥内容。
cookies.txt 是 Netscape 格式,一个文件里可以放多个站点 —— 读取时按 domain 列区分
(net.load_cookie_jar):第 2 列 TRUE = 域级 cookie(补前导点,子域也发),
FALSE = Host-Only。别再用旧的"裸 {name: value} 字典"加载方式,那会把 exhentai 的
登录 cookie 发给 pawchive(空域 = 任何主机都收到)。
Comments
Loading…