Initial commit: CardioAI - 心血管疾病智能辅助系统
This commit is contained in:
54
module2_predictor/app.py
Normal file
54
module2_predictor/app.py
Normal 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)
|
||||
257
module2_predictor/templates/index.html
Normal file
257
module2_predictor/templates/index.html
Normal 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>
|
||||
71
module2_predictor/train_and_save.py
Normal file
71
module2_predictor/train_and_save.py
Normal 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("训练完成!")
|
||||
Reference in New Issue
Block a user