USF-MSDS626-实时编程笔记-全-
USF MSDS626 实时编程笔记(全)
001:第1讲 - 终端与环境管理 🖥️


在本节课中,我们将学习如何从零开始设置一个高效、无冲突的Python数据科学工作环境。我们将重点介绍终端的使用、Python环境的独立管理,以及如何通过Mamba Forge安装核心工具。
概述
数据科学工作流的第一步是建立一个可靠且易于管理的计算环境。一个常见的陷阱是使用系统自带的Python,这会导致库版本冲突和权限问题。本节课将引导你通过终端,使用Mamba Forge创建一个独立的、干净的环境,并安装Jupyter Lab和PyTorch等核心工具。
1. 认识终端与Shell
上一节我们介绍了课程目标,本节中我们来看看数据科学家的核心工具之一:终端(Terminal)。
终端是一个程序,它提供了一个文本界面来运行另一个叫做“Shell”的程序。Shell(如Bash或Zsh)才是真正接收并执行你输入命令(如ls、cd)的组件。虽然我们常将两者混用,但了解其区别是有帮助的。

使用终端而非图形界面(GUI)的优势在于效率与自动化。你可以将一系列操作写入脚本,一键执行,这类似于用语言交流比用手势交流更强大、更精确。

不同操作系统的启动方式:
- Mac: 按
Command + 空格,搜索“Terminal”并打开。 - Windows: 从Microsoft Store安装“Windows Terminal”。首次启动后,建议安装WSL(Windows Subsystem for Linux)。
- Linux: 通常已预装,可通过应用菜单找到。



对于Windows用户,建议在PowerShell(管理员模式)中运行 wsl --install 来安装Ubuntu,并将Ubuntu设置为Windows Terminal的默认配置文件。
2. 安装独立的Python环境
我们已经打开了终端,接下来要确保我们使用的是独立的Python环境,而非系统自带的Python。
首先,检查当前Python的路径:
which python
如果返回类似 /usr/bin/python 的路径,说明你正在使用系统Python,这不是我们想要的。

我们需要一个安装在用户主目录下的Python。为此,我们选择安装 Mamba Forge(一个Conda的快速替代品)。以下是安装步骤:

- 访问Mamba Forge的GitHub发布页面,找到适合你操作系统(Linux/macOS)的安装脚本链接。
- 在终端中,使用
wget命令下载该安装脚本。 - 运行该脚本以完成安装。


在安装过程中,当询问是否“初始化”时,请选择“是”。这样每次打开终端时,环境都会自动激活。

安装完成后,关闭并重新打开终端。你应该能在提示符前看到 (base) 字样,这表示你已处于基础环境中。再次运行 which python,路径应指向你的用户主目录(如 /home/yourname/mambaforge/bin/python)。



3. 使用Mamba管理包
环境设置好后,我们就可以用Mamba来安装和管理软件包了。Mamba与Conda命令兼容,但速度更快。



核心命令是 mamba install。例如,安装一个交互式Python环境(IPython):
mamba install ipython
Mamba会解析依赖关系并征得你的同意后开始安装。安装完成后,输入 ipython 即可进入交互模式。




为什么优先使用Mamba/Conda而非pip?
- 管理二进制依赖: Mamba能处理非Python的库(如CUDA驱动),安装更完整。
- 环境隔离: 轻松创建独立环境,避免项目间的包冲突。
- 可靠性: 对于PyTorch等复杂库,通过指定频道(如
-c pytorch)安装更可靠。


若要安装PyTorch(CPU版本),可以复制官网提供的Conda命令,并将开头的 conda 替换为 mamba:
mamba install pytorch torchvision torchaudio cpuonly -c pytorch
4. 安装与运行Jupyter Lab


对于数据科学工作,Jupyter Lab是一个强大的交互式笔记本环境。

