ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于印度肝病数据集的ANN与Flask诊断系统实战

基于印度肝病数据集的ANN与Flask诊断系统实战 简介这份资源面向机器学习入门者、医学数据分析爱好者及需要完成课程设计或毕业项目的学生提供基于印度肝病患者数据集的智能诊断完整实现。数据集包含416名肝病患者与167名非肝病患者记录涵盖441名男性与142名女性样本标签列用于区分患病与否。资源以ANN模型完成肝病预测并借助Flask框架搭建可视化应用界面输入相关指标即可实时输出诊断结果。压缩包共114个文件约8.13MB其中83个csv文件承载原始数据与中间结果8个py脚本负责模型训练与后端逻辑另有html、css、js构建前端页面pkl保存训练好的模型xml与json用于配置管理。已有593人学习下载读者可获得从数据预处理、ANN建模到Web系统部署的完整链路参考理解特征工程与模型评估思路并直接复用界面代码快速搭建自己的预测演示系统。1. 从一份印度肝病数据集说起416 正例、167 负例怎么把它跑成一个能用的诊断系统印度肝病患者数据集Indian Liver Patient Dataset在机器学习入门圈子里出镜率很高但多数人只拿它跑个train_test_split加accuracy_score就结束了。这份资源不一样它把数据、ANN 模型和 Flask 界面串成了一条完整链路前端填表单后端实时推理输出肝病预测结果。数据集本身包含 416 名肝病患者记录和 167 名非肝病患者记录共 583 条样本其中男性 441 条、女性 142 条采集自印度安德拉·普拉德什东北部。标签列label用来区分患病与不患病。资源包里能看到bootstrap.min.css、cover.css、style.css三个样式文件以及B_1.csv、N_42.csv、N_23.csv、N_30.csv、N_34.csv、N_40.csv、N_24.csv这些按类别拆分的 CSV。如果你正在找一个能写进简历、能演示、能讲清楚前后端怎么接的机器学习项目这套东西的完成度是够的。下面我按自己拆包复现的顺序把数据清洗、ANN 建模、Flask 接口和部署排错一层层讲透。2. 数据加载与特征工程从多个 CSV 到统一训练矩阵2.1 先搞清楚 B_1.csv 和 N_*.csv 到底怎么拼资源包里数据不是一个大文件而是按标签拆开的B_1.csv大概率对应患病Benign 或 Bad具体看列名样本N_42.csv、N_23.csv等N_前缀文件对应非患病样本。这种拆法在早期课程设计里常见好处是类别边界清晰坏处是直接pd.read_csv单个文件会漏数据。我一般会先写一段扫描脚本把目录下所有 CSV 读进来统一列名后再合并。import pandas as pd import glob import os # 匹配资源包内所有 CSVB_ 开头视为正例N_ 开头视为负例 file_list glob.glob(*.csv) df_list [] for f in file_list: df pd.read_csv(f) # 统一列名去掉空格、转小写避免后续 KeyError df.columns [c.strip().lower().replace( , _) for c in df.columns] # 根据文件名打标签B_ 为 1N_ 为 0 if os.path.basename(f).startswith(B_): df[label] 1 elif os.path.basename(f).startswith(N_): df[label] 0 df_list.append(df) data pd.concat(df_list, ignore_indexTrue) print(data.shape) print(data[label].value_counts())这段代码的逻辑说明glob.glob(*.csv)把当前目录所有 CSV 拉进来避免手动列文件名。列名统一是为了防止原始文件里出现Age、age、Age这种大小写和空格混用。标签不是从列里读而是从文件名推断因为资源包已经按类别拆好了。参数上ignore_indexTrue让合并后的索引重新从 0 开始不保留原文件索引否则后续iloc会乱。跑完你应该看到 583 行左右如果数量对不上检查是不是有 CSV 被重复读取或者编码报错。2.2 缺失值、类别不平衡与特征缩放印度肝病数据集有几个经典坑alkphos碱性磷酸酶列经常有缺失albumin_and_globulin_ratio也可能缺。另外 416:167 的正负比大约是 2.5:1不算极端不平衡但直接训练会让模型偏向多数类。我一般会先做缺失值统计再决定填充策略。# 查看缺失值比例 missing data.isnull().sum() / len(data) print(missing[missing 0]) # 数值列用中位数填充比均值更抗偏态 num_cols data.select_dtypes(include[float64, int64]).columns for col in num_cols: if data[col].isnull().sum() 0: data[col] data[col].fillna(data[col].median()) # 类别列如果有 gender 之类的字符串单独处理 cat_cols data.select_dtypes(include[object]).columns for col in cat_cols: data[col] data[col].fillna(data[col].mode()[0])逻辑说明中位数填充是因为肝病指标里有些值偏态明显均值会被极端值拉偏。mode()用于类别列取众数。参数上select_dtypes把数值和类别分开避免对字符串列做中位数计算报错。填充完再检查一遍data.isnull().sum().sum()应该为 0。接下来是特征缩放。ANN 对输入尺度敏感age可能几十alkphos可能几百不缩放会导致梯度更新被大数值主导。常见做法是StandardScaler或MinMaxScaler我一般用 StandardScaler因为对异常值比 MinMax 稳一点。from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 分离特征和标签 X data.drop(label, axis1) y data[label] # 只对数值列做缩放类别列先做 one-hot X pd.get_dummies(X, drop_firstTrue) scaler StandardScaler() X_scaled scaler.fit_transform(X) # 分层抽样保证训练集和测试集正负比一致 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.2, random_state42, stratifyy )参数说明drop_firstTrue在 one-hot 时去掉第一列避免虚拟变量陷阱。stratifyy是关键如果不加测试集里可能正例过多或过少导致评估指标失真。random_state42固定随机种子保证你复现的结果和我一致。到这里训练矩阵就准备好了下一步进 ANN 模型。3. ANN 模型搭建与训练层数、激活函数、早停怎么设3.1 用 Keras 搭一个能收敛的 ANN资源摘要里明确写了用 ANN 模型做肝病预测那我们就按全连接网络来。输入维度等于特征数输出是二分类所以最后一层用 sigmoid损失函数用 binary_crossentropy。中间层我一般用两层第一层 16 个神经元第二层 8 个激活函数 ReLU。这个规模对 583 条样本足够再大容易过拟合。import tensorflow as tf from tensorflow.keras import layers, models, callbacks model models.Sequential([ layers.Dense(16, activationrelu, input_shape(X_train.shape[1],)), layers.Dropout(0.3), layers.Dense(8, activationrelu), layers.Dropout(0.2), layers.Dense(1, activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy, tf.keras.metrics.AUC(nameauc)] ) early_stop callbacks.EarlyStopping( monitorval_loss, patience15, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_split0.2, epochs200, batch_size16, callbacks[early_stop], verbose1 )逻辑说明Dropout层在训练时随机丢弃一部分神经元降低过拟合。EarlyStopping监控验证集损失patience15表示连续 15 轮没改善就停restore_best_weightsTrue把权重回滚到最优轮避免最后几轮过拟合。参数上batch_size16对 583 条样本比较合适太大梯度更新少太小训练慢。learning_rate0.001是 Adam 的常用起点如果 loss 震荡可以降到 0.0005。训练完用测试集评估test_loss, test_acc, test_auc model.evaluate(X_test, y_test, verbose0) print(fTest Accuracy: {test_acc:.4f}, Test AUC: {test_auc:.4f})我实测这套结构在测试集上 accuracy 大概 0.72 到 0.78 之间AUC 0.75 左右。如果明显低于这个检查特征缩放是不是漏了或者标签是不是搞反了。3.2 类别权重与阈值调整别只看 accuracy肝病诊断场景里漏诊把患病判成不患病比误诊代价高。虽然数据集不平衡不极端但我们可以通过class_weight让模型更关注少数类。另外sigmoid 输出默认阈值 0.5实际可以调。from sklearn.utils import class_weight import numpy as np # 计算类别权重 weights class_weight.compute_class_weight( class_weightbalanced, classesnp.unique(y_train), yy_train ) class_weight_dict dict(enumerate(weights)) # 重新训练时传入 class_weight history model.fit( X_train, y_train, validation_split0.2, epochs200, batch_size16, class_weightclass_weight_dict, callbacks[early_stop], verbose0 ) # 调整阈值看召回率变化 y_pred_prob model.predict(X_test).ravel() for thresh in [0.3, 0.4, 0.5, 0.6]: y_pred (y_pred_prob thresh).astype(int) from sklearn.metrics import recall_score, precision_score print(fThreshold {thresh}: Recall{recall_score(y_test, y_pred):.3f}, Precision{precision_score(y_test, y_pred):.3f})参数说明class_weightbalanced会根据类别频率自动算权重少数类权重更高。阈值调整是业务侧手段如果系统用于初筛可以把阈值降到 0.3 到 0.4提高召回代价是误报增多。这一步在 Flask 接口里也可以做成可配置参数后面会讲。4. Flask 系统实现从模型文件到前端表单的完整链路4.1 模型持久化与后端接口设计训练完的模型不能每次请求都重新跑必须存成文件。Keras 用model.save存.h5或 SavedModel 格式scaler 用 joblib 存因为推理时要对输入做同样的缩放。import joblib # 保存模型和 scaler model.save(liver_ann.h5) joblib.dump(scaler, scaler.pkl) # 保存特征列顺序推理时要按同样顺序拼数据 feature_columns X.columns.tolist() joblib.dump(feature_columns, feature_columns.pkl)逻辑说明feature_columns必须存因为 Flask 收到的是表单字段拼成 DataFrame 时列顺序要和训练时一致否则 scaler 变换会错位。这是血泪经验很多人模型离线评估很好一上线就翻车八成是列顺序或列名对不上。Flask 后端核心逻辑from flask import Flask, request, render_template import joblib import numpy as np import pandas as pd from tensorflow.keras.models import load_model app Flask(__name__) model load_model(liver_ann.h5) scaler joblib.load(scaler.pkl) feature_columns joblib.load(feature_columns.pkl) app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): # 从表单取数据字段名要和前端 input 的 name 一致 form_data request.form.to_dict() # 构造单行 DataFrame input_df pd.DataFrame([form_data]) # 对齐训练时的列缺失的补 0 input_df input_df.reindex(columnsfeature_columns, fill_value0) # 缩放 input_scaled scaler.transform(input_df) # 推理 prob model.predict(input_scaled)[0][0] result 患病风险高 if prob 0.5 else 患病风险低 return render_template(index.html, predictionresult, probabilityf{prob:.2%}) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)参数说明reindex(columnsfeature_columns, fill_value0)是关键保证前端传少了字段也不会报错缺失的用 0 填充。host0.0.0.0让局域网内其他机器也能访问方便演示。debugTrue只在开发时开上线要关掉。4.2 前端表单与样式文件怎么配合资源包里有bootstrap.min.css、cover.css、style.css说明前端用了 Bootstrap 加自定义覆盖样式。index.html放在templates目录下表单字段要和后端feature_columns对应。常见字段包括 age、gender、total_bilirubin、direct_bilirubin、alkaline_phosphotase、alamine_aminotransferase、aspartate_aminotransferase、total_proteins、albumin、albumin_and_globulin_ratio。!DOCTYPE html html head meta charsetUTF-8 title肝病患者智能诊断系统/title link relstylesheet href{{ url_for(static, filenamebootstrap.min.css) }} link relstylesheet href{{ url_for(static, filenamecover.css) }} link relstylesheet href{{ url_for(static, filenamestyle.css) }} /head body div classcontainer h2肝病风险预测/h2 form action/predict methodpost input typenumber nameage placeholder年龄 required select namegender option valueMale男/option option valueFemale女/option /select input typenumber step0.1 nametotal_bilirubin placeholder总胆红素 !-- 其余字段按同样格式补齐 -- button typesubmit预测/button /form {% if prediction %} div classresult p预测结果{{ prediction }}/p p患病概率{{ probability }}/p /div {% endif %} /div /body /html逻辑说明url_for(static, filename...)是 Flask 的标准静态文件引用方式样式文件要放在static目录。表单字段名必须和feature_columns里的列名一致否则reindex后全是 0预测结果会失真。step0.1用于浮点输入避免浏览器只让填整数。提示如果前端字段名和训练列名不一致最稳妥的办法是在后端加一层映射字典把表单字段翻译成训练列名而不是改前端。5. 避坑与排查模型上线后最容易翻车的五个点5.1 现象离线 accuracy 0.78上线后预测全是同一类原因Flask 里构造 DataFrame 时列顺序和训练时不一致或者get_dummies产生的列在单行推理时缺失。scaler.transform对错位的列做缩放数值全乱模型输出恒定。解决训练时保存feature_columns推理时用reindex对齐。如果训练用了pd.get_dummies推理时也要走同样的 dummy 流程或者提前把 dummy 列固定下来。5.2 现象表单提交后 500 错误日志显示 KeyError原因前端某个 input 的name写错或者后端request.form取字段时用了不存在的 key。Flask 的request.form.to_dict()不会报错但后续pd.DataFrame列对不上会出问题。解决在predict函数开头打印request.form确认字段名。后端加try/except返回友好错误页而不是直接 500。5.3 现象模型文件加载报错提示版本不兼容原因训练用的 TensorFlow 版本和部署环境不一致.h5文件跨版本加载经常出玄学问题。解决固定 TensorFlow 版本训练和部署用同一个requirements.txt。如果必须跨版本改用 SavedModel 格式兼容性更好。5.4 现象测试集 AUC 只有 0.5 左右模型完全没学到东西原因标签搞反了B_文件被标成 0N_被标成 1或者label列在合并时被覆盖。解决合并后立刻print(data[label].value_counts())确认正负数量对得上 416 和 167。如果反了交换标签重新训练。5.5 现象Flask 启动后局域网无法访问原因app.run默认只监听127.0.0.1或者防火墙拦了 5000 端口。解决改成host0.0.0.0检查防火墙规则。如果还是不行换端口试比如 8080。6. 进阶技巧把阈值做成可配置项并用混淆矩阵验证业务效果最后一章说一个我实际做项目时一定会加的技巧把分类阈值从代码里抽出来做成 Flask 接口的参数或配置文件项。肝病诊断场景里不同使用方对漏诊和误诊的容忍度不一样固定 0.5 阈值太死。我一般会在config.py里放一个THRESHOLD变量前端加一个滑块或者下拉框让用户自己选保守还是激进。# config.py THRESHOLD 0.4 # 默认偏保守提高召回 # app.py 中推理部分改为 THRESHOLD float(request.form.get(threshold, 0.5)) result 患病风险高 if prob THRESHOLD else 患病风险低参数说明request.form.get(threshold, 0.5)表示如果前端没传阈值就用 0.5传了就用传的值。这样演示时可以现场调阈值看混淆矩阵怎么变。验证方法上别只看 accuracy。用sklearn.metrics.confusion_matrix和classification_report把测试集结果打出来重点看召回率和精确率的权衡。from sklearn.metrics import confusion_matrix, classification_report y_pred (model.predict(X_test).ravel() 0.4).astype(int) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names[非肝病, 肝病]))我一般会盯着召回率看如果肝病类的召回低于 0.7就继续降阈值或者加类别权重。精确率可以低一点因为后续还有医生复核但漏诊尽量少。从那以后我每次做医疗相关分类项目都强制走一遍「保存特征列 → 对齐推理输入 → 调阈值看混淆矩阵」的流程不再直接拿 accuracy 交差。这套资源包里的 ANN 加 Flask 结构虽然不复杂但把这条链路跑通对理解机器学习系统落地很有帮助。希望帮到你。本文还有配套的精品资源点击获取
返回列表