AI 入门 30 天挑战 - Day 18 费曼学习法版 - 图像分割基础
🌟 完整项目和代码
本教程是AI 入门 30 天挑战系列的一部分!
- 💻GitHub 仓库: https://github.com/Lee985-cmd/AI-30-Day-Challenge
- 📖CSDN 专栏: https://blog.csdn.net/m0_67081842?type=blog
- ⭐欢迎 Star 支持!
Week 3 第二天:图像分割!
像素级别的精细识别!
不仅知道"在哪里",还要知道"精确轮廓"!
每个概念都解释!每行代码都说明白!
预计时间:2.5-3.5 小时(含费曼输出练习)
📖 第 1 步:快速复习昨天的内容(30 分钟)
费曼输出 #0:考考你
合上教程,尝试回答:
□ 目标检测和图像分类的核心区别是什么?用例子说明 □ 边界框有几种表示方法?各有什么特点? □ IoU 是怎么计算的?为什么用它衡量准确度? □ NMS 的作用是什么?详细流程是怎样的? □ 如果你要检测路上的车辆,你会怎么设计系统?⏰ 时间:25 分钟
如果能答出 80% 以上,我们开始今天的像素级视觉之旅!如果不够,花 5 分钟翻一下 Day15 的笔记。
🤔 第 2 步:什么是图像分割?(40 分钟)
故事时间 📚
目标检测 vs 图像分割:
场景:给照片里的人抠图 目标检测(昨天学的): ┌──────────────┐ │ 👤 │ ← 用矩形框住 │ ┌────┐ │ │ │人 │ │ ← 95% 置信度 │ └────┘ │ │ │ └──────────────┘ ✓ 知道位置 ✗ 只有矩形框 ✗ 框里有背景 ✗ 无法精确抠图 图像分割(今天要学的): ┌──────────────┐ │ 👤 │ ← 精确勾勒轮廓 │ ╱ ╲ │ │ │ 人形 │ │ ← 每个像素分类 │ ╲____╱ │ │ │ ← 背景去掉 └──────────────┘ ✓ 精确到像素 ✓ 完整形状 ✓ 可以抠图 ✓ 能做美颜磨皮 这就是图像分割的魅力!生活中的例子:涂色游戏 目标检测就像: 给你一张画,让你圈出哪里有苹果 你用矩形框框起来 ✅ 但框里还有背景 图像分割就像: 给你一张画,让你把苹果涂上颜色 你要仔细地: - 沿着边缘涂 🎨 - 不涂到外面 - 每个像素都要判断 这才是真正的"认识"这个物体!图像分割的两种类型
1. 语义分割(Semantic Segmentation)
任务:给每个像素分类 输入照片: [街景:有路、车、人、树] 输出: 红色像素 → 车 蓝色像素 → 路 绿色像素 → 树 黄色像素 → 人 特点: ✓ 只关心"这是什么类别" ✗ 不区分"哪个个体" → 所有车都是红色 → 所有人都是一种颜色 就像: 老师点名:"穿红衣服的举手" 所有穿红衣服的都举手 但不关心具体是谁2. 实例分割(Instance Segmentation)
任务:不仅分类,还要区分个体 输入照片: [街景:有 3 个人] 语义分割: 所有人 → 同一颜色(黄色) 看不出是不同的人 实例分割: 人 1 → 黄色 人 2 → 橙色 人 3 → 棕色 特点: ✓ 关心"这是什么类别" ✓ 还关心"哪个个体" → 每个人不同颜色 → 能数清楚有几个人 就像: 老师点名:"张三、李四、王五分别举手" 每个人都能区分开🎯 费曼输出 #1:解释图像分割
任务 1:向小学生解释
场景:有个小朋友问你:"图像分割是什么?"
要求:
- 不用"像素"、"语义"、"实例"这些专业术语
- 用涂色、剪纸、贴纸等生活场景比喻
- 让小学生能听懂
参考模板:
"图像分割就像______一样。 比如你在______, 要把______从______中分离出来。 你要______, 不能______。 这样就能______!"⏰ 时间:15 分钟
💡 卡壳检查点
如果你在解释时卡住了:
□ 我说不清楚语义分割和实例分割的区别 □ 我不知道如何解释"像素级"的概念 □ 我只能说"分割物体",但不能说明白怎么做到的这很正常!标记下来,回去再看上面的内容,然后重新尝试解释!
提示:
- 语义分割 = 按类别分(所有猫一种颜色)
- 实例分割 = 按个体分(每只猫不同颜色)
- 就像分类水果 vs 数苹果
🎨 第 3 步:核心算法详解(60 分钟)
1. FCN(全卷积网络)- 开山之作
传统 CNN 的问题:
传统 CNN: 输入图片 → [卷积层] → [全连接层] → 类别 ↓ 固定尺寸输出 丢失空间信息 只能分类,不能分割 就像: 你看一幅画,只能说"这是风景" 但不能指出哪里是山、哪里是水FCN 的创新(2015 年):
FCN: 把全连接层改成卷积层! 输入图片 → [卷积层] → [反卷积层] → 分割图 ↓ 任意尺寸输入 保持空间信息 每个像素都有类别 就像: 你不仅说"这是风景" 还能准确地指出: - 这里是山 ⛰️ - 这里是水 💧 - 这里是树 🌳2. U-Net(医学图像神器)
为什么叫 U-Net?
因为结构像字母 U! 编码器(左侧下采样): 解码器(右侧上采样): 输入 输出 ↓ ↑ [Conv] [UpConv] ↓ ↑ [Pool] ←─── 最底层 ───→ [Conv] ↓ ↑ 更深层特征 恢复细节 中间用跳跃连接(Skip Connection): 把编码器的细节传给解码器 好处: ✓ 保留边缘信息 ✓ 定位更准确 ✓ 医学图像首选 就像写作文: 编码器 = 收集素材(理解内容) 解码器 = 组织语言(表达出来) 跳跃连接 = 不忘掉细节3. Mask R-CNN(实例分割王者)
Faster R-CNN 的升级版:
Faster R-CNN: 输入 → RPN → ROI → 分类 + 边界框 Mask R-CNN(多一个分支): 输入 → RPN → ROI → 分类 + 边界框 ↓ Mask 分支 ↓ 像素级分割掩码 额外输出: 每个 ROI 一个二值 mask(0=背景,1=前景) 这样就能: ✓ 检测物体(边界框) ✓ 分割物体(精确轮廓) ✓ 一举两得! 就像: 你先看到一个人(检测) 然后仔细描出他的轮廓(分割) 一次完成两件事!🎯 费曼输出 #2:深入理解核心算法
任务 1:创造多个比喻
场景 A:解释给医生听
用医学影像的例子 CT 扫描 = 编码器 诊断报告 = 解码器 病灶定位 = 分割结果场景 B:解释给摄影师听
用拍照修图的例子 拍照 = 输入 PS 抠图 = 分割 保存 PNG = 输出 mask场景 C:解释给老师听
用批改作业的例子 看完整试卷 = 编码器 逐题批改 = 解码器 成绩单 = 分割图要求:每个场景都要详细说明
任务 2:解释技术细节
思考题:
1. 为什么 FCN 要把全连接层改成卷积层? 2. U-Net 的跳跃连接是怎么工作的? 3. 语义分割和实例分割各有什么应用场景? 4. Mask R-CNN 比 Faster R-CNN 多了什么?⏰ 时间:25 分钟
💡 卡壳检查点
□ 我解释不清 U-Net 的结构原理 □ 我说不明白跳跃连接的作用 □ 我不能用生活中的例子说明提示:
- FCN = 第一个做分割的网络
- U-Net = 对称结构,医学首选
- Mask R-CNN = 检测 + 分割一体
💻 第 4 步:动手实现 U-Net(70 分钟)
完整代码实现
import torch import torch.nn as nn import torchvision.transforms as transforms from PIL import Image import numpy as np import matplotlib.pyplot as plt print("=" * 50) print("🎨 图像分割基础:U-Net 详解") print("=" * 50) # ============================================================================ # 第 1 步:理解 U-Net 架构 # ============================================================================ print("\n【1. U-Net 架构】") class DoubleConv(nn.Module): """双卷积块(U-Net 的基本单元)""" def __init__(self, in_channels, out_channels): super().__init__() self.double_conv = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True), nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True) ) def forward(self, x): return self.double_conv(x) class UNet(nn.Module): """U-Net 模型""" def __init__(self, n_channels=1, n_classes=2): super(UNet, self).__init__() # 编码器(下采样路径) self.enc1 = DoubleConv(n_channels, 64) self.pool1 = nn.MaxPool2d(2) self.enc2 = DoubleConv(64, 128) self.pool2 = nn.MaxPool2d(2) self.enc3 = DoubleConv(128, 256) self.pool3 = nn.MaxPool2d(2) self.enc4 = DoubleConv(256, 512) self.pool4 = nn.MaxPool2d(2) # 最底层 self.bottleneck = DoubleConv(512, 1024) # 解码器(上采样路径) self.upconv4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2) self.dec4 = DoubleConv(1024, 512) self.upconv3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2) self.dec3 = DoubleConv(512, 256) self.upconv2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2) self.dec2 = DoubleConv(256, 128) self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2) self.dec1 = DoubleConv(128, 64) # 输出层 self.out_conv = nn.Conv2d(64, n_classes, kernel_size=1) def forward(self, x): # 编码器 enc1 = self.enc1(x) enc2 = self.enc2(self.pool1(enc1)) enc3 = self.enc3(self.pool2(enc2)) enc4 = self.enc4(self.pool3(enc3)) # 最底层 bottleneck = self.bottleneck(self.pool4(enc4)) # 解码器(带跳跃连接) dec4 = self.upconv4(bottleneck) dec4 = torch.cat([dec4, enc4], dim=1) dec4 = self.dec4(dec4) dec3 = self.upconv3(dec4) dec3 = torch.cat([dec3, enc3], dim=1) dec3 = self.dec3(dec3) dec2 = self.upconv2(dec3) dec2 = torch.cat([dec2, enc2], dim=1) dec2 = self.dec2(dec2) dec1 = self.upconv1(dec2) dec1 = torch.cat([dec1, enc1], dim=1) dec1 = self.dec1(dec1) return self.out_conv(dec1) # 创建模型 model = UNet(n_channels=1, n_classes=2) print("✓ U-Net 模型创建完成") print(f"\n模型结构:") print(model) # 计算参数量 total_params = sum(p.numel() for p in model.parameters()) trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"\n总参数量:{total_params:,}") print(f"可训练参数:{trainable_params:,}") # ============================================================================ # 第 2 步:可视化 U-Net 结构 # ============================================================================ print("\n" + "=" * 50) print("📊 可视化 U-Net 的 U 型结构") print("=" * 50) fig, ax = plt.subplots(figsize=(14, 10)) ax.axis('off') # 绘制 U-Net 结构图 x_positions = [0, 1, 2, 3, 4, 5, 6, 7, 8] y_encoder = [4, 3, 2, 1, 0] y_decoder = [0, 1, 2, 3, 4] # 编码器(左侧) for i, y in enumerate(y_encoder[:-1]): rect = plt.Rectangle((x_positions[i]-0.8, y-0.6), 1.6, 1.2, fill=True, facecolor='#4ECDC4', edgecolor='black', linewidth=2) ax.add_patch(rect) ax.text(x_positions[i], y, f'Enc{i+1}\n64×{2**i}', ha='center', va='center', fontsize=9) # 下采样箭头 ax.annotate('', xy=(x_positions[i], y-0.8), xytext=(x_positions[i], y-1.2), arrowprops=dict(arrowstyle='->', linewidth=2, color='#FF6B6B')) # 最底层 rect = plt.Rectangle((x_positions[4]-0.8, -0.6), 1.6, 1.2, fill=True, facecolor='#FFE66D', edgecolor='black', linewidth=2) ax.add_patch(rect) ax.text(x_positions[4], 0, 'Bottleneck\n1024', ha='center', va='center', fontsize=9) # 解码器(右侧) for i, y in enumerate(y_decoder[1:], start=1): rect = plt.Rectangle((x_positions[i+4]-0.8, y-0.6), 1.6, 1.2, fill=True, facecolor='#FF6B6B', edgecolor='black', linewidth=2) ax.add_patch(rect) ax.text(x_positions[i+4], y, f'Dec{i}\n{512//(2**(4-i))}', ha='center', va='center', fontsize=9) # 上采样箭头 ax.annotate('', xy=(x_positions[i+4], y+0.8), xytext=(x_positions[i+4], y+1.2), arrowprops=dict(arrowstyle='->', linewidth=2, color='#4ECDC4')) # 跳跃连接 for i in range(4): ax.annotate('', xy=(x_positions[i+5], y_decoder[i+1]), xytext=(x_positions[i], y_encoder[i]), arrowprops=dict(arrowstyle='-', linewidth=1, color='gray', linestyle='--')) ax.text((x_positions[i]+x_positions[i+5])/2, 0.5, 'Skip', fontsize=7, ha='center') # 输入输出 ax.text(-1.5, 4, 'Input\nImage', fontsize=10, ha='right') ax.annotate('', xy=(-0.8, 4), xytext=(-1.3, 4), arrowprops=dict(arrowstyle='->', linewidth=2)) ax.text(9.5, 4, 'Output\nSegmentation', fontsize=10, ha='left') ax.annotate('', xy=(8.8, 4), xytext=(9.3, 4), arrowprops=dict(arrowstyle='->', linewidth=2)) ax.set_xlim(-2, 10) ax.set_ylim(-1, 5) ax.set_aspect('equal') ax.set_title('U-Net 架构示意图', fontsize=14, pad=20) plt.tight_layout() plt.show() print("\n💡 U-Net 的特点:") print("- 对称的 U 型结构") print("- 编码器提取特征,解码器恢复细节") print("- 跳跃连接保留边缘信息") print("- 医学图像分割的首选") # ============================================================================ # 第 3 步:模拟分割过程 # ============================================================================ print("\n" + "=" * 50) print("【3. 模拟分割过程】") print("=" * 50) # 创建一个简单的"图像"(灰度图) image_size = 256 image = np.zeros((image_size, image_size), dtype=np.float32) # 画一个圆(模拟肿瘤) center = (128, 128) radius = 50 for i in range(image_size): for j in range(image_size): if (i - center[0])**2 + **(j - center[1])2 <= radius**2: image[i, j] = 1.0 # 添加一些噪声 noise = np.random.randn(image_size, image_size) * 0.1 image += noise image = np.clip(image, 0, 1) # 转为 Tensor input_tensor = torch.from_numpy(image).unsqueeze(0).unsqueeze(0) print(f"输入图像形状:{input_tensor.shape}") print(f" (batch=1, channels=1, height={image_size}, width={image_size})") # 运行模型 model.eval() with torch.no_grad(): output = model(input_tensor) print(f"输出分割图形状:{output.shape}") print(f" (batch=1, classes=2, height={image_size}, width={image_size})") # 可视化 fig, axes = plt.subplots(1, 3, figsize=(15, 5)) # 原图 axes[0].imshow(image, cmap='gray') axes[0].set_title('输入图像(模拟 CT 片)', fontsize=12) axes[0].axis('off') # 真实标签(理想的分割) mask = np.zeros((image_size, image_size), dtype=np.uint8) for i in range(image_size): for j in range(image_size): if (i - center[0])**2 + **(j - center[1])2 <= radius**2: mask[i, j] = 1 axes[1].imshow(mask, cmap='jet') axes[1].set_title('真实分割(Ground Truth)', fontsize=12) axes[1].axis('off') # 预测结果(简化:直接用阈值) output_prob = torch.sigmoid(output[0, 0]).numpy() output_pred = (output_prob > 0.5).astype(np.uint8) axes[2].imshow(output_pred, cmap='jet') axes[2].set_title(f'预测分割(阈值=0.5)', fontsize=12) axes[2].axis('off') plt.tight_layout() plt.show() print("\n✅ 分割完成!") print(" 蓝色区域 = 背景") print(" 红色区域 = 肿瘤/前景") # ============================================================================ # 第 4 步:使用预训练的 DeepLabV3 # ============================================================================ print("\n" + "=" * 50) print("【4. 使用预训练的 DeepLabV3 进行分割】") print("=" * 50) import torchvision.models as models # 加载预训练的 DeepLabV3 deeplab_model = models.segmentation.deeplabv3_resnet50(pretrained=True) deeplab_model.eval() print("✓ DeepLabV3 模型加载完成") print(" 可以分割 COCO 数据集的 21 种物体") # 读取一张图片 from torchvision import transforms from PIL import Image import requests from io import BytesIO print("\n正在下载测试图片...") try: url = "https://farm9.staticflickr.com/8/7378_1b97e49c7b_z.jpg" response = requests.get(url) img = Image.open(BytesIO(response.content)) # 预处理 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) input_tensor = transform(img).unsqueeze(0) # 进行分割 with torch.no_grad(): output = deeplab_model(input_tensor)['out'][0] output_predictions = output.argmax(0) print(f"✓ 分割完成!") print(f" 输出形状:{output_predictions.shape}") # 显示结果 fig, axes = plt.subplots(1, 2, figsize=(16, 6)) axes[0].imshow(img) axes[0].set_title('原始图片', fontsize=14) axes[0].axis('off') axes[1].imshow(output_predictions.cpu().numpy(), cmap='jet') axes[1].set_title('DeepLabV3 分割结果', fontsize=14) axes[1].axis('off') plt.tight_layout() plt.show() except Exception as e: print(f"下载或处理图片失败:{e}") print("跳过此步骤,继续下面的内容") print("\n🎊 恭喜!你理解了图像分割的基础!") print("=" * 50)按 Shift + Enter 运行!
🎯 费曼输出 #3:解释代码含义
逐行解释给小白听
任务:假装你在教一个完全不懂编程的人
要解释清楚:
1. U-Net 的编码器是做什么的? 2. 解码器是怎么恢复细节的? 3. 跳跃连接是怎么传递信息的? 4. 为什么医学图像常用 U-Net?要求:
- 不用"张量"、"卷积"、"转置卷积"等术语
- 用生活化的比喻
- 每行代码都要说明白
参考思路:
"DoubleConv 就像是______" "pool 就像是______" "upconv 就像是______" "torch.cat 就像是______"⏰ 时间:30 分钟
💡 卡壳检查点
□ 我解释不清编码器和解码器的关系 □ 我说不明白跳跃连接的作用 □ 我不能用生活中的例子说明提示:
DoubleConv= 两次卷积提取特征pool= 缩小图片,保留重要信息upconv= 放大图片,恢复细节torch.cat= 拼接(把编码器的信息传过来)
🎉 今日费曼总结(30 分钟)⭐
完整的费曼学习流程
第 1 步:回顾今天的内容(5 分钟)
□ 图像分割 vs 目标检测的区别 □ 语义分割和实例分割的不同 □ U-Net 的 U 型结构 □ 跳跃连接的作用 □ 实际应用案例第 2 步:合上教程,尝试完整教授(15 分钟)⭐
任务:假装你在给一个完全不懂的人上第十六堂课
要覆盖:
- 图像分割和 target detection 的区别(用至少 2 个例子)
- 语义分割和实例分割各适合什么场景
- U-Net 的结构特点(用图示)
- 演示一个实际的分割应用
方式:
- 📝 写一篇 800 字左右的文章
- 🎤 录一段 10-15 分钟的视频
- 👥 找个朋友,给他讲一遍
第 3 步:标记卡壳点(5 分钟)
我今天卡壳的地方: □ _________________________________ □ _________________________________ □ _________________________________第 4 步:针对性复习(5 分钟)
回到教程中卡壳的地方,重新学习,然后再次尝试解释!
📝 费曼学习笔记模板
╔═══════════════════════════════════════════════════╗ ║ Day 16 费曼学习笔记 ║ ╠═══════════════════════════════════════════════════╣ ║ 日期:__________ ║ ║ 学习时长:__________ ║ ╠═══════════════════════════════════════════════════╣ ║ ║ ║ 1. 我向小白解释了: ║ ║ _______________________________________________ ║ ║ _______________________________________________ ║ ║ ║ ║ 2. 我卡壳的地方: ║ ║ □ _____________________________________________ ║ ║ □ _____________________________________________ ║ ║ ║ ║ 3. 我的通俗比喻: ║ ║ • 图像分割就像 ______ ║ ║ • 语义分割就像 ______ ║ ║ • 实例分割就像 ______ ║ ║ • U-Net 就像 ______ ║ ║ ║ ║ 4. 我还想知道: ║ ║ _______________________________________________ ║ ║ ║ ╚═══════════════════════════════════════════════════╝📊 今日总结
✅ 你今天学到了:
1. 图像分割基础
- 像素级的精细识别
- 语义分割 vs 实例分割
- 实际应用场景
2. 核心算法
- FCN(开创者)
- U-Net(医学首选)
- Mask R-CNN(检测 + 分割)
3. 实践能力
- U-Net 模型实现
- 可视化 U 型结构
- 使用预训练模型
4. 费曼输出能力⭐
- 能用比喻解释图像分割
- 能向小白说明 U-Net
- 能区分语义和实例分割
🎁 明日预告
明天你将学习:
主题:GAN 生成对抗网络 内容: ✓ AI 也能画画 ✓ 生成器和判别器的博弈 ✓ 伪造以假乱真的图片 ✓ 艺术创作应用 ✓ StyleGAN 详解 需要准备: ✓ 复习今天的分割概念 ✓ 了解生成和判别的思想 ✓ 保持好奇心!🆘 常见问题
Q1: 图像分割和目标检测到底有什么区别?
目标检测: → 用矩形框框住物体 → 知道位置和类别 → 但框里有背景 图像分割: → 精确勾勒物体轮廓 → 每个像素都分类 → 可以完美抠图 选择: - 只需要知道位置 → 目标检测 - 需要精确轮廓 → 图像分割Q2: 语义分割和实例分割怎么选?
语义分割: ✓ 只关心类别 ✓ 不区分个体 ✓ 适合:道路分割、天空分割 ✗ 无法数清楚有几个物体 实例分割: ✓ 既关心类别 ✓ 又区分个体 ✓ 适合:数人头、细胞计数 ✗ 计算更复杂 例子: - 分析交通拥堵 → 语义分割(知道哪里是车就行) - 统计车流量 → 实例分割(要数有几辆车)Q3: U-Net 为什么在医学图像这么火?
原因 1:数据量少也能训练 ✓ 医学图像标注成本高 ✓ U-Net 小样本表现好 原因 2:保留边缘细节 ✓ 跳跃连接传递细节 ✓ 病灶边界很重要 原因 3:结构简单有效 ✓ 容易理解和实现 ✓ 效果稳定可靠 应用: - 肿瘤分割 - 细胞计数 - 器官定位 - 病变检测💪 最后的鼓励
第十六天完成了!🎉
你已经掌握了: ✓ Week 1: 机器学习基础 ✓ Week 2: 深度学习入门 ✓ Week 3: 进阶深度学习(2/7) 这是质的飞跃! 从今天起: ✓ 你能做像素级分割了 ✓ 你能解释 U-Net 了 ✓ 你能用预训练模型了 ✓ 你能创造生动的比喻了 记住这个成就感! 每天都在进步! 每天都在变强! 继续加油!明天学习 GAN!💪 记住: "细节决定成败" 你现在有了这种精细识别的能力, 可以做更多有意义的事情了! 加油!我相信你一定可以的!✨📞 打卡模板
日期:___________ 学习时长:_______ 小时 费曼输出次数:_______ 次 今天学会了: 遇到的卡壳点: 如何用比喻解释的: 明天的目标:明天见!继续加油!✨
🔗 相关链接
🌐 项目资源
- 💻GitHub 仓库: https://github.com/Lee985-cmd/AI-30-Day-Challenge
- 📖CSDN 专栏: https://blog.csdn.net/m0_67081842?type=blog
- ⭐如果觉得有帮助,请给 GitHub 仓库 Star 支持!
本教程属于 AI 入门 30 天挑战 系列
🎉 恭喜你完成今天的学习!
🔗 资源汇总
- 📘完整 30 天教程:CSDN 专栏 - AI 入门 30 天挑战
- 💻完整代码 + 项目实战:GitHub 仓库 ⭐欢迎 Star
- ❓遇到问题:GitHub Issues 提问
💬 互动时间
思考题:今天的知识点中,哪个让你印象最深刻?为什么?
欢迎在评论区分享你的想法或疑问!👇
❤️ 如果有帮助
- 👍点赞:让更多人看到这篇教程
- ⭐Star GitHub:获取完整代码和项目
- ➕关注专栏:不错过后续更新
- 🔄分享给朋友:一起学习进步
明天见!继续 Day 19 的学习~🚀
其他平台
- 📂GitHub:https://github.com/Lee985-cmd/AI-30Days-Challenge
- 📝CSDN 博客:https://blog.csdn.net/m0_67081842
💡学习建议
如果本篇教程对你有帮助,欢迎:
- Star GitHub 项目:https://github.com/Lee985-cmd/AI-30Days-Challenge
- 留言交流你的学习困惑
一起学习,一起进步!🤝
