🌟 完整项目和代码
本教程是 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
- 留言交流你的学习困惑
一起学习,一起进步! 🤝