news 2026/10/7 10:14:35

SpringBoot食物营养分析与推荐系统:数据模型到混合推荐实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot食物营养分析与推荐系统:数据模型到混合推荐实战

简介:这是一份面向Java学习者与课程设计学生的SpringBoot实战项目源码,实现了食物信息查询、营养成分分析和个性化饮食推荐等完整功能。项目采用SpringBoot与MySQL为主要技术栈,前端结合Vue、HTML/CSS/JavaScript构建交互界面,后台包含系统管理、数据维护等模块,可帮助读者理解前后端分离开发与推荐算法在真实项目中的落地方式。系统内置的营养分析覆盖蛋白质、脂肪、碳水化合物、维生素和矿物质等维度,推荐逻辑会结合年龄、性别、运动量及健康目标给出个性化饮食方案。资源包共649个文件,约40.09MB,涵盖124个Java源码、99个Vue页面、63个JavaScript脚本、SQL数据库脚本及部署说明,并附带安装/运行批处理和爬虫配置,便于快速搭建环境,也适合二次开发。已有49人学习,可作为毕业设计、课程设计的重要参考,也可在现有代码上扩展食物库数据、优化推荐逻辑,从而积累完整的SpringBoot Web项目开发经验。

1. “吃什么”这个每日难题,为什么值得做一个 SpringBoot 网站

每天一到饭点,问自己“吃什么”的次数,可能比写业务代码的次数还多。食物营养分析与推荐网站要解决的,就是这个高频且高度个性化的问题:站在一个人面前,知道他今天的热量消耗、饮食习惯和忌口,给他推荐一份既能吃饱、营养又达标、还不至于吃完就后悔的餐单。这类系统在健康管理、食堂订餐、减脂陪跑和医疗膳食辅助场景里都有实际需求,也是 SpringBoot 技术栈里少见的“后端算法 + 业务 CRUD + 推荐逻辑”三者俱全的项目形态。这个 .zip 背后就是一个完整的、能跑通的工程方案,适合正在做毕业设计、想给简历补一个可演示的推荐系统项目、或者打算把营养配餐做成小产品的开发者参考。

2. 营养数据模型怎么建:从“每 100 克”到“你吃的那一盘”

2.1 食物成分表的建表思路:三张表还是单表

做营养分析,先得有数据。市面上能参考的公开数据源是中国食物成分表,一般以“每 100 克可食部”为基准记录能量、蛋白质、脂肪、碳水化合物、膳食纤维、钠、钾、钙等指标。真正建库时有两种常见做法,我推荐用“食物主表 + 营养素字典 + 营养数值关联”的三张表设计,而不是把所有营养素堆在一张表里。原因很实际:不同用户关注不同的营养素,减脂的人看热量和脂肪,高血压人群看钠,三张表可以随时扩展营养素种类,不用改表结构。

食物主表 food 只放基本信息和可食部,每 100 克可食部表示营养数值的基准单位。营养素字典 nutrient 维护营养素名称和推荐每日摄入量。食物营养关联表 food_nutrient 存具体数值,支持同一食物多条营养记录。下面是 MySQL 建表脚本的核心片段:

CREATE TABLE food ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT '食物名称', category VARCHAR(32) COMMENT '分类,如主食/蔬菜/肉类', edible_ratio DECIMAL(5,2) DEFAULT 100.00 COMMENT '可食部比例(克/100克)', heat DECIMAL(8,2) COMMENT '每100克可食部热量(kcal)', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT '食物主表'; CREATE TABLE nutrient ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) UNIQUE NOT NULL COMMENT '营养素编码,如PROTEIN', name VARCHAR(32) NOT NULL COMMENT '营养素名称', daily_value DECIMAL(8,2) COMMENT '成人每日推荐摄入量' ) COMMENT '营养素字典'; CREATE TABLE food_nutrient ( id BIGINT PRIMARY KEY AUTO_INCREMENT, food_id BIGINT NOT NULL, nutrient_id BIGINT NOT NULL, amount DECIMAL(8,2) NOT NULL COMMENT '每100克可食部的含量(克或毫克)', INDEX idx_food (food_id), INDEX idx_nutrient (nutrient_id) ) COMMENT '食物营养关联表';

