Java版本TransE代码的学习

news2024/11/22 16:11:11

参考资料

Anery/transE: transE算法 简单python实现 FB15k (github.com)

Translating Embeddings for Modeling Multi-relational Data (nips.cc)

输入

1.数据集S

2.Entities集合E

3.Relations集合L

4.margin hyperparameter γ

5.每个向量的长度 k

初始化

 

为Entities集合中的每个实体以及Relation集合中的实体,初始化一个向量,并对初始化的向量进行L2范数归一化

其中,相同的entity在头或者在尾出现,都是使用相同的向量

for (int i = 0; i < relation_num; i++) {  
    for (int j = 0; j < vector_len; j++) {  
        relation_vec[i][j] = uniform(-6 / sqrt(vector_len), 6 / sqrt(vector_len));  
    }  
}  
for (int i = 0; i < entity_num; i++) {  
    for (int j = 0; j < vector_len; j++) {  
        entity_vec[i][j] = uniform(-6 / sqrt(vector_len), 6 / sqrt(vector_len));//初始化所有的数据组合都有一个向量  
    }  
    norm(entity_vec[i], vector_len);  
}
    static double uniform(double min, double max) {
        // generate a float number which is in [min, max), refer to the Python uniform
        return min + (max - min) * Math.random();
    }

用梯度下降更新每个初始化的向量

1.第6行表示,每次迭代,都从数据集中随机抽出大小为b的数据,为Sbatch

2.第7到10行表示,替换头或者尾生成错误的数据,正确和错误的数据是Tbatch中的一个子集

3.根据损失值,分别更新h的向量,l的向量,t的向量,错误的h‘或者错误的t’向量

其中,代码中的更新顺序会和伪代码有略微的差别。另外一个问题是代码的终止条件是按循环的次数,但实际上论文当中写的是按照验证集的预测效果来终止迭代

for (int epoch = 0; epoch < nepoch; epoch++) {
            res = 0;  // means the total loss in each epoch
            for (int batch = 0; batch < nbatches; batch++) {
                for (int k = 0; k < batchsize; k++) {
                    int i = rand_max(fb_h.size());//第i条数据
                    int j = rand_max(entity_num);//生成一个随机的节点,第j个节点
                    int relation_id = fb_r.get(i);//第i条数据的relation
                   double pr = 1000 * right_num.get(relation_id) / (right_num.get(relation_id) + left_num.get(relation_id));//随机选择
                    if (method == 0) {
                        pr = 500;
                    }
                    if (rand() % 1000 < pr) {
                        Pair<Integer, Integer> key = new Pair<>(fb_h.get(i), fb_r.get(i));
                        Set<Integer> values = head_relation2tail.get(key);  // 获取头实体和关系对应的尾实体集合
                        while (values.contains(j)) {
                            j = rand_max(entity_num);//这个随机节点需要是一个错误的数值,生成尾巴是错误的数据
                        }
                        res += train_kb(fb_h.get(i), fb_l.get(i), fb_r.get(i), j, fb_l.get(i), fb_r.get(i), res);
                    } else {
                        Pair<Integer, Integer> key = new Pair<>(j, fb_r.get(i));//生成头是错误的数据
                        Set<Integer> values = head_relation2tail.get(key);
                        if (values != null) {
                            while (values.contains(fb_l.get(i))) {
                                j = rand_max(entity_num);
                                key = new Pair<>(j, fb_r.get(i));
                                values = head_relation2tail.get(key);
                                if (values == null) break;
                            }
                        }
                        res += train_kb(fb_h.get(i), fb_l.get(i), fb_r.get(i), j, fb_l.get(i), fb_r.get(i), res);
                    }
                    norm(relation_vec[fb_r.get(i)], vector_len);//归一化
                    norm(entity_vec[fb_h.get(i)], vector_len);//归一化
                    norm(entity_vec[fb_l.get(i)], vector_len);//归一化
                    norm(entity_vec[j], vector_len);//归一化
                }
            }
            System.out.printf("epoch: %s %s\n", epoch, res);
        }

生成一个随机数

根据数值决定生成错误的头还是错误的尾

                   double pr = 1000 * right_num.get(relation_id) / (right_num.get(relation_id) + left_num.get(relation_id));//随机选择
                    if (method == 0) {
                        pr = 500;
                    }