使用Mamba安装Jupyter Lab:
mamba install jupyterlab
安装完成后,为了组织文件,建议先创建一个专用目录:
mkdir nbs
cd nbs
然后启动Jupyter Lab。为了避免它自动打开浏览器可能产生的问题,可以加上 --no-browser 参数:
jupyter lab --no-browser
启动后,终端会显示一个本地URL(如 http://localhost:8888/lab)。你可以按住 Ctrl 键并点击该链接,在浏览器中打开Jupyter Lab界面。

5. 提高效率的技巧



为了提升日常使用效率,这里有一些实用技巧:

终端操作技巧:
- Tab键自动补全: 输入命令或路径的前几个字母后按Tab键。
- 上下箭头: 翻阅历史命令。
- Ctrl + R: 反向搜索历史命令。
- Ctrl + A / Ctrl + E: 快速跳转到命令行的开头或结尾。
- Ctrl + D: 退出当前程序(如IPython或Shell)。
创建命令别名:
如果觉得 jupyter lab --no-browser 太长,可以为其创建一个别名(alias):
alias jl='jupyter lab --no-browser'
但这样设置的别名只在当前会话有效。若要永久生效,需要将这条别名命令添加到Shell的配置文件中(如 ~/.bashrc 或 ~/.zshrc)。这样每次打开新终端时,都可以直接输入 jl 来启动Jupyter Lab。



总结






本节课中我们一起学习了数据科学环境搭建的“正确打开方式”。我们从认识终端开始,逐步完成了使用Mamba Forge安装独立Python环境、用Mamba管理包(以IPython和PyTorch为例)、安装并启动Jupyter Lab,最后还介绍了一些提升终端使用效率的技巧。关键在于将用户环境与系统环境分离,并优先使用Mamba进行包管理,这能为后续平滑、无冲突的学习和实践打下坚实基础。
002:Linux与Git开发流程 🐧📚


在本节课中,我们将学习如何在Linux环境下使用Git进行版本控制,并建立一个完整的开发工作流程。我们将从创建GitHub仓库开始,逐步学习克隆、编辑、提交和推送代码,同时介绍一些提高效率的终端工具。


概述
我们将从零开始,创建一个GitHub仓库,将其克隆到本地计算机,然后使用Jupyter Lab编辑一个笔记本文件。接着,我们会学习如何使用Git跟踪更改、提交版本,并将更改推送回GitHub。在此过程中,我们还会介绍SSH密钥认证、Tmux终端复用器等实用工具,帮助你建立一个流畅的开发环境。
创建GitHub仓库
首先,我们需要在GitHub上创建一个新的代码仓库(Repository)。仓库本质上是一个包含文件和子文件夹的目录,Git可以跟踪其所有版本变化,并允许你轻松地将代码存储和分享到GitHub等平台。
以下是创建仓库的步骤:
- 登录GitHub,点击右上角你的头像,选择 “Your repositories”。
- 点击 “New” 按钮创建一个新仓库。
- 为仓库命名(例如
walkthrough2)。 - 可以添加一个描述(可选)。
- 选择仓库的可见性(公开或私有)。对于学习和分享,公开仓库通常更有益。
- 勾选 “Add a README file” 来初始化一个说明文件。
- 在 “.gitignore” 模板中选择
Python,这会自动生成一个忽略临时文件的配置。 - 选择一个开源许可证(例如 Apache License 2.0),以便他人使用你的代码。
- 点击 “Create repository” 完成创建。
创建完成后,你会看到一个包含 README.md 文件的仓库页面。README.md 是一个特殊的Markdown文件,其内容会自动显示在仓库主页,用于介绍项目。
编辑README文件
现在,我们可以直接在线编辑 README.md 文件来完善项目介绍。Markdown是一种轻量级标记语言,在Jupyter和GitHub中广泛使用。

点击文件右上角的铅笔图标进入编辑模式。你可以使用 # 创建标题,用空行分隔段落,用 - 创建列表。编辑完成后,在页面底部填写本次更改的摘要(Commit message),然后点击 “Commit changes” 保存。每次提交都会在Git历史中创建一个新的版本。


克隆仓库到本地


虽然可以在线编辑,但更常见的做法是在本地计算机上工作。为此,我们需要将远程仓库“克隆”(Clone)到本地。
首先,在GitHub仓库页面点击绿色的 “Code” 按钮,选择 “SSH” 选项卡,并复制提供的URL。


然后,打开终端(Terminal),导航到你希望存放代码的目录(例如 ~/git),执行克隆命令:
git clone <你复制的SSH_URL>
如果这是你第一次使用SSH连接GitHub,可能会遇到权限错误。这是因为SSH认证需要密钥对。
设置SSH密钥认证
SSH使用非对称加密进行认证,比输入密码更方便、安全。你需要生成一对密钥:私钥(保密)和公钥(可公开)。



在终端中运行以下命令生成密钥:
ssh-keygen -t ed25519 -C "your_email@example.com"
连续按回车接受默认设置。这会在 ~/.ssh/ 目录下生成私钥(id_ed25519)和公钥(id_ed25519.pub)。



接下来,需要将公钥添加到GitHub账户:
- 在终端中显示公钥内容:
cat ~/.ssh/id_ed25519.pub - 复制输出的全部内容。
- 登录GitHub,点击头像 -> “Settings” -> “SSH and GPG keys”。
- 点击 “New SSH key”,粘贴公钥内容,为其命名(如 “My Laptop”),然后保存。
完成后,重新运行 git clone 命令,应该就能成功克隆仓库了。




使用Tmux管理终端会话
在终端中运行Jupyter Lab等服务时,该终端会被占用。Tmux是一个终端复用器,可以让你在一个窗口内创建多个面板(Pane),并能保持会话在后台运行,即使关闭终端窗口也不会中断进程。



安装Tmux(Ubuntu/Debian):
sudo apt install tmux
启动Tmux:
tmux
你会看到屏幕底部出现一个状态栏。以下是一些常用快捷键(所有操作先按 Ctrl+b 作为前缀):
%:垂直分割当前面板。":水平分割当前面板。- 方向键:在面板间切换焦点。
z:最大化/恢复当前面板。d:分离当前Tmux会话(让它在后台运行)。tmux attach:重新连接到后台的Tmux会话。
使用Tmux,你可以在一个面板运行Jupyter,在另一个面板执行Git命令,互不干扰。

在本地使用Jupyter Lab

在终端中,导航到克隆的仓库目录,然后启动Jupyter Lab:
jupyter lab
终端会输出一个带有令牌(Token)的URL。在浏览器中打开此URL即可访问Jupyter Lab界面。
在Jupyter Lab中,你可以创建新的笔记本(.ipynb 文件)或Python脚本。使用 y 键将单元格切换为代码模式,使用 m 键切换为Markdown模式。编辑完成后,记得为文件起一个清晰的名称。





Git基本工作流程
现在,我们有了本地的代码更改(例如新创建的笔记本),需要将其纳入版本控制并同步到GitHub。

以下是核心的Git命令流程:
- 检查状态:随时使用
git status查看哪些文件被修改或尚未跟踪。 - 添加更改:使用
git add <文件名>将文件放入暂存区(Staging Area),准备提交。例如git add sample.ipynb。 - 提交更改:使用
git commit -m “提交信息”创建一个本地版本记录。提交信息应简洁描述本次更改。 - 推送至远程:使用
git push将本地提交上传到GitHub远程仓库。

完成推送后,刷新你的GitHub仓库页面,就能看到新增的文件了。整个流程可以概括为:修改 -> 添加 -> 提交 -> 推送。


Fork与协作

你不仅可以管理自己的仓库,还可以参与他人的项目。Fork(分叉)功能允许你创建某个仓库的个人副本,你可以在副本上自由实验和修改,而不会影响原项目。




在GitHub上找到你感兴趣的项目(如FastAI的 fastbook 仓库),点击右上角的 “Fork” 按钮。这会在你的账户下创建一个完全独立的副本。然后,你可以像操作自己的仓库一样克隆、修改和推送这个副本。



如果你想将改进贡献回原项目,可以在你的Fork仓库页面发起 “Pull Request”(拉取请求)。



总结

本节课我们一起学习了Linux与Git的核心开发流程。我们从创建GitHub仓库开始,配置了SSH密钥以实现安全便捷的认证,并使用Tmux来高效管理终端任务。我们实践了完整的Git工作循环:在本地修改代码,通过 git add 和 git commit 进行版本控制,最后用 git push 同步到远程仓库。此外,我们还了解了如何通过Fork参与开源项目协作。掌握这些工具和流程,将为你的后续学习和项目开发打下坚实的基础。
003:Paperspace配置与自动化 🚀
在本节课中,我们将学习如何配置和使用Paperspace的免费GPU服务器,并实现环境的自动化设置,使其使用体验尽可能接近本地计算机。我们将涵盖环境变量、符号链接、SSH密钥管理以及如何持久化自定义配置。

概述



上一节我们介绍了如何在本地计算机上使用Python和Jupyter。本节中,我们来看看如何在云端使用Paperspace的GPU服务器。我们将学习如何配置服务器,使其在每次启动时都能自动恢复我们的个人设置、安装的库和SSH密钥,从而创建一个高效、可重复的工作环境。
环境变量与路径

在Linux系统中,Shell使用一个名为PATH的环境变量来查找可执行程序。PATH是一个用冒号分隔的字符串,列出了系统搜索程序的目录列表。
你可以使用echo命令查看当前PATH:
echo $PATH
当你在终端输入python时,系统会按照PATH中列出的目录顺序,查找名为python的可执行文件并运行它。
Python也有一个类似的机制,称为sys.path。这是一个Python列表,包含了Python解释器搜索模块(即库文件)的目录。你可以通过以下代码查看:
import sys
sys.path
通常,通过pip或conda安装的库会被放置在site-packages目录中,该目录默认包含在sys.path里。


Conda环境的工作原理
Conda或Mamba环境本质上是独立的目录,包含了Python解释器、库和其他依赖的副本。它们通过修改PATH环境变量来工作,将特定环境的bin目录置于搜索路径的前列。
以下是创建和激活一个名为test的Conda环境的命令:
mamba create -n test python=3.9 fastcore
mamba activate test
激活环境后,which python命令会显示Python来自新环境的目录(例如~/mambaforge/envs/test/bin/python)。环境的所有文件都存储在~/mambaforge/envs/目录下。
要返回基础环境,只需运行:
mamba activate
虽然环境对于隔离项目依赖很有用,但对于追求快速迭代的数据科学工作,维护一个统一的基础环境并保持所有库更新,通常是更简单高效的做法。
配置Paperspace服务器
Paperspace的Gradient Notebooks提供了免费的GPU资源。但需要注意的是,每次启动的服务器实例都是全新的,不会保留之前的更改(除了挂载的持久化存储)。因此,我们需要一种方法来自动化设置过程。
持久化存储与符号链接
Paperspace提供了一个名为/storage的目录,用于持久化存储文件。我们的目标是将所有个人配置和安装的库链接到这个目录。
例如,使用pip install --user安装的库会出现在~/.local目录中。为了让这些库在服务器重启后依然可用,我们需要:
- 将
~/.local目录移动到持久化存储中。 - 创建一个指向持久化存储的符号链接。
以下是操作步骤:
# 1. 将本地目录移动到持久化存储
mv ~/.local /storage/
# 2. 创建符号链接
ln -s /storage/.local ~/.local
符号链接(Symbolic Link)类似于Windows的快捷方式或Mac的替身,它允许一个目录指向另一个位置。
自动化脚本:prerun.sh
Paperspace允许我们创建一个名为/storage/prerun.sh的脚本,该脚本会在每次启动Jupyter服务器之前自动执行。我们可以将所有的符号链接创建命令放在这里。
以下是一个prerun.sh脚本的示例内容:
#!/bin/bash
# 创建必要的符号链接,将持久化存储链接到正确位置
ln -sf /storage/.local ~/.local
ln -sf /storage/.ssh ~/.ssh
ln -sf /storage/.git-credentials ~/.git-credentials
ln -sf /storage/.cache ~/.cache
ln -sf /storage/git ~/git
确保脚本有执行权限:
chmod +x /storage/prerun.sh
这样,每次启动新的Paperspace实例时,我们的个人环境都会自动准备就绪。

在Paperspace上管理SSH密钥

为了能从Paperspace服务器访问GitHub等代码仓库,我们需要配置SSH密钥。
以下是操作步骤:
- 生成或上传密钥:你可以在本地生成SSH密钥对,然后通过Jupyter Lab的文件上传功能将私钥(如
id_rsa)和公钥(如id_rsa.pub)上传到服务器。也可以直接在服务器上使用ssh-keygen命令生成新的密钥对。 - 放置密钥:将密钥文件移动到
~/.ssh/目录下。 - 设置正确权限:SSH对密钥文件的权限要求非常严格。私钥必须只有所有者可读。
chmod 600 ~/.ssh/id_rsa # 私钥:仅用户可读写 chmod 644 ~/.ssh/id_rsa.pub # 公钥:用户可读写,其他人只读 - 测试连接:
如果看到欢迎信息,说明认证成功。ssh -T git@github.com - 持久化密钥:最后,记得将整个
~/.ssh目录移动到/storage并创建符号链接,或者确保你的prerun.sh脚本包含了创建该链接的命令。
使用Python调试器(PDB)
在开发过程中,调试是必不可少的技能。Python自带了一个强大的命令行调试器pdb。
在Jupyter单元格中,你可以使用以下魔法命令启动pdb:
%%debug
# 你的代码将在这里进入调试模式
for i in range(3):
print(i)
进入调试器后,可以使用一些简短的命令进行控制:
n(next): 执行下一行代码。s(step): 进入函数调用内部。p <变量名>(print): 打印变量的值。l(list): 显示当前行附近的代码。c(continue): 继续执行,直到下一个断点或程序结束。q(quit): 退出调试器。
另一种更常用的方法是在代码中直接插入断点:
import pdb; pdb.set_trace()
# 这行代码之后的程序将会暂停,并进入pdb调试模式
掌握pdb这类通用工具的技能,可以在各种环境和编程语言中复用,大大提高效率。
总结


本节课中我们一起学习了如何深度配置Paperspace的GPU服务器。我们了解了环境变量PATH和Python的sys.path如何影响程序与模块的查找。掌握了使用符号链接将易失的实例目录与持久化存储/storage绑定,并通过prerun.sh脚本实现自动化。我们还实践了在服务器上配置SSH密钥以访问Git仓库,并介绍了使用Python内置调试器pdb进行代码调试的基本方法。通过这些步骤,我们可以打造一个既拥有强大GPU算力,又具备高度可定制性和持久性的云端开发环境,使其体验无限接近本地计算机。
004:从零配置 Paperspace 🚀


在本节课中,我们将学习如何从零开始,一步步配置 Paperspace 环境,以复现上一节课的操作。我们将重点关注如何设置持久化存储、管理 Python 包以及自动化配置过程,确保环境可重复且易于维护。
环境概览与核心目录 📁
上一节我们介绍了 Paperspace 的基本操作,本节中我们来看看其文件系统的核心结构。理解这一点对后续配置至关重要。
在 Paperspace 的根目录中,有两个特别重要的文件夹:
/storage:这是一个持久化存储目录。存放在这里的任何内容,在你创建的所有 Notebook 服务器实例中都是可见和共享的。它位于一个独立的 500GB 磁盘上。/notebooks:这也是一个持久化存储目录,但它仅对当前这个特定的 Notebook 服务器实例可见。如果你删除了这个服务器,这里的文件也会消失。它同样位于一个独立的 500GB 磁盘上。
你可以使用 df -h 命令来查看这些磁盘的挂载情况。注意,你需要为使用的存储空间付费,因此请定期在账户的“账单”部分查看使用量。
管理 Python 包:Pip 与 Conda 🐍
接下来,我们讨论如何在 Paperspace 中安装额外的 Python 包。这里涉及到包管理工具 Pip 和 Conda 的选择。
一个常见的疑问是:同时使用 Pip 和 Conda 会冲突吗?官方的回答是“会,不要这样做”。但根据实践社区(如 Fast.ai 论坛)的经验,成千上万的用户同时使用两者,很少遇到实际问题。
我们的建议是:
- 对于需要 GPU 支持的包(如 PyTorch、TensorFlow),优先使用 Conda。因为 Conda 能自动处理 CUDA 工具包等复杂的依赖关系,省去手动配置的麻烦。
- 对于其他通用包,使用 Pip 并安装到用户目录通常是安全且方便的。我们本节课将采用这种方式。
如果你想卸载包,可以使用 pip uninstall <package_name> 命令,或者直接删除相关的目录(如 mambaforge)并重新开始。关键在于,你应该能够随时重建一个可工作的环境。
持久化用户安装的包 🔗
我们的目标是将用户通过 Pip 安装的包(默认在 ~/.local 目录下)移动到持久化存储中,这样每次启动新实例时都能使用它们。
以下是实现步骤:
- 首先,在
/storage中创建一个目录来存放我们的配置,例如叫cfg。mkdir /storage/cfg - 将家目录下的
.local文件夹移动到持久化存储中。mv ~/.local /storage/cfg - 创建一个符号链接(软链接),将持久化存储中的
.local“映射”回原来的家目录位置。ln -s /storage/cfg/.local ~/.local
现在,~/.local 看起来像一个普通文件夹,但实际上指向 /storage/cfg/.local。之后使用 pip install --user 安装的包,都会实际存储在持久化空间里。
你可以通过导入包来测试是否成功,例如:
import fastcore
print(fastcore.__version__)
自动化配置:创建启动脚本 ⚙️
为了让每个新启动的 Notebook 服务器都能自动建立好我们需要的环境(如上面的符号链接),我们需要创建一个启动脚本。
在 Paperspace 中,一个名为 prerun.sh 的特殊脚本会在每次实例启动时自动运行。我们将把这个脚本放在 /storage 中,使其对所有实例生效。
以下是创建和设置 prerun.sh 脚本的方法:
- 为了能在 Jupyter 界面中编辑
/storage中的文件,我们可以在/notebooks目录下创建一个指向/storage的符号链接。ln -s /storage /notebooks/storage - 在 Jupyter 的文件浏览器中,进入
notebooks/storage,点击“新建” -> “文本文件”。 - 将文件重命名为
prerun.sh。 - 编辑
prerun.sh文件,内容如下。这个脚本会先清理可能存在的旧链接,然后为我们需要的目录创建符号链接。#!/bin/bash # 移除可能已存在的链接或目录 rm -rf ~/.local ~/.ssh # 创建指向持久化存储的符号链接 ln -s /storage/cfg/.local ~/.local ln -s /storage/cfg/.ssh ~/.ssh - 保存文件后,需要为其添加可执行权限。我们使用
chmod命令设置权限为755。
这里解释一下chmod 755 /storage/prerun.sh755:它是三个数字(7,5,5)的组合,分别代表文件所有者、所属用户组和其他用户的权限。每个数字是读(4)、写(2)、执行(1)权限值的和。因此,7=4+2+1表示所有者有全部权限;5=4+0+1表示组用户和其他用户有读和执行权限。
现在,当你下次创建新的 Paperspace 实例时,这个 prerun.sh 脚本就会自动运行,为你设置好 .local 和 .ssh 的符号链接。
配置 SSH 密钥 🔐
为了能够从 Paperspace 访问 GitHub 等外部服务,我们需要配置 SSH 密钥。其流程与处理 .local 目录类似。
以下是配置 SSH 密钥的步骤:
- 生成或上传你的 SSH 密钥对到
~/.ssh目录。 - 设置正确的文件权限至关重要,这是 SSH 安全协议的要求。
chmod 700 ~/.ssh # 目录仅所有者可读、写、执行 chmod 600 ~/.ssh/id_rsa # 私钥仅所有者可读、写 chmod 644 ~/.ssh/id_rsa.pub # 公钥所有者可读、写,其他人可读 - 同样,我们将整个
.ssh目录移动到持久化存储,并通过prerun.sh脚本创建符号链接(上一步脚本已包含此操作)。 - 使用以下命令测试 SSH 连接是否成功:
如果连接失败,通常是因为文件权限设置不正确。你可以添加ssh -T git@github.com-v(verbose)参数来获取详细的调试信息,例如ssh -v -T git@github.com。
测试与验证 ✅
完成所有配置后,最关键的一步是进行测试。
- 停止你当前的 Notebook 服务器。
- 重新启动一个新的 Notebook 服务器实例。在创建时,请确保:
- 选择 Fast.ai 镜像,因为它包含了我们所需的基础环境。
- 在“高级选项”中,可以删除“Workspace URL”的内容,以便从一个干净的环境开始。
- 启动后,打开新的实例,检查以下内容:
~/.local和~/.ssh是否已正确链接到/storage/cfg下的目录。- 能否成功导入之前安装的 Python 包。
- SSH 连接到 GitHub 是否正常。
如果一切顺利,说明你的自动化配置脚本工作正常,环境已经可以持久化并跨实例使用了。
总结 📝
本节课中我们一起学习了如何从零开始配置 Paperspace 环境。我们首先理解了 /storage 和 /notebooks 这两个核心持久化目录的区别。然后,我们探讨了如何使用 Pip 和 Conda 管理 Python 包,并将用户安装的包通过符号链接持久化到 /storage 中。接着,我们创建了自动化的 prerun.sh 启动脚本,确保每次启动新实例时环境都能自动配置好。最后,我们还配置了 SSH 密钥并进行了全面测试。


通过这一套流程,你建立了一个可重复、易维护的云端开发环境,为后续的机器学习项目打下了坚实的基础。如果在尝试过程中遇到任何问题,请随时在论坛提问。
005:Vim编辑器实战 🚀



在本节课中,我们将学习如何使用Vim编辑器。Vim是一个功能强大的文本编辑器,以其高效性和可定制性而闻名。虽然它最初可能看起来有些复杂,但掌握其基本操作将极大地提升你的工作效率。我们将从安装Vim开始,逐步学习其核心概念和实用技巧,包括如何移动、编辑、保存文件,以及如何利用其强大的组合命令和代码导航功能。

Vim的安装与启动


首先,我们需要在系统中安装Vim。安装方法因操作系统而异。

- 在Linux系统上,可以使用包管理器安装,例如:
sudo apt install vim。 - 在macOS系统上,可以通过Homebrew安装:
brew install vim。 - 在Windows的WSL(Windows Subsystem for Linux)环境中,安装方法与Linux相同。

安装完成后,在终端中输入 vim 命令即可启动Vim编辑器。
理解Vim的模式
启动Vim后,你可能会发现直接打字,文本并不会出现在屏幕上。这是因为Vim是一个模态编辑器,这意味着它有不同的操作模式。
默认情况下,Vim处于命令模式。在此模式下,按键用于执行命令(如移动光标、删除文本),而不是输入字符。这与Jupyter Notebook类似,在Notebook中,你需要进入一个单元格的编辑模式才能输入代码。
要进入插入模式(即开始输入文本),请按 i 键(代表“insert”)。此时,屏幕底部会显示“-- INSERT --”字样,表示你现在可以正常打字了。要退出插入模式并返回命令模式,请按 Esc 键。
基本移动与编辑


在命令模式下,你可以使用多种方式移动光标。
- 使用键盘的方向键是最直观的方式。
- Vim也提供了基于字母的移动键:
h(左)、j(下)、k(上)、l(右)。这些键位让你的手指无需离开主键盘区。
现在,让我们看看如何进行一些基本编辑操作。
- 删除一行:按两次
d键,即dd。 - 删除一个单词:按
dw(d代表删除,w代表移动到下一个单词开头)。 - 撤销操作:按
u键。 - 重做上一个操作:按
.(句点)键。
保存与退出文件

我们很少直接启动Vim而不指定文件。通常,我们会编辑一个特定的文件,例如:vim .zshrc。
编辑完成后,你需要保存更改并退出。这涉及到Vim的第三种模式:Ex模式。
按 :(冒号)键进入Ex模式,光标会移动到屏幕底部。在此你可以输入命令:
- 保存文件:输入
w(代表“write”),然后按回车。例如,:w myfile.txt会将内容保存到myfile.txt。 - 退出Vim:输入
q(代表“quit”),然后按回车。 - 保存并退出:可以将命令组合起来,输入
:wq,然后按回车。这是最常用的退出方式。 - 不保存强制退出:如果文件有未保存的更改,Vim会阻止你退出。此时可以输入
:q!来强制退出。
Vim的强大之处:组合命令
上一节我们介绍了基本的移动和编辑命令,本节中我们来看看Vim真正强大的功能:将命令、数字和动作组合起来。
在Vim中,许多命令可以接受一个数字前缀,表示重复该操作的次数。
- 例如,
5dd会删除5行。 10p会粘贴10次。

更重要的是,删除(d)、复制(y,称为“yank”)和更改(c,删除并进入插入模式)等命令后面可以跟一个动作,该命令会作用于从光标位置到动作结束的整个区域。

以下是几个核心组合示例:


d$:删除从光标到行尾的内容($代表行尾)。dG:删除从光标到文件末尾的内容(G代表文件末尾)。y}:复制从光标到下一个段落末尾的内容(}代表下一个空行)。cw:删除当前单词并进入插入模式以输入新单词(w代表下一个单词)。
这种组合性让你可以用极少的击键完成复杂的文本操作。
搜索、替换与Ex命令
除了单行命令,Vim的Ex模式提供了强大的批量处理能力。
- 搜索:在命令模式下按
/,然后输入要搜索的内容(支持正则表达式),按回车。按n跳转到下一个匹配项,按N跳转到上一个。- 例如,
/^#会搜索以#开头的行(^代表行首)。
- 例如,
- 搜索并作为动作:你可以将搜索用作动作。例如,
d/error会删除从光标到下一个“error”单词之间的所有内容。 - 全局命令:
:g命令可以在匹配特定模式的所有行上执行操作。- 例如,
:g/^#/d会删除所有以#开头的行。 :g/^$/d会删除所有空行。
- 例如,
- 替换命令:
:s用于搜索和替换。- 例如,
:%s/old/new/g会将整个文件(%代表所有行)中的“old”替换为“new”(g代表全局替换,即一行内所有匹配)。
- 例如,
多窗口与代码导航
对于更复杂的项目,Vim支持多窗口和强大的代码导航。
- 分割窗口:在Ex模式下,输入
:sp filename可以水平分割窗口并打开另一个文件。输入:vsp filename可以垂直分割。 - 在窗口间切换:按
Ctrl+w,然后按方向键(或h/j/k/l)在不同窗口间移动。 - 使用Ctags进行代码导航:这是一个高级功能,可以让你在代码库中快速跳转到函数或类的定义处。
- 首先,在项目根目录生成标签索引文件:
ctags -R .。 - 在Vim中,将光标移动到一个符号(如函数名)上,按
Ctrl+]跳转到其定义。 - 按
Ctrl+t跳转回之前的位置。 - 你也可以用
:tag function_name直接跳转到指定符号。
- 首先,在项目根目录生成标签索引文件:
学习建议与资源
面对Vim众多的命令,初学者可能会感到不知所措。关键在于循序渐进。
- 从核心开始:首先掌握如何进入插入模式(
i)、返回命令模式(Esc)、保存退出(:wq)以及用方向键移动。这足以让你开始使用Vim编辑简单的脚本。 - 每日学习一两个新命令:例如,今天学习
w(下一个单词)和b(上一个单词),明天学习dd和p。 - 使用教程和游戏:
- 在终端中运行
vimtutor,这是一个内置的交互式教程。 - 尝试浏览器游戏 “Vim Adventures”,在游戏中学习键位。
- 在终端中运行
- 实践出真知:强迫自己在一些任务中使用Vim,即使最初比用其他编辑器慢。这种“投资”会随着时间累积,最终让你的编辑效率产生质的飞跃。
- 保持简单:初期避免安装大量插件。Vim本身已非常强大,过多的定制反而会增加学习负担。你可以参考一些简洁的配置文件(如
~/.vimrc)来获得一些实用的默认设置。

