神经网络原理

想象你手写了一个数字"3",然后拿给计算机看——它是怎么"认出"这是3而不是8的?这个看似简单的问题,背后隐藏着整个深度学习的核心思想。让我们从最直觉的方式开始,一层一层揭开神经网络的面纱。 一、从像素到数字:问题的本质 1.1 手写数字识别 假设我们有一张28x28像素的灰度手写数字图片。每个像素是一个0到1之间的数字——0代表全黑,1代表全白。这意味着一张图片其实就是一个包含784个数字的列表。 图片 → [0.0, 0.0, 0.12, 0.85, 0.93, ..., 0.0] (784个数字) 我们的目标是设计一个函数,输入这784个数字,输出10个数字,分别代表这张图是0~9每个数字的概率: f(784个像素值) → [P(0), P(1), P(2), ..., P(9)] 比如输入一张手写的"3",我们期望输出类似: [0.01, 0.02, 0.05, 0.89, 0.01, 0.01, 0.00, 0.01, 0.00, 0.00] ^^^^ "3"的概率最高 这就是神经网络要解决的问题。但关键问题是:这个函数 f 长什么样? 1.2 为什么传统编程行不通 你可能会想:能不能写一堆if-else规则?比如"如果上方有一个圆弧,下方也有一个圆弧,那就是3"。 问题在于:人类写字的方式千变万化。同一个"3",有人写得圆润,有人写得尖锐,有人偏左,有人偏右。你几乎不可能用明确的规则覆盖所有情况。 我们需要的是一种从数据中自动学习规则的方法——这就是神经网络。 二、神经网络的结构 2.1 神经元:最基本的单元 让我们从最简单的单元开始。一个神经元本质上就是一个持有数字的容器。这个数字叫做激活值(activation),取值范围通常在0到1之间。 你可以把它想象成一个灯泡:0表示完全熄灭,1表示完全点亮,中间的值表示不同亮度。 2.2 层:神经元的组织方式 神经网络把神经元组织成层(layer): graph LR subgraph 输入层 [输入层 - 784个神经元] I1[像素1] I2[像素2] I3[...] I784[像素784] end subgraph 隐藏层1 [隐藏层1 - 16个神经元] H1_1[h1] H1_2[h2] H1_3[...] H1_16[h16] end subgraph 隐藏层2 [隐藏层2 - 16个神经元] H2_1[h1] H2_2[h2] H2_3[...] H2_16[h16] end subgraph 输出层 [输出层 - 10个神经元] O0[0] O1[1] O2[...] O9[9] end I1 --> H1_1 I1 --> H1_2 I784 --> H1_16 H1_1 --> H2_1 H1_16 --> H2_16 H2_1 --> O0 H2_16 --> O9 输入层:784个神经元,每个对应一个像素值 隐藏层:中间的层(这里用2层,每层16个神经元) 输出层:10个神经元,对应数字0~9 为什么叫"隐藏层"?因为在训练数据中,我们只知道输入(像素)和输出(标签),中间层在"学什么"我们并不直接指定——它们是被自动学出来的。 ...

May 2, 2026

责任链模式