逻辑说明:food_nutrient 是典型的多对多关联表,一个食物有十几条营养记录,一条营养记录也对应多个食物。amount 字段不直接叫“含量”,是因为不同营养素的单位不同,蛋白质用“克”,钠用“毫克”,统一存数值、在代码里做单位换算,表结构更干净。edible_ratio 是可食部概念,比如带骨鸡腿只有 70% 能吃,计算时先折算。

参数说明:DECIMAL(8,2) 比 FLOAT 靠谱,浮点类型在累计营养摄入时会出现 0.1 + 0.2 不等于 0.3 的精度问题,做营养分析这种对数值敏感的业务,一律用定点数。daily_value 参考成年人标准,热量取 2000 kcal、蛋白质取 60g、钠取 2000mg,这些值在代码里要能配置,不能写死,因为推荐模块会按性别、年龄做修正。

2.2 营养摄入计算:份量换算与单位归一

用户点了一盘菜,记录的是“吃了 200 克”,而食物成分表是“每 100 克可食部”。计算逻辑要先把实际摄入量按可食部和基准单位折算:实际可食部克数 = 输入克数 × 可食部比例 / 100,然后 各项营养素 = 实际可食部克数 × 每 100 克含量 / 100。如果某种营养素在数据库里单位是毫克,页面要显示克,就得在 Service 层统一做归一。我一般会建一个 IngestionCalculator 组件,专门负责这类换算和合计算。

@Service public class NutritionCalculator { /** * 计算用户某餐的各类营养素摄入量 * * @param items 餐食条目:食物ID + 实际称重克数 * @return 营养素编码 -> 摄入量(克) */ public Map<String, BigDecimal> calculateMeal(List<MealItem> items) { Map<String, BigDecimal> total = new HashMap<>(); for (MealItem item : items) { Food food = foodMapper.selectById(item.getFoodId()); BigDecimal edibleWeight = item.getWeightInGram() .multiply(food.getEdibleRatio()) .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP); List<FoodNutrient> nutrients = foodNutrientMapper.selectByFoodId(food.getId()); for (FoodNutrient fn : nutrients) { BigDecimal amount = fn.getAmount() .multiply(edibleWeight) .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP); total.merge(fn.getNutrientCode(), amount, BigDecimal::add); } } return total; } }

逻辑说明:这段代码做过两层折算。第一层把“实际入口重量”算出来,鸡腿 200 克、可食部 0.7,实际只算 140 克;第二层把每 100 克基准放大到实际重量。merge 方法使用 BigDecimal::add 做累加,避免用 += 带来的浮点误差。返回的 Map 以营养素编码为键,页面拿到后直接按编码展示。

参数说明:除以 100 时保留了两位小数,用 HALF_UP 四舍五入。营养分析场景小数点后一位就够,保留两位是为了后续做“达标率”计算时不至于因精度问题出现 100.01% 这种尴尬数值。这里没把性别、年龄修正写进去,那些应该在推荐策略层做,不在数据计算层做,因为计算层只负责“账算对”,策略层才负责“怎么吃更合适”。

2.3 数据初始化的两种路径

一般项目起步时没有真实食物数据,我建议用两条路并行:一条是把食物成分表里常用的两三百条数据写成 SQL 初始化脚本,随项目启动时执行;另一条做一个“食物录入”后台接口,方便后续运营人员随时补充。很多毕设只做前者,演示确实够用,但答辩时面试官大概率会问“数据量大怎么办”,所以录入接口建议也带上。

SpringBoot 里做启动初始化,可以用 SQL 脚本放在 src/main/resources/db/ 下,配合 spring.sql.init 配置。注意要设置成“先删后建”还是“增量插入”,否则每次重启数据会翻倍。我一般这么配置:

spring: sql: init: mode: always schema-locations: classpath:db/schema.sql >public List<Food> recommendByRule(UserProfile profile, List<Food> candidates) { return candidates.stream() .filter(f -> !containsAllergen(f, profile.getAllergens())) .filter(f -> isWithinHeatWindow(f, profile)) .filter(f -> isNutrientQualified(f)) .sorted(Comparator.comparing(Food::getHeat)) .limit(10) .collect(Collectors.toList()); }

