diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5dc4cd..6194812 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,7 +127,7 @@ pnpm dev 打开浏览器访问 [http://localhost:3000](http://localhost:3000)。 -修改 `docs/` 下的 `.md` 或 `.mdx` 文件,会自动热更新。 +修改 `docs/` 下的 `.md` 文件,会自动热更新。 --- @@ -155,7 +155,7 @@ pnpm postinstall # 同步必要的 Husky/Fumadocs 配置 docxA 引用了 imgA 图片, 那么他们的文档结构应该是 `docxA.assets/imgA`: ```md -docsA.mdx +docsA.md docsA.assets/ imgA ``` @@ -199,35 +199,35 @@ tags: ``` 📂 docs/ ├── 📂 computer-science/ # 计算机科学 -│ ├── 📄 index.mdx # 概述 +│ ├── 📄 index.md # 概述 │ └── 📂 data-structures/ # 数据结构 -│ ├── 📄 index.mdx # 概述 +│ ├── 📄 index.md # 概述 │ ├── 📂 array/ # 数组 -│ │ ├── 📄 index.mdx # 概述 -│ │ ├── 📄 01-static-array.mdx # 静态数组 -│ │ └── 📄 02-dynamic-array.mdx # 动态数组 +│ │ ├── 📄 index.md # 概述 +│ │ ├── 📄 01-static-array.md # 静态数组 +│ │ └── 📄 02-dynamic-array.md # 动态数组 │ └── 📂 linked-list/ # 链表 -│ ├── 📄 index.mdx # 概述 -│ └── 📄 01-singly-linked-list.mdx # 单向链表 +│ ├── 📄 index.md # 概述 +│ └── 📄 01-singly-linked-list.md # 单向链表 ``` ### URL 生成 文件结构会自动生成简洁的 URL: -- `docs/computer-science/index.mdx` → `/computer-science` -- `docs/computer-science/data-structures/array/01-static-array.mdx` → `/computer-science/data-structures/array/static-array` +- `docs/computer-science/index.md` → `/computer-science` +- `docs/computer-science/data-structures/array/01-static-array.md` → `/computer-science/data-structures/array/static-array` ### 命名约定 **文件夹:** - 使用 `kebab-case` 命名: `computer-science`, `data-structures` -- 每个主题文件夹应该有一个 `index.mdx` 文件作为概述 +- 每个主题文件夹应该有一个 `index.md` 文件作为概述 **文件:** -- 使用 `kebab-case` 命名: `static-array.mdx` +- 使用 `kebab-case` 命名: `static-array.md` - 使用数字前缀排序: `01-`, `02-` - 前缀会自动从最终 URL 中移除 diff --git a/README.en.md b/README.en.md index b988a80..e0b5deb 100644 --- a/README.en.md +++ b/README.en.md @@ -2,7 +2,24 @@ 简体中文 | English

-# Involution Hell Knowledge Base +

+ + + +

+

Typing SVG

+ +

+ Next.js + TypeScript + Tailwind CSS + Vercel + + License + +

+ + ## 📋 About @@ -64,6 +81,8 @@ Community contributions are always welcome: For the full workflow, PR checklist, and UI collaboration guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). +[![Contributors](https://contrib.rocks/image?repo=InvolutionHell/involutionhell.github.io)](https://github.com/InvolutionHell/involutionhell.github.io/graphs/contributors) + ## 🖼️ Documentation & Assets The repo ships with automated image migration and linting. Learn how to place assets, reference images, and structure frontmatter in: diff --git a/README.md b/README.md index ad4af69..c5111c7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,23 @@ 简体中文 | English

-# 内卷地狱知识库 +

+ + + +

+ +

Typing SVG

+ +

+ Next.js + TypeScript + Tailwind CSS + Vercel + + License + +

## 📋 关于 @@ -74,6 +90,8 @@ pnpm dev 完整流程、PR 检查与 UI 协作约定请参考 [CONTRIBUTING.md](CONTRIBUTING.md)。 +[![Contributors](https://contrib.rocks/image?repo=InvolutionHell/involutionhell.github.io)](https://github.com/InvolutionHell/involutionhell.github.io/graphs/contributors) + ## 🖼️ 文档与资产 仓库提供自动化图片迁移与 Lint 规则。如何放置图片、引用资产、撰写 Frontmatter 等细节已在贡献指南中整理: diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 820c88f..5e872c6 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,81 +1,52 @@ -import { createOpenAI } from "@ai-sdk/openai"; -import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { streamText, UIMessage, convertToModelMessages } from "ai"; +import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models"; +import { buildSystemMessage } from "@/lib/ai/prompt"; -// Allow streaming responses up to 30 seconds +// 流式响应最长30秒 export const maxDuration = 30; -export async function POST(req: Request) { - const { - messages, - system, - pageContext, - provider, - apiKey, - }: { - messages: UIMessage[]; - system?: string; // System message forwarded from AssistantChatTransport - tools?: unknown; // Frontend tools forwarded from AssistantChatTransport - pageContext?: { - title?: string; - description?: string; - content?: string; - slug?: string; - }; - provider?: "openai" | "gemini"; - apiKey?: string; - } = await req.json(); - - // Check if API key is provided - if (!apiKey || apiKey.trim() === "") { - return Response.json( - { - error: - "API key is required. Please configure your API key in the settings.", - }, - { status: 400 }, - ); - } +interface ChatRequest { + messages: UIMessage[]; + system?: string; + tools?: unknown; + pageContext?: { + title?: string; + description?: string; + content?: string; + slug?: string; + }; + provider?: AIProvider; + apiKey?: string; +} +export async function POST(req: Request) { try { - // Build system message with page context - let systemMessage = - system || - `You are a helpful AI assistant for a documentation website. - You can help users understand the documentation, answer questions about the content, - and provide guidance on the topics covered in the docs. Be concise and helpful.`; + const { + messages, + system, + pageContext, + provider = "intern", // 默认使用书生模型 + apiKey, + }: ChatRequest = await req.json(); - // Add current page context if available - if (pageContext?.content) { - systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; - if (pageContext.title) { - systemMessage += `Page Title: ${pageContext.title}\n`; - } - if (pageContext.description) { - systemMessage += `Page Description: ${pageContext.description}\n`; - } - if (pageContext.slug) { - systemMessage += `Page URL: /docs/${pageContext.slug}\n`; - } - systemMessage += `Page Content:\n${pageContext.content}`; - systemMessage += `\n--- END OF CONTEXT ---\n\nWhen users ask about "this page", "current page", or refer to the content they're reading, use the above context to provide accurate answers. You can summarize, explain, or answer specific questions about the current page content.`; + // 对指定Provider验证key是否存在 + if (requiresApiKey(provider) && (!apiKey || apiKey.trim() === "")) { + return Response.json( + { + error: + "API key is required. Please configure your API key in the settings.", + }, + { status: 400 }, + ); } - // Select model based on provider - let model; - if (provider === "gemini") { - const customGoogle = createGoogleGenerativeAI({ - apiKey: apiKey, - }); - model = customGoogle("models/gemini-2.0-flash"); - } else { - // Default to OpenAI - const customOpenAI = createOpenAI({ - apiKey: apiKey, - }); - model = customOpenAI("gpt-4.1-nano"); - } + // 构建系统消息,包含页面上下文 + const systemMessage = buildSystemMessage(system, pageContext); + + // 根据Provider获取 AI 模型实例 + const model = getModel(provider, apiKey); + // 生成流式响应 const result = streamText({ model: model, system: systemMessage, @@ -85,6 +56,12 @@ export async function POST(req: Request) { return result.toUIMessageStreamResponse(); } catch (error) { console.error("Chat API error:", error); + + // 处理特定模型创建错误 + if (error instanceof Error && error.message.includes("API key")) { + return Response.json({ error: error.message }, { status: 400 }); + } + return Response.json( { error: "Failed to process chat request" }, { status: 500 }, diff --git a/app/components/Contribute.tsx b/app/components/Contribute.tsx index 55bb5f5..9a2f080 100644 --- a/app/components/Contribute.tsx +++ b/app/components/Contribute.tsx @@ -27,7 +27,7 @@ const FILENAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]+$/; // 统一调用工具函数生成 GitHub 新建链接,路径规则与 Edit 按钮一致 function buildGithubNewUrl(dirPath: string, filename: string, title: string) { - const file = filename.endsWith(".mdx") ? filename : `${filename}.mdx`; + const file = filename.endsWith(".md") ? filename : `${filename}.md`; const frontMatter = `--- title: '${title || "New Article"}' description: "" diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx index 8e25a62..92b76fb 100644 --- a/app/components/DocsAssistant.tsx +++ b/app/components/DocsAssistant.tsx @@ -53,7 +53,9 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) { const currentApiKey = currentProvider === "openai" ? openaiApiKeyRef.current - : geminiApiKeyRef.current; + : currentProvider === "gemini" + ? geminiApiKeyRef.current + : ""; // intern provider doesn't need API key console.log("[DocsAssistant] useChat body function called with:", { provider: currentProvider, @@ -118,9 +120,14 @@ interface AssistantErrorState { function deriveAssistantError( err: unknown, - provider: "openai" | "gemini", + provider: "openai" | "gemini" | "intern", ): AssistantErrorState { - const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + const providerLabel = + provider === "gemini" + ? "Google Gemini" + : provider === "intern" + ? "Intern-AI" + : "OpenAI"; const fallback: AssistantErrorState = { message: "The assistant couldn't complete that request. Please try again later.", @@ -176,14 +183,16 @@ function deriveAssistantError( let showSettingsCTA = false; + // For intern provider, don't show settings CTA for API key related errors if ( - statusCode === 400 || - statusCode === 401 || - statusCode === 403 || - normalized.includes("api key") || - normalized.includes("apikey") || - normalized.includes("missing key") || - normalized.includes("unauthorized") + provider !== "intern" && + (statusCode === 400 || + statusCode === 401 || + statusCode === 403 || + normalized.includes("api key") || + normalized.includes("apikey") || + normalized.includes("missing key") || + normalized.includes("unauthorized")) ) { showSettingsCTA = true; } diff --git a/app/components/assistant-ui/SettingsDialog.tsx b/app/components/assistant-ui/SettingsDialog.tsx index a8520cf..9929afe 100644 --- a/app/components/assistant-ui/SettingsDialog.tsx +++ b/app/components/assistant-ui/SettingsDialog.tsx @@ -51,9 +51,13 @@ export const SettingsDialog = ({ - setProvider(value as "openai" | "gemini") + setProvider(value as "openai" | "gemini" | "intern") } > +
+ + +
@@ -90,6 +94,15 @@ export const SettingsDialog = ({ />
)} + + {provider === "intern" && ( +
+
+ 感谢上海AILab的书生大模型对本项目的算力支持,Intern-AI + 模型已预配置,无需提供 API Key。 +
+
+ )} diff --git a/app/components/assistant-ui/assistant-modal.tsx b/app/components/assistant-ui/assistant-modal.tsx index ee46937..621f5a5 100644 --- a/app/components/assistant-ui/assistant-modal.tsx +++ b/app/components/assistant-ui/assistant-modal.tsx @@ -2,11 +2,17 @@ import { BotIcon, ChevronDownIcon } from "lucide-react"; -import { type FC, forwardRef } from "react"; +import { type FC, forwardRef, useState, useEffect } from "react"; import { AssistantModalPrimitive } from "@assistant-ui/react"; +import { usePathname } from "next/navigation"; import { Thread } from "@/app/components/assistant-ui/thread"; import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/app/components/ui/tooltip"; interface AssistantModalProps { errorMessage?: string; @@ -19,11 +25,61 @@ export const AssistantModal: FC = ({ showSettingsAction = false, onClearError, }) => { + const [showBubble, setShowBubble] = useState(false); + + useEffect(() => { + // 检查本次访问是否已关闭过气泡 + const bubbleClosed = sessionStorage.getItem("ai-bubble-closed"); + + if (!bubbleClosed) { + // 页面加载后2秒显示气泡提示 + const showTimer = setTimeout(() => { + setShowBubble(true); + }, 2000); + + // 15秒后自动关闭气泡 + const hideTimer = setTimeout(() => { + setShowBubble(false); + sessionStorage.setItem("ai-bubble-closed", "true"); + }, 17000); // 2秒显示 + 15秒停留 = 17秒 + + return () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + }; + } + }, []); + + const handleCloseBubble = () => { + setShowBubble(false); + // 记录本次访问已关闭气泡 + sessionStorage.setItem("ai-bubble-closed", "true"); + }; + return ( - + + {/* 自定义气泡组件 */} + {showBubble && ( +
+
+
+ 有问题可以问我哦~ +
+ {/* 气泡尾巴箭头 - 指向下方按钮 */} +
+
+
+
+
+
+ )} + - +
= ({ ); }; -type AssistantModalButtonProps = { "data-state"?: "open" | "closed" }; +type AssistantModalButtonProps = { + "data-state"?: "open" | "closed"; + onCloseBubble?: () => void; + onClick?: (e: React.MouseEvent) => void; +}; const AssistantModalButton = forwardRef< HTMLButtonElement, AssistantModalButtonProps ->(({ "data-state": state, ...rest }, ref) => { +>(({ "data-state": state, onCloseBubble, ...rest }, ref) => { const tooltip = state === "open" ? "Close Assistant" : "Open Assistant"; + const handleClick = (e: React.MouseEvent) => { + // 当点击open按钮时,关闭气泡对话 + if (onCloseBubble) { + onCloseBubble(); + } + // 继续执行原有的点击事件 + if (rest.onClick) { + rest.onClick(e); + } + }; + return ( {tooltip} diff --git a/app/components/assistant-ui/thread.tsx b/app/components/assistant-ui/thread.tsx index c71b6cc..4a0394f 100644 --- a/app/components/assistant-ui/thread.tsx +++ b/app/components/assistant-ui/thread.tsx @@ -268,9 +268,19 @@ const Composer: FC = ({ onClearError, }) => { const { provider, openaiApiKey, geminiApiKey } = useAssistantSettings(); - const activeKey = provider === "openai" ? openaiApiKey : geminiApiKey; - const hasActiveKey = activeKey.trim().length > 0; - const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + const activeKey = + provider === "openai" + ? openaiApiKey + : provider === "gemini" + ? geminiApiKey + : ""; + const hasActiveKey = provider === "intern" || activeKey.trim().length > 0; + const providerLabel = + provider === "gemini" + ? "Google Gemini" + : provider === "intern" + ? "Intern-AI" + : "OpenAI"; const handleOpenSettings = useCallback(() => { onClearError?.(); @@ -278,7 +288,7 @@ const Composer: FC = ({ }, [onClearError, onOpenChange]); return ( -
+
diff --git a/app/docs/CommunityShare/Amazing-AI-Tools/index.md b/app/docs/CommunityShare/Amazing-AI-Tools/index.md new file mode 100644 index 0000000..6537bb5 --- /dev/null +++ b/app/docs/CommunityShare/Amazing-AI-Tools/index.md @@ -0,0 +1,19 @@ +--- +title: 学生邮箱能免费领的AI提效工具系列 +description: "" +date: "2025-10-03" +tags: + - edu-email + - ai-tools + - productivity +docId: mgb41edhi9cz1kxzae074an0 +--- + +学生邮箱能免费领取很多AI产品, +这两年靠着免费体验,有几款AI工具已经融入了我的生活,对我的工作效率蛮有帮助 +所以打算开个栏目,分享一下这些AI产品和使用经验,帮大家把学费的钱薅回来。 + +目前这个系列能想到的暂时只有3款产品,想不到的说明用处不大,就不提了。 +按使用频率从高到低来排是:perplexity Comet, Cursor, V0.dev + +写完这三款产品,后面应该会不局限于学生免费,去分享一些别的对我很有用的免费/付费产品:gemini cli, codex等 diff --git a/app/docs/CommunityShare/Amazing-AI-Tools/perplexity-comet.md b/app/docs/CommunityShare/Amazing-AI-Tools/perplexity-comet.md new file mode 100644 index 0000000..363b88a --- /dev/null +++ b/app/docs/CommunityShare/Amazing-AI-Tools/perplexity-comet.md @@ -0,0 +1,98 @@ +--- +title: Perplexity Comet 浏览器:能当私人管家的自动化浏览器 +description: "" +date: "2025-10-03" +tags: + - perlexity + - comet + - productivity +docId: eej2awin6irhbdgcy8vvs3xb +--- + +## 关键信息 + +**学生免费一年领取地址:[Get Comet with free Perplexity Pro. The AI browser built for students.](https://www.perplexity.ai/students)** +到这个链接,领取一年的Perplexity会员, +然后你就可以下载他们的Comet浏览器来使用了: +[Comet Browser: a Personal AI Assistant](https://www.perplexity.ai/comet/) + +## Comet 介绍 + +该系列的第一篇是Perplexity Comet,一个AI浏览器。 +因为它的使用频率是最高的,甚至超越了cursor,毕竟是要天天打开的浏览器。 + +目前市面上的AI浏览器还比较多,什么Dia,微软的Edge也做成了AI浏览器,Chrome也在发力中。 +但我跟别人了解了一下,也许是得益于Perplexity的早早布局,Comet有一些很强大的别的浏览器做不到的功能。 +![ab3becf9-fe7f-40f3-92f3-8e861e68f1fc.png](https://img.coly.cc/obs-img/2025/10/1df9eb3648e7893e32c0139de7ad5a6d.png) + +它的牛逼之处在于主要有两点 + +1. 能够帮我操作浏览器,做一点我懒得做的工作——虽然精准度和速度不一定好,但好处就是可以把任务丢给它然后自己去做别的事情, +2. 替换掉我的滴答清单,成为安排我一天工作的管家:整合邮件,整合近期事项,提醒我。 + +### 浏览器自动操作 + +它的浏览器自动操作有两种方式: + +1. 后台自动访问页面并执行工作 +2. 前台直接对你当前的页面自动化操作 + +#### 后台的使用案例 + +帮你翻邮件,翻Moodle课程网站自动查分等 + +如果你提前登录过Moodle,你可以直接在它的输入框里要求它: +![image.png](https://img.coly.cc/obs-img/2025/10/2979472a93976749b4f45b5960906c57.png) +![image.png](https://img.coly.cc/obs-img/2025/10/bff8830b849d3d64ad5ee40f4eb589d8.png) +然后你能看到它正在后台一步步地访问对应页面并执行对应的操作 +![image.png](https://img.coly.cc/obs-img/2025/10/5e31b513d633a1383ef637293e320c3e.png) +最后得到结果。 + +同理,你还可以让它帮你查邮箱,把垃圾邮件删除之类的工作。这里就不一一举例了,你理解意思了,就知道这个后台AI能做哪些活了。 +注意Comet浏览器默认会用自研的模型,效果很一般,记得自己手动换成目前最牛逼的模型: +![image.png](https://img.coly.cc/obs-img/2025/10/aeba741491cad07e3c105db3f49cdf1a.png) + +#### 前台的使用案例 + +自动操作Coles,将清单里的商品一一加入购物车。 +![8c574a67ac53577520eb8758b7e892a5.png](https://img.coly.cc/obs-img/2025/10/0759eb80fe077ad310c0f235bf8843c3.png) +用法是打开登录好的coles页面,点击右上角的Assistant按钮,然后给他下指令:把xxx加入购物车 +然后你就会看见页面上浮现一个蓝框,它吭哧吭哧地开始工作了。 +虽然是前台,但你依然可以切到其他页面去,该干啥干啥 +其实我也没搞懂前后台有啥区别,但效果好像是有些页面后台操作不够精准,容易出错,前台的话会精准一点。 + +### 连接邮箱,日历的私人管家 + +现在我每天早上起来的第一件事,就是在输入框输入我自己定义好的命令 +![image.png](https://img.coly.cc/obs-img/2025/10/98605f02a120be2cb67a090c85a40b2b.png) +提醒我啥时候该收衣服,悉尼雨季多次被淋透衣服的痛…… +提醒我啥时候换汇划算 +提醒我最近有啥事要做 +帮我整理邮件 + +你首先到设置,连接器里添加好你的账号,我主要用的是谷歌服务,如果还有别的东西你也用,那么它一样可以连接起来优化你的效率。 +![image.png](https://img.coly.cc/obs-img/2025/10/ed069ee778622782832ed06b947c24d6.png) + +然后去设置里添加一个快捷方式,这样后面每天就不用手动输一大段prompt,直接打几个命令就能执行了。 +![image.png](https://img.coly.cc/obs-img/2025/10/feb661e4aa4c71b5cf2c68fd5284f294.png) + +效果是这样的: +![image.png](https://img.coly.cc/obs-img/2025/10/26eb7f9cb633787058b0efe5ecfa86e6.png) +![image.png](https://img.coly.cc/obs-img/2025/10/751ab79cce1abfd9c194d09666cb6061.png) +还是蛮方便的。 + +我最喜欢的就是日历连接功能,自从用上它以后,滴答清单就彻底不用了,谷歌日历也不打开了,课表之类的会自动添加到日历的事件,它会提醒。你有自己的事件要记录,也可以直接跟他对话,提醒我xxxx +![image.png](https://img.coly.cc/obs-img/2025/10/8feab1d5662ba82d64855d47b46cb949.png) +它会帮你记录在你的日历里,然后每日命令执行时候,发现最近会有事件,就会在对话里提醒你,并且还能通过浏览器通知+email的方式一起提醒,很难忘事了。 + +## Perplexity 介绍 + +除了Comet以外,Perplexity其实本身是一家做AI搜索引擎的公司,所以除了浏览器,它本身的产品也蛮好用的,但跟GPT啥的相比也没有特别显著的优势,但好在是各家模型都能随便用,还算是一个很不错的AI chat. +![image.png](https://img.coly.cc/obs-img/2025/10/544381e5bde62b83b952ce3ad96b3820.png) + +## 下期预告 + +最高频的 Perplexity Comet 讲完了,下一个是Cursor,主题应该是目前许多人最关心的,开发新手怎么样快速做项目,能帮助快速充实自己的简历。 +这玩意真的是神,用了它的这一年里我快速完成了好多质量不错的项目。 +但缺点就是长期依赖下,个人的代码能力会变弱…… +所以我其实很犹豫要不要讲Cursor。 diff --git a/app/docs/CommunityShare/Geek/cloudflare-r2-sharex-free-image-hosting.mdx b/app/docs/CommunityShare/Geek/cloudflare-r2-sharex-free-image-hosting.mdx new file mode 100644 index 0000000..83e40f0 --- /dev/null +++ b/app/docs/CommunityShare/Geek/cloudflare-r2-sharex-free-image-hosting.mdx @@ -0,0 +1,149 @@ +--- +title: 使用 Cloudflare R2 + ShareX 搭建个人/团队专属“永久”图床 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: gj4bn01un0s0841berfvwrn5 +--- + +# 使用 Cloudflare R2 + ShareX 搭建个人/团队专属“永久”图床 + +本指南旨在提供一个从零开始,利用 Cloudflare R2 的免费套餐和 ShareX 的强大功能,搭建一个高性能、高可靠性、几乎零成本且数据完全由自己掌控的图床的完整流程。 + +**最终效果**:通过一个快捷键截图,图片自动上传到你的专属存储空间,或手动上传自己的图片,自动将 Markdown 格式的链接复制到剪贴板,实现无缝写作。 + +--- + +## 目录 + +1. [第一部分:配置 Cloudflare R2 (云端存储)](#第一部分配置-cloudflare-r2-云端存储) +2. [第二部分:配置 ShareX (桌面客户端)](#第二部分配置-sharex-桌面客户端) +3. [第三部分:优化 ShareX 工作流](#第三部分优化-sharex-工作流) +4. [常见问题 (F.A.Q) 与排错](#常见问题-faq-与排错) + +--- + +## 第一部分:配置 Cloudflare R2 (云端存储) + +首先来到Cloudflare开通R2存储。 +![](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/%E5%9B%BE%E7%89%87_20250927143514.png) + +### 1.1 创建 R2 存储桶 (Bucket) + +存储桶是存放你所有图片的容器。 + +1. 登录 Cloudflare 控制台,在左侧菜单进入 **R2**。 +2. 点击 **Create bucket (创建存储桶)**。 +3. **Bucket name**: 输入一个全局唯一的存储桶名称 (例如 `your-org-images-2025`)。 +4. **Location**: 保持默认的 **Automatic**。 +5. 点击 **Create bucket**。 + +![](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/xrRAVMB2Sz.png) + +### 1.2 开放存储桶的公开访问权限 + +为了让上传的图片能被外部访问,需要开启公共访问。 + +1. 进入你刚创建的存储桶,点击顶部的 **Settings (设置)** 选项卡。 + ![1r0AU3aWkD.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/1r0AU3aWkD.png) +2. 在 **下方Public Development URL** 部分,点击右侧的 **Enable**。 +3. 输入确认信息。 + ![fgc4amk7S7.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/fgc4amk7S7.png) +4. **记下**这里显示的 `https://pub-....r2.dev` 格式的 URL,这是你的公共访问域名。 + ![RmQwxSxpLi.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/RmQwxSxpLi.png) + +### 1.3 创建用于上传的 API Token + +API Token 是让 ShareX 有权限上传文件到 R2 的“钥匙”。 + +1. 回到 R2 的主页(概览页面),点击右上角的 **Manage API Tokens (管理 R2 API 令牌)**。 + ![CTzhiiSl04.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/CTzhiiSl04.png) +1. 点击 **Create Account API token (创建 API 令牌)**。 + ![FBEzXXohz7.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/FBEzXXohz7.png) +1. **Permissions (权限)**: **务必选择 `Object Read & Write` (对象读和写)**。这是最关键的一步,只读权限会导致上传失败。 + ![xB4pYQOeEI.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/xB4pYQOeEI.png) +1. 点击 **Create API token**。 +1. **⚠️ 立即复制并保存!** 页面上会显示 `Access Key ID` 和 `Secret Access Key`。**这两个密钥只会出现这一次**,请立刻将它们复制并粘贴到一个安全的地方。最下方的Default endpoints链接也需要保存一下。 + ![kg9E8tEozI.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/kg9E8tEozI.png) + +--- + +## 第二部分:配置 ShareX (桌面客户端) + +### 2.1 下载并安装 ShareX + +从官网下载最新版本:[https://getsharex.com/](https://getsharex.com/) + +### 2.2 配置 S3 上传目标 + +这是整个配置过程的核心。 + +1. 打开 ShareX,在主界面点击 `Destination settings...`。 +2. 在弹出的窗口左侧选择 `Amazon S3`,在右侧输入你的配置信息。 +3. 按照下表精确填写你的 R2 信息: + +| ShareX 设置项 | 填写内容 | 说明 | +| --------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **Access key ID** | 粘贴你保存的 `Access Key ID` | | +| **Secret access key** | 粘贴你保存的 `Secret Access Key` | | +| **Region** | 留空 | | +| **Endpoints** | 留空 | +| **Endpoint** | `https://<你的AccountID>.r2.cloudflarestorage.com` | 如果你在刚才保存过可以直接复制,切记最后没有`/`。 | +| **Bucket name** | 你的 R2 存储桶名称 (例如 `your-org-images-2025`) | | +| **Upload path** | `img/%y/%mo/%d/` | 按 `img/年/月/日/` 格式存放图片,有助于管理。ShareX 的变量格式是 `%`。(也可自定义) | +| **Use custom domain** | **勾选此项** | | +| (自定义域名输入框) | `https://<你记下的r2.dev公共URL>` | **注意**:这里只填刚才你记下的Public Development URL,不要加后面的 `$key$`。ShareX 新版本会自动处理。 | + +#### 2.2.1 关键的高级 (Advanced) 设置 + +在 S3 配置窗口的最下方,找到 **Advanced** 区域,进行如下设置: + +- `Set public-read ACL on file`: **必须取消勾选**。R2 不支持此功能,勾选会导致 `403 Forbidden` 错误。 +- `Use path style request`: **必须勾选**。R2 需要这种格式的请求 URL。 + +### 2.3 将 S3 设置为默认图片上传器 + +1. 回到 ShareX 主界面。 +2. 点击 `Destinations` -> `Image uploader` -> `File uploader` -> 选择 `Amazon S3`。 + ![Code_nqStY1UhqR.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/Code_nqStY1UhqR.png) + +--- + +## 第三部分:优化 ShareX 工作流 + +### 3.1 自动复制 Markdown 链接 + +1. 在 ShareX 主界面,点击 `Task settings...`。 +2. 在弹出的窗口左侧选择下方的 **`Advanced`**。 + ![ShareX_ZWFVlZZu0W.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/ShareX_ZWFVlZZu0W.png) +3. 接着,点击 `After upload` 下方的 `ClipboardContentFormat`。 +4. 将里面的内容替换为markdown格式 `![$filename]($result)` 。 + +### 3.2 打开自动复制到剪切板 + +1. 在主页面的 `After upload tasks` -> 右侧选择 `Copy URL to clipboard`。 + ![ShareX_zf6qftjnu6.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/ShareX_zf6qftjnu6.png) + +### 3.3 修改热键 + +在主页面的 `Hotkey settings` 中可以修改热键,实现截图直接上传并复制markdown到剪贴板一条龙。 + +--- + +## 常见问题 (F.A.Q) 与排错 + +**Q1: 上传时报错 `(403) Forbidden`,怎么办?** +**A1:** 这是最常见的问题,请检查以下两项 S3 高级设置: + +1. 确保 **`Set public-read ACL on file`** **没有**被勾选。 +2. 确保 **`Use path style request`** **已经**被勾选。 +3. 如果依然报错,请重新生成一个**权限为 `Object Read & Write` 的 API Token** 并更新到 ShareX 中。 + +**Q2: 我不是想上传截图,我想上传我自己本地的别的图片** +**A2:** +**使用左侧的upload选项卡**: 在选项卡内选择你需要的上传内容。 +![ShareX_34TAgHco1T.png](https://pub-85d4dcece16844bf8290aa4b33608ccd.r2.dev/ShareX/2025/09/ShareX_34TAgHco1T.png) + +**Q3: PicGo 为什么不能用?** +**A3:** 在我的测试中,PicGo 的 S3 插件与 Cloudflare R2 存在一些兼容性问题,导致文件名和路径处理不正确。ShareX 的 S3 实现更标准,是目前更可靠的选择。 diff --git a/app/docs/CommunityShare/Geek/picturecdn.mdx b/app/docs/CommunityShare/Geek/picturecdn.mdx new file mode 100644 index 0000000..5e88989 --- /dev/null +++ b/app/docs/CommunityShare/Geek/picturecdn.mdx @@ -0,0 +1,159 @@ +--- +title: 如何部署你自己的Github图床-PictureCDN +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: e6udpzrorhvgeeda6xpy1e0s +--- + +# 一.什么是图床? + +- 图床,顾名思义,就是存储图片的“床”。它是一种在线服务,允许用户上传、存储和分享图片。通过图床,用户可以将图片上传到云端,然后获得一个链接,可以在任何地方分享这个链接,而不需要担心图片的存储和带宽问题。 + +## 图床能解决什么问题? + +- 1.个人用户或者小型网站往往没有足够的服务器空间和带宽来存储和传输大量图片。图床提供了廉价甚至免费的图片存储解决方案,让用户可以无忧地存储大量图片。 +- 2.图床通常具备图片管理的功能,用户可以组织和管理自己的图片,方便查找和使用。 +- 3.性能问题:图床往往拥有强大的服务器和CDN(内容分发网络),可以快速地向全球用户传输图片,提高图片的加载速度。这对于提升用户体验至关重要。 + +# 二.在github上面创建图床仓库 + +## 2.1 创建仓库 + +![创建仓库](https://ravencaffeine.github.io/PictureCDN/images/20250927160645295.png) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645296.png) + +## 2.2 生成token令牌 + +- 点击右上角的头像或照片。 +- 从下拉菜单中选择 “Settings”。 + ![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645297.png) + +### 2.2.1 在 “Developer settings” 页面中,点击 “Personal access tokens”。 + +- 在左侧菜单中,点击 “Developer settings”。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645298.png) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645299.png) + +### 2.2.2 Generate new token (classic) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645300.png) + +- 在 “Note” 字段中,输入一个描述性名称,以便你记住这个令牌的用途。 +- 选择令牌的 “Expiration” 日期。你可以选择让令牌永不过期,或者设置一个过期日期。 +- 选择令牌的 “Scopes” 或权限。根据你利用令牌的目的,选择合适的权限。例如,如果你只需要访问仓库内容,可以选择 “repo” 权限。 +- 下划,点击 “Generate token” 按钮。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645301.png) + +- 一旦生成令牌,你将看到令牌的明文。请立即复制并保存这个令牌到一个安全的地方。这是你唯一一次看到这个令牌的机会。 +- 之后,你将无法查看这个令牌的明文,只能看到它是否仍然使用。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645302.png) + +注意: + +- 个人访问令牌非常敏感,应像密码一样保护。不要将其泄露给他人,也不要将其硬编码在代码中。 +- 如果你怀疑令牌的安全性受到了威胁,应立即在 GitHub 设置中撤销该令牌。 +- 通过GitHub 令牌能够用于执行与你的 GitHub 账户相关的各种操作,因此请谨慎选择令牌的权限。 + +# 三. PicGo + +PicGo 是一个开源的图片上传工具,支持多种图床服务,如七牛云、又拍云、SM.MS等。它可以帮助用户将本地图片上传到图床,并生成图片链接,方便在社交媒体、博客或论坛中分享。 + +## 3.1 PicGo能解决什么问题? + +1. 图片上传:PicGo可以快速上传本地图片到图床,节省用户手动上传的时间。 +2. 图片管理:PicGo支持图片的批量上传和管理,用户可以方便地查找和使用已上传的图片。 +3. 图片链接生成:PicGo在上传图片后,会自动生成图片链接,方便用户在各种平台上分享。 +4. 图床切换:PicGo支撑多种图床服务,用户允许根据要求随时切换图床,灵活应对不同的需求。 + +## 3.2 下载地址 + +- 山东大学镜像 +- https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo/v2.3.1/ +- 原Github仓库地址可在release下载 +- https://github.com/Molunerfinn/PicGo +- 下载后在左下角小图标打开 + ![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645303.png) + +## 3.3 配置Github图床 + +- 打开图床设置 +- 打开Github + ![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645304.png) + +- 设定仓库名,实际上就是用户名+仓库名。这个仓库就是你刚刚在2.1中新建的仓库。 +- 设定分支名:就是你的仓库在哪个分支里,就填哪个分支。一般有main分支,master分支等。我这里是main分支 +- 设定Token:就是刚刚在2.2中大家复制的Token字符串。将其填进去即可。 +- 设定存储路径,一般来说,它行是你项目仓库中的一个文件夹。大家这里就统一设置成了img/,会自动创建文件夹的 +- 设置自定义域名。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645305.png) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645306.png) + +# 四.Github Pages设置自定义域名 + +## 4.1 GitHub Pages 的基本限制 + +GitHub Pages 是一种静态网站托管服务,允许用户通过 GitHub 仓库创建网站。根据 GitHub 的规则,GitHub Pages 分为以下三种类型: + +#### 1. 用户/组织站点(User/Organization Site): + +- 每个 GitHub 账户(用户或组织)只能创建一个用户或组织站点。 +- 用户站点的仓库必须命名为 .github.io(例如 jacinli.github.io),组织站点则是 .github.io。 +- 域名格式为 https://.github.io 或 https://.github.io。 + 限制:每个账户只能有一个用户/组织站点。 + +#### 2. 项目站点(Project Site): + +- 项目站点与特定的 GitHub 仓库相关联,域名格式为 https://.github.io/。 +- 每个 GitHub 仓库都可以启用一个项目站点。 +- 限制:项目站点的数量没有明确上限,你可以为每个仓库创建一个项目站点(只要你的仓库数量不受限)。 + ####3. GitHub Pages 的总体限制: +- GitHub Pages 源仓库建议大小不超过 1GB。 +- 已发布的 GitHub Pages 站点总大小不得超过 1GB。 +- 每月带宽限制为 100GB(软限制)。 +- 每小时构建次数限制为 10 次(软限制,适用于使用 GitHub Actions 构建的情况)。 + +## 4.2 启用 GitHub Pages + +- 如果你想用更简洁的 URL 访问图片,可以启用 GitHub Pages: +- 进入你的 GitHub 仓库,点击 “Settings”。 +- 向下滚动到 “Pages” 部分。 +- 在 “Source” 下拉菜单中选择 main 分支(或 master,取决于你的默认分支),然后点击 “Save”。 +- 稍等片刻,GitHub 会生成一个 Pages 链接(比如 https://your-username.github.io/image-hosting)。 +- 返回 PicGo,在 GitHub 图床设置中将 “自定义域名” 设为这个 Pages 链接。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645308.png) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645309.png) + +# 五.图床上传 + +- 拖拽上传: + 1. 打开 PicGo 窗口,将图片拖到窗口中,PicGo 会自动上传。 +- 手动选择文件: + 1. 点击 PicGo 窗口中的 “上传” 按钮,选择本地图片文件上传。 + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645310.png) + +![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645311.png) + +- 可以看到仓库里也自动添加了 + ![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645312.png) + +- 可以获取图片的链接地址了 + ![](https://ravencaffeine.github.io/PictureCDN/images/20250927160645313.png) + +# 参考文章 + +- 1.完整教程:2025最新在GitHub上搭建个人图床,保姆级图文教程,实现图片高效管理 + https://www.cnblogs.com/ljbguanli/p/18928090 +- 2.CSDN博主「三金C_C」Picgo 配置--免费图床使用 +- 原文链接:https://blog.csdn.net/QAZJOU/article/details/146449613 diff --git a/app/docs/CommunityShare/Life/unsw-student-benefit.md b/app/docs/CommunityShare/Life/unsw-student-benefit.md new file mode 100644 index 0000000..a935456 --- /dev/null +++ b/app/docs/CommunityShare/Life/unsw-student-benefit.md @@ -0,0 +1,58 @@ +--- +title: UNSW学费回收计划-那些你还不知道的隐藏福利 +description: "" +date: "2025-10-03" +tags: + - tag-one +docId: jgyg6bp0nceyrxirz5qw3zsv +--- + +最近和同学聊天,发现学校有好多隐藏福利很多同学都不知道,不光是就业方面还有学业方面的各种福利。那么今天学姐就来打破信息差,为大家整理一个校园隐藏福利福利合集,还有其他更多隐藏福利欢迎大家评论区补充哦。 + +## 学生专属 Apple 教育商店 + +UNSW 学生可以直接通过 Apple 教育商店购买 iPad、MacBook 等产品,价格比正常渠道便宜,最高可以有 12%的折扣,而且在每年 2 月左右,还经常有 back to uni 活动,可以赠送 AirPods 或者 240 刀的礼品卡等。 +[Apple unsw 专属教育优惠商店入口](https://www.apple.com/au_edu_800094/store) +小提示:unsw 的专属链接一般会比普通的教育优惠便宜一点点,大家不要进错了链接哦 + +## UNSW Career Discovery Mentoring Program + +UNSW 的职业导师计划为期 9 周,包含前期培训和目标设定、在线职业发展课程、五小时一对一导师辅导(线上或线下)、最终反思总结并获得 AHEGS 认证。 +一般这个项目只在 Term1 和 Term3 开放,大家记得及时去官网查看报名截止日期。学姐参加的这一期据说有 700 多个学生报名,有些报名的同学遗憾的没有匹配上导师,所以申请材料一定要认真写哦! + +[详细项目介绍](https://www.unsw.edu.au/employability/discover/unsw-career-discovery-mentoring-program) +这个项目很多大牛导师,包括 MIT 的大佬,不光教你很多行业的知识,有的还会给你内推。不少学生通过该项目获得行业内部实习推荐,对职业发展帮助很大哦。 + +## Internships & Job Portal + +UNSW 官方就业机会平台提供各类实习、兼职与全职岗位,涵盖计算机、工程、金融、市场、科研等方向,并可根据签证状态筛选。之前很多同学投了简历都不知道为什么秒拒,其实很多时候也是签证不符合要求。通过这个筛选功能,能搜索哪些公司是接收学生签和 485 工签的,可以大大提高申请效率哦! + +[实习与工作机会平台](https://unswconnect.unsw.edu.au/careers?disciplines=computer-science&disciplines=engineering-software&job_type=internships&work_rights=au-australian-working-visa&work_rights=au-australian-student-visa&work_rights=au-australian-skilled-migration-visa-485&work_rights=au-australian-bridging-visa) +许多大厂和知名企业都会在此平台优先发布招聘信息。 + +## UNSW 校内岗位招聘 + +除了外部实习与合作项目,UNSW 本校也会招聘学生助理、研究助理、行政支持等校内岗位。这些工作通常灵活,适合学生课余时间安排。 + +[UNSW 校内岗位招聘网站](https://external-careers.jobs.unsw.edu.au/cw/en/listing/) +不少同学通过校内岗位不仅获得了收入,还积累了宝贵的工作经验,并能与学院和科研团队建立紧密联系。 + +## 海外交换机会 + +UNSW 的交换项目资源十分强大,合作院校包括苏黎世联邦理工(ETH Zurich)、宾夕法尼亚大学沃顿商学院(UPenn Wharton)以及欧洲、亚洲、北美的众多顶尖院校。 +[搜索合作院校](https://www.unsw.edu.au/student/opportunities/overseas-study/search-exchange-partners) +交换不仅能修读学分,还能体验不同文化,结交国际友人。 而且有去沃顿商学院的学长说,通过交换项目无需支付高昂的美国院校的学费,只需要正常走 unsw 的学费支付流程。这么一看,分分钟省下几千刀啊! + +## 其他学生福利 + +Oweek 新生可以领取免费的 hoodie 还有各种社团福利 + +UNSW Fitness & Aquatic Centre 学生折扣:健身房和游泳池价格远低于校外。热门的 unsw 游泳课只需要 75 刀就可游泳一学期,这个福利还不快抓住! + +Arc 学生会员:可免费参加社团活动,领取 Welcome Pack,并享受各类活动折扣。比如可以去arc sports搜一下global sports,之前看到有同学分享 16.5 刀就可参加原价 95 刀的皮划艇等活动,但是名额有限,大家要随时关注这类活动哦! + +免费心理咨询:可预约中英文心理咨询服务,这些都是学生保险都可以报销的。 + +UNSW Library:提供丰富的电子数据库和免费借阅服务。 + +职业发展讲座与 Workshop:包括简历修改、模拟面试和行业嘉宾讲座等等。 diff --git a/app/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo.md b/app/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo.md new file mode 100644 index 0000000..123c888 --- /dev/null +++ b/app/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo.md @@ -0,0 +1,145 @@ +--- +title: PPO +description: "" +date: "2025-10-03" +tags: + - tag-one +docId: zf8zk108oqbsg56xjyqb5txk +--- + +# PPO + +## 1.完整链路 + +prompt batch -> actor.forward -> reward model -> critic.forward + +## 2.算法实现方式: + +1. 重要性采样:使用一次采样,进行多次更新,在尽可能高效率使用样本的前提下,尽可能的修正老策略与新策略的偏差 +2. 裁剪(常用),KL约束:现在新旧策略的偏移,防止梯度爆炸或者坍塌 + +### 1.公式 + +1. 概率比 + $$r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$$ + +$\pi:策略$ +$\theta:参数$ +$a:动作$ +$s:状态$ +$t:时间$ + +个人理解:新旧策略在同一状态下决策的相对差异 + +2. 裁剪目标 + +$$L^{CLIP}(\theta) = \mathbb{E}_t[min(r_t(\theta)\hat{A}_t,clip(r_t(\theta),1-\epsilon,1+\epsilon)\hat{A}_t)]$$ + +$L:损失函数$ +$\mathbb{E}_t:对时间步t时的重要性采样结果的期望$ +$\hat{A}_t:当前时间步的优势函数$ +$\epsilon:裁剪系数$ + +个人理解:用裁剪可以限制更新步长,既不要太大也不要太小,保证稳定性 + +3. 优势函数: + +- GAE(广义优势估计,Generalized Advantage Estimation) + +$$A^{GAE}(a_t,s_t) = \sum^{\infty}_{l=0}(\gamma\lambda)^l\delta_{t+l}$$ + +$$\delta_t = r_t+\gamma V(s_{t+1})-V(s_t)$$ + +$\gamma:折扣因子$ +$r:奖励值$ +$l:延迟的步数$ +$\lambda :控制TD的偏差与方差$ +$\lambda = 1:相当于蒙特卡洛回报,即保留了每一个时间步的TD$ +$\lambda = 0:相当于单步TD$ +$0<\lambda<1:保留了每一个时间步的TD,但是每一个TD有着不同的权重$ + +4. 值函数回归 + +$$L^{value} = \frac{1}{2}(V_{\theta}(s_t)-\hat{R}_t)^2$$ + +$V_{\theta}(s_t)$:t时状态为时的价值函数,由一个mlp拟合 + +5. 熵正则化(鼓励探索) + $$H(\pi_{\theta})=\mathbb{E_t}[-\sum_{\alpha}\pi_{\theta}(\alpha|s_t)log\pi_\theta(\alpha|s_t)]$$ + +注:说白了就是求信息熵取平均值 + +6. 总损失 + +$$L^{PPO} = L^{CLIP}(\theta)-c_1L_{value}+c_2H(\pi_{\theta})$$ + +## 2.流程详解 + +### 1.初始阶段 + +#### 1.prompt batch: + +就是一批数据 + +#### 2.actor.forward: + +- 使用主干网络对每一条数据生成回复,并逐步保存每一个token的logits作为$log\pi_{old}$ + +#### 3.reward + +- **model** + +作用:用于打分获取奖励值,分数用于上述公式构造损失函数,将奖励值r存入bufferi以供后续训练使用 + +构造:一个mlp + +位置:接在骨干网络最后一层 + +优点:泛化能力强 + +缺点:需要大量有标注数据,可解释性差,稳定性差,计算需要资源多 + +- **function** + +使用具体规则奖励,如编辑距离,重复度等 + +优点:计算简单迅速,可解释性强,稳定性强, + +缺点:泛化能力差,仅在特定场景下有效 + +#### 4.critic.forward + +作用:用于拟合骨干网络的价值函数,计算出优势函数,存入buffer,供后续训练使用 + +构造:一个mlp + +位置:接在骨干网络左右一层 + +### 2.训练阶段 + +使用buffer里面保存的数据反复训练模型,几轮后重新采样 + +### 3.网络结构 + +```mermaid +graph TD + Inputs--> b[Backbone network] + b --(optional)--> reward + b --> crtirc +``` + +### 4.数据集构造 + +- reward function + +```json +{ +role: user, +content: <完整上下文/包含用于区分角色的特殊token> +} +solution: <模型回答正例或正例组>:用于reward function +``` + +# 特别注意 + +如果有内容错误或者解释不清楚,请联系本人进行修改。vx: m1197501753 diff --git a/app/docs/CommunityShare/RAG/context_engineering_intro.md b/app/docs/CommunityShare/RAG/context_engineering_intro.md new file mode 100644 index 0000000..dc4d966 --- /dev/null +++ b/app/docs/CommunityShare/RAG/context_engineering_intro.md @@ -0,0 +1,70 @@ +--- +title: context engineering 快速了解 +description: "" +date: "2025-10-03" +tags: + - tag-one +docId: wdqqrepoy43jiieyyjmaekk1 +--- + +# context engineering 快速了解 + +## 一些基本概念 + +解决问题: + +- 大多数模型的context window 非常有限 +- 输入信息杂乱影响模型理解 +- 输入越多,成本越高(token太贵了) + +context :模型输入。用户问题,背景信息,相关资料,可用工具列表,工具执行结果,历史对话等——模型基于这些内容来生成答案 + +context window:模型输入容量上限。模型输入中最多能包含的token数量,例如Gemini 2.5 pro 的context window 是100万,代表其能处理100万的token输入。 + +context engineering:精心设计给模型的输入内容。让模型在有限的context window内尽可理解的更准,答的更好,花的更少。 + +我们经常遇见的大模型会遗忘我们输入的信息就是因为context window大小受限。 + +context engineering 对于agent的构建非常重要。 + +## 实现方法 + +### 保存context + +比较典型的例子是gpt的长记忆功能。 + +用一个数据库/硬盘等存储我们想要模型记住的上下文信息。 + +### 选择context + +从海量信息里选择与用户提问最相关的信息。 + +静态选择:例如指导模型回答问题的系统prompt,确保模型输出安全可靠输出。—-必须放入context + +动态选择:选择与用户问题最相关的内容,例如gpt从长记忆库里面挑选内容放入context,例如agent选择与当前任务相关的工具来调用。 + +rag是一种动态选择实现的工具。 + +### 压缩context + +context里面最占空间的两类数据:模型输出文本,工具执行结果 + +Claude code 4 的实践:每当上下文到一定的数量,就执行auto- compact。扔到本身的信息,只在context里面保存对原本信息的总结。 + +### 隔离context + +通常出现在multi agent 场景 + +Anthropic的实践: + +![Anthropic的实践](https://img.coly.cc/obs-img/2025/10/7110909d5366ba7747f037ae9300f7bc.png) + +不同agent有自己独立的工具,独立的运行历史,独立的记忆体系。 + +这些agent的context是互相独立的。 + +### 进一步学习 + +langchain—context engineering http://blog.langchain.com/context-engineering-for-agents/ + +cognition: https://cognition.ai/blog/dont-build-multi-agents diff --git a/app/docs/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.md b/app/docs/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.md new file mode 100644 index 0000000..0b3d18a --- /dev/null +++ b/app/docs/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.md @@ -0,0 +1,170 @@ +--- +title: Introduction of Multi-agents system(In any task you want) +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: h53uwefhlykt9ietsx9x0vtn +--- + +# Introduction of Multi-agents system(In any task you want) + +Multi-Agent System(多智能体系统)概览 + +1. 什么是 Multi-Agent System(多智能体系统, MAS)? + +多智能体系统(MAS)指由多个相对自治的 智能体(agent) 组成、在共享环境中交互、协作或竞争以达成个体或群体目标的计算系统。 +它关注的并非单个智能体的最优行为,而是群体层面的组织、协调与涌现行为。 +注:涌现行为即为多智能体交互协作后出现的单个智能体无法完成的行为,如:鸟群都遵循一定的规则飞行最终形成了优雅的队形可以对抗气流,而这个队形预先没有被设计过 +直观理解:可把 LLM 作为多个“角色”来 模拟团队/部门协作,共同完成任务。 + +2. 典型应用与问题类型 + +现实分布式问题:电网调度、智慧交通、供应链、灾害应对等——天然具备分布式、动态与不确定特性,单体系统难以兼顾全局最优与鲁棒性。 + +研究方向示例:generation、translation、repair、judge 等。 + +3. 多智能体的核心概念 + +3.1 智能体(Agent) + +在环境中 感知(Perception)—决策(Deliberation/Policy)—行动(Action) 的计算实体。 + +典型特性:自治性、反应性、前瞻性(主动性)、社会性(可交互)。 + +3.2 环境(Environment) + +智能体感知与行动的客体;可为 完全/部分可观测、确定/随机、静态/动态、连续/离散。 + +| 维度 | 定义 | 特征 / 要点 | 典型例子 | 对 agent 设计的影响 | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| 完全可观测 vs 部分可观测 | agent 是否能在每个时刻感知环境的全部状态 | 若为完全可观测,agent 可直接基于当前状态做决策;若为部分可观测,则存在隐藏信息,agent 可能需要内部记忆与不确定性推理 | 国际象棋是完全可观测;扑克(看不到对手手牌)是部分可观测 | 在部分可观测环境中,agent 通常需要维护 **belief state**(对真实状态的概率分布)或内部状态模型,使策略更加复杂 | +| 确定性 / 随机性(Deterministic vs Stochastic / Nondeterministic) | 在给定状态 + 动作的情况下,是否有确定的下一状态 /结果,还是有多种可能 /概率分布 | 确定性环境:动作 + 当前状态唯一决定下一状态;随机 / 非确定性环境:存在多种可能转移,有概率分布 | 棋类游戏(如国际象棋)通常近似确定性;现实中的机器人操作、交通系统常有随机性 | 在随机环境里,agent 的策略要考虑期望 / 分布 / 风险,比如用概率策略、强化学习、健壮性设计 | +| 静态 / 动态 | agent 在作出决策 / 行动期间,环境是否可能发生变化 | 静态:在 agent 决策期间环境保持不变;动态:环境可能在 agent 思考 /行动时自行演化 | 若两方交替下棋,则在当前 agent 决策期间环境静止;交通系统是动态的,其他车辆 /行人持续变化 | 在动态环境中,agent 需具备快速响应能力、实时规划、预测未来等特性,不能长时间停留在高代价计算 | +| 离散 / 连续 | 环境的状态、动作、时间等是否构成离散 / 可枚举集合,还是连续 / 实数值域 | 离散环境:状态 /动作 /时间都是可枚举或离散的;连续环境:这些量在实数域或者实数区间变化 | 棋盘游戏、格子世界、回合制游戏是离散的;机器人位置 /速度 /加速度、无人机控制是连续的 | 在连续环境中,agent 通常需要用函数逼近(神经网络、控制模型)、连续策略、微分方程或连续动作优化;在离散环境中可用枚举、搜索、离散 RL 等方法 | + +3.3 交互(Interaction) + +形式包括 通信、协商、竞争、合作、博弈 等。 + +3.4 组织(Organization) + +角色、层级、规范、协议与团队结构 的总和。 + +| 组成要素 | 含义 / 功能 | 常见设计方式 / 例子 | 需要考虑的问题 / 权衡 | +| :------------------------------------------------: | :-------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | +| 角色(Roles) | 在组织里,每个 agent 扮演的功能定位与行为职责。角色抽象了行为接口与能力约束 | “Planner” 角色负责任务分解;“Executor” 角色负责执行;“Critic” 角色负责评估;“Communicator” 角色负责信息中转 | 职责要清晰,不要重叠太多,避免角色耦合过强;能力与资源分配要匹配 | +| 层级(Hierarchy) | 角色/agent 的上下级关系,指导控制、监督、指挥流向 | Manager/Worker 架构:高层 agent 做策略决策,低层 agent 做执行;多层嵌套(macro → meso → micro) | 层级能帮助控制复杂性、保持清晰指令流;但过多层级可能导致沟通瓶颈、延迟、中心故障 | +| 规范(Norms / Normative Rules) | 约定俗成或硬性规则,用来约束 agent 行为、协调冲突、确保安全 | 如“不得同时访问同一资源”、“优先响应紧急任务”、“不得跨角色越权” | 太松会混乱;太严会缺乏灵活性;需设计惩罚机制 / 合规检查机制 | +| 协议(Protocols / Interaction Protocols) | agent 之间如何通信、协商、交易、同步、谈判等的机制和约定 | 拍卖 (Auction)、契约网 (Contract Net)、谈判协议 (Negotiation Protocol)、共识协议 (Consensus) | 需要考虑性能(通信成本、延迟)、健壮性(异常处理、失败恢复)、表达能力(语义交互是否足够) | +| 团队结构(Team Structure / Coalitions / Grouping) | agent 如何被组织为子团队或协作小组,以及这些小组如何彼此协作 | 静态团队(固定组队)、动态团队(任务触发组队)、跨团队联盟 | 要适应任务需求与能力分布;动态结构增加灵活性但带来重组成本和协调开销 | + +3.5 目标(Goals/Utility) + +个体目标与全局社会福利可能 一致或冲突,涉及 机制设计。最终目的应指向 任务完成与效用最优。 + +4. 系统构成与典型架构 + +4.1 智能体内部架构 + +反射式/行为式(Reactive):如 subsumption(抑制/分层行为),响应快但规划能力弱。 + +BDI(Belief–Desire–Intention):以信念/愿望/意图建模理性决策,适合可解释规划场景。 + +学习型:基于 RL/监督/自监督;在 MARL 中可共享或独立训练策略。 + +LLM-Agent:以 大语言模型 为核心,结合 工具调用、记忆、检索、反思与执行器,擅长复杂推理与开放环境任务。 + +4.2 多智能体体系结构 + +集中式编排(Orchestrator):中央调度(如 Planner/Router)分配任务;全局视角强,但有 单点瓶颈。 + +分布式协同(Peer-to-Peer):各智能体平等交互;弹性高但 协议复杂。 + +分层/混合式(Hierarchical/Hybrid):上层规划、下层执行;兼顾全局与局部效率。 + +黑板(Blackboard)/共享记忆:通过公共工作区交换假设与部分解。 + +4.3 通信与协调机制 + +通信语言/协议:早期如 KQML、FIPA-ACL;工程上常用 MQ/HTTP/gRPC 与结构化消息(JSON/Proto)。 + +4.4 协调方式 + +契约网(Contract Net)与拍卖/竞价:适合任务分派与资源竞争。 + +协商/投票/共识:如 Paxos/Raft 或多方投票策略。 + +编队/编组与角色切换:队形控制、动态角色分配。 + +机制设计:通过激励相容规则引导个体理性行为产生期望群体结果。 + +组织结构:层级(Hierarchy)、合弄(Holarchy)、团队/联盟(Team/Coalition)、基于角色与规范(Roles & Norms) 的社会化组织。 + +4.5 多智能体强化学习(MARL)要点 + +非平稳性:他人策略变化使环境对单体呈现非静态,训练更难。 + +训练-执行范式:集中式训练、分布式执行(CTDE) 常见。 + +4.6 方法族(举例) + +值分解:VDN、QMIX 将全局价值分解为个体价值。 + +Actor-Critic:如 MADDPG(集中式 Critic、分布式 Actor)。 + +对手建模/博弈学习:纳什均衡、可转移策略、元学习。 + +关键挑战:信用分配、可扩展性、部分可观测、探索-利用平衡、通信带宽与延迟。 + +5. LLM 驱动的多智能体范式(Main Focus) + +5.1 角色分工 + +Planner(计划) + +Researcher(检索/分析) + +Coder/Executor(工具执行) + +Critic/Verifier(审查校验) + +Refiner(修复) + +5.2 协作模式 + +辩论/对话式求解(Debate/Deliberation):互评提升推理稳健性。 + +反思与记忆(Reflection/Memory):总结经验、长期记忆库、外部知识检索。 + +图式编排(Graph-of-Agents):以 DAG/状态机 显式表达任务流程。 + +5.3 工程要点 + +Prompt 模板化 + +工具/数据库/代码执行器接入 + +消息路由与缓存 + +成本与延迟控制 + +安全防护(越权/数据泄露/注入) + +6. 经典论文/工作推荐 + +AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation + +CAMEL: Communicative Agents for “Mind” Exploration of LLM Society + +Improving Factuality and Reasoning in Language Models through Multi-Agent Debate + +Should We Be Going MAD? A Look at Multi-Agent Debate + +Reflexion: Language Agents with Verbal Reinforcement Learning + +Self-Refine: Iterative Refinement with Self-Feedback + +Language Agents as Optimizable Graphs (GPTSwarm) + +Graph of Thoughts: Solving Elaborate Problems with LLMs diff --git a/app/docs/ai/ai-math-basics/math_books.md b/app/docs/ai/ai-math-basics/math_books.md new file mode 100644 index 0000000..6e23fec --- /dev/null +++ b/app/docs/ai/ai-math-basics/math_books.md @@ -0,0 +1,119 @@ +--- +title: Recommended Books on Mathematics and Data Science +description: "" +date: "2025-10-06" +tags: + - tag-one +docId: kzi6k1yg1sehlxidnxdsf59a +--- + +# Recommended Books on Mathematics and Data Science + +## 导读 + +### A. 机器学习 / 数据科学(初学者–工程师) + +- 数据有道,编程不难 +- 鸢尾花丛书 ③④⑤⑦(数学要素/矩阵力量/统计至简/机器学习) +- Math with Bad Drawings +- Numbers Don’t Lie +- The Ten Equations That Rule the World + +### B. 概率 / 统计 / 贝叶斯(研究者–高阶爱好者) + +- 概率论沉思录 +- 利己主义的数学解析 + +### C. 数学基础 & 通识(高中–本科及大众) + +- 什么是数学 +- 数学与生活 1 / 2 / 3 +- 怪曲线、数兔子… + +### D. 竞赛 / 题库 / 参考手册(学生–教师) + +- 伯克利数学问题集 +- 数学手册 + +### E. 数学史 & 传记 & 哲学(对文化背景感兴趣者) + +- 从数学到哲学 +- 哥德尔传 +- 一个数学家的辩白 + +--- + +## 1. 鸢尾花丛书系列 + +- [鸢尾花书系列 01·编程不难](https://space.bilibili.com/513194466?spm_id_from=333.337.search-card.all.click) + — **Dr Ginger 姜伟生**。给完全没有编程经验的人做 Python 入门,覆盖变量、流程控制、函数、面向对象。 + +- 《鸢尾花丛书·数据有道》 + 以 Python 为主线,面向零基础读者讲述数据获取、清洗、可视化及简单分析流程,属于“数据科学入门读物”。 + +- 《鸢尾花丛书·数学要素(第三册)》 + 机器学习所需的线性代数、微积分、概率统计快速补课。 + +- 《鸢尾花丛书·矩阵力量(第四册)》 + 专门聚焦线性代数与矩阵分解(SVD、特征值、奇异值)在 ML 中的应用。 + +- 《鸢尾花丛书·统计至简(第五册)》 + 以直观案例讲 Bayes、假设检验、回归分析等核心统计概念。 + +- 《鸢尾花丛书·机器学习(第七册)》 + 用鸢尾花数据集贯穿,讲解监督 / 无监督学习常用算法与 scikit-learn 实战。 + +- [Math with Bad Drawings](https://www.goodreads.com/book/show/36205393-math-with-bad-drawings) + — **Ben Orlin**。用“坏画”漫画解读函数、统计、博弈论,轻松幽默。 + +- [Numbers Don’t Lie:71 Things You Need to Know About the World](https://www.goodreads.com/book/show/50705179-numbers-don-t-lie) + 以数据与数量级视角解读能源、人口、技术等全球议题。 + +- [The Ten Equations That Rule the World and How You Can Use Them](https://www.goodreads.com/book/show/55607293-the-ten-equations-that-rule-the-world) + 从线性回归到幂律的 10 个方程,展示其在金融、社交网络、体育中的预测威力。 + +--- + +## 2. 竞赛 / 工具书 + +- [伯克利数学问题集(第 3 版)](https://book.douban.com/subject/2066460/) — P. N. Sousa 等 + 收录伯克利大学入学 / 竞赛类典型题目,侧重分析、代数与组合;附有详解。 + +- [数学手册](https://book.douban.com/subject/20418732/) — 四川矿业学院编 + 公式查阅型工具书,含常用代数、三角、微积分、统计表格。 + +--- + +## 3. 概率 / 贝叶斯 + +- [概率论沉思录](https://wap.sciencenet.cn/blog-1319915-1449152.html?mobile=1) — Edwin T. Jaynes + 主张用最大熵与贝叶斯视角统一概率论,被誉为“贝叶斯圣经”。 + +- [利己主义的数学解析](https://book.douban.com/subject/27150468/) — Karl Sigmund + 进化博弈论入门,用囚徒困境解释合作与利他行为数学模型。 + +--- + +## 4. 科普读物 + +- [怪曲线、数兔子及其他数学探究](https://book.douban.com/subject/6985480/) + 面向大众的趣味数学随笔,话题含分形、斐波那契、拓扑悖论等。 + +- [什么是数学](https://book.douban.com/subject/10455982/) — 柯朗 & 罗宾(经典新版) + 数学文化通识书,覆盖数论、几何、微积分、无穷概念。 + +- [数学与生活](https://book.douban.com/subject/26148739/) 1 / 2 / 3 — 远山启 + 日本经典科普系列,以生活场景说明数学思想、方法与趣味。 + +--- + +## 5. 数学史・传记・哲学 + +- [一个数学家的辩白](https://book.douban.com/subject/2135227/) — G. H. Hardy + 数学美学与研究心路的自白,文学性强。 + +- [哥德尔传](https://book.douban.com/subject/36073022/) — John Dawson + 系统梳理哥德尔生平、学术轨迹及不完备定理的背景影响。 + +- [从数学到哲学](https://book.douban.com/subject/36532721/) — 王浩 (Hao Wang) + 探讨形式逻辑、哥德尔不完备性、数学基础与哲学命题的关系。 diff --git a/app/docs/ai/llm-basics/deep-learning/misc/index.mdx b/app/docs/ai/llm-basics/deep-learning/misc/index.mdx index 0b7bcb7..faef1a6 100644 --- a/app/docs/ai/llm-basics/deep-learning/misc/index.mdx +++ b/app/docs/ai/llm-basics/deep-learning/misc/index.mdx @@ -12,6 +12,13 @@ docId: lodydcd211esraq1r55ze9ey - **适用人群**: 希望深入理解算法数学原理的学习者 - **内容特色**: 详细的数学推导和证明过程 +## 李宏毅深度学习笔记 + +- **github库**: https://github.com/datawhalechina/leedl-tutorial         +- **核心价值**: 阅读门槛很低,深度学习的各个方面都包含了 +- **适用人群**: 没有学过机器学习可以直接看 +- **内容特色**: 对数学不好的人比较友好 + ## 花书 (深度学习) - **作者**: Ian Goodfellow, Yoshua Bengio, Aaron Courville diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_crossing.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_crossing.mdx new file mode 100644 index 0000000..a091353 --- /dev/null +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_crossing.mdx @@ -0,0 +1,257 @@ +--- +title: 王树森推荐系统学习笔记_特征交叉 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: hajz43iblku13mmevia8zrhv +--- + +# 王树森推荐系统学习笔记\_特征交叉 + +## 特征交叉 + +### Factorized Machine (FM) + +#### 线性模型 + +- 有 $d$ 个特征,记作 $ \mathbf{x} = [x_1, \cdots, x_d] $。 + +- **线性模型**: + + $$ + p = b + \sum_{i=1}^{d} w_i x_i。 + $$ + +- **模型有 $d + 1$ 个参数**:$ \mathbf{w} = [w_1, \cdots, w_d] $ 和 $b$。 + +- **预测是特征的加权和**。(_只有加,没有乘。_) + +#### 二阶交叉特征 + +- **有 $d$ 个特征,记作** $ \mathbf{x} = [x_1, \cdots, x_d] $。 + +- **线性模型 + 二阶交叉特征**: + + $$ + p = b + \sum_{i=1}^{d} w_i x_i + \sum_{i=1}^{d} \sum_{j=i+1}^{d} u_{ij} x_i x_j。 + $$ + +- **模型有 $O(d^2)$ 个参数**。 + +**线性模型 + 二阶交叉特征**: + +$$ +p = b + \sum_{i=1}^{d} w_i x_i + \sum_{i=1}^{d} \sum_{j=i+1}^{d} u_{ij} x_i x_j。 +$$ + +$$ +u_{ij} \approx v^T_iv_j +$$ + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-1-1.png) + +矩阵 $U$ $d$ 行 $d$ 列,矩阵 $V$ $d$ 行 $k$ 列,矩阵 $V^T$ $k$ 行 $d$ 列。 + +- **Factorized Machine (FM)**: + + $$ + p = b + \sum_{i=1}^{d} w_i x_i + \sum_{i=1}^{d} \sum_{j=i+1}^{d} \left( \mathbf{v}_i^T \mathbf{v}_j \right) x_i x_j。 + $$ + +- **FM 模型有 $O(kd)$ 个参数**。($k \ll d$) + +#### Factorized Machine + +- FM 是线性模型的替代品,能用线性回归、逻辑回归的场景,都可以用 FM。 +- FM 使用二阶交叉特征,表达能力比线性模型更强。 +- 通过做近似 $ u\_{ij} \approx \mathbf{v}\_i^T \mathbf{v}\_j $,FM 把二阶交叉权重的数量从 $ O(d^2) $ 降低到 $ O(kd) $**。** + +### 深度交叉网络(DCN) + +#### 召回、排序模型 + +双塔模型和多目标排序模型只是结构,内部的神经网络可以用任意网络。 + +#### 交叉层(Cross Layer) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-1.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-2.png) + +利用 Resnet 思想,防止梯度消失 + +#### 交叉网络 (Cross Network) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-3.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-4.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-5.png) + +**深度交叉网络 (Deep & Cross Network)** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-2-6.png) + +DCN 的实际效果优于全连接,可以用于双塔模型中的用户塔和物品塔,多目标排序模型中的 shared bottom 神经网络,以及MMoE中的专家神经网络。 + +### Learning Hidden Unit Contributions (LHUC) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-3-1.png) + +神经网络中的结构为[多个全连接层] ➝ [Sigmoid 乘以 2],这样神经网络的输出向量中都是 0 到 2 之间的数。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-3-2.png) + +### SENet & Bilinear Cross + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-1.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-2.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-3.png) + +- **SENet 对离散特征做 field-wise 加权。** + +- **Field**: + - 用户 ID Embedding 是 64 维向量。 + - 64 个元素(即一个特征的 embedding 向量)算一个 field,获得相同的权重。 + - 特征越重要,获得的权重越大。 + +- **如果有 $m$ 个 fields,那么权重向量是 $m$ 维。** + +#### Field 间特征交叉 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-4.png) + +**内积** + +$x^T_i$ 和 $x_j$ 都是特征的 embedding 向量,$f_{ij}$ 是一个实数,如果有 $m$ 个 field,他们之间两两内积,就会有 $m^2$ 个实数。 + +**哈达玛乘积** + +$x^T_i$ 和 $x_j$ 都是特征的 embedding 向量,$f_{ij}$ 是一个向量,如果有 $m$ 个 field,他们之间两两哈达玛乘积,就会有 $m^2$ 个向量。量太大,需要人工指定一部分向量做交叉,而不是所有向量都交叉。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-5.png) + +**Bilineard Cross(内积)** + +如果有 $m$ 个 field,就会有 $m^2$ 个实数 $f_{ij}$,$m^2/2$ 个参数矩阵 $W_{ij}$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-6.png) + +**Bilineard Cross(哈达玛)** + +如果有 $m$ 个 field,就会有 $m^2$ 个向量 $f_ij$,$m^2/2$ 个参数矩阵 $W_{ij}$。 + +#### FiBiNet + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/4-4-7.png) + +## 行为序列 + +### 用户行为序列建模 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/5-1-1.png) + +**LastN 特征** + +- **LastN**:用户最近的 $n$ 次交互(点击、点赞等)的物品 ID。 +- 对 **LastN** 物品 ID 做 embedding,得到 $n$ 个向量。 +- 把 $n$ 个向量取平均,作为用户的一种特征。 +- 适用于召回双塔模型、粗排三塔模型、精排模型。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/5-1-2.png) + +### DIN 模型 + +**DIN 模型** + +- DIN 用 加权平均 代替 平均,即注意力机制(attention)。 +- 权重:候选物品与用户 **LastN** 物品的相似度。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/5-2-1.png) + +**DIN 模型** + +- 对于某候选物品,计算它与用户 **LastN** 物品的相似度。 +- 以相似度为权重,求用户 **LastN** 物品向量的加权和,结果是一个向量。 +- 把得到的向量作为一种用户特征,输入排序模型,预估(用户,候选物品)的点击率、点赞率等指标。 +- 本质是注意力机制(attention)。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/5-2-2.png) + +**简单平均 v.s 注意力机制** + +- _简单平均_ 和 注意力机制 都适用于精排模型。 +- _简单平均_ 适用于双塔模型、三塔模型。 + - _简单平均_ 只需要用到 **LastN**,属于用户自身的特征。 + - 把 LastN 向量的平均作为用户塔的输入。 +- 注意力机制 不适用于双塔模型、三塔模型。 + - 注意力机制 需要用到 **LastN** + **候选物品**。 + - 用户塔看不到候选物品,不能把 注意力机制 用在用户塔。 + +### SIM模型 + +**DIN 模型** + +- 计算用户 **LastN** 向量的加权平均。 +- 权重是候选物品与 LastN 物品的相似度。 + +**DIN 模型的缺点** + +- 注意力层的计算量 $\propto n$(用户行为序列的长度)。 +- 只能记录最近几百个物品,否则计算量太大。 +- 缺点:关注短期兴趣,遗忘长期兴趣。 + +**如何改进** **DIN**? + +- **目标**:保留用户长期行为序列($n$ 很大),而且计算量不会过大。 + +- **改进 DIN**: + - DIN 对 **LastN** 向量做加权平均,权重是相似度。 + - 如果某 **LastN** 物品与候选物品差异很大,则权重接近零。 + - 快速排除掉与候选物品无关的 **LastN** 物品,降低注意力层的计算量。 + +#### SIM 模型 + +- 保留用户长期行为记录,$n$ 的大小可以是几千。 +- 对于每个候选物品,在用户 **LastN** 记录中做快速查找,找到 $k$ 个相似物品。 +- 把 **LastN** 变成 **TopK**,然后输入到注意力层。 +- **SIM** 模型减小计算量(从 $n$ 降到 $k$)。 + +**第一步:查找** + +- **方法一:Hard Search** + - 根据候选物品的类别,保留 **LastN** 物品中类别相同的。 + - 简单,快速,无需训练。 + +- **方法二:Soft Search** + - 把物品做 **embedding**,变成向量。 + - 把候选物品向量作为 **query**,做 $k$ 近邻查找,保留 **LastN** 物品中最近的 $k$ 个。 + - 效果更好,编程实现更复杂。 + +**第二步:注意力机制** + +**使用时间信息** + +- 用户与某个 **LastN** 物品的交互时刻距离今为 $\delta$。 +- 对 $\delta$ 做离散化,再做 **embedding**,变成向量 **d**。 +- 把两个向量做 **concatenation**,表征一个 **LastN** 物品。 + - 向量 **x** 是物品 **embedding**。 + - 向量 **d** 是时间的 **embedding**。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/5-3-1.png) + +为什么 SIM **使用时间信息**? + +- **DIN** 的序列短,记录用户近期行为。 +- **SIM** 的序列长,记录用户长期行为。 +- 时间越久远,重要性越低。 + +#### 结论 + +- 长序列(长期兴趣)优于短序列(近期兴趣)。 +- 注意力机制 优于 简单平均。 +- **Soft search** 还是 **Hard search**?取决于工程基建。 +- 使用时间信息有提升。 diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_note_coldstart.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_note_coldstart.mdx new file mode 100644 index 0000000..26d57e4 --- /dev/null +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_note_coldstart.mdx @@ -0,0 +1,496 @@ +--- +title: 王树森推荐系统学习笔记_冷启动 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: bvccoatft6y7bph83oivdcfe +--- + +# 王树森推荐系统学习笔记\_冷启动 + +## 物品冷启动 + +### 物品冷启动:评价指标 + +**物品冷启动** + +- 小红书上用户新发布的笔记。 +- B 站上用户新上传的视频。 +- 今日头条上作者新发布的文章。 + +**新笔记冷启动** + +- 新笔记缺少与用户的交互,导致推荐的难度大、效果差。 +- 扶持新发布、低曝光的笔记,可以增强作者发布意愿。 + +**优化冷启的目标** + +1. 精准推荐:克服冷启的困难,把新笔记推荐给合适的用户,不引起用户反感。 + +2. 激励发布:流量向低曝光新笔记倾斜,激励作者发布。 + +3. 挖掘高潜:通过初期小流量的试探,找到高质量的笔记,给予流量倾斜。 + +**评价指标** + +- 作者侧指标: + - 发布渗透率、人均发布量。 + +- 用户侧指标: + - 新笔记指标:新笔记的点击率、交互率。 + - 大盘指标:消费时长、日活、月活。 + +- 内容侧指标: + - 高热笔记占比。 + +#### 作者侧指标 + +**发布渗透率(penetration rate)** + +- 发布渗透率 = 当日发布人数 / 日活人数 +- 发布一篇或以上,就算一个发布人数。 +- **例**: + - 当日发布人数 = 100 万 + - 日活人数 = 2000 万 + - 发布渗透率 = 100 / 2000 = 5% + +**人均发布量** + +- 人均发布量 = 当日发布笔记数 / 日活人数 +- **例**: + - 每日发布笔记数 = 200 万 + - 日活人数 = 2000 万 + - 人均发布量 = 200 / 2000 = 0.1 + +发布渗透率、人均发布量反映出作者的发布积极性。 + +冷启的重要优化目标是促进发布,增大内容池。 + +新笔记获得的曝光越多,首次曝光和交互出现得越早,作者发布积极性越高。 + +#### 用户侧指标 + +**新笔记的消费指标** + +- 新笔记的点击率、交互率。 + - 问题:曝光的基尼系数很大。 + - 少数头部新笔记占据了大部分的曝光。 + +- 分别考察高曝光、低曝光新笔记。 + - 高曝光:比如 >1000 次曝光。 + - 低曝光:比如 <1000 次曝光。 + +#### **内容侧指标** + +**高热笔记占比** + +- 高热笔记:前 30 天获得 1000+ 次点击。 +- 高热笔记占比越高,说明冷启阶段挖掘优质笔记的能力越强。 + +#### **总结** + +- **作者侧指标**:发布渗透率、人均发布量。 +- **用户侧指标**:新笔记消费指标、大盘消费指标。 +- **内容侧指标**:高热笔记占比。 + +**冷启动的优化点** + +- **优化全链路**(_包括召回和排序_)。 +- **流量调控**(_流量怎么在新物品、老物品中分配_)。 + +### 物品冷启动:简单的召回通道 + +#### 召回的依据 + +**冷启召回的困难** + +- 缺少用户交互,还没学好笔记 ID embedding,导致双塔模型效果不好。 +- 缺少用户交互,导致 ItemCF 不适用。 + +#### 双塔模型 + +**ID Embedding** + +**改进方案 1:新笔记使用 default embedding** + +- 物品塔做 ID embedding 时,让所有新笔记共享一个 ID,而不是用自己真正的 ID。 +- Default embedding:共享的 ID 对应的 embedding 向量。 +- s到下次模型训练的时候,新笔记才有自己的 ID embedding 向量。 + +**改进方案 2:利用相似笔记 embedding 向量** + +- 查找 top k 内容最相似的高曝光笔记。 +- 把 k 个高曝光笔记的 embedding 向量取平均,作为新笔记的 embedding。 + +**多个向量召回池** + +- 多个召回池,让新笔记有更多曝光机会。 + - 1 小时新笔记, + - 6 小时新笔记, + - 24 小时新笔记, + - 30 天笔记。 + +- 共享同一个双塔模型,那么多个召回池不增加训练的代价。 + +#### 类目召回 + +**基于类目的召回** + +- 系统维护类目索引: + $$\text{类目} \rightarrow \text{笔记列表(按时间倒排)}$$ + +- 用类目索引做召回: + $$\text{用户画像} \rightarrow \text{类目} \rightarrow \text{笔记列表}$$ + +- 取回笔记列表上前 k 篇笔记(即最新的 k 篇)。 + +**基于关键词的召回** + +- 系统维护关键词索引: + $$\text{关键词} \rightarrow \text{笔记列表(按时间倒排)}$$ + +- 根据用户画像上的 **关键词** 做召回。 + +**缺点** + +- 缺点 1:只对刚刚发布的新笔记有效。 + - 取回某类目 / 关键词下最新的 k 篇笔记。 + - 发布几小时之后,就再没有机会被召回。 + +- 缺点 2:弱个性化,不够精准。 + +### 物品冷启动:聚类召回 + +#### 聚类召回 + +**基本思想** + +- 如果用户喜欢一篇笔记,那么他会喜欢内容相似的笔记。 + +- 事先训练一个神经网络,基于笔记的类别和图文内容,把笔记映射到向量。 + +- 对笔记向量做聚类,划分为 1000 个 $cluster$,记录每个 $cluster$ 的中心方向。($k$-means 聚类,用余弦相似度。) + +**聚类索引** + +- 一篇新笔记发布之后,用神经网络把它映射到一个特征向量。 + +- 从 1000 个向量(对应 1000 个 $cluster$)中找到最相似的向量,作为新笔记的 $cluster$。 + +- 索引: + + $$ + cluster \rightarrow \text{笔记ID列表(按时间倒排)} + $$ + +**线上召回** + +- 给定用户 ID,找到他的 last-$n$ 交互的笔记列表,把这些笔记作为种子笔记。 + +- 把每篇种子笔记映射到向量,寻找最相似的 $cluster$。 (知道了用户对哪些 $cluster$ 感兴趣。) + +- 从每个 $cluster$ 的笔记列表中,取回最新的 $m$ 篇笔记。 + +- 最多取回 $mn$ 篇新笔记。 + +#### 内容相似度模型 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-3-1.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-3-2.png) + +#### 训练内容相似度模型 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-3-3.png) + +**模型的训练** + +基本想法:鼓励 $\cos(\mathbf{a}, \mathbf{b}^+)$ 大于 $\cos(\mathbf{a}, \mathbf{b}^-)$ + +**Triplet hinge loss:** + +$$ +L(\mathbf{a}, \mathbf{b}^+, \mathbf{b}^-) = \max\{0, \cos(\mathbf{a}, \mathbf{b}^-) + m - \cos(\mathbf{a}, \mathbf{b}^+)\} +$$ + +**Triplet logistic loss:** + +$$ +L(\mathbf{a}, \mathbf{b}^+, \mathbf{b}^-) = \log(1 + \exp(\cos(\mathbf{a}, \mathbf{b}^-) - \cos(\mathbf{a}, \mathbf{b}^+))) +$$ + +**<种子笔记,正样本>** + +方法一:人工标注二元组的相似度 + +方法二:算法自动选正样本 + +筛选条件: + +- 只用高曝光笔记作为二元组 (因为有充足的用户交互信息)。 +- 两篇笔记有相同的二级类别,比如都是 “菜谱教程”。 + +- 用 ItemCF 的物品相似度选正样本。 + +**<种子笔记,负样本>** + +- 从全体笔记中随机选出满足条件的: + - 字数较多 _(神经网络提取的文本信息有效)_。 + - 笔记质量高,避免图文无关。 + +#### 总结 + +- **基本思想**:根据用户的点赞、收藏、转发记录,推荐内容相似的笔记。 + +- **线下训练**:多模态神经网络把图文内容映射到向量。 + +- **线上服务**: + + $$ + \text{用户喜欢的笔记} \rightarrow \text{特征向量} \rightarrow \text{最近的 Cluster} \rightarrow \text{新笔记} + $$ + +### 物品冷启动:Look-Alike 人群扩散 + +#### Look-Alike 起源于互联网广告 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-4-1.png) + +- 如何计算两个用户的相似度? + +- UserCF:两个用户有共同的兴趣点。 + +- Embedding:两个用户向量的 $\cos$ 较大。 + +#### Look-Alike 用于新笔记召回 + +- 点击、点赞、收藏、转发 —— 用户对笔记可能感兴趣。 + +- 把有交互的用户作为新笔记的种子用户。 + +- 用 look-alike 在相似用户中扩散。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-4-2.png) + +- 近线更新特征向量。 + +- 特征向量是有交互的用户的向量的平均。 + +- 每当有用户交互该物品,更新笔记的特征向量。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-4-3.png) + +利用双塔模型计算出用户的特征向量,将这个特征向量在向量数据库中做最近邻查找。这个过程就叫做 Look-Alike 召回。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-4-4.png) + +如果种子用户喜欢某篇笔记,那么相似用户也可能喜欢这篇笔记,这叫做 Look-Alike 扩散召回通道。 + +### 物品冷启动:流量调控 + +**扶持新笔记的目的** + +- **目的1**:促进发布,增大内容池。 + - 新笔记获得的曝光越多,作者创作积极性越高。 + - 反映在发布渗透率、人均发布量。 + +- **目的2**:挖掘优质笔记。 + - 做探索,让每篇新笔记都能获得足够曝光。 + - 挖掘的能力反映在高热笔记占比。 + +**工业界的做法** + +- 假设推荐系统只分发年龄 <30 天的笔记。 + +- 假设采用自然分发,新笔记(年龄 <24 小时)的曝光占比为 1/30。 + +- 扶持新笔记,让新笔记的曝光占比远大于 1/30。 + +**流量调控技术的发展** + +1. 在推荐结果中强插新笔记。 +2. 对新笔记的排序分数做提权(boost)。 +3. 通过提权,对新笔记做保量。 +4. 差异化保量。 + +#### 新笔记提权(boost) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-5-1.png) + +**新笔记提权** + +- **目标**:让新笔记有更多机会曝光。 + - 如果做自然分发,24 小时新笔记占比为 1/30。 + - 做人为干涉,让新笔记占比大幅提升。 + +- 干涉粗排、重排环节,给新笔记提权。 + +- **优点**:容易实现,投入产出比好。 + +- **缺点**: + - 曝光量对提权系数很敏感。 + - 很难精确控制曝光量,容易过度曝光和不充分曝光。 + +#### 新笔记保量 + +- **保量**:不论笔记质量高低,都保证 24 小时获得 100 次曝光。 + +- 在原有提权系数的基础上,乘以额外的提权的系数,例如: + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-5-2.png) + +**动态提权保量** + +用下面四个值计算提权系数 + +- 目标时间:比如 24 小时。 +- 目标曝光:比如 100 次。 +- 发布时间:比如笔记已经发布 12 小时。 +- 已有曝光:比如笔记已经获得 20 次曝光。 + +计算公式: + +$$ +\text{提权系数} = f\left( \frac{\text{发布时间}}{\text{目标时间}}, \frac{\text{已有曝光}}{\text{目标曝光}} \right) = f(0.5, 0.2) +$$ + +**保量的难点** + +保量成功率远低于 100% + +- 很多笔记在 24 小时达不到 100 次曝光。 +- 召回、排序存在不足。 +- 提权系数调得不好。 + +线上环境变化会导致保量失败 + +- 线上环境变化:新增召回通道、升级排序模型、改变重排打散规则…… +- 应对措施:线上环境变化后,需要调整提权系数。 + +给新笔记分数 boost 越多,对新笔记越有利? + +- 好处:分数提升越多,曝光次数越多。 +- 坏处:把笔记推荐给不太合适的受众。 + - 提权系数过高,导致预估兴趣分数偏高,会将笔记推荐给不合适的受众 + - 点击率、点赞率等指标会偏低。 + - 长期会受推荐系统打压,难以成长为热门笔记。 + +#### 差异化保量 + +- **保量**:不论新笔记质量高低,都做扶持,在前 24 小时给 100 次曝光。 + +- **差异化保量**:不同笔记有不同保量目标,普通笔记保 100 次曝光,内容优质的笔记保 100~500 次曝光。 + +**差异化保量** + +- **基础保量**:24 小时 100 次曝光。 + +- **内容质量**:用模型评价内容质量高低,给予额外保量目标,上限是加 200 次曝光。 + +- **作者质量**:根据作者历史上的笔记质量,给予额外保量目标,上限是加 200 次曝光。 + +- **一篇笔记最少有 100 次保量,最多有 500 次保量。** + +#### 总结 + +- **流量调控**:流量怎么在新老笔记之间分配。 + +- **扶持新笔记**:单独的召回通道,在排序阶段提权。 + +- **保量**:帮助新笔记在前 24 小时获得 100 次曝光。 + +- **差异化保量**:根据内容质量、作者质量,决定保量目标。 + +### 物品冷启动:AB测试 + +**新笔记冷启的 AB 测试** + +- **作者侧指标**: + - 发布渗透率、人均发布量。 + +- **用户侧指标**: + - 对新笔记的点击率、交互率。 + - 大盘指标:消费时长、日活、月活。 + +#### 用户侧实验 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-6-1.png) + +**用户侧实验** + +**缺点** + +- 限定:保量 100 次曝光。 +- 假设:新笔记曝光越多,用户使用 APP 时长越低。 +- 新策略:把新笔记排序时的权重增大两倍。 + +- 结果(只看消费指标) + - AB 测试的 diff 是负数(策略组不如对照组)。 + + - 如果推全,diff 会缩小(比如 -2% ➝ -1%)。 + - 这是因为新笔记采取保量。实验组的新笔记曝光量偏多,对照组偏少。比如有90篇新笔记,每篇保量100次,一共曝光9000次。实验组曝光了6000次,对照组曝光了3000次。试验结束后,原来实验组的 50% 用户曝光了4500次,对照组的 50% 用户也曝光了4500次,因此实验计算的 diff 偏大。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-6-2.png) + +#### 作者侧实验 + +**作者侧实验:方案一** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-6-3.png) + +**缺点:新笔记之间会抢流量** + +- 设定: + - 新老笔记走各自队列,没有竞争。 + + - 重排:分给新笔记 1/3 流量,分给老笔记 2/3 流量。 + +- 新策略:把新笔记的权重增大两倍。 + +- 结果(只看发布指标): + - AB 测试的 diff 是正数(策略组优于对照组)。 + + - 如果推全,diff 会消失(比如 2% ➝ 0)。 + +**缺点:新笔记和老笔记抢流量** + +- 设定:新老笔记自由竞争。 + +- 新策略:把新笔记排序时的权重增大两倍。 + +- AB 测试时,50% 新笔记(带策略)跟 100% 老笔记抢流量。 + +- 推全后,100% 新笔记(带策略)跟 100% 老笔记抢流量。 + +- 作者侧 AB 测试结果与推全结果有些差异。 + +**作者侧实验:方案二** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-6-4.png) + +**方案二比方案一的优缺点** + +- 优点:新笔记的两个桶不抢流量,作者侧实验结果更可信。 + +- 相同:新笔记和老笔记抢流量,作者侧 AB 测试结果与推全结果有些差异。 + +- 缺点:新笔记池减小一半,对用户体验造成负面影响。 + +**作者侧实验:方案三** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/7-6-5.png) + +对公司业务造成影响。 + +#### 总结 + +- 冷启的 AB 测试需要观测 作者发布指标 和 用户消费指标。 + +- 各种 AB 测试的方案都有缺陷。(小红书有更好的方案,但也不完美。) + +- 设计方案的时候,问自己几个问题: + - 实验组、对照组新笔记会不会抢流量? + - 新笔记、老笔记怎么抢流量? + - 同时隔离笔记、用户,会不会让内容池变小? + - 如果对新笔记做保量,会发生什么? diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_note_improvement.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_note_improvement.mdx new file mode 100644 index 0000000..88dcf2a --- /dev/null +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_note_improvement.mdx @@ -0,0 +1,484 @@ +--- +title: 王树森推荐系统学习笔记_涨指标 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: qmy3p4vc45ek61ce4n62fpxy +--- + +## 涨指标的方法 + +### 涨指标的方法 + +#### 推荐系统的评价指标 + +- 日活用户数($DAU$)和留存是最核心的指标。 +- 目前工业界最常用 $LT7$ 和 $LT30$ 衡量留存。 + - 某用户今天($t_0$)登录 APP,未来 7 天($t_0 \sim t_6$)中有 4 天登录 APP,那么该用户今天($t_0$)的 $LT7$ 等于 4。 + - 显然有 $1 \leq LT7 \leq 7$ 和 $1 \leq LT30 \leq 30$。 + - $LT$ 增长通常意味着用户体验提升。(除非 $LT$ 增长且 $DAU$ 下降。) + - 假设 APP 禁止低活用户登录,则 $DAU$ 下降,$LT$ 增长。 + +- 其他核心指标:用户使用时长、总阅读数(即总点击数)、总曝光数。这些指标的重要性低于 $DAU$ 和留存。 + - 时长增长,$LT$ 通常会增长。 + - 时长增长,阅读数、曝光数可能会下降。 + +- 非核心指标:点击率、交互率、等等。 +- 对于 UGC 平台,发布量和发布渗透率也是核心指标。 + +#### 涨指标的方法有哪些? + +1. 改进召回模型,添加新的召回模型。 +2. 改进粗排和精排模型。 +3. 提升召回、粗排、精排中的多样性。 +4. 特殊对待新用户、低活用户等特殊人群。 +5. 利用关注、转发、评论这三种交互行为。 + +### 涨指标的方法:召回 + +**召回模型 & 召回通道** + +- 推荐系统有几十条召回通道,它们的召回总量是固定的。总量越大,指标越好,粗排计算量越大。 +- 双塔模型($two\text{-}tower$)和 $item\text{-}to\text{-}item$($I2I$)是最重要的两类召回模型,占据召回的大部分配额。 +- 有很多小众的模型,占据的配额很少。在召回总量不变的前提下,添加某些召回模型可以提升核心指标。 +- 有很多内容池,比如 30 天物品、1 天物品、6 小时物品、新用户优质内容池、分人群内容池。 +- 同一个模型可以用于多个内容池,得到多条召回通道。 + +#### 改进双塔模型 + +**方向1:优化正样本、负样本。** + +- **简单正样本**:有点击的(用户,物品)二元组。 +- **简单负样本**:随机组合的(用户,物品)二元组。 +- **困难负样本**:排序靠后的(用户,物品)二元组。 + +**方向2:改进神经网络结构。** + +- **Baseline**:用户塔、物品塔分别是全连接网络,各输出一个向量,分别作为用户、物品的表征。 +- **改进**:用户塔、物品塔分别用 $DCN$ 代替全连接网络。 +- **改进**:在用户塔中使用用户行为序列($last\text{-}n$)。 +- **改进**:使用多向量模型代替单向量模型。(标准的双塔模型也叫单向量模型。) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-2-1.png) + +**方向3:改进模型的训练方法。** + +- **Baseline**:做二分类,让模型学会区分正样本和负样本。 +- **改进**:结合二分类、$batch$ 内负采样。(对于 $batch$ 内负采样,需要做纠偏。) +- **改进**:使用自监督学习方法,让冷门物品的 $embedding$ 学得更好。 + +#### Item-to-Item (I2I) + +- $I2I$ 是一大类模型,基于相似物品做召回。 +- 最常见的用法是 $U2I2I$($user \rightarrow item \rightarrow item$)。 + - 用户 $u$ 喜欢物品 $i_1$(用户历史上交互过的物品)。 + - 寻找 $i_1$ 的相似物品 $i_2$(即 $I2I$)。 + - 将 $i_2$ 推荐给 $u$。 + +- 如何计算物品相似度? +- 方法1:ItemCF 及其变体。 + - 一些用户同时喜欢物品 $i_1$ 和 $i_2$,则认为 $i_1$ 和 $i_2$ 相似。 + - $ItemCF$、$Online\ ItemCF$、$Swing$、$Online\ Swing$ 都是基于相同的思想。 + - 线上同时使用上述 4 种 $I2I$ 模型,各分配一定配额。 +- 方法2:基于物品向量表征,计算向量相似度。(双塔模型、图神经网络均可计算物品向量表征。) + +#### 小众的召回模型 + +**类似 I2I 的模型** + +- **U2U2I**($user \rightarrow user \rightarrow item$):已知用户 $u_1$ 与 $u_2$ 相似,且 $u_2$ 喜欢物品 $i$,那么给用户 $u_1$ 推荐物品 $i$。 +- **U2A2I**($user \rightarrow author \rightarrow item$):已知用户 $u$ 喜欢作者 $a$,且 $a$ 发布物品 $i$,那么给用户 $u$ 推荐物品 $i$。 +- **U2A2A2I**($user \rightarrow author \rightarrow author \rightarrow item$):已知用户 $u$ 喜欢作者 $a_1$,且 $a_1$ 与 $a_2$ 相似,$a_2$ 发布物品 $i$,那么给用户 $u$ 推荐物品 $i$。 + +#### 总结:改进召回模型 + +- **双塔模型**:优化正负样本、改进神经网络结构、改进训练的方法。 +- **I2I 模型**:同时使用 $ItemCF$ 及其变体,使用物品向量表征计算物品相似度。 +- **添加小众的召回模型**,比如 $PDN$、$Deep\ Retrieval$、$SINE$、$M2GRL$ 等模型。 +- **在召回总量不变的前提下,调整各召回通道的配额**。(可以让各用户群体用不同的配额。) + +### 涨指标的方法:排序模型 + +**排序模型** + +1. 精排模型的改进 +2. 粗排模型的改进 +3. 用户行为序列建模 +4. 在线学习 +5. 老汤模型 + +#### 精排模型的改进 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-3-1.png) + +**精排模型:基座** + +- 基座的输入包括离散特征和连续特征,输出一个向量,作为多目标预估的输入。 +- **改进 1**:基座加宽加深,计算量更大,预测更准确。 +- **改进 2**:做自动的特征交叉,比如 $bilinear$ [1] 和 $LHUC$ [2]。 +- **改进 3**:特征工程,比如添加统计特征、多模态内容特征。 + +**精排模型:多目标预估** + +- 基于基座输出的向量,同时预估点击率等多个目标。 +- **改进 1**:增加新的预估目标,并把预估结果加入融合公式。 + - 最标准的目标包括点击率、点赞率、收藏率、转发率、评论率、关注率、完播率…… + - 寻找更多目标,比如进入评论区、给他人写的评论点赞…… + - 把新的预估目标加入融合公式。 + +- **改进 2**:$MMoE$、$PLE$ 等结构可能有效,但往往无效。 +- **改进 3**:纠正 $position\ bias$ 可能有效,也可能无效。 + +#### 粗排模型的改进 + +**粗排模型** + +- 粗排的打分量比精排大 10 倍,因此粗排模型必须够快。 +- **简单模型**:多向量双塔模型,同时预估点击率等多个目标。 +- **复杂模型**:三塔模型效果好,但工程实现难度较大。 + +**粗精排一致性建模** + +- 蒸馏精排训练粗排,让粗排与精排更一致。 +- **方法1**:pointwise 蒸馏。 + - 设 $y$ 是用户真实行为,设 $p$ 是精排的预估。 + - 用 $\frac{y + p}{2}$ 作为粗排拟合的目标。 + - **例**: + - 对于点击率目标,用户有点击($y=1$),精排预估 $p=0.6$。 + - 用 $\frac{y + p}{2} = 0.8$ 作为粗排拟合的点击率目标。 + +- **方法2**:pairwise 或 listwise 蒸馏。 + - 给定 $k$ 个候选物品,按照精排预估做排序。 + - 做 learning to rank ($LTR$),让粗排拟合物品的序(而非值)。 + - **例**: + - 对于物品 $i$ 和 $j$,精排预估点击率为 $p_i > p_j$。 + - $LTR$ 鼓励粗排预估点击率满足 $q_i > q_j$,否则有惩罚。 + - $LTR$ 通常使用 pairwise logistic loss。 + +- **优点**:粗精排一致性建模可以提升核心指标。 +- **缺点**:如果精排出 bug,精排预估值 $p$ 有偏,会污染粗排训练数据。 + +#### 用户行为序列建模 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-3-2.png) + +- 最简单的方法是对物品向量取平均,作为一种用户特征。 +- $DIN$ 使用注意力机制,对物品向量做加权平均。 +- 工业界目前沿着 $SIM$ 的方向发展。先用类别等属性筛选物品,然后用 $DIN$ 对物品向量做加权平均。 + +**用户行为序列建模** + +- **改进1**:增加序列长度,让预测更准确,但是会增加计算成本和推理时间。 +- **改进2**:筛选的方法,比如用类别、物品向量表征聚类。 + - 离线用多模态神经网络提取物品内容特征,将物品表征为向量。 + - 离线将物品向量聚类为 1000 类,每个物品有一个聚类序号。 + - 线上排序时,用户行为序列中有 $n = 1,000,000$ 个物品。某候选物品的聚类序号是 70,对 $n$ 个物品做筛选,只保留聚类序号为 70 的物品。$n$ 个物品中只有数千个被保留下来。 + - 同时有好几种筛选方法,取筛选结果的并集。 + +- **改进3**:对用户行为序列中的物品,使用 ID 以外的一些特征。 +- **概括**:沿着 $SIM$ 的方向发展,让原始的序列尽量长,然后做筛选降低序列长度,最后将筛选结果输入 $DIN$。 + +#### 在线学习 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-3-3.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-3-4.png) + +**在线学习的资源消耗** + +- 既需要在凌晨做全量更新,也需要全天不断做增量更新。 +- 设在线学习需要 10,000 $CPU\ core$ 的算力增量更新一个精排模型。推荐系统一共需要多少额外的算力给在线学习? +- 为了做 $AB$ 测试,线上同时运行多个不同的模型。 +- 如果线上有 $m$ 个模型,则需要 $m$ 套在线学习的机器。 +- 线上有 $m$ 个模型,其中 1 个是 $holdout$,1 个是推全的模型,$m-2$ 个测试的新模型。 + +- 每套在线学习的机器成本都很大,因此 $m$ 数量很小,制约模型开发迭代的效率。 +- 在线学习对指标的提升巨大,但是会制约模型开发迭代的效率。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/8-3-5.png) + +#### 老汤模型 + +- 用每天新产生的数据对模型做 1 $epoch$ 的训练。 +- 久而久之,老模型训练得非常好,很难被超过。 +- 对模型做改进,重新训练,很难追上老模型…… +- **问题 1**:如何快速判断新模型结构是否优于老模型?(不需要追上线上的老模型,只需要判断新老模型谁的结构更优。) + - 对于新、老模型结构,都随机初始化模型全连接层。 + - $Embedding$ 层可以是随机初始化,也可以是复用老模型训练好的参数。 + - 用 $n$ 天的数据训练新老模型。(从旧到新,训练 1 $epoch$) + - 如果新模型显著优于老模型,新模型很可能更优。 + - 只是比较新老模型结构谁更好,而非真正追平老模型。 +- **问题 2**:如何更快追平、超过线上的老模型?(只有几十天的数据,新模型就能追上训练上百天的老模型。) + - 已经得出初步结论,认为新模型很可能优于老模型。用几十天的数据训练新模型,早日追平老模型。 + - **方法 1**:尽可能多地复用老模型训练好的 $embedding$ 层,避免随机初始化 $embedding$ 层。($Embedding$ 层是对用户、物品特征的“记忆”,比全连接层学得慢。) + - **方法 2**:用老模型做 $teacher$,蒸馏新模型。(用户真实行为是 $y$,老模型的预测是 $p$,用 $\frac{y + p}{2}$ 作为训练新模型的目标。) + +#### 总结:改进排序模型 + +- **精排模型**:改进模型基座(加宽加深、特征交叉、特征工程),改进多目标预估(增加新目标、$MMoE$、$position\ bias$)。 +- **粗排模型**:三塔模型(取代多向量双塔模型),粗精排一致性建模。 +- **用户行为序列建模**:沿着 $SIM$ 的方向迭代升级,加长序列长度,改进筛选物品的方法。 +- **在线学习**:对指标提升大,但是会降低模型迭代升级效率。 +- **老汤模型** 制约模型迭代升级效率,需要特殊技巧。 + +### 涨指标的方法:提升多样性 + +#### 排序的多样性 + +**精排多样性** + +- **精排阶段**,结合兴趣分数和多样性分数对物品 $i$ 排序。 + - $s_i$:兴趣分数,即融合点击率等多个预估目标。 + - $d_i$:多样性分数,即物品 $i$ 与已选中的物品的差异。 + - 用 $s_i + d_i$ 对物品做排序。 + +- 常用 MMR$、$DPP 等方法计算多样性分数,精排使用滑动窗口,粗排不使用滑动窗口。 + - 精排决定最终的曝光,曝光页面上邻近的物品相似度应该小。所以计算精排多样性要使用滑动窗口。 + - 粗排要考虑整体的多样性,而非一个滑动窗口中的多样性。 + +- 除了多样性分数,精排还使用打散策略增加多样性。 + - **类目**:当前选中物品 $i$,之后 5 个位置不允许跟 $i$ 的二级类目相同。 + - **多模态**:事先计算物品多模态内容向量表征,将全库物品聚为 1000 类;在精排阶段,如果当前选中物品 $i$,之后 10 个位置不允许跟 $i$ 同属一个聚类。 + +**粗排多样性** + +- 粗排给 5000 个物品打分,选出 500 个物品送入精排。 +- 提升粗排和精排多样性都可以提升推荐系统核心指标。 +- 根据 $s_i$ 对 5000 个物品排序,分数最高的 200 个物品送入精排。 +- 对于剩余的 4800 个物品,对每个物品 $i$ 计算兴趣分数 $s_i$ 和多样性分数 $d_i$。 +- 根据 $s_i + d_i$ 对剩余 4800 个物品排序,分数最高的 300 个物品送入精排。 + +#### 召回的多样性 + +**双塔模型:添加噪声** + +- 用户塔将用户特征作为输入,输出用户的向量表征;然后做 $ANN$ 检索,召回向量相似度高的物品。 +- 线上做召回时(在计算出用户向量之后,在做 $ANN$ 检索之前),往用户向量中添加随机噪声。 +- 用户的兴趣越窄(比如用户最近交互的 $n$ 个物品只覆盖少数几个类目),则添加的噪声越强。 +- 添加噪声使得召回的物品更多样,可以提升推荐系统核心指标。 + +**双塔模型:抽样用户行为序列** + +- 用户最近交互的 $n$ 个物品(用户行为序列)是用户塔的输入。 +- 保留最近的 $r$ 个物品($r \ll n$)。 +- 从剩余的 $n - r$ 个物品中随机抽样 $t$ 个物品($t \ll n$)。(可以是均匀抽样,也可以用非均匀抽样让类目平衡。) +- 将得到的 $r + t$ 个物品作为用户行为序列,而不是用全部 $n$ 个物品。 +- **抽样用户行为序列为什么能涨指标?** + - 一方面,注入随机性,召回结果更多样化。 + - 另一方面,$n$ 可以非常大,可以利用到用户很久之前的兴趣。 + +**U2I2I:抽样用户行为序列** + +- $U2I2I$($user \to item \to item$)中的第一个 $item$ 是指用户最近交互的 $n$ 个物品之一,在 $U2I2I$ 中叫作**种子物品**。 +- $n$ 个物品覆盖的类目数较少,且类目不平衡。 + - 系统共有 200 个类目,某用户的 $n$ 个物品只覆盖 15 个类目。 + - 足球类目的物品有 $0.4n$ 个,电视剧类目的物品有 $0.2n$ 个,其余类目的物品数均少于 $0.05n$ 个。 + +- 做非均匀随机抽样,从 $n$ 个物品中选出 $t$ 个,让类目平衡。(想法和效果与双塔中的用户行为序列抽样相似。) +- 用抽样得到的 $t$ 个物品(代替原本的 $n$ 个物品)作为 $U2I2I$ 的种子物品。 +- 一方面,类目更平衡,多样性更好。另一方面,$n$ 可以更大,覆盖的类目更多。 + +**探索流量** + +- 每个用户曝光的物品中有 2% 是非个性化的,用作兴趣探索。 +- 维护一个精选内容池,其中物品均为交互率指标高的优质物品。(内容池可以分人群,比如 30~40 岁男性内容池。) +- 从精选内容池中随机抽样几个物品,跳过排序,直接插入最终排序结果。 +- 兴趣探索在短期内负向影响核心指标,但长期会产生正向影响。 + +#### 总结:提升多样性 + +- **精排**:结合兴趣分数和多样性分数做排序;做规则打散。 +- **粗排**:只用兴趣分数选出部分物品;结合兴趣分数和多样性分数选出部分物品。 +- **召回**:往双塔模型的用户向量添加噪声;对用户行为序列做非均匀随机抽样(对双塔和 U2I2I 都适用)。 +- **兴趣探索**:保留少部分的流量给非个性化推荐。 + +### 涨指标的方法:特殊对待特殊人群 + +**为什么要特殊对待特殊人群**? + +1. 新用户、低活用户的行为很少,个性化推荐不准确。 +2. 新用户、低活用户容易流失,要想办法促使他们留存。 +3. 特殊用户的行为(比如点击率、交互率)不同于主流用户,基于全体用户行为训练出的模型在特殊用户人群上有偏。 + +**涨指标的方法** + +1. 构造特殊内容池,用于特殊用户人群的召回。 +2. 使用特殊排序策略,保护特殊用户。 +3. 使用特殊的排序模型,消除模型预估的偏差。 + +#### 构造特殊的内容池 + +**特殊内容池** + +- 为什么需要特殊内容池? +- 新用户、低活用户的行为很少,个性化召回不准确。(既然个性化不好,那么就保证内容质量好。) +- 针对特定人群的特点构造特殊内容池,提升用户满意度。例如,对于喜欢留下评论的中年女性,构造促进评论内容池,满足这些用户的互动需求。 + +**如何构造特殊内容池** + +- 方法 1:根据物品获得的交互次数、交互率选择优质物品。 + - 圈定人群:只考虑特定人群,例如 18~25 岁二线城市男性。 + - 构造内容池:用该人群对物品的交互次数、交互率给物品打分,选出分数最高的物品进入内容池。 + - 内容池有弱个性化的效果。 + - 内容池定期更新,加入新物品,排除交互率低和失去时效性的老物品。 + - 该内容池只对该人群生效。 +- 方法 2:做因果推断,判断物品对人群留存率的贡献,根据贡献值选物品。 + +**特殊内容池的召回** + +- 通常使用双塔模型从特殊内容池中做召回。 + - 双塔模型是个性化的。 + - 对于新用户,双塔模型的个性化做不准。 + - 靠高质量内容、弱个性化做弥补。 + +- 额外的训练代价? + - 对于正常用户,不论有多少内容池,只训练一个双塔模型。 + - 对于新用户,由于历史交互记录很少,需要单独训练模型。 + +- 额外的推理代价? + - 内容池定期更新,然后需要更新 ANN 索引。 + - 线上做召回时,需要做 ANN 检索。 + - 特殊内容池都很小(比全量内容池小 10~100 倍),所以需要的额外算力不大。 + +#### 特殊的排序策略 + +差异化的排序模型 + +- 特殊用户⼈群的行为不同于普通用户。新用户、低活用户的点击率、交互率偏高或偏低。 +- 排序模型被主流用户主导,对特殊用户做不准预估。 + - 用全体用户数据训练出的模型,给新用户做的预估有严重偏差。 + - 如果⼀个APP的用90%是女性,用全体⽤户数据训练出的模型, 对男性用户做的预估有偏差。 +- 问题:对于特殊用户,如何让排序模型预估做得准? + +- **方法 1:大模型 + 小模型。** + - 用全体用户行为训练大模型,大模型的预估 $p$ 拟合用户行为 $y$。 + - 用特殊用户的行为训练小模型,小模型的预估 $q$ 拟合大模型的残差 $y - p$。 + - 对主流用户只用大模型做预估 $p$。 + - 对特殊用户,结合大模型和小模型的预估 $p + q$。 + +- **方法 2:融合多个 experts,类似 MMoE。** + - 只用一个模型,模型有多个 experts,各输出一个向量。 + - 对 experts 的输出做加权平均。 + - 根据用户特征计算权重。 + - 以新用户为例,模型将用户的新老、活跃度等特征作为输入,输出权重,用于对 experts 做加权平均。 + +- **方法 3:大模型预估之后,用小模型做校准。** + - 用大模型预估点击率、交互率。 + - 将用户特征、大模型预估点击率和交互率作为小模型(例如 GBDT)的输入。 + - 在特殊用户人群的数据上训练小模型,小模型的输出拟合用户真实行为。 + +**错误的做法** + +- 每个用户人群使用一个排序模型,推荐系统同时维护多个大模型。 + - 系统有一个主模型;每个用户人群有自己的一个模型。 + - 每天凌晨,用全体用户数据更新主模型。 + - 基于训练好的主模型,在某特殊用户人群的数据上再训练 1 epoch,作为该用户人群的模型。 + +- 短期可以提升指标;维护代价大,长期有害。 + - 起初,低活男性用户模型比主模型的 AUC 高 0.2%。 + - 主模型迭代几个版本后,AUC 累计提升 0.5%。 + - 特殊人群模型太多,长期没有人维护和更新。 + - 如果把低活男性用户模型下线,换成主模型,在低活男性用户上的 AUC 反倒提升 0.3%! + +#### 总结:特殊对待特殊用户人群 + +- **召回**:针对特殊用户人群,构造特殊的内容池,增加相应的召回通道。 +- **排序策略**:排除低质量物品,保护新用户和低活用户;特殊用户人群使用特殊的融分公式。 +- **排序模型**:结合大模型和小模型,小模型拟合大模型的残差;只用一个模型,模型有多个 experts;大模型预估之后,用小模型做校准。 + +### 涨指标的方法:利用交互行为 + +#### 关注 + +**关注量对留存的价值** + +- 对于一位用户,他关注的作者越多,则平台对他的吸引力越强。 +- 用户留存率($r$)与他关注的作者数量($f$)正相关。 +- 如果某用户的 $f$ 较小,则推荐系统要促使该用户关注更多作者。 + +- 如何利用关注关系提升用户留存? +- 方法 1:用排序策略提升关注量。 + - 对于用户 $u$,模型预估候选物品 $i$ 的关注率为 $p_i$。 + - 设用户 $u$ 已经关注了 $f$ 个作者。 + - 我们定义单调递减函数 $w(f)$,用户已经关注的作者越多,则 $w(f)$ 越小。 + - 在排序融分公式中添加 $w(f) \cdot p_i$,用于促关注。(如果 $f$ 小且 $p_i$ 大,则 $w(f) \cdot p_i$ 给物品 $i$ 带来很大加分。) + +- 方法 2:构造促关注内容池和召回通道。 + - 这个内容池中物品的关注率高,可以促关注。 + - 如果用户关注的作者数 $f$ 较小,则对该用户使用该内容池。 + - 召回配额可以固定,也可以与 $f$ 负相关。 + +**粉丝数对促发布的价值** + +- UGC 平台将作者发布量、发布率作为核心指标,希望作者多发布。 +- 作者发布的物品被平台推送给用户,会产生点赞、评论、关注等交互。 +- 交互(尤其是关注、评论)可以提升作者发布积极性。 +- 作者的粉丝数越少,则每增加一个粉丝对发布积极性的提升越大。 + +- 用排序策略帮助低粉新作者涨粉。 +- 某作者 $a$ 的粉丝数(被关注数)为 $f_a$。 +- 作者 $a$ 发布的物品 $i$ 可能被推荐给用户 $u$,模型预估关注率为 $p_{ui}$。 +- 我们定义单调递减函数 $w(f_a)$ 作为权重;作者 $a$ 的粉丝越多,则 $w(f_a)$ 越小。 +- 在排序融分公式中添加 $w(f_a) \cdot p_{ui}$,帮助低粉作者涨粉。 + +**隐式关注关系** + +- **召回通道 U2A2I**:user → author → item。 +- **显式关注关系**:用户 $u$ 关注了作者 $a$,将 $a$ 发布的物品推荐给 $u$。(点击率、交互率指标通常高于其他召回通道。) +- **隐式关注关系**:用户 $u$ 喜欢看作者 $a$ 发布的物品,但是 $u$ 没有关注 $a$。 +- **隐式关注的作者数量远大于显式关注**。挖掘隐式关注关系,构造 U2A2I 召回通道,可以提升推荐系统核心指标。 + +#### 转发(分享) + +**促转发(分享回流)** + +- A 平台用户将物品转发到 B 平台,可以为 A 吸引站外流量。 +- 推荐系统做促转发(也叫分享回流)可以提升 DAU 和消费指标。 +- 简单提升转发次数是否有效呢? + - 模型预估转发率为 $p$,融分公式中有一项 $w \cdot p$,让转发率大的物品更容易获得曝光机会。 + - 增大权重 $w$ 可以促转发,吸引站外流量,但是会负面影响点击率和其他交互率。 + +**KOL 建模** + +- 目标:在不损害点击和其他交互的前提下,尽量多吸引站外流量。 +- 什么样的用户的转发可以吸引大量站外流量? 其他平台的 Key Opinion Leader (KOL)! +- 如何判断本平台的用户是不是其他平台的 KOL? +- 该用户历史上的转发能带来多少站外流量。 + +- 方法2:构造促转发内容池和召回通道,对站外KOL⽣效。 + +#### 评论 + +**评论促发布** + +- UGC 平台 将作者发布量、发布率作为核心指标,希望作者多发布。 +- 关注、评论等交互 可以提升作者发布积极性。 +- 如果新发布物品尚未获得很多评论,则给预估评论率提权,让物品尽快获得评论。 +- 排序融分公式中添加额外一项 $w_i \cdot p_i$。 + - $w_i$:权重,与物品 $i$ 已有的评论数量负相关。 + - $p_i$:为用户推荐物品 $i$,模型预估的评论率。 + +**评论的其他价值** + +- 有的用户喜欢留言评论,喜欢跟作者、评论区用户互动。 + - 给这样的用户添加促评论的内容池,让他们更多机会参与讨论。 + - 有利于提升这些用户的留存。 + +- 有的用户常留高质量评论(评论的点赞量高)。 + - 高质量评论对作者、其他用户的留存有贡献。(作者、其他用户觉得这样的评论有趣或者有帮助。) + - 用排序和召回策略鼓励这些用户多留评论。 + +#### 总结:利用交互行为 + +- **关注**: + - **留存价值**(让新用户关注更多作者,提升新用户留存)。 + - **发布价值**(帮助新作者获得更多粉丝,提升作者发布积极性)。 + - **利用隐式关注关系做召回**。 + +- **转发**:判断哪些用户是站外的 KOL,利用他们转发的价值,吸引站外的流量。 + +- **评论**: + - **发布价值**(促使新物品获得评论,提升作者发布积极性)。 + - **留存价值**(给喜欢讨论的用户创造更多留言机会)。 + - **鼓励高质量评论的用户多留评论**。 diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_note_rank.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_note_rank.mdx new file mode 100644 index 0000000..389b868 --- /dev/null +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_note_rank.mdx @@ -0,0 +1,350 @@ +--- +title: 王树森推荐系统学习笔记_排序 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: vjwogf9afghpbvi71e4dfsgj +--- + +# 王树森推荐系统学习笔记\_排序 + +## 排序 + +### 多目标排序模型 + +**用户一笔记的交互** + +- 对于每篇笔记,系统记录: + - **曝光次数**(_number of impressions_) + - **点击次数**(_number of clicks_) + - **点赞次数**(_number of likes_) + - **收藏次数**(_number of collects_) + - **转发次数**(_number of shares_) + +- **点击率** = 点击次数/曝光次数 +- **点赞率** = 点赞次数/点击次数 +- **收藏率** = 收藏次数/点击次数 +- **转发率** = 转发次数/点击次数 + +**排序的依据** + +- 排序模型预估点击率、点赞率、收藏率、转发率等多种分数。 +- 融合这些预估分数。(_比如加权和。_) +- 根据融合的分数做排序、截断。 + +#### 多目标模型 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-1-1.png) + +- 用户特征:如用户ID,用户画像 +- 物品特征:如物品ID,物品画像,作者信息 +- 统计特征:如用户在过去一段时间内曝光了多少篇笔记,点击了多少篇笔记,点赞了多少篇笔记。物品在过去一段时间内获得了多少曝光机会,被点击了多少次,点赞了多少次 +- 场景特征:如当前的时间,用户所在的地点 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-1-2.png) + +训练: + +- 对于点击率来说,模型实际上就是根据点击率来判断一个物品是否为被点击物品。这是一个二元分类问题,因此用交叉熵损失函数。 + +- 总的损失函数: $ \sum\_{i=1}^{4} \alpha_i \cdot \text{CrossEntropy}(y_i, p_i) $ 。 +- 对损失函数求梯度,做梯度下降更新参数。 + +**训练** + +- 困难:类别不平衡。 + - 每 100 次曝光,约有 10 次点击,90 次无点击。 + - 每 100 次点击,约有 10 次收藏,90 次无收藏。 + - 负样本与正样本数量差距悬殊,多出的负样本意义不大,浪费计算资源。 + +- 解决方案:负样本降采样(_down-sampling_) + - 保留一小部分负样本。 + - 让正负样本数量平衡,节约计算。 + +#### 预估值校准 + +- 正样本、负样本数量为 $n_+$ 和 $n_-$。 +- 对负样本做降采样,抛弃一部分负样本。 +- 使用 $ \alpha \cdot n\_- $ 个负样本,$ \alpha \in (0,1) $ 是采样率。 +- 由于负样本变少,**预估点击率** 大于 **真实点击率**。而且 $\alpha$ 越小,负样本越少,模型对点击率高估就越严重。 + +- **真实点击率**: + +$$ +p_{\text{true}} = \frac{n_+}{n_+ + n_-} \quad (\text{期望}) +$$ + +- **预估点击率**: + +$$ +p_{\text{pred}} = \frac{n_+}{n_+ + \alpha \cdot n_-} \quad (\text{期望}) +$$ + +- 由上面两个等式可得校准公式 : + +$$ +p_{\text{true}} = \frac{\alpha \cdot p_{\text{pred}}}{(1 - p_{\text{pred}}) + \alpha \cdot p_{\text{pred}}}。 +$$ + +### Multi-gate Mixture-of-Experts (MMoE) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-2-1.png) + +1号,2号,3号神经网络分别是专家神经网络。 + +将用户特征,物品特征,统计特征和行为特征输入到左侧的神经网络中,再经过 softmax 激活函数得到一个权重向量,这种神经网络也叫做门控神经网络。向量中的三个值 $p_1,p_2,p_3$ 分别代表着对 $x_1,x_2,x_3$ 的权重值。右侧神经网络同理。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-2-2.png) + +将左侧门控神经网络输出的权重向量与专家神经网络生成的特征向量结合,输入到左侧的任务神经网络中,预测点击率。右侧同理。 + +#### 极化现象(Polarization) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-2-3.png) + +当左侧门控神经网络输出向量中 3 号专家神经网络的权重接近 1,其余接近 0。右侧门控神经网络输出向量中 2 号专家神经网络的权重接近 1,其余接近 0。这样加权过后就会导致 1 号神经网络的输出向量没有参与模型工作。应该尽量避免这种情况。 + +#### 解决极化问题 + +- 如果有 $n$ 个“专家”,那么每个 softmax 的输入和输出都是 $n$ 维向量。 + +- **在训练时,对 softmax 的输出使用 dropout**。 + - Softmax 输出的 $n$ 个数值被 mask 的概率都是 10%。 + - 每个“专家”被随机丢弃的概率都是 10%。 + +### 预估分数的融合 + +**简单的加权和** + +$$ +p_{\text{click}} + w_1 \cdot p_{\text{like}} + w_2 \cdot p_{\text{collect}} + \cdots +$$ + +**点击率乘以其他项的加权和** + +$$ +p_{\text{click}} \cdot \left( 1 + w_1 \cdot p_{\text{like}} + w_2 \cdot p_{\text{collect}} + \cdots \right) +$$ + +- $p_{\text{click}}= \frac{\text{点击}}{\text{曝光}}$ +- $p_{\text{like}}\ = \frac{\text{点赞}}{\text{点击}}$ + +**国内某短视频 APP 的融合分公式** + +- 根据预估时长 $p_{\text{time}}$,对 $n$ 篇候选视频做排序。 +- 如果某视频排名第 $r_{\text{time}}$ ,则它得分 $ \frac{1}{r\_{\text{time}}^{\alpha} + \beta} $。 +- 对点击、点赞、转发、评论等预估分数做类似处理。 +- 最终融合分数:( $\alpha_{1,2,3\dots}$ 为超参数) + +$$ +\frac{w_1}{r_{\text{time}}^{\alpha_1} + \beta_1} + \frac{w_2}{r_{\text{click}}^{\alpha_2} + \beta_2} + \frac{w_3}{r_{\text{like}}^{\alpha_3} + \beta_3} + \cdots +$$ + +**某电商的融合分公式** + +- 电商的转化流程: + + $$\text{曝光} \rightarrow \text{点击} \rightarrow \text{加购物车} \rightarrow \text{付款}$$ + +- 模型预估:$ p*{\text{click}} $、$ p*{\text{cart}} $、$ p\_{\text{pay}} $。 + +- 最终融合分数:( $\alpha_{1,2,3,4\dots}$ 为超参数) + +$$ +p_{\text{click}}^{\alpha_1} \times p_{\text{cart}}^{\alpha_2} \times p_{\text{pay}}^{\alpha_3} \times \text{price}^{\alpha_4} +$$ + +### 视频播放建模 + +#### 视频播放时长 + +**图文 vs 视频** + +- 图文笔记排序的主要依据: + + _点击、点赞、收藏、转发、评论⋯⋯_ + +- 视频排序的依据还有播放时长和完播。 + +- 直接用回归拟合播放时长效果不好。建议用 YouTube 的时长建模 。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-4-1.png) + +如果 $p = y$,那么 $\exp(z) = t$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-4-2.png) + +#### 视频播放时长建模 + +- 把最后一个全连接层的输出记作 $z$。设 $p = \text{sigmoid}(z)$。 +- 实际观测的播放时长记作 $t$。(_如果没有点击,则 $t = 0$。_) +- 做训练:最小化交叉熵损失 + +$$ +\left( \frac{t}{1 + t} \cdot \log p + \frac{1}{1 + t} \cdot \log (1 - p) \right)。 +$$ + +- 做推理:把 $\exp(z)$ 作为播放时长的预估。 +- 把 $\exp(z)$ 作为融合公式中的一项。 + +#### 视频完播 + +**回归方法** + +- 例:视频长度 10 分钟,实际播放 4 分钟,则实际播放率为 $y = 0.4$。 + +- 让预估播放率 $p$ 拟合 $y$: + +$$ +\text{loss} = y \cdot \log p + (1 - y) \cdot \log (1 - p)。 +$$ + +- 线上预估完播率,模型输出 $p = 0.73$,意思是预计播放 $73 \%$ + +**二元分类方法** + +- 定义完播指标,比如完播 80%。 +- 例:视频长度 10 分钟,播放 >8 分钟作为正样本,播放 <8 分钟作为负样本。 +- 做二元分类训练模型:播放 >80% vs 播放 <80%。 +- 线上预估完播率,模型输出 $p = 0.73$,意思是: + +$$ +\mathbb{P}(\text{播放} > 80\%) = 0.73。 +$$ + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-4-3.png) + +视频越长完播率越低,直接用预估完播率会偏向推荐短视频而非长视频。因此需要进行优化,使短视频和长视频一样公平。 + +- 线上预估完播率,然后做调整: + + $$ + p_{\text{finish}} = \frac{\text{预估完播率}}{f(\text{视频长度})} + $$ + +- 把 $p_{\text{finish}}$ 作为融合公式中的一项。 + +### 排序模型的特征 + +#### 特征 + +**用户画像** (User Profile) + +- 用户 ID(_在召回、排序中做 embedding_)。 +- 人口统计学属性:性别、年龄。 +- 账号信息:新老、活跃度⋯⋯ +- 感兴趣的类目、关键词、品牌。 + +**物品画像** (Item Profile) + +- 物品 ID(_在召回、排序中做 embedding_)。 +- 发布时间(_或者年齿_)。 +- GeoHash(_经纬度编码_)、所在城市。 +- 标题、类目、关键词、品牌⋯⋯ +- 字数、图片数、视频清晰度、标签数⋯⋯ +- 内容信息量、图片美学⋯⋯ + +**用户统计特征** + +- 用户最近 30 天(7 天、1 天、1 小时)的曝光数、点击数、点赞数、收藏数⋯⋯ +- 按照笔记 _图文/视频_ 分桶。(_比如最近 7 天,该用户对图文笔记的点击率、对视频笔记的点击率。_) +- 按照笔记类目分桶。(_比如最近 30 天,用户对美妆笔记的点击率、对美食笔记的点击率、对科技数码笔记的点击率。_) + +**笔记统计特征** + +- 笔记最近 30 天(7 天、1 天、1 小时)的曝光数、点击数、点赞数、收藏数⋯⋯ +- 按照用户性别分桶、按照用户年龄分桶⋯⋯ +- 作者特征: + - 发布笔记数 + - 粉丝数 + - 消费指标(_曝光数、点击数、点赞数、收藏数_) + +**场景特征** (Context) + +- 用户定位 GeoHash(_经纬度编码_)、城市。 +- 当前时刻(_分段,做 embedding_)。 +- 是否是周末、是否是节假日。 +- 手机品牌、手机型号、操作系统。 + +#### 特征处理 + +- **离散特征**:做 embedding。 + - 用户 ID、笔记 ID、作者 ID。 + - 类目、关键词、城市、手机品牌。 + +- **连续特征**:做分桶,变成离散特征。 + - 年龄、笔记字数、视频长度。 + +- **连续特征**:其他变换。 + - 曝光数、点击数、点赞数等数值做 $ \log(1 + x) $。 + - 转化为点击率、点赞率等值,并做平滑。 + +#### 特征覆盖率 + +- 很多特征无法覆盖 100% 样本。 +- 例:很多用户不填年龄,因此用户年龄特征的覆盖率远小于 100%。 +- 例:很多用户设置隐私权限,APP 不能获取用户地理定位,因此场景特征有缺失。 +- 提高特征覆盖率,可以让精排模型更准。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-5-1.png) + +主服务器从召回服务器召回一批物品ID。用户发送请求,主服务器将用户ID,场景特征以及物品ID发送给排序服务器。 + +用户画像数据库压力较小,因为每次只读一个用户的特征。物品画像压力比较大,因为每次要读几千篇笔记的特征。同理,用户统计值数据库压力较小,物品统计值数据压力很大。用户画像的向量可以很大,但是物品画像的向量不要很大。 + +用户画像以及物品画像都比较静态,甚至可以将用户画像和物品画像直接缓存在排序服务器本地。统计数据是动态的,需要及时更新数据库。 + +排序服务器在收取到特征后,将特征打包给 TF Serving 。TF 会给笔记打分,将结果返回给排序服务器。排序服务器根据一系列规则给笔记排序,把排名最高的几十篇笔记返回给服务器。 + +### 粗排 + +**粗排 VS 精排** + +| **粗排** | **精排** | +| -------------------- | ------------------ | +| 给几千篇笔记打分。 | 给几百篇笔记打分。 | +| 单次推理代价必须小。 | 单次推理代价很大。 | +| 预估的准确性不高。 | 预估的准确性更高。 | + +#### 精排模型&双塔模型 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-6-1.png) + +**精排模型** + +- 前期融合:先对所有特征做 concatenation,再输入神经网络。 +- 线上推理代价大:如果有 $n$ 篇候选笔记,整个大模型要做 $n$ 次推理。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-6-2.png) + +**双塔模型** + +- 后期融合:把用户、物品特征分别输入不同的神经网络,不对用户、物品特征做融合。 + +- 线上计算量小: + - 用户塔只需要做一次线上推理,计算用户表征 a。 + - 物品表征 b 事先储存在向量数据库中,物品塔在线上不做推理。 + +- 预估准确性不如精排模型。 + 后期融合准确性不如前期融合 + +#### 粗排的三塔模型 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-6-4.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/3-6-3.png) + +- 有 $n$ 个物品,模型上层需要做 $n$ 次推理。 +- 粗排推理的大部分计算量在模型上层。 + +**三塔模型的推理** + +- 从多个数据源取特征: + - 1 个用户的画像、统计特征。 + - $n$ 个物品的画像、统计特征。 + +- 用户塔:只做 1 次推理。 +- 物品塔:未命中缓存时需要做推理。 +- 交叉塔:必须做 $n$ 次推理。 +- 上层网络:做 $n$ 次推理,给 $n$ 个物品打分。 diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_note_rerank.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_note_rerank.mdx new file mode 100644 index 0000000..3dfa270 --- /dev/null +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_note_rerank.mdx @@ -0,0 +1,418 @@ +--- +title: 王树森推荐系统学习笔记_重排 +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: ol03smbujgwztho45ycj52ah +--- + +# 王树森推荐系统学习笔记\_重排 + +## 重排 + +### 推荐系统中的多样性 + +#### 物品相似性的度量 + +**相似性的度量** + +- 基于物品属性标签。 + - 类目、品牌、关键词…… + +- 基于物品向量表征。 + - 用召回的双塔模型学到的物品向量 _(不好)_。 + - 基于内容的向量表征 _(好)_。 + +**基于物品属性标签** + +- 物品属性标签:类目、品牌、关键词…… +- 根据 _一级类目_、_二级类目_、_品牌_ 计算相似度。 + - 物品 $i$:美妆、彩妆、香奈儿。 + - 物品 $j$:美妆、香水、香奈儿。 + - 相似度:$\text{sim}_1(i, j) = 1$,$\text{sim}_2(i, j) = 0$,$\text{sim}_3(i, j) = 1$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-1-1.png) + +基于图文内容的物品表征可以提升多样性。 + +可以用 CNN 处理图片输出特征向量,BERT 处理文字输出特征向量。最后将两个向量拼起来即可。 + +**如何训练 CNN 与 BERT?** + +- **CLIP** 是当前公认最有效的预训练方法。 +- **思想**:对于 _图片—文本_ 二元组,预测图文是否匹配。 +- **优势**:无需人工标注。小红书的笔记天然包含图片 + 文字,大部分笔记图文相关。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-1-2.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-1-3.png) + +- 一个 batch 内有 $m$ 对正样本。 +- 一张图片和 $m - 1$ 条文本组成负样本。 +- 这个 batch 内一共有 $m(m - 1)$ 对负样本。 + +#### 提升多样性的方法 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-1-4.png) + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-1-5.png) + +粗排的后处理也需要多样性算法。 + +精排的后处理也被称为重排。 + +### Maximal Marginal Relevance (MMR) + +**多样性** + +- 精排给 $n$ 个候选物品打分,融合之后的分数为 + $$\text{reward}_1, \dots, \text{reward}_n$$ +- 把第 $i$ 和 $j$ 个物品的相似度记作 $\text{sim}(i,j)$。 +- 从 $n$ 个物品中选出 $k$ 个,既要有高精排分数,也要有多样性。 + +#### MMR多样性算法 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-2-1.png) + +- 计算集合 $\mathcal{R}$ 中每个物品 $i$ 的 Marginal Relevance 分数: + + $$ + \text{MR}_i = \theta \cdot \text{reward}_i - (1 - \theta) \cdot \max\limits_{j \in \mathcal{S}} \text{sim}(i, j) + $$ + +- $ \text{reward}_i$ 是物品的精排分数,$\max\limits_{j \in \mathcal{S}} \text{sim}(i, j)$ 是未选中的物品 $i$ 与已选中物品的相似程度。精排分数分数越高,相似程度越低,物品的 MR 分数就越高。 + +- **Maximal Marginal Relevance (MMR)**: + 从未选中物品中选出 MR 分数最高的 + $$ + \arg\max\limits_{i \in \mathcal{R}} \text{MR}_i + $$ + +**MMR 多样性算法** + +1. 已选中的物品 $\mathcal{S}$ 初始化为空集,未选中的物品 $\mathcal{R}$ 初始化为全集 $\{1, \dots, n\}$。 +2. 选择精排分数 $\text{reward}_i$ 最高的物品,从集合 $\mathcal{R}$ 移到 $\mathcal{S}$。 +3. 做 $k - 1$ 轮循环: + a. 计算集合 $\mathcal{R}$ 中所有物品的分数 $\{\text{MR}_i\}_{i \in \mathcal{R}}$。 + b. 选出分数最高的物品,将其从 $\mathcal{R}$ 移到 $\mathcal{S}$。 + +#### **滑动窗口** + +- **MMR**: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \left\{ \theta \cdot \text{reward}_i - (1 - \theta) \cdot \max\limits_{j \in \mathcal{S}} \text{sim}(i, j) \right\} + $$ + +- 已选中的物品越多(即集合 $\mathcal{S}$ 越大),越难找出物品 $i \in \mathcal{R}$,使得 $i$ 与 $\mathcal{S}$ 中的物品都不相似。 + +- 设 $\text{sim}$ 的取值范围是 $[0,1]$。当 $\mathcal{S}$ 很大时,多样性分数 $\max\limits_{j \in \mathcal{S}} \text{sim}(i, j)$ 总是约等于 1,导致 MMR 算法失效。 + +- **解决方案**:设置一个滑动窗口 $\mathcal{W}$,比如最近选中的 10 个物品,用 $\mathcal{W}$ 代替 MMR 公式中的 $\mathcal{S}$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-2-2.png) + +- **标准 MMR**: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \left\{ \theta \cdot \text{reward}_i - (1 - \theta) \cdot \max\limits_{j \in \mathcal{S}} \text{sim}(i, j) \right\}. + $$ + +- **用滑动窗口**: + $$ + \arg\max\limits_{i \in \mathcal{R}} \left\{ \theta \cdot \text{reward}_i - (1 - \theta) \cdot \max\limits_{j \in \mathcal{W}} \text{sim}(i, j) \right\} + $$ + +### 重排的规则 + +#### 重排的规则 + +**规则:最多连续出现 $k$ 篇某种笔记** + +- 小红书推荐系统的物品分为图文笔记、视频笔记。 +- 最多连续出现 $k = 5$ 篇图文笔记,最多连续出现 $k = 5$ 篇视频笔记。 +- 如果排 $i$ 到 $i+4$ 的全部是图文笔记,那么排在 $i+5$ 的必须是视频笔记。 + +**规则:每 $k$ 篇笔记最多出现 1 篇某种笔记** + +- 运营推广笔记的精排分会乘以大于 1 的系数(boost),帮助笔记获得更多曝光。 +- 为了防止 boost 影响体验,限制每 $k = 9$ 篇笔记最多出现 1 篇运营推广笔记。 +- 如果排第 $i$ 位的是运营推广笔记,那么排 $i+1$ 到 $i+8$ 的不能是运营推广笔记。 + +**规则:前 $t$ 篇笔记最多出现 $k$ 篇某种笔记** + +- 排名前 $t$ 篇笔记最容易被看到,对用户体验最重要。 + (_小红书的 top 4 为首屏_) +- 小红书推荐系统有带电商卡片的笔记,过多可能会影响体验。 +- 前 $t=1$ 篇笔记最多出现 $k=0$ 篇带电商卡片的笔记。 +- 前 $t=4$ 篇笔记最多出现 $k=1$ 篇带电商卡片的笔记。 + +#### MMR + 重排规则 + +- MMR 每一轮选出一个物品: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \left\{ \theta \cdot \text{reward}_i - (1 - \theta) \cdot \max\limits_{j \in \mathcal{W}} \text{sim}(i, j) \right\} + $$ + +- 重排结合 MMR 与规则,在满足规则的前提下最大化 MR。 + +- 每一轮先用规则排除掉 $\mathcal{R}$ 中的部分物品,得到子集 $\mathcal{R'}$。 + +- MMR 公式中的 $\mathcal{R}$ 替换成子集 $\mathcal{R'}$,选中的物品符合规则。 + +### DPP:数学基础 + +#### 超平行体 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-4-1.png) + +- 2 维空间的 **超平行体** 为 **平行四边形**。 + +- 平行四边形中的点可以表示为: + + $$\mathbf{x} = \alpha_1 \mathbf{v}_1 + \alpha_2 \mathbf{v}_2.$$ + +- 系数 $\alpha_1$ 和 $\alpha_2$ 取值范围是 $[0,1]$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-4-2.png) + +- 3 维空间的 **超平行体** 为 **平行六面体**。 + +- 平行六面体中的点可以表示为: + + $$\mathbf{x} = \alpha_1 \mathbf{v}_1 + \alpha_2 \mathbf{v}_2 + \alpha_3 \mathbf{v}_3.$$ + +- 系数 $\alpha_1, \alpha_2, \alpha_3$ 取值范围是 $[0,1]$。 + +**超平行体** + +- 一组向量 $\mathbf{v}_1, \cdots, \mathbf{v}_k \in \mathbb{R}^d$ 可以确定一个 $k$ 维超平行体: + + $$P(\mathbf{v}_1, \cdots, \mathbf{v}_k) = \{\alpha_1 \mathbf{v}_1 + \cdots + \alpha_k \mathbf{v}_k \mid 0 \leq \alpha_1, \cdots, \alpha_k \leq 1\}.$$ + +- 要求 $k \leq d$,比如 $d = 3$ 维空间中有 $k = 2$ 维平行四边形。 + +- 如果 $\mathbf{v}_1, \cdots, \mathbf{v}_k$ 线性相关,则体积 $\text{vol}(P) = 0$。(例:有 $k = 3$ 个向量,落在一个平面上,则平行六面体的体积为 0。) + +**平行四边形的面积** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-4-3.png) + +以 $\mathbf{v}_2$ 为底,如何计算高 $\mathbf{q}_1$? + +- 计算 $\mathbf{v}_1$ 在 $\mathbf{v}_2$ 上的投影: + + $$\text{Proj}_{\mathbf{v}_2}(\mathbf{v}_1) = \frac{\mathbf{v}_1^T \mathbf{v}_2}{\|\mathbf{v}_2\|_2^2} \cdot \mathbf{v}_2.$$ + +- 计算 + + $$\mathbf{q}_1 = \mathbf{v}_1 - \text{Proj}_{\mathbf{v}_2}(\mathbf{v}_1).$$ + +- 性质:底 $\mathbf{v}_2$ 与高 $\mathbf{q}_1$ 正交。 + +**平行六面体的体积** + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-4-4.png) + +- **体积** = 底面积 × $\|\text{高}\|_2$。 + +- 平行四边形 $P(\mathbf{v}_1, \mathbf{v}_2)$ 是平行六面体 $P(\mathbf{v}_1, \mathbf{v}_2, \mathbf{v}_3)$ 的底。 + +- 高 $\mathbf{q}_3$ 垂直于底 $P(\mathbf{v}_1, \mathbf{v}_2)$。 + +**体积何时最大化、最小化?** + +- 设 $\mathbf{v}_1$、$\mathbf{v}_2$、$\mathbf{v}_3$ 都是单位向量。 + +- 当三个向量正交时,平行六面体为正方体,体积最大化,$\text{vol} = 1$。 + +- 当三个向量线性相关时,体积最小化,$\text{vol} = 0$。 + +#### **衡量物品多样性** + +- 给定 $k$ 个物品,把它们表征为单位向量 $\mathbf{v}_1, \cdots, \mathbf{v}_k \in \mathbb{R}^d$。($d \geq k$) + +- 用超平行体的体积衡量物品的多样性,体积介于 $0$ 和 $1$ 之间。 + +- 如果 $\mathbf{v}_1, \cdots, \mathbf{v}_k$ 两两正交(_多样性好_),则体积最大化,$\text{vol} = 1$。 + +- 如果 $\mathbf{v}_1, \cdots, \mathbf{v}_k$ 线性相关(_多样性差_),则体积最小化,$\text{vol} = 0$。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-4-5.png) + +- 给定 $k$ 个物品,把它们表征为单位向量 $\mathbf{v}_1, \cdots, \mathbf{v}_k \in \mathbb{R}^d$。($d \geq k$) + +- 把它们作为矩阵 $\mathbf{V} \in \mathbb{R}^{d \times k}$ 的列。 + +- 设 $d \geq k$,行列式与体积满足: + + $$\det(\mathbf{V}^T \mathbf{V}) = \text{vol}(P(\mathbf{v}_1, \cdots, \mathbf{v}_k))^2.$$ + +- 因此,可以用行列式 $\det(\mathbf{V}^T \mathbf{V})$ 衡量向量 $\mathbf{v}_1, \cdots, \mathbf{v}_k$ 的多样性。 + +### DPP:多样性算法 + +#### **多样性问题** + +- 精排给 $n$ 个物品打分:$\text{reward}_1, \cdots, \text{reward}_n$。 + +- $n$ 个物品的向量表征:$\mathbf{v}_1, \cdots, \mathbf{v}_n \in \mathbb{R}^d$。 + +- 从 $n$ 个物品中选出 $k$ 个物品,组成集合 $\mathcal{S}$。 + - **价值大**:分数之和 $\sum_{j \in \mathcal{S}} \text{reward}_j$ 越大越好。 + - **多样性好**:$\mathcal{S}$ 中 $k$ 个向量组成的超平行体 $P(\mathcal{S})$ 的体积越大越好。 + +- 集合 $\mathcal{S}$ 中的 $k$ 个物品的向量作为列,组成矩阵 $\mathbf{V}_{\mathcal{S}} \in \mathbb{R}^{d \times k}$。 + +- 以这 $k$ 个向量作为边,组成超平行体 $P(\mathcal{S})$。 + +- 体积 $\text{vol}(P(\mathcal{S}))$ 可以衡量 $\mathcal{S}$ 中物品的多样性。 + +- 设 $k \leq d$,行列式与体积满足: + + $$\det(\mathbf{V}_{\mathcal{S}}^T \mathbf{V}_{\mathcal{S}}) = \text{vol}(P(\mathcal{S}))^2$$ + +#### **行列式点过程(DPP)** + +- DPP 是一种传统的统计机器学习方法: + + $$ + \arg\max_{\mathcal{S}: |\mathcal{S}|=k} \log \det(\mathbf{V}_{\mathcal{S}}^T \mathbf{V}_{\mathcal{S}}) + $$ + +- Hulu 的论文将 DPP 应用在推荐系统: + + $$ + \arg\max_{\mathcal{S}: |\mathcal{S}|=k} \theta \cdot \left( \sum_{j \in \mathcal{S}} \text{reward}_j \right) + (1 - \theta) \cdot \log \det(\mathbf{V}_{\mathcal{S}}^T \mathbf{V}_{\mathcal{S}}) + $$ + +- **DPP 应用在推荐系统**: + + $$ + \arg\max_{\mathcal{S}: |\mathcal{S}|=k} \theta \cdot \left( \sum_{j \in \mathcal{S}} \text{reward}_j \right) + (1 - \theta) \cdot \log \det(\mathbf{V}_{\mathcal{S}}^T \mathbf{V}_{\mathcal{S}}) + $$ + +- 设 $\mathbf{A}$ 为 $n \times n$ 的矩阵,它的 $(i,j)$ 元素为 $a_{ij} = \mathbf{v}_i^T \mathbf{v}_j$。 + +- 给定向量 $\mathbf{v}_1, \cdots, \mathbf{v}_n \in \mathbb{R}^d$,需要 $O(n^2 d)$ 时间计算 $\mathbf{A}$。 + +- $\mathbf{A}_{\mathcal{S}} = \mathbf{V}_{\mathcal{S}}^T \mathbf{V}_{\mathcal{S}}$ 为 $\mathbf{A}$ 的一个 $k \times k$ 子矩阵。如果 $i, j \in \mathcal{S}$,则 $a_{ij}$ 是 $\mathbf{A}_{\mathcal{S}}$ 的一个元素。 + +- **DPP 应用在推荐系统**: + + $$ + \arg\max_{\mathcal{S}: |\mathcal{S}|=k} \theta \cdot \left( \sum_{j \in \mathcal{S}} \text{reward}_j \right) + (1 - \theta) \cdot \log \det(\mathbf{A}_{\mathcal{S}}) + $$ + +- **DPP 是个组合优化问题**,从集合 $\{1, \cdots, n\}$ 中选出一个大小为 $k$ 的子集 $\mathcal{S}$。 + +- 用 $\mathcal{S}$ 表示已选中的物品,用 $\mathcal{R}$ 表示未选中的物品,贪心算法求解: + + $$ + \arg\max_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det(\mathbf{A}_{\mathcal{S} \cup \{i\}}) + $$ + +#### 求解DPP + +**暴力算法** + +- **贪心算法求解**: + + $$ + \arg\max_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det(\mathbf{A}_{\mathcal{S} \cup \{i\}}). + $$ + +- **计算复杂度分析**: + - 对于单个 $i$,计算 $\mathbf{A}_{\mathcal{S} \cup \{i\}}$ 的行列式需要 $O(|\mathcal{S}|^3)$ 时间。 + + - 对于所有的 $i \in \mathcal{R}$,计算行列式需要时间 $O(|\mathcal{S}|^3 \cdot |\mathcal{R}|)$。 + + - 需要求解上述式子 $k$ 次才能选出 $k$ 个物品。如果暴力计算行列式,那么总时间复杂度为: + + $$ + O(|\mathcal{S}|^3 \cdot |\mathcal{R}| \cdot k) = O(nk^4). + $$ + +- **暴力算法的总时间复杂度为**: + $$ + O(n^2 d + nk^4). + $$ + +**Hulu 的快速算法** + +- Hulu 的论文设计了一种数值算法,仅需 $O(n^2 d + nk^2)$ 的时间从 $n$ 个物品中选出 $k$ 个物品。 + +- 给定向量 $\mathbf{v}_1, \cdots, \mathbf{v}_n \in \mathbb{R}^d$,需要 $O(n^2 d)$ 时间计算 $\mathbf{A}$。 + +- 用 $O(nk^2)$ 时间计算所有的行列式(**利用 Cholesky 分解**)。 + +- **Cholesky 分解** $\mathbf{A}_{\mathcal{S}} = \mathbf{L} \mathbf{L}^T$,其中 $\mathbf{L}$ 是下三角矩阵(**对角线以上的元素全零**)。 + +- **Cholesky 分解可供计算** $\mathbf{A}_{\mathcal{S}}$ **的行列式**: + - 下三角矩阵 $\mathbf{L}$ 的行列式 $\det(\mathbf{L})$ 等于 $\mathbf{L}$ 对角线元素乘积。 + + - $\mathbf{A}_{\mathcal{S}}$ 的行列式为: + + $$ + \det(\mathbf{A}_{\mathcal{S}}) = \det(\mathbf{L})^2 = \prod_i l_{ii}^2. + $$ + +- 已知 $\mathbf{A}_{\mathcal{S}} = \mathbf{L} \mathbf{L}^T$,则可以快速求出所有 $\mathbf{A}_{\mathcal{S} \cup \{i\}}$ 的 Cholesky 分解,因此可以快速计算出所有 $\mathbf{A}_{\mathcal{S} \cup \{i\}}$ 的行列式。 + +- **贪心算法求解**: + + $$ + \arg\max_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det(\mathbf{A}_{\mathcal{S} \cup \{i\}}). + $$ + +- **初始化**:$\mathcal{S}$ 中只有一个物品,$\mathbf{A}_{\mathcal{S}}$ 是 $1 \times 1$ 的矩阵。 + +- **每一轮循环**: + - 基于上一轮算出的 $\mathbf{A}_{\mathcal{S}} = \mathbf{L} \mathbf{L}^T$,快速求出 $\mathbf{A}_{\mathcal{S} \cup \{i\}}$ 的 Cholesky 分解($\forall i \in \mathcal{R}$)。 + - 从而求出 $\log \det(\mathbf{A}_{\mathcal{S} \cup \{i\}})$。 + +#### DPP 的扩展 + +**滑动窗口** + +- 用 $\mathbf{S}$ 表示已选中的物品,用 $\mathcal{R}$ 表示未选中的物品,DPP 的贪心算法求解: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det (\mathbf{A}_{\mathbf{S} \cup \{i\}}). + $$ + +- 随着集合 $\mathbf{S}$ 增大,其中相似物品越来越多,物品向量会趋近线性相关。 + +- 行列式 $\det(\mathbf{A}_{\mathbf{S}})$ 会塌缩到零,对数趋于负无穷。 + +![](https://raw.githubusercontent.com/H0SH123/Books-and-Notes/main/RecommenderSystem/images/6-5-1.png) + +- 贪心算法: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det (\mathbf{A}_{\mathbf{S} \cup \{i\}}) + $$ + +- 用滑动窗口: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det (\mathbf{A}_{\mathcal{W} \cup \{i\}}) + $$ + +**规则约束** + +- 贪心算法每轮从 $\mathcal{R}$ 中选出一个物品: + + $$ + \arg\max\limits_{i \in \mathcal{R}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det (\mathbf{A}_{\mathcal{W} \cup \{i\}}) + $$ + +- 有很多规则约束,例如最多连续出 5 篇视频笔记(_如果已经连续出了 5 篇视频笔记,下一篇必须是图文笔记_)。 + +- 用规则排除掉 $\mathcal{R}$ 中的部分物品,得到子集 $\mathcal{R'}$,然后求解: + + $$ + \arg\max\limits_{i \in \mathcal{R'}} \theta \cdot \text{reward}_i + (1 - \theta) \cdot \log \det (\mathbf{A}_{\mathcal{W} \cup \{i\}}) + $$ diff --git a/app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md new file mode 100644 index 0000000..1eaa40d --- /dev/null +++ b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md @@ -0,0 +1,417 @@ +--- +title: 手写线程池 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: mnjkrtrs7xk3fq538eqreuge +--- + +# 线程池 + +> 以下是调用单队列BlockingQueue的代码,使用双队列时将所有BlockingQueue改为BlockingQueuePro + +## 接口 + +构造:用智能指针 unique_ptr 初始化阻塞队列,创建threads_num个线程并给每个绑定Worker函数开始执行task。(此时全部Worker阻塞在Pop()里) + +Post: 发布任务到线程池。 + +析构:通知唤醒所有消费者,没有任务就自己下班。与生产者没关系。 + +```cpp +#pragma once + +#include +#include +#include + +template +class BlockingQueue; + +class ThreadPool { +public: + // 初始化线程池 + explicit ThreadPool(int threads_num); + + // 停止线程池 + ~ThreadPool(); + + // 发布任务到线程池 + void Post(std::function task); + +private: + // 每个线程运行的循环函数 + void Worker(); + // 任务队列 + std::unique_ptr>> task_queue_; + // 存放所有的线程对象。每个线程都会绑定 Worker() 执行函数 + std::vector workers_; +}; +``` + +```cpp +#include "blockingqueue.h" +#include +#include "threadpool.h" + +// 创建一个阻塞队列,开threads_num个线程,每个线程绑定执行 Worker() 方法。 +ThreadPool::ThreadPool(int threads_num) { + task_queue_ = std::make_unique>>(); + for (size_t i = 0; i < threads_num; ++i) { + workers_.emplace_back([this] {Worker();}); +// 使用 lambda 捕获 this [this] { Worker(); },所以每个线程都能调用当前对象的 Worker 函数。 + } +} + +// 停止线程池 +// 取消队列并唤醒所有阻塞线程 +ThreadPool::~ThreadPool() { + task_queue_->Cancel(); + for(auto &worker : workers_) { + if (worker.joinable()) + worker.join(); + } +} + +// 发布任务 +void ThreadPool::Post(std::function task) { + task_queue_->Push(task); +} + +// 从task_queue_中取出任务 +void ThreadPool::Worker() { + while (true) { + std::function task; + // 阻塞在Pop里实现 + if (!task_queue_->Pop(task)) { + break; + } + task(); + } +} +``` + +# 阻塞队列(单队列版) + +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758722093302-3845f815-ddbc-4bee-a789-de63daa92cd1.jpeg) + +**单队列维护:** + +1. nonblock\_(bool): 未阻塞标记。为true时,队列不会阻塞。初始(构造时)默认为阻塞状态。 +2. queue\_(std::queue): 底层存储容器。 +3. mutex\_(std::mutex): 保证多线程操作安全的互斥锁。 +4. not*empty*(std::condition_variable):用于线程前同步。 + +```cpp +template +class BlockingQueue { +public: + BlockingQueue(bool nonblock = false) : nonblock_(nonblock) { } + // 入队操作 + void Push(const T &value) { + // lock_guard自动加锁解锁,与声明周期一致(实现了构造加锁和析构解锁) + std::lock_guard lock(mutex_); + // 元素入队 + queue_.push(value); + // 通知一个正在 wait 的线程: 队列不空,可以取任务。 + not_empty_.notify_one(); + } + // 正常 pop 弹出元素 + // 异常 pop 没有弹出元素 + bool Pop(T &value) { + // 加锁 + // 如果该线程已被加锁则进不来 + // condition_variable::wait需要这个unique_lock + std::unique_lock lock(mutex_); + + // 这一行的主要作用是 维护取操作的安全性(在队列非空的时候才能继续往下走。 + // 但是如果只判断队列是否为空来决定是否往下走,Cancel之后怎么办? + // 即我想清空队列后结束所有线程,此时消费者线程依旧会被阻塞在这一行,因为它不知道是否要结束。 + // 所以我们设置标志nonblock_来告诉消费者线程是否结束) + // 当 队列非空 或者 队列执行了Cancel 时 -> predicate谓词为true->正常往下走 + // 即队列里还有任务,或者队列里没任务了,但是消费者知道任务结束了,可以继续下班了。都会继续往下走。 + // 当 队列为空 并且 队列未执行Cancel 时 -> 谓词为false -> 此线程自动解锁mutex,让出CPU,阻塞在这一行 + // 即队列空了,并且消费者知道还没下班,则阻塞等待。 + not_empty_.wait(lock, [this]{ return !queue_.empty() || nonblock_; }); + if (queue_.empty()) return false; // 该消费者线程下班 + + value = queue_.front(); + queue_.pop(); + return true; + } + + // 解除阻塞在当前队列的线程 + void Cancel() { + // 自动加锁解锁 + std::lock_guard lock(mutex_); + // 告诉消费者下班 + nonblock_ = true; + // 主动唤醒所有阻塞在wait的消费者(在wait的消费者被唤醒后还会走一遍wait的谓词判断。false则继续阻塞) + not_empty_.notify_all(); + } + +private: + bool nonblock_; + std::queue queue_; + std::mutex mutex_; + std::condition_variable not_empty_; +}; +``` + +# 阻塞队列(双队列版) + +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1759131100901-946e59ae-cd19-4546-aa9d-a9ee658f0b5a.jpeg) + +单队列中, 生产者和消费者都要竞争同一把锁。 + +双队列: + +- `prod_queue_`:生产者写入队列(保护锁 `prod_mutex_`)。 +- `cons_queue_`:消费者读取队列(保护锁 `cons_mutex_`)。 +- 当消费者队列空时,通过 `SwapQueue_()` 将生产队列和消费队列 **交换**,实现“批量搬运”。 + +好处是: + +- **减少锁竞争**:生产者和消费者基本不抢同一把锁。 +- **吞吐更高**:一次交换,消费者能批量取走数据,减少频繁加锁。 + +```cpp +template +class BlockingQueuePro { +public: + BlockingQueuePro(bool nonblock = false) : nonblock_(nonblock) {} + + void Push(const T &value) { + std::lock_guard lock(prod_mutex_); + prod_queue_.push(value); + not_empty_.notify_one(); + } + + bool Pop(T &value) { + std::unique_lock lock(cons_mutex_); + // 消费者队列为空时自动触发swap,如果swap后仍为空,则消费失败 + // 此时就不需要.wait了 + if (cons_queue_.empty() && SwapQueue_() == 0) { + return false; + } + value = cons_queue_.front(); + cons_queue_.pop(); + return true; + } + + void Cancel() { + std::lock_guard lock(prod_mutex_); + nonblock_ = true; + not_empty_.notify_all(); + } + +private: + int SwapQueue_() { + std::unique_lock lock(prod_mutex_); + // 当生产者队列为空,并且未告知下班时 -> 两个false -> 阻塞 + // 当Cancel了,不管队列是否为空,都会往下走 + not_empty_.wait(lock, [this] {return !prod_queue_.empty() || nonblock_; }); + std::swap(prod_queue_, cons_queue_); + // 返回交换后消费者队列中的任务数量 + // =0只有一种情况,生产者队列为空时被告知下班 + return cons_queue_.size(); + } + + bool nonblock_; + std::queue prod_queue_; + std::queue cons_queue_; + std::mutex prod_mutex_; + std::mutex cons_mutex_; + std::condition_variable not_empty_; +}; +``` + +# Test + +理论上双队列比单队列快,因为锁的竞争/碰撞少了。下面的实验结果也是如此: + +写一个实验:设置4个生产者线程,一个生产者给25000个任务,任务是1000次循环。消费者数量指定当前cpu最适合线程数(std::thread::hardware_concurrency())。我这个WSL环境是16个。 + +分别跑single(单队列)和double(双队列),输出总耗时和QPS。 + +```cpp +std::atomic task_counter{0}; +int main() { + const int num_producers = 4; + const int num_tasks_per_producer = 25000; // 总共 100,000 个任务 + const int num_threads_in_pool = std::thread::hardware_concurrency(); + + // 为了测试,single版本和double版本改为了继承自ThreadPoolBase的两个派生类 + // 想看具体实现可见附录 + ThreadPoolSingle pool(num_threads_in_pool); + + auto start = std::chrono::high_resolution_clock::now(); + + std::vector producers; + // 4个生产者开始干活 + for (int i = 0; i < num_producers; ++i) { + producers.emplace_back(Producer, std::ref(pool), i, num_tasks_per_producer); + } + for (auto& p : producers) { + p.join(); + } + + // 这里主线程忙等,用std::condition_variable,等待唤醒可能更高效 + while (task_counter < num_producers * num_tasks_per_producer) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + auto end = std::chrono::high_resolution_clock::now(); + double elapsed = std::chrono::duration(end - start).count(); + + int total_tasks = num_producers * num_tasks_per_producer; + std::cout << "[Single Queue] Total: " << total_tasks + << " tasks. Time: " << elapsed + << " seconds. QPS = " << total_tasks / elapsed << std::endl; +} +``` + +```cpp +void Task(int id) { + volatile long sum = 0; + for (int i = 0; i < 1000; ++i) { + sum += i; + } + task_counter++; +} + +void Producer(ThreadPoolSingle& pool, int producer_id, int num_tasks) { + for (int i = 0; i < num_tasks; ++i) { + int task_id = producer_id * 100000 + i; + pool.Post([task_id]() { Task(task_id); }); + } +} +``` + +结果: +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1758958199070-807c1517-f594-4617-bf88-3f8228a66594.png) + +将任务数和主线程轮询时间各减一个0后(任务数改为2500,主线程轮询等待5)(因为完成的太快了,主线程等待时间过长影响结果) + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1758958396516-f9017fa6-3732-4c52-b585-ed44e7c8b3ef.png) + +用平均数更准,但是这么大差距也不会被误差所影响。 + +# 附录 + +修改版单队列和双队列 + +```cpp +#pragma once + +#include +#include +#include +#include "blockingqueue.h" +#include "blockingqueuepro.h" + +// 前置声明 +// blockingqueue 仅仅只能用作指针或引用 + +// template +// class BlockingQueue; +// template +// class BlockingQueuePro; + +// class ThreadPool { +// public: +// // 初始化线程池 +// explicit ThreadPool(int threads_num); + +// // 停止线程池 +// ~ThreadPool(); + +// // 发布任务到线程池 +// void Post(std::function task); + +// private: +// void Worker(); +// std::unique_ptr>> task_queue_; +// std::vector workers_; +// }; + + +class ThreadPoolBase { +public: + explicit ThreadPoolBase(int threads_num) : threads_num_(threads_num) {} + virtual ~ThreadPoolBase() = default; + + virtual void Post(std::function task) = 0; + +protected: + int threads_num_; + std::vector workers_; +}; + +class ThreadPoolSingle : public ThreadPoolBase { +public: + explicit ThreadPoolSingle(int threads_num) + : ThreadPoolBase(threads_num), + task_queue_(std::make_unique>>()) { + for (int i = 0; i < threads_num_; ++i) { + workers_.emplace_back([this] { Worker(); }); + } + } + + ~ThreadPoolSingle() { + task_queue_->Cancel(); + for (auto &w : workers_) { + if (w.joinable()) w.join(); + } + } + + void Post(std::function task) override { + task_queue_->Push(task); + } + +private: + void Worker() { + while (true) { + std::function task; + if (!task_queue_->Pop(task)) break; + task(); + } + } + + std::unique_ptr>> task_queue_; +}; + +class ThreadPoolDouble : public ThreadPoolBase { +public: + explicit ThreadPoolDouble(int threads_num) + : ThreadPoolBase(threads_num), + task_queue_(std::make_unique>>()) { + for (int i = 0; i < threads_num_; ++i) { + workers_.emplace_back([this] { Worker(); }); + } + } + + ~ThreadPoolDouble() { + task_queue_->Cancel(); + for (auto &w : workers_) { + if (w.joinable()) w.join(); + } + } + + void Post(std::function task) override { + task_queue_->Push(task); + } + +private: + void Worker() { + while (true) { + std::function task; + if (!task_queue_->Pop(task)) break; + task(); + } + } + + std::unique_ptr>> task_queue_; +}; +``` diff --git a/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md new file mode 100644 index 0000000..7566d4b --- /dev/null +++ b/app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md @@ -0,0 +1,132 @@ +--- +title: 手写定长内存池 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: xgxqqvglxyauoeh8eye7lzu6 +--- + +# 手写定长内存池 + +## 设计图 + +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758718719250-e6f52459-0f73-493b-8294-7b8f931da054.jpeg) + +## 代码部分 + +### 结构体定义 + +#### 管理内存池结构的结构体mempool_s + +```c +typedef struct mempool_s { + int blocksize; // 每个内存块的size + int freecount; // 剩余空的内存块数量 + char *free_ptr; // 指向下一空内存块 + char *mem; // 整块内存的头指针 +} mempool_t; +``` + +### 对外接口 + +#### memp_create: 内存池创建 + +```c +int memp_create(mempool_t *m, int block_size) { + + if (!m) return -1; + + // 1. 初始化这两个简单的int + m->blocksize = block_size; + m->freecount = MEM_PAGE_SIZE / block_size; + + // 2. 开整个内存池的空间,顺便初始化m->mem + m->mem = (char *)malloc(MEM_PAGE_SIZE); + if (!m->mem) { // 开失败了(剩余空闲内存不够) + return -2; + } + // 将这个空间初始化一下 + memset(m->mem, 0, MEM_PAGE_SIZE); + + // 3. 初始化free_ptr + m->free_ptr = m->mem; + + // 依次初始化每个block里的"next->ptr" + int i = 0; + char *ptr = m->mem; + for (i = 0;i < m->freecount;i ++) { + + *(char **)ptr = ptr + block_size; + ptr = ptr + block_size; + } + // 最后一个block的"next_ptr"指向NULL + *(char **)ptr = NULL; + return 0; +} +``` + +#### memp_alloc: 分配block + +```c +void *memp_alloc(mempool_t *m) { + // 满了 + if (!m || m->freecount == 0) return NULL; + // 1. 获取当前下一个空闲块,作为返回值 + void *ptr = m->free_ptr; + // 2. 更新free_ptr + m->free_ptr = *(char **)ptr; + // 3. 更新freecount + m->freecount --; + + return ptr; +} +``` + +#### memp_free: 删除指定block + +```c +void memp_free(mempool_t *m, void *ptr) { + // 相当于 ptr->next = m->free_ptr; + // 头插法将要释放的block插到空闲block链表的头部,即空闲block链表又增加了一个 + *(char **)ptr = m->free_ptr; + // 更新free_ptr这一空闲block链表头指针 + m->free_ptr = (char *)ptr; + // 更新freecount + m->freecount ++; +} +``` + +#### memp_destory: 删除整个内存池 + +```c +void memp_destory(mempool_t *m) { + if (!m) return ; + // 直接free内存池,因为内存池是整体malloc的,而不是一个一个block malloc的 + free(m->mem); +} +``` + +## 使用example + +```c +int main() { + mempool_t m; + memp_create(&m, 32); + + void *p1 = memp_alloc(&m); + printf("memp_alloc : %p\n", p1); + + void *p2 = memp_alloc(&m); + printf("memp_alloc : %p\n", p2); + + void *p3 = memp_alloc(&m); + printf("memp_alloc : %p\n", p3); + + memp_free(&m, p2); +} +``` + +输出:可以看到每个block确实是32个字节 + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759069995143-4548da88-8c23-463e-b9e7-0f7d8978f03b.png) diff --git a/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md b/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md new file mode 100644 index 0000000..91c5350 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md @@ -0,0 +1,184 @@ +--- +title: linux/win上的c++库 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: totx4pej5lhyt1nl4anwhakj +--- + +# linux/win上的c++库 + +## 一、一个库长什么样? + +每个库源码的格式都不一样。但都会有: + +1. .hpp/.h(位于include或根目录下) :用来链接的头文件,不能没有 +2. .cpp :实现头文件的逻辑,有的源码被称为头文件库(直接在头文件里实现了逻辑,没有.cpp实现,也不需要链接动态库或静态库)。 + +- 在 Linux 上:`XXX.so`(shared object动态库) 或 `XXX.a`(static静态库) +- 在 Windows 上:`XXX.lib`(静态库) 或 `XXX.dll`(动态库) + +## 二、如何得到一个库? + +> 非特殊标明,都是linux环境(具体为Ubuntu) + +### 下载别人写的库 + +#### win环境 + + 1. **手动从源码编译安装 :**下载或git clone一个zip/7z等压缩包,解压后得到源代码,编译得到所需库。 + 2. **使用对应语言的包管理工具**(c++: 如vcpkg,python: 如pip,java: 如maven)下载。如vcpkg会将包下载到vcpkg/installed/下(默认)。 + +> 简单介绍一下vcpkg +> +> - **vcpkg** 是微软开源的跨平台 C/C++ 包管理器。 +> - 支持 **Windows / Linux / macOS**,但最早是为 Windows 生态开发的。 +> - 主要用来解决:C/C++ 第三方库获取困难、编译配置复杂的问题。 + +#### linux环境 + + 1. **从源码编译安装 ** + +> 有些库只能源码安装,比如最新版本的 gRPC、Protobuf。 +> +> 一般步骤是:clone -> make流程或者build.sh这种一键脚本。 + + 2. **使用系统自带的包管理器** + +> apt, yum, dnf等 + + 3. **使用对应语言的包管理工具** + +> vcpkg,conan等 + +### 自己写一个库 + +写完.cpp和.h后,可选择将其编译成静态库或动态库。 + +#### 打包为静态库 + +```bash +g++ -c mylib.cpp -o mylib.o +ar rcs my_static_lib.a mylib.o // 不建议 +// 事实上,打包时应在目标文件名前+lib。 +// 这不只是约定俗成。 +// 使用-lmylib时,链接器会自动加前缀去找,即找libmylib.a或.so。 +// 如果不加lib前缀,只能写清完整路径了(不能依靠链接器自己去找了) +ar rcs libmy_static_lib.a mylib.o // 建议 +``` + +#### 打包为动态库 + +```bash +// 简单的项目,一步到位 +g++ -shared -o mylib.dll mylib.cpp +// 复杂的项目,先转为中间文件.o,再将.o文件转为mylib.dll +g++ -c -fPIC mylib.cpp -o mylib.o +g++ -shared -o my_dynamic_lib.dll mylib.o +``` + +-fPIC表示生成位置独立代码(Position Independent Code),这是动态库所需要的。 + +如上,我们就得到my_dynamic_lib.dll动态库和my_static_lib.a静态库了。 + +## 三、下载的库被放在了哪里 + +#### python的库可能放在 + +- 项目根目录下的venv文件夹中(用venv创建的虚拟环境,也可以叫其他名,自己创建时命名) +- conda目录下的某环境文件夹中(下载conda时指定一个存放环境的文件路径,所有由conda创建的虚拟环境都放在这里,环境名作为二级目录名) + +#### 前端的库可能放在 + +- 项目根目录下的node_modules中(有npm包管理创建和管理) + +#### Java的Spring项目(Maven包管理)可能放在 + +- 缓存到 ~/.m2/repository/ (本地仓库,win和linux都是) + +#### c++的库(linux环境) + +- 系统自带库:`/lib`, `/lib64`, `/usr/lib`, `/usr/lib64` +- apt/yum/dnf/pacman 包管理器库:`/usr/lib/x86_64-linux-gnu`(库文件), `/usr/include`(头文件) +- 自己编译:`/usr/local/lib`, `/usr/local/include` +- 包管理器(vcpkg/conan):用户目录下的专用路径 + - vcpkg:`~/vcpkg/installed//lib` + - conan:`~/.conan/data///...` + - 自己放的:`~/lib`, `~/include` + +## 四、如何使用一个库呢? + +> 使用库两个步骤,一是**链接头文件**,二是**链接库实现** + +### 链接 头文件 + +> 不用-I(大写i)的时候默认寻找当前文件目录 + +1. 不改变库头文件位置,硬编码指定所有库的头文件路径在哪(每个头文件各指定一次) + +```bash +g++ -I path/to/s_lib1.h -I path/to/s_lib2.h ... -o output main.cpp +``` + +2. 整合头文件移到include目录下(注意如果是三方库,不建议移动,因为一般还有其他依赖项。自己的库,建议整合头文件),移到项目下的include目录下,然后指定头文件目录为include目录(只指定一次) + +```bash +g++ -I include/ -o output main.cpp +``` + +(注意指定头文件目录 + #include “path/to/lib.h”共同拼接成头文件完整路径) + +> 是否觉得纯命令行编译太麻烦太长,后面用CMake时写进CMakeList.txt里更方便 + +### 链接 库实现 + +> **对于非纯头文件库,还需要连接库实现,其分为动态库和静态库** + +#### 链接动态库 + +```bash +g++ myapp.cpp -L /path/to/library -l mylib +``` + +在运行时建议将 .dll动态库 放在与 可执行文件 同级目录下。因为其查找顺序为: + +1. win环境(优先级高到低) + +- 程序当前目录(通常是 `exe` 文件所在的目录)。 +- Windows 系统目录(例如 `C:\Windows\System32`)。 +- 当前用户的 `AppData` 文件夹。 + +2. linux环境(优先级高到低) + +- 运行时指定的 `LD_LIBRARY_PATH` 环境变量 +- 可执行文件的 `rpath` / `runpath` 设置 +- 系统配置文件 `/etc/ld.so.cache` +- ** 系统默认目录(最常用)** - `/lib` - `/usr/lib` - `/usr/local/lib` - `/lib64`(64 位系统) + 这些是硬编码在动态链接器里的。 + +> 也可以1. 设置PATH环境变量。2. 在代码里使用LoadLibrary("D:\libs\mylib.dll"); 显示加载DLL + +#### 链接静态库 + +```bash +g++ main.cpp -L /path/to/lib -l +``` + +注意:静态库在编译过程中,只会检查头文件的可用性,不会在编译阶段检查三方库是否存在或是否链接正确。这是因为静态库的编译阶段只生成目标文件(`.o` 文件)并打包成 `.a` 文件,而不会涉及链接阶段。因此,在一个c++程序实现时,可划分为以下三个阶段: + +- **编译静态库时**: + - 只需要包含头文件(`include_directories`),不需要指定三方库的 `.a` 或 `.so` 文件。 +- **编译主程序时**: + - 只需要包含静态库的头文件。 + - 不需要指定三方库的 `.a` 或 `.so` 文件。 +- **主程序链接阶段**: + - 必须显式引入静态库和它依赖的三方库文件(如 `lthirdparty`)。 + +**四. 常见编译参数解释**(命令行编译方式): + +1. -I(这是个大写i) + 库的头文件目录,告诉编译器去哪找头文件(必须) +2. -L + 指定库文件所在目录 +3. -l(这是个小写L) + ,指定库名,搜索目标是.so或.a(Win的是.dll和.lib)(不需要写后缀,优先链接动态库) + +-L和-l(小写L)的区别是前者指定库所在路径,后者指定该路径下具体库名 diff --git a/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md b/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md new file mode 100644 index 0000000..a97f44f --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md @@ -0,0 +1,152 @@ +--- +title: 基础gcc/g++ +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: kyu85av71b4n07hbdycbhvj9 +--- + +# 基础gcc/g++ + +## g++启蒙 + +### 下载gcc/g++(Linux平台) + +利用主流发行版的包管理器安装。 + +```cpp +sudo apt update +sudo apt install build-essential -y +// 检查版本 +gcc --version +g++ --version +``` + +```cpp +// CentOS / RHEL 7: +sudo yum groupinstall "Development Tools" -y +// CentOS Stream 8 / RHEL 8+ / Fedora: +sudo dnf groupinstall "Development Tools" -y +``` + +### 下载gcc/g++(windows平台) + +下载(以下是几个网址)并写入环境变量(以便在任何工作目录下都可以使用gcc/g++命令(.exe可执行文件)) + +1. minGW + +[https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/](https://www.mingw-w64.org/downloads/) + +[https://www.mingw-w64.org/downloads/](https://www.mingw-w64.org/downloads/) (推荐) + +**MinGW关于thread内置库踩了个坑:** + +纯MinGW 9.2.0版本使用thread时需要这几步: + +1. 需要去这个仓库下载(补充)几个头文件[https://github.com/meganz/mingw-std-threads](https://github.com/meganz/mingw-std-threads) + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049314232-221dc93b-c560-4036-b049-db786935066f.png) + +将这几个头文件放入MinGW的include下。 + +1. 代码中引入头文件将include 改为include (这个点在上述仓库中的ReadMe里有写) +2. 如果是命令行编译,加上-D_WIN32_WINNT=0x0501这个参数,让编译器知道你正在针对 **Windows XP**(或更高版本)进行编译。(不知道是不是我的版本是win32的原因,也许mingw-win64版本不需要) + +这是我踩坑的版本 + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049360158-4e44b580-0b41-4c64-8266-3a2bf893aa12.png) + +这是换成w64devkit + +![](https://cdn.nlark.com/yuque/0/2025/png/43055607/1759049365783-54107cad-7f77-497a-a679-1cc80f2c5095.png) + +而且纯MinGW在使用MinGW Installer(mingw-get.exe)时还要下载mingw32-make + +### g++ 基本使用example + +创建一个文本文件(test.txt),改后缀为cpp(表明是一个cpp源文件),用记事本/VS code(等文本编辑器)打开,写入: + +```cpp +int main(){ + return 0; +} +``` + +保存关闭后。 + +在当前工作目录下(包含test.cpp的目录下)打开命令行(cmd),输入: + +```bash +g++ test.cpp +``` + +在当前工作目录下出现a.exe可执行文件(execute是执行的意思)。这是因为没有指定输出文件的名称,编译器默认命名输出的.exe文件为a + +下面在输出时命名输出文件(-o) + +```bash +g++ -o b test.cpp +``` + +执行后在当前工作目录下出现b.exe可执行文件。 + +-o参数表达output,后面跟着输出文件名。(上述命令中我写了输出文件名为b) + +可以更换参数与cpp文件的位置,如下(也可以随意更换参数与参数之间的位置) + +```bash +g++ test.cpp -o b +``` + +(但注意参数(-o)和后面的参数值(b)是一体的,不要拆开。) + +众所周知,c++/c的编译分为了四个阶段:(上述展示了“一步到位”的命令) + +预处理(Preprocessing)→ 编译(Compilation)→ 汇编(Assembly)→ 链接(Linking) + +| 阶段 | 输入文件 | 输出文件及扩展名 | 命令参数(缩写含义) | +| ------ | -------- | ------------------------------------ | -------------------- | +| 预处理 | .cpp/.h | 预处理文件.i (Intermediate或Include) | -E (Expansion) | +| 编译 | .i | 汇编代码.s | -S (Source) | +| 汇编 | .s | 目标文件.o | -c (Compile) | +| 链接 | .o | 可执行文件.exe或无扩展 | 单g++ | + +_**预处理阶段: .cpp → .i**_ +处理头文件包含(`#include`)、宏展开(`#define`)、条件编译(`#ifdef` 等)等指令 + +```bash +g++ -E test.cpp // 预处理后的代码(包含展开的宏和包含的头文件内容)直接显示在终端。 +g++ -E test.cpp -o preprocess.i // 生成b.i输出文件 +``` + +_**编译阶段: .i → .s**_ + +预处理后的代码 → 汇编代码 + +```bash +g++ -S preprocess.i -o assemble.s +``` + +_**汇编阶段: .s → .o**_ + +将汇编代码 → 机器代码,生成目标文件(通常不可直接执行)。 + +```bash +g++ -c assemble.s -o machine.o +g++ -c test.cpp // 也可以直接放入.cpp生成同名.o机器代码 +``` + +_**链接阶段: .o → .exe**_ + +将一个或多个目标文件与库文件链接 → 可执行文件。 + +```bash +g++ machine.o -o test +``` + +_**生成调试信息:**_ + +```bash +g++ -g test.cpp -o test // 比直接g++ test.cpp -o test 多生成调试信息 +``` diff --git a/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md b/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md new file mode 100644 index 0000000..6fbf759 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/3_Make.md @@ -0,0 +1,65 @@ +--- +title: Make编译 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: g6wucmr69lamd9xyxm7uunnd +--- + +# Make编译 + +### **1. **`make`** 工作原理** + +`make` 的工作方式基于文件的 **依赖关系** 和 **时间戳**,它通过以下步骤来管理构建过程: + +1. 读取 `Makefile`:`make` 通过读取 `Makefile` 文件来获取构建规则。 +2. **检查目标文件的修改时间**:`make` 会根据文件的修改时间来判断是否需要重新编译。例如,如果源文件 `source.c` 的修改时间晚于目标文件 `source.o`,`make` 会认为目标文件过时,重新执行相关的编译命令。 +3. **执行构建规则**:如果目标文件需要重新构建,`make` 会根据依赖关系和规则执行编译、链接等操作,直到最终目标文件(如可执行文件或库文件)完成。 + +### **2. **`Makefile`** 的基本结构** + +`Makefile` 是 `make` 使用的配置文件,定义了构建规则、目标文件、依赖关系和命令。一个基本的 `Makefile` 通常包含以下几个部分: + +### **基本语法** + +- **目标(Target)**:要构建的文件(通常是目标文件或最终可执行文件)。 +- **依赖(Dependency)**:目标文件所依赖的文件。如果依赖文件有更新,目标文件就需要重新构建。 +- **命令(Command)**:用于生成目标的具体命令(如编译命令)。命令必须以 `TAB` 缩进。 + +```makefile +target: dependencies + command +``` + +### **3. **`Makefile`** 示例** + +假设我们有一个简单的 C++ 项目,包含两个源文件 `main.cpp` 和 `utils.cpp`,它们生成目标文件 `main.o` 和 `utils.o`,并最终生成可执行文件 `myapp`。 + +```makefile +CC = g++ # 编译器设置为 g++ +CFLAGS = -Wall -g # 编译选项,-Wall 启用所有警告,-g 用于调试 + +# 目标文件 +OBJS = main.o utils.o # 目标文件 + +# 可执行文件 +TARGET = myapp + +# 默认目标 +all: $(TARGET) + +$(TARGET): $(OBJS) # 目标文件依赖关系 + $(CC) $(OBJS) -o $(TARGET) # 链接命令,生成可执行文件 + +main.o: main.cpp utils.h + $(CC) $(CFLAGS) -c main.cpp # 编译命令,生成 main.o + +utils.o: utils.cpp utils.h + $(CC) $(CFLAGS) -c utils.cpp # 编译命令,生成 utils.o + +clean: + rm -f $(OBJS) $(TARGET) # 清理中间文件和目标文件 +``` + +目标作为执行的 diff --git a/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md b/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md new file mode 100644 index 0000000..67a011b --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md @@ -0,0 +1,166 @@ +--- +title: CMake +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: xk44lx4q1gpcm1uqk8nnbg7q +--- + +# CMake + +[https://juejin.cn/post/6844903557183832078](https://juejin.cn/post/6844903557183832078) 掘金的一个cmake教程 + +[https://zhuanlan.zhihu.com/p/97369704](https://zhuanlan.zhihu.com/p/97369704) 知乎的一个cmake教程 + +CMake用来编译一个c++项目,将其编译为一个可执行文件或动态\静态lib库 + +## cmake相关指令 + +cmake —help查看更多信息 + +1. CMake需要一个CMakeList.txt或在执行CMake时将参数手动加入 +2. 在构建时建议在项目目录下新建一个build目录,进入build目录中执行(后续用—build参数可指定构建目录) + +```bash +cmake .. // + 需要额外添加的参数(未在CMakeList.txt中声明的,一般都在CMakeList.txt中声明了) +``` + +从命令可以看出,执行cmake的工作目录在build,是为了构建生成文件(包括构建过程中的中间文件和目标文件)不与项目目录文件混为一谈。而上一层指令(“..”)代表CMakeList.txt和源代码位于上一层即项目根目录中。 + +cmake分两步,第一步生成构建系统文件,第二部使用构建系统。对应关键指令: + +1. 执行预设 + +```bash +cmake --preset=default +``` + +build.ninja + +CMakeCache.txt + +CMakeFiles + +cmake_install.cmake + +vcpkg_installed + +vcpkg-manifest-install.log + +除了执行预设文件,还有原始的手动版本: + +声明-S配置源文件目录,-B配置build目录即可。比如 + +cmake -S . 代表设定当前目录为源目录(在cmake中有关键字**`CMAKE_SOURCE_DIR`**得到源目录) + +1. 执行构建 + +```bash +cmake --build vcpkg-build // 表示构建目标去vcpkg-build,这就不用进入build目录再cmake ..了 +``` + +build.ninja + +CMakeCache.txt + +CMakeFiles + +cmake_install.cmake + +libfeature-extraction-lib.a (比上面第一步多的一个生成的静态库) + +vcpkg_installed + +vcpkg-manifest-install.log + +## CMakeList.txt + +### find_package方法深入理解 + +find_package(某个库名 可选参数:CONFIG REQUIRED) + +find_package**不会提前查找所有 Boost 组件**,而是延迟到 `target_link_libraries` 或其他需要时才加载相应的组件。只是指定一个全局目标。 + +CONFIG + +REQUIRED + +find_package源代码 + +find_package寻找路径 + +### .cmake文件 + +CMakeList.txt和.cmake文件的语法是一模一样的。准确来说,.cmake文件就像CMakeList.txt一个封装好的结构。使用以下方法引入: + +```makefile +include(cmakes/gtests_main.cmake) +include(cmakes/gtests_fe.cmake) +``` + +一般结合条件语句,比如是否开启单元测试: + +```makefile +option(BUILD_TESTS "Build tests" OFF) +if (BUILD_TESTS) + include(cmakes/gtests_main.cmake) + include(cmakes/gtests_fe.cmake) +endif () +``` + +1. 每个cmake语法中的执行参数都与g++参数对应,除非是内部使用的(类似于内部形参,方便编写用的) + +CMakeList.txt中几个必需的执行参数/配置项,及其对应的g++参数 + +1. 定义生成的目标是exe还是lib add_executable(my_program main.cpp) 或 add_library(my_library STATIC my_library.cpp) + +前者对应 + +```bash +g++ -o my_program main.cpp +``` + +后者对应 + +```bash +g++ -c my_library.cpp -o my_library.o +ar rcs libmy_library.a my_library.o +``` + +1. 链接依赖库 target_link_libraries(my_program gtest gtest_main) +2. 依赖的外部头文件和库的头文件include_directories(my_program PRIVATE /path/to/include) 或 target_include_directories(my_program PRIVATE /path/to/include) +3. 设置构建类型 CMAKE_BUILD_TYPE + +内部使用,cmake专属: + +1. 指定CMake最低版本 cmake_minimum_required(VERSION 3.10) +2. 设置项目名称和版本 project(项目名称 VERSION 1.0 LANGUAGES CXX) +3. 找包 find_package(包名:与文件名同名 CONFIG REQUIRED) + +找包路径顺序(来自chatGPT,期待后续改正): + +**默认查找路径**`CMake` 会按照以下顺序查找 `spdlog` 的配置文件: + +- 在环境变量 `CMAKE_PREFIX_PATH` 指定的路径下查找。 +- 在系统默认的安装路径(如 `/usr/lib/cmake`、`/usr/local/lib/cmake`)中查找。 +- 在 `CMAKE_INSTALL_PREFIX` 指定的路径下查找。 +- 如果项目使用包管理工具(如 `vcpkg`),在工具链文件指定的路径下查找。 + +(默认是[vcpkg-root]/installed/[triplet]/**share/**[package]/[package]Config.cmake) + +引申一下: + +包的头文件会安装到:[vcpkg-root]/installed/[triplet]/include/ + +库文件会安装到:[vcpkg-root]/installed/[triplet]/lib/ + +1. 如果不使用CMakeList.txt, 命令行中所必需的参数\配置 + 1. 指定源代码路径(必须) + 2. 指定构建类型-D CMAKE_BUILD_TYPE=Release或Debug + 3. 选择构建生成器 + 1. -G "Unix Makefiles” // unix的一个make生成器 + 2. -G “MinGW Makefiles” // win的一个make生成器 + 3. -G “Ninja” // Ninja生成器,需要额外安装 + 4. -G "Visual Studio 16 2019” // Visual Studio 生成器2019版本 + 5. -G "Xcode” // 苹果的Xcode diff --git a/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md b/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md new file mode 100644 index 0000000..bd16401 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md @@ -0,0 +1,148 @@ +--- +title: vcpkg包管理器 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: gtqamuq3tftmvzstbunkgbo5 +--- + +# vcpkg包管理器 + +vcpkg分经典Classic模式和清单Manifest模式 + +经典模式是直接vcpkg install下载包,然后在CMakeList.txt中指定include目录和库文件目录引用 + +清单模式是编写vcpkg.json清单文件 + +## 配置 + +下载并配置环境变量: + +```bash +git clone +cd vcpkg/ +./bootstrap-vcpkg.sh +``` + +之后建议加入环境变量 + +```bash +echo 'export VCPKG_ROOT="$HOME/vcpkg"' >> ~/.bashrc +// 我是直接clone到了~根目录下,具体换成自己clone到的目录 +``` + +1. 在项目中添加vcpkg + +```bash +// 创建清单文件vcpkg.json和vcpkg-configuration.json并初始化 +vcpkg new --application +// 添加XXX库(比如fmt)依赖项,将在vcpkg.json中增加一个dependencies:fmt +vcpkg add port fmt // 不会检查正确与否,只是单纯修改vcpkg.json, 与手动修改vcpkg.json一样 +``` + +vcpkg 读取清单文件(vcpkg.json),以了解要安装和与 CMake 集成的依赖项,从而提供项目所需的依赖项。 + +1. 在CMakeList.txt中添加库相关信息 + +```bash +find_package(fmt CONFIG REQUIRED) +target_link_libraries(HelloWorld PRIVATE fmt::fmt) +``` + +1. 运行CMake配置 + +创建CMakePresets.json预设文件,设置工具链(CMAKE_TOOLCHAIN_FILE)使用vcpkg自定义工具链时,CMake 可以自动链接 vcpkg 安装的库 + +```json +{ + "version": 2, + "configurePresets": [ + { + "name": "vcpkg", + "generator": "Ninja", //(或"MinGW Makefiles")这里就是上述用的-G "" + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + // 其中{VCPKG_ROOT}可在CMakeUserPresets.json中设置 + // 也可配置成环境变量 + } + } + ] +} +``` + +```json +{ + "version": 2, + "configurePresets": [ + { + "name": "default", + "inherits": "vcpkg", + "environment": { + "VCPKG_ROOT": "" + } + } + ] +} +``` + +## 使用 + +1. 打包自己的库并用vcpkg管理(注册进vcpkg) +2. 下载使用vcpkg管理的库(三方库) + +**创建自己的vcpkg注册表** + +有别于官方git注册表https://github.com/microsoft/vcpkg + +也可以建立自己的git注册表(除了git注册表这种形式,还有文件系统注册表,这里先说git注册表) + +**什么是vcpkg注册表?** + +注册表里放库的一些信息(但不是库的源码,如果是源码的话,那太臃肿了)。 + +**做什么用?** + +在vcpkg-configuration.json中使用,格式如下 + +```bash +{ + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg.git", + "baseline": "234534dfvbsdvw43434f" + }, // 默认仓库必须有,加载官方注册表(上千个库) + "registries": [ // 这里是自定义注册表 + { + "kind": "git", + "repository": "https://github.com/xxx/xxx.git", // 仓库地址 + "baseline": "d3e4723c1224t34fsdsvd0e4c2615f6d75", // 版本基线 + "reference": "main", // 分支名 + "packages": [ + "datastax-cpp-driver", // 注册表中包含的库名 + "cpp-common", + "ppconsul", + "leveldb", + "grpc", + "polaris-cpp" + ] + }, + { + "kind": "git", + "repository": "git@gitlab.xxxxx/xxxx.git", + "baseline": "15efa5017d9a3esdvsdvsdvwecs1d316", + "reference": "main", + "packages": [ + "feature-generation-lib", + "nps-client-brpc", + "brpc", + "cybercore-sdk-cpp", + "opentelemetry-cpp", + "mv-protocols-cpp", + "feature-extraction-lib" + ] + } + ] +} +``` diff --git a/app/docs/computer-science/cpp_backend/mempool_simple.mdx b/app/docs/computer-science/cpp_backend/mempool_simple.mdx new file mode 100644 index 0000000..17055e7 --- /dev/null +++ b/app/docs/computer-science/cpp_backend/mempool_simple.mdx @@ -0,0 +1,116 @@ +--- +title: 手写内存池(简单定长) +description: "" +date: "2025-09-27" +tags: + - tag-one +docId: q8290wmhyofuiskzn1ph63ta +--- + +# 手写内存池(简单定长) + +# 简单版(定长block) + +## 设计图 + +![画板](https://cdn.nlark.com/yuque/0/2025/jpeg/43055607/1758718719250-e6f52459-0f73-493b-8294-7b8f931da054.jpeg) + +## 代码结构 + +```c +typedef struct mempool_s { + int blocksize; // 每个内存块的size + int freecount; // 剩余空的内存块数量 + char *free_ptr; // 指向下一空内存块 + char *mem; // 整块内存的头指针 +} mempool_t; +``` + +```c +int memp_create(mempool_t *m, int block_size) { + + if (!m) return -1; + + // 1. 初始化这两个简单的int + m->blocksize = block_size; + m->freecount = MEM_PAGE_SIZE / block_size; + + // 2. 开整个内存池的空间,顺便初始化m->mem + m->mem = (char *)malloc(MEM_PAGE_SIZE); + if (!m->mem) { // 开失败了(剩余空闲内存不够) + return -2; + } + // 将这个空间初始化一下 + memset(m->mem, 0, MEM_PAGE_SIZE); + + // 3. 初始化free_ptr + m->free_ptr = m->mem; + + // 依次初始化每个block里的"next->ptr" + int i = 0; + char *ptr = m->mem; + for (i = 0;i < m->freecount;i ++) { + + *(char **)ptr = ptr + block_size; + ptr = ptr + block_size; + } + // 最后一个block的"next_ptr"指向NULL + *(char **)ptr = NULL; + return 0; +} +``` + +```c +void *memp_alloc(mempool_t *m) { + // 满了 + if (!m || m->freecount == 0) return NULL; + // 1. 获取当前下一个空闲块,作为返回值 + void *ptr = m->free_ptr; + // 2. 更新free_ptr + m->free_ptr = *(char **)ptr; + // 3. 更新freecount + m->freecount --; + + return ptr; +} +``` + +```c +void memp_free(mempool_t *m, void *ptr) { + // 相当于 ptr->next = m->free_ptr; + // 头插法将要释放的block插到空闲block链表的头部,即空闲block链表又增加了一个 + *(char **)ptr = m->free_ptr; + // 更新free_ptr这一空闲block链表头指针 + m->free_ptr = (char *)ptr; + // 更新freecount + m->freecount ++; +} +``` + +```c +void memp_destory(mempool_t *m) { + if (!m) return ; + // 直接free内存池,因为内存池是整体malloc的,而不是一个一个block malloc的 + free(m->mem); +} +``` + +## 使用example + +```c +int main() { + mempool_t m; + memp_create(&m, 32); + + void *p1 = memp_alloc(&m); + printf("memp_alloc : %p\n", p1); + + void *p2 = memp_alloc(&m); + printf("memp_alloc : %p\n", p2); + + void *p3 = memp_alloc(&m); + printf("memp_alloc : %p\n", p3); + + memp_free(&m, p2); +} +``` diff --git a/app/docs/computer-science/index.mdx b/app/docs/computer-science/index.mdx index acb199f..8daa875 100644 --- a/app/docs/computer-science/index.mdx +++ b/app/docs/computer-science/index.mdx @@ -8,6 +8,37 @@ tags: docId: ksjj9shalh6hqezx6t6am5vw --- +# 计算机科学 + +欢迎来到计算机科学知识库!我们在此收集了计算机科学各个领域的核心概念和深入分析。 + +## 主体内容 + +### 数据结构和算法 + +- [数据结构基础](https://github.com/sherlock-zhang/blog/blob/master/computer-science/data-structures) +- 常见算法分析 +- 复杂性理论 + +### 编程语言 + +- 编程范式 +- 语言设计原则 +- 编译器理论 + +## 学习建议 + +我们建议按照以下顺序学习: + +1. 首先掌握基本的数据结构 +2. 理解常见算法的实现 +3. 学习算法复杂度分析 +4. 深入特定领域的高级主题 + +--- + +_本知识库由学生社区维护。欢迎投稿!_ + # Computer Science Welcome to the Computer Science Knowledge Base! Here we collect core concepts and in-depth analysis from various fields of computer science. diff --git a/app/docs/jobs/interview-prep/interview-tips.mdx b/app/docs/jobs/interview-prep/interview-tips.mdx index d2a4619..0463e0b 100644 --- a/app/docs/jobs/interview-prep/interview-tips.mdx +++ b/app/docs/jobs/interview-prep/interview-tips.mdx @@ -1,5 +1,5 @@ --- -title: 面试阶段逐关击破 | OA刷题血泪史 + VI分数偷看技巧 + 群面套路合集 +title: "面试阶段逐关击破 | OA刷题血泪史 + VI分数偷看技巧 + 群面套路合集" description: "" date: "2025-09-19" tags: @@ -14,9 +14,11 @@ docId: fkk8ghklsr15a0s3vcxnswnj 别慌,这篇笔记整理了我自己踩过的坑和身边同学的血泪史,手把手带你逐关击破整个面试流程。 -## OA刷题修罗场 | HackerRank/CodeSignal/SHL全解 +## OA在线测评| HackerRank/CodeSignal/SHL全解 -第一次收到 OA 邮件的时候,心跳快过网课抢名额。常见的平台有 **HackerRank、CodeSignal 和 SHL**。 +第一次收到 OA 邮件的时候,看着题目两眼一黑,没刷过力扣的我只能默默流泪。 +询问无数大佬得到的答案永远是惊人的相似,算法的提高只有一个字,练! +常见的平台有 **HackerRank、CodeSignal 和 SHL**。 - **HackerRank**:全球 IT 求职标配,澳洲大厂几乎全都用。 - **CodeSignal**:氛围很像真实 coding 场景,live coding 感拉满。 diff --git a/app/docs/jobs/interview-prep/pre-interview.md b/app/docs/jobs/interview-prep/pre-interview.md new file mode 100644 index 0000000..48c407b --- /dev/null +++ b/app/docs/jobs/interview-prep/pre-interview.md @@ -0,0 +1,47 @@ +--- +title: " 面试前必看:掌握这四个小技巧,你的成功率会大大增加" +description: "" +date: "2025-09-28" +tags: + - tag-one +docId: cgo4lweflk5jx1hsncr8hshk +--- + +面试之前,不仅是温习简历和练习回答,更重要的是做“背景功课”——那种别人忽略,你却做得透彻的准备。为什么?因为在竞争激烈的面试中,面试官往往一开口就能感觉出你有没有下功夫。你若能在谈话里偶尔丢几个他们感兴趣的小细节,印象分就能瞬间拉满。 + +--- + +## 1. 搞清公司层面的关键信息 + +要了解公司的业务线、战略举措、竞争格局、行业趋势、重大新闻事件。比如:公司最近参与了哪个大项目?在行业里是靠技术驱动、渠道驱动,还是靠品牌驱动? +如果你能在面试里谈到: + +> “我看到你们最近在××业务方向投入很多,那这个岗位将如何配合公司战略?” +> 面试官会立刻觉得你是来“对的地方”的人。 + +## 2. 调研面试官的背景 + +利用 LinkedIn、公司官网、公开演讲、甚至内部人员反馈,尽量去了解面试官的履历和关注点。比如:对方是哪个业务线的负责人?过去做过哪些项目?公开演讲时都强调过什么? +这些信息不仅能帮你找到聊天的素材,更能在交流中制造共鸣。候选人若能顺势提到面试官熟悉的话题,往往会瞬间拉近距离。 + +## 3. 打听内部的真实情况 + +比起官方资料,员工的真实反馈更有价值。比如:公司文化是宽松还是制度化?团队节奏快还是稳定?老板风格是亲和还是铁腕? +这些“暗面”可以通过前同事或内部人士了解清楚。这样一来,在面试提问环节,你就能抛出更贴切的问题,展现出用心和思考。 + +## 4. 准备“差异化素材” + +不要只准备常见问题的标准答案,而要有一两个别人难以复制的“亮点”。比如: + +- 你在项目中负责过一个小但关键的部分; +- 你对某个细分领域有独特观察; +- 你从失败经历中总结出过有价值的经验。 + +这种差异化的回答,会让你在众多候选人中脱颖而出。相比于简单说“我很喜欢你们的产品”,更有力量的是一句: + +> “我觉得你们在 X 环节可能会面临挑战,而这正是我能补足的地方。” + +## 参考文献 + +- [如何在面试前做公司调研(Jianli.com)](https://www.jianli.com/article/gvvjqb.html) +- [高赞面试经验分享(简书)](https://www.jianshu.com/p/ea07ec667730) diff --git a/app/docs/jobs/interview-prep/preparations-to-get-an-offer-as-a-student.mdx b/app/docs/jobs/interview-prep/preparations-to-get-an-offer-as-a-student.mdx new file mode 100644 index 0000000..c96696b --- /dev/null +++ b/app/docs/jobs/interview-prep/preparations-to-get-an-offer-as-a-student.mdx @@ -0,0 +1,133 @@ +--- +title: 程序员学生时期求职与实习经验分享 +description: "" +date: "2025-09-29" +tags: + - tag-one +docId: pne40puz5alzsf0f5jb0frbm +--- + +# 程序员学生时期求职与实习经验分享 + +本帖主要分享我学生时期的求职经历,涵盖兼职、实习与(New Graduate Program),并介绍我认为有助于在毕业前获得Offer的课外活动。由于这些准备都是在学生身份下完成的,因此我决定将这些经历整合在这一个帖子中分享。 + +首先,**写在前面(免责声明)**: + +1. 我毕业于新冠疫情初期,当时的就业市场与如今在人工智能冲击下的环境相比,竞争可能没有现在这么激烈。 +2. 我没有身份方面的担忧。对于缺少PR的同学,可能需要在本文的基础上做更充分的准备。加油! +3. 运气固然重要,但这并非你所能控制。有时即使准备万全也可能与机会失之交臂,这并非你的过错,而是市场与时机使然。请务必保持自信。 +4. 我的求职方向是软件开发工程师(SDE),对当前热门的数据科学家(Scientist)路线了解不多。 +5. 本篇内容仅代表**个人观点**,与我曾经或现在任职的任何公司无关。 + +## 1. 如何找到第一份实习或兼职 + +在大学期间,我主要通过以下三种方式勤工俭学,难度由高到低排序如下: + +1. **担任学校计算机专业课程的助教 (Tutor)** [强烈推荐] + - **简介**:大学教授通常很忙,因此他们会招聘上过这门课的优秀学生来担任Tutor,以协助Tutorial, workshop, 改Assignment和改卷。 + - **寻找途径**:留意各大学的招聘网站,通常在开学前会发布职位。当然,要胜任助教,你的课程成绩至少需要达到优异(HD)水平。如果成绩出色,也可以主动发送邮件给教授,询问未来是否有担任助教的机会。 + - **推荐理由**:在我看来,担任助教是大学期间性价比最高的兼职。 + - **简历含金量高**:在HR筛选简历时,这份经历的认可度很高。它证明了你:1) 专业知识扎实;2) 具备很强的沟通与Mentorin能力。这两者在职场中都至关重要。 + - **时薪优厚**:我当时带领 Workshop 的时薪是 50 澳元/小时,小课则是 120 澳元/小时。 + - **软技能锻炼**:你会发现,在程序员的职业生涯中,沟通与教学能力同技术能力一样重要。这个行业非常鼓励Mentor/Mente文化。 + - **拓展人脉**:与教授建立良好关系,有机会解锁第二条职业路径。 + - **时长灵活**:一般不受学生签证工作时长的严格限制。 + +2. **担任教授的研究助理 (RA, Research Assistant)** + - **简介**:这个机会在很大程度上取决于教授和运气。主要工作是协助教授完成项目,编写各类代码,范围可以从前端开发、实验模拟、数据可视化,到后端实现、论文辅助等,工作内容非常多样。 + - **寻找途径**:可以说是“八仙过海,各显神通”。具体包括但不限于: + - 关注学校的计算机社群,教授有时会发布招聘帖子。 + - 留意暑期研究项目(Summer Research School)的通知,项目结束后你可能会被留下来继续参与工作。 + - 如果你在哪门课上表现优异,可以主动联系教授询问相关机会(反正课程已经结束,不用担心成绩问题,不妨大胆一试)。 + - **优点**: + - 积累实际项目经验,并能向教授学习更前沿的知识。未来做背景调查(Reference Check)时也可以请教授作为推荐人。 + - HR 认可度高,毕竟这是一份有报酬的正式工作。 + +3. **在咨询公司担任合同工 (Contractor)** + - **简介**:这类机会相对较少,因为公司通常直接从市场上招聘有经验的专业人士。 + - **我的经历**:我比较幸运,在参加某咨询公司举办的Hackatho时,我们团队奋力完成了产品的原型开发,后来我收到了该公司的合同工邀请,获得了第一份正式工作。 + +## 2. 大厂实习 + +通常在你毕业前一年的长假(11月至次年2月),各大公司会开放实习生项目。这段实习经历不仅可能为你赢得第一份全职录用通知,至少也能保证你的简历在后续求职中更容易通过筛选。 + +**注意**:此路径通常要求申请者拥有P)。持学生签证的同学可以考虑利用这段时间申请国内大厂的实习。 + +**时间线 (Timeline)**: + +1. **毕业前一年的 2-3 月**:各大公司开放暑期(11月至次年2月)实习项目的申请。 +2. **6-7 月**:公司发放Offer并进行Team Matc。 +3. **11 月**:入职实习。 +4. **次年 2 月**:实习结束。如果表现出色,次年 3 月你或许就能收到第一份全职 Offer。 + +**公司列表**: + +1. **高频交易公司 (HFT)**:如 Optiver, IMC, Akuna 等。 +2. **IT 科技公司**:如 Google, Atlassian, Amazon, Canva, Rokt 等。 +3. **各大咨询公司与银行**。 + +**如何准备**: + +1. **刷 LeetCode / HackerRank**:两者差别不大,重点是持续练习。 +2. **积累项目/助教/兼职经历**:丰富你的简历内容。 +3. **保持优异成绩**:在其他条件相近的情况下,高 GPA 依然是重要的区分标准。 + +如果你成功获得实习机会,恭喜你!请利用这段时间像海绵一样吸收公司的:技术栈、企业文化、团队协作方式、招聘流程、面试标准、知名系统的架构和设计文档。多与同事交流,并努力完成工作。这些都将是你离开公司后可以带走的宝贵财富。 + +此外,Google Australia 还有一个针对大二学生的特殊实习项目,名为 STEP Program,也请留意。该项目面试基本只考察 LeetCode 的解题能力。 + +## 3. 毕业生求职 + +以我为例,在我研究生毕业时(之所以读研,是因为我本科毕业时没找到工作....),我的背景和经历包括: + +1. 两段Hackathon经历 +2. 两年计算机专业课助教经历 +3. 一段教授研究助理(RA)经历 +4. 一段咨询公司合同工(Contractor)经历 +5. Atlassian 实习经历及返聘录用(Return Offer) +6. 毕业论文 关于对Kubernetes的研究 + +然而,这份简历投递 Canva 时甚至未能通过筛选,这给我的内心带来了巨大的震撼和焦虑。 + +于是,我联系了一位学长,请他帮忙内推了 AWS 的毕业生项目。在面试准备上,由于我之前的经历多为连续的(back-to-back)技术面试,对非IT大厂的流程了解不多。我当时主要做了以下准备: + +1. **寻求内推**:一定、一定要找人内推!这有几率帮你跳过在线笔试(OA)。 +2. **刷 LeetCode**:Labuladong 的算法小抄对归纳题型很有帮助。 +3. **准备行为面试问题 (Behavioral Questions)**:即使没有正式职场经历,也可以利用在校期间的团队项目经验,结合当时的场景和反思,来构建你的答案。当时为准备 AWS 的行为面试,我写了 10 个小故事,并用不同问题反复模拟回答。建议使用 STAR 法则来组织你的回答。 +4. **复盘项目**:重新审视自己做过的项目架构,思考有哪些可以优化和改进的地方。 +5. **模拟面试**:找朋友或同行进行模拟面试。我当时还使用了 interviewing.io 这个平台,付费请匿名的资深面试官对我进行模拟面试(价格较高,约一百多美元一次)。 + +出于“闲着也是闲着”的心态,我还尝试了阿里巴巴的常规招聘和“阿里星”计划... 对于国内的秋季招聘,熟记“八股文”(常考的技术知识点)也是一个关键。 + +## 总结 + +毕业求职是一个容易引发焦虑且需要长期规划的过程。它并非始于毕业前的最后一个学期,而是从你入学那一刻起就应该着手准备。如果要我重新规划大学生涯的时间线: + +1. **大一**:保持高 GPA,开始刷 LeetCode。(即使你上过学校的算法课,也并不意味着你就会刷 LeetCode。它是一种应试技巧,越早开始越好。)同时,寻找参加学校程序竞赛(Competitive Programming)的机会。 +2. **大二**:关注 Google STEP Program, Optiver 实习项目, 以及暑期研究机会(Summer School Research Opportunity)。 +3. **大三(或三年制本科的大二)**:重点关注各大科技公司的暑期实习项目。 +4. **大四/毕业年**:正式开始求职,海投简历。 + +在整个大学期间,还需要持续进行以下活动: + +1. 积极参与各类竞赛,比如Hackathon和competitive programming。 +2. 为开源项目做贡献。**[involutionhell.github.io](https://github.com/InvolutionHell/involutionhell.github.io) 就是一个不错的机会**. +3. 寻找担任Tutor的机会。 +4. 争取担任教授的RA。 +5. 如果能参加并打好程序竞赛,那将是极大的加分项。 +6. **扩展团队作业(Assignment)的范围**:不要仅满足作业的最低要求,而应将其视为学习新技术的机会。例如,后端可以尝试使用云服务或自己搭建 K8s 集群;前端可以尝试 React、Next.js 等现代框架;或者在应用中集成大语言模型(LLM)。 +7. **不断探索如何与 AI 协同编程**。AI 正深刻地改变着这个行业。 + +所有这些努力,都会从不同角度体现你的以下特质,从而增加你通过简历筛选的几率: + +1. 对技术的热情以及自主学习能力。 +2. 获得外界认可的技术实力。 +3. 优秀的交流、沟通与合作能力。 + +## 其他 + +大家可以投票选出后续感兴趣的话题: + +1. 悉尼 IT 大厂面试风格/准备策略 +2. 程序员职业发展路线(前端/后端, Mobile, DevOps/SRE, 大模型, 大小公司对比) +3. 职场晋升与个人成长 diff --git a/app/hooks/useAssistantSettings.tsx b/app/hooks/useAssistantSettings.tsx index 878d697..e8beecc 100644 --- a/app/hooks/useAssistantSettings.tsx +++ b/app/hooks/useAssistantSettings.tsx @@ -10,7 +10,7 @@ import { } from "react"; import type { ReactNode } from "react"; -type Provider = "openai" | "gemini"; +type Provider = "openai" | "gemini" | "intern"; interface AssistantSettingsState { provider: Provider; @@ -45,7 +45,12 @@ const parseStoredSettings = (raw: string | null): AssistantSettingsState => { try { const parsed = JSON.parse(raw) as Partial; return { - provider: parsed.provider === "gemini" ? "gemini" : "openai", + provider: + parsed.provider === "gemini" + ? "gemini" + : parsed.provider === "intern" + ? "intern" + : "openai", openaiApiKey: typeof parsed.openaiApiKey === "string" ? parsed.openaiApiKey : "", geminiApiKey: diff --git a/generated/doc-contributors.json b/generated/doc-contributors.json index 58e77b2..f0a6908 100644 --- a/generated/doc-contributors.json +++ b/generated/doc-contributors.json @@ -1,16 +1,25 @@ { "repo": "InvolutionHell/involutionhell.github.io", - "generatedAt": "2025-10-07T17:15:27.302Z", + "generatedAt": "2025-10-08T08:46:00.561Z", "docsDir": "app/docs", - "totalDocs": 75, + "totalDocs": 99, "results": [ { "docId": "ue27z7z95yzw3lhhfj7nit1c", "path": "app/docs/ai/agents-todo/agent-ecosystem.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -25,6 +34,7 @@ "docId": "eo5rwumxkh7twfdvlp5po9rc", "path": "app/docs/ai/agents-todo/cs294-194-196/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -35,6 +45,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -42,6 +60,7 @@ "docId": "v8m8kdjzzx7uhiz69r5m3m9o", "path": "app/docs/ai/ai-math-basics/calculus-optimization/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -52,6 +71,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -59,6 +86,7 @@ "docId": "gpoh50befguf7zgsetzkvbi3", "path": "app/docs/ai/ai-math-basics/information-theory/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -69,6 +97,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -76,6 +112,7 @@ "docId": "l1kvojw2gvggxflrmzc7j7sm", "path": "app/docs/ai/ai-math-basics/linear-algebra/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -86,6 +123,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -93,6 +138,7 @@ "docId": "ba5lqs2zg1jqc30qzw3osm9v", "path": "app/docs/ai/ai-math-basics/linear-algebra/resources/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -103,6 +149,31 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] + }, + { + "docId": "kzi6k1yg1sehlxidnxdsf59a", + "path": "app/docs/ai/ai-math-basics/math_books.md", + "contributorStats": { + "84211946": 3 + }, + "contributors": [ + { + "githubId": "84211946", + "contributions": 3, + "lastContributedAt": "2025-10-06T10:04:51.000Z", + "login": "AudreyYZY", + "avatarUrl": "https://avatars.githubusercontent.com/u/84211946?v=4", + "htmlUrl": "https://github.com/AudreyYZY" } ] }, @@ -110,9 +181,18 @@ "docId": "vcfer8dvlt80se4kmbnshx7x", "path": "app/docs/ai/ai-math-basics/math-foundations.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -127,6 +207,7 @@ "docId": "ebgss2sa91drisxswsh6iu8x", "path": "app/docs/ai/ai-math-basics/numerical-analysis/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -137,6 +218,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -144,6 +233,7 @@ "docId": "d5fya0gd1w8vblv8qeqgnqtu", "path": "app/docs/ai/ai-math-basics/probability-statistics/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -154,6 +244,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -161,6 +259,7 @@ "docId": "q7kagbrpnek7b89axvssn4bo", "path": "app/docs/ai/ai-math-basics/probability-statistics/resources/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -171,6 +270,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -178,9 +285,18 @@ "docId": "d73h3kyjnzytk1y2nizulyr6", "path": "app/docs/ai/compute-platforms/compute-platforms-handbook.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -194,13 +310,25 @@ { "docId": "ns7q5ehuje6oiua7as6rtnyf", "path": "app/docs/ai/compute-platforms/model-compuational-resource-demand.md", - "contributorStats": {}, - "contributors": [] + "contributorStats": { + "114939201": 1 + }, + "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] }, { "docId": "egpawb1yui58yprrsgxn9qj2", "path": "app/docs/ai/foundation-models/datasets/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -211,6 +339,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -218,6 +354,7 @@ "docId": "z157s85hnz1y37tr28y2a8h2", "path": "app/docs/ai/foundation-models/deploy-infer/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -228,6 +365,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -235,6 +380,7 @@ "docId": "lndxpf7luoeqwwde4in23xr1", "path": "app/docs/ai/foundation-models/evaluation/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -245,6 +391,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -252,6 +406,7 @@ "docId": "l5nes88zd54y6ao64ufkylz2", "path": "app/docs/ai/foundation-models/finetune/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -262,6 +417,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -269,6 +432,7 @@ "docId": "i88bna4sg5pr4ekhg32drv2i", "path": "app/docs/ai/foundation-models/foundation-models-lifecycle.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -279,6 +443,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -286,6 +458,7 @@ "docId": "h7s6nm7h5oqnhhdq9m1mgwwo", "path": "app/docs/ai/foundation-models/qkv-interview/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -296,6 +469,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -303,6 +484,7 @@ "docId": "jgz0nl0cbd4frj2dg98mdv0x", "path": "app/docs/ai/foundation-models/training/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -313,6 +495,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -320,9 +510,18 @@ "docId": "nor5ktairygnt4dorqbddo9n", "path": "app/docs/ai/generative-todo/generative-models-plan.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -337,6 +536,7 @@ "docId": "ix9azldhgm46j4i1xzgnd26r", "path": "app/docs/ai/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 4 }, "contributors": [ @@ -347,6 +547,40 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] + }, + { + "docId": "h53uwefhlykt9ietsx9x0vtn", + "path": "app/docs/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.md", + "contributorStats": { + "123258594": 2, + "166212872": 2 + }, + "contributors": [ + { + "githubId": "166212872", + "contributions": 2, + "lastContributedAt": "2025-10-01T06:29:10.000Z", + "login": "yoyofancy", + "avatarUrl": "https://avatars.githubusercontent.com/u/166212872?v=4", + "htmlUrl": "https://github.com/yoyofancy" + }, + { + "githubId": "123258594", + "contributions": 2, + "lastContributedAt": "2025-09-29T10:32:11.000Z", + "login": "PenghaoJiang", + "avatarUrl": "https://avatars.githubusercontent.com/u/123258594?v=4", + "htmlUrl": "https://github.com/PenghaoJiang" } ] }, @@ -354,6 +588,7 @@ "docId": "xboc8qj2128aivvt0goo1wow", "path": "app/docs/ai/llm-basics/courses/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -364,6 +599,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -371,6 +614,7 @@ "docId": "nwt5322vw4q6sz8ho8qynv28", "path": "app/docs/ai/llm-basics/cuda/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -381,6 +625,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -388,6 +640,7 @@ "docId": "dqg4iqz7hgyq38cqz3tg9tlf", "path": "app/docs/ai/llm-basics/deep-learning/d2l/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -398,6 +651,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -405,6 +666,7 @@ "docId": "vdclex41huib10ccsqw9u76k", "path": "app/docs/ai/llm-basics/deep-learning/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -415,6 +677,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -422,10 +692,19 @@ "docId": "lodydcd211esraq1r55ze9ey", "path": "app/docs/ai/llm-basics/deep-learning/misc/index.mdx", "contributorStats": { + "114939201": 2, "163523387": 2, "166212872": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 2, + "lastContributedAt": "2025-10-08T08:34:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 2, @@ -448,6 +727,7 @@ "docId": "nrelvvfzq0gma7pqfx9fkfxt", "path": "app/docs/ai/llm-basics/deep-learning/nlp/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -458,6 +738,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -465,6 +753,7 @@ "docId": "xnl2yzrb4x748zhhfe26ragt", "path": "app/docs/ai/llm-basics/embeddings/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -475,6 +764,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -482,6 +779,7 @@ "docId": "jq6323xynmyapm5vgncmyymh", "path": "app/docs/ai/llm-basics/embeddings/qwen3-embedding/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -492,16 +790,33 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" - } - ] + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] }, { "docId": "h8awdow89uicdy4kx9iimlta", "path": "app/docs/ai/llm-basics/llm-foundations.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -516,7 +831,7 @@ "docId": "psc0xf6oa1m7g8s9wfwiojkf", "path": "app/docs/ai/llm-basics/pytorch/index.mdx", "contributorStats": { - "114939201": 1, + "114939201": 2, "163523387": 3 }, "contributors": [ @@ -530,8 +845,8 @@ }, { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-14T09:59:08.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -542,6 +857,7 @@ "docId": "k1owc5kfw3vihc5hnmysqttl", "path": "app/docs/ai/llm-basics/transformer/ai-by-hand/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -552,6 +868,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -559,6 +883,7 @@ "docId": "lsokl8ofmo7msxlqyvihbhz5", "path": "app/docs/ai/llm-basics/transformer/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -569,6 +894,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -576,9 +909,18 @@ "docId": "r68izu11bkrkk6st194kwk80", "path": "app/docs/ai/methodology/research-methodology.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -593,6 +935,7 @@ "docId": "uguqyqpacxyj5irjickbt8n9", "path": "app/docs/ai/misc-tools/learning-toolkit.mdx", "contributorStats": { + "114939201": 1, "163523387": 3 }, "contributors": [ @@ -603,6 +946,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -610,9 +961,18 @@ "docId": "x3xs4hk0mc7lxlgbgskti5qk", "path": "app/docs/ai/model-datasets-platforms/platform-and-datasets.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -626,13 +986,25 @@ { "docId": "qftv72k0kzwiz8ddksbcl2aw", "path": "app/docs/ai/MoE/MOE-intro.md", - "contributorStats": {}, - "contributors": [] + "contributorStats": { + "114939201": 1 + }, + "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] }, { "docId": "qaezsrj15sudk796r5otne36", "path": "app/docs/ai/Multi-agents-system-on-Code-Translation/code-translation-intro.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -643,6 +1015,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -650,6 +1030,7 @@ "docId": "pmrtokz6393ywte5zqeskpm0", "path": "app/docs/ai/multimodal/courses/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -660,6 +1041,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -667,14 +1056,14 @@ "docId": "pffzdgytknyhaywar8uzyf2e", "path": "app/docs/ai/multimodal/llava/index.mdx", "contributorStats": { - "114939201": 3, + "114939201": 4, "163523387": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 3, - "lastContributedAt": "2025-09-13T17:34:51.000Z", + "contributions": 4, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -693,6 +1082,7 @@ "docId": "gc6tdzkkwxn5t90nw69fibl6", "path": "app/docs/ai/multimodal/mllm/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 3 }, "contributors": [ @@ -703,6 +1093,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -710,6 +1108,7 @@ "docId": "zte4s8s8ls4cs25mfrzyepfl", "path": "app/docs/ai/multimodal/multimodal-overview.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -720,6 +1119,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -727,6 +1134,7 @@ "docId": "ybczrbgxo5t4pl0erce7qz6w", "path": "app/docs/ai/multimodal/qwenvl/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -737,6 +1145,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -744,6 +1160,7 @@ "docId": "ssrhm03fw9sbogk78dmy92ml", "path": "app/docs/ai/multimodal/video-mm-todo/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 3 }, "contributors": [ @@ -754,6 +1171,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -761,6 +1186,7 @@ "docId": "xd3q72ubqzlesz8x4gewhi5r", "path": "app/docs/ai/multimodal/vit/index.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -771,6 +1197,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -778,9 +1212,18 @@ "docId": "as876rdhtmpnyyeclxt226s1", "path": "app/docs/ai/recommender-systems/recommender-roadmap.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -792,37 +1235,149 @@ ] }, { - "docId": "c3a4nmid9plytif5ameigj7d", - "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx", + "docId": "hajz43iblku13mmevia8zrhv", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_crossing.mdx", + "contributorStats": { + "188854497": 1 + }, + "contributors": [ + { + "githubId": "188854497", + "contributions": 1, + "lastContributedAt": "2025-09-27T02:41:56.000Z", + "login": "0dysseus13", + "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", + "htmlUrl": "https://github.com/0dysseus13" + } + ] + }, + { + "docId": "bvccoatft6y7bph83oivdcfe", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note_coldstart.mdx", "contributorStats": { - "114939201": 1, "163523387": 1, - "188854497": 2 + "188854497": 1 }, "contributors": [ + { + "githubId": "163523387", + "contributions": 1, + "lastContributedAt": "2025-09-27T10:49:41.000Z", + "login": "Mira190", + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", + "htmlUrl": "https://github.com/Mira190" + }, { "githubId": "188854497", - "contributions": 2, - "lastContributedAt": "2025-09-22T00:58:31.000Z", + "contributions": 1, + "lastContributedAt": "2025-09-27T02:48:39.000Z", "login": "0dysseus13", "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", "htmlUrl": "https://github.com/0dysseus13" + } + ] + }, + { + "docId": "qmy3p4vc45ek61ce4n62fpxy", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note_improvement.mdx", + "contributorStats": { + "163523387": 1, + "188854497": 1 + }, + "contributors": [ + { + "githubId": "163523387", + "contributions": 1, + "lastContributedAt": "2025-09-27T07:30:55.000Z", + "login": "Mira190", + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", + "htmlUrl": "https://github.com/Mira190" }, + { + "githubId": "188854497", + "contributions": 1, + "lastContributedAt": "2025-09-27T02:50:46.000Z", + "login": "0dysseus13", + "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", + "htmlUrl": "https://github.com/0dysseus13" + } + ] + }, + { + "docId": "vjwogf9afghpbvi71e4dfsgj", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note_rank.mdx", + "contributorStats": { + "163523387": 1, + "188854497": 1 + }, + "contributors": [ { "githubId": "163523387", "contributions": 1, - "lastContributedAt": "2025-09-24T23:29:45.000Z", + "lastContributedAt": "2025-09-27T10:49:41.000Z", "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" }, { - "githubId": "114939201", + "githubId": "188854497", + "contributions": 1, + "lastContributedAt": "2025-09-27T02:38:03.000Z", + "login": "0dysseus13", + "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", + "htmlUrl": "https://github.com/0dysseus13" + } + ] + }, + { + "docId": "ol03smbujgwztho45ycj52ah", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note_rerank.mdx", + "contributorStats": { + "188854497": 1 + }, + "contributors": [ + { + "githubId": "188854497", "contributions": 1, - "lastContributedAt": "2025-09-24T16:26:12.000Z", + "lastContributedAt": "2025-09-27T02:46:02.000Z", + "login": "0dysseus13", + "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", + "htmlUrl": "https://github.com/0dysseus13" + } + ] + }, + { + "docId": "c3a4nmid9plytif5ameigj7d", + "path": "app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx", + "contributorStats": { + "114939201": 2, + "163523387": 1, + "188854497": 2 + }, + "contributors": [ + { + "githubId": "114939201", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" + }, + { + "githubId": "188854497", + "contributions": 2, + "lastContributedAt": "2025-09-22T00:58:31.000Z", + "login": "0dysseus13", + "avatarUrl": "https://avatars.githubusercontent.com/u/188854497?v=4", + "htmlUrl": "https://github.com/0dysseus13" + }, + { + "githubId": "163523387", + "contributions": 1, + "lastContributedAt": "2025-09-24T23:29:45.000Z", + "login": "Mira190", + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", + "htmlUrl": "https://github.com/Mira190" } ] }, @@ -830,9 +1385,18 @@ "docId": "s4fuhmdf6hj49jx1l7k87d4p", "path": "app/docs/ai/reinforcement-learning/reinforcement-learning-overview.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -843,18 +1407,69 @@ } ] }, + { + "docId": "mgb41edhi9cz1kxzae074an0", + "path": "app/docs/CommunityShare/Amazing-AI-Tools/index.md", + "contributorStats": { + "7187663": 1 + }, + "contributors": [ + { + "githubId": "7187663", + "contributions": 1, + "lastContributedAt": "2025-10-03T13:14:06.000Z", + "login": "Crokily", + "avatarUrl": "https://avatars.githubusercontent.com/u/7187663?v=4", + "htmlUrl": "https://github.com/Crokily" + } + ] + }, + { + "docId": "eej2awin6irhbdgcy8vvs3xb", + "path": "app/docs/CommunityShare/Amazing-AI-Tools/perplexity-comet.md", + "contributorStats": { + "7187663": 1 + }, + "contributors": [ + { + "githubId": "7187663", + "contributions": 1, + "lastContributedAt": "2025-10-03T13:14:06.000Z", + "login": "Crokily", + "avatarUrl": "https://avatars.githubusercontent.com/u/7187663?v=4", + "htmlUrl": "https://github.com/Crokily" + } + ] + }, + { + "docId": "gj4bn01un0s0841berfvwrn5", + "path": "app/docs/CommunityShare/Geek/cloudflare-r2-sharex-free-image-hosting.mdx", + "contributorStats": { + "194795025": 1 + }, + "contributors": [ + { + "githubId": "194795025", + "contributions": 1, + "lastContributedAt": "2025-09-27T05:22:14.000Z", + "login": "LynPtl", + "avatarUrl": "https://avatars.githubusercontent.com/u/194795025?v=4", + "htmlUrl": "https://github.com/LynPtl" + } + ] + }, { "docId": "xqz5iiv3p52h6d9g3c0w2baf", "path": "app/docs/CommunityShare/Geek/CommonUsedMarkdown.md", "contributorStats": { "73457237": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-20T21:14:49.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -873,6 +1488,7 @@ "docId": "tksz80mfqqyzwzzer5p3uxtg", "path": "app/docs/CommunityShare/Geek/git101.mdx", "contributorStats": { + "114939201": 1, "163523387": 2 }, "contributors": [ @@ -883,6 +1499,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -890,9 +1514,18 @@ "docId": "jee9yt8n8tmo8yclqujerw2x", "path": "app/docs/CommunityShare/Geek/index.mdx", "contributorStats": { - "73457237": 1 + "73457237": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "73457237", "contributions": 1, @@ -907,9 +1540,18 @@ "docId": "yxd2qpfl2li6092bjx8bz7vb", "path": "app/docs/CommunityShare/Geek/Katex/index.mdx", "contributorStats": { - "73457237": 1 + "73457237": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "73457237", "contributions": 1, @@ -924,7 +1566,8 @@ "docId": "r0inttjcby48tly602p410vo", "path": "app/docs/CommunityShare/Geek/Katex/Seb1.mdx", "contributorStats": { - "73457237": 2 + "73457237": 2, + "114939201": 1 }, "contributors": [ { @@ -934,6 +1577,14 @@ "login": "TSK-Glofy", "avatarUrl": "https://avatars.githubusercontent.com/u/73457237?v=4", "htmlUrl": "https://github.com/TSK-Glofy" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] }, @@ -941,7 +1592,8 @@ "docId": "khcrztruqdku9fntd3dwzvwe", "path": "app/docs/CommunityShare/Geek/Katex/Seb2.mdx", "contributorStats": { - "73457237": 2 + "73457237": 2, + "114939201": 1 }, "contributors": [ { @@ -951,6 +1603,31 @@ "login": "TSK-Glofy", "avatarUrl": "https://avatars.githubusercontent.com/u/73457237?v=4", "htmlUrl": "https://github.com/TSK-Glofy" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + } + ] + }, + { + "docId": "e6udpzrorhvgeeda6xpy1e0s", + "path": "app/docs/CommunityShare/Geek/picturecdn.mdx", + "contributorStats": { + "59130571": 2 + }, + "contributors": [ + { + "githubId": "59130571", + "contributions": 2, + "lastContributedAt": "2025-09-27T07:16:02.000Z", + "login": "RavenCaffeine", + "avatarUrl": "https://avatars.githubusercontent.com/u/59130571?v=4", + "htmlUrl": "https://github.com/RavenCaffeine" } ] }, @@ -958,9 +1635,18 @@ "docId": "i0xmpskau105p83vq35wnxls", "path": "app/docs/CommunityShare/Geek/raspberry-guide.md", "contributorStats": { - "73457237": 1 + "73457237": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "73457237", "contributions": 1, @@ -975,9 +1661,18 @@ "docId": "mhyoknm6vj8jmp186oli5f5c", "path": "app/docs/CommunityShare/Geek/swanlab.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -993,6 +1688,7 @@ "path": "app/docs/CommunityShare/index.mdx", "contributorStats": { "73457237": 2, + "114939201": 1, "194795025": 1 }, "contributors": [ @@ -1004,6 +1700,14 @@ "avatarUrl": "https://avatars.githubusercontent.com/u/73457237?v=4", "htmlUrl": "https://github.com/TSK-Glofy" }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "194795025", "contributions": 1, @@ -1014,14 +1718,40 @@ } ] }, + { + "docId": "jgyg6bp0nceyrxirz5qw3zsv", + "path": "app/docs/CommunityShare/Life/unsw-student-benefit.md", + "contributorStats": { + "163523387": 2 + }, + "contributors": [ + { + "githubId": "163523387", + "contributions": 2, + "lastContributedAt": "2025-10-03T06:25:47.000Z", + "login": "Mira190", + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", + "htmlUrl": "https://github.com/Mira190" + } + ] + }, { "docId": "q8d1j9bii2ve2p7pp4xtok79", "path": "app/docs/CommunityShare/MentalHealth/burnout-guide.mdx", "contributorStats": { "73457237": 1, - "95595902": 1 + "95595902": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "95595902", "contributions": 1, @@ -1044,9 +1774,18 @@ "docId": "rxyvvumcvfl2oh3hky8urkfn", "path": "app/docs/CommunityShare/MentalHealth/index.mdx", "contributorStats": { - "73457237": 1 + "73457237": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "73457237", "contributions": 1, @@ -1057,13 +1796,65 @@ } ] }, + { + "docId": "zf8zk108oqbsg56xjyqb5txk", + "path": "app/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo.md", + "contributorStats": { + "206296353": 4 + }, + "contributors": [ + { + "githubId": "206296353", + "contributions": 4, + "lastContributedAt": "2025-10-03T11:54:29.000Z", + "login": "codemanQAQ", + "avatarUrl": "https://avatars.githubusercontent.com/u/206296353?v=4", + "htmlUrl": "https://github.com/codemanQAQ" + } + ] + }, + { + "docId": "wdqqrepoy43jiieyyjmaekk1", + "path": "app/docs/CommunityShare/RAG/context_engineering_intro.md", + "contributorStats": { + "7187663": 2, + "166212872": 1 + }, + "contributors": [ + { + "githubId": "7187663", + "contributions": 2, + "lastContributedAt": "2025-10-03T12:25:49.000Z", + "login": "Crokily", + "avatarUrl": "https://avatars.githubusercontent.com/u/7187663?v=4", + "htmlUrl": "https://github.com/Crokily" + }, + { + "githubId": "166212872", + "contributions": 1, + "lastContributedAt": "2025-10-03T11:36:59.000Z", + "login": "yoyofancy", + "avatarUrl": "https://avatars.githubusercontent.com/u/166212872?v=4", + "htmlUrl": "https://github.com/yoyofancy" + } + ] + }, { "docId": "eyd32o3ebd5q69hfbb2enxqi", "path": "app/docs/CommunityShare/RAG/embedding.mdx", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -1079,6 +1870,7 @@ "path": "app/docs/CommunityShare/RAG/rag.mdx", "contributorStats": { "59130571": 1, + "114939201": 1, "163523387": 1, "166212872": 2 }, @@ -1091,6 +1883,14 @@ "avatarUrl": "https://avatars.githubusercontent.com/u/166212872?v=4", "htmlUrl": "https://github.com/yoyofancy" }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -1109,18 +1909,163 @@ } ] }, + { + "docId": "totx4pej5lhyt1nl4anwhakj", + "path": "app/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "kyu85av71b4n07hbdycbhvj9", + "path": "app/docs/computer-science/cpp_backend/easy_compile/2_base_gcc.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "g6wucmr69lamd9xyxm7uunnd", + "path": "app/docs/computer-science/cpp_backend/easy_compile/3_Make.md", + "contributorStats": { + "7187663": 1, + "100033202": 1 + }, + "contributors": [ + { + "githubId": "7187663", + "contributions": 1, + "lastContributedAt": "2025-09-30T11:49:26.000Z", + "login": "Crokily", + "avatarUrl": "https://avatars.githubusercontent.com/u/7187663?v=4", + "htmlUrl": "https://github.com/Crokily" + }, + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "xk44lx4q1gpcm1uqk8nnbg7q", + "path": "app/docs/computer-science/cpp_backend/easy_compile/4_CMake.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "gtqamuq3tftmvzstbunkgbo5", + "path": "app/docs/computer-science/cpp_backend/easy_compile/5_vcpkg.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "mnjkrtrs7xk3fq538eqreuge", + "path": "app/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "xgxqqvglxyauoeh8eye7lzu6", + "path": "app/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1.md", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-30T07:08:38.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, + { + "docId": "q8290wmhyofuiskzn1ph63ta", + "path": "app/docs/computer-science/cpp_backend/mempool_simple.mdx", + "contributorStats": { + "100033202": 1 + }, + "contributors": [ + { + "githubId": "100033202", + "contributions": 1, + "lastContributedAt": "2025-09-27T14:18:03.000Z", + "login": "duang3457", + "avatarUrl": "https://avatars.githubusercontent.com/u/100033202?v=4", + "htmlUrl": "https://github.com/duang3457" + } + ] + }, { "docId": "gmpls10e2dz0bbizotvhglc8", "path": "app/docs/computer-science/data-structures/array/01-static-array.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1140,13 +2085,13 @@ "path": "app/docs/computer-science/data-structures/array/02-dynamic-array.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1166,13 +2111,13 @@ "path": "app/docs/computer-science/data-structures/array/index.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1192,13 +2137,13 @@ "path": "app/docs/computer-science/data-structures/index.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1218,13 +2163,13 @@ "path": "app/docs/computer-science/data-structures/linked-list/01-singly-linked-list.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1244,13 +2189,13 @@ "path": "app/docs/computer-science/data-structures/linked-list/index.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1270,13 +2215,13 @@ "path": "app/docs/computer-science/index.mdx", "contributorStats": { "7187663": 1, - "114939201": 1 + "114939201": 2 }, "contributors": [ { "githubId": "114939201", - "contributions": 1, - "lastContributedAt": "2025-09-12T20:52:18.000Z", + "contributions": 2, + "lastContributedAt": "2025-09-26T19:56:04.000Z", "login": "longsizhuo", "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", "htmlUrl": "https://github.com/longsizhuo" @@ -1295,9 +2240,18 @@ "docId": "uzoqs57kwc4tfut4wvgnbjhf", "path": "app/docs/frontend/frontend-learning/index.mdx", "contributorStats": { - "104760545": 1 + "104760545": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "104760545", "contributions": 1, @@ -1312,9 +2266,18 @@ "docId": "y3xkz4kituc738jwsojo7cml", "path": "app/docs/frontend/index.mdx", "contributorStats": { - "104760545": 1 + "104760545": 1, + "114939201": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "104760545", "contributions": 1, @@ -1330,6 +2293,7 @@ "path": "app/docs/jobs/business-flow/HelloWorld.md", "contributorStats": { "7187663": 1, + "114939201": 1, "163523387": 1 }, "contributors": [ @@ -1337,9 +2301,17 @@ "githubId": "163523387", "contributions": 1, "lastContributedAt": "2025-09-27T10:49:41.000Z", - "login": "Mira190", - "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", - "htmlUrl": "https://github.com/Mira190" + "login": null, + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387", + "htmlUrl": null + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" }, { "githubId": "7187663", @@ -1355,9 +2327,18 @@ "docId": "u68pjetu592c9zvs3f5xa82j", "path": "app/docs/jobs/interview-prep/bq.md", "contributorStats": { + "114939201": 1, "163523387": 1 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 1, @@ -1372,9 +2353,18 @@ "docId": "fkk8ghklsr15a0s3vcxnswnj", "path": "app/docs/jobs/interview-prep/interview-tips.mdx", "contributorStats": { + "114939201": 2, "163523387": 2 }, "contributors": [ + { + "githubId": "114939201", + "contributions": 2, + "lastContributedAt": "2025-10-08T08:34:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" + }, { "githubId": "163523387", "contributions": 2, @@ -1385,11 +2375,46 @@ } ] }, + { + "docId": "cgo4lweflk5jx1hsncr8hshk", + "path": "app/docs/jobs/interview-prep/pre-interview.md", + "contributorStats": { + "163523387": 2 + }, + "contributors": [ + { + "githubId": "163523387", + "contributions": 2, + "lastContributedAt": "2025-09-28T07:54:00.000Z", + "login": "Mira190", + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", + "htmlUrl": "https://github.com/Mira190" + } + ] + }, + { + "docId": "pne40puz5alzsf0f5jb0frbm", + "path": "app/docs/jobs/interview-prep/preparations-to-get-an-offer-as-a-student.mdx", + "contributorStats": { + "90535573": 1 + }, + "contributors": [ + { + "githubId": "90535573", + "contributions": 1, + "lastContributedAt": "2025-09-29T13:08:12.000Z", + "login": "SiYG", + "avatarUrl": "https://avatars.githubusercontent.com/u/90535573?v=4", + "htmlUrl": "https://github.com/SiYG" + } + ] + }, { "docId": "fzieiqiepxw9yfo2id9eham3", "path": "app/docs/jobs/tech-stack/HelloWorld.md", "contributorStats": { "7187663": 1, + "114939201": 1, "163523387": 1 }, "contributors": [ @@ -1397,9 +2422,17 @@ "githubId": "163523387", "contributions": 1, "lastContributedAt": "2025-09-27T10:49:41.000Z", - "login": "Mira190", - "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", - "htmlUrl": "https://github.com/Mira190" + "login": null, + "avatarUrl": "https://avatars.githubusercontent.com/u/163523387", + "htmlUrl": null + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" }, { "githubId": "7187663", @@ -1415,6 +2448,7 @@ "docId": "m37j6a24hb9mlrm0g6jfcxop", "path": "app/docs/Language/pte-intro.md", "contributorStats": { + "114939201": 1, "163523387": 3 }, "contributors": [ @@ -1425,6 +2459,14 @@ "login": "Mira190", "avatarUrl": "https://avatars.githubusercontent.com/u/163523387?v=4", "htmlUrl": "https://github.com/Mira190" + }, + { + "githubId": "114939201", + "contributions": 1, + "lastContributedAt": "2025-09-26T19:56:04.000Z", + "login": "longsizhuo", + "avatarUrl": "https://avatars.githubusercontent.com/u/114939201?v=4", + "htmlUrl": "https://github.com/longsizhuo" } ] } diff --git a/lib/ai/models.ts b/lib/ai/models.ts new file mode 100644 index 0000000..b94968d --- /dev/null +++ b/lib/ai/models.ts @@ -0,0 +1,43 @@ +import { createOpenAIModel } from "./providers/openai"; +import { createGeminiModel } from "./providers/gemini"; +import { createInternModel } from "./providers/intern"; + +export type AIProvider = "openai" | "gemini" | "intern"; + +/** + * Model工厂 用于返回对应的 AI 模型实例 + * @param provider - 要用的provider + * @param apiKey - API key (intern provider不需要用户提供 API key) + * @returns 配置好的 AI 模型实例 + */ +export function getModel(provider: AIProvider, apiKey?: string) { + switch (provider) { + case "openai": + if (!apiKey || apiKey.trim() === "") { + throw new Error("OpenAI API key is required"); + } + return createOpenAIModel(apiKey); + + case "gemini": + if (!apiKey || apiKey.trim() === "") { + throw new Error("Gemini API key is required"); + } + return createGeminiModel(apiKey); + + case "intern": + // Intern 书生模型不需要用户提供 API key + return createInternModel(); + + default: + throw new Error(`Unsupported AI provider: ${provider}`); + } +} + +/** + * 检查指定的提供者是否需要用户提供 API key + * @param provider - 要检查的provider + * @returns 如果需要 API key,返回 true,否则返回 false + */ +export function requiresApiKey(provider: AIProvider): boolean { + return provider !== "intern"; +} diff --git a/lib/ai/prompt.ts b/lib/ai/prompt.ts new file mode 100644 index 0000000..5dacf20 --- /dev/null +++ b/lib/ai/prompt.ts @@ -0,0 +1,46 @@ +interface PageContext { + title?: string; + description?: string; + content?: string; + slug?: string; +} + +/** + * 构建系统消息,包含页面上下文 + * @param customSystem - 自定义系统消息 (可选) + * @param pageContext - 当前页面上下文 (可选) + * @returns 完整的系统消息字符串 + */ +export function buildSystemMessage( + customSystem?: string, + pageContext?: PageContext, +): string { + // 默认系统消息 + let systemMessage = + customSystem || + `You are a helpful AI assistant for a documentation website. + Always respond in the same language as the user's question: if the user asks in 中文, answer in 中文; if the user asks in English, answer in English. + You can help users understand the documentation, answer questions about the content, and provide guidance on the topics covered in the docs. Be concise and helpful.`; + + // 如果当前页面上下文可用,则添加到系统消息中 + if (pageContext?.content) { + systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; + + if (pageContext.title) { + systemMessage += `Page Title: ${pageContext.title}\n`; + } + + if (pageContext.description) { + systemMessage += `Page Description: ${pageContext.description}\n`; + } + + if (pageContext.slug) { + systemMessage += `Page URL: /docs/${pageContext.slug}\n`; + } + + systemMessage += `Page Content:\n${pageContext.content}`; + systemMessage += `\n--- END OF CONTEXT ---\n\nWhen users ask about "this page", "current page", or refer to the content they're reading, use the above context to provide accurate answers. You can summarize, explain, or answer specific questions about the current page content.`; + } + + return systemMessage; +} diff --git a/lib/ai/providers/gemini.ts b/lib/ai/providers/gemini.ts new file mode 100644 index 0000000..9c6249d --- /dev/null +++ b/lib/ai/providers/gemini.ts @@ -0,0 +1,15 @@ +import { createGoogleGenerativeAI } from "@ai-sdk/google"; + +/** + * Create Google Gemini model instance + * @param apiKey - Google Gemini API key provided by user + * @returns Configured Gemini model instance + */ +export function createGeminiModel(apiKey: string) { + const customGoogle = createGoogleGenerativeAI({ + apiKey: apiKey, + }); + + // Use the specific model configured for this project + return customGoogle("models/gemini-2.0-flash"); +} diff --git a/lib/ai/providers/intern.ts b/lib/ai/providers/intern.ts new file mode 100644 index 0000000..71d06a5 --- /dev/null +++ b/lib/ai/providers/intern.ts @@ -0,0 +1,17 @@ +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; + +/** + * Create Intern-AI model instance + * Uses environment variable INTERN_KEY for API key + * @returns Configured Intern-AI model instance + */ +export function createInternModel() { + const intern = createOpenAICompatible({ + name: "intern", + baseURL: "https://chat.intern-ai.org.cn/api/v1/", + apiKey: process.env.INTERN_KEY, + }); + + // Use the specific model configured for this project + return intern("intern-s1"); +} diff --git a/lib/ai/providers/openai.ts b/lib/ai/providers/openai.ts new file mode 100644 index 0000000..88f2724 --- /dev/null +++ b/lib/ai/providers/openai.ts @@ -0,0 +1,15 @@ +import { createOpenAI } from "@ai-sdk/openai"; + +/** + * Create OpenAI model instance + * @param apiKey - OpenAI API key provided by user + * @returns Configured OpenAI model instance + */ +export function createOpenAIModel(apiKey: string) { + const customOpenAI = createOpenAI({ + apiKey: apiKey, + }); + + // Use the specific model configured for this project + return customOpenAI("gpt-4.1-nano"); +} diff --git a/next.config.mjs b/next.config.mjs index 482ae71..2d45cdf 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,4 @@ -// next.config.mjs +// next.config.mjs import { createMDX } from "fumadocs-mdx/next"; import createNextIntlPlugin from "next-intl/plugin"; @@ -15,7 +15,33 @@ const config = { remotePatterns: [ { protocol: "https", - hostname: "avatars.githubusercontent.com", + hostname: "*.githubusercontent.com", + pathname: "/**", + }, + { + protocol: "https", + hostname: "*.github.io", + pathname: "/**", + }, + { + protocol: "https", + hostname: "*.r2.dev", + pathname: "/**", + }, + { + protocol: "https", + hostname: "cdn.nlark.com", + pathname: "/**", + }, + { + protocol: "https", + hostname: "*.amazonaws.com", + pathname: "/**", + }, + { + protocol: "https", + hostname: "*.coly.cc", + pathname: "/**", }, ], }, diff --git a/package.json b/package.json index b2b8940..36d6f24 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "prebuild": "node scripts/escape-angles.mjs", "build": "next build", "start": "next start -p 3000", "postinstall": "fumadocs-mdx", @@ -17,6 +18,7 @@ "dependencies": { "@ai-sdk/google": "^2.0.14", "@ai-sdk/openai": "^2.0.32", + "@ai-sdk/openai-compatible": "^1.0.19", "@ai-sdk/react": "^2.0.48", "@assistant-ui/react": "^0.11.14", "@assistant-ui/react-ai-sdk": "^1.1.0", @@ -41,6 +43,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dotenv": "^17.2.3", + "fast-glob": "^3.3.3", "fumadocs-core": "^15.7.13", "fumadocs-mdx": "^11.10.1", "fumadocs-ui": "^15.7.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99c2a1f..9514493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ importers: "@ai-sdk/openai": specifier: ^2.0.32 version: 2.0.32(zod@4.1.11) + "@ai-sdk/openai-compatible": + specifier: ^1.0.19 + version: 1.0.19(zod@4.1.11) "@ai-sdk/react": specifier: ^2.0.48 version: 2.0.48(react@19.1.1)(zod@4.1.11) @@ -85,6 +88,9 @@ importers: dotenv: specifier: ^17.2.3 version: 17.2.3 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 fumadocs-core: specifier: ^15.7.13 version: 15.7.13(@types/react@19.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -161,9 +167,6 @@ importers: eslint-config-next: specifier: ^15.5.3 version: 15.5.3(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.2) - fast-glob: - specifier: ^3.3.3 - version: 3.3.3 gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -220,6 +223,15 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + "@ai-sdk/openai-compatible@1.0.19": + resolution: + { + integrity: sha512-hnsqPCCSNKgpZRNDOAIXZs7OcUDM4ut5ggWxj2sjB4tNL/aBn/xrM7pJkqu+WuPowyrE60wPVSlw0LvtXAlMXQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + "@ai-sdk/openai@2.0.32": resolution: { @@ -229,6 +241,15 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + "@ai-sdk/provider-utils@3.0.10": + resolution: + { + integrity: sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + "@ai-sdk/provider-utils@3.0.9": resolution: { @@ -8510,12 +8531,25 @@ snapshots: "@ai-sdk/provider-utils": 3.0.9(zod@4.1.11) zod: 4.1.11 + "@ai-sdk/openai-compatible@1.0.19(zod@4.1.11)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.10(zod@4.1.11) + zod: 4.1.11 + "@ai-sdk/openai@2.0.32(zod@4.1.11)": dependencies: "@ai-sdk/provider": 2.0.0 "@ai-sdk/provider-utils": 3.0.9(zod@4.1.11) zod: 4.1.11 + "@ai-sdk/provider-utils@3.0.10(zod@4.1.11)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@standard-schema/spec": 1.0.0 + eventsource-parser: 3.0.6 + zod: 4.1.11 + "@ai-sdk/provider-utils@3.0.9(zod@4.1.11)": dependencies: "@ai-sdk/provider": 2.0.0 diff --git a/scripts/check-images.mjs b/scripts/check-images.mjs index baf4e09..516a6a1 100644 --- a/scripts/check-images.mjs +++ b/scripts/check-images.mjs @@ -3,7 +3,7 @@ * MDX 图片路径校验脚本 * * 功能 - * - 扫描 `app/docs/??/?.mdx`(含 .md) + * - 扫描 `app/docs/??/?.md`(含 .mdx) * - 识别 Markdown `![]()` 与内联 `` 的图片引用 * - 强制使用“就近图片”:推荐相对路径(如 `./images/…`) * - 仅对站点级共享资源允许绝对路径:`/images/site/*`、`/images/components/*` @@ -38,7 +38,7 @@ const IMAGE_FILE_EXTS = new Set([ ".webp", ".svg", ]); -const exts = new Set([".mdx", ".md"]); +const exts = new Set([".md", ".mdx"]); /** Recursively list files */ function* walk(dir) { @@ -60,7 +60,7 @@ function toRoutePath(file) { const rel = path.relative(DOCS_DIR, file).split(path.sep).join("/"); const base = path.basename(rel); const dir = path.dirname(rel); - if (base.toLowerCase() === "index.mdx") return dir === "." ? "" : dir; + if (base.toLowerCase() === "index.md") return dir === "." ? "" : dir; const name = base.replace(/\.[^.]+$/, ""); return dir === "." ? name : `${dir}/${name}`; } @@ -207,7 +207,7 @@ function main() { console.log(`\nFound ${bad}/${total} files with image issues.`); process.exit(1); } else { - console.log(`Checked ${total} MDX files: no issues.`); + console.log(`Checked ${total} MD files: no issues.`); } } diff --git a/scripts/escape-angles.mjs b/scripts/escape-angles.mjs new file mode 100644 index 0000000..49d772a --- /dev/null +++ b/scripts/escape-angles.mjs @@ -0,0 +1,49 @@ +/** + * @description 转义尖括号脚本 + * @author Siz Long + * @date 2025-09-27 + */ +import { promises as fs } from "node:fs"; +import fg from "fast-glob"; + +/** + * 极简策略: + * 1) 跳过 fenced code / inline code(保留原样) + * 2) 仅在普通文本行内转义形如 <数字开头...> 或 <单词里含逗号/空格/数学符号...> 的片段 + * 3) 不动像 /
这类“正常标签/组件名”的片段 + */ +const files = await fg(["app/docs/**/*.md"], { dot: false }); + +for (const file of files) { + let src = await fs.readFile(file, "utf8"); + + // 粗粒度:把代码块剥离(防止误替换),留下占位符 + const blocks = []; + src = src.replace(/```[\s\S]*?```/g, (m) => { + blocks.push(m); + return `__CODE_BLOCK_${blocks.length - 1}__`; + }); + + // 行内代码也剥离 + src = src.replace(/`[^`]*`/g, (m) => { + blocks.push(m); + return `__CODE_BLOCK_${blocks.length - 1}__`; + }); + + // 在普通文本里做“可疑尖括号”的转义: + // - <\d...> 如 <8>、<1,2,3> + // - <[^\s/>][^>]*[,;+\-*/= ]+[^>]*> 含明显非标签符号的 + src = src + .replace(/<\d[^>]*>/g, (m) => + m.replaceAll("<", "<").replaceAll(">", ">"), + ) + .replace(/<(?![A-Za-z/][A-Za-z0-9:_-]*\s*\/?>)[^>]*>/g, (m) => + m.replaceAll("<", "<").replaceAll(">", ">"), + ); + + // 还原占位的代码块/行内代码 + src = src.replace(/__CODE_BLOCK_(\d+)__/g, (_, i) => blocks[Number(i)]); + + await fs.writeFile(file, src, "utf8"); + console.log(`Escaped angles in ${file}`); +} diff --git a/scripts/move-doc-images.mjs b/scripts/move-doc-images.mjs index f8a1ce8..7233a3e 100644 --- a/scripts/move-doc-images.mjs +++ b/scripts/move-doc-images.mjs @@ -5,8 +5,8 @@ * @date 2025-09-27 * * 目标 - * - 扫描 `app/docs/??/?.mdx`(含 .md)里的图片引用 - * - 对于以 `/images/...` 绝对路径引用且仅被“单一文档”使用的图片:移动到对应 MDX 同目录下的 `images/` 子目录 + * - 扫描 `app/docs/??/?.md`(含 .mdx)里的图片引用 + * - 对于以 `/images/...` 绝对路径引用且仅被“单一文档”使用的图片:移动到对应 MD 同目录下的 `images/` 子目录 * - 同时将文中的绝对路径替换为相对路径 `./images/<文件名>`(站点级资源除外) * * 为什么需要 @@ -36,7 +36,7 @@ const PUBLIC_DIR = path.join(ROOT, "public"); const EXCLUDE_PREFIXES = ["/images/site/", "/images/components/"]; // 需要处理的文档扩展名 -const exts = new Set([".mdx", ".md"]); +const exts = new Set([".md", ".mdx"]); /** 递归遍历目录,产出文件路径 */ function* walk(dir) { @@ -224,7 +224,7 @@ function moveForFile(file, refs) { return { moved, updated: content !== raw }; } -/** 主流程:遍历所有 MDX,累计迁移数量并输出统计 */ +/** 主流程:遍历所有 MD,累计迁移数量并输出统计 */ function main() { let totalFiles = 0; let totalMoved = 0;