本节课中我们一起学习了Vim编辑器的核心使用方法。我们从安装和基本模式开始,逐步探索了移动、编辑、保存文件的操作,并深入了解了Vim强大的组合命令、搜索替换以及多窗口和代码导航功能。记住,学习Vim是一个旅程,不要试图一次性掌握所有内容。从今天开始,尝试在下一个文本编辑任务中使用它,哪怕只使用最基础的几个命令,你也会慢慢体会到其高效和优雅之处。
006:Paperspace高级优化 🚀





在本节课中,我们将学习如何在Paperspace环境中进行高级优化,包括持久化安装软件、配置环境变量、管理存储空间以及使用Git进行版本控制。我们将通过一系列实践步骤,确保我们的开发环境在每次启动新实例时都能保持一致和高效。
概述
上一节我们介绍了在Paperspace上使用Vim和Ctags的基本方法。本节中,我们将深入探讨如何配置一个持久化、可重复使用的开发环境。我们将学习如何安装和管理软件包,设置环境变量,以及使用Git来管理我们的代码库。
持久化安装软件
为了确保我们安装的软件在Paperspace实例重启后依然可用,我们需要将它们安装到持久化存储中。默认情况下,/storage 和 /notebooks 目录是持久化的。
我们可以利用 prerun.sh 和 .bashrc.local 脚本,将持久化存储中的内容链接或复制到我们的主目录。
例如,使用 pip install --user 会将包安装到主目录下的 .local 子目录中。我们可以将这个目录移动到 /storage,然后创建一个符号链接指回主目录,这样所有用户安装的Python包都会在会话间持久保存。
使用Mamba安装二进制文件
对于像Ctags这样的非Python二进制文件,我们可以使用Mamba进行安装。默认情况下,Mamba会安装到系统目录,这些内容在实例重启后会丢失。
我们可以使用 -p (prefix) 标志,将软件安装到我们指定的目录,例如主目录下的一个自定义目录。
以下是安装Mamba和Ctags到持久化目录的步骤:
-
创建一个目录来存放Conda软件:
mkdir ~/conda -
使用
-p标志安装Mamba:conda install -p ~/conda mamba -
使用Mamba安装Ctags(指定conda-forge频道):
mamba install -c conda-forge universal-ctags
安装完成后,我们需要将安装目录的 bin 文件夹添加到系统的 PATH 环境变量中,以便在终端中直接运行这些命令。
配置环境变量
为了让系统能够找到我们自定义安装的软件,我们需要修改 PATH 环境变量。
我们可以通过编辑 ~/.bashrc.local 文件来实现(如果文件不存在则创建它):
export PATH="$HOME/conda/bin:$PATH"
然后,通过运行 source ~/.bashrc.local 使更改在当前shell生效,或者重新打开一个终端。
为了确保每次启动新实例时都能自动配置,我们可以将这个 .bashrc.local 文件也移动到 /storage 目录,并在 prerun.sh 脚本中创建指向它的符号链接。
管理存储空间
在免费或低配账户中,存储空间是有限的。我们需要定期检查和管理 /storage 目录的使用情况。
使用 du 命令可以查看目录的磁盘使用情况:
du -sh ~/conda
如果发现某些大型文件或目录不再需要,可以将其删除以释放空间。例如,如果Python库占用了大量空间,我们可以考虑移除不必要的包。
使用Git进行版本控制
为了有效地管理我们的代码,特别是像《Fastbook》这样的教程代码,我们应该创建自己的GitHub仓库分支(Fork)。这样我们可以自由地修改代码,并轻松地将更改同步回原始仓库或保存自己的版本。
以下是操作步骤:
- 在GitHub上找到原始仓库(例如
fastai/fastbook),并点击“Fork”按钮创建你自己的副本。 - 在Paperspace的终端中,克隆你自己的分支:
git clone https://github.com/你的用户名/fastbook.git - 进入仓库目录,进行修改并提交:
cd fastbook # 进行一些修改... git add -A git commit -m '描述你的更改' - 将更改推送到你的GitHub分支:
git push
如果你想将改进贡献回原始项目,可以在GitHub上你的分支页面发起“Pull Request”。
理解代码导入机制
在Python中,from fastai.vision.all import * 这样的语句是如何工作的?
- 它将
fastai视为一个目录。 - 在该目录下寻找
vision子目录。 - 在
vision目录中寻找all.py文件。 - 导入
all.py文件中定义或导入的所有符号(如果该文件定义了__all__列表,则只导入列表中的符号;否则导入所有)。
要查看某个导入对象的来源,可以在Jupyter Notebook中在其后输入 ??,例如 DataBlock??,这将显示其源代码。
实验与探索
理解代码的最佳方式之一是进行实验。例如,当你遇到一个不熟悉的函数或类时:
- 使用
?查看其文档字符串:ImageDataLoaders? - 使用
??查看其源代码:ImageDataLoaders?? - 在独立的单元格中逐行运行代码,并检查中间变量的值和类型。
- 尝试修改参数,观察输出如何变化。
例如,fastai 库中的 L 对象是一个增强版的Python列表,它提供了类似NumPy数组的高级索引功能,并且打印输出更友好(会显示元素数量并用 ... 省略中间内容)。
你可以通过创建普通的Python列表和 L 对象来比较它们的行为:
# 普通列表
list_a = [1, 2, 3]
# L对象
from fastcore.foundation import L
list_l = L(1, 2, 3)
# 比较它们的打印输出和功能
print(list_a)
print(list_l)
总结


本节课中我们一起学习了如何在Paperspace上构建一个强大且持久的开发环境。我们涵盖了从使用Mamba持久化安装软件、配置环境变量、管理存储空间,到使用Git进行有效的版本控制。我们还深入探讨了Python的模块导入机制,并强调了通过实验和查阅文档来深入理解代码的重要性。掌握这些技能将为你后续的高效学习和开发打下坚实的基础。
007:Kaggle竞赛实战配置 🚀

在本节课中,我们将学习如何配置环境并开始参与一个Kaggle图像分类竞赛。我们将从安装必要的工具开始,下载竞赛数据,并运行一个基础的深度学习模型。整个过程将帮助你熟悉在Kaggle或类似平台上进行机器学习项目的基本工作流。
概述
我们将跟随一个社区成员分享的Kaggle竞赛(水稻病害分类)作为实践案例。课程将涵盖从环境搭建到初步模型训练的全过程,包括安装Kaggle命令行工具、认证、下载数据、数据探索以及使用fastai库构建图像分类器。
环境配置与数据下载
首先,我们需要在计算环境中安装Kaggle命令行工具并配置认证,以便下载竞赛数据。
安装Kaggle命令行工具
Kaggle不仅是一个Python库,还提供了一个命令行工具。我们使用pip进行安装。
pip install --user kaggle
安装后,命令行工具通常位于用户主目录的.local/bin路径下。如果该路径不在系统的环境变量PATH中,我们需要手动添加。

配置系统路径

编辑你的shell配置文件(例如~/.bashrc或~/.bash_profile),在文件末尾添加以下行:


export PATH=$HOME/.local/bin:$PATH
然后,执行source命令使更改生效,或重新打开终端。

source ~/.bashrc




现在,你应该可以在终端中直接运行kaggle命令了。


设置Kaggle API认证

要使用Kaggle命令行工具下载数据,需要进行API认证。



- 登录Kaggle网站,进入你的账户设置页面。
- 点击“Create New API Token”。这将下载一个名为
kaggle.json的文件到你的电脑。 - 在你的计算环境(例如Paperspace或本地机器)中,创建一个名为
.kaggle的目录,并将kaggle.json文件移动到此目录。

mkdir ~/.kaggle
mv /path/to/downloaded/kaggle.json ~/.kaggle/
- 为确保安全,修改该文件的权限。
chmod 600 ~/.kaggle/kaggle.json
下载竞赛数据
找到你想参与的Kaggle竞赛页面。在“Data”选项卡下,通常会提供使用命令行下载数据的指令。例如:
kaggle competitions download -c plant-pathology-2020-fgvc7

运行此命令前,请确保你已点击“Join Competition”加入该竞赛。下载的数据通常是一个压缩文件,使用unzip命令解压。
unzip -q plant-pathology-2020-fgvc7.zip