逻辑说明:filter 链依次完成“不能吃的去掉、热量不合适的去掉、营养不达标的去掉”,最后按热量排序取前 10。这个接口返回的结果可以直接在页面上展示“为什么推荐给你”:在排序前保留命中规则的明细,就能生成推荐理由文案。相比黑匣子式的算法推荐,规则版对用户更友好,也方便你后续做 AB 测试。

参数说明:limit(10) 是我比较常用的推荐位数量,首页推荐位 5 个、每日推荐 10 个。isWithinHeatWindow 的实现里要留热量窗口的上下阈值,我建议做成 @ConfigurationProperties 配置项,便于运营调参,不要写死在代码里,避免每次改规则都要重新打包发布。

3.2 基于用户的协同过滤:用收藏和点餐行为做评分矩阵

当系统积累了一些用户行为后,再上协同过滤才有意义。这个标题挂在“推荐网站”上,协同过滤是明显的加分项。我建议从“基于用户的协同过滤”入手,比较容易理解和实现:找到和你口味相似的用户,把他们喜欢而你还没吃过的食物推荐给你。

评分数据不需要用户打星,用三个行为事件合成一个评分:收藏计 2 分、点餐计 3 分、浏览计 1 分。把这些行为按天衰减,30 天前的行为权重降为原来的 50%。这样做的目的是让评分更贴近当前口味,避免用户去年爱吃炸鸡、今年想减脂,系统还在推炸鸡的情况。

