Initial commit: CardioAI - 心血管疾病智能辅助系统

This commit is contained in:
Zhengzong
2026-01-30 18:09:22 +08:00
commit aa51c1f4ed
10 changed files with 1227 additions and 0 deletions

54
module2_predictor/app.py Normal file
View File

@@ -0,0 +1,54 @@
from flask import Flask, request, jsonify, render_template
import pandas as pd
import joblib
app = Flask(__name__)
# 加载模型
model_path = "cardio_predictor_model.pkl"
model = joblib.load(model_path)
@app.route('/')
def home():
return render_template('index.html')
@app.route('/predict_cardio', methods=['POST'])
def predict_cardio():
try:
# 接收JSON请求数据
data = request.json
# 验证输入特征数量
required_features = ['age', 'gender', 'height', 'weight', 'ap_hi', 'ap_lo',
'cholesterol', 'gluc', 'smoke', 'alco', 'active']
if not all(feature in data for feature in required_features):
return jsonify({'error': '缺少必要的特征值'}), 400
# 转换为DataFrame
input_data = pd.DataFrame([data])
# 执行与训练时相同的特征工程
input_data['age_years'] = round(input_data['age'] / 365.25, 0)
input_data['bmi'] = input_data['weight'] / ((input_data['height'] / 100) ** 2)
# 删除原始age字段
input_data = input_data.drop('age', axis=1)
# 预测
probability = model.predict_proba(input_data)[0][1]
prediction = int(model.predict(input_data)[0])
# 返回结果
response = {
'probability': float(probability),
'prediction': prediction
}
return jsonify(response)
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

View File