if (method == 0) {
                        pr = 500;
                    }
                    if (rand() % 1000 < pr) {

生成错误的triple

其中这里生成的数据是原数据集中没有的

生成错误的尾实体数据

                                   if (rand() % 1000 < pr) {
                        Pair<Integer, Integer> key = new Pair<>(fb_h.get(i), fb_r.get(i));
                        Set<Integer> values = head_relation2tail.get(key);  // 获取头实体和关系对应的尾实体集合
                        while (values.contains(j)) {
                            j = rand_max(entity_num);//这个随机节点需要是一个错误的数值,生成尾巴是错误的数据
                        }
                        res += train_kb(fb_h.get(i), fb_l.get(i), fb_r.get(i), fb_h.get(i), j, fb_r.get(i), res);
                    }    }

其中,代码文件中有一个错误的地方,j传递的位置出现了问题

      res += train_kb(fb_h.get(i), fb_l.get(i), fb_r.get(i), fb_h.get(i), j, fb_r.get(i),

生成错误的头实体数据

                        Pair<Integer, Integer> key = new Pair<>(j, fb_r.get(i));//生成头是错误的数据
                        Set<Integer> values = head_relation2tail.get(key);
                        if (values != null) {
                            while (values.contains(fb_l.get(i))) {
                                j = rand_max(entity_num);
                                key = new Pair<>(j, fb_r.get(i));
                                values = head_relation2tail.get(key);
                                if (values == null) break;
                            }
                        }

计算损失值

只有损失值为正数的时候,才执行embedding的更新

 

    static double train_kb(int head_a, int tail_a, int relation_a, int head_b, int tail_b, int relation_b, double res) {
        // 极大似然估计的计算过程
        double sum1 = calc_sum(head_a, tail_a, relation_a);
        double sum2 = calc_sum(head_b, tail_b, relation_b);
        if (sum1 + margin > sum2) { {

计算向量距离,即上面的sum1和sum2

 

两种计算方式,一种是用绝对值,另一种是开方

其中,sum是对每一位的差值进行累加

    static double calc_sum(int e1, int e2, int rel) {
        // 计算头实体、关系与尾实体之间的向量距离
        double sum = 0;
        if (L1_flag) {
            for (int i = 0; i < vector_len; i++) {
                sum += abs(entity_vec[e2][i] - entity_vec[e1][i] - relation_vec[rel][i]);
            }
        } else {
            for (int i = 0; i < vector_len; i++) {
                sum += sqr(entity_vec[e2][i] - entity_vec[e1][i] - relation_vec[rel][i]);
            }
        }
        return sum;
    }

更新embedding

对正确的embedding中的向量head,relation,tail执行梯度下降

对错误的embedding中的向量head,relation,tail执行梯度下降

gradient(head_a, tail_a, relation_a, head_b, tail_b, relation_b);
    static void gradient(int head_a, int tail_a, int relation_a, int head_b, int tail_b, int relation_b) {
        for (int i = 0; i < vector_len; i++) {
            double delta1 = entity_vec[tail_a][i] - entity_vec[head_a][i] - relation_vec[relation_a][i];
            double delta2 = entity_vec[tail_b][i] - entity_vec[head_b][i] - relation_vec[relation_b][i];
            double x;
            if (L1_flag) {
                if (delta1 > 0) {
                    x = 1;
                } else {
                    x = -1;
                }
                relation_vec[relation_a][i] += x * learning_rate;
                entity_vec[head_a][i] += x * learning_rate;
                entity_vec[tail_a][i] -= x * learning_rate;

                if (delta2 > 0) {
                    x = 1;
                } else {
                    x = -1;
                }
                relation_vec[relation_b][i] -= x * learning_rate;
                entity_vec[head_b][i] -= x * learning_rate;
                entity_vec[tail_b][i] += x * learning_rate;
            } else {
                delta1 = abs(delta1);
                delta2 = abs(delta2);
                relation_vec[relation_a][i] += learning_rate * 2 * delta1;
                entity_vec[head_a][i] += learning_rate * 2 * delta1;
                entity_vec[tail_a][i] -= learning_rate * 2 * delta1;

                relation_vec[relation_b][i] -= learning_rate * 2 * delta2;
                entity_vec[head_b][i] -= learning_rate * 2 * delta2;
                entity_vec[tail_b][i] += learning_rate * 2 * delta2;
            }
        }
    }

对更新后的向量,执行归一化

                    norm(relation_vec[fb_r.get(i)], vector_len);//归一化
                    norm(entity_vec[fb_h.get(i)], vector_len);//归一化
                    norm(entity_vec[fb_l.get(i)], vector_len);//归一化
                    norm(entity_vec[j], vector_len);//归一化

完成预测

    public void run() throws IOException {
        relation_vec = new double[relation_num][vector_len];
        entity_vec = new double[entity_num][vector_len];

        Read_Vec_File("resource/result/relation2vec.bern", relation_vec);
        Read_Vec_File("resource/result/entity2vec.bern", entity_vec);

        int head_meanRank_raw = 0, tail_meanRank_raw = 0, head_meanRank_filter = 0, tail_meanRank_filter = 0;  // 在正确三元组之前的匹配距离之和
        int head_hits10 = 0, tail_hits10 = 0, head_hits10_filter = 0, tail_hits10_filter = 0;  // 在正确三元组之前的匹配个数之和
        int relation_meanRank_raw = 0, relation_meanRank_filter = 0;
        int relation_hits10 = 0, relation_hits10_filter = 0;

        // ------------------------ evaluation link predict ----------------------------------------
        System.out.printf("Total test triple = %s\n", fb_l.size());
        System.out.printf("The evaluation of link predict\n");
        for (int id = 0; id < fb_l.size(); id++) {
            int head = fb_h.get(id);
            int tail = fb_l.get(id);
            int relation = fb_r.get(id);
            List<Pair<Integer, Double>> head_dist = new ArrayList<>();//预测头
            for (int i = 0; i < entity_num; i++) {
                double sum = calc_sum(i, tail, relation);//计算所有组合的距离
                head_dist.add(new Pair<>(i, sum));
            }
            Collections.sort(head_dist, (o1, o2) -> Double.compare(o1.b, o2.b));//对headlist排序
            int filter = 0;  // 统计匹配过程已有的正确三元组个数
            for (int i = 0; i < head_dist.size(); i++) {
                int cur_head = head_dist.get(i).a;
                if (hrt_isvalid(cur_head, relation, tail)) {  // 如果当前三元组是正确三元组,则记录到filter中
                    filter += 1;
                }
                if (cur_head == head) {
                    head_meanRank_raw += i; // 统计小于<h, l, r>距离的数量
                    head_meanRank_filter += i - filter;
                    if (i <= 10) {
                        head_hits10++;//不过滤的结果
                    }
                    if (i - filter <= 10) {//去掉在数据集中存在,但不是想要的结果数据
                        head_hits10_filter++;//过滤的结果
                    }
                    break;
                }
            }

            filter = 0;
            List<Pair<Integer, Double>> tail_dist = new ArrayList<>();//预测尾巴
            for (int i = 0; i < entity_num; i++) {
                double sum = calc_sum(head, i, relation);
                tail_dist.add(new Pair<>(i, sum));
            }
            Collections.sort(tail_dist, (o1, o2) -> Double.compare(o1.b, o2.b));
            for (int i = 0; i < tail_dist.size(); i++) {
                int cur_tail = tail_dist.get(i).a;
                if (hrt_isvalid(head, relation, cur_tail)) {
                    filter++;
                }
                if (cur_tail == tail) {
                    tail_meanRank_raw += i;
                    tail_meanRank_filter += i - filter;
                    if (i <= 10) {
                        tail_hits10++;
                    }
                    if (i - filter <= 10) {
                        tail_hits10_filter++;
                    }
                    break;
                }
            }
        }
        System.out.printf("-----head prediction------\n");
        System.out.printf("Raw MeanRank: %.3f,  Filter MeanRank: %.3f\n",
                (head_meanRank_raw * 1.0) / fb_l.size(), (head_meanRank_filter * 1.0) / fb_l.size());
        System.out.printf("Raw Hits@10: %.3f,  Filter Hits@10: %.3f\n",
                (head_hits10 * 1.0) / fb_l.size(), (head_hits10_filter * 1.0) / fb_l.size());

        System.out.printf("-----tail prediction------\n");
        System.out.printf("Raw MeanRank: %.3f,  Filter MeanRank: %.3f\n",
                (tail_meanRank_raw * 1.0) / fb_l.size(), (tail_meanRank_filter * 1.0) / fb_l.size());
        System.out.printf("Raw Hits@10: %.3f,  Filter Hits@10: %.3f\n",
                (tail_hits10 * 1.0) / fb_l.size(), (tail_hits10_filter * 1.0) / fb_l.size());

        // ------------------------ evaluation relation-linked predict ----------------------------------------
        int relation_hits = 5;  // 选取hits@5为评价指标
        for (int id = 0; id < fb_l.size(); id++) {
            int head = fb_h.get(id);
            int tail = fb_l.get(id);
            int relation = fb_r.get(id);
            List<Pair<Integer, Double>> relation_dist = new ArrayList<>();
            for (int i = 0; i < relation_num; i++) {
                double sum = calc_sum(head, tail, i);
                relation_dist.add(new Pair<>(i, sum));
            }
            Collections.sort(relation_dist, (o1, o2) -> Double.compare(o1.b, o2.b));
            int filter = 0;  // 统计匹配过程已有的正确三元组个数
            for (int i = 0; i < relation_dist.size(); i++) {
                int cur_relation = relation_dist.get(i).a;
                if (hrt_isvalid(head, cur_relation, tail)) {  // 如果当前三元组是正确三元组,则记录到filter中
                    filter += 1;
                }
                if (cur_relation == relation) {
                    relation_meanRank_raw += i; // 统计小于<h, l, r>距离的数量
                    relation_meanRank_filter += i - filter;
                    if (i <= 5) {
                        relation_hits10++;
                    }
                    if (i - filter <= 5) {
                        relation_hits10_filter++;
                    }
                    break;
                }
            }
        }
        System.out.printf("-----relation prediction------\n");
        System.out.printf("Raw MeanRank: %.3f,  Filter MeanRank: %.3f\n",
                (relation_meanRank_raw * 1.0) / fb_r.size(), (relation_meanRank_filter * 1.0) / fb_r.size());
        System.out.printf("Raw Hits@%d: %.3f,  Filter Hits@%d: %.3f\n",
                relation_hits, (relation_hits10 * 1.0) / fb_r.size(),
                relation_hits, (relation_hits10_filter * 1.0) / fb_r.size());
    }

首先读取训练集中的文件

读取relation的向量,entity的向量

        Read_Vec_File("resource/result/relation2vec.bern", relation_vec);
        Read_Vec_File("resource/result/entity2vec.bern", entity_vec);

以预测head为例

每一条数据为一个测试样例

1.计算每一个答案的距离(分数)

2.对答案降序排名

3.统计过滤的结果,以及不过滤的结果

过滤的结果,在数据集当中有,满足head的条件,但是与这条数据中的head不相同,在计算排名的时候将这些答案过滤掉

        // ------------------------ evaluation link predict ----------------------------------------
        System.out.printf("Total test triple = %s\n", fb_l.size());
        System.out.printf("The evaluation of link predict\n");
        for (int id = 0; id < fb_l.size(); id++) {
            int head = fb_h.get(id);
            int tail = fb_l.get(id);
            int relation = fb_r.get(id);
            List<Pair<Integer, Double>> head_dist = new ArrayList<>();//预测头
            for (int i = 0; i < entity_num; i++) {
                double sum = calc_sum(i, tail, relation);//计算所有组合的距离
                head_dist.add(new Pair<>(i, sum));
            }
            Collections.sort(head_dist, (o1, o2) -> Double.compare(o1.b, o2.b));//对headlist排序
            int filter = 0;  // 统计匹配过程已有的正确三元组个数
            for (int i = 0; i < head_dist.size(); i++) {
                int cur_head = head_dist.get(i).a;
                if (hrt_isvalid(cur_head, relation, tail)) {  // 如果当前三元组是正确三元组,则记录到filter中
                    filter += 1;
                }
                if (cur_head == head) {
                    head_meanRank_raw += i; // 统计小于<h, l, r>距离的数量
                    head_meanRank_filter += i - filter;
                    if (i <= 10) {
                        head_hits10++;//不过滤的结果
                    }
                    if (i - filter <= 10) {//去掉在数据集中存在,但不是想要的结果数据
                        head_hits10_filter++;//过滤的结果
                    }
                    break;
                }
            }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务发现组件:Consul简易攻略

简介 Consul是一种分布式、高可用性、支持多数据中心的解决方案&#xff0c;可动态、分布式基础架构连接和配置的应用程序。 特性 『多数据中心』  Consul是为支持数据中心而构建的&#xff0c;可以支持任意数量的区域&#xff0c;而无需复杂的配置。『服务网格』  Consul服…

ROS移动机器人——32电机驱动

驱动电机&#xff0c;我们在这里使用TB6612来驱动电机&#xff0c;同时&#xff0c;电机的内部我们有编码器进行计次 输出比较 cnt&#xff0c;就是时基单元寄存器 ccr&#xff0c;在cnt下方&#xff0c;为捕获比较寄存器&#xff0c;cc是捕获比较的意思&#xff0c;r就是寄存…

内存溢出问题排查

一、问题背景下午突发服务器CPU频繁撑爆&#xff0c;服务启动后不久就挂掉。一周前系统有一次投产&#xff0c;之后再没有更新过系统。同时在日志中看到大量的dubbo服务调用失败。二、排查问题产生原因1.查看JVM崩溃日志hs_err_pid.logJVM崩溃时会生成hs_err_pid_xxx.log日志文…

面经分享|2022年秋招斩获7个规控算法工程师offer

该面经来自深蓝学院用户投稿&#xff0c;作者为东北大学自动化专业硕士。 作者2022年秋招情况&#xff1a;投递的岗位包括决策规划&#xff0c;规划控制和控制岗位。本人累计投递70家自动驾驶公司&#xff0c;最终收获10多家公司的笔试或面试反馈。最终拿到的offer包括主机厂、…

抖音短视频运营中的六大定位法

抖音六大定位法 1年龄反转法年龄反转法的秘诀就是指&#xff0c;所设定的人物、性格与实际的年龄并不是特别符合&#xff0c;从而让用户产生比较强烈的差异感。比如之前很火的北海爷爷&#xff0c;是一位有着七十多岁高龄的先生&#xff0c;但他依然很有精神&#xff0c;举止优…

【Mybatis-plus 入门教程】

&#x1f308;博客主页&#xff1a;屠一乐的博客 &#x1f4c5; 发文时间&#xff1a;2023.1.6 &#x1f388; 一定存在只有你才能做成的事 &#x1f339; 博主水平有限&#xff0c;如有错误&#xff0c;欢迎指正 欢迎各位&#x1f44d;收藏&#x1f48e;评论✉ MyBatisPlus …

舆情监测技术手段有哪些,网络舆情监测监测技术应用到哪些方面?

随着网络舆情的快速发展&#xff0c;舆情影响到方方面面&#xff0c;大多数企业将网络舆情监测纳入其日常工作。对于舆情监测上如何做到全面监测&#xff0c;那些技术上可以实现&#xff0c;接下来TOOM舆情监测小编带您简单了解舆情监测技术手段有哪些&#xff0c;网络舆情监测…

C语言 文件处理

文件操作 为什么使用文件 什么是文件 程序文件 数据文件 文件名 文件的打开和关闭 文件指针 只要没打开一个文件就会有一个文件信息区&#xff0c;只要一更改文件信息区也会跟着更改 **FILE *fopen( const char filename, const char mode ); filename 文件名 *char mode 打…

疑难杂症之vscode--During startup program exited with code 0xc0000139.--缺失重要文件(杂记)

问题展示在vscode中&#xff0c;只要用了STL容器&#xff0c;就会出现这样的提示发现自己的 vscode 不能运行带有部分 stl 库的程序&#xff0c;编译不会报错&#xff0c;运行也不会报错但是也没有结果&#xff0c;调试的话会有下图中报错。拿一个以前的程序做测试&#xff0c;…

Java 集合系列:Vector源码深入解析

概论 学完ArrayList和LinkedList之后&#xff0c;我们接着学习Vector。学习方式还是和之前一样&#xff0c;先对Vector有个整体认识&#xff0c;然后再学习它的源码&#xff1b;最后再通过实例来学会使用它。 第1部分 Vector介绍 Vector简介 Vector 是矢量队列&#xff0c;…

java服装商城购物商场项目源码

简介 Java基于ssm开发的服装商城&#xff0c;用户可以浏览商品和特价商品&#xff0c;加入购物车&#xff0c;直接下单支付&#xff0c;在我的个人中心里可以管理自己的订单&#xff0c;收货地址&#xff0c;编辑资料等。管理员可以发布商品&#xff0c;上下架商品&#xff0c…

Neo4j详细介绍及使用教程

文章目录一、Neo4j介绍1.Neo4j简介2.图数据库简介3.Neo4j的优缺点4.Neo4j的常见应用场景二、使用教程1.下载安装2.数据插入和查询(1)基本概念(2)基本语法Ⅰ.CREATE操作——创建Ⅱ.MERGE——创建或更新Ⅲ.Match操作——查找指定的图数据Ⅳ.DELETE操作——删除节点3.JAVA实战一、…

FPGA之VGA/LCD数字时钟显示

文章目录前言一、LCD显示控制1.LCD显示一个字符2.LCD显示多个字符二、数字时钟输出1.数字时钟2.十进制数据拆分BCD码三、按键检测及LCD驱动1.按键检测2.LCD驱动四、总结前言 软件实现了在4.3寸LCD左上角显示一个数字时钟&#xff0c;效果如下图所示。本文针对VGA/LCD控制时许有…

leetcode:2103. 环和杆(python3解法)

难度&#xff1a;简单 总计有 n 个环&#xff0c;环的颜色可以是红、绿、蓝中的一种。这些环分布穿在 10 根编号为 0 到 9 的杆上。 给你一个长度为 2n 的字符串 rings &#xff0c;表示这 n 个环在杆上的分布。rings 中每两个字符形成一个 颜色位置对 &#xff0c;用于描述每个…

makefile 入门

make常用选项 # make 默认在当前目录中寻找GUNmakefile,makefile,Makefile的文件作为make的输入文件 # -f 可以指定默认的输入文件名,如: -f MyMakefile # -v 显示make版本号 # -n 只输出命令,但不执行,一般用于测试 # -s 只执行命令,但不显示具体命令,与在命令中使用作用一样…

第四十四讲:神州防火墙双机热备配置

两台防火墙硬件型号和软件版本都完全相同&#xff0c;为了避免防火墙不堪重负而宕机引起网络中断&#xff0c;可以考虑应用双机热备&#xff08;HA&#xff09;解决方案。双机热备能够把两台防火墙构成一个工作组&#xff0c;一主一备&#xff0c;保证数据通信畅通&#xff0c;…

【实际开发01】- 单元测试 ( 追求正确性 )

目录 0. 单元测试 概念 / 解析 1. 为什么要进行单元测试 1. JUnit ~ Test 2. IDEA 中使用 junit 单元测试 , 不能使用 Scanner 的解决方法 3. Junit 测试 Tutorial 1. daiding 4. Test 修饰的方法必须 public 1. validatePublicVoidNoArgMethods(Test.class, false, er…

功率二极管的损耗分析和选型原则

功率二极管的损耗分析和选型原则 tip&#xff1a;参考网上资料&#xff0c;学习为主 1.二极管的分类 2.二极管的损耗组成 3.二级管的损耗分析 4.应用实例1.Flyback电源电路二极管损耗计算 5.实例应用2.BOOST电路二极管损耗计算 6.实例应用3.大功率整流桥二极管参数计算 7.选型…

sqli-labs 5~6 多命通关攻略

sqli-labs 5~6 多命通关攻略描述判断注入类型正常输入不正常输入错误输入判断 SQL 查询结果的列数猜测 SQL 查询结果中的列数为两列猜测 SQL 查询结果中的列数为三列猜测 SQL 查询结果中的列数为四列爆破方式的可行性函数 UpdateXML()爆破&#xff08;报错注入&#xff09;爆破…

农业智能化进入“刚需时代 ” ,维视智造机器视觉实验室赋能新农科人才培养

1、传统农业数字化转型 新农科人才急需紧缺数千年来&#xff0c;农业是我国立国基础&#xff0c;农业兴衰关系到国家的命运。在大力推动乡村振兴的背景下&#xff0c;高校作为强农兴农的“国之重器”&#xff0c;在培育“农”的传人、新农科建设方面扮演着不可替代的角色。世界…