@Autowired private UserBehaviorMapper behaviorMapper; public List<Food> recommendByUserCF(Long userId, int topN) { List<UserBehavior> behaviors = behaviorMapper.selectRecentBehaviors(userId); Map<Long, Double> myVector = buildVector(behaviors); List<Long> similarUsers = behaviorMapper.selectUsersWithCommonFoods(userId); Map<Long, Double> similarityScores = new HashMap<>(); for (Long otherUserId : similarUsers) { List<UserBehavior> otherBehaviors = behaviorMapper.selectRecentBehaviors(otherUserId); Map<Long, Double> otherVector = buildVector(otherBehaviors); double sim = pearsonSimilarity(myVector, otherVector); similarityScores.put(otherUserId, sim); } return similarityScores.entrySet().stream() .sorted(Map.Entry.<Long, Double>comparingByValue().reversed()) .limit(topK) .flatMap(e -> recommendFoodsFromUser(e.getKey(), userId)) .distinct() .limit(topN) .collect(Collectors.toList()); } private double pearsonSimilarity(Map<Long, Double> v1, Map<Long, Double> v2) { // 两个用户共同评分过的食物ID集合 Set<Long> common = new HashSet<>(v1.keySet()); common.retainAll(v2.keySet()); if (common.size() < 3) { return 0.0; // 共同评分太少,相似度不可信 } // 这里省略均值和协方差计算,实际项目用循环累加即可 return calculatePearson(v1, v2, common); }

逻辑说明:selectUsersWithCommonFoods 先做一次粗筛,只找出和我吃过相同食物的用户,避免全量用户两两计算相似度,这个前置过滤在数据量大时能省好几个数量级的计算。pearsonSimilarity 函数里提到 common.size() < 3 时直接返回 0,是因为共同评分数太少,计算出的相关系数不稳定,这个阈值也是需要实际调试的。

参数说明:topK 取相似用户数量,我一般取 20 到 50 之间;topN 是最终推荐条数,取 10。需要注意协同过滤在数据稀疏时效果很差,一个新用户行为记录只有一两条,common.size() 根本过不了 3 的阈值,所以这个模块必须配合规则推荐做降级。

3.3 混合推荐策略:降级顺序和权重合并

到了这一步,就该把两种策略合起来了。我常用的策略是:用户行为数据足够时,协同过滤结果占 70% 权重,规则推荐结果占 30%;行为数据不足时,直接降级为纯规则推荐。这个权重不是拍脑袋定的,可以通过日志和 AB 测试慢慢调。接口实现上不把策略细节暴露给前端,前端只拿 final 推荐列表。

public List<RecommendedFood> recommend(UserContext ctx) { List<RecommendedFood> ruleItems = buildRuleItems(ctx); List<RecommendedFood> cfItems = buildCfItems(ctx); Map<Long, RecommendedFood> merged = new LinkedHashMap<>(); for (RecommendedFood item : cfItems) { merged.put(item.getFoodId(), item); } for (RecommendedFood item : ruleItems) { merged.merge(item.getFoodId(), item, (a, b) -> a.toBuilder().score(a.getScore() * 0.7 + b.getScore() * 0.3).build()); } return merged.values().stream() .sorted(Comparator.comparing(RecommendedFood::getScore).reversed()) .limit(10) .collect(Collectors.toList()); }

逻辑说明:LinkedHashMap 保证插入顺序,所以先插入的协同过滤结果在分数相同时会排在前面。merge 函数的 lambda 做了加权合并:如果同一个食物既在协同过滤结果里也在规则结果里,就用 0.7 和 0.3 的权重算出新分数;如果只出现在其中一边,就保留原始分数。这个合并逻辑比“简单拼接再排序”要精细,它让两路结果有了真正的交互。

参数说明:0.7 和 0.3 的权重不建议一开始就定死,后续可以用网格搜索的方式调,每次迭代一组权重,看点击率变化。这里 toBuilder() 是 Lombok 的 @Builder 配合使用,如果不用 Lombok,手动 new 一个 RecommendedFood 对象再 set 分数也一样。

4. SpringBoot 工程落地:从项目结构到能扛住并发的接口

4.1 项目结构与自动装配的取舍

SpringBoot 项目结构有一个常被忽视的问题:包结构直接影响自动装配能不能扫到。我见过不少毕设翻车,把 Mapper 接口放在 controller 之外的包里,又没加 @MapperScan,启动直接报“找不到 bean”。这个工程我建议按职责分包:controller、service、mapper、entity、config、recommender,启动类放在最外层,保证 @SpringBootApplication 默认扫描路径覆盖所有子包。

@SpringBootApplication @MapperScan("com.example.nutrition.mapper") @EnableScheduling public class NutritionApplication { public static void main(String[] args) { SpringApplication.run(NutritionApplication.class, args); } }

逻辑说明:@MapperScan 指定 MyBatis Mapper 接口所在的包,避免每个 Mapper 都写 @Mapper 注解。@EnableScheduling 打开定时任务功能,后续做每日推荐缓存更新要用。这三行注解基本是 SpringBoot 项目的标配,改成自己的包名就行。

参数说明:包名不建议叫 demo 或 test,系统提交到 git 上后改包名是所有重构里最麻烦的,没有之一。启动类放在顶层包,比如 com.example.nutrition,下面直接分 controller 等子包,这是 SpringBoot 官方推荐的结构,也是扫描路径最不容易出问题的写法。

4.2 REST API 设计与统一返回结构

网站前后端要分离,推荐接口要设计得让前端好接。我习惯定义三个核心接口:食物检索接口 GET /api/foods?keyword=,营养分析接口 POST /api/analysis/meal,推荐接口 GET /api/recommend。每个接口都返回统一结构 Result 对象,前端只看 code 是否为 0,不用每个接口单独判空。

@Data public class Result<T> { private int code; // 0 成功,非 0 失败 private String message; // 错误描述 private T data; // 业务数据 public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 0; r.message = "success"; r.data = data; return r; } }

逻辑说明:Result 泛型设计让所有接口的返回结构统一,Swagger 文档也更好看。code 用 0 表示成功是为了前端判断简单,很多项目的成功码用 200,但 HTTP status 已经是 200 了,body 里再放一个 200 属于重复编码,用 0 反而更清晰。

参数说明:message 在成功时固定为 “success”,失败时由全局异常处理器填充。全局异常处理是必须的,否则 MyBatis 抛出的 SQLException 会以默认格式返回给前端,前端拿到一堆堆栈信息,既不安全也不友好。用 @RestControllerAdvice 统一处理业务异常、参数校验异常和兜底异常,是这里的标准做法。

4.3 缓存和定时任务:把推荐接口的响应时间压到 100ms 以内

推荐接口如果每次请求都实时计算协同过滤,数据库和 CPU 都扛不住。我一般把推荐结果缓存起来,用 Spring Cache 的 @Cacheable 做方法级缓存,缓存 key 是 userId,过期时间设置成 2 小时。用户行为发生变化时,手动调用 CacheManager.evict 清掉对应用户的缓存,保证推荐结果不长期滞后。

@Cacheable(value = "recommend:user", key = "#userId", unless = "#result == null || #result.isEmpty()") public List<RecommendedFood> getRecommendList(Long userId) { UserContext ctx = userContextService.build(userId); return recommendService.recommend(ctx); }

逻辑说明:@Cacheable 会在方法执行前检查缓存是否存在,存在就直接返回,不存在才执行方法体并把结果存入缓存。unless 条件防止空结果也被缓存,这是推荐接口的一个细节:如果用户行为太少导致推荐列表为空,缓存空列表会导致后续请求一直拿到空结果,除非等过期。

参数说明:缓存的过期时间我设为 120 分钟,在配置里是 spring.cache.redis.time-to-live=PT120M。定时任务配合缓存“预热”:每天凌晨 3 点用 @Scheduled 扫描当天的活跃用户,提前算好推荐结果放入缓存,这样白天用户访问时直接命中缓存,接口响应时间基本在 100 毫秒以内,这个数值在答辩演示时非常亮眼。

5. 避坑手册:SpringBoot 版本、缓存穿透与推荐翻车的常见问题排查

5.1 springboot 版本太高导致 MyBatis 启动失败

现象:启动时报 java.lang.NoClassDefFoundError: javax/sql/DataSource,或者 MyBatis 的 SqlSessionFactory 创建失败。我用 SpringBoot 3.2 搭项目时遇到过一次,折腾了半小时才定位到。

原因:SpringBoot 3.x 把 javax 命名空间迁移到了 jakarta,旧版 mybatis-spring-boot-starter 还是基于 javax 编译的,两者不兼容。很多网上的教程还在用 1.x 版本的 starter,直接套用就会翻车。

解决:要么把 SpringBoot 降到 2.7.x,要么把 mybatis-spring-boot-starter 升到 2.3.0 以上。毕设项目我一般推荐直接用 2.7.18 加 mybatis starter 2.3.2,兼容性最稳,网上资料也最多。热词里“springboot版本太高”确实是个高频问题,建议动手前先确认版本组合。

5.2 营养成分出现 0.30000000000000004 这种浮点残影

现象:前端页面显示蛋白质 20.30000000000000004 克,热量 345.99999999999994 千卡。

原因:Java 的 double 计算浮点数有二进制表示误差,食物营养计算又涉及多次乘除,误差被放大。营养分析对数字精度敏感,绝不能直接用 double 累计。

解决:数据库字段用 DECIMAL,Java 对象用 BigDecimal,计算工具类统一用 BigDecimal 而不是 double。注意 BigDecimal 的除法一定要指定精度和舍入模式,比如 divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP),不指定会抛 ArithmeticException。

5.3 推荐结果“偏食”:热门食物霸榜,小众选择永远不见天日

现象:推荐列表翻来覆去是米饭、鸡胸肉、西兰花那几样,用户想换个口味都没机会。

原因:协同过滤的评分数据天然有长尾分布,热门食物被大量用户评分,相似度和推荐分数都碾压小众食物。这就是推荐系统里的“马太效应”。规则推荐里如果只按热量排序,热量适中的食物也会一直霸榜。

解决:在排序策略里加入随机扰动或“探索因子”,比如每 10 次推荐中有 1 次不按分数排序,而是从候选集里随机取 3 个替代食物。也可以给低热度食物加一个加权系数,让它们有机会进列表。加了探索机制后,推荐列表的多样性指标会明显上升,但总体点击率可能短期波动,这是正常现象。

5.4 定时任务在多实例部署时被重复执行

现象:配置的推荐预热任务明明是凌晨 3 点执行一次,实际日志里看到 3 分钟内有两次执行记录,缓存被重复写入。

原因:项目部署了多个实例,每个实例都在跑 @Scheduled 任务,没有做分布式协调。本地开发看不出问题,一旦上生产容器,多个副本同时触发任务就会重复执行。

解决:轻量方案是给任务加 Redis 分布式锁,执行前先尝试设置锁,设置成功才执行逻辑,任务跑完释放锁。更规范的做法是用 ShedLock 库,把锁的持有时间设置为任务最大运行时长加一点余量,比如 recommendedCache.task-timeout=PT10M。毕设部署单机可忽略这个问题,但要在答辩时主动提一句,这个细节能拉开和同龄人的差距。

6. 验证推荐效果的最低成本方案:用日志驱动迭代

推荐系统做到能跑只是第一步,关键是“推荐得好不好”要有量化结论。我常用的低成本方案是在推荐接口里埋两条日志:一条记录“给谁推荐了哪些食物”,一条记录“用户实际点了哪些食物”。这两条日志对上,就能离线算出核心指标:推荐在用户点击中的覆盖率,以及推荐列表被点击的比例。不需要复杂的埋点平台,用 SpringBoot 的日志文件就能做分析。

具体操作是给推荐接口加一个日志切面,输出 userId、推荐列表、点击列表。每周导出一份日志,写个 Python 脚本统计覆盖率。我踩过一个坑:第一次统计的点击率只有 15%,比猜的 30% 差很远。后来发现是因为推荐的 10 个食物里有 4 个用户已经吃腻了,相当于系统一直在推用户不感兴趣的东西。加了一个“近 7 天吃过的食物降权”的逻辑,点击率涨到 22%。这类优化靠的就是日志数据反馈。

验证调优还有一个技巧:把推荐列表从 10 个缩到 5 个,如果点击率不降反升,说明推荐质量大于数量;如果点击率下降,说明用户确实需要更多候选。这个 AB 测试在接口层加一个开关参数就能跑,不需要改动推荐算法。我做这类验证时习惯先在规则推荐和协同过滤的结果上分别打版本号标签,方便日志归类。

做完这一套,回头再看这个 SpringBoot 项目的全部链路:数据模型支撑营养分析,规则加协同过滤做推荐,缓存和定时任务保性能,日志和 AB 测试驱动迭代,每一环都能独立讲清原理和实现。这是我做信息管理系统类项目以来,觉得最容易出亮点、也最能体现工程能力的方向。希望你搭的时候少走点弯路,做完之后的每一次日志分析,都能成为下一次优化的起点,希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 10:13:42

微信在线AI客服系统源码:私有化部署与异步消息处理实战

简介&#xff1a;这是一套基于PHP开发的微信在线AI客服系统源码&#xff0c;面向需要为企业微信搭建智能客服的中小团队与个人开发者&#xff0c;可解决724小时自动应答、人工转接与对话管理等实际需求。压缩包共38个文件&#xff0c;以31个PHP源码文件为主体&#xff0c;另含说…

作者头像 李华
网站建设 2026/10/7 10:13:07

微信小程序+ThinkPHP商城源码快速跑通指南

简介&#xff1a;这是一套开箱即用的微信小程序商城全栈解决方案&#xff0c;面向前端与PHP后端初学者、小型电商项目开发者及教学实践者&#xff0c;解决从零搭建轻量级线上商城的技术门槛问题。资源包含完整的小程序前端&#xff08;商品展示、购物车、订单全流程&#xff09…

作者头像 李华
网站建设 2026/10/7 10:12:33

电机控制器功率器件选型:电气应力、热设计与MCU协同的硬核计算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 10:11:42

ESP32模组选型避坑指南:一文读懂N、R、H、U后缀含义

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 10:11:01

DeepLens 论文精读 · 复现笔记

DeepLens 论文精读 复现笔记 论文&#xff1a;Curriculum Learning for ab initio Deep Learned Refractive Optics 作者&#xff1a;Xinge Yang, Qiang Fu, Wolfgang Heidrich&#xff08;KAUST 计算成像组&#xff09; 发表&#xff1a;Nature Communications 15, 6572 (202…

作者头像 李华