基于印度肝病数据集的ANN与Flask诊断系统实战
2026/9/23 4:22:19 网站建设 项目流程

简介:这份资源面向机器学习入门者、医学数据分析爱好者及需要完成课程设计或毕业项目的学生,提供基于印度肝病患者数据集的智能诊断完整实现。数据集包含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_splitaccuracy_score就结束了。这份资源不一样,它把数据、ANN 模型和 Flask 界面串成了一条完整链路:前端填表单,后端实时推理,输出肝病预测结果。数据集本身包含 416 名肝病患者记录和 167 名非肝病患者记录,共 583 条样本,其中男性 441 条、女性 142 条,采集自印度安德拉·普拉德什东北部。标签列label用来区分患病与不患病。资源包里能看到bootstrap.min.csscover.cssstyle.css三个样式文件,以及B_1.csvN_42.csvN_23.csvN_30.csvN_34.csvN_40.csvN_24.csv这些按类别拆分的 CSV。如果你正在找一个能写进简历、能演示、能讲清楚前后端怎么接的机器学习项目,这套东西的完成度是够的。下面我按自己拆包复现的顺序,把数据清洗、ANN 建模、Flask 接口和部署排错一层层讲透。

2. 数据加载与特征工程:从多个 CSV 到统一训练矩阵

2.1 先搞清楚 B_1.csv 和 N_*.csv 到底怎么拼

资源包里数据不是一个大文件,而是按标签拆开的:B_1.csv大概率对应患病(Benign 或 Bad,具体看列名)样本,N_42.csvN_23.csvN_前缀文件对应非患病样本。这种拆法在早期课程设计里常见,好处是类别边界清晰,坏处是直接pd.read_csv单个文件会漏数据。我一般会先写一段扫描脚本,把目录下所有 CSV 读进来,统一列名后再合并。

import pandas as pd import glob import os # 匹配资源包内所有 CSV,B_ 开头视为正例,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_ 为 1,N_ 为 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_index=True) print(data.shape) print(data["label"].value_counts())

这段代码的逻辑说明:glob.glob("*.csv")把当前目录所有 CSV 拉进来,避免手动列文件名。列名统一是为了防止原始文件里出现AgeageAge这种大小写和空格混用。标签不是从列里读,而是从文件名推断,因为资源包已经按类别拆好了。参数上,ignore_index=True让合并后的索引重新从 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可能几百,不缩放会导致梯度更新被大数值主导。常见做法是StandardScalerMinMaxScaler,我一般用 StandardScaler,因为对异常值比 MinMax 稳一点。

from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 分离特征和标签 X = data.drop("label", axis=1) y = data["label"] # 只对数值列做缩放,类别列先做 one-hot X = pd.get_dummies(X, drop_first=True) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 分层抽样,保证训练集和测试集正负比一致 X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.2, random_state=42, stratify=y )

参数说明:drop_first=True在 one-hot 时去掉第一列,避免虚拟变量陷阱。stratify=y是关键,如果不加,测试集里可能正例过多或过少,导致评估指标失真。random_state=42固定随机种子,保证你复现的结果和我一致。到这里,训练矩阵就准备好了,下一步进 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, activation="relu", input_shape=(X_train.shape[1],)), layers.Dropout(0.3), layers.Dense(8, activation="relu"), layers.Dropout(0.2), layers.Dense(1, activation="sigmoid") ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss="binary_crossentropy", metrics=["accuracy", tf.keras.metrics.AUC(name="auc")] ) early_stop = callbacks.EarlyStopping( monitor="val_loss", patience=15, restore_best_weights=True ) history = model.fit( X_train, y_train, validation_split=0.2, epochs=200, batch_size=16, callbacks=[early_stop], verbose=1 )

逻辑说明:Dropout层在训练时随机丢弃一部分神经元,降低过拟合。EarlyStopping监控验证集损失,patience=15表示连续 15 轮没改善就停,restore_best_weights=True把权重回滚到最优轮,避免最后几轮过拟合。参数上,batch_size=16对 583 条样本比较合适,太大梯度更新少,太小训练慢。learning_rate=0.001是 Adam 的常用起点,如果 loss 震荡可以降到 0.0005。

训练完用测试集评估:

test_loss, test_acc, test_auc = model.evaluate(X_test, y_test, verbose=0) print(f"Test 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_weight="balanced", classes=np.unique(y_train), y=y_train ) class_weight_dict = dict(enumerate(weights)) # 重新训练时传入 class_weight history = model.fit( X_train, y_train, validation_split=0.2, epochs=200, batch_size=16, class_weight=class_weight_dict, callbacks=[early_stop], verbose=0 ) # 调整阈值看召回率变化 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(f"Threshold {thresh}: Recall={recall_score(y_test, y_pred):.3f}, Precision={precision_score(y_test, y_pred):.3f}")

参数说明:class_weight="balanced"会根据类别频率自动算权重,少数类权重更高。阈值调整是业务侧手段,如果系统用于初筛,可以把阈值降到 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(columns=feature_columns, fill_value=0) # 缩放 input_scaled = scaler.transform(input_df) # 推理 prob = model.predict(input_scaled)[0][0] result = "患病风险高" if prob > 0.5 else "患病风险低" return render_template("index.html", prediction=result, probability=f"{prob:.2%}") if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=5000)

参数说明:reindex(columns=feature_columns, fill_value=0)是关键,保证前端传少了字段也不会报错,缺失的用 0 填充。host="0.0.0.0"让局域网内其他机器也能访问,方便演示。debug=True只在开发时开,上线要关掉。

4.2 前端表单与样式文件怎么配合

资源包里有bootstrap.min.csscover.cssstyle.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 charset="UTF-8"> <title>肝病患者智能诊断系统</title> <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='cover.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="container"> <h2>肝病风险预测</h2> <form action="/predict" method="post"> <input type="number" name="age" placeholder="年龄" required> <select name="gender"> <option value="Male">男</option> <option value="Female">女</option> </select> <input type="number" step="0.1" name="total_bilirubin" placeholder="总胆红素"> <!-- 其余字段按同样格式补齐 --> <button type="submit">预测</button> </form> {% if prediction %} <div class="result"> <p>预测结果:{{ prediction }}</p> <p>患病概率:{{ probability }}</p> </div> {% endif %} </div> </body> </html>

逻辑说明:url_for('static', filename=...)是 Flask 的标准静态文件引用方式,样式文件要放在static目录。表单字段名必须和feature_columns里的列名一致,否则reindex后全是 0,预测结果会失真。step="0.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_文件被标成 0,N_被标成 1,或者label列在合并时被覆盖。

解决:合并后立刻print(data["label"].value_counts()),确认正负数量对得上 416 和 167。如果反了,交换标签重新训练。

5.5 现象:Flask 启动后局域网无法访问

原因:app.run默认只监听127.0.0.1,或者防火墙拦了 5000 端口。

解决:改成host="0.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_matrixclassification_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 结构虽然不复杂,但把这条链路跑通,对理解机器学习系统落地很有帮助。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询