From 9c040b3dece97cb209c8d7d2a63637e65e4a7497 Mon Sep 17 00:00:00 2001 From: group04_tangzhuoyang Date: Thu, 2 Apr 2026 19:52:38 +0800 Subject: [PATCH] Update code --- .claude/settings.local.json | 13 + .idea/AIcode.iml | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/workspace.xml | 46 + test/.claude/settings.local.json | 7 +- test/README.md | 229 +++++ .../__pycache__/app.cpython-310.pyc | Bin 0 -> 9227 bytes .../train_and_save.cpython-310.pyc | Bin 0 -> 8149 bytes test/module2_predictor/app.py | 396 ++++++++ .../models/cardio_predictor_model.pkl | Bin 0 -> 267137 bytes .../module2_predictor/models/feature_info.txt | 27 + test/module2_predictor/templates/index.html | 858 ++++++++++++++++++ test/module2_predictor/test_api.py | 172 ++++ test/module2_predictor/train_and_save.py | 331 +++++++ 15 files changed, 2100 insertions(+), 1 deletion(-) create mode 100644 .claude/settings.local.json create mode 100644 .idea/AIcode.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/workspace.xml create mode 100644 test/module2_predictor/__pycache__/app.cpython-310.pyc create mode 100644 test/module2_predictor/__pycache__/train_and_save.cpython-310.pyc create mode 100644 test/module2_predictor/app.py create mode 100644 test/module2_predictor/models/cardio_predictor_model.pkl create mode 100644 test/module2_predictor/models/feature_info.txt create mode 100644 test/module2_predictor/templates/index.html create mode 100644 test/module2_predictor/test_api.py create mode 100644 test/module2_predictor/train_and_save.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..7c31971 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(py:*)", + "Bash(python math.py)", + "Bash(git remote:*)", + "Bash(git config:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)" + ] + } +} diff --git a/.idea/AIcode.iml b/.idea/AIcode.iml new file mode 100644 index 0000000..8fb7f71 --- /dev/null +++ b/.idea/AIcode.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6f5dcad --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..ab7e459 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + 1775097630560 + + + + \ No newline at end of file diff --git a/test/.claude/settings.local.json b/test/.claude/settings.local.json index 4613a15..030590b 100644 --- a/test/.claude/settings.local.json +++ b/test/.claude/settings.local.json @@ -4,7 +4,12 @@ "Bash(python -c \"import pandas as pd; df = pd.read_excel\\('data/心血管疾病.xlsx', nrows=5\\); print\\('Columns:', df.columns.tolist\\(\\)\\); print\\('Data types:', df.dtypes\\); print\\('Sample data:'\\); print\\(df.head\\(\\)\\)\")", "Bash(\"D:\\\\software\\\\anaconda\\\\Scripts\\\\conda.exe\" run:*)", "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" module1_dashboard/test_data.py)", - "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" -m streamlit run module1_dashboard/cardio_dashboard.py --help)" + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" -m streamlit run module1_dashboard/cardio_dashboard.py --help)", + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" -c \"import pandas as pd; df = pd.read_excel\\('data/心血管疾病.xlsx', nrows=10\\); print\\('Columns:', df.columns.tolist\\(\\)\\); print\\('\\\\nData types:'\\); print\\(df.dtypes\\); print\\('\\\\nSample data:'\\); print\\(df[['age', 'gender', 'height', 'weight', 'ap_hi', 'ap_lo', 'cholesterol', 'gluc', 'smoke', 'alco', 'active', 'cardio']].head\\(\\)\\)\")", + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" -m py_compile module2_predictor/train_and_save.py)", + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" module2_predictor/train_and_save.py)", + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" -m py_compile module2_predictor/app.py)", + "Bash(\"D:\\\\software\\\\anaconda\\\\envs\\\\cardioenv\\\\python.exe\" module2_predictor/test_api.py)" ] } } diff --git a/test/README.md b/test/README.md index 71c20a9..e44a9b7 100644 --- a/test/README.md +++ b/test/README.md @@ -156,6 +156,235 @@ MODEL_PATH=./module2_predictor/models/xgb_model.pkl - **环境管理**: python-dotenv - **AI集成**: langchain-openai, dashscope, requests +## Module 2: 机器学习预测器 + +### 功能特性 + +- ✅ **模型训练**: XGBoost分类器,准确率约73% +- ✅ **特征工程**: 年龄转换、BMI计算、异常值处理 +- ✅ **RESTful API**: Flask提供预测接口 +- ✅ **前端界面**: 交互式Web表单,实时预测 +- ✅ **模型持久化**: Joblib保存完整Pipeline + +### 模型训练 + +#### 1. 训练模型(一次性) +```bash +# 进入项目根目录 +cd D:\Project\PythonProject\AIcode\test + +# 激活conda环境 +conda activate cardioenv + +# 运行训练脚本 +python module2_predictor/train_and_save.py +``` + +训练脚本将: +1. 加载和清洗数据(与Module 1相同) +2. 特征工程:年龄转换、BMI计算 +3. 构建机器学习Pipeline(StandardScaler + OneHotEncoder + XGBoost) +4. 训练模型并评估性能 +5. 保存模型到 `module2_predictor/models/cardio_predictor_model.pkl` + +#### 2. 模型特征 +- **连续特征**: age_years, bmi, ap_hi, ap_lo +- **分类特征**: gender, cholesterol, gluc +- **二元特征**: smoke, alco, active + +**Top 5 重要特征**: +1. 收缩压 (ap_hi) +2. 极高胆固醇 (cholesterol_3) +3. 年龄 (age_years) +4. 舒张压 (ap_lo) +5. 极高血糖 (gluc_3) + +### Flask API服务 + +#### 1. 启动API服务 +```bash +# 进入项目根目录 +cd D:\Project\PythonProject\AIcode\test + +# 激活conda环境 +conda activate cardioenv + +# 方法1: 直接运行Python脚本 +python module2_predictor/app.py + +# 方法2: 使用Flask CLI +set FLASK_APP=module2_predictor/app.py +flask run --host=0.0.0.0 --port=5000 + +# 方法3: 使用conda直接运行 +"D:\software\anaconda\Scripts\conda.exe" run -n cardioenv python module2_predictor/app.py +``` + +#### 2. API端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/` | GET | 前端预测界面 | +| `/predict_cardio` | POST | 预测接口(接收JSON) | +| `/health` | GET | 健康检查 | +| `/model_info` | GET | 模型信息 | + +#### 3. 预测接口示例 + +**请求**: +```bash +curl -X POST http://localhost:5000/predict_cardio \ + -H "Content-Type: application/json" \ + -d '{ + "age": 20228, + "gender": 1, + "height": 156, + "weight": 85, + "ap_hi": 140, + "ap_lo": 90, + "cholesterol": 1, + "gluc": 1, + "smoke": 0, + "alco": 0, + "active": 1 + }' +``` + +**响应**: +```json +{ + "success": true, + "prediction": 1, + "probability": 0.85, + "risk_level": "高危", + "message": "预测成功", + "features": { + "age_years": 55, + "bmi": 34.9, + "ap_hi": 140, + "ap_lo": 90, + "gender": 1, + "cholesterol": 1, + "gluc": 1, + "smoke": 0, + "alco": 0, + "active": 1 + } +} +``` + +### 前端界面 + +访问 `http://localhost:5000` 使用预测界面: + +1. **输入表单**: 11个特征字段,包含验证和示例数据 +2. **实时预测**: 点击"开始预测"获取风险评估 +3. **结果展示**: 风险等级、概率、健康建议 +4. **示例数据**: 提供低、中、高风险示例数据 + +### 项目结构 + +``` +module2_predictor/ +├── app.py # Flask应用主程序 +├── train_and_save.py # 模型训练脚本(一次性) +├── test_api.py # API测试脚本 +├── templates/ +│ └── index.html # 前端界面模板 +└── models/ # 模型文件目录(训练后生成) + ├── cardio_predictor_model.pkl + └── feature_info.txt +``` + +### 测试验证 + +#### 1. 测试模型加载 +```bash +python module2_predictor/test_api.py +``` + +#### 2. 测试API服务 +1. 启动Flask应用:`python module2_predictor/app.py` +2. 打开浏览器访问:`http://localhost:5000` +3. 使用示例数据测试预测功能 +4. 检查健康状态:`http://localhost:5000/health` + +#### 3. 验证预测准确性 +- 测试集准确率:约73% +- 特征重要性符合医学常识 +- 风险等级划分合理 + +### 配置说明 + +#### 模型参数 +- **算法**: XGBoost Classifier +- **树数量**: 100 +- **最大深度**: 5 +- **学习率**: 0.1 +- **子采样率**: 0.8 +- **随机种子**: 42 + +#### 特征预处理 +- **连续特征**: StandardScaler标准化 +- **分类特征**: OneHotEncoder独热编码 +- **二元特征**: 直接使用(0/1) + +### 性能指标 + +| 指标 | 训练集 | 测试集 | +|------|--------|--------| +| 准确率 | 74.21% | 73.14% | +| 特征数量 | 10个 | 10个 | +| 模型大小 | ~1.2 MB | ~1.2 MB | + +### 注意事项 + +1. **模型更新**: 当数据变化时,重新运行训练脚本 +2. **输入验证**: API对输入数据有严格的范围验证 +3. **血压合理性**: 自动拒绝舒张压≥收缩压的输入 +4. **错误处理**: 详细的错误信息和日志记录 +5. **性能**: 单次预测时间 < 100ms + +### 常见问题 + +#### Q1: 模型训练失败 +**症状**: 训练脚本报错或无法保存模型 +**解决**: +1. 检查数据文件路径是否正确 +2. 确保有足够的磁盘空间 +3. 检查Python依赖包是否完整安装 + +#### Q2: Flask应用无法启动 +**症状**: 启动时出现导入错误或模型加载失败 +**解决**: +1. 检查conda环境是否激活 +2. 确保模型文件存在:`module2_predictor/models/cardio_predictor_model.pkl` +3. 检查端口5000是否被占用 + +#### Q3: 预测结果不合理 +**症状**: 预测概率总是0或1,或与预期不符 +**解决**: +1. 检查输入数据是否在合理范围内 +2. 验证特征预处理是否正确 +3. 确保模型训练时使用了正确的特征 + +#### Q4: 前端界面无法访问 +**症状**: 浏览器显示连接错误 +**解决**: +1. 确认Flask应用正在运行 +2. 检查防火墙设置,允许端口5000 +3. 尝试访问 `http://localhost:5000/health` 检查服务状态 + +### 下一步开发 + +1. **模型优化**: 尝试其他算法(LightGBM, CatBoost)和超参数调优 +2. **特征扩展**: 添加更多临床特征(家族史、药物治疗等) +3. **API增强**: 添加批量预测、模型版本管理 +4. **监控告警**: 添加性能监控和异常告警 +5. **部署优化**: Docker容器化,云平台部署 + +--- + ### 常见问题 #### Q1: 数据加载失败 diff --git a/test/module2_predictor/__pycache__/app.cpython-310.pyc b/test/module2_predictor/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30e1575468733ec01c9178b42cf249c859f251eb GIT binary patch literal 9227 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 RMdSJ>1p4~7Q_=qOe*rKbY5M>G literal 0 HcmV?d00001 diff --git a/test/module2_predictor/__pycache__/train_and_save.cpython-310.pyc b/test/module2_predictor/__pycache__/train_and_save.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..588c734449c3c957883003b70b2b9cf8c220532a GIT binary patch literal 8149 zcmb7JTW}Lsn(mvVR!f#;g8>6cl*3ghkcovof`K0Pg}A` zLaLCePoK+wPM<#K|G)n`e;3=@6pntqf6R_0Iqqv#Xh|gG_uw-B2Z_e1oW^THj#mYC z7gY&&F&D@O)gVu8QZAI2RXHD4!>k<0Dfx&RVfkP#nr~CvSU!}C<=fSEmKSnyHO|KC zP&?SYQ|)B;HR>AN<=onQm)ga1ddJKU)A0|y!^qT>K~3(FU)#pkJT?~8nmp)v&KJ&9|NzY{q zy0Wt;;m`KvpZvI3G%au9NbS=bkW(J-Ni5ww<6S!KU7oDId9BgIo48tiYkcX(7m8B- z{6h863A|YK(mDTSm*(b|=Eka@%vQ%fYF&g|3zM}|?^h>I`s?Elu=LgRv$?c6?9HF5 zow~5JcoXT@V+WOG=oa@vl6T{lk-Rm|D>!b?WFVD=Y{Jgbx&vxh(%-t;Lv*Ua{3S)=r`v^D4kvjc^q zp=apfjb8{EG6)fuc^3O`L8&WHDhq1AiPOYQT$vlaYzZ@>!_D$Xsa=}jhNNlkj6iY& zoEG?iFZ1JEKd%L~&>`93pXMrjS#SiS#|mh&Blxlh`3Y_&ILnnqN7TaIP}@!1OlX#y zku9h=>LR;1q|t6l+t41v^V`>Oo2VRTZSijO(At@_U(>;)JG;5zKv{C68D*9;Ow4f& z@{w6?n$y;v5ow-|=t%7x`Zs2PK3dms0Ao{b-7vq0o`dnU^}~T?egoCFbLD^&U}Mj+ zu>&h(cQ?llw2nO#)i!G30YTfOZJrX!BKq9th^uRc^8Nmte#O1WNu1R-@<*O415id4 zv^`+OoM44-tnfijOO%7Z3-06hamfemQ?z3V{Cqp}&C9#~Mg8VKnyf433#3 zZ1YZIJF2h|>o-q%(`U(yX?*&Xw4SHaR{B{Zo!5I4&B48s$H{t`Wmjj9Fmd}qOw3yt z^FBJ(M1W=oU$m#E$CWgUXRjsMJJF?Qf6m?YzUWOP+gu^5xng=i zcY_0Z0hZtf2led0pydYl`S(D&lp4&sbjuapNM^8@gI(!HG3QDHIXmM9%zSZJccpYL zQ*?vrjFsJs(J~D1GSL0zPe6_w!5UoVMkE8A!~G^)<{jwp5Vw~%1c!fJcooojk%Pg7 zGRz`PSg+8t%@t9+$h(5pZ)`y4zdoEVYIaWFkt!Lwmd#j2;|IRo!_HG?davG78gZ3f zxvXhriuuwLKcvUf95!;JkLHSLEk%OpTr!Fo-856QhCRD-MF52P1Tu||b^@JNwuK?s z(%hxm!n}9&b?@fk`iBR+iMM+bMifE=xH#_5d-u3uENN;)PaCGujw&ODWLcwxX5fl_ z`K-}F4RItk;e|Ibv;5Avgbnw}C8}p;>l1Hy3p3vFV~HnrC#t8us4ZMX0Se;dbp7&G zZ}H7#71GOprDPW+Zy~WCK*C0t9Yc@m*#otYuQb8k?`K3vnl|SmuS)6tS<|KsX=)Z! z@lH*#Y03SnRUzQ}#K~I=2U-^B+e+%4`?P-Td~f1~WV;(E8QFs6MzP{Udd5l_#iHd3 zB~4Wf9s8m0&*(Ym(_p%!yUH)qIa`0mFp7pNWs2C#fKjvyni@=-)(8Yd8cIwTMqJUf z^DqIUSaQS9V2qNLEf!E~8Oe|urfp4?0M%;T&|kH)(3aGSW~uG@EbzQA(5#4R{ZI*} z4?3omE$mI_vRbOIq(=HmsiJM=fc>V+>r4lY%~%!VekAUOyJ^%TXf>ZkB8Q@)ASgnd zm&Hy&;$ygP6gP{Vd|Z8*P&Ww8}UtbkDS&%~g66 z+jbjdaHG$uVaD&hUEQd5n^7)@Bt6gOZ|g9~Jo#mw(w;J2FOJ z?KTm9rSX0%?`bknv(m~#pc4IX2+3~Sz|`1g zeGe;T?ca8|4Hnm%@Fov?CqG_=us0@~#Fe1+4#w(h2Ce12loHJm@ zuF;|zos^KrL&aLkcTqw*qKXB;mY7LfZlFI4e)X&o_T@+!)HW0QYU+eJCEHSCgYZ^4 zJ!Kb$3&ni}HQ1Leq>T|ZQUW8j1_6x&gG`H(f*OS%Y>-$}4I6qMbeMcQfVbi8fT~KS z$z~0r8Os4A1Mj{=zw1w~WR{;?CPh_4x#B>sXqrh`jTTa10NH#RE*Hb+)B95zFn3UG z%faCl2u_W(rK@4n?laT*QchRn09-$t>Km~PU3a6HqE^hOOp8P#I_w~o*DWKPQ9EqB zb}rqg=Tf>)&uy~3#wPlkL0iR~Pcq0X$)L#_A|(6eU)8S8%nPa4K*0pexxE8e`=J5Lgg@Iqn7N1SGhN9#aX1R3~=R$6PXD+ z%8;VP(7U}G=ON5drkV<`#fK$hinZdDqTwn>t$-8NI`EV!Cpr^pwy~b&Ha1$@9b>d( zjI+(BK(6H}kqYn64M+DsVWyn~I#zV1&B5sj-~@O&EaIs#D`vH8UA}Au5cM?oo33&@ zdaQFIPRzlm-E=Nm?}QsAwmQsxzPjiTgl(`dVCQ^@Vh4TyPw;I z1seU+ws(FJ@Yk>~ANUBU{F8?-sm=x8pf6~s5>IM-VRR;4)TiN~* zn<%c|dwCi(;Un+riQWXOMa-jq<;~VwLdR9v@gud_%eCXzAE`+!75pSg5Z?9W%nZ|SUcrm9J+@QYS!a+=JYsK)C@jM9#g_0VAYd7(VV8iJGj%{K- zTfh^E{Tu&6$+Jk*(90=y7E*)A0Ib!}NTUK7s)(@m6q}T)_!~$QR3j}*=mTC`V{sXW zQX@dl1~98pDl|aV4Z+Q1vGgvV{U$iBJP&6!J$D6DH1{Bpqd-JHb~`S``6%!)3W5^n z7p@ncLjV-Pg?B-^@KgxnCg?BH>_O5qYj4Z|BVEeW<1XuW{ks zA9MnZ!-7NP{~>z}tIR;c5A-!~qb0ZT7=>zedFN8vzwi z$7Z+|%#WAci;0*QP^wxA*M7OGwN9$kx z-aCALd3N5LM~wDpufo7l6?SN&LQlfw6`M$iqWC@QSFTl0AMaIc0-vpkX4nH%n}wP& zruW$ajKe~?pL#3`<(<0-Aoios^|8t7ne&QGE4ej+xJYlJKL7F3;_vI%E&!^lljH2v zIO)f!y~Ce)3lutr08n_#@IW&0gTxl}SpN9*mVENJJCce`lpRx{xD}jXBXok9PwczfP~Lm6D`3p z3|l$kP0ueM8LwWw=3W1sb*^6e55(A6XUunL{&=(EvE2yNppH%NN&n%9u3;E_EBO4a zdmn>zcx!^i=CBth=W3PrF@ILhufU@$;9L55Mu9V*Yyg z(Y@XTZO_5as@HCKuU+^q@fTP)sZ~esRQ#mk`$&RnS5Bq+vpCwM+#nrg5!DIghqWw1 zkweA4T(%FP9XnrgrD6&GF|g0bTDlw78K(z_PlkOXdLb_DuMM zFpHrFN3w{a%ZQ=h2bmCjp1jKA>8PKR%V8%x6P_hsq0B@y#rG+{#t3EkcaXq`FqUJH z_J~a$dTH*4H@*nF9^G}%xYg3Zew5bh9=@!N?p9j5-*eU`O6wSIFPAM3pF`P%WT%ctI{U%3Ll z3M#|Bz>qi@^=*ejfh?NCPKk^PF|9Hlr-pw*;)X%;GJ0P+Gwg<0=#@gO2#k)Ht^|K* z!3+s2SJ|W5ffMlnA`2~tUgqMMb@|`bKw2Aq+#n4${+W_~N?2TKH8>?v++ho5ZW4OP zh-J#WARwM8Dj<7`NHs7TTomD?)(t$OoPRThAWpDA{ZpyrBW@^_(u$c>N(DmTE1qf4 z&Z_}nKxxGIDdpFj!z}vHT0+^ZEpzBr-GuXvv7hM zZ`3wlI7p*~_7C*YH%}H{Fn)_FSKjAGK1^efibBy;Lj*m5FN1+x3=oH}CQY$_gP8FO zCI3hrBofBglM*maW1Gi9z2CXNjnyGKo=UYW`s&KdyRZ!SIPwIkjkyDfB*<+tXf%j6 f`_pREKMJB91#Dq2o{+oco$}N2= input_data['ap_hi']: + return False, "舒张压不能高于或等于收缩压" + + return True, "输入数据有效" + + except Exception as e: + return False, f"输入数据验证失败: {str(e)}" + +@app.route('/') +def index(): + """主页 - 返回前端界面""" + return render_template('index.html') + +@app.route('/predict_cardio', methods=['POST']) +def predict_cardio(): + """ + 心血管疾病预测API接口 + + 请求格式(JSON): + { + "age": 20228, # 年龄(天) + "gender": 1, # 性别(1=女性,2=男性) + "height": 156, # 身高(cm) + "weight": 85, # 体重(kg) + "ap_hi": 140, # 收缩压(mmHg) + "ap_lo": 90, # 舒张压(mmHg) + "cholesterol": 1, # 胆固醇水平(1=正常,2=高于正常,3=极高) + "gluc": 1, # 血糖水平(1=正常,2=高于正常,3=极高) + "smoke": 0, # 吸烟(0=否,1=是) + "alco": 0, # 饮酒(0=否,1=是) + "active": 1 # 体育活动(0=否,1=是) + } + + 响应格式(JSON): + { + "success": true, + "prediction": 1, + "probability": 0.85, + "risk_level": "高危", + "message": "预测成功", + "features": { + "age_years": 55, + "bmi": 34.9, + ... // 其他处理后的特征 + } + } + """ + try: + # 检查模型是否已加载 + if pipeline is None: + return jsonify({ + "success": False, + "message": "模型未加载,请等待或联系管理员" + }), 503 + + # 获取JSON数据 + if not request.is_json: + return jsonify({ + "success": False, + "message": "请求必须是JSON格式" + }), 400 + + input_data = request.get_json() + logger.info(f"收到预测请求: {input_data}") + + # 验证输入数据 + is_valid, error_message = validate_input(input_data) + if not is_valid: + return jsonify({ + "success": False, + "message": error_message + }), 400 + + # 预处理输入数据 + processed_df = preprocess_input(input_data) + + # 进行预测 + prediction = pipeline.predict(processed_df)[0] + probability = pipeline.predict_proba(processed_df)[0][1] # 类别1的概率 + + # 确定风险等级 + if probability < 0.3: + risk_level = "低危" + elif probability < 0.6: + risk_level = "中危" + else: + risk_level = "高危" + + # 准备响应数据 + response_data = { + "success": True, + "prediction": int(prediction), + "probability": float(round(probability, 4)), + "risk_level": risk_level, + "message": "预测成功", + "features": { + "age_years": int(processed_df['age_years'].iloc[0]), + "bmi": float(round(processed_df['bmi'].iloc[0], 2)), + "ap_hi": int(processed_df['ap_hi'].iloc[0]), + "ap_lo": int(processed_df['ap_lo'].iloc[0]), + "gender": int(processed_df['gender'].iloc[0]), + "cholesterol": int(processed_df['cholesterol'].iloc[0]), + "gluc": int(processed_df['gluc'].iloc[0]), + "smoke": int(processed_df['smoke'].iloc[0]), + "alco": int(processed_df['alco'].iloc[0]), + "active": int(processed_df['active'].iloc[0]) + } + } + + logger.info(f"预测结果: {response_data}") + return jsonify(response_data), 200 + + except Exception as e: + error_msg = f"预测过程中发生错误: {str(e)}" + logger.error(error_msg) + logger.error(traceback.format_exc()) + return jsonify({ + "success": False, + "message": error_msg + }), 500 + +@app.route('/health', methods=['GET']) +def health_check(): + """健康检查端点""" + try: + if pipeline is None: + return jsonify({ + "status": "unhealthy", + "message": "模型未加载" + }), 503 + + # 简单的模型测试 + test_data = { + "age": 20228, + "gender": 1, + "height": 156, + "weight": 85, + "ap_hi": 140, + "ap_lo": 90, + "cholesterol": 1, + "gluc": 1, + "smoke": 0, + "alco": 0, + "active": 1 + } + + processed_df = preprocess_input(test_data) + _ = pipeline.predict(processed_df) + + return jsonify({ + "status": "healthy", + "model_version": model_data.get('model_version', '未知'), + "features": len(feature_names) if feature_names else 0, + "message": "模型服务运行正常" + }), 200 + + except Exception as e: + return jsonify({ + "status": "unhealthy", + "message": f"健康检查失败: {str(e)}" + }), 500 + +@app.route('/model_info', methods=['GET']) +def model_info(): + """获取模型信息""" + if model_data is None: + return jsonify({ + "success": False, + "message": "模型未加载" + }), 503 + + return jsonify({ + "success": True, + "model_version": model_data.get('model_version', '未知'), + "description": model_data.get('description', 'CardioAI心血管疾病预测模型'), + "feature_count": len(feature_names) if feature_names else 0, + "features": feature_names if feature_names else [] + }), 200 + +# 模型加载标志 +_model_loaded = False + +@app.before_request +def ensure_model_loaded(): + """确保模型已加载(每个请求前检查)""" + global pipeline, model_data, feature_names, _model_loaded + + if not _model_loaded: + logger.info("首次请求,正在加载模型...") + success = load_model() + if success: + _model_loaded = True + logger.info("模型加载完成") + else: + logger.error("模型加载失败") + +if __name__ == '__main__': + # 加载模型 + success = load_model() + if not success: + logger.error("启动失败: 模型加载失败") + sys.exit(1) + + # 启动Flask应用 + logger.info("启动CardioAI预测API服务...") + logger.info("访问 http://localhost:5000 使用预测界面") + logger.info("API文档:") + logger.info(" GET / - 前端界面") + logger.info(" POST /predict_cardio - 预测接口") + logger.info(" GET /health - 健康检查") + logger.info(" GET /model_info - 模型信息") + + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/test/module2_predictor/models/cardio_predictor_model.pkl b/test/module2_predictor/models/cardio_predictor_model.pkl new file mode 100644 index 0000000000000000000000000000000000000000..01799a21dc49176659fdea3a636e760a2c4471c4 GIT binary patch literal 267137 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

