前言
也是跟着网上的教程实现了自己(最近)的第一个人工智能工具,所以来记录下过程。
~(部分内容经过Deepseek润色)~自己只把代码给了Deepseek,具体的描述都是Deepseek写的。之后有时间了再自己写一些吧(
🌸 环境配置篇
from captcha.image import ImageCaptcha # 验证码图片生成器
import random, time, os # 随机魔法/时间管理/系统小助手
from PIL import Image # 图像处理小爪子
import torch # 核心魔法书!✨
from torch import nn, Adam # 神经网络层+优化器甜点
from torch.utils.data import Dataset, DataLoader # 数据蛋糕烘焙师
from torchvision import transforms # 图像变形术大全
功能说明:
就像做蛋糕需要准备材料一样~这里我们在收集魔法道具呢!ImageCaptcha是生成验证码的魔法印章,random用来搅拌随机字符,torch是我们的主厨台,Dataset是装食材的魔法篮子喵~ (≧∇≦)ノ
设计原因:
- 为什么用
transforms
?因为要把图片变成张量面团才能放进烤箱(模型)呀! DataLoader
就像传送带,可以把数据小饼干批量送进训练炉~
🎏 Flag定义区
captcha_array = list("0123456789abcdefghijklmnopqrstuvwxyz") # 36种调味料
captcha_size = 4 # 每块饼干4个字符
train_flag = True # 小火车启动旗
功能说明:
这里是魔法厨房的配方板!决定了验证码的口味和形状~ (๑>ᴗ<๑)
设计原因:
- 使用小写字母+数字:覆盖常见验证码类型
- 长度设为4:平衡难度(太短容易破解,太长训练困难)
train_flag
:方便随时切换训练/测试模式喵~
🍳 数据生成器
def generate_data(test_size, train_size):
for dataset in [("test",test_size), ("train",train_size)]:
for _ in range(dataset[1]):
# 随机撒料+时间戳防撞名
image_text = "".join(random.sample(captcha_array, captcha_size))
image_path = f"./datasets/{dataset[0]}/{image_text}_{int(time.time())}.png"
ImageCaptcha().write(image_text, image_path)
功能说明:
像自动饼干模具!🍪 批量制作训练和测试用的验证码图片~
设计细节:
- 分层存储:test/train分开放,避免数据污染
- 时间戳命名:防止同名文件覆盖
- random.sample:确保字符不重复,提高数据质量
文件结构:
datasets/ ├─test/ └─train/
🔮 编码转换术
def text2vec(text):
vec = torch.zeros(captcha_size, len(captcha_array))
for i in range(len(text)):
vec[i, captcha_array.index(text[i])] = 1 # 点亮对应位置
def vec2text(vec):
return "".join([captcha_array[i] for i in torch.argmax(vec, dim=1)])
功能说明:
就像字符和魔法阵的翻译器!(ノ>ω<)ノ
text2vec
:把"a1b2"变成[[0,1,0,...],[1,0,0,...],...]vec2text
:把神经网络输出变回字符串
为什么重要:
- 神经网络只能吃数字饼干,所以要转换格式
- One-hot编码适合多分类任务,每个字符独立判断
🎁 自定义数据集
class my_dataset(Dataset):
def __init__(self, root_dir):
self.image_path = [os.path.join(root_dir, name) for name in os.listdir(root_dir)]
self.transforms = transforms.Compose([
transforms.ToTensor(), # 转张量
transforms.Resize((60, 160)), # 统一尺寸
transforms.Grayscale() # 去颜色干扰
])
def __getitem__(self, index):
image = self.transforms(Image.open(path)) # 打开并处理图片
label = path.split("/")[-1].split("_")[0] # 从文件名提取标签
return image, text2vec(label).flatten() # 返回数据对
魔法流程:
- ToTensor:把图片变成0-1之间的数字矩阵
- Resize:所有图片统一大小,神经网络才能批量处理
- Grayscale:去掉颜色信息,降低复杂度(彩色通道可能影响识别效果喵~)
设计亮点:
- 直接从文件名提取标签,不需要额外标注文件
- 使用PIL的懒加载:只有调用__getitem__时才读图,节省内存
🧠 神经网络结构
class vkmodel(nn.Module):
def __init__(self):
self.layers = nn.Sequential(
nn.Conv2d(1,64,3,padding=1), nn.ReLU(), nn.MaxPool2d(2), # 第一魔法阵
nn.Conv2d(64,128,3,padding=1), nn.ReLU(), nn.MaxPool2d(2), # 升级通道数
... # 更多层
nn.Linear(4096, captcha_size*len(captcha_array)) # 最终输出
)
每层作用:
- Conv2d:提取局部特征(比如边缘、曲线)
- ReLU:引入非线性,让网络能学习复杂模式
- MaxPool:缩小特征图尺寸,增强鲁棒性
- Flatten:把多维特征拉平成向量
- Dropout:随机失活,防止过拟合(像定期清理记忆喵~)
参数设计:
- 逐步增加通道数(64→128→256→512):由简单到复杂特征
- 最后一层输出
4*36=144
个节点:对应4个字符*36种可能
🚂 训练小火车
vkmodel = vkmodel().cuda() # 召唤GPU精灵!
optim = Adam(vkmodel.parameters(), lr=0.001) # 智能导航仪
for epoch in range(10):
for images, labels in DataLoader(...):
images, labels = images.cuda(), labels.cuda() # 数据送上GPU
loss = loss_fn(vkmodel(images), labels) # 计算误差
optim.zero_grad(), loss.backward(), optim.step() # 三步魔法!
关键技术:
- GPU加速:利用显卡并行计算,提速10倍以上!
- Adam优化器:自动调整学习率,比普通SGD更聪明
- batch_size=32:平衡内存消耗和梯度稳定性
- 10个epoch:经过实验不会过拟合的最佳迭代次数
训练日志: print(f"当前loss:{loss.item():.4f}")
# 保留四位小数更精准观察收敛
🔍 预测与验证
m = torch.load("model.pth").cuda()
correct = 0
for images, labels in DataLoader(...):
output = m(images).view(-1, 36) # 变形为(4,36)
if vec2text(labels) == vec2text(output):
correct +=1 # 庆祝猜对啦!🎉
核心技巧:
view(-1, 36)
:将模型输出的144维向量拆成4个36维(每个字符对应一个)- 使用与训练时相同的
vec2text
:保证解码一致性 - batch_size=1:测试时逐个验证更准确
为什么重要:
- 测试集要完全独立于训练集,才能反映真实效果
- 正确率计算方式:整体匹配(四个字符全对才算对)