关于数据存储位置的提示:在云平台(如Paperspace)上,将数据存储在持久化目录(如/storage)或临时目录(如/home)各有利弊。持久化目录方便但可能I/O速度较慢;临时目录速度快但会话结束后数据会丢失。你可以编写一个脚本来自动化每次启动时的数据下载和解压步骤。


数据探索与准备
上一节我们完成了环境和数据的准备工作,本节中我们来看看数据的具体内容,并为模型训练做准备。
解压后,查看数据目录结构。
ls -la
通常你会看到train.csv、sample_submission.csv以及train_images和test_images文件夹。我们可以快速查看文件数量和样本。
# 查看训练图像数量
ls train_images | wc -l
# 查看某个类别下的图像数量
ls train_images/bacterial_leaf_blight | wc -l
# 查看CSV文件的前几行
head train.csv
在Jupyter Notebook中,我们可以更直观地探索数据。
from fastai.vision.all import *
import pandas as pd
# 设置数据路径
path = Path(‘./plant-pathology-2020-fgvc7’)
# 读取训练标签CSV
df = pd.read_csv(path/‘train.csv’)
print(df.shape)
df.head()
使用PIL库查看一张图片。
from PIL import Image
img_path = path/‘train_images’/df.iloc[0][‘image_id’]
img = Image.open(img_path)
print(img.size)
img
使用fastai的get_image_files函数可以递归获取所有图像文件的路径列表。
files = get_image_files(path/‘train_images’)
len(files)
构建与训练模型
经过数据探索,我们对数据集有了基本了解。现在,我们使用fastai库快速构建一个图像分类模型。
fastai提供了便捷的API来创建数据加载器(DataLoaders)。由于我们的数据已经按类别存放在不同的文件夹中,可以直接使用ImageDataLoaders.from_folder方法。
# 定义训练和验证路径
train_path = path/‘train_images’
# 使用20%的数据作为验证集
dls = ImageDataLoaders.from_folder(train_path, valid_pct=0.2, seed=42,
item_tfms=Resize(224))
# 显示一个批次的数据以确认
dls.show_batch()
接下来,我们创建一个卷积神经网络学习器(Learner),并使用预训练的ResNet架构进行迁移学习。
# 使用resnet18架构,并加载预训练权重
learn = vision_learner(dls, resnet18, metrics=error_rate)
# 使用1周期策略进行微调
learn.fine_tune(1)
训练开始后,你可以使用nvidia-smi命令(如果使用GPU)来监控GPU的使用情况,确保计算资源被有效利用。
# 动态监控GPU使用率(每1秒刷新)
nvidia-smi dmon
关键指标是sm(流多处理器)利用率,通常保持在50%以上表明数据读取和预处理没有成为瓶颈。如果过低,可能需要考虑将数据移至更快的存储、预先调整图像大小或减少数据增强的复杂度。
总结与后续步骤
本节课中我们一起学习了参与Kaggle竞赛的完整入门流程。我们从安装和配置Kaggle命令行工具开始,下载了特定竞赛的数据集,并对数据进行了初步探索。最后,我们使用fastai库构建并训练了一个基础的图像分类模型,为水稻病害图片进行分类。
这只是一个起点。在后续的学习中,你可以尝试:
- 将模型训练更多轮次以提高精度。
- 尝试不同的网络架构(如resnet34, resnet50)。
- 进行更细致的数据增强。
- 使用学习率查找器来寻找最佳学习率。
- 将训练好的模型生成预测文件,并提交到Kaggle竞赛中查看初步排名。


通过重复这些步骤或在其他数据集上实践,你将能熟练掌握机器学习项目从环境配置到模型迭代的全过程。
008:Kaggle竞赛本地实战 🚀

在本节课中,我们将学习如何在本地环境中准备和参与Kaggle竞赛。我们将涵盖从设置环境、下载数据、训练模型,到生成预测并提交结果的全过程。通过实际操作,你将掌握在本地高效进行机器学习项目的基本工作流。

环境设置与数据准备

上一节我们介绍了课程目标,本节中我们来看看如何设置本地环境并获取竞赛数据。




首先,我们需要安装Kaggle命令行工具并配置身份验证。

pip install kaggle


接着,将你的Kaggle API令牌文件(kaggle.json)移动到正确目录。

mkdir -p ~/.kaggle
cp kaggle.json ~/.kaggle/
现在,我们可以从Kaggle竞赛页面下载所需的数据集。
kaggle competitions download -c <竞赛名称>
下载完成后,通常是一个压缩文件,我们需要解压它。
unzip -q <下载的文件名>.zip
数据探索与加载
数据准备就绪后,下一步是加载并探索数据,为模型训练做准备。
我们从FastAI库中导入必要的模块,并查看数据路径下的文件。
from fastai.vision.all import *
path = Path('.')
image_files = get_image_files(path/'train_images')
我们可以查看一张图片及其尺寸,以了解数据的基本情况。
img = PILImage.create(image_files[0])
print(img.size)
为了高效地获取所有图片的尺寸,我们可以使用并行处理。以下是两种方法:
- 串行处理(较慢):使用列表推导式逐一处理。
- 并行处理(较快):使用
fastcore.parallel模块加速。
# 串行处理示例
sizes = [PILImage.create(f).size for f in image_files[:100]]
# 并行处理示例
from fastcore.parallel import *
def get_size(f): return PILImage.create(f).size
sizes = parallel(get_size, image_files[:100], n_workers=4)
构建数据加载器与模型
在了解了数据之后,我们需要将其转换为模型可以训练的格式,并选择一个合适的模型架构。
我们使用FastAI的ImageDataLoaders来创建数据加载器,并指定验证集比例和图像尺寸变换。
dls = ImageDataLoaders.from_folder(path/'train_images', valid_pct=0.2, item_tfms=Resize(224))
对于模型,我们不再使用基础的ResNet34,而是尝试性能更好的ConvNeXt系列模型。这需要安装timm库。
pip install timm>=0.6.2dev
然后,我们使用vision_learner创建学习器,并指定使用混合精度训练(fp16)以加速GPU计算。
import timm
learn = vision_learner(dls, 'convnext_small_in22k', metrics=error_rate).to_fp16()
模型训练与学习率
模型构建完成后,我们开始训练。FastAI的fine_tune方法会自动进行预训练模型的微调。
learn.fine_tune(2)
在训练过程中,学习率调度策略(如fit_one_cycle)非常重要。它会在训练初期使用较低的学习率,然后逐渐增加,最后再减小,以帮助模型更稳定、更快速地收敛。你可以使用learn.lr_find()来可视化并选择一个合适的学习率范围。
生成预测与提交结果
模型训练好后,我们需要对测试集进行预测,并按照Kaggle要求的格式提交结果。
首先,为测试集创建数据加载器。
test_files = get_image_files(path/'test_images').sorted()
test_dl = dls.test_dl(test_files)
然后,使用模型获取预测结果。get_preds方法返回预测概率、目标类别(测试集为None)和类别索引。
preds, _, idxs = learn.get_preds(dl=test_dl, with_decoded=True)
我们需要将预测的类别索引映射回实际的类别名称(即dls.vocab)。
# 方法一:使用映射字典(高效)
mapping = dict(enumerate(dls.vocab))
results = pd.Series(idxs).map(mapping)
# 方法二:使用lambda函数
results = pd.Series(idxs).map(lambda i: dls.vocab[i])
最后,将结果保存为CSV文件,并使用Kaggle CLI提交。

submission = pd.DataFrame({'id': test_files.map(lambda x: x.stem), 'label': results})
submission.to_csv('submission.csv', index=False)
kaggle competitions submit -c <竞赛名称> -f submission.csv -m "提交信息"


常见问题与调试

在实践过程中,你可能会遇到一些问题。本节汇总了一些常见情况及其解决方法。

以下是几个关键问题的排查步骤:

NameError: name 'timm' is not defined:这表示Python环境中没有导入timm库。请确保在代码中执行了import timm。如果是在安装timm后首次导入,可能需要重启Jupyter内核。- 提交结果排名异常低:这通常是因为测试文件的顺序与Kaggle期望的顺序不一致。确保在创建
test_dl之前,对test_files进行排序(例如使用.sorted())。 - 并行处理未加速:如果并行处理没有明显提速,可能是因为任务瓶颈在于磁盘I/O(读取图片)而非CPU计算。使用SSD硬盘会更有帮助。


本节课中我们一起学习了在本地进行Kaggle竞赛的完整流程:从环境配置、数据下载与探索,到使用FastAI和timm库构建并训练先进的图像分类模型(如ConvNeXt),最后生成预测并提交结果。掌握这个工作流将使你能够更灵活、更高效地在本地开展机器学习项目。
009:Kaggle竞赛进阶优化 🚀

在本节课中,我们将学习如何通过数据增强、模型选择、学习率调整和测试时增强等技术,来优化一个Kaggle图像分类竞赛的模型性能。我们将从改进一个基础模型开始,逐步应用这些技巧,并最终提交一个更好的结果。
概述
上一节我们建立了一个基础的图像分类模型。本节中,我们将通过一系列优化策略来提升其性能,目标是超越排行榜上的基准分数。我们将重点关注数据增强、更优的模型架构、学习率调整以及模型集成等方法。
安装与配置优化
在开始优化模型之前,我们需要确保开发环境配置正确,特别是第三方库的安装。
以下是配置timm库持久化安装的步骤:
- 创建一个别名,以便将库安装到用户目录,该目录已链接到持久化存储。
echo "alias p='pip install --upgrade --user'" >> ~/.bashrc source ~/.bashrc - 使用新别名安装
timm库的预发布版本,以获取最新功能。p timm>=0.6.2.dev0 - 验证安装,并确保
.gitconfig等配置文件已正确链接到持久化存储,避免环境重置时丢失。
Vim编辑器高效技巧
在编写和修改代码时,掌握一些编辑器技巧可以极大提升效率。这里介绍几个实用的Vim操作。
以下是一些提高编码速度的Vim移动和编辑命令:
f{字符}:在当前行向前搜索指定字符并跳转。例如,f"跳转到下一个双引号。F{字符}:在当前行向后搜索指定字符并跳转。%:在配对的括号、花括号或方括号之间跳转。d%:删除从光标位置到配对符号之间的内容。di(:删除当前()内的所有内容(不包括括号本身)。ci(用于修改内容。Ctrl-o和Ctrl-i:在跳转历史中向后和向前移动,类似于浏览器的后退和前进。
模型优化策略
现在,我们回到核心任务:提升Kaggle竞赛模型的性能。我们将从分析现有模型的不足开始,并逐一实施改进措施。
1. 数据增强
为了防止模型在训练多个周期后过拟合,我们需要让模型每次看到略有不同的图像。这可以通过数据增强实现。
aug_transforms函数提供了多种增强方式:
tfms = aug_transforms(mult=1.0, do_flip=True, flip_vert=False, max_rotate=10.0, min_zoom=1.0, max_zoom=1.1, max_lighting=0.2, max_warp=0.2, p_affine=0.75, p_lighting=0.75, xtra_tfms=None, size=None, mode='bilinear', pad_mode='reflection', align_corners=True, batch=False, min_scale=1.0)
关键参数包括随机翻转、旋转、缩放、亮度调整和透视扭曲。在创建DataLoaders时传入这些变换,模型在训练时就会看到增强后的图像。
2. 选择更优的预训练模型
除了常用的ResNet,timm库提供了更多先进的模型。对于本任务,我们选择在ImageNet-22k数据集上预训练的ConvNeXt模型。ImageNet-22k包含2.2万个类别,其预训练权重可能包含与“水稻病害”更相关的特征。
arch = ‘convnext_small_in22k’
3. 调整学习率
FastAI默认的学习率偏保守。使用学习率查找器(lr_find)可以帮助我们找到更优的值。通常,我们可以尝试比建议值稍高的学习率,并结合单周期策略(1cycle policy)进行训练,这有助于模型跳出局部最优。
learn.fine_tune(12, base_lr=0.01)
4. 保存模型
训练完成后,需要保存模型以备后续使用或提交。learn.export()会保存整个Learner对象(包括模型、数据转换信息等),这是推荐的方法。
learn.export(Path(‘convnext_small_squish_12ep.pkl’))
评估与提交优化
模型训练好后,我们需要在验证集上评估其性能,并生成测试集的预测结果用于提交。
1. 测试时增强
数据增强通常只用于训练。但在预测时,我们也可以对验证集或测试集图像进行多次增强,然后对预测结果取平均或最大值,这被称为测试时增强,能进一步提升模型鲁棒性。

preds, targs = learn.tta(dl=learn.dls.valid, n=4, beta=0.25, use_max=False)
error_rate(preds, targs)



2. 生成提交文件
使用优化后的模型和TTA生成测试集的预测,并转换为竞赛要求的提交格式。
# 获取测试集预测(使用TTA)
test_dl = learn.dls.test_dl(test_df)
preds, _ = learn.tta(dl=test_dl)
# 取概率最大的类别作为预测结果
pred_idxs = preds.argmax(dim=1)
# 将索引映射回类别标签
vocab = learn.dls.vocab
mapping = dict(enumerate(vocab))
sub_df[‘label’] = [mapping[i] for i in pred_idxs]
# 保存提交文件
sub_df.to_csv(‘submission.csv’, index=False)
进阶探索:模型集成
为了追求极致的性能,可以尝试集成多个不同配置训练出的模型。集成模型的预测结果往往比单一模型更稳定、更准确。


