简介:基于印度肝病患者数据集(ILPD)构建的智能诊断完整项目,面向医学数据分析和机器学习入门者,尤其适合需要从数据到Web应用全流程实战的开发者。资源包含肝病患者与非患者共583条记录,标签明确,可直接用于分类模型训练。核心实现采用ANN人工神经网络进行肝病预测,并基于Flask框架搭建交互界面,输入指标即可实时得到诊断结果。压缩包内共114个文件,涵盖83个csv原始数据、8个py模型与后端脚本、HTML/CSS/JS前端页面,以及训练好的pkl模型文件,整体约8.13MB,结构清晰便于按模块学习。目前已有594人学习下载。通过该项目可完整掌握数据清洗、特征处理、ANN模型调参及Flask系统部署的实用技能,是一份兼具数据集与可运行系统的综合参考资料。
1. 肝病智能诊断系统从哪里开始:数据、模型与上线路径
接手“基于机器学习的肝病患者智能诊断及系统实现”这类机器学习项目时,很多人第一反应是先把分类模型跑起来。实际做一轮就会发现,真正卡住进度的不是算法选型,而是化验单字段怎么清洗、正负样本怎么定义、模型阈值定到多少才符合临床筛查习惯、以及封装成服务后能不能接住不同来源的数据。这篇笔记以公开肝病数据集为原料,从特征工程、模型训练、阈值调整、系统封装到上线验证,完整走一遍机器学习应用流程,适合正在做医疗AI毕设、医院信息化系统或健康管理平台中风险分层模块的工程师。新手可以照步骤复现一个能跑的机器学习项目,熟手则能在这里找到医疗场景里最容易翻车的边界条件。和常规分类任务不同,医疗诊断类模型不是准召率刷高就能交付,还必须回答“为什么给出这个结论”。
2. 数据清洗与特征工程:肝病模型效果的天花板在这里
2.1 化验单字段怎么进模型:先看一张表的字段映射
公开的肝病数据集中,Indian Liver Patient Dataset(ILPD)是最常被拿来练手的原料,包含 583 条样本,字段基本覆盖常规肝功化验单:年龄、性别、总胆红素、直接胆红素、碱性磷酸酶、谷丙转氨酶、谷草转氨酶、总蛋白、白蛋白、白蛋白/球蛋白比值,以及一个类别标签。真实系统里接到上游 HIS 系统的数据,字段名未必一致,但含义可以按这张表来对齐。
| 字段 | 常见化验单含义 | 数据类型 |
|---|---|---|
| Age | 年龄 | 数值型 |
| Gender | 性别 | 类别型 |
| TB / DB | 总胆红素 / 直接胆红素 | 连续数值 |
| Alkphos | 碱性磷酸酶 | 连续数值 |
| Sgpt / Sgot | 谷丙转氨酶 / 谷草转氨酶 | 连续数值 |
| TP / ALB | 总蛋白 / 白蛋白 | 连续数值 |
| AG_Ratio | 白蛋白/球蛋白比值 | 连续数值 |
| Label | 是否肝病患者 | 二分类 |
数据拿到的第一件事不是建模,而是确认有没有用问号、空字符串、负数这类脏值。很多医疗数据导出时会把缺失值写成“?”,pandas 默认会把它当成字符串,导致后续数值计算直接报错或静默出错。
import pandas as pd import numpy as np columns = [ "Age", "Gender", "TB", "DB", "Alkphos", "Sgpt", "Sgot", "TP", "ALB", "AG_Ratio", "Label" ] df = pd.read_csv( "liver_patient_data.csv", header=None, names=columns, na_values="?" ) print(df.shape) print(df.dtypes) print(df.isna().sum())读取时用na_values="?"统一把问号解析成 NaN,避免数据在后续被当成文本参与计算。df.dtypes用来快速识别字段类型是否和预期一致,df.isna().sum()输出每个字段的缺失量,决定后面是补全还是丢弃。这步属于机器学习基础里的标准化动作,但医疗数据里漏掉它,后面所有指标都不可信。
字段类型确认后,需要把类别特征做编码。性别这类二值特征用 one-hot 编码就行,不要手工映射成 1/2,因为上游系统传过来的可能是 Male/Female,也可能是 男/女,交给编码器统一处理更稳妥。
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler numeric_feats = [ "Age", "TB", "DB", "Alkphos", "Sgpt", "Sgot", "TP", "ALB", "AG_Ratio" ] binary_feats = ["Gender"] preprocessor = ColumnTransformer( transformers=[ ("num", StandardScaler(), numeric_feats), ("cat", OneHotEncoder(drop="first"), binary_feats) ] )ColumnTransformer 的好处是让预处理策略跟着字段名走,不用手工把 DataFrame 切成两块再拼接。数值字段做标准化,性别字段只编码不标准化,drop="first"去掉冗余列,避免线性模型出现多重共线性。这套结构后面会整个塞进 Pipeline,训练和上线共用同一份逻辑。
2.2 缺失值与类别不平衡:先别急着上 SMOTE
肝病数据集的 A/G 比值常有缺失,因为部分化验单不打印这个计算项。处理缺失值的常见做法是中位数填充或删除整行。我的习惯是:缺失比例低于 5% 且字段重要,用中位数填充;缺失比例高或者字段本身和业务强相关,先看它和标签的关系再决定。A/G 比值是肝病评估里一个参考指标,但消失数据里往往有其他字段也能解释病情,盲目线性插值反而会给模型注入不存在的趋势。
# 看缺失比例,再决定填充策略 missing_ratio = df.isna().mean().sort_values(ascending=False) print(missing_ratio) df["AG_Ratio"] = df["AG_Ratio"].fillna(df["AG_Ratio"].median()) X = df.drop(columns=["Label"]) y = df["Label"].map(lambda v: 1 if v == 1 else 0)用isna().mean()看每个字段的缺失比例,AG_Ratio 缺失在 10% 左右时用中位数填充是稳妥选择。标签映射这里有个容易搞反的坑:ILPD 原始约定里 1 代表肝病患者、2 代表非肝病患者,建模前必须确认你手里的数据说明,把正类明确映射成 1,否则后面所有精确率和召回率都是反着看的。
样本不平衡在肝病数据里往往不是极端情况,正负样本大概在 6:4 左右,但真实医院系统里健康体检人群比例会远高于患者,这时候别急着上 SMOTE。SMOTE 在相邻样本之间插值生成新样本,对图像和文本任务还行,医学化验指标这种边界本身就有噪声的场景,插值很容易制造出既不像病人也不像健康人的假样本。优先做法是用class_weight="balanced"让模型给少数类更高的错分代价,只有当你确认少数类样本分布比较紧凑时才考虑 SMOTE。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) clf = RandomForestClassifier( n_estimators=400, max_depth=6, min_samples_leaf=8, class_weight="balanced", random_state=42, n_jobs=-1 ) clf.fit(X_train, y_train) print(classification_report(y_val, clf.predict(X_val)))stratify=y保证训练集和验证集的正负比例一致,这在类别不平衡尤其是小样本时至关重要,否则一次随机切分就可能把验证集切得只剩十几个正样本。max_depth=6和min_samples_leaf=8是为了限制树结构,样本量只有几百条时,树太深几乎必然过拟合。class_weight="balanced"比 SMOTE 保守得多,它不生成新数据,只是调整损失函数对少数类样本的权重。
2.3 特征筛选与相关性:ALT/AST 高度共线时怎么取舍
肝功化验单里谷丙转氨酶和谷草转氨酶高度正相关,总蛋白和白蛋白也强相关,这会让树模型的特征重要性分散,也会让线性模型系数不稳定。处理这类字段的原则是:先从业务解释上判断,看两个字段是不是在测量同一件事,再借助模型做量化验证。
from sklearn.inspection import permutation_importance perm = permutation_importance( clf, X_val, y_val, n_repeats=10, random_state=42, scoring="roc_auc" ) for name, imp, std in zip(X.columns, perm.importances_mean, perm.importances_std): print(f"{name}: {imp:.4f} ± {std:.4f}")排列重要性会把某个字段随机打乱,再看模型 AUC 掉多少。如果打乱一个字段后 AUC 几乎不掉,说明这个字段在当前模型里没有增量信息。这个指标比 Gini importance 更可靠,因为 Gini importance 容易被高基数特征带偏,而排列重要性是在真实预测效果上做衡量。对于 ALT 和 AST,你可以分别打乱它们,保留掉分更多的那个,另一个可以作为冗余特征先排除掉。
做完这轮清洗和筛选后,数据规模和特征空间基本定型。接下来进入模型训练,但训练前必须想清楚一件事:你要的是“把病人分对”,还是“尽量不放过任何一个疑似病人”。这个选择直接决定模型阈值,而不是由准确率决定。
3. 模型训练与阈值调整:从基线模型到能用的诊断判定
3.1 三种基线模型对比:逻辑回归、随机森林与 XGBoost
肝病诊断这类机器学习风险预测模型,我不会一上来就上深度模型。几百条样本级别的数据,复杂模型完全没有优势,反而会把噪声学进去。常见做法是先跑三组基线:逻辑回归、随机森林、XGBoost。十大机器学习算法里,这三类已经覆盖了线性边界、非线性边界和梯度提升三条路线,足够判断数据的可分性。
from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score, StratifiedKFold from xgboost import XGBClassifier models = { "lr": Pipeline([ ("pre", preprocessor), ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")) ]), "rf": Pipeline([ ("pre", preprocessor), ("clf", RandomForestClassifier( n_estimators=400, max_depth=6, min_samples_leaf=8, class_weight="balanced", random_state=42 )) ]), "xgb": Pipeline([ ("pre", preprocessor), ("clf", XGBClassifier( n_estimators=400, learning_rate=0.05, max_depth=3, subsample=0.8, colsample_bytree=0.8, scale_pos_weight=1.5, eval_metric="auc", random_state=42 )) ]), } for name, model in models.items(): scores = cross_val_score( model, X, y, cv=StratifiedKFold(n_splits=5), scoring="roc_auc" ) print(f"{name}: AUC={scores.mean():.3f} ± {scores.std():.3f}")逻辑回归提供的是一个“线性边界能不能区分”的下限,如果它的 AUC 已经接近随机森林,说明特征和标签之间的关系比较线性,没必要上复杂模型。随机森林负责检验非线性,XGBoost 负责把梯度提升的收益榨出来。scale_pos_weight=1.5是正负样本比例的近似值,用于给正类更大权重,它和class_weight="balanced"效果类似,但 XGBoost 的参数需要单独设置。eval_metric="auc"只是让训练日志输出 AUC,不会影响模型本身。
交叉验证用StratifiedKFold而不是普通KFold,就是为了保证每一折里正负样本比例和全集一致。小样本医疗数据最怕的就是某折里正样本特别少,AUC 方差被拉得很大。
3.2 GridSearchCV 有限调参:只搜你信任的空间
网上关于机器学习算法调参的教程喜欢给一个大参数网格,然后让 GridSearchCV 全空间搜索。在肝病这种几百条样本的数据集上,参数空间越大,越容易在验证集上搜出一个运气好的点。我一般只搜对模型影响最大的三四个参数,并且每个参数给三个候选值。
from sklearn.model_selection import GridSearchCV param_grid = { "clf__n_estimators": [300, 500], "clf__max_depth": [4, 6, 8], "clf__min_samples_leaf": [4, 8, 16], "clf__max_features": ["sqrt", "log2"] } gs = GridSearchCV( models["rf"], param_grid, scoring="roc_auc", cv=StratifiedKFold(n_splits=5), n_jobs=-1, verbose=1 ) gs.fit(X_train, y_train) print(gs.best_params_) print(gs.best_score_)这里的clf__前缀对应 Pipeline 里命名步骤clf,GridSearchCV 会只调节指定参数。scoring="roc_auc"而不是accuracy,是因为类别不平衡时准确率很容易虚高,模型只要全判成多数类就能拿到七八成的准确率,而 AUC 能反映排序能力。搜索网格时顺手把n_estimators从 400 缩到 300/500 两个档位,树数量对结果影响不大,但能缩短训练时间。
调参到这里就够了,不要再往下加学习率、贪心搜索之类的花活。小样本调参本身有很强的随机性,超过这个粒度就是在拟合验证集噪声。真正影响诊疗体验的其实是下一步:判定阈值。
3.3 阈值移动:不追求准确率,追求“漏诊最少”
医院场景里,肝病诊断项目要解决的往往是“先把疑似病人筛出来,再去做进一步检查”。这意味着模型宁愿产生一些假阳性,也不能放过一个真阳性。模型默认阈值固定在 0.5,但这个值只是数学上的对称点,不是业务上的最优分界。实际落地时要根据精确率和召回率的权衡关系来调。
from sklearn.metrics import precision_recall_curve, confusion_matrix xgb_model = models["xgb"].fit(X_train, y_train) proba = xgb_model.predict_proba(X_val)[:, 1] precision, recall, thresholds = precision_recall_curve(y_val, proba) for thr in [0.5, 0.4, 0.3, 0.2]: pred = (proba >= thr).astype(int) tn, fp, fn, tp = confusion_matrix(y_val, pred).ravel() print(f"thr={thr:.1f}: 漏诊={fn}, 误诊={fp}, 召回率={recall:.3f}, 精确率={precision:.3f}")看输出时重点关注漏诊数,也就是fn。在肝病筛查需求里,阈值从 0.5 降到 0.3,往往能让召回率从 0.75 提到 0.9 以上,代价是误诊增加,但这些被标记成高风险的人会进入复查流程,而不是直接被判定为病人。注意precision_recall_curve返回的thresholds和proba是对应的,但数组长度比样本数少 1,直接按阈值列表取值即可。
选好阈值后,这个阈值要作为参数存进配置,而不是写死在代码里。后面系统实现时会发现,阈值是业务方最常要求调整的东西,把它做成可配置项能省很多沟通成本。
4. 系统实现:把机器学习模型封装成能跑的诊断服务
4.1 用 Pipeline 做持久化:一个 joblib 文件包含全部预处理
模型训练完成后,最容易出的问题不是模型文件太大,而是上线时少做了一步预处理。很多人训练时先对数据做标准化、编码,然后训练模型,保存时只保存了模型权重,线上加载后直接用原始字段预测,结果完全不对。解决办法是把预处理和模型绑在同一个 Pipeline 里,整个保存、整个加载。
import joblib final_model = Pipeline([ ("pre", preprocessor), ("clf", gs.best_estimator_.named_steps["clf"]) ]) final_model.fit(X_train, y_train) joblib.dump(final_model, "liver_diagnosis_model.joblib")gs.best_estimator_本身就是完整的 Pipeline,这里重新构造一遍是为了防止 GridSearchCV 内部引用关系导致序列化出问题。joblib.dump保存的是预处理步骤和分类器的完整状态,后续加载时,外部只需要传原始化验单字段,标准化和编码都会在 Pipeline 内部自动执行。这个做法是医疗机器学习项目里的保命习惯,它让训练和推理的代码路径保持完全一致。
4.2 用 Flask 封装诊断接口:字段校验与风险分级
模型封装成服务后,第一个要考虑的不是性能,而是入口参数校验。真实调用方可能来自门诊系统、体检系统或移动端,字段缺失、单位换算错误、类型不对都会造成线上事故。Flask 写一个最小可用的诊断接口,把字段校验放在业务逻辑前面。
from flask import Flask, request, jsonify import joblib import pandas as pd app = Flask(__name__) model = joblib.load("liver_diagnosis_model.joblib") REQUIRED_FIELDS = [ "Age", "Gender", "TB", "DB", "Alkphos", "Sgpt", "Sgot", "TP", "ALB", "AG_Ratio" ] @app.route("/api/diagnose", methods=["POST"]) def diagnose(): data = request.get_json() if not data or any(f not in data for f in REQUIRED_FIELDS): return jsonify({"error": "missing fields"}), 400 df = pd.DataFrame([{f: data[f] for f in REQUIRED_FIELDS}]) proba = model.predict_proba(df)[0][1] level = "high_risk" if proba >= 0.3 else "low_risk" return jsonify({ "probability": round(proba, 4), "risk_level": level, "threshold": 0.3 }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)接口返回概率、风险级别和阈值三样东西。probability是模型原始输出,risk_level是根据业务调好的阈值做的分级,threshold随接口返回,这样调用方即使换了系统也知道当前判定标准是什么。字段校验用any(f not in data for f in REQUIRED_FIELDS)逐项检查,缺任何一个都直接拒绝,而不是用空值去预测然后返回一个奇怪的概率。
4.3 输出可读结论:医生想看的是“为什么”而不只是 0/1
风险等级只解决了“要不要复查”的问题,还解决不了“为什么”。一线医生不会因为界面上显示 high_risk 就接受这个判断,必须能点开看到是哪些指标把风险推高的。这里不需要把整个 SHAP 图塞进接口,先做一个按区间分层提示的功能就够用。
def explain_risk(patient_df, proba): reasons = [] if patient_df["TB"].iloc[0] > 2.0: reasons.append("总胆红素偏高") if patient_df["ALB"].iloc[0] < 3.5: reasons.append("白蛋白偏低") if patient_df["Sgot"].iloc[0] > 40: reasons.append("谷草转氨酶偏高") return reasons这些规则不是模型,而是把模型判断依据和常规临床参考区间对齐,做一层可读性解释。真实项目里可以换用 SHAP 的 top-k 特征来做,但要注意:临床判断的边界和高低概念来自医学参考范围,SHAP 的特征排序不一定能直接翻译成“偏高/偏低”。所以在初版系统里,规则化解释更稳,也更容易过科室评审。
5. 落地避坑:模型上线后最容易翻车的 5 个问题
5.1 化验单单位不统一,模型上线当天就集体翻车
现象:离线验证 AUC 0.85,上线后第一周风险分布全部偏向 high_risk 或全部偏向 low_risk。
原因:训练数据里胆红素按 mg/dL 记录,线上接口接入的是同一个医院不同科室的数据,有的科室导出的单位是 umol/L。模型没见过大数值区间,标准化后依然会产生离群点,导致概率被推向极端。
解决:在接口层加单位范围校验。每个数值字段给一个基于训练数据分布的大致合理范围,超出范围直接拒绝并提示检查单位,而不是把数据强行喂给模型。
UNIT_RANGE = { "TB": (0.1, 10.0), # mg/dL 范围,超过说明可能是 umol/L "DB": (0.0, 5.0), "ALB": (1.0, 6.0), } for field, (low, high) in UNIT_RANGE.items(): val = data.get(field) if val is None: continue if val < low or val > high: return jsonify({ "error": f"{field} out of expected range, please check unit" }), 400范围校验不能替代单位换算,但它能第一时间拦下整批错误调用。这个校验最好做成一个独立的函数,后面接入新数据源时直接复用。
5.2 数据泄漏:标准化顺序错了,离线 AUC 全是幻觉
现象:训练时 AUC 0.92,压测时一上真实分布就只剩 0.70。
原因:数据清洗时先对整个 DataFrame 做了标准化,然后才切分训练集和验证集。验证集里已经有训练集的数据分布信息,等于“提前偷看了答案”。
解决:标准化必须放进 Pipeline,并且只调用fit在训练集上,验证集靠transform。前面所有代码都把preprocessor放在 Pipeline 里,就是为了防止你写出先标准化再切分的代码。如果项目里已经有这段代码,赶紧改成 Pipeline 结构,不用重训模型,只要把预处理步骤挪进去即可。
5.3 小样本过拟合:模型把“年龄”当成了诊断捷径
现象:特征重要性里年龄排第一,但医学解释上年龄对肝病来说不是最关键因素。
原因:样本量只有几百条时,模型容易抓住年龄这种方便切分的特征做捷径。年龄和肝病风险确实相关,但它在不同医院的分布差异很大,换一家医院年龄结构一变,模型就崩。
解决:用排列重要性复查每个特征,把只对当前样本敏感、没有业务解释支撑的特征剔掉。另外一个更有效的办法是按年龄段做分层验证,让验证集里每个年龄段的占比和真实筛查人群一致,至少不会让模型靠年龄段差异刷指标。
5.4 训练标签是“临床印象”而不是金标准
现象:模型输出的风险等级和医生病历上的最终诊断经常对不上,尤其是早期肝病患者被大量漏掉。
原因:很多数据集里的标签不是病理金标准,而是住院记录或门诊诊断文本的粗略映射。医生写“肝炎待查”就被标成阳性,但实际上这批人里有一部分后来确诊不是肝病。
解决:项目启动时先做标签复核,找临床医生抽检 50 条不确定样本,看标签的一致性。如果一致性低于 0.8,宁可把这类样本删掉也别留着训练。系统输出的措辞也要收敛成“建议复查”,而不是“疑似肝病”,避免把模型的不确定性转嫁给患者。
5.5 上线后数据漂移:检验科换试剂,模型分布整体跑偏
现象:系统上线三个月后,low_risk 比例突然从 60% 掉到 30%,风险分布整体右移。
原因:检验科换了检测试剂盒,参考区间变了,肝功指标整体平移。模型在旧分布上训练的阈值,自然会把新分布里的样本往高风险推。
解决:对模型输出的概率分布做漂移监控,常用 PSI(Population Stability Index)作为指标。我不让读者自己实现一遍,而是在项目里把这个监控函数单独做成脚本,每个星期跑一次。
def psi(expected, actual, bins=10): eps = 1e-6 expected_pct = np.histogram(expected, bins=bins, range=(0, 1))[0] / len(expected) actual_pct = np.histogram(actual, bins=bins, range=(0, 1))[0] / len(actual) return np.sum( (actual_pct - expected_pct) * np.log((actual_pct + eps) / (expected_pct + eps)) )PSI 小于 0.1 表示分布稳定,0.1 到 0.25 表示需要关注,大于 0.25 表示必须排查数据来源。这里把range=(0, 1)固定住,是因为概率输出本来就在这个区间,分箱边界统一才可比较。发现漂移后不要立刻重训模型,先找业务方确认是不是检验流程变了,否则你只是在用新噪声覆盖旧噪声。
6. 校准曲线与 SHAP 解释:上线前再加两道质量关卡
6.1 校准曲线:概率值不可信,阈值就没有意义
模型输出的 0.3 这个阈值,隐含的假设是“预测概率接近真实风险”。但很多分类模型只是排序分数,不是真实概率,尤其是 XGBoost 这类树模型,输出概率经常偏高。上线前必须做一次概率校准检查。
from sklearn.calibration import calibration_curve prob_true, prob_pred = calibration_curve(y_val, proba, n_bins=10) for true, pred in zip(prob_true, prob_pred): print(f"实际比例={true:.2f}, 预测均值={pred:.2f}")校准曲线把预测概率分成十桶,每桶里取预测均值和实际正样本比例对比。如果预测 0.3 的样本实际只有 0.2 是阳性,说明模型把风险整体高估了,这时候阈值 0.3 的业务含义就和你想的不一样。偏差明显时用CalibratedClassifierCV做等渗回归校准,再重新评估阈值。
from sklearn.calibration import CalibratedClassifierCV calibrated = CalibratedClassifierCV( final_model, method="isotonic", cv=5 ) calibrated.fit(X_train, y_train)校准不是让 AUC 变高,而是让概率变得更像概率。在医疗诊断里,这一步比重训模型还重要,因为医生和业务系统都是按概率阈值来做分流决策的。
6.2 SHAP 解释:高风险结论要有理由
黑匣子模型在医疗场景里天然不受信任。哪怕只是给字段排个序,也能让医生从“模型说的”变成“我也看看是不是这几个指标”。
import shap pre_pipe = final_model.named_steps["pre"] model_rf = final_model.named_steps["clf"] X_val_pre = pre_pipe.transform(X_val) explainer = shap.TreeExplainer(model_rf) shap_values = explainer.shap_values(X_val_pre) shap.summary_plot(shap_values[1], X_val_pre)TreeExplainer 只接受原始树模型,不能直接吃 Pipeline,所以要先把预处理后的特征矩阵拿出来。shap_values[1]取的是正类对应的 SHAP 值,每个样本每个特征会得到一个贡献值。实际系统里可以对单条预测提取 top-3 特征,拼成一句“高风险原因:总胆红素偏高、谷草转氨酶偏高、白蛋白偏低”。
我现在养成的习惯是:任何诊断类模型,先跑一遍校准曲线再定阈值,再上 SHAP 看特征解释。校准偏差明显的项目,我宁可多等几周数据,让模型在真实样本上稳定下来再进系统。系统能不能落地,从来不取决于模型刷多高,而取决于每一次高风险预警都有据可查、经得起追问。希望这个流程能帮你在做肝病智能诊断系统时少走一段弯路,也顺便把那些别人趟过的坑提前绕开。希望帮到你。
本文还有配套的精品资源,点击获取