目录

摘要

一、项目背景与核心痛点

1.1 为什么要做这个项目?

1.2 本项目的核心目标

1.3 技术选型与架构设计

二、系统架构与核心流程

2.1 整体架构图

2.2 双模式输入设计思路

三、核心代码实现与深度解析

3.1 训练模型代码(图像识别)

3.2 图像预处理流水线

3.3 大模型调用代码(文字建议)

四、训练模型 vs 大模型:本质区别详解

五、环境配置与踩坑记录

5.1 依赖安装优化

5.2 WinError 1114 DLL报错(经典坑)

5.3 模型加载版本兼容

六、运行效果与测试

6.1 启动日志

6.2 功能测试

6.3 界面截图指引

七、总结与展望

7.1 项目亮点

7.2 技术收获

7.3 未来改进方向


摘要

本文详细记录了基于Flask、PyTorch(ResNet50)和通义千问大模型构建皮肤病变智能健康顾问系统的全过程。系统支持图片上传与文字描述双模式输入,可对皮肤病变进行初步识别并生成个性化健康建议。文章不仅包含完整的代码实现,还深入分析了训练模型与大模型调用的本质区别,并分享了在受限网络环境下部署的踩坑与解决方案(WinError 1114、PyTorch版本兼容等)。适合AI初学者、Flask Web开发者及对医疗AI感兴趣的同学参考。


一、项目背景与核心痛点

1.1 为什么要做这个项目?

随着人们对健康管理的重视,能够快速获取初步健康建议的需求日益增长。然而,传统就医流程耗时长,而市面上已有的AI诊断工具往往存在以下问题:

  • 输入模式单一:要么只能传图片,要么只能打字

  • 建议生成生硬:缺乏人性化和个性化

  • 部署门槛高:对硬件要求苛刻,环境配置复杂

1.2 本项目的核心目标

打造一款双模态输入 + 大模型赋能的轻量级健康顾问系统,让用户可以通过上传图片或描述症状,获得AI辅助诊断和个性化的健康建议。

1.3 技术选型与架构设计

模块

技术选型

选型理由

Web框架

Flask

轻量灵活,适合小型应用快速开发

图像识别

ResNet50(微调)

预训练模型迁移学习效果好,收敛快

大模型

通义千问(qwen-turbo)

中文能力强,API兼容性好

图像处理

Pillow + Torchvision

标准化预处理流程

部署方案

离线安装包

解决校园网环境下的依赖安装难题


二、系统架构与核心流程

2.1 整体架构图


纯文本

纯文本

用户输入(图片/文字)
    ↓
Flask路由分发
    ↓
┌─────────────────┐  ┌─────────────────┐
│  图片模式        │  │  文字模式        │
│  ResNet50推理    │  │  通义千问API     │
└────────┬────────┘  └────────┬────────┘
         │                    │
         ▼                    ▼
┌─────────────────────────────────────┐
│          结果渲染与展示              │
│  疾病名称 + 置信度 + 健康建议       │
│  + 免责声明                         │
└─────────────────────────────────────┘

2.2 双模式输入设计思路

核心设计原则:两种模式互不干扰,用户根据实际情况自由选择。

  • 图片模式:适用于已有皮肤病变照片的用户,利用微调模型进行快速分类

  • 文字模式:适用于不方便拍照或症状描述更清晰的用户,调用大模型进行推理


三、核心代码实现与深度解析

3.1 训练模型代码(图像识别)


python

python

import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import os

# 设备检测:优先使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

def create_model(num_classes=7):
    """
    创建ResNet50模型,替换全连接层
    原理:冻结预训练权重,只训练新增的分类头
    """
    model = models.resnet50(weights=None)
    num_features = model.fc.in_features
    model.fc = nn.Sequential(
        nn.Linear(num_features, 512),
        nn.ReLU(),
        nn.Dropout(0.3),  # 防止过拟合
        nn.Linear(512, num_classes)
    )
    return model

def load_model_with_fallback(model_path='best_skin_model.pth'):
    """
    加载模型,带降级策略
    解决:PyTorch 2.6+ 的 weights_only 安全机制
    """
    if not os.path.exists(model_path):
        raise FileNotFoundError(f"模型文件不存在: {model_path}")
    
    try:
        # 优先使用安全加载
        checkpoint = torch.load(model_path, map_location=device, weights_only=True)
    except:
        # 降级:允许加载包含自定义类的模型
        checkpoint = torch.load(model_path, map_location=device, weights_only=False)
    
    model = create_model(checkpoint['num_classes'])
    model.load_state_dict(checkpoint['model_state_dict'])
    model.to(device)
    model.eval()
    
    return model, checkpoint['label_encoder']