以下是一些可以尝试的模型变体,用于构建集成:
- 变体A:使用
size=460和squish模式训练的模型。 - 变体B:使用
size=(336, 252)(保持原图4:3宽高比)且不使用squish的模型。 - 变体C:使用
size=(320, 240)(原图尺寸的一半)并降低仿射变换概率(p_affine=0.5)的模型。
分别训练这些模型后,可以对它们的预测概率进行平均,作为最终的集成预测。
# 假设 preds_a, preds_b, preds_c 是三个模型的预测概率
final_preds = (preds_a + preds_b + preds_c) / 3
submission_idxs = final_preds.argmax(dim=1)
总结


本节课中我们一起学习了优化Kaggle图像分类竞赛模型的完整流程。我们从配置环境开始,应用了数据增强来防止过拟合,选择了更强大的ConvNeXt预训练模型,并调整了学习率以加速训练。在评估阶段,我们引入了测试时增强来提升预测准确性。最后,我们探讨了通过集成多个模型来进一步提升性能的策略。通过系统性地应用这些技巧,我们成功地将模型排名提升至前10%,展示了系统化优化方法的强大效力。
010:Kaggle图像识别竞赛进阶 🚀

在本节课中,我们将学习如何为Kaggle图像识别竞赛进行进阶优化。我们将探讨如何系统地评估和选择最适合微调的预训练模型,以及如何通过实验设计高效地提升模型性能。

概述
在图像识别任务中,微调预训练模型是一种常见且有效的方法。然而,模型的选择、数据预处理和训练策略都会显著影响最终结果。本节将分享一个系统性的实验流程,用于找出在特定数据集上表现最佳的模型。
上一节我们介绍了Kaggle竞赛的基础知识,本节中我们来看看如何通过系统实验来优化模型性能。
模型微调的关键维度
微调预训练模型的效果主要受两个关键维度影响:


- 数据相似度:你的数据集与预训练模型所用数据集(如ImageNet)的相似程度。
- 数据规模:你用于微调的数据集大小。
以下是这两个维度的具体影响:
- 高相似度、大数据集:例如“Pets”数据集,其图像与ImageNet相似。此时,关键因素是微调过程能否有效保留预训练权重。你可以利用大型、精确的模型,因为它们已经学会了与你任务几乎相同的功能。
- 低相似度、小数据集:例如“Planet”卫星图像数据集,与ImageNet差异很大。此时,预训练模型的许多权重可能无用,因为它们学习的是特定特征(如纹理、眼睛、毛发)。同时,数据量小也限制了模型学习利用大量参数的能力。
系统性模型评估实验
为了找出最佳微调模型,我们设计了一个实验,在两个代表性数据集(Pets和Planet)上测试了多种模型架构。
以下是实验的核心步骤:
- 编写自动化训练脚本:脚本核心仅包含几行标准代码,用于创建数据加载器、初始化学习器并进行训练。
# 示例代码结构 dls = ImageDataLoaders.from_folder(...) learn = vision_learner(dls, arch, metrics=error_rate) learn.fine_tune(epochs) - 参数化与自动化搜索:使用工具(如Weights & Biases)对关键超参数(如学习率、模型架构、图像缩放方法)进行自动化扫描。
- 结果收集与分析:通过API收集所有实验的运行结果(如错误率、训练时间、GPU内存使用量),并整理到CSV文件中进行后续分析。
实验结果与洞察
通过对结果数据的分析,我们可以得出一些通用性洞察。
以下是针对Pets数据集(高相似度)的分析结果:
- 最佳模型家族:ConvNeXt、ResNet表现突出。
- 权衡取舍:例如,
VIT_base模型错误率很低,但训练速度较慢;ConvNeXt_tiny则在速度和准确率之间取得了良好平衡。 - 实用选择:对于需要快速迭代的场景,可以筛选出在错误率和训练速度上都优于中位数的模型。其中
ConvNeXt_tiny的综合表现最佳。
以下是针对Planet数据集(低相似度)的分析结果:
- 最佳模型家族:基于Transformer的模型(如VIT、Swin)占据了前列。这表明这类模型特别擅长快速学习未见过的数据特征。
- 模型大小与数据量的关系:大型、复杂的模型在此类数据上未必表现更好,因为数据量不足以有效利用其大量参数。
- 推荐方向:对于与ImageNet差异大的任务,可以优先尝试VIT等Transformer架构的中小型模型。
在具体竞赛中应用洞察
我们将上述洞察应用于“Plant Pathology”竞赛中,进行快速实验验证。
以下是尝试的不同预处理和模型组合:
- 基础预处理:使用
ConvNeXt_small模型,尝试不同的图像缩放方法(挤压、裁剪、矩形填充)。 - 尝试优秀模型:根据之前的结论,尝试了
VIT_small和SwinV2模型。其中SwinV2即使在较小图像尺寸(192px)下也表现优异。 - 处理内存限制:当模型过大导致GPU内存不足时,使用梯度累积技术来模拟更大的批次大小,而无需更换硬件。
# 梯度累积示例 learn = vision_learner(dls, arch, metrics=error_rate, cbs=GradientAccumulation(n_acc=2)) - 模型集成:训练多个表现良好的模型,并对它们的预测概率进行平均,这通常比简单的投票法能保留更多信息,获得更好的集成效果。
总结


本节课中我们一起学习了Kaggle图像识别竞赛的进阶优化策略。我们了解到通过系统性的实验设计,可以高效地评估不同预训练模型在特定任务上的表现,并得出具有指导意义的洞察。关键步骤包括:理解数据特性、自动化超参数搜索、分析实验结果以权衡速度与精度、以及使用如梯度累积和概率平均集成等实用技巧来提升最终成绩。这种方法使得我们能够在有限的计算资源下,科学地进行模型选择和优化。
011:Paddy竞赛进阶技巧 🚀

在本节课中,我们将学习如何系统地进行模型实验、跟踪结果并优化Kaggle竞赛提交。我们将涵盖从基础实验设置到高级集成策略的完整流程。
概述


上一节我们介绍了Kaggle竞赛的基础知识。本节中,我们将深入探讨如何通过系统化的实验、结果分析和模型集成来提升竞赛成绩。我们将学习使用工具跟踪实验、分析超参数影响,并最终构建一个强大的集成模型。




实验设置与跟踪



为了高效地进行模型实验,我们需要一个系统来跟踪不同的配置和结果。以下是设置实验跟踪的关键步骤。


使用脚本进行批量训练

将笔记本中的训练代码重构为独立的Python脚本,便于批量运行和参数化。例如,一个基础的训练脚本可能如下所示:


import argparse
from fastai.vision.all import *

def train(data_path, model_name, lr=0.008):
# 1. 加载数据
dls = ImageDataLoaders.from_folder(...)
# 2. 创建学习器
learn = vision_learner(dls, model_name, metrics=error_rate)
# 3. 训练模型
learn.fine_tune(epochs, lr)
# 4. 记录结果(例如,使用wandb)
return learn

利用Weights & Biases进行实验管理


Weights & Biases(wandb)是一个强大的实验跟踪平台。它可以自动记录超参数、指标、系统资源使用情况,并与Git提交关联。
- 创建扫描(Sweep):通过一个YAML配置文件定义要探索的超参数网格。
- 并行运行:在多个GPU上同时启动多个实验运行。
- 结果分析:通过API获取所有运行结果,并转换为Pandas DataFrame进行灵活分析。


import wandb
api = wandb.Api()
sweep = api.sweep("project/sweep_id")
runs_df = pd.DataFrame([r.config for r in sweep.runs])


超参数探索策略
在探索超参数时,采用系统化的策略比盲目的网格搜索更有效。
假设驱动的实验
与其运行庞大的超参数网格搜索,不如基于假设进行测试。例如:
- 假设:不同架构的最佳学习率是相似的。
- 测试方法:用几种代表性架构(如CNN、Transformer)在固定数据集上测试少量学习率。
- 结论:如果发现0.008对大多数模型都表现良好,后续实验就可以固定此值,从而减少搜索维度。



关键发现
通过系统实验,我们常能发现一些通用规律:
- 学习率:对于计算机视觉任务,一个较优的学习率(如0.008)通常在多种架构和数据集上表现稳定。
- 图像调整方法:对于大多数模型,“裁剪”(crop)方法通常优于“挤压”(squish)。但对于ViT等固定输入尺寸的模型,“填充”(pad)可能是更好的选择。
- 模型选择:在Paddy竞赛中,Vision Transformer(ViT)模型明显优于其他卷积网络模型。

模型集成与提交优化
单一模型的表现存在上限,集成多个模型可以显著提升预测的鲁棒性和准确性。


集成方法

- 选择基模型:根据实验,挑选出几个表现最佳且差异化的模型(例如,最好的ViT模型和最好的ConvNeXt模型)。
- 生成预测:用每个模型对测试集进行预测(通常使用测试时增强TTA)。
- 组合预测:对多个模型的预测概率进行平均(或加权平均)。

# 假设 preds_vit, preds_convnext 是形状为 [n_samples, n_classes] 的概率矩阵
ensemble_preds = (preds_vit + preds_convnext) / 2
final_predictions = ensemble_preds.argmax(dim=1) # 获取最终类别

提交策略


- 记录关联:在Kaggle提交描述中,明确记录使用的笔记本名称和模型组合,便于回溯。
- 渐进优化:先提交单个最佳模型的结果作为基线,然后提交集成模型的结果,以量化集成带来的提升。

实用工具与技巧
以下是一些能提升工作效率的Linux和开发技巧。
进程与作业管理
在终端中管理长时间运行的任务:
Ctrl+Z:暂停当前前台任务。jobs:查看当前会话中的作业列表。bg %1:将1号作业置于后台继续运行。fg %1:将1号作业调回前台。command &:直接在后台运行命令。
查找进程
查找特定的运行进程(如Jupyter):
ps aux | grep jupyter
总结


本节课中我们一起学习了系统化进行深度学习实验的完整流程。我们从设置可跟踪的实验脚本开始,探讨了高效的超参数探索策略,并实践了通过模型集成来优化竞赛成绩的方法。关键要点在于:通过假设驱动的小规模实验发现通用规律,利用工具自动化管理实验,并最终通过集成强大且多样的模型来获得最佳表现。记住,成为优秀实践者的路径在于深入理解每个决策背后的原因,而不仅仅是运行大量实验。
012:多任务学习与模型修改