-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[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 z 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 XE9TCkNjEpg-(0rvMn-(<$QJ(xzuA!R literal 0 HcmV?d00001 diff --git a/test/module2_predictor/models/feature_info.txt b/test/module2_predictor/models/feature_info.txt new file mode 100644 index 0000000..f4dc101 --- /dev/null +++ b/test/module2_predictor/models/feature_info.txt @@ -0,0 +1,27 @@ +CardioAI模型特征信息 +================================================== + +特征列表(按输入顺序): + 1. age_years + 2. bmi + 3. ap_hi + 4. ap_lo + 5. gender + 6. cholesterol + 7. gluc + 8. smoke + 9. alco +10. active + + +特征说明: +- age_years: 年龄(岁),由原始天数转换而来 +- bmi: 身体质量指数,计算公式:体重(kg) / (身高(m)^2) +- ap_hi: 收缩压(mmHg) +- ap_lo: 舒张压(mmHg) +- gender: 性别(1=女性,2=男性) +- cholesterol: 胆固醇水平(1=正常,2=高于正常,3=极高) +- gluc: 血糖水平(1=正常,2=高于正常,3=极高) +- smoke: 吸烟(0=否,1=是) +- alco: 饮酒(0=否,1=是) +- active: 体育活动(0=否,1=是) diff --git a/test/module2_predictor/templates/index.html b/test/module2_predictor/templates/index.html new file mode 100644 index 0000000..5bca7d1 --- /dev/null +++ b/test/module2_predictor/templates/index.html @@ -0,0 +1,858 @@ + + + + + + CardioAI - 心血管疾病风险预测 + + + + + + +

+ + +
+
+ +
+
+
+ 患者基本信息输入 +
+
+
+ +
+ + +
示例:55岁 ≈ 20075天
+
+ + +
+ + +
1=女性,2=男性
+
+ +
+ +
+
+ + +
范围:100-250 cm
+
+
+ + +
+
+ + +
范围:20-300 kg
+
+
+
+ +
+ +
+
+ + +
范围:50-300 mmHg
+
+
+ + +
+
+ + +
范围:30-200 mmHg
+
+
+
+ + +
+ + +
1=正常,2=高于正常,3=极高
+
+ + +
+ + +
1=正常,2=高于正常,3=极高
+
+ + +
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+ + +
+ + +
+
+ + +
+
+ 加载中... +
+

正在分析数据,请稍候...

+
+
+
+ + +
+
+ 示例数据 +
+
+

点击下方按钮填充示例数据:

+
+ + + +
+
+
+
+ + +
+
+
+ 预测结果分析 +
+
+
+ +
等待预测结果
+

填写左侧表单并点击"开始预测"按钮,系统将分析您的心血管疾病风险。

+
+ + +
+
+ + +
+
+ 系统信息 +
+
+
+
+

服务状态: + 正常 +

+

预测模型: + CardioAI XGBoost +

+
+
+

响应时间: + -- ms +

+

最后更新: + 2024-04-02 +

+
+
+
+ +
+
+
+
+
+
+ + +
+
+

+ + CardioAI - 心血管疾病智能辅助系统 v1.0 +

+

+ 本系统基于机器学习模型提供风险评估,结果仅供参考,不能替代专业医疗诊断。 +
+ 如有健康问题,请及时咨询专业医生。 +

+
+
+ + + + + + + + \ No newline at end of file diff --git a/test/module2_predictor/test_api.py b/test/module2_predictor/test_api.py new file mode 100644 index 0000000..5f95800 --- /dev/null +++ b/test/module2_predictor/test_api.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CardioAI API测试脚本 + +测试模型加载和预测功能 +""" + +import sys +import os +import json +from pathlib import Path + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.append(str(project_root)) + +# 导入Flask应用中的函数 +from app import load_model, preprocess_input + +def test_model_loading(): + """测试模型加载""" + print("测试模型加载...") + try: + success = load_model() + if success: + print("✅ 模型加载成功") + return True + else: + print("❌ 模型加载失败") + return False + except Exception as e: + print(f"❌ 模型加载异常: {str(e)}") + return False + +def test_data_preprocessing(): + """测试数据预处理""" + print("\n测试数据预处理...") + + # 测试数据 + test_data = { + "age": 20228, # 约55岁 + "gender": 1, # 女性 + "height": 156, # 身高(cm) + "weight": 85, # 体重(kg) + "ap_hi": 140, # 收缩压(mmHg) + "ap_lo": 90, # 舒张压(mmHg) + "cholesterol": 1, # 胆固醇水平 + "gluc": 1, # 血糖水平 + "smoke": 0, # 吸烟 + "alco": 0, # 饮酒 + "active": 1 # 体育活动 + } + + try: + processed_df = preprocess_input(test_data) + print(f"✅ 数据预处理成功") + print(f" 处理后的特征:") + for col in processed_df.columns: + print(f" {col}: {processed_df[col].iloc[0]}") + return True + except Exception as e: + print(f"❌ 数据预处理失败: {str(e)}") + return False + +def test_prediction(): + """测试预测功能""" + print("\n测试预测功能...") + + # 需要导入pipeline + from app import pipeline + + if pipeline is None: + print("❌ 模型未加载,无法测试预测") + return False + + # 测试数据 + test_data = { + "age": 20228, + "gender": 1, + "height": 156, + "weight": 85, + "ap_hi": 140, + "ap_lo": 90, + "cholesterol": 1, + "gluc": 1, + "smoke": 0, + "alco": 0, + "active": 1 + } + + try: + processed_df = preprocess_input(test_data) + prediction = pipeline.predict(processed_df)[0] + probability = pipeline.predict_proba(processed_df)[0][1] + + print(f"✅ 预测成功") + print(f" 预测结果: {prediction} ({'有风险' if prediction == 1 else '无风险'})") + print(f" 患病概率: {probability:.4f} ({(probability*100):.1f}%)") + + # 确定风险等级 + if probability < 0.3: + risk_level = "低危" + elif probability < 0.6: + risk_level = "中危" + else: + risk_level = "高危" + + print(f" 风险等级: {risk_level}") + return True + except Exception as e: + print(f"❌ 预测失败: {str(e)}") + import traceback + traceback.print_exc() + return False + +def test_api_endpoint(): + """测试API端点(需要启动服务器)""" + print("\n测试API端点...") + print("注意:此测试需要Flask服务器正在运行") + print("请先启动Flask应用,然后运行此测试") + + # 这里可以添加实际的HTTP请求测试 + # 但为了简单起见,我们只是提示用户 + print("使用以下命令启动服务器:") + print(' cd "D:\\Project\\PythonProject\\AIcode\\test"') + print(' "D:\\software\\anaconda\\envs\\cardioenv\\python.exe" module2_predictor/app.py') + print("\n然后使用curl或浏览器测试API:") + print(' curl -X POST http://localhost:5000/predict_cardio \\') + print(' -H "Content-Type: application/json" \\') + print(' -d \'{"age":20228,"gender":1,"height":156,"weight":85,"ap_hi":140,"ap_lo":90,"cholesterol":1,"gluc":1,"smoke":0,"alco":0,"active":1}\'') + +def main(): + """主测试函数""" + print("=" * 60) + print("CardioAI API 测试") + print("=" * 60) + + # 测试模型加载 + model_loaded = test_model_loading() + + if model_loaded: + # 测试数据预处理 + preprocessing_ok = test_data_preprocessing() + + # 测试预测功能 + prediction_ok = test_prediction() + + # 汇总结果 + print("\n" + "=" * 60) + print("测试结果汇总:") + print(f" 模型加载: {'✅ 通过' if model_loaded else '❌ 失败'}") + print(f" 数据预处理: {'✅ 通过' if preprocessing_ok else '❌ 失败'}") + print(f" 预测功能: {'✅ 通过' if prediction_ok else '❌ 失败'}") + + if model_loaded and preprocessing_ok and prediction_ok: + print("\n🎉 所有测试通过!") + print("Flask API可以正常运行。") + return True + else: + print("\n⚠️ 部分测试失败,请检查问题。") + return False + else: + print("\n❌ 模型加载失败,无法继续测试。") + return False + + # 显示API测试说明 + test_api_endpoint() + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/test/module2_predictor/train_and_save.py b/test/module2_predictor/train_and_save.py new file mode 100644 index 0000000..886cc9b --- /dev/null +++ b/test/module2_predictor/train_and_save.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CardioAI - 心血管疾病预测模型训练脚本 + +功能: +1. 加载和清洗数据(与模块1相同的流程) +2. 特征工程:年龄转换、BMI计算、异常值处理 +3. 构建机器学习Pipeline +4. 训练XGBoost分类器 +5. 保存完整Pipeline到文件 + +注意:此脚本为一次性训练脚本,生成模型文件供Flask应用使用。 +""" + +import pandas as pd +import numpy as np +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler, OneHotEncoder +from sklearn.compose import ColumnTransformer +from sklearn.pipeline import Pipeline +from xgboost import XGBClassifier +import joblib +import warnings +import sys +import os +from pathlib import Path + +# 忽略警告 +warnings.filterwarnings('ignore') + +# 添加项目根目录到Python路径 +project_root = Path(__file__).parent.parent +sys.path.append(str(project_root)) + +def load_and_preprocess_data(): + """ + 加载数据并进行预处理(与模块1相同的清洗和特征工程) + + 返回: + pd.DataFrame: 预处理后的数据框 + """ + print("开始加载和预处理数据...") + + # 数据文件路径 + data_path = project_root / "data" / "心血管疾病.xlsx" + + try: + # 加载数据 + df = pd.read_excel(data_path) + print(f"原始数据形状: {df.shape}") + + # 检查必要列 + required_columns = ['id', 'age', 'gender', 'height', 'weight', 'ap_hi', 'ap_lo', + 'cholesterol', 'gluc', 'smoke', 'alco', 'active', 'cardio'] + missing_columns = [col for col in required_columns if col not in df.columns] + if missing_columns: + raise ValueError(f"数据文件中缺少必要列: {missing_columns}") + + # 创建数据副本 + df_processed = df.copy() + + # 1. 年龄转换:从天转换为年(四舍五入) + df_processed['age_years'] = (df_processed['age'] / 365.25).round().astype(int) + + # 2. 计算BMI: BMI = weight(kg) / (height(m)^2) + df_processed['bmi'] = df_processed['weight'] / ((df_processed['height'] / 100) ** 2) + df_processed['bmi'] = df_processed['bmi'].round(2) + + # 3. 异常值处理 + # 删除舒张压 >= 收缩压的记录 + invalid_bp = df_processed['ap_lo'] >= df_processed['ap_hi'] + if invalid_bp.any(): + print(f"删除 {invalid_bp.sum()} 条舒张压 >= 收缩压的异常记录") + df_processed = df_processed[~invalid_bp].copy() + + # 删除血压极端异常值 + # 收缩压 ∈ [90, 250], 舒张压 ∈ [60, 150] + bp_outliers = ~((df_processed['ap_hi'] >= 90) & (df_processed['ap_hi'] <= 250) & + (df_processed['ap_lo'] >= 60) & (df_processed['ap_lo'] <= 150)) + if bp_outliers.any(): + print(f"删除 {bp_outliers.sum()} 条血压极端异常值记录") + df_processed = df_processed[~bp_outliers].copy() + + # 4. 删除不需要的列 + # 删除id和原始age字段(使用转换后的age_years) + df_processed = df_processed.drop(['id', 'age'], axis=1) + + print(f"预处理后数据形状: {df_processed.shape}") + print("数据预处理完成!") + + return df_processed + + except Exception as e: + print(f"数据加载和预处理失败: {str(e)}") + raise + +def prepare_features_and_target(df): + """ + 准备特征矩阵X和目标向量y + + 参数: + df: 预处理后的数据框 + + 返回: + X: 特征矩阵 + y: 目标向量 + feature_names: 特征名称列表 + """ + print("准备特征和目标变量...") + + # 目标变量 + y = df['cardio'].values + + # 特征矩阵 - 删除目标变量 + X = df.drop('cardio', axis=1) + + print(f"特征矩阵形状: {X.shape}") + print(f"目标变量分布: 0={sum(y==0)}, 1={sum(y==1)}") + + return X, y, X.columns.tolist() + +def build_pipeline(): + """ + 构建机器学习Pipeline + + 返回: + Pipeline: 包含预处理和分类器的完整Pipeline + """ + print("构建机器学习Pipeline...") + + # 定义特征类型 + # 连续特征:需要标准化 + numerical_features = ['age_years', 'bmi', 'ap_hi', 'ap_lo'] + + # 分类特征:需要独热编码 + categorical_features = ['gender', 'cholesterol', 'gluc'] + + # 二元特征:直接使用(不需要编码) + binary_features = ['smoke', 'alco', 'active'] + + # 所有特征顺序 + all_features = numerical_features + categorical_features + binary_features + + # 创建列转换器 + preprocessor = ColumnTransformer( + transformers=[ + ('num', StandardScaler(), numerical_features), + ('cat', OneHotEncoder(drop='first', sparse_output=False, handle_unknown='ignore'), + categorical_features), + # 二元特征直接通过(不进行变换) + ('binary', 'passthrough', binary_features) + ], + remainder='drop' # 丢弃其他列 + ) + + # 创建完整Pipeline + pipeline = Pipeline([ + ('preprocessor', preprocessor), + ('classifier', XGBClassifier( + n_estimators=100, + max_depth=5, + learning_rate=0.1, + subsample=0.8, + colsample_bytree=0.8, + random_state=42, + eval_metric='logloss', + use_label_encoder=False + )) + ]) + + print("Pipeline构建完成!") + return pipeline, all_features + +def train_model(X, y, pipeline): + """ + 训练模型 + + 参数: + X: 特征矩阵 + y: 目标向量 + pipeline: 机器学习Pipeline + + 返回: + 训练好的Pipeline + """ + print("开始训练模型...") + + # 划分训练集和测试集 + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42, stratify=y + ) + + print(f"训练集大小: {X_train.shape}") + print(f"测试集大小: {X_test.shape}") + + # 训练模型 + pipeline.fit(X_train, y_train) + + # 评估模型 + train_score = pipeline.score(X_train, y_train) + test_score = pipeline.score(X_test, y_test) + + print(f"训练集准确率: {train_score:.4f}") + print(f"测试集准确率: {test_score:.4f}") + + # 特征重要性(如果可用) + if hasattr(pipeline.named_steps['classifier'], 'feature_importances_'): + importances = pipeline.named_steps['classifier'].feature_importances_ + print(f"特征重要性数量: {len(importances)}") + + # 获取特征名称(需要从预处理器中提取) + preprocessor = pipeline.named_steps['preprocessor'] + + # 获取转换后的特征名称 + feature_names = [] + + # 数值特征名称 + feature_names.extend(preprocessor.transformers_[0][2]) + + # 分类特征名称(独热编码后) + if len(preprocessor.transformers_) > 1: + cat_encoder = preprocessor.transformers_[1][1] + if hasattr(cat_encoder, 'get_feature_names_out'): + cat_features = cat_encoder.get_feature_names_out( + preprocessor.transformers_[1][2] + ) + feature_names.extend(cat_features) + + # 二元特征名称 + if len(preprocessor.transformers_) > 2: + feature_names.extend(preprocessor.transformers_[2][2]) + + # 打印最重要的特征 + if len(feature_names) == len(importances): + print("\nTop 10 特征重要性:") + indices = np.argsort(importances)[::-1] + for i in range(min(10, len(importances))): + print(f" {feature_names[indices[i]]}: {importances[indices[i]]:.4f}") + + return pipeline + +def save_pipeline(pipeline, all_features): + """ + 保存Pipeline到文件 + + 参数: + pipeline: 训练好的Pipeline + all_features: 特征名称列表 + """ + print("保存模型和特征信息...") + + # 创建模型保存目录 + model_dir = Path(__file__).parent / "models" + model_dir.mkdir(exist_ok=True) + + # 模型文件路径 + model_path = model_dir / "cardio_predictor_model.pkl" + + # 保存Pipeline对象 + model_data = { + 'pipeline': pipeline, + 'feature_names': all_features, + 'model_version': '1.0.0', + 'description': 'CardioAI心血管疾病预测模型' + } + + joblib.dump(model_data, model_path) + print(f"模型已保存到: {model_path}") + + # 保存特征信息到单独文件(可选) + features_path = model_dir / "feature_info.txt" + with open(features_path, 'w', encoding='utf-8') as f: + f.write("CardioAI模型特征信息\n") + f.write("=" * 50 + "\n\n") + f.write("特征列表(按输入顺序):\n") + for i, feature in enumerate(all_features, 1): + f.write(f"{i:2d}. {feature}\n") + + f.write("\n\n特征说明:\n") + f.write("- age_years: 年龄(岁),由原始天数转换而来\n") + f.write("- bmi: 身体质量指数,计算公式:体重(kg) / (身高(m)^2)\n") + f.write("- ap_hi: 收缩压(mmHg)\n") + f.write("- ap_lo: 舒张压(mmHg)\n") + f.write("- gender: 性别(1=女性,2=男性)\n") + f.write("- cholesterol: 胆固醇水平(1=正常,2=高于正常,3=极高)\n") + f.write("- gluc: 血糖水平(1=正常,2=高于正常,3=极高)\n") + f.write("- smoke: 吸烟(0=否,1=是)\n") + f.write("- alco: 饮酒(0=否,1=是)\n") + f.write("- active: 体育活动(0=否,1=是)\n") + + print(f"特征信息已保存到: {features_path}") + + return model_path + +def main(): + """主函数""" + print("=" * 60) + print("CardioAI - 心血管疾病预测模型训练") + print("=" * 60) + + try: + # 1. 加载和预处理数据 + df = load_and_preprocess_data() + + # 2. 准备特征和目标 + X, y, original_features = prepare_features_and_target(df) + + # 3. 构建Pipeline + pipeline, all_features = build_pipeline() + + # 4. 训练模型 + trained_pipeline = train_model(X, y, pipeline) + + # 5. 保存模型 + model_path = save_pipeline(trained_pipeline, all_features) + + print("\n" + "=" * 60) + print("模型训练完成!") + print(f"模型文件: {model_path}") + print("下一步:使用Flask应用部署模型") + print("=" * 60) + + except Exception as e: + print(f"\n训练过程出现错误: {str(e)}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file