集成学习:让多个模型一起做决定
前面学习决策树时,我们提到过一个问题:单棵决策树虽然直观、可解释,但很容易过拟合,也很容易受到数据扰动影响。 集成学习(Ensemble Learning)就是为了解决这类问题而出现的思想。它不再把希望全部压在一个模型身上,而是训练多个模型,再把它们的结果组合起来。 可以把它理解成:一个人判断可能会偏,很多人从不同角度判断,再投票或取平均,结果通常更稳定。 1. 什么是集成学习集成学习的核心思想很简单: 训练多个基模型,再把这些模型的预测结果组合成最终结果。 这里的基模型也叫弱学习器(Weak Learner)或基学习器(Base Learner)。它不一定真的很弱,只是相对于最终组合模型来说,每个单独模型只负责贡献一部分判断。 例如一个分类任务中,我们训练了 5 个模型,它们分别预测: 模型 1:通过 模型 2:通过 模型 3:不通过 模型 4:通过 模型 5:通过 如果使用多数投票,最终结果就是“通过”。 如果是回归任务,例如预测房价,多个模型分别预测: 90 万、94 万、92 万、96 万、93 万 最终可以取平均值: $$\hat{y}=\fra...
AICompanion 开发日志 03:当前版本功能总览
前两篇分别记录了项目搭建和 PySide6 聊天窗口的实现。这一篇不继续单独展开某一个模块,而是做一次阶段性整理:把目前代码里已经实现的功能完整梳理一遍。 AICompanion 现在还不是最终形态,还没有 TTS,也还没有 Live2D。但它已经不只是一个空窗口了。目前这个版本已经把桌面聊天、模型接口、本地存储、长期记忆、情绪状态和主动消息的基础流程都接了起来。 1. 当前项目状态当前项目路径是: D:\Study\AICompanion 项目采用 src layout,核心代码放在: src/aicompanion/ 目前主要模块如下: src/aicompanion/ app.py # 应用装配入口 config.py # 配置读取 controllers/ chat_controller.py # 聊天流程控制 domain/ character.py # 角色设定 emotion.py # 情绪状态 llm...
AICompanion 开发日志 02:用 PySide6 搭建聊天窗口
上一篇主要记录了 AICompanion 的项目搭建、技术选型和目录结构。这一篇开始进入真正的功能实现。 当前阶段的目标很明确:先用 PySide6 做出一个可以运行的聊天窗口。它不需要一开始就很华丽,也不需要马上接入 Live2D,但必须把后续扩展需要的基础结构搭好。 也就是说,这一篇的重点不是“画一个窗口”,而是做出一个可以继续生长的桌面聊天程序骨架。 1. 当前阶段目标AICompanion 的整体计划分成几个阶段: 阶段 目标 第一阶段 Python + PySide6 做聊天窗口 第二阶段 接入大模型,实现多轮聊天 第三阶段 SQLite 保存聊天记录和记忆 第四阶段 主动消息、角色设定、表情切换 第五阶段 加入 TTS 语音回复 第六阶段 尝试 Live2D 这篇文章对应第一阶段的核心内容:聊天窗口。 不过在实现窗口时,我没有把所有逻辑都写进一个文件里,而是提前拆成了几个层次: ui:负责界面显示。 controllers:负责聊天流程。 llm:负责大模型接口。 storage:负责 SQLite 数据读写。 service...
决策树:像做选择题一样理解分类与回归
决策树(Decision Tree)是一种非常符合人类直觉的机器学习算法。它的预测过程就像做选择题:先问一个问题,根据答案走到下一步,再继续问问题,直到得到最终结论。 例如判断一个学生考试是否通过,可以问: 学习时间是否大于 3 小时? 睡眠时间是否大于 7 小时? 是否完成复习? 最后走到某个叶子节点,得到“通过”或“不通过”的预测结果。 1. 决策树的基本思想决策树的核心思想是:不断选择一个最合适的特征,把数据集切分得越来越“纯”。 这里的“纯”可以理解为:一个节点里的样本类别越统一,就越纯。 例如一个节点里有 10 个样本: 如果 10 个都是“通过”,这个节点非常纯。 如果 5 个“通过”、5 个“不通过”,这个节点就很混乱。 训练决策树时,算法会不断寻找最佳划分条件,例如: 学习时间 >= 3 小时? 如果这个问题能把“通过”和“不通过”分得更开,就说明它是一个不错的划分。 2. 决策树由哪些部分组成一棵决策树通常包含三类节点: 结构 含义 根节点 整棵树的起点,包含全部训练数据 内部节点 一个判断条件,例如“年龄是否大于 18” 叶...
Hexo 部署到 GitHub Pages 后构建失败:从 .nojekyll 到 ignore_hidden 的完整排查
最近在使用 Hexo 部署博客时遇到了一个问题:本地执行 npx hexo d 看起来是成功的,但 GitHub Pages 的 pages build and deployment 工作流却失败了。 一开始我以为是 Hexo 生成出错,后来排查发现,真正的问题并不在文章内容,也不在本地生成,而是在 GitHub Pages 构建流程和 .nojekyll 文件没有真正部署到公开仓库之间。 这篇文章记录一下完整排查过程。 1. 问题现象(若想直接看如何解决可以跳转到7.最终解决)本地执行: npx hexo d 命令看起来可以正常执行,静态文件也会被推送到 GitHub Pages 仓库。 但是在 GitHub 仓库的 Actions 页面中,可以看到 pages build and deployment 失败,具体失败位置在 build 步骤,错误信息中出现了: Failed to download archive 'https://codeload.github.com/actions/jekyll-build-pages/...' An action could ...
AICompanion 开发日志 01:项目搭建与环境准备
最近我开始做一个新的项目:AICompanion。它的目标是做一个运行在 Windows 电脑桌面上的 AI 聊天陪伴系统,未来也可能扩展到安卓端。 这个项目不只是普通聊天窗口,而是希望逐步实现一个更像“桌面伙伴”的系统:它可以拥有动漫角色设定,能记住聊天内容,有情绪状态,在我长时间没有回复时主动发消息给我,后续再加入 TTS 语音回复和 Live2D 角色展示。 这篇文章是这个系列的第 1 篇,主要记录项目从 0 到 1 的环境搭建和基础结构设计。 1. 项目目标AICompanion 当前的核心目标可以拆成几部分: 使用 PySide6 开发 Windows 桌面聊天窗口。 通过 API 调用大模型,实现多轮对话。 使用 SQLite 保存聊天记录、长期记忆和任务提醒。 加入情感系统,让角色回复不只是机械问答。 支持主动消息,例如用户长时间没回复时,角色可以主动问候。 后续加入 TTS 语音回复。 最终尝试接入 Live2D,让动漫角色可以根据情绪切换表情和动作。 我希望它最终不是一个“输入框 + 回复框”的简单工具,而是一个有角色感、有陪伴感、能长期相处的桌面 AI...
梯度下降法:机器学习如何一步步找到最优解
在机器学习中,我们经常会说一句话:模型训练的目标,是让损失函数尽可能小(在深度学习中也经常使用梯度下降)。 但问题是:参数那么多,损失函数又可能很复杂,模型到底应该怎样找到那个“比较好”的参数呢? 梯度下降法就是最常用的答案之一。它的思想并不神秘:先站在一个位置,看哪里下降最快,然后朝那个方向走一小步;重复很多次以后,就逐渐接近损失函数的低谷(梯度下降就是通过多元求极值的方式来求最小值)。 1. 为什么需要梯度下降以线性回归为例,模型可以写成: $$\hat{y} = wx + b$$ 其中: $x$ 是输入特征。 $\hat{y}$ 是模型预测值。 $w$ 是权重。 $b$ 是偏置。 我们希望预测值 $\hat{y}$ 尽量接近真实值 $y$,因此可以使用均方误差作为损失函数: $$J(w, b) = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)})^2$$ 因为 $\hat{y}^{(i)} = wx^{(i)} + b$,所以也可以写成: $$J(w, b) = \...
交叉验证与网格搜索:让模型选择更可靠
训练机器学习模型时,我们经常会遇到两个问题: 这个模型到底是不是真的好? 参数应该怎么选,比如 KNN 的 $K$、逻辑回归的正则化强度、决策树的最大深度? 如果只把数据随便分成一次训练集和测试集,然后看一次准确率,很容易被偶然性误导。交叉验证(Cross Validation)就是为了解决“评估不稳定”的问题;网格搜索(Grid Search)则是为了解决“超参数怎么选”的问题。 1. 为什么不能只划分一次数据假设我们有一份数据集,要判断一个模型效果如何。最常见的做法是: 训练集:用来训练模型 测试集:用来评估模型 这听起来很合理,但有一个问题:一次划分可能有运气成分。 如果测试集刚好比较简单,模型分数会偏高;如果测试集刚好比较困难,模型分数会偏低。也就是说,一次测试结果可能不能代表模型的真实水平。 可以把它想象成考试: 只考一张卷子,可能刚好考到你熟悉的题。 多考几张不同卷子,平均成绩才更接近真实水平。 交叉验证的思想就是:让模型多考几次,再看平均表现。 2. 什么是 K 折交叉验证K 折交叉验证(K-Fold Cross Validation)会把数据分成 ...
Hexo 如何实现源码私有、页面公开部署
使用 Hexo 搭建博客时,我们通常会接触到两类文件: Hexo 源代码:Markdown 文章、主题配置、站点配置、脚手架、依赖文件等。 静态网页文件:Hexo 生成后的 HTML、CSS、JS、图片等,也就是访问者真正看到的网页。 如果把这两类文件都放在同一个公开仓库里,别人不仅能看到博客页面,也能看到你的 Markdown 原文、草稿结构、主题配置和提交历史。为了更好地管理博客,可以采用一种更清晰的方案:源码放私有仓库,网页放公开仓库。 1. 为什么要分开部署Hexo 的工作流程可以理解为: Hexo 源代码 | | npx hexo generate v 静态网页文件 | | npx hexo deploy v GitHub Pages 公开访问 访问者真正需要的是生成后的静态网页,并不需要看到你的 Hexo 源代码。 因此,更合理的结构是: 私有仓库:保存 Hexo 源代码 公开仓库:保存 hexo deploy 生成的网页文件 这样既能正常公开博客,又能避免源码仓库暴露。 2. public 和 privac...
逻辑回归:从直线到概率的分类算法
逻辑回归(Logistic Regression)名字里有“回归”,但它最常用于分类任务,尤其是二分类问题(逻辑回归说白了就是在线性回归的基础上使用Sigmoid 函数将原先结果转为概率的形式,然后根据设定的阈值来判断是否是正类)。 它要回答的问题不是“这个样本的数值是多少”,而是“这个样本属于某一类的概率有多大”。例如: 一封邮件是垃圾邮件的概率是多少? 一个用户会不会点击广告? 一个肿瘤样本是恶性的概率是多少? 一个学生是否能通过考试? 逻辑回归的核心思想可以概括为一句话:先用一条直线算出一个分数,再用 Sigmoid 函数把这个分数压缩成 $0$ 到 $1$ 之间的概率。 1. 从线性回归说起在线性回归中,我们用一个线性函数预测连续值: $$z = wx + b$$ 如果是多个特征(w可以看作权重),可以写成: $$z = \mathbf{w}^{T}\mathbf{x} + b$$ 这里的 $z$ 可以理解为模型给样本打出的“原始分数”。 但是分类问题不能直接使用这个分数。比如判断一封邮件是不是垃圾邮件时,我们希望模型输出的是: $$P(y&...
线性回归:从直觉到最小二乘法
线性回归(Linear Regression)是机器学习中最基础、也最常用的监督学习算法之一。它主要用于解决回归问题,也就是预测一个连续数值。 例如: 根据房屋面积预测房价。 根据学习时长预测考试分数。 根据广告投入预测销售额。 线性回归的核心思想很朴素:如果两个变量之间存在近似线性的关系,就可以用一条直线或者一个线性函数去描述这种关系,并用它来预测未知样本。 图中的蓝色点是训练数据,红色直线是拟合出的线性模型,灰色虚线表示预测值和真实值之间的误差。线性回归训练时,本质上就是在寻找一条让整体误差尽可能小的直线。 1. 线性回归的直觉假设我们想根据学习时长预测考试分数,已有数据如下: 学习时长/小时 考试分数 1 50 2 55 3 65 4 70 5 80 把这些点画到坐标系中,可以发现:学习时间越长,考试分数通常越高。虽然这些点不一定完全落在同一条直线上,但整体趋势接近一条向上倾斜的直线。 线性回归要做的事情,就是找到一条尽可能贴近这些数据点的直线: $$\hat{y} = wx + b$$ 其中: $x$ 表示输...
K 近邻算法:KNN(K-Nearest Neighbors)
KNN(K-Nearest Neighbors,K 近邻)是一种非常直观的监督学习算法:一个样本属于什么类别,可以参考它在特征空间中最近的 $K$ 个邻居。 在分类任务中,KNN 通常采用“多数投票”:最近的 $K$ 个样本里哪个类别最多,就预测为哪个类别;在回归任务中,KNN 通常取最近 $K$ 个样本标签值的平均值或加权平均值。 1. KNN 的直觉可以把 KNN 想象成“向邻居打听答案”。 假设小区里新搬来一户人家,你想判断他们更像“高收入家庭”还是“普通收入家庭”。最直接的方式,是观察离他们最近的几户邻居:如果最近的 5 户里有 3 户都是高收入家庭,那么你可能会猜测这户新邻居也更接近高收入家庭。 机器学习里的 KNN 做的是类似的事情: 输入:一个尚未标记类别的新样本。 过程:在训练集中找到距离它最近的 $K$ 个已知样本。 输出:分类时进行多数投票,回归时进行平均或加权平均。 KNN 本质上没有显式的训练过程,它把训练数据保存下来,预测时再计算新样本与训练样本之间的距离。因此它也被称为惰性学习(Lazy Learning)。 2. 数学原理KNN 要解决两个核...