在本节课中,我们将学习如何修改一个预训练的计算机视觉模型,使其能够同时预测两个不同的目标:水稻的病害类型和水稻的品种。这是一个多任务学习的例子,我们将深入模型内部,手动调整其结构。
模型性能概览与实验策略
上一节我们介绍了不同模型架构在Kaggle竞赛中的表现。本节中我们来看看如何为特定任务选择和修改模型。
在实验中,我尝试了多种模型,并在224x224像素的图像上进行了测试。以下是一些关键发现:
- 模型多样性:在性能排名前15的模型中,包含了ResNet、Vision Transformer (ViT)、ConvNeXt等多种架构,这表明不同的方法都能取得不错的效果。
- 速度与内存:一些ViT模型(如
VIT small patch 32)在保持较快速度的同时,内存占用也很低,这令人惊喜。 - 图像尺寸的影响:较大的ViT模型通常需要更大的输入图像尺寸(如384x384),因此在224x224的测试中,它们的性能被截断了。在实际应用中,我们通常从较小的图像开始迭代,以加快实验速度,然后在最终部署时根据精度和延迟的权衡考虑是否使用更大的图像。
对于生产环境,我的策略与Kaggle竞赛类似:首先在小尺寸图像和数据的子集上,用快速的小模型进行实验,以确定最佳的数据预处理流程和模型架构。然后,再根据实际的延迟、成本和精度要求进行规模化调整。
多任务学习:一个有趣的思路
本次竞赛的数据集中,除了病害标签,还包含了水稻的品种信息。一个有趣且可能违反直觉的思路是:让模型同时学习预测病害和品种,可能会提升其在主要任务(病害分类)上的性能。
其原理是,为模型提供更多的学习信号(即需要识别的特征),可能有助于它更好地理解图像内容。例如,如果模型学会了区分不同品种的水稻,它或许能利用这些信息来识别不同品种所特有的病害模式。
接下来,我们将尝试实现这个多任务学习模型。这将涉及到比以往更深入的模型修改操作。
深入模型内部:理解结构与修改
为了构建多任务模型,我们需要深入理解并修改现有的模型结构。一个典型的FastAI视觉学习器(Learner)包含两个主要部分:数据加载器(dls)和模型(model)。
我们的模型通常分为两部分:
- 主体(Body):负责从像素中提取特征的卷积神经网络部分。
- 头部(Head):接收主体提取的特征,并输出最终预测的全连接层部分。
我们可以使用 model_summary 来可视化模型结构和数据流。目前,我们的模型头部末尾是一个线性层(Linear),其公式为:
output = input * weight^T + bias
在我们的例子中,它将512维的输入特征转换为10维的输出(对应10种病害)。
为了实现多任务预测,我们需要修改这个头部。基本思路是:移除最终的单一线性层,然后创建两个新的线性层分支,分别用于预测病害和品种。
动手修改模型架构
我们将通过创建一个新的PyTorch模块(Module)来封装这个逻辑。以下是修改步骤的概述:
- 移除原头部最后一层:我们可以直接操作模型的
head部分,它是一个Sequential容器,支持类似Python列表的删除操作(del head[-1])。 - 创建新的多任务模块类:
- 在构造函数(
__init__)中,我们保存原始的主体模型,并定义两个新的线性层(nn.Linear),例如self.lin_disease和self.lin_variety。 - 在
forward方法中,我们首先将输入数据通过主体模型得到特征,然后将这些特征分别输入两个线性层,得到两个预测结果,最后将它们作为元组返回。
- 在构造函数(
class DiseaseAndVarietyClassifier(nn.Module):
def __init__(self, body_model, n_disease=10, n_variety=5):
super().__init__()
self.body = body_model
self.lin_disease = nn.Linear(512, n_disease) # 假设主体输出512维
self.lin_variety = nn.Linear(512, n_variety)
def forward(self, x):
features = self.body(x)
pred_disease = self.lin_disease(features)
pred_variety = self.lin_variety(features)
return pred_disease, pred_variety
- 组装新模型并创建学习器:用我们新建的类替换原来的模型,并创建一个新的学习器。
在修改过程中,我们可能会遇到一些技术问题,例如混合精度训练(FP16)带来的数据类型错误,或者模型深拷贝(copy)导致的状态异常。解决方法是简化步骤,逐步验证,必要时重建数据加载器或学习器。
调整损失函数与评估指标
修改模型后,我们的输出从单个张量变成了一个包含两个张量的元组。这会导致原来的损失函数和评估指标无法直接工作,因为它们期望的是单一输出。
我们需要进行以下调整:
-
自定义损失函数:我们需要创建一个新的损失函数,它能够处理多任务输出。最初,我们可以先让损失只计算主要任务(病害分类)的部分,以确保流程能跑通。
def custom_loss(predictions, targets): pred_disease, pred_variety = predictions # 解包预测值 # 暂时只计算病害损失 loss_disease = F.cross_entropy(pred_disease, targets) return loss_disease然后,将这个损失函数设置给学习器:
learn.loss_func = custom_loss。 -
调整评估指标:同样,我们需要修改或暂时移除原有的评估指标(如错误率
error_rate),因为它们也依赖于单一输出。我们可以先创建一个只针对病害预测的错误率函数。def dtc_error(predictions, targets): pred_disease, _ = predictions return error_rate(pred_disease, targets) learn.metrics = [dtc_error] -
修改数据目标:目前,我们的数据加载器只加载了病害标签。要真正进行多任务学习,我们还需要修改数据管道,使其能同时加载病害和品种标签作为目标。
完成这些步骤后,我们的新学习器应该能够进行基本的训练循环了,例如使用 learn.lr_find() 来寻找合适的学习率。
总结与展望
本节课中我们一起学习了计算机视觉模型微调的一个进阶主题:多任务学习。我们从分析模型性能的策略出发,提出了一个同时预测病害和品种以提升性能的思路。然后,我们深入模型内部,详细讲解了如何通过修改模型头部、自定义损失函数和评估指标来实现一个多任务学习模型。


虽然这个过程比使用标准流程更复杂,但它极大地加深了我们对PyTorch模型结构和FastAI学习器工作方式的理解。这也揭示了现有库在支持多任务学习方面可能存在的简化空间。在接下来的课程中,我们将继续完善这个多任务模型,并尝试将其应用到竞赛数据中,观察其实际效果。
013:fastai DataBlock深度解析 🧩



在本节课中,我们将深入学习fastai库中一个非常强大且灵活的组件——DataBlock。我们将通过一个具体的案例,即同时预测水稻图像中的疾病和品种,来详细解析DataBlock的各个组成部分和工作原理。通过本教程,你将学会如何构建处理多输出任务的复杂数据管道。


概述:从简单到复杂的数据管道
上一节我们介绍了使用高级API ImageDataLoaders 进行图像分类。本节中,我们将探讨当任务变得复杂(例如需要预测多个目标)时,如何利用更底层的 DataBlock API 来构建自定义的数据加载流程。
DataBlock 是fastai中用于定义数据流的核心抽象。它允许你清晰地指定数据的来源、类型、如何分割、以及应用哪些变换。
第一步:使用DataBlock复现基础流程
首先,为了理解DataBlock,我们用它来复现之前用ImageDataLoaders.from_folder完成的单标签(疾病)分类任务。
以下是构建该DataBlock的关键步骤:
- 定义数据块类型:使用
ImageBlock和CategoryBlock分别处理图像和标签。 - 指定获取标签的函数:
get_y=parent_label表示从图像父文件夹名获取标签。 - 定义数据分割方式:
splitter=RandomSplitter()进行随机训练集/验证集分割。 - 应用数据变换:
item_tfms和batch_tfms用于图像预处理和数据增强。
dblock = DataBlock(
blocks=(ImageBlock, CategoryBlock), # 输入:图像, 输出:类别
get_items=get_image_files, # 获取所有图像文件路径
get_y=parent_label, # 从文件夹名获取标签
splitter=RandomSplitter(valid_pct=0.2, seed=42),
item_tfms=Resize(224), # 将每张图像缩放到统一尺寸
batch_tfms=aug_transforms(size=128) # 批处理时进行数据增强(测试时用128x128加速)
)
创建DataBlock实例后,需要为其提供数据源并生成DataLoaders:
dls = dblock.dataloaders(path, bs=64)
这样,我们就得到了和之前功能完全相同的数据加载器,但使用了更灵活的DataBlock方式。
第二步:扩展DataBlock以处理多输出任务
我们的目标是同时预测疾病和品种。这意味着对于每个输入图像,我们需要两个类别标签作为输出。
以下是实现这一目标的关键修改:
- 修改Blocks:我们需要三个块:一个
ImageBlock用于输入,两个CategoryBlock用于两个输出。 - 指定输入数量:通过
n_inp=1告诉DataBlock,元组中的第一个元素是输入,其余是输出。 - 自定义获取标签的函数:我们需要为第二个标签(品种)编写一个查找函数。
首先,创建一个从图像ID到品种的映射字典:
# 假设df是一个包含‘image_id’和‘variety’列的DataFrame
image_to_variety = {row[‘image_id‘]: row[‘variety‘] for _, row in df.iterrows()}
然后,定义一个函数,根据图像文件路径获取品种:
def get_variety(p):
# 从路径中提取文件名(不含扩展名)作为image_id
image_id = p.stem
return image_to_variety[image_id]
现在,构建新的DataBlock:


dblock = DataBlock(
blocks=(ImageBlock, CategoryBlock, CategoryBlock), # 图像 + 疾病类别 + 品种类别
get_items=get_image_files,
get_y=[parent_label, get_variety], # 返回两个标签的列表
n_inp=1, # 指明第一个块(ImageBlock)是输入
splitter=RandomSplitter(valid_pct=0.2, seed=42),
item_tfms=Resize(224),
batch_tfms=aug_transforms(size=128)
)


当我们查看生成的数据集中的一个样本时,会得到一个包含三个元素的元组:(图像张量, 疾病类别索引, 品种类别索引)。DataLoaders 会自动为这两个类别标签生成对应的词汇表(Vocab)。
深入理解:DataBlock的核心组件
为了更自如地使用DataBlock,了解其内部组件的职责很有帮助:
blocks:定义每个数据项最终应被转换成的类型(如图像、类别、多标签等)。它包含type_tfms,负责将get_x/get_y的原始输出转换为指定类型(例如,将文件路径字符串转换为PIL图像对象)。get_items:函数,返回一个可迭代对象(如文件路径列表),其中每个元素代表一个数据样本的“源信息”。get_x/get_y:函数,它们接收get_items返回的单个“源信息”,并从中提取出需要传递给对应block的type_tfms的输入。如果block可以直接处理get_items的原始输出(如图像路径直接给ImageBlock),则可以省略get_x。n_inp:指定blocks元组中有多少个是输入块(默认为1)。其余均为输出块。splitter:函数,负责将get_items返回的列表分割为训练集和验证集索引。item_tfms:在单个样本被组装成批次之前应用的变换(例如,调整每张图片的大小至相同尺寸)。这些变换在CPU上执行。batch_tfms:在样本被组装成张量批次之后应用的变换(例如,标准的数据增强操作,如旋转、裁剪、亮度调整等)。这些变换可以利用GPU加速。
工作流程简述:
get_items 提供原始项目列表 -> splitter 将其分为训练/验证集 -> 对于每个项目,get_x/get_y 提取原始特征/标签 -> 各block的type_tfms将其转换为指定类型(形成Dataset)-> item_tfms进行逐项预处理 -> 组成批次 -> batch_tfms进行批处理增强 -> 最终输入模型。
模型与损失函数适配
当我们有了输出两个标签的数据加载器后,需要调整模型和损失函数来处理它们。
- 修改模型输出层:标准分类器头输出节点数等于类别数。现在我们需要两个头,或者一个能同时输出两个预测的大头。一个简单的方法是让模型最后一层输出
疾病类别数 + 品种类别数个节点(例如10+10=20)。 - 自定义损失函数:我们需要一个能同时计算两个分类任务损失的函数。可以使用
CrossEntropyLossFlat分别计算疾病和品种的损失,然后求和或平均。
# 伪代码示意
def combined_loss(preds, targets):
disease_preds = preds[:, :10] # 假设前10个节点是疾病预测
variety_preds = preds[:, 10:] # 后10个节点是品种预测
disease_targ = targets[0]
variety_targ = targets[1]
loss = F.cross_entropy(disease_preds, disease_targ) + F.cross_entropy(variety_preds, variety_targ)
return loss
在创建Learner时,需要指定这个自定义的损失函数,以及相应的评估指标(如分别计算疾病和品种的错误率)。
总结


本节课中我们一起深入探索了fastai的DataBlock API。我们从复现简单的单标签分类任务开始,逐步将其扩展为一个能够处理多输出(预测疾病和品种)的复杂数据管道。我们学习了DataBlock中blocks、get_items、get_x/get_y、n_inp、splitter、item_tfms和batch_tfms等核心组件的含义与用法。最后,我们探讨了如何为多输出任务调整模型和损失函数。DataBlock的模块化设计使得构建复杂且高效的数据处理流程变得清晰而直观,是解决多样化机器学习任务的重要工具。
014:糖尿病视网膜病变实战


在本节课中,我们将学习如何通过多种技术优化一个糖尿病视网膜病变图像分类模型。我们将探讨早停法、测试时增强、加权采样以及渐进式调整图像尺寸等概念,并理解它们在实际应用中的取舍。
早停法:为何通常不推荐使用?
上一节我们介绍了模型训练的基本流程,本节中我们来看看一个常见的训练技巧——早停法。早停法是指在验证集性能不再提升时提前终止训练,以防止过拟合。
在FastAI中,可以通过回调函数 EarlyStoppingCallback 实现早停。然而,在实践中,尤其是在使用单周期学习率调度或微调时,早停法可能并不适用。主要原因如下:
- 学习率未稳定:如果提前停止,模型的学习率可能尚未下降到合适的微调阶段。此时验证集上表现稍好的模型,其泛化能力可能不如完成完整训练周期的模型。
- 过拟合的更好解决方案:如果发现模型在训练早期就出现过拟合迹象(即训练损失远低于验证损失),更有效的策略通常是增加数据增强,而不是停止训练。
- 验证集波动:验证集通常较小,其损失或准确率的微小波动可能只是噪声,过度解读这些波动可能导致选择次优的模型。
因此,与其依赖早停法,不如让模型完成预定的训练周期,并专注于通过数据增强和正则化来改善泛化能力。
模型集成与测试时增强
当我们拥有多个模型时,一个自然的想法是将它们组合起来,即模型集成。一种简单的方法是训练多个模型,然后使用它们的预测结果作为特征,再训练一个梯度提升机(如XGBoost)进行最终预测。


然而,对于神经网络这类高度灵活的模型,不同模型在数据不同区域表现差异巨大的情况并不常见。通常,一种模型架构(如ConvNeXt)会显著优于其他架构。因此,复杂的集成方法往往只在竞赛中为追求最后0.002%的性能提升时才值得尝试,并非获取主要性能收益的“低垂果实”。
接下来,我们看看另一种提升单模型性能的技术——测试时增强。
测试时增强是指在模型预测时,对输入图像进行多种数据增强(如旋转、缩放、调整亮度),生成多个变体,分别进行预测,然后对预测结果取平均。
以下是其工作原理的简单示例:
# 伪代码:测试时增强流程
predictions = []
for augmentation in augmentations:
augmented_img = apply_augmentation(original_img, augmentation)
pred = model.predict(augmented_img)
predictions.append(pred)
final_prediction = average(predictions)
TTA主要有两个好处:
- 提供多角度信息:就像人从不同角度观察一个难以辨认的物体一样,TTA让模型看到图像的不同增强版本,对于难以分类的样本,可能在某个特定增强版本中变得清晰可辨,从而提高预测准确性。
- 缓解过度自信:在训练后期,模型可能对训练集变得过度自信,输出非常极端的概率(如0.99)。TTA通过对多个增强版本的预测取平均,可以使最终输出的概率更加平缓和合理,有时也能间接提升准确率。
处理类别不平衡:加权采样
在我们的数据集中,不同类别的样本数量可能差异很大,这被称为类别不平衡。一个自然的想法是对少数类进行过采样,即让模型在训练中更频繁地看到少数类的样本。
在FastAI中,可以通过 WeightedDL 回调函数实现加权采样。它会根据你提供的权重来调整每个样本在训练中被选中的概率。
以下是使用加权采样的关键步骤:
- 为数据集的每个类别或样本计算一个权重。
- 在创建
DataLoaders时,使用weighted_dataloaders方法并传入权重。
需要注意的是,加权采样并非总是有益。它可能导致模型对某些图像重复学习,而对其他图像学习不足。是否使用取决于不平衡的严重程度和评估指标。如果评估指标对每个类别平等看待,那么加权采样很可能有帮助。如果测试集分布与训练集相似,且更关注模型在多数类上的表现,则可能不需要。
渐进式调整图像尺寸
渐进式调整图像尺寸是一种强大的训练技巧,它不是在固定分辨率下训练整个周期,而是从小尺寸图像开始训练,然后逐步增大图像尺寸并继续训练。
其核心优势在于:
- 训练速度更快:训练小图像的计算成本远低于大图像,可以快速完成多个周期,让模型学习到基础特征。
- 平滑过渡:模型已经在小图像上学到了良好的特征表示,将其微调到更大图像相对容易,可以更快地收敛。
- 一种数据增强:在不同尺寸上训练本身就是一种形式的数据增强,可以提高模型的鲁棒性。
实现方法很简单:在训练一段时间后,用更大尺寸图像创建新的 DataLoaders,然后替换掉学习者中的旧数据加载器,并继续微调模型。
# 伪代码:渐进式调整尺寸
learn.dls = get_dls(new_size=160) # 替换为更大尺寸的数据加载器
learn.fine_tune(epochs=5) # 继续微调
这种方法由FastAI团队在DAWNBench竞赛中首创,后来被谷歌在EfficientNetV2等工作中进一步研究和完善。它允许我们以更少的计算资源,获得与直接训练大尺寸图像相当甚至更好的性能。
其他训练技巧与问答
关于保存最佳模型:与早停法类似,SaveModelCallback 会保存验证集上表现最好的模型参数。但由于同样的原因(学习率未稳定、验证集波动),保存的“最佳”模型可能并非全局最优。建议让训练完整进行,并通过改进数据和方法来提升性能。

关于训练更长时间:如果训练和验证误差仍在下降,可以继续训练。但请注意,fit_one_cycle 结束后,学习率会进入循环。若想继续训练,可能需要手动将学习率降低(例如除以4),然后调用 learn.fit 继续训练。
关于分辨率独立性:我们使用的卷积神经网络架构(如ResNet, ConvNeXt)通常是分辨率独立的。这意味着改变输入图像尺寸不会改变模型参数,只是改变了卷积操作处理的“补丁”数量,因此无需为新的图像尺寸重新初始化任何模型部分。
本节课中我们一起学习了多种优化深度学习模型性能的实用技术。我们了解到,像早停法和复杂模型集成这类技巧的收益往往有限,且容易导致时间浪费。相反,测试时增强和渐进式调整尺寸是两种简单有效、能带来稳定提升的策略。同时,在处理类别不平衡问题时,需要根据评估目标和数据分布谨慎选择是否使用加权采样。掌握这些技术的原理和适用场景,能帮助我们在实践中更高效地构建和优化模型。
015:加权采样与数据处理进阶




在本节课中,我们将学习如何处理类别不平衡的数据集,并探索一种名为“加权采样”的技术。我们将从基础概念入手,逐步构建一个使用加权数据加载器的模型,并在此过程中了解如何改进FastAI库以简化这一流程。
概述
在上一节中,我们探讨了渐进式调整图像大小等技术。本节中,我们将重点关注数据集中类别不平衡的问题。当某些类别的样本数量远少于其他类别时,模型可能难以学习这些少数类的特征。加权采样是一种通过调整数据加载过程中每个样本被选中的概率,来让模型更频繁地看到少数类样本的方法。

加权采样的动机

首先,让我们查看数据集中各类别的分布情况。
df.label.value_counts()



输出结果会显示每个类别的样本数量。例如,可能存在大量“正常”类样本,而“细菌性叶斑病”类样本则很少。这种不平衡可能导致模型对多数类过拟合,而对少数类识别能力不足。
加权采样主要有两个目的:
- 平衡类别频率:让模型更频繁地看到稀有类别的样本,给予它们更多学习机会。
- 处理困难样本:即使某些类别样本数量不少,但如果它们本身难以分类,也可以通过增加权重让模型更多地关注它们。
一种简单的加权策略是使用样本数量的倒数。例如,如果某个类别有 N 个样本,则其权重可以是 1/N。这样,样本数少的类别权重更大。但直接使用倒数可能导致权重差异过大,因此我们也可以尝试使用平方根来缓和这种差异,例如 1/sqrt(N)。
实现加权数据加载器
接下来,我们将使用FastAI的DataBlock和WeightedRandomSampler来实现加权采样。以下是实现步骤:
- 计算权重:根据数据框中的标签列计算每个类别的权重,并将其映射到每个样本。
- 创建DataBlock:定义数据块,包括图像块和类别块,以及必要的转换。
- 使用加权数据加载器:调用我们即将添加到
DataBlock类中的新方法weighted_dataloaders来创建数据加载器。
以下是计算权重的示例代码:
# 假设 df 是包含‘label’列的数据框
class_counts = df.label.value_counts()
# 使用类别的倒数作为基础权重,这里使用平方根使其更平缓
weights = 1 / np.sqrt(class_counts)
# 将权重映射回原始数据框的每一行
weight_df = df.copy()
weight_df['weight'] = weight_df['label'].map(weights)
改进FastAI库
在实现过程中,我们发现直接使用Datasets.weighted_dataloaders有些繁琐,需要手动处理数据分割和转换应用。因此,我们决定为DataBlock类添加一个weighted_dataloaders方法,使其更易于使用。
我们查看了DataBlock.dataloaders的源代码,并创建了一个新的weighted_dataloaders方法。这个方法会:
- 接收源数据、权重列名等参数。
- 内部调用
Datasets.weighted_dataloaders。 - 自动处理训练集和验证集的分割,确保只为训练集应用权重。
- 继承
DataBlock中定义的item_tfms和batch_tfms,无需手动传递。
核心添加的代码如下(位于fastai/callback/data.py中的DataBlock类):
@patch
def weighted_dataloaders(self:DataBlock, source, weights, bs=64, **kwargs):
dsets = self.datasets(source, verbose=False)
trn_weights = weights[dsets.splits[0]] if hasattr(weights, '__array__') else [weights[i] for i in dsets.splits[0]]
return dsets.weighted_dataloaders(bs=bs, weights=trn_weights, **kwargs)
完成代码修改后,我们运行了测试以确保新功能不会破坏现有功能,并提交了更改。
使用新功能训练模型


现在,我们可以使用新的weighted_dataloaders方法来简洁地创建数据加载器并训练模型:

# 创建DataBlock
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_y=ColReader('label'),
splitter=RandomSplitter(seed=42),
item_tfms=Resize(224),
batch_tfms=aug_transforms(size=128))

