前言

也是跟着网上的教程实现了自己(最近)的第一个人工智能工具,所以来记录下过程。

~(部分内容经过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)

功能说明
像自动饼干模具!🍪 批量制作训练和测试用的验证码图片~

设计细节

  1. 分层存储:test/train分开放,避免数据污染
  2. 时间戳命名:防止同名文件覆盖
  3. random.sample:确保字符不重复,提高数据质量
  4. 文件结构

    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()    # 返回数据对

魔法流程

  1. ToTensor:把图片变成0-1之间的数字矩阵
  2. Resize:所有图片统一大小,神经网络才能批量处理
  3. 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))  # 最终输出
        )

每层作用

  1. Conv2d:提取局部特征(比如边缘、曲线)
  2. ReLU:引入非线性,让网络能学习复杂模式
  3. MaxPool:缩小特征图尺寸,增强鲁棒性
  4. Flatten:把多维特征拉平成向量
  5. 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:测试时逐个验证更准确

为什么重要

  • 测试集要完全独立于训练集,才能反映真实效果
  • 正确率计算方式:整体匹配(四个字符全对才算对)

最后修改:2025 年 02 月 22 日
如果觉得我的文章对你有用,请随意赞赏