定义 责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它将请求的发送者和接收者解耦,让多个对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 责任链模式的核心思想是:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象组成一条链,并沿着这条链传递请求,直到有对象处理它为止。 为什么需要责任链模式 问题场景:假设我们正在开发一个审批系统,不同金额的报销需要不同级别的人员审批。 最直接的方式可能是这样: class ExpenseApproval { func approve(amount: Double) { if amount <= 1000 { // 组长审批 teamLeaderApprove(amount) } else if amount <= 5000 { // 部门经理审批 departmentManagerApprove(amount) } else if amount <= 10000 { // 总监审批 directorApprove(amount) } else { // CEO审批 ceoApprove(amount) } } func teamLeaderApprove(_ amount: Double) { ... } func departmentManagerApprove(_ amount: Double) { ... } func directorApprove(_ amount: Double) { ... } func ceoApprove(_ amount: Double) { ... } } 这种方式有什么问题? ...

May 2, 2026

反向传播算法

在上一篇文章中,我们构建了一个神经网络,了解了前向传播是如何产生预测的,也知道了损失函数可以衡量预测有多差。但我们遗留了最核心的问题:那13,002个参数到底该怎么调整,才能让网络变得更好? 这就是反向传播算法(Backpropagation)要回答的问题。它是神经网络学习的核心引擎,几乎所有现代深度学习的训练都建立在它之上。 一、梯度下降:从直觉开始 1.1 一个简单的类比 想象你站在一片浓雾中的山地上,看不到全貌,但你能感受到脚下地面的倾斜方向。你的目标是找到最低的山谷。最自然的策略是什么? 顺着脚下最陡的下坡方向走一小步。 这就是**梯度下降(Gradient Descent)**的核心思想。 1.2 数学表述 假设损失函数 $L$ 取决于所有参数 $\theta = (w_1, w_2, ..., w_n, b_1, b_2, ..., b_m)$。 梯度是一个向量,指向损失增长最快的方向: $$\nabla L = \left(\frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \cdots, \frac{\partial L}{\partial b_m}\right)$$梯度的每个分量告诉我们:如果微微增大某个参数,损失会增加多少。 要降低损失,我们朝梯度的反方向迈步: $$\theta_{\text{new}} = \theta_{\text{old}} - \eta \cdot \nabla L$$其中 $\eta$ 是学习率(learning rate),控制每一步的步长。 1.3 学习率的微妙之处 学习率 下降行为 结果 太小 每步挪动极小,到达局部最小值后就停住了,无法跳出 收敛极慢,可能困在局部最小值 太大 一步跨过了最低点,又跨回来,反复来回 在最低点附近震荡,甚至发散 合适 每步大小适中,沿着曲面稳步走向谷底 稳步下降,逐渐逼近最优解 1.4 梯度下降的关键问题 现在整个问题归结为一件事:怎么高效地计算 $\nabla L$,即损失对每一个参数的偏导数? 你可能会想:对每个参数微微扰动一下,看看损失变化了多少。但如果有13,002个参数,每个参数都扰动一次,就需要做13,002次前向传播——太慢了。 ...

May 2, 2026

Swift二进制兼容性

Swift二进制兼容性是指Swift编译后的二进制代码能够跨版本、跨模块正确链接和运行的能力。它由三个核心机制组成:ABI稳定(Swift 5.0)、模块稳定性(Swift 5.1)和Library Evolution。这三者共同使得Swift二进制框架的分发成为可能。 graph TD A[Swift二进制兼容性] --> B[ABI稳定Swift 5.0] A --> C[模块稳定性Swift 5.1] A --> D[Library Evolution] B --> B1[统一运行时固定调用约定/内存布局] C --> C1[.swiftinterface跨编译器版本导入模块] D --> D1[运行时布局查询库可独立于客户端更新] B1 --> E[二进制框架分发] C1 --> E D1 --> E ABI稳定 什么是ABI ABI(Application Binary Interface,应用程序二进制接口)定义了二进制层面的接口规范,包括: 数据类型的大小和对齐方式:如Int在64位系统上是8字节 函数调用约定:参数如何传递、返回值如何获取 名称修饰(Name Mangling)规则:函数和类型在二进制中的命名方式 内存布局:对象在内存中的结构 异常处理机制:错误如何在二进制层面传播 运行时元数据格式:类型信息的存储方式 ABI vs API 特性 API(源码接口) ABI(二进制接口) 层面 源代码层面 编译后的二进制层面 兼容性检查 编译时 链接时/运行时 关注点 函数签名、类型定义 内存布局、调用约定 变化影响 需要重新编译 需要重新链接或替换二进制 一个简单的例子: // API层面:函数签名 func calculate(value: Int) -> Int // ABI层面关注的是: // - Int在内存中占多少字节 // - value参数通过哪个寄存器传递 // - 返回值通过哪个寄存器返回 // - 函数在二进制中的符号名是什么 ABI不稳定时期的问题 在Swift 5.0之前,Swift的ABI是不稳定的。这意味着: ...

May 2, 2026

Swift中import详解

源码版本说明:本文涉及的源码基于 Swift 编译器 开发版本(swift main分支,commit: e0db5152d4d4,2026-01-19)。不同版本的实现细节可能略有差异,但核心机制保持一致。 在前一篇文章Objective-C中import详解中,我们深入探讨了 Objective-C 中的 import 机制,包括 #include、#import、@import 以及 Clang Modules 的工作原理。本文将继续从 Swift 编译器的角度,讲解 Swift 中 import 的语法、模块查找机制、底层实现原理以及与 Objective-C 的互操作。 从 Clang Module 到 Swift Module 在深入 Swift 的 import 机制之前,让我们先回顾一下上一篇文章中 Clang Module 的核心概念。 Clang Module 核心概念回顾 在 Objective-C 文章中,我们学习了 Clang Module 的几个关键特性: module.modulemap:定义模块结构的配置文件 framework module Foundation { umbrella header "Foundation.h" export * } 预编译模块缓存(.pcm 文件):Clang 将模块编译为二进制格式,缓存以加速后续编译 懒加载机制:只加载实际使用的声明,未使用的部分不会被解析 自动链接:@import 会自动链接对应的库,无需手动配置 模块查找路径:通过 -I、-F 等参数指定搜索路径 Swift 如何继承 Clang Module 的设计 Swift 的模块系统并非从零开始设计,而是站在 Clang Module 的肩膀上,继承了其核心优势,并针对 Swift 的特性进行了扩展: ...

May 2, 2026

iOS中AI Chat实战

做一个能跑的AI聊天Demo只需要几十行代码:拼一个HTTP请求、把模型返回的文本显示出来即可。但要做一个达到生产级体验的AI Chat,就会面对一堆并不"AI"的工程问题:字怎么一个个"蹦"出来?Markdown怎么边流边渲染?模型想让前端弹一个卡片甚至一个表单,该怎么办?用户点击卡片里的按钮怎么回传给模型? 本文以iOS为实现目标,系统梳理AI Chat的工程架构,重点讲清楚两件事:流式输出与A2UI(Agent to UI),同时覆盖工具调用、取消、多模态、端侧推理等常见高级能力。 一、整体架构 一个完整的iOS AI Chat客户端,通常可以拆成如下几层: graph TB subgraph UI ["展示层"] LIST["消息列表UICollectionView / List"] CELL["消息Cell文本 / Markdown / A2UI Surface"] INPUT["输入区文本 / 图片 / 语音"] end subgraph VM ["ViewModel层"] STATE["会话状态机"] STREAM["流式拼装器"] TOOL["工具执行器"] end subgraph NET ["网络层"] SSE["SSE / HTTP 分块"] PARSER["协议解析OpenAI / Claude / A2UI"] end subgraph DATA ["数据层"] DB["消息持久化GRDB / WCDB / CoreData"] CTX["上下文裁剪Token预算"] end INPUT --> STATE STATE --> NET NET --> PARSER PARSER --> STREAM STREAM --> CELL STREAM --> TOOL TOOL --> NET STATE --> DB DB --> CTX CTX --> NET 几个关键特征: ...

May 2, 2026

观察者模式

定义 观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(主题/被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会收到通知并自动更新。 观察者模式也被称为发布-订阅模式(Publish-Subscribe Pattern)。 为什么需要观察者模式 观察者模式要解决的核心问题是:当一个对象的状态发生变化时,如何自动通知所有依赖它的对象。 问题场景:假设我们正在开发一个电商App,用户登录后需要更新多个页面的状态(首页显示用户名、购物车同步、个人中心更新等)。 最直接的方式可能是这样: class LoginService { func login(username: String, password: String) { // 登录成功后... let user = User(name: username) // 直接调用各个需要更新的对象 HomeViewController.shared.updateUser(user) CartViewController.shared.syncCart(for: user) ProfileViewController.shared.refreshProfile(user) // 如果还有其他需要通知的地方,继续添加... } } 这种方式有什么问题? 紧耦合:LoginService必须知道所有需要通知的对象,依赖关系错综复杂 难以维护:每新增一个需要监听登录事件的页面,都要修改LoginService 违反开闭原则:对扩展不开放,每次扩展都要修改已有代码 单例滥用:为了让LoginService能访问到各个页面,不得不使用大量单例 观察者模式的解决思路: 被观察者(Subject)只负责"广播"状态变化,不关心谁在监听;观察者(Observer)自己注册感兴趣的事件: // 使用NotificationCenter实现 class LoginService { func login(username: String, password: String) { let user = User(name: username) // 只负责发送通知,不关心谁在监听 NotificationCenter.default.post( name: .userDidLogin, object: nil, userInfo: ["user": user] ) } } // 各个页面自己注册监听 class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(handleUserLogin), name: .userDidLogin, object: nil ) } @objc func handleUserLogin(_ notification: Notification) { if let user = notification.userInfo?["user"] as? User { updateUI(with: user) } } } 观察者模式的好处: ...

May 2, 2026

Transformer架构原理

ChatGPT、Claude、Gemini——这些模型的核心都是同一个架构:Transformer。它在2017年由Google的论文"Attention Is All You Need"提出,彻底改变了自然语言处理(NLP)领域,随后席卷了计算机视觉、语音、蛋白质折叠等几乎所有AI领域。 我们从最直觉的角度出发,一步步搞清楚Transformer到底在做什么。 一、从序列到序列:Transformer要解决什么问题 1.1 语言的本质是序列 一句话是一个词的序列: "我 今天 很 开心" → [我, 今天, 很, 开心] 语言模型的核心任务是:给定前面的词,预测下一个词。 "我 今天 很" → ?(开心?难过?忙碌?) 但词与词之间的关系非常复杂。“它"指代谁?“银行"是河岸还是金融机构?这取决于上下文中其他词的信息。 1.2 RNN的困境 在Transformer之前,处理序列的主流方法是循环神经网络(RNN)。RNN像一个人从左到右逐词阅读,用一个"记忆状态"来记住看过的内容: graph LR X1["我"] --> H1["h₁"] H1 --> H2["h₂"] X2["今天"] --> H2 H2 --> H3["h₃"] X3["很"] --> H3 H3 --> H4["h₄"] X4["开心"] --> H4 问题在于: 长距离依赖困难:信息需要经过很多步才能从序列开头传到末尾,容易丢失 无法并行:必须逐步处理,前一步完成才能开始后一步,训练很慢 Transformer的核心洞察是:不需要逐步处理,让序列中的每个位置直接与所有其他位置交互。这就是"注意力(Attention)“机制。 二、词嵌入:给词语一个数学身份 2.1 从独热编码到词向量 计算机不认识"猫”、“狗"这样的文字。我们需要把词转换成数字。 最朴素的方式是独热编码(One-Hot Encoding):假设词汇表有50,000个词,每个词用一个50,000维的向量表示,只有对应位置是1,其余全是0。 "猫" → [0, 0, ..., 1, ..., 0, 0] (第3721个位置是1) "狗" → [0, 0, ..., 0, 1, ..., 0, 0] (第5042个位置是1) 问题:在这种表示下,“猫"和"狗"的距离与"猫"和"经济学"的距离一样远——完全丢失了语义信息。 ...

May 2, 2026

SIL(Swift Intermediate Language)

SIL(Swift Intermediate Language,Swift 中间语言)是 Swift 编译器在编译过程中生成的一种中间表示(Intermediate Representation,IR)。它位于 Swift 源码与 LLVM IR 之间,是 Swift 编译器架构中至关重要的一层。 SIL 的设计目标是: 对 Swift 语言的高级语义进行精确建模 在高层抽象的基础上执行强大的优化 保留足够的类型信息,用于诊断和安全检查 作为 Swift 特有优化的载体,弥补 LLVM IR 无法直接表达 Swift 语义的不足 SIL 并不是给开发者日常编写的语言,而是编译器的内部表示。理解它有助于深入理解 Swift 的编译过程、性能优化原理和内存管理机制。 Swift 编译流程概览 在了解 SIL 之前,需要先理解 Swift 的整体编译流程: flowchart TD A[Swift 源代码 .swift] --> B[词法分析 Lexer] B --> C[语法分析 Parser] C --> D[语义分析 Sema] D --> E[SILGen: 生成 Raw SIL] E --> F[SIL 优化 Guaranteed Passes] F --> G[SIL 优化 General Passes] G --> H[IRGen: 生成 LLVM IR] H --> I[LLVM 优化] I --> J[机器码 .o] J --> K[链接器 Linker] K --> L[可执行文件 / 动态库] 各阶段简述 词法分析(Lexer) 编译的第一步。Lexer 逐字符扫描 .swift 源文件,将其切分为一系列Token(词法单元),例如关键字 func、标识符 add、运算符 +、字面量 42、左右括号等。Token 是后续所有分析的最小输入单位。这一步会剥离注释和空白,但保留它们的位置信息以便后续生成精确的诊断信息(错误提示的行号和列号)。 ...

May 2, 2026

装饰器模式

定义 装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向现有对象动态添加新功能,同时又不改变其结构。装饰器通过创建一个包装对象来包裹真实对象,实现在不修改原有对象的基础上扩展功能。 装饰器模式的核心思想是:使用组合而非继承来扩展对象的功能,遵循"对扩展开放,对修改关闭"的原则。 为什么需要装饰器模式 装饰器模式要解决的核心问题是:在不修改原有对象的情况下,动态地给对象添加新功能。 问题场景 假设我们正在开发一个UI组件库,需要为UILabel添加各种视觉效果:圆角、阴影、边框、渐变背景等。而且这些效果需要能够自由组合。 方案一:使用继承(不推荐) // 基础标签 class BasicLabel: UILabel { init(text: String) { super.init(frame: .zero) self.text = text } } // 圆角标签 class RoundedLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.cornerRadius = 8 clipsToBounds = true } } // 阴影标签 class ShadowLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0, height: 2) layer.shadowOpacity = 0.3 } } // 边框标签 class BorderLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.borderWidth = 1 layer.borderColor = UIColor.gray.cgColor } } // 如果需要圆角+阴影呢? class RoundedShadowLabel: BasicLabel { override init(text: String) { super.init(text: text) // 重复圆角代码 layer.cornerRadius = 8 clipsToBounds = true // 重复阴影代码 layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0, height: 2) layer.shadowOpacity = 0.3 } } // 如果需要圆角+边框呢? class RoundedBorderLabel: BasicLabel { /* 又要重复代码 */ } // 如果需要阴影+边框呢? class ShadowBorderLabel: BasicLabel { /* 继续重复代码 */ } // 如果需要圆角+阴影+边框呢? class RoundedShadowBorderLabel: BasicLabel { /* 所有代码都重复一遍 */ } 这种方式的问题: ...

May 2, 2026