# 使用加权数据加载器
dls = dblock.weighted_dataloaders(source=df, weights=weight_df['weight'], bs=64)




# 创建学习器并进行训练
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(5)


使用加权采样后,模型在每个周期内看到稀有类别样本的次数会增加。需要注意的是,这可能会改变“周期”的传统定义,并且存在对少数类样本过拟合的风险。因此,一种常见的策略是在训练后期(当模型已经学得较好时)再使用几轮加权训练,或者配合更强的数据增强使用。



总结


本节课中我们一起学习了如何处理类别不平衡的数据集。我们介绍了加权采样的概念及其动机,并一步步实现了在FastAI中使用加权数据加载器。更重要的是,我们通过查看源代码、添加新方法并运行测试,亲身体验了如何为开源库贡献一个实用的新功能。这展示了FastAI库的模块化设计如何让我们能够灵活地扩展和定制工作流程。
016:高级训练技巧

在本节课中,我们将学习一些高级的训练技巧,包括梯度累积、多目标损失函数、以及如何处理类别不平衡和数据分布差异等问题。这些技巧能帮助我们在资源有限或面对复杂数据时,更有效地训练模型。
上一节我们介绍了随机森林模型,本节中我们来看看在深度学习中如何优化训练过程。
梯度累积的正确使用
梯度累积是一种在内存有限的GPU上模拟更大批次训练的技术。其核心思想是:在多次前向传播和反向传播后,再一次性更新模型权重。
公式:
有效批次大小 = 实际批次大小 × 累积步数
之前在使用梯度累积时,我犯了一个错误:我错误地将参数设置为期望的累积步数(如2),但实际上,应该设置为期望的有效批次大小(如64)。正确的做法是,根据你的GPU内存,找到一个能正常运行的批次大小(例如32),然后设置累积步数,使得 32 × 累积步数 = 64,即累积步数为2。
代码示例:
# 假设默认批次大小为64,但你的GPU只能容纳32
# 设置累积步数为2,以达到有效批次大小64的效果
accum_steps = 2
处理类别不平衡与课程学习
在训练中,我们常遇到某些类别样本极少的问题。简单的重采样(如过采样少数类)可能不是最佳方案,因为测试集的分布可能与训练集不同。
一个更有效的策略是课程学习:让模型更多地关注那些它预测错误的困难样本。这不同于提升方法,课程学习是通过在训练中提高错误样本的权重来实现的。
以下是实现思路:
- 训练一个初始模型。
- 使用模型在验证集上的预测结果,找出预测错误的样本。
- 在后续训练中,为这些错误样本分配更高的采样权重。
数据分布与验证集构建
构建一个能真实反映部署环境或测试集分布的验证集至关重要。如果发现训练集和测试集存在系统性差异(例如,图像来源不同、设备不同),必须引起警惕。
处理此类差异的步骤:
- 分析差异:检查图像尺寸、标签分布、视觉特征等。
- 调整策略:如果测试集来自不同环境(如另一家医院),应考虑在部署前使用新环境的数据对模型进行少量样本的微调。
- 保持透明:在竞赛中,组织者应明确告知测试集与训练集的差异,以便参赛者能更好地建模。
模型输出与损失函数
模型的输出层设计直接影响其能力。对于多分类问题,常用Softmax激活函数配合交叉熵损失。然而,当数据集中可能存在“不属于任何已知类别”的样本时,使用Softmax就不合适了。
解决方案:使用二元Sigmoid输出配合二元交叉熵损失。这样,模型会为每个类别独立输出一个概率。我们可以设定一个阈值(如0.5),当所有类别的概率都低于阈值时,则认为该样本不属于任何已知类别,并将其交由人工复核。
多目标损失就是这种策略的延伸,它允许一个样本同时拥有多个标签(例如,一张X光片同时显示多种病症)。


词嵌入与协同过滤
词嵌入是将离散数据(如单词、类别ID)转换为连续向量表示的核心技术。这些向量能捕捉数据间的语义关系。
一个经典的应用是协同过滤(用于推荐系统):
- 输入:用户ID和物品ID。
- 嵌入层:为每个用户和物品学习一个嵌入向量。
- 目标:通过计算用户向量和物品向量的点积(或相似度)来预测用户对物品的评分。
通过Excel表格,我们可以直观地演示嵌入向量如何通过梯度下降进行学习和更新,这有助于深刻理解嵌入的本质。
模型调试与伦理考量
对于高级调试,我们可以检查网络中间层的激活值分布,以诊断梯度消失或爆炸等问题。Fast.ai的“彩色维度图”是进行此类分析的有用工具。
最后,我们必须关注人工智能伦理。在容易构建模型的今天,负责任地应用它们更为重要。建议深入学习Fast.ai的《数据伦理》课程,或回顾Rachel Thomas在2020年的相关讲座,以确保技术向善。


本节课中我们一起学习了梯度累积的正确用法、通过课程学习处理样本不平衡、构建反映真实分布的验证集、根据任务选择合适的输出层与损失函数、以及词嵌入和协同过滤的基本概念。这些高级技巧将帮助你更从容地应对复杂的现实世界机器学习问题。
017:TIMM模型整合
在本节课中,我们将学习如何为FastAI库中的TIMM模型添加缺失的归一化功能。我们将从设置开发环境开始,逐步分析问题,修改代码,并最终验证修复效果。
环境设置与代码库克隆

首先,我们需要设置一个开发环境并克隆FastAI代码库。
以下是具体步骤:

- 启动一台机器并进入主目录。
- 克隆FastAI代码库。
- 运行安装脚本
./setup.sh。 - 脚本执行完毕后,重启机器以应用所有设置。
重启后,环境将包含用于安装Python包的工具(如 pip 和 mamba)。此时,我们可以通过 pip install -U fastai 命令安装或升级FastAI到最新版本。安装完成后,在Python中导入 fastai 并检查版本,可以确认安装成功。
接下来,我们还需要安装一些必要的二进制依赖,例如用于加速计算的 universal-ctxt。这些安装是持久化的,在免费的计算资源上同样有效。
完成上述步骤后,开发环境就准备就绪了。

问题分析:TIMM模型缺失归一化

上一节我们完成了环境搭建,本节中我们来看看需要解决的具体问题。