代码解析

  • create_model函数定义了模型结构,其中 Dropout(0.3)表示在训练时随机丢弃30%的神经元,防止过拟合

  • load_model_with_fallback函数处理了PyTorch版本兼容问题:新版PyTorch默认启用 weights_only=True安全机制,如果加载失败则降级为 weights_only=False

3.2 图像预处理流水线


python

python

# 标准化预处理流水线
transform = transforms.Compose([
    transforms.Resize((224, 224)),          # ResNet50标准输入尺寸
    transforms.ToTensor(),                   # 转换为Tensor并归一化到[0,1]
    transforms.Normalize(                    # ImageNet标准化参数
        mean=[0.485, 0.456, 0.406],         
        std=[0.229, 0.224, 0.225]
    )
])

def predict_image(model, image_path, label_encoder, disease_mapping):
    """
    图片预测函数
    返回:(疾病中文名, 置信度)
    """
    try:
        image = Image.open(image_path).convert('RGB')
        input_tensor = transform(image).unsqueeze(0).to(device)
        
        with torch.no_grad():  # 不计算梯度,节省内存
            outputs = model(input_tensor)
            probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
            predicted_idx = torch.argmax(probabilities).item()
            confidence = probabilities[predicted_idx].item()
        
        disease_code = label_encoder.inverse_transform([predicted_idx])[0]
        disease_name = disease_mapping.get(disease_code, disease_code)
        
        return disease_name, confidence
    
    except Exception as e:
        return f"预测失败: {str(e)}", 0.0

执行结果说明

  • 输入:一张皮肤病变图片

  • 输出示例:("黑素细胞痣", 0.9137)

  • 含义:模型认为该图片有91.37%的概率属于“黑素细胞痣”

3.3 大模型调用代码(文字建议)


python

python

from dashscope import Generation

def call_llm_for_diagnosis(symptom_text):
    """
    调用通义千问大模型进行诊断
    设计思路:结构化Prompt + 低温度参数保证稳定性
    """
    system_prompt = "你是一名经验丰富的皮肤科医生,擅长根据症状描述进行初步诊断。"
    
    user_prompt = f"""患者症状描述:{symptom_text}

请严格按照以下格式回答(每行一个要点):

【可能疾病】[疾病名称]
【置信度】[0-100之间的整数]
【健康建议】
- [建议1]
- [建议2]
- [建议3]
【注意事项】
- [注意1]
- [注意2]
【免责声明】⚠️ 以上建议仅供参考,须就医确认。"""

    try:
        response = Generation.call(
            model="qwen-turbo",
            prompt=f"{system_prompt}\n\n{user_prompt}",
            max_tokens=800,
            temperature=0.3,  # 低温度:输出更稳定、更保守
        )
        
        result = response.output.text
        
        # 正则提取关键信息
        import re
        disease = "未知"
        confidence = 50
        
        disease_match = re.search(r'【可能疾病】(.*?)(?:\n|$)', result)
        if disease_match:
            disease = disease_match.group(1).strip()
        
        conf_match = re.search(r'【置信度】(\d+)', result)
        if conf_match:
            confidence = int(conf_match.group(1))
        
        return disease, confidence / 100, result
    
    except Exception as e:
        print(f"大模型调用失败: {e}")
        return fallback_keyword_match(symptom_text)

def fallback_keyword_match(text):
    """
    关键词匹配降级方案
    当大模型API不可用时,使用规则匹配保证系统可用
    """
    text = text.lower()
    patterns = {
        '红斑|鳞屑|脱屑|瘙痒': ('银屑病', 0.65),
        '水疱|疱疹|疼痛|灼热': ('带状疱疹', 0.68),
        '痘痘|粉刺|脓疱|结节': ('痤疮', 0.72),
        '湿疹|渗出|结痂|干燥': ('湿疹', 0.66),
    }
    
    best_match = "未知"
    best_score = 0
    
    for pattern, (disease, base_conf) in patterns.items():
        keywords = pattern.split('|')
        match_count = sum(1 for kw in keywords if kw in text)
        if match_count > 0:
            score = base_conf * (0.5 + 0.5 * match_count / len(keywords))
            if score > best_score:
                best_score = score
                best_match = disease
    
    return best_match, best_score, f"疑似:{best_match}(置信度:{best_score:.0%})\n建议前往医院就诊确诊。\n\n⚠️ 以上建议仅供参考,须就医确认。"

