医疗信息系统开发实验指导-实验11:基于人工智能算法实现智能化功能

194 阅读8分钟

一、实验目的

1、掌握百度AI Studio平台的基本使用方法。

2、学习ERNIE-UIE模型在信息抽取任务中的应用。

3、掌握Python模型封装和Java接口调用的技术。

4、实现中医症状信息抽取的功能开发。

二、实验学时

2学时

三、实验类型

综合性

四、实验需求

1、硬件

每⼈配备计算机1台,建议优先使⽤个⼈计算机开展实验。

2、软件

安装IntelliJ IDEA Community,安装Python 3.7+环境,安装Node.js环境。

3、⽹络

本地主机能够访问互联⽹和实验中⼼⽹络。

4、⼯具

⽆。

五、实验任务

1、注册百度AI Studio账号并创建项目。

2、在AI Studio平台上使用ERNIE-UIE模型

3、将uie-medical-base模型封装为Python接口

4、Java调用Python接口实现业务逻辑

5、前端调用Java接口实现主诉症状标准化

6、结合系统实现智能增强

六、实验内容及步骤

1、注册百度AI Studio账号并创建项目

步骤1:​访问AI Studio官网注册个人账号。

打开浏览器,访问网址 aistudio.baidu.com/,点击页面右上角的“登录/注册”按钮,选择使用百度账号进行登录,若尚未拥有账号则可注册新账号,具体操作可参考图1。

图1 注册AI Studio账号图2 查找ERNIE-UIE模型

步骤2:​查找ERNIE-UIE模型

登录后进入AI Studio控制台,点击左侧导航栏的“模型库”,接着在搜索框中输入“ERNIE-UIE”,如图2所示,找到对应的模型并点击进入其详情页,如图3所示。

图3 查看ERNIE-UIE模型的详情图4 查看快递单信息抽取的学习模型范例

步骤3:学习模型范例

点击“模型范例”中的“快递单信息抽取”,仔细阅读示例代码和说明文档,了解UIE模型的基本使用流程,如图4所示。

步骤4:创建项目并启动环境

点击"运行一下"按钮,系统会自动创建项目,如图5所示,点击启动环境,等待环境初始化完成(约1-2分钟)即可进入操作,如图6所示。

图5 创建项目图6 启动环境

💡 在选择运行环境时,建议优先选用包含GPU的环境,以便在进行模型微调时使用。

2、 在AI Studio平台上使用ERNIE-UIE模型

💡 参照参考项目中的文档说明,按要求依次执行操作。若执行失败,可尝试重试几次;特别是在环境准备阶段,依赖下载失败可能是由网络问题所致。

步骤1:点击代码前边的执行按钮,完成paddlenlp的环境准备,如图7、8所示。

图7 查看执行环境图8 执行代码,准备环境

步骤2:使用默认配置进行快递信息抽取

PaddleNLP已经封装了NLP的模型,可直接使用,具体示例如图9所示。

图9 使用默认模型抽取信息图10 轻度定制

步骤3:模型微调

使用默认配置抽取快递信息时,电话、详细地址类的数据没有抽取出来,因此可对模型进行微调,具体微调步骤如下。

  • 安装doccano(请勿在AI Studio内部运行,需下载至本地执行)
  • 初始化数据库和账户
  • 启动doccano服务
  • 运行doccano标注实体和关系

具体请参考项目的《5.1 数据标注》节。

本实验可使用已经标注好的数据执行模型微调,如图11、12所示。

图11 获取已标注数据图12 进行模型微调

步骤4:使用之前的数据和微调后的模型测试微调效果,如图13所示。

图13 验证微调效果

3、将uie-medical-base模型封装为Python接口

步骤1:配置Paddle运行环境,创建requirements.txt,记录所需要的依赖,并执行命令安装依赖。

# requirements.txt
flask==2.3.3
paddlenlp==2.6.0
requests==2.31.0
werkzeug==2.3.7
pip install -r requirements.txt

步骤2:创建model_loader.py文件配置模型专用方法。

# model_loader.py
from paddlenlp import Taskflow
import logging

class MedicalModel:
    def __init__(self):
        self.model = None
        self.schema = ['症状', '部位', '性质', '程度', '诱发因素', '缓解因素']
        
    def load_model(self):
        """加载医疗专用模型"""
        try:
            self.model = Taskflow('information_extraction', 
                                schema=self.schema, 
                                model='uie-medical-base')
            logging.info("医疗模型加载成功")
            return True
        except Exception as e:
            logging.error(f"模型加载失败: {e}")
            return False
    
    def predict(self, text):
        """症状信息抽取预测"""
        if not self.model:
            raise Exception("模型未加载")
        return self.model(text)

步骤3:创建app.py,使用Flask框架开发Web接口实现模型调用

# app.py
from flask import Flask, request, jsonify
from model_loader import MedicalModel
import logging

app = Flask(__name__)
medical_model = MedicalModel()

