ARTICLE DETAIL

资讯详情

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

Java与大数据分析的远程糖尿病随访系统:架构设计与实战

Java与大数据分析的远程糖尿病随访系统:架构设计与实战 简介面向医疗信息化开发者与Java技术栈学习者这份基于Java与大数据分析的远程糖尿病医疗随访系统设计源码聚焦慢性病管理中的远程监测、智能随访与医疗数据分析场景帮助医护人员提升糖尿病患者的管理效率。压缩包共201个文件约51.43MB其中136个Java源文件是系统核心功能实现涵盖WebSocket服务、病历记录与JWT鉴权等模块36个XML配置文件配合properties与YAML文件完成参数及环境配置6张PNG图片服务界面设计5份PDF研究文献与xmind设计文档提供理论依据和结构参考。代码结构清晰包内工程文件完整可直接导入主流IDE进行二次开发或作为毕业设计蓝本。该资源已有347人学习下载作者为wjs2024通过研读源码可掌握大数据分析在远程随访系统中的落地方法以及一套完整可运行的医疗系统设计思路。1. 远程糖尿病随访系统到底需要怎样的Java后端一个内分泌科的护士每个月要对着Excel给几百个糖尿病患者打电话血糖高不高、药有没有停、要不要复查全靠一条条翻记录。这是很多慢病随访项目的真实痛点。名为“基于Java与大数据分析的远程糖尿病医疗随访系统”的工程就是要把这套流程程序化用Java做业务后端承接患者档案、随访计划、血糖采集用大数据分析对血糖曲线、随访记录做批量计算把患者分成不同风险等级让该被关注的异常个案自动浮出来。这篇实战笔记不说虚的直接给架构、建表SQL和核心Java代码并标出最容易翻车的几个坑。适合用Java做课程设计的同学、院方慢病系统开发者以及准备进智慧医疗方向的技术人。2. 架构与技术选型Java为主干大数据分析做支路2.1 分层设计采集、业务、分析三件事不能搅一起我在早期做类似系统时犯过一个错把采集接口和分析逻辑全塞进同一个Controller结果改血压逻辑要动血糖接口上线后一改就抖。后来按“接入层、业务层、数据层、分析层”来拆才稳定下来。接入层是数据入口通常有微信小程序、App、护士电话录入页面。业务层用JavaSpring Boot处理患者档案、随访计划、随访记录、血糖上报这些核心操作。数据层用MySQL存业务数据Redis做缓存和分布式锁。分析层独立跑任务每天凌晨计算风险标签或者实时接收血糖异常事件并触发干预。为什么选Java做主干因为医疗行业后端对稳定性、事务和团队上手成本要求高Java生态在这三方面都是最舒服的。Spring Boot MyBatis Plus能让CRUD开发量减少一半Spring Security做权限控制、Spring Scheduled做定时任务都现成。大数据分析这边不需要一上来就搞Hadoop集群先用Spark本地模式甚至直接用SQL聚合把流程跑通数据量真正大了再平滑迁移到集群。2.2 用Spring Boot搭出最小业务骨架先看工程的主干结构我用Maven管理依赖。diabetes-followup/ ├── pom.xml ├── src/main/java/com/hospital/diabetes/ │ ├── controller/ # REST接口层 │ ├── service/ # 业务层 │ ├── mapper/ # MyBatis-Plus的数据访问接口 │ ├── entity/ # 数据库实体类 │ ├── job/ # 定时任务、Spark分析任务入口 │ └── config/ # 配置类Redis、异步线程池等 ├── src/main/resources/ │ ├── application.yml │ └── mapper/ # 自定义SQL XML └── sql/ └── init.sql # 建表脚本pom.xml里最关键的依赖集中在下面这组。Spring Boot分父版本管理下面不再重复写版本号。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus把单表增删改查简化掉 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency !-- MySQL驱动URL里指定了时区参数后面排坑会细说 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Redis做分布式锁和缓存 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Spark SQL只在analysis模块用避免业务代码也拉一大包 -- dependency groupIdorg.apache.spark/groupId artifactIdspark-sql_2.12/artifactId scopeprovided/scope /dependency /dependencies注意最后那个Spark依赖我没有把它放进默认的编译范围而是标了provided。因为分析任务会打成独立Jar包丢给定时调度器跑不需要打进业务系统的部署包里。否则每次启动Spring Boot都会尝试初始化SparkContext内存直接吃掉一大半。接下来是application.yml业务系统里最基本的配置项server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/diabetes_followup?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath:mapper/**/*.xml global-config: db-config: id-type: auto说下这里必调的两个参数。第一serverTimezoneAsia/Shanghai必须指定否则MySQL驱动会拿本机时区对比服务器时区时间字段存取出现8小时偏差血糖记录的时间戳就全乱了。第二mybatis-plus.global-config.db-config.id-type: auto让主键走数据库自增避免每次插入都要手动拼一个ID。2.3 大数据分析不是非得上Spark先认清数据量级很多同学一听“大数据分析”脑子就是Hadoop、Spark、集群。但你这套随访系统的早期用户可能只有几千个患者每天产生的血糖记录也就几百条。这种情况用一条SQL聚合就能算完Spark反而成了“杀鸡用牛刀”。我一般这样决策如果患者量在5万以下、单日新增记录不超过10万条直接用MyBatis-Plus写聚合查询或者用Elasticsearch做轻量聚合分析结果写回业务表就行。当患者量上升到几十万、需要按月份批次算历史趋势、或者要实时计算多条时间窗指标时再把Spark引进来。上面提到的pom依赖保留就是为了给后续留好接口。所以标题里的“大数据分析”落实下来并不是必须集群而是让系统具备处理大规模数据的能力。下面的章节里我会先讲业务表和随访流程再讲Spark怎么接入这样你就能看到分析层和业务层是怎么配合的。3. 数据库与随访流程从建表到自动派单3.1 患者、随访计划、血糖记录的表结构设计随访系统的表逻辑很简单核心是一个患者有多个随访计划每个计划会生成多张随访工单另有一张血糖记录表存患者的定时或随机血糖值。先看建表SQL注意字段类型和索引。CREATE TABLE patient ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 姓名, id_card varchar(18) DEFAULT NULL COMMENT 身份证号加密存储, phone varchar(20) NOT NULL COMMENT 联系电话, risk_level varchar(10) DEFAULT low COMMENT 风险等级low/medium/high, last_follow_up_at datetime DEFAULT NULL COMMENT 最近一次随访时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_phone (phone), KEY idx_risk_level (risk_level) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT患者档案表; CREATE TABLE follow_up_plan ( id bigint NOT NULL AUTO_INCREMENT, patient_id bigint NOT NULL COMMENT 患者ID, plan_type varchar(20) DEFAULT regular COMMENT 计划类型regular/urgent, frequency_days int NOT NULL COMMENT 随访周期天, next_follow_up_at datetime NOT NULL COMMENT 下次计划执行时间, status tinyint DEFAULT 1 COMMENT 1待执行0已关闭, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_patient_next (patient_id, next_follow_up_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT随访计划表; CREATE TABLE blood_glucose_record ( id bigint NOT NULL AUTO_INCREMENT, patient_id bigint NOT NULL, glucose_value decimal(5,2) NOT NULL COMMENT 血糖值mmol/L, measure_time datetime NOT NULL COMMENT 测量时间, remark varchar(255) DEFAULT NULL COMMENT 备注空腹/餐后/随机, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_patient_measure (patient_id, measure_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT血糖记录表;索引有两处别省follow_up_plan表里的idx_patient_next是要频繁查询“某个患者接下来的计划”索引能直接命中blood_glucose_record表的idx_patient_measure是为后面的风险分析准备按患者和时间窗口聚合时避免全表扫描。patient.id_card我注释了“加密存储”因为医疗数据有合规要求。实际项目里不要存明文可以用AES在Java层加密或者接院内的加密中间件。同样是合规问题接口返回时要脱敏身份证只显示前3后4。3.2 用策略模式生成不同频率的随访计划随访频率不是恒定值。低风险患者每30天随访一次中风险每14天高风险每7天。这个规则如果写满if-else后续要加“妊娠期糖尿病每3天一次”还得改动核心代码。所以我把频率生成逻辑做成策略接口。先定义一个频率选择器public interface FollowUpFrequencyStrategy { int getIntervalDays(Patient patient); boolean supports(String riskLevel); }然后分别实现三个策略类Service public class LowRiskStrategy implements FollowUpFrequencyStrategy { Override public int getIntervalDays(Patient patient) { return 30; } Override public boolean supports(String riskLevel) { return low.equals(riskLevel); } } Service public class HighRiskStrategy implements FollowUpFrequencyStrategy { Override public int getIntervalDays(Patient patient) { return 7; } Override public boolean supports(String riskLevel) { return high.equals(riskLevel); } }这里为了演示只列了低风险和高风险中风险同理。重点是策略的supports方法它决定了这块逻辑是否属于该类。在生成计划的服务里注入所有策略让容器帮我们把符合条件的挑出来。Service public class FollowUpPlanService { Resource private ListFollowUpFrequencyStrategy strategies; Resource private PatientMapper patientMapper; Resource private FollowUpPlanMapper planMapper; public void generatePlansForAllPatients() { ListPatient patients patientMapper.selectList(null); for (Patient patient : patients) { FollowUpFrequencyStrategy strategy strategies.stream() .filter(s - s.supports(patient.getRiskLevel())) .findFirst() .orElseThrow(() - new RuntimeException(未找到对应的风险策略)); FollowUpPlan plan new FollowUpPlan(); plan.setPatientId(patient.getId()); plan.setPlanType(regular); plan.setFrequencyDays(strategy.getIntervalDays(patient)); plan.setNextFollowUpAt(calculateNextDate(strategy.getIntervalDays(patient))); plan.setStatus(1); planMapper.insert(plan); } } private Date calculateNextDate(int days) { Calendar calendar Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.DAY_OF_MONTH, days); return calendar.getTime(); } }说下这段代码的设计意图。strategies是Spring会把所有FollowUpFrequencyStrategy的实现类注入到这个List里以后新增策略不需要改这个方法。calculateNextDate用Calendar.add而不是date days * 86400000是因为后者遇到夏令时或毫秒溢出会出偏差虽然这里项目在中国但团队里其他人可能拿这套代码去接海外版本最好一开始就避免。定时调度上用Spring自带的Scheduled每天凌晨跑一次generatePlansForAllPatients扫描第二天需要随访的患者。但是注意这个“每天全量跑”在患者量大了以后会变成灾难要改成按next_follow_up_at筛选只补未来几天需要执行的患者。那是后话先跑通流程最重要。3.3 血糖异常触发即时干预一条链路的完整代码常规随访是“到点就打电话”但急性风险需要在血糖值一上传时就发现。临床上一个常用的干预阈值是13.9 mmol/L超过这个值说明血糖过高需要尽快联系患者或安排就诊。我一般会在血糖上报接口里做一个旁路判断不污染正常存库逻辑。用Spring的事件机制让业务代码与警报解耦。先定义事件类public class HighGlucoseEvent extends ApplicationEvent { private final Long patientId; private final double glucoseValue; public HighGlucoseEvent(Object source, Long patientId, double glucoseValue) { super(source); this.patientId patientId; this.glucoseValue glucoseValue; } public Long getPatientId() { return patientId; } public double getGlucoseValue() { return glucoseValue; } }血糖上报服务只需要发布事件Service public class BloodGlucoseService { Resource private BloodGlucoseRecordMapper recordMapper; Resource private ApplicationEventPublisher eventPublisher; public void upload(Long patientId, double glucoseValue, Date measureTime, String remark) { BloodGlucoseRecord record new BloodGlucoseRecord(); record.setPatientId(patientId); record.setGlucoseValue(BigDecimal.valueOf(glucoseValue)); record.setMeasureTime(measureTime); record.setRemark(remark); recordMapper.insert(record); // 超过干预阈值发布事件让监听器去处理紧急随访计划 if (glucoseValue 13.9) { eventPublisher.publishEvent(new HighGlucoseEvent(this, patientId, glucoseValue)); } } }监听器拿到事件后插入一条紧急随访计划并提醒护士Component public class HighGlucoseEventListener { Resource private FollowUpPlanMapper planMapper; Resource private PatientMapper patientMapper; EventListener Async public void onHighGlucose(HighGlucoseEvent event) { FollowUpPlan urgentPlan new FollowUpPlan(); urgentPlan.setPatientId(event.getPatientId()); urgentPlan.setPlanType(urgent); urgentPlan.setFrequencyDays(1); urgentPlan.setNextFollowUpAt(new Date()); urgentPlan.setStatus(1); planMapper.insert(urgentPlan); Patient patient patientMapper.selectById(event.getPatientId()); // 实际上这里应通过短信网关或企业微信机器人推送 System.out.println(需要紧急随访 patient.getName() 血糖 event.getGlucoseValue()); } }这里要注意Async如果不同步调用监听器会阻塞血糖上报接口。所以我在监听方法上加了Async让它跑到独立线程池去执行。代价是异常抛出来不会直接影响调用方所以线程池的setWaitForTasksToCompleteOnShutdown和拒绝策略要提前设置好不然系统重启时紧急计划可能丢掉。4. 患者风险分层Spark任务怎么把随访系统带飞4.1 风险分层指标为什么选均值、标准差、超标次数随访计划里的风险等级不是每次调用都重新算而是由分析层定期更新。计算维度要能反映患者短期血糖控制水平我选了三个关键指标最近90天平均血糖值、血糖标准差、超过13.9 mmol/L的次数。平均血糖反映整体控制水平标准差反映血糖波动幅度糖尿病并发症和波动关系更密切超标次数反映急性高风险的频率。这三个字段在数据库中通过一次分组聚合就能得到。如果数据量不大MyBatis-Plus的LambdaQueryWrapper也能搞定但为了展示大数据分析路径下面用Spark来做并且把结果写回一张patient_risk_score表业务层以后只读这张表不再直接查血糖明细。4.2 用Java API写一个可运行的Spark分析任务Spark虽然原生用Scala但Java API也足够成熟。我们用DatasetRow做整个任务跑完大约几十行适合作为分析层的起点。import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import static org.apache.spark.sql.functions.*; public class DiabetesRiskScoreJob { public static void main(String[] args) { if (args.length 4) { System.err.println(Usage: DiabetesRiskScoreJob jdbcUrl dbUser dbPass days); System.exit(1); } String jdbcUrl args[0]; String dbUser args[1]; String dbPass args[2]; int days Integer.parseInt(args[3]); SparkSession spark SparkSession.builder() .appName(DiabetesRiskScore) .master(local[3]) .getOrCreate(); DatasetRow glucose spark.read() .format(jdbc) .option(url, jdbcUrl) .option(dbtable, blood_glucose_record) .option(user, dbUser) .option(password, dbPass) .option(driver, com.mysql.cj.jdbc.Driver) .option(predicates, new String[]{ measure_time DATE_SUB(CURDATE(), days ) AND patient_id 100000, measure_time DATE_SUB(CURDATE(), days ) AND patient_id 100000 }) .load(); DatasetRow stats glucose .groupBy(patient_id) .agg( avg(glucose_value).as(avg_glucose), stddev(glucose_value).as(glucose_stddev), sum(when(col(glucose_value).geq(13.9), 1).otherwise(0)).as(high_count) ); stats.createOrReplaceTempView(stats); // 根据业务规则生成风险等级 DatasetRow risk spark.sql( SELECT patient_id, CASE WHEN avg_glucose 10 OR glucose_stddev 3 OR high_count 10 THEN high WHEN avg_glucose 8 OR glucose_stddev 2 OR high_count 5 THEN medium ELSE low END AS risk_level FROM stats); risk.write() .mode(overwrite) .format(jdbc) .option(url, jdbcUrl) .option(dbtable, patient_risk_score) .option(user, dbUser) .option(password, dbPass) .option(driver, com.mysql.cj.jdbc.Driver) .save(); spark.stop(); } }需要说明的是predicates参数。这个参数把读取MySQL的任务拆成多个分区每个分区一个SQL条件避免一个JDBC连接拉全表导致Driver端的OOM。这里按patient_id范围拆成两段真实环境应该按主键范围拆更多段比如每50万条一个分区。风险等级的阈值不是拍脑袋写的。这里我用的是“平均血糖10”作为高危线略微高于诊断标准里“控制尚可”的上限“超标次数10”意味着过去90天超过十分之一的天数处于高值需要临床关注。如果你的医院有自己的质控数据这些阈值要重新校准不要照抄。4.3 分析结果回写业务库让随访计划动态更新Spark任务算出的patient_risk_score表本质是个标签表。业务系统里有定时任务每天读取这张表如果发现某个患者的风险等级和patient表里的risk_level不一致就更新档案并且重新生成下一次随访计划。这个闭环让你的随访系统从“固定频率”升级成“风险驱动”。低风险患者拉长随访间隔减少骚扰高风险患者自动加密随访并且会触发上一章讲的紧急干预逻辑。代码逻辑不复杂关键是不要每次全量更新所有患者用SQL只捞“risk_level发生变化”的行。-- 假设 jdbc 已经从 Spark 写完 risk 表 UPDATE patient p JOIN patient_risk_score s ON p.id s.patient_id SET p.risk_level s.risk_level, p.update_time NOW() WHERE p.risk_level ! s.risk_level;这样一条SQL实现增量更新避免产生大量无效的Binlog与审计日志。如果你用的是MyBatis-Plus也可以用UpdateWrapper带嵌套查询但SQL直连在分析任务里更直观。5. 避坑记录上线前后最容易翻车的五个细节5.1 随访任务重复生成闹钟像闹鬼现象患者一天能收到三通随访电话护士和管理员后台看到大量重复的随访计划。原因定时任务没跑完下一次调度就开始了。比如generatePlansForAllPatients执行超过30分钟而Scheduled默认单线程串行理论上不会重叠。问题出在部署了多实例的时候两台机器同时执行定时任务。解决给任务加Redis分布式锁。用setIfAbsent方法抢锁执行完再删锁锁过期时间设为任务耗时的3倍。Component public class ScheduledPlanGenerator { Resource private StringRedisTemplate redisTemplate; Scheduled(cron 0 0 2 * * ?) public void run() { Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:generatePlans, 1, Duration.ofMinutes(30)); if (!Boolean.TRUE.equals(locked)) { return; } try { planService.generatePlansForAllPatients(); } finally { redisTemplate.delete(lock:generatePlans); } } }注意锁的过期时间要考虑任务最慢情况比预计慢3倍以上避免任务还在跑锁就释放了导致另一台机器进来重复执行。5.2 血糖时间戳时区错乱趋势图变成锯齿现象患者早上8点测的血糖在系统里显示成凌晨0点甚至有的显示到昨天趋势图上下乱跳。原因三层时间错位。前端小程序把时间转成UTC字符串上传后端用LocalDateTime接收然后MySQL连接串里没加serverTimezoneJDBC驱动用了系统默认时区。再加上Spark任务的spark.sql.session.timeZone没设置同一个时间戳在不同组件里读出不一样。解决统一规范“后端存储一律使用UTC时间展示层转本地时区”。我的做法是MySQL连接串里写serverTimezoneUTCJava实体用LocalDateTime读取在返回前端时用Jackson的时区配置转成Asia/Shanghai。Spark侧加一句spark.conf().set(spark.sql.session.timeZone, UTC)保证聚合计算时的时间窗一致。5.3 Spark连接MySQL时Driver加载失败现象Spark任务提交后报ClassNotFoundException: com.mysql.cj.jdbc.Driver而同样的驱动在Spring Boot里能用。原因Spark应用运行在独立的JVM里Classpath与业务应用不同。你用Maven把mysql-connector-java标为runtime业务侧没问题但分析任务打包时候没把这个驱动带进去。解决提交Spark应用时使用--jars mysql-connector-java-xxx.jar或者干脆在分析模块的pom里把驱动设为compile。我一般把分析模块独立打包用maven-shade-plugin做成fat jar把驱动和Spark依赖一起打进去。注意Spark本身的内存开销fat jar比较大提交时--driver-memory要留足。5.4 明文存储敏感字段上线前被甲方打回现象系统的“患者管理”页面能直接看到身份证号、家庭住址测试环境没感觉等保测评时直接被整改。原因医疗健康数据属于敏感个人信息按照法规要求必须加密存储。很多开发图方便把身份证、电话直接存字符串QQ邮箱和手机号都不加密上线后不可挽回。解决数据库层面至少对证件号、手机号做加密Java里用AES-GCM加一层。业务查询时如果需要外呼手机号可以解密后使用但接口给前端返回时做脱敏例如138****1234。我在代码里封装了一个EncryptUtil所有写库操作经过它处理读库时在带搜索条件的字段上再做匹配。注意不要用MD5当加密没有密钥的哈希等于裸奔。5.5 同一患者多条档案分析结果虚高现象通过手机号能查到两个患者一个来自院内部系统一个来自随访App手动建档血糖记录分散在两份档案里风险评分被稀释或重复计算。原因患者主索引没有做好。HIS系统以就诊卡IDApp以手机号两个ID没打通导致同一人数据分裂。解决在建患者表时加一个unified_patient_id字段先按身份证号查找找不到再按手机号合并。分析任务以这个统一ID为分组键而不是patient_id。如果历史数据已经脏了需要写一个合并脚本先按身份证、再按手机号做相似度匹配把重复档案的血糖记录迁移到主档案。这个活儿不轻松但越早做越省事。6. 验证模型是否有效用历史数据做一次回测风险分层的阈值写好后不能直接拍板上线。我建议先用历史数据做一次回测验证“上个月打上高危标签的患者未来是不是真的容易出问题”。具体做法是切两条时间线用前6个月的血糖数据计算风险标签然后看后3个月这些患者里有多少人出现了紧急随访、住院或血糖值连续超过16.7的事件。如果高危组的事件发生率明显高于低危组说明模型有效如果不显著就要调整阈值或增加特征。代码上不需要Spark直接用SQL就能完成回测。假设我们有一个emergency_visit表记录紧急事件SELECT r.risk_level, COUNT(DISTINCT p.id) AS total_patients, COUNT(DISTINCT e.patient_id) AS emergency_patients, COUNT(DISTINCT e.patient_id) / COUNT(DISTINCT p.id) AS event_rate FROM patient_risk_score r LEFT JOIN patient p ON p.id r.patient_id LEFT JOIN emergency_visit e ON e.patient_id p.id AND e.visit_time BETWEEN 2024-01-01 AND 2024-03-31 WHERE r.calc_date BETWEEN 2023-07-01 AND 2023-12-31 GROUP BY r.risk_level;回测里最容易被忽略的是“时间穿越”。如果用患者后3个月的血糖数据去算风险标签再拿风险标签预测后3个月的事件那是拿未来预测未来指标虚高得厉害。正确做法是严格把数据分割成训练期和观察期再用measure_time过滤血糖记录确保用来计算标签的数据都发生在观察期之前。做这个回测时我吃过一次亏。当时为了省事直接用了当前全量血糖数据算标签再用历史事件做验证结果模型表现“完美”上线后一塌糊涂。后来才意识到是数据窗口重叠了。从此以后我养成一个习惯分析模型的验证规则里第一行永远写“不允许任何一行训练数据的时间晚于验证数据的时间”。这个习惯救了我很多次。希望帮到你。本文还有配套的精品资源点击获取
返回列表