@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CardioAI - 心血管疾病预测</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input[type="number"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
button {
background-color: #4CAF50;
color: white;
padding: 15px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin-top: 20px;
}
button:hover {
background-color: #45a049;
}
.result {
margin-top: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 5px;
border-left: 4px solid #4CAF50;
}
.error {
margin-top: 30px;
padding: 20px;
background-color: #ffebee;
border-radius: 5px;
border-left: 4px solid #f44336;
color: #c62828;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .form-group {
flex: 1;
}
@media (max-width: 768px) {
.form-row {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<h1>CardioAI - 心血管疾病预测</h1>
<form id="predictionForm">
<div class="form-row">
<div class="form-group">
<label for="age">年龄 (天)</label>
<input type="number" id="age" name="age" min="10000" max="25000" required>
</div>
<div class="form-group">
<label for="gender">性别</label>
<select id="gender" name="gender" required>
<option value="1"></option>
<option value="2"></option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="height">身高 (cm)</label>
<input type="number" id="height" name="height" min="100" max="250" required>
</div>
<div class="form-group">
<label for="weight">体重 (kg)</label>
<input type="number" id="weight" name="weight" min="30" max="200" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="ap_hi">收缩压</label>
<input type="number" id="ap_hi" name="ap_hi" min="90" max="250" required>
</div>
<div class="form-group">
<label for="ap_lo">舒张压</label>
<input type="number" id="ap_lo" name="ap_lo" min="60" max="150" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="cholesterol">胆固醇</label>
<select id="cholesterol" name="cholesterol" required>
<option value="1">正常</option>
<option value="2">偏高</option>
<option value="3">很高</option>
</select>
</div>
<div class="form-group">
<label for="gluc">血糖</label>
<select id="gluc" name="gluc" required>
<option value="1">正常</option>
<option value="2">偏高</option>
<option value="3">很高</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="smoke">吸烟</label>
<select id="smoke" name="smoke" required>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div class="form-group">
<label for="alco">饮酒</label>
<select id="alco" name="alco" required>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div class="form-group">
<label for="active">运动</label>
<select id="active" name="active" required>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
</div>
<button type="submit">预测心血管疾病风险</button>
</form>
<div id="result" class="result" style="display: none;">
<h2>预测结果</h2>
<p><strong>预测结果:</strong> <span id="predictionValue"></span></p>
<p><strong>患病概率:</strong> <span id="probabilityValue"></span>%</p>
<p><strong>风险评估:</strong> <span id="riskAssessment"></span></p>
</div>
<div id="error" class="error" style="display: none;">
<h2>错误</h2>
<p id="errorMessage"></p>
</div>
</div>
<script>
document.getElementById('predictionForm').addEventListener('submit', function(e) {
e.preventDefault();
// 隐藏之前的结果和错误
document.getElementById('result').style.display = 'none';
document.getElementById('error').style.display = 'none';
// 收集表单数据
const formData = {
age: parseInt(document.getElementById('age').value),
gender: parseInt(document.getElementById('gender').value),
height: parseInt(document.getElementById('height').value),
weight: parseInt(document.getElementById('weight').value),
ap_hi: parseInt(document.getElementById('ap_hi').value),
ap_lo: parseInt(document.getElementById('ap_lo').value),
cholesterol: parseInt(document.getElementById('cholesterol').value),
gluc: parseInt(document.getElementById('gluc').value),
smoke: parseInt(document.getElementById('smoke').value),
alco: parseInt(document.getElementById('alco').value),
active: parseInt(document.getElementById('active').value)
};
// 发送POST请求
fetch('/predict_cardio', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
if (data.error) {
// 显示错误
document.getElementById('errorMessage').textContent = data.error;
document.getElementById('error').style.display = 'block';
} else {
// 显示结果
document.getElementById('predictionValue').textContent = data.prediction === 1 ? '有心血管疾病风险' : '无心血管疾病风险';
document.getElementById('probabilityValue').textContent = (data.probability * 100).toFixed(2);
// 风险评估
let riskAssessment = '';
if (data.probability < 0.3) {
riskAssessment = '低风险';
} else if (data.probability < 0.6) {
riskAssessment = '中等风险';
} else {
riskAssessment = '高风险';
}
document.getElementById('riskAssessment').textContent = riskAssessment;
document.getElementById('result').style.display = 'block';
}
})
.catch(error => {
document.getElementById('errorMessage').textContent = '预测过程中发生错误,请稍后重试';
document.getElementById('error').style.display = 'block';
console.error('Error:', error);
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,71 @@
import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier
import joblib
# 数据路径
DATA_PATH = "D:\\AI_Coding\\data\\心血管疾病.xlsx"
# 加载和预处理数据
def load_and_preprocess_data():
# 加载数据
df = pd.read_excel(DATA_PATH)
# 特征工程
# 将age转换为年四舍五入
df['age_years'] = round(df['age'] / 365.25, 0)
# 计算BMI
df['bmi'] = df['weight'] / ((df['height'] / 100) ** 2)
# 异常值处理
# 删除舒张压≥收缩压的记录
df = df[df['ap_lo'] < df['ap_hi']]
# 删除血压极端异常值
df = df[(df['ap_hi'] >= 90) & (df['ap_hi'] <= 250)]
df = df[(df['ap_lo'] >= 60) & (df['ap_lo'] <= 150)]
# 删除id和原始age字段
df = df.drop(['id', 'age'], axis=1)
return df
# 加载数据
df = load_and_preprocess_data()
# 定义特征和目标变量
X = df.drop('cardio', axis=1)
y = df['cardio']
# 定义特征类型
continuous_features = ['age_years', 'height', 'weight', 'ap_hi', 'ap_lo', 'bmi']
categorical_features = ['gender', 'cholesterol', 'gluc', 'smoke', 'alco', 'active']
# 创建预处理器
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), continuous_features),
('cat', OneHotEncoder(), categorical_features)
]
)
# 创建完整的Pipeline
pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', XGBClassifier(random_state=42))
])
# 训练模型
pipeline.fit(X, y)
# 保存模型
model_path = "module2_predictor\\cardio_predictor_model.pkl"
joblib.dump(pipeline, model_path)
print(f"模型已保存到: {model_path}")
print("训练完成!")