在计算机视觉任务中,归一化是一个常见的预处理步骤,其目的是对每个颜色通道减去均值并除以标准差。公式表示为:
normalized_image = (image - mean) / std
对于使用预训练权重的模型,我们必须使用与模型训练时相同的均值和标准差进行归一化,否则输入数据的分布将与模型学习时的分布不一致,影响性能。
在FastAI的 VisionLearner 中,有一个 normalize 参数。当设置为 True 且模型是预训练模型时,它会尝试从模型的元数据中获取正确的统计量(均值和标准差)并添加归一化变换。
然而,当前代码仅对非TIMM模型实现了此功能。TIMM模型拥有自己的归一化统计量,存储在模型的 default_cfg 属性中,但FastAI尚未利用这些信息。这意味着目前使用TIMM架构时,即使设置了 normalize=True,也不会自动添加正确的归一化层。
代码修改:为TIMM模型添加归一化
分析了问题所在后,本节我们将着手修改代码以实现功能。
核心思路是:在创建TIMM模型时,检查其配置(default_cfg),提取其中的均值(mean)和标准差(std)信息,然后为数据加载器添加相应的 Normalize 变换。


我们需要修改 fastai/vision/learner.py 文件中的 _add_norm 函数。修改后的逻辑如下:
def _add_norm(self, stats):
if self.normalize:
# 检查是否为TIMM模型
if hasattr(self.model[0], ‘default_cfg’):
cfg = self.model[0].default_cfg
mean = cfg.get(‘mean’, [0.5, 0.5, 0.5])
std = cfg.get(‘std’, [0.5, 0.5, 0.5])
stats = (mean, std)
# 原有的非TIMM模型处理逻辑
# ...
self.dls.add_tfms([Normalize.from_stats(*stats)], ‘after_batch’)
这样,当学习者使用TIMM模型时,就会自动根据模型配置添加正确的归一化。
测试验证与结果分析
修改代码后,我们需要验证其是否正常工作。
首先,我们创建一个使用TIMM模型(例如 ‘vit_tiny_patch16_224’)的 VisionLearner,并检查其数据加载器的变换列表。应该能看到包含从模型配置中获取的统计量的 Normalize 变换。
为了全面测试,我们运行了FastAI的测试套件。最初发现了一些因代码重构(例如 create_timm_model 函数返回顺序模型)而导致的错误。通过调整函数返回值和调用方式,我们解决了这些问题,确保了测试通过。
最后,我们在实际任务(如CIFAR-10分类)上对比了修复前后的模型性能。有趣的是,在使用了我们常用的微调技巧(如解冻层、差分学习率)后,添加归一化对最终准确率的影响微乎其微。这可能是因为批量归一化(BatchNorm)层在微调过程中已经适应了新的数据分布,削弱了输入归一化的重要性。

然而,这个修复仍然至关重要。如果用户希望不进行微调而直接使用预训练模型进行推理,或者从头开始训练模型,那么正确的输入归一化就是必不可少的。


拓展讨论:动态UNet与TIMM模型集成




本节课我们主要解决了视觉分类模型中的归一化问题。本节我们将视野拓展到图像分割领域,讨论如何将TIMM模型集成到FastAI的动态UNet架构中。
动态UNet是FastAI的一个强大特性,它允许编码器(下采样路径)使用任何预训练模型,并自动构建对称的解码器(上采样路径)。其核心原理是:通过前向传播一个虚拟输入来钩取(hook)编码器各层的输出形状,识别出特征图尺寸发生变化的层,并将这些层的输出作为解码器对应层的跳跃连接(skip connection)。
要让动态UNet支持TIMM模型,需要完成以下步骤:
- 修改模型创建逻辑:类似于
create_vision_model,需要一个新的函数(如create_timm_body)来实例化TIMM模型并移除其分类头。 - 集成到UNet创建流程:在
DynamicUnet初始化时,如果传入的是TIMM模型字符串,则调用create_timm_body来构建编码器。 - 处理跳跃连接:动态UNet现有的钩子机制应该能自动适应TIMM模型,捕捉其下采样点的输出。
这项工作具有很高的价值,能让用户方便地在图像分割任务中利用各种先进的TIMM预训练模型。有兴趣的开发者可以尝试实现这一功能,这将是对FastAI生态一个很有意义的贡献。
总结与展望



本节课中我们一起学习了如何为FastAI库中的TIMM模型集成缺失的归一化功能。我们从环境配置开始,分析了问题根源,实施了代码修改,并进行了测试验证。虽然在实际微调场景下影响不大,但这一修复保证了模型在不同使用场景下的正确性。
我们还探讨了将TIMM模型集成到动态UNet中的可能性,这为图像分割任务打开了利用更强大预训练模型的大门。
FastAI是一个持续发展的项目,鼓励社区贡献。欢迎大家尝试使用最新代码,测试不同模型,报告问题或提交改进,共同完善这个深度学习工具库。
018:现代API使用



在本节课中,我们将学习如何解决一个实际问题:处理论坛帖子中的图片链接。我们将使用Python的正则表达式来解析和替换文本,并最终构建一个简单的Web应用来自动化这个过程。我们将从数据准备开始,逐步构建解决方案,并学习一些有用的命令行技巧和正则表达式知识。


数据准备与问题定义
上一节我们介绍了课程目标,本节中我们来看看具体要解决的问题。论坛用户Daniel在总结课程内容时,需要将帖子末尾的图片链接替换到正文的特定位置。原始数据包含两种格式的图片引用:一种是带尺寸的完整链接(在帖子末尾),另一种是正文中的简略引用。我们的目标是自动完成这个替换。


首先,我们需要将原始数据保存到文本文件中,以便于处理。以下是在命令行中快速创建文本文件的方法:

cat > source.txt
# 粘贴源文本内容
# 按 Control+D 结束输入
cat > dest.txt
# 粘贴目标文本内容
# 按 Control+D 结束输入
在Python中读取数据
现在,我们在Jupyter Notebook中读取这些文件。我们将使用fastcore库增强的Path类,它提供了更便捷的文件操作方式。
from fastcore.all import *
src = Path('source.txt').read_text()
dest = Path('dest.txt').read_text()
为了查看内容,我们可以使用!在Jupyter中执行bash命令并打印结果。

print(f"{src}")
print(f"{dest}")


使用正则表达式提取映射关系


上一节我们导入了数据,本节中我们来看看如何从中提取图片名称与链接的映射关系。我们需要从source.txt中提取出图片名称和对应的完整Markdown图片链接。

正则表达式(regex)是处理这类文本模式匹配的强大工具。我们将使用Python的re模块。
import re
我们需要匹配的源格式类似于:。我们关心括号内的文件名(不含扩展名)和整个链接部分。
以下是构建正则表达式并提取字典的步骤:
# 正则表达式模式:匹配 
# 我们使用括号捕获“文件名”和“完整的链接部分”
pat = r'!\[[^\]]+\]\(attachment:([^\.]+)\.[^\)]+\)'
founds = re.findall(pat, src)
# 创建一个从文件名到找到的匹配项的列表(暂时)
print(founds[:5])
然而,我们实际需要的是一个从图片名(如add_to_class)到其完整引用字符串的映射。我们需要调整正则表达式以捕获更多信息。
# 改进的正则表达式,捕获图片名和完整的attachment链接
pat = r'(!\[[^\]]+\]\(attachment:[^\)]+\))'
founds = re.findall(pat, src)
# 现在 founds 包含了完整的图片标记字符串
img_dict = {}
for img_tag in founds:
# 提取图片名,例如从 `` 中提取 `add_to_class`
# 使用另一个正则表达式
name_match = re.search(r'!\[[^\]]+\]\(attachment:([^\.]+)', img_tag)
if name_match:
name = name_match.group(1)
img_dict[name] = img_tag
print(list(img_dict.items())[:3])
在目标文本中进行查找和替换
现在我们已经有了映射字典,接下来需要在目标文本(dest.txt)中查找简短的图片引用(如)并将其替换为完整的图片标记。
目标文本中的引用格式为:。我们需要构建另一个正则表达式来匹配这种模式,并使用一个函数来执行替换。
# 匹配目标文本中的图片引用,例如 
dest_pat = r'!\[\]\(([^\)]+)\)'
def replace_img(match):
"接收一个re.Match对象,返回替换后的字符串"
filename = match.group(1) # 例如 'add_to_class.png'
# 去掉扩展名以获取键
key = filename.split('.')[0]
# 从字典中获取完整的图片标记,如果找不到则返回原字符串
full_tag = img_dict.get(key)
if full_tag:
return full_tag
else:
return f"![缺失图片: {key}]"
# 使用 re.sub 进行替换,其第二个参数可以是一个函数
new_dest = re.sub(dest_pat, replace_img, dest)
print(new_dest[:500]) # 打印一部分看看效果
处理更复杂的情况和可选尺寸


实际上,Daniel的源数据中,链接可能包含可选的尺寸信息(如|900)。我们的正则表达式和替换函数需要处理这种可选情况。
我们需要修改源数据的正则表达式,以捕获可选的尺寸部分。
# 更新源正则表达式,使尺寸部分可选
src_pat = r'(!\[[^\]]+\]\(attachment:[^\)]+(?:\|\d+)?\))'
founds = re.findall(src_pat, src)
img_dict = {}
for img_tag in founds:
name_match = re.search(r'!\[[^\]]+\]\(attachment:([^\.]+)', img_tag)
if name_match:
name = name_match.group(1)
img_dict[name] = img_tag


同时,目标文本中的引用也可能包含尺寸(如)。我们需要更新目标正则表达式和替换函数。


# 更新目标正则表达式,匹配可能的尺寸
dest_pat = r'!\[\]\(([^\)]+?(?:\|\d+)?)\)'

def replace_img_v2(match):
filename_part = match.group(1) # 例如 'add_to_class.png' 或 'add_to_class.png|900'
# 分离文件名和尺寸
if '|' in filename_part:
filename, size = filename_part.split('|')
else:
filename, size = filename_part, None
key = filename.split('.')[0]
full_tag = img_dict.get(key)
if full_tag:
# 如果原目标有尺寸,且完整标签也有尺寸,可能需要调整。这里简单返回完整标签。
# 更复杂的逻辑可以在这里添加,例如保留目标尺寸或替换为源尺寸。
return full_tag
else:
return f"![缺失图片: {key}]"

new_dest_v2 = re.sub(dest_pat, replace_img_v2, dest)
封装为函数并创建Web应用
现在我们已经有了一个可以工作的脚本,让我们将其封装成一个函数,并尝试使用Gradio创建一个简单的Web界面,让Daniel可以更方便地使用。
首先,将核心逻辑放入一个函数:
def fix_images(source_text, dest_text):
import re
# 从源文本构建映射字典
src_pat = r'(!\[[^\]]+\]\(attachment:[^\)]+(?:\|\d+)?\))'
founds = re.findall(src_pat, source_text)
img_dict = {}
for img_tag in founds:
name_match = re.search(r'!\[[^\]]+\]\(attachment:([^\.]+)', img_tag)
if name_match:
name = name_match.group(1)
img_dict[name] = img_tag
# 替换目标文本
dest_pat = r'!\[\]\(([^\)]+?(?:\|\d+)?)\)'
def replacer(match):
filename_part = match.group(1)
if '|' in filename_part:
filename, _ = filename_part.split('|')
else:
filename = filename_part
key = filename.split('.')[0]
full_tag = img_dict.get(key)
if full_tag:
return full_tag
else:
return f"![缺失图片: {key}]"
fixed_text = re.sub(dest_pat, replacer, dest_text)
return fixed_text
# 测试函数
fixed_output = fix_images(src, dest)
print(fixed_output[:1000])
接下来,使用Gradio创建一个界面。Gradio允许我们快速构建机器学习或数据处理模型的Web界面。
# 确保已安装 gradio: pip install gradio
import gradio as gr
# 创建界面
iface = gr.Interface(
fn=fix_images,
inputs=[
gr.Textbox(label="源文本 (包含完整图片链接)", lines=10),
gr.Textbox(label="目标文本 (需要替换的文本)", lines=10)
],
outputs=gr.Textbox(label="处理后的文本", lines=20),
title="论坛图片链接修复工具",
description="粘贴源文本和目标文本,自动替换图片引用。"
)
# 启动界面(在Jupyter中可以使用`launch`方法,但通常更适合在脚本中运行)
# iface.launch(share=True) # 这会启动一个本地服务器并生成一个可分享的链接
# 在Jupyter中,我们可能只想显示界面
iface.launch(inline=True, debug=True) # 在notebook内联显示
部署到Hugging Face Spaces
为了使工具更容易分享,我们可以将其部署到Hugging Face Spaces。这需要创建一个包含代码和依赖的仓库。
- 创建一个新的Hugging Face Space,选择Gradio SDK。
- 创建
app.py文件,包含上面的Gradio界面代码。 - 创建
requirements.txt文件,列出依赖:fastcore和gradio。 - 将代码推送到仓库,Hugging Face会自动构建并部署应用。
命令行工具技巧回顾
在课程中,我们还学习了一些有用的命令行技巧:
cat > file.txt:创建新文件并输入内容。Ctrl+D:在命令行中表示输入结束(EOF)。- 使用
grep或rg(ripgrep) 进行文本搜索。rg是一个用Rust编写的更快更强大的搜索工具。

总结与展望
本节课中我们一起学习了如何利用Python正则表达式解决一个实际的文本处理问题。我们从理解问题、准备数据开始,逐步构建了正则表达式模式来提取和映射信息,然后实现了查找替换逻辑。最后,我们将脚本封装成函数,并用Gradio构建了一个Web应用界面,甚至探讨了将其部署到云服务的可能性。
通过这个案例,我们不仅练习了正则表达式这一强大工具,还实践了从问题分析到应用部署的完整开发流程。正则表达式作为一种领域特定语言(DSL),是处理文本模式匹配不可或缺的技能,值得深入学习和掌握。

浙公网安备 33010602011771号