# 在应用启动时初始化模型
with app.app_context():
    medical_model.load_model()

@app.route('/api/health', methods=['GET'])
def health_check():
    """健康检查接口"""
    return jsonify({"status": "healthy", "model_loaded": medical_model.model is not None})

@app.route('/api/extract-symptoms', methods=['POST'])
def extract_symptoms():
    """症状信息抽取接口"""
    try:
        data = request.get_json()
        if not data or 'text' not in data:
            return jsonify({"error": "缺少text参数"}), 400
        
        text = data['text']
        if len(text.strip()) == 0:
            return jsonify({"error": "文本内容为空"}), 400
        
        # 调用模型进行预测
        result = medical_model.predict(text)
        
        return jsonify({
            "success": True,
            "data": result,
            "text": text
        })
        
    except Exception as e:
        logging.error(f"症状抽取失败: {str(e)}")
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

步骤4:启动项目,PaddleNLP将自动下载指定的模型并加载,具体运行记录如图13所示。

python app.py
图13 启动项目

步骤5:接口测试与验证

打开APIPost输入主诉数据,进行模型测试,如图14所示。

图14 测试接口执行

4、Java调用Python接口实现业务逻辑

步骤1:创建Spring Boot项目结构,示例代码如下。

<!-- pom.xml 依赖 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

步骤2:实现Python接口调用服务,示例代码如下。

// SymptomExtractionService.java
@Service
public class SymptomExtractionService {
    
    private static final Logger logger = LoggerFactory.getLogger(SymptomExtractionService.class);
    
    @Value("${python.api.url:http://localhost:5000}")
    private String pythonApiUrl;
    
    private final RestTemplate restTemplate;
    
    public SymptomExtractionService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder
            .setConnectTimeout(Duration.ofSeconds(30))
            .setReadTimeout(Duration.ofSeconds(60))
            .build();
    }
    
    public SymptomExtractionResult extractSymptoms(String text) {
        try {
            // 构建请求
            String url = pythonApiUrl + "/api/extract-symptoms";
            Map<String, String> request = new HashMap<>();
            request.put("text", text);
            
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            HttpEntity<Map<String, String>> entity = new HttpEntity<>(request, headers);
            
            // 发送请求
            ResponseEntity<SymptomExtractionResult> response = restTemplate.exchange(
                url, HttpMethod.POST, entity, SymptomExtractionResult.class);
            
            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                return response.getBody();
            } else {
                throw new SymptomExtractionException("Python服务调用失败");
            }
            
        } catch (Exception e) {
            logger.error("症状抽取服务调用异常", e);
            throw new SymptomExtractionException("症状抽取服务暂时不可用", e);
        }
    }
}

步骤3:定义数据模型,示例代码如下。

// SymptomExtractionResult.java
@Data
public class SymptomExtractionResult {
    private Boolean success;
    private String text;
    private List<SymptomEntity> data;
    private String error;
    
    @Data
    public static class SymptomEntity {
        private String type;
        private List<EntityInfo> entities;
    }
    
    @Data
    public static class EntityInfo {
        private String text;
        private Double probability;
        private Integer start;
        private Integer end;
    }
}

步骤3:创建RESTful API控制器,示例代码如下。

// SymptomController.java
@RestController
@RequestMapping("/api/symptoms")
@Validated
public class SymptomController {
    
    private final SymptomExtractionService extractionService;
    
    public SymptomController(SymptomExtractionService extractionService) {
        this.extractionService = extractionService;
    }
    
    @PostMapping("/extract")
    public ResponseEntity<?> extractSymptoms(@Valid @RequestBody SymptomRequest request) {
        try {
            SymptomExtractionResult result = extractionService.extractSymptoms(request.getText());
            return ResponseEntity.ok(result);
        } catch (SymptomExtractionException e) {
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body(Map.of("error", e.getMessage()));
        }
    }
    
    @Data
    public static class SymptomRequest {
        @NotBlank(message = "文本内容不能为空")
        @Size(max = 1000, message = "文本长度不能超过1000字符")
        private String text;
    }
}

5、前端调用Java接口实现主诉症状标准化

步骤1:实现症状标准化组件,示例代码如下。

推荐结合实验10通过语音识别录入主诉,后通过UIE抽取关键字。