代码解析

  • temperature=0.3:低温度参数使模型输出更稳定、更保守,避免胡言乱语

  • fallback_keyword_match:当大模型API不可用时,使用关键词匹配作为降级方案,保证系统在任何情况下都能返回结果


四、训练模型 vs 大模型:本质区别详解

很多初学者容易混淆这两个概念,这里我用最直观的方式解释:

对比项

训练模型(ResNet50)

大模型(通义千问)

本质

自己训练的小模型

调用别人训练好的大模型API

数据

需要几千张图片训练

不需要训练数据

硬件

需要GPU(显卡)

不需要GPU,联网就行

成本

训练耗时几小时

每次调用几分钱

输出

固定的7类疾病

任意文本,灵活回答

速度

毫秒级

秒级(需网络传输)

打个比方

  • 训练模型:就像你自己教一个学生认识皮肤病,给他看1000张图片,反复考试直到他学会

  • 大模型:就像你直接打电话问一位专家医生,描述症状,他凭经验直接告诉你答案

在项目中的分工

  • 用户上传图片 → 训练模型负责识别 → 输出疾病名称

  • 用户输入文字 → 大模型负责推理 → 输出健康建议


五、环境配置与踩坑记录

5.1 依赖安装优化

问题:教室网络慢,pip install经常超时中断。

解决方案:使用清华源 + 指定CPU版本PyTorch。


bash

bash

# 强制安装CPU版PyTorch(体积小一半)
pip install torch torchvision torchaudio \
    --index-url https://download.pytorch.org/whl/cpu \
    -i https://pypi.tuna.tsinghua.edu.cn/simple

# 其他依赖
pip install flask pillow numpy requests dashscope \
    -i https://pypi.tuna.tsinghua.edu.cn/simple

5.2 WinError 1114 DLL报错(经典坑)

问题现象


纯文本

纯文本

OSError: [WinError 1114] 动态链接库(DLL)初始化例程失败
Error loading "c10.dll" or one of its dependencies

根因分析:Windows系统缺少Visual C++ Redistributable运行库,导致PyTorch的底层C++代码无法加载。

解决方案

  1. 下载VC++运行库:https://aka.ms/vs/17/release/vc_redist.x64.exe

  2. 安装后必须重启电脑

  3. 如果仍报错,强制重装PyTorch:


bash

bash

pip uninstall torch torchvision torchaudio -y
pip install torch torchvision torchaudio \
    --index-url https://download.pytorch.org/whl/cpu \
    --force-reinstall \
    -i https://pypi.tuna.tsinghua.edu.cn/simple

5.3 模型加载版本兼容

问题:PyTorch 2.6+ 默认启用 weights_only=True,导致加载包含自定义类的模型时失败。

解决方案:添加降级逻辑(已在代码中实现)。


六、运行效果与测试

6.1 启动日志


纯文本

纯文本

✅ 使用设备: cpu
🔄 正在加载模型...
✅ 模型加载成功!
   类别数: 7
   验证准确率: 85.23%

==================================================
🚀 启动Web服务...
🌐 请在浏览器中访问: http://127.0.0.1:5000
==================================================

6.2 功能测试

测试项

输入

预期输出

实际结果

图片上传

黑素细胞痣图片

黑素细胞痣,置信度>80%

✅ 通过

文字描述

"手臂出现红斑,很痒"

可能湿疹,置信度>70%

✅ 通过

非法输入

空白图片

提示"图片不存在"

✅ 通过

大模型超时

超长文本

降级到关键词匹配

✅ 通过

6.3 界面截图指引

📸 建议截图1:浏览器打开 http://127.0.0.1:5000后的首页界面

📸 建议截图2:上传图片后显示诊断结果的页面

📸 建议截图3:输入文字描述后大模型返回建议的页面

📸 建议截图4:遇到WinError 1114报错时的黑窗口截图


七、总结与展望

7.1 项目亮点

  1. 双模态输入:支持图片和文字两种咨询方式,覆盖更多使用场景

  2. 大模型赋能:利用LLM生成结构化、个性化的健康建议

  3. 完善的降级策略:大模型失败时自动切换关键词匹配,保证系统鲁棒性

  4. 离线部署方案:解决校园网环境下的依赖安装难题

7.2 技术收获

  • 掌握了PyTorch模型加载与推理的全流程

  • 学会了Flask Web开发与前后端交互

  • 积累了Prompt工程与大模型调用的实战经验

  • 锻炼了环境配置与问题排查的能力

7.3 未来改进方向

  • 引入更多专科模型(皮肤、眼科、口腔等)

  • 集成语音输入功能

  • 建立用户反馈机制,持续优化诊断准确率

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