<!-- SymptomStandardization.vue -->
<template>
  <div class="symptom-standardization">
    <h2>中医主诉症状标准化系统</h2>
    
    <!-- 症状输入区域 -->
    <div class="input-section">
      <textarea 
        v-model="symptomText" 
        placeholder="请输入患者主诉症状描述..."
        :disabled="loading"
        @keyup.enter="extractSymptoms"
      ></textarea>
      
      <div class="button-group">
        <button 
          @click="extractSymptoms" 
          :disabled="!symptomText || loading"
          class="primary-btn"
        >
          {{ loading ? '处理中...' : '症状标准化' }}
        </button>
        
        <button @click="clearInput" class="secondary-btn">
          清空
        </button>
      </div>
    </div>

    <!-- 标准化结果显示 -->
    <div v-if="results" class="results-section">
      <h3>标准化结果</h3>
      
      <div v-for="(result, index) in results" :key="index" class="result-category">
        <h4>{{ getCategoryName(result.type) }}</h4>
        <div v-for="entity in result.entities" :key="entity.text" class="entity-item">
          <span class="entity-text">{{ entity.text }}</span>
          <span class="confidence">(置信度: {{ (entity.probability * 100).toFixed(1) }}%)</span>
        </div>
      </div>
    </div>

    <!-- 错误提示 -->
    <div v-if="error" class="error-message">
      {{ error }}
    </div>

    <!-- 加载状态 -->
    <div v-if="loading" class="loading-spinner">
      正在分析症状信息...
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const symptomText = ref('')
const results = ref(null)
const loading = ref(false)
const error = ref('')

// API基础URL
const API_BASE = 'http://localhost:8080/api'

// 症状类型映射
const categoryMap = {
  '症状': '主要症状',
  '部位': '发病部位', 
  '性质': '症状性质',
  '程度': '严重程度',
  '诱发因素': '诱发因素',
  '缓解因素': '缓解因素'
}

const getCategoryName = (type) => {
  return categoryMap[type] || type
}

// 提取症状信息
const extractSymptoms = async () => {
  if (!symptomText.value.trim()) {
    error.value = '请输入症状描述'
    return
  }

  loading.value = true
  error.value = ''
  results.value = null

  try {
    const response = await axios.post(`${API_BASE}/symptoms/extract`, {
      text: symptomText.value
    })

    if (response.data.success) {
      results.value = response.data.data
    } else {
      error.value = '症状提取失败'
    }
  } catch (err) {
    error.value = err.response?.data?.error || '网络请求失败'
    console.error('症状提取错误:', err)
  } finally {
    loading.value = false
  }
}

// 清空输入
const clearInput = () => {
  symptomText.value = ''
  results.value = null
  error.value = ''
}
</script>

<style scoped>
.symptom-standardization {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.input-section textarea {
  width: 100%;
  height: 120px;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  resize: vertical;
}

.button-group {
  margin-top: 10px;
  display: flex;
  gap: 10px;
}

.primary-btn, .secondary-btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.primary-btn {
  background: #1890ff;
  color: white;
}

.primary-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.secondary-btn {
  background: #f5f5f5;
  color: #333;
}

.results-section {
  margin-top: 20px;
  padding: 15px;
  border: 1px solid #e8e8e8;
  border-radius: 4px;
}

.result-category {
  margin-bottom: 15px;
}

.entity-item {
  padding: 5px 0;
  border-bottom: 1px solid #f0f0f0;
}

.entity-text {
  font-weight: bold;
  color: #1890ff;
}

.confidence {
  color: #666;
  font-size: 12px;
}

.error-message {
  color: #ff4d4f;
  padding: 10px;
  background: #fff2f0;
  border-radius: 4px;
  margin-top: 10px;
}

.loading-spinner {
  text-align: center;
  padding: 20px;
  color: #1890ff;
}
</style>

步骤2:测试并验证功能

按照以下顺序依次启动系统:

  • 启动Python服务:python app.py
  • 启动Java服务:运行Spring Boot应用
  • 启动前端服务:npm run dev

测试症状信息正确抽取和分类,置信度显示合理,错误处理机制正常,界面交互流畅。

6、结合系统实现智能增强

步骤1:功能设计

结合系统功能实现基于智能算法的智能增强,例如症状、疾病信息的标准化或自行引入其他模型(如使用OCR模型实现处方内容识别)。

步骤2:编码实现

(1)使用百度AI Studio选择模型进行训练或者微调,形成专属的模型。

(2)实现模型的调用封装,需要正确处理各种输入格式,设置合适的超时机制和重试策略,保证服务的稳定性。

(3)开发业务处理逻辑,需要设计合理的数据结构来存储和传递分析结果,确保信息的完整性和准确性。

(4)实现友好的命令行交互界面,提供清晰的进度反馈和状态提示。优化结果展示格式,同时需要完善错误处理和异常提示机制。

步骤3:测试优化

验证每个功能模块的正确性,重点实现模型处理、API调用等,确保系统在各种情况下都能正常工作,特别是边界情况和异常场景。

七、实验考核

1、本课程实验考核方案

本课程实验考核采用【实验智能评】【实验随堂查】方式开展,根据不同的实验内容选择不同的考核方式。

【实验智能评】:实验完成后提交GitLab,通过自动化代码评审工具进行评分。

【实验随堂查】:在实验课上通过现场演示的方式向实验指导教师进行汇报,并完成现场问答交流。

2、本实验考核要求

本实验考核方式:实验智能评

实验10-12作为本课程第3次实验考核。

考核要求:

(1)学生通过GitLab提交实验成果:{此部分说明需要提交的内容}。

(2)由GitLab根据成果和交流情况综合评分。