使用最大边界相关算法处理文章自动摘要

news2024/11/26 20:19:02

一、需求背景

       对于博客或者文章来说,摘要是普遍性的需求。但是我们不可能让作者自己手动填写摘要或者直接暴力截取文章的部分段落作为摘要,这样既不符合逻辑又不具有代表性,那么,是否有相关的算法或者数学理论能够完成这个需求呢?我想,MMR(Maximal Marginal Relevance)是处理文章自动摘要的杰出代表。

二、最大边界相关算法—MMR

       MMR算法又叫最大边界相关算法,此算法在设计之初是用来计算Query文本与被搜索文档之间的相似度,然后对文档进行rank排序的算法。算法公式如下:
image.png

       仔细观察下公式方括号中的两项,其中前一项的物理意义指的是待抽取句子和整篇文档的相似程度,后一项指的是待抽取句子和已得摘要的相似程度,通过减号相连,其含义是希望:抽取的摘要既能表达整个文档的含义,有具备多样性 。而image.png则是控制摘要多样性程度的一个超参数,你可以根据自己的需求去调节。

三、使用MMR算法实现文章自动摘要

3.1 具体算法实现

       具体的代码实现如下:

import com.microblog.util.tools.FileUtils;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文本摘要提取文中重要的关键句子,使用top-n关键词在句子中的比例关系
 **/
public class SummaryUtil {
    //保留关键词数量
    int N = 50;

    //关键词间的距离阀值
    int CLUSTER_THRESHOLD = 5;

    //前top-n句子
    int TOP_SENTENCES = 20;

    //最大边缘相关阀值
    double λ = 0.4;

    //停用词列表
    private static final Set<String> stopWords = new HashSet<>();

    //句子编号及分词列表
    Map<Integer, List<String>> sentSegmentWords = null;

    /**
     * 加载停词(文章摘要的停顿/分割词)
     */
    public static void initStopWords(String[] paths) {
        for (String path : paths) {
            try {
                File file = new FileUtils().readFileFromNet(path);
                InputStreamReader read = new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8);
                if (file.isFile() && file.exists()) {
                    BufferedReader br = new BufferedReader(read);
                    String txt = null;
                    while ((txt = br.readLine()) != null) {
                        stopWords.add(txt);
                    }
                    br.close();
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 利用正则将文本拆分成句子
     */
    private List<String> SplitSentences(String text) {
        List<String> sentences = new ArrayList<String>();
        String regEx = "[!?。!?.]";
        Pattern p = Pattern.compile(regEx);
        String[] sents = p.split(text);
        Matcher m = p.matcher(text);
        int sentsLen = sents.length;
        if (sentsLen > 0) {  //把每个句子的末尾标点符号加上
            int index = 0;
            while (index < sentsLen) {
                if (m.find()) {
                    sents[index] += m.group();
                }
                index++;
            }
        }
        for (String sentence : sents) {
            //处理掉的html的标志
            sentence = sentence.replaceAll("(&rdquo;|&ldquo;|&mdash;|&lsquo;|&rsquo;|&middot;|&quot;|&darr;|&bull;)", "");
            sentences.add(sentence.trim());
        }
        return sentences;
    }

    /**
     * 这里使用IK进行分词
     * @param text 待处理句子
     * @return 句子分词列表
     */
    private List<String> IKSegment(String text) {
        List<String> wordList = new ArrayList<String>();
        Reader reader = new StringReader(text);
        IKSegmenter ik = new IKSegmenter(reader, true);
        Lexeme lex = null;
        try {
            while ((lex = ik.next()) != null) {
                String word = lex.getLexemeText();
                if (word.equals("&nbsp;") || stopWords.contains(word) || "\t".equals(word)) continue;
                if (word.length() > 1 ) wordList.add(word);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return wordList;
    }


    /**
     * 每个句子得分  (keywordsLen*keywordsLen/totalWordsLen)
     *
     * @param sentences 分句
     * @param topnWords keywords top-n关键词
     */
    private Map<Integer, Double> scoreSentences(List<String> sentences, List<String> topnWords) {
        Map<Integer, Double> scoresMap = new LinkedHashMap<Integer, Double>();//句子编号,得分
        sentSegmentWords = new HashMap<>();
        int sentence_idx = -1;//句子编号
        for (String sentence : sentences) {
            sentence_idx += 1;
            List<String> words = this.IKSegment(sentence);//对每个句子分词
            sentSegmentWords.put(sentence_idx, words);
            List<Integer> word_idx = new ArrayList<Integer>();//每个关词键在本句子中的位置
            for (String word : topnWords) {
                if (words.contains(word)) {
                    word_idx.add(words.indexOf(word));
                }
            }
            if (word_idx.size() == 0) continue;
            Collections.sort(word_idx);
            //对于两个连续的单词,利用单词位置索引,通过距离阀值计算一个族
            List<List<Integer>> clusters = new ArrayList<List<Integer>>();//根据本句中的关键词的距离存放多个词族
            List<Integer> cluster = new ArrayList<Integer>();
            cluster.add(word_idx.get(0));
            int i = 1;
            while (i < word_idx.size()) {
                if ((word_idx.get(i) - word_idx.get(i - 1)) < this.CLUSTER_THRESHOLD) {
                    cluster.add(word_idx.get(i));
                } else {
                    clusters.add(cluster);
                    cluster = new ArrayList<>();
                    cluster.add(word_idx.get(i));
                }
                i += 1;
            }
            clusters.add(cluster);
            //对每个词族打分,选择最高得分作为本句的得分
            double max_cluster_score = 0.0;
            for (List<Integer> clu : clusters) {
                int keywordsLen = clu.size();//关键词个数
                int totalWordsLen = clu.get(keywordsLen - 1) - clu.get(0) + 1;//总的词数
                double score = 1.0 * keywordsLen * keywordsLen / totalWordsLen;
                if (score > max_cluster_score) max_cluster_score = score;
            }
            scoresMap.put(sentence_idx, max_cluster_score);
        }
        return scoresMap;
    }

    /**
     * 利用最大边缘相关自动文摘
     */
    public String SummaryMMRNTxt(String text) {
        //将文本拆分成句子列表
        List<String> sentencesList = this.SplitSentences(text);

        //利用IK分词组件将文本分词,返回分词列表
        List<String> words = this.IKSegment(text);

        //统计分词频率
        Map<String, Integer> wordsMap = new HashMap<>();
        for (String word : words) {
            Integer val = wordsMap.get(word);
            wordsMap.put(word, val == null ? 1 : val + 1);
        }

        //使用优先队列自动排序
        Queue<Entry<String, Integer>> wordsQueue = new PriorityQueue<>(wordsMap.size(), (o1, o2) -> o2.getValue() - o1.getValue());
        wordsQueue.addAll(wordsMap.entrySet());

        if (N > wordsMap.size()) {
            N = wordsQueue.size();
        }

        //取前N个频次最高的词存在wordsList
        List<String> wordsList = new ArrayList<>(N);//top-n关键词
        for (int i = 0; i < N; i++) {
            Optional.ofNullable(wordsQueue.poll()).ifPresent(entry -> {
                wordsList.add(entry.getKey());
            });
        }
        //利用频次关键字,给句子打分,并对打分后句子列表依据得分大小降序排序
        Map<Integer, Double> scoresLinkedMap = scoreSentences(sentencesList, wordsList);//返回的得分,从第一句开始,句子编号的自然顺序
        List<Entry<Integer, Double>> sortedSentList = new ArrayList<>(scoresLinkedMap.entrySet());
        //按得分从高到底排序好的句子,句子编号与得分
        sortedSentList.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

        if (sentencesList.size() == 2) {
            return sentencesList.get(0) + sentencesList.get(1);
        } else if (sentencesList.size() == 1) {
            return sentencesList.get(0);
        }
        Map<Integer, String> keySentence = new TreeMap<Integer, String>();
        dealWithCommentWithMMR(sentencesList, sortedSentList, keySentence);

        StringBuilder sb = new StringBuilder();
        for (int index : keySentence.keySet())
            sb.append(keySentence.get(index));
        return sb.toString();
    }

    /**
     * 使用MMR算法处理句子
     *
     * @param sentencesList  句子列表
     * @param sortedSentList 排好序的句子(句子编号及其得分)
     * @param keySentence    处理结果
     */
    private void dealWithCommentWithMMR(List<String> sentencesList, List<Entry<Integer, Double>> sortedSentList, Map<Integer, String> keySentence) {
        int count = 0;
        Map<Integer, Double> MMR_SentScore = MMR(sortedSentList);
        for (Entry<Integer, Double> entry : MMR_SentScore.entrySet()) {
            count++;
            int sentIndex = entry.getKey();
            String sentence = sentencesList.get(sentIndex);
            keySentence.put(sentIndex, sentence);
            if (count == this.TOP_SENTENCES) break;
        }
    }


    /**
     * 最大边缘相关(Maximal Marginal Relevance),根据λ调节准确性和多样性
     * max[λ*score(i) - (1-λ)*max[similarity(i,j)]]:score(i)句子的得分,similarity(i,j)句子i与j的相似度
     * User-tunable diversity through λ parameter
     * - High λ= Higher accuracy
     * - Low λ= Higher diversity
     *
     * @param sortedSentList 排好序的句子,编号及得分
     */
    private Map<Integer, Double> MMR(List<Entry<Integer, Double>> sortedSentList) {
        double[][] simSentArray = sentJSimilarity();//所有句子的相似度
        Map<Integer, Double> sortedLinkedSent = new LinkedHashMap<>();
        for (Entry<Integer, Double> entry : sortedSentList) {
            sortedLinkedSent.put(entry.getKey(), entry.getValue());
        }
        Map<Integer, Double> MMR_SentScore = new LinkedHashMap<>();//最终的得分(句子编号与得分)
        Entry<Integer, Double> Entry = sortedSentList.get(0);//第一步先将最高分的句子加入
        MMR_SentScore.put(Entry.getKey(), Entry.getValue());
        boolean flag = true;
        while (flag) {
            int index = 0;
            double maxScore = Double.NEGATIVE_INFINITY;//通过迭代计算获得最高分句子
            for (Map.Entry<Integer, Double> entry : sortedLinkedSent.entrySet()) {
                if (MMR_SentScore.containsKey(entry.getKey())) continue;
                double simSentence = 0.0;
                for (Map.Entry<Integer, Double> MMREntry : MMR_SentScore.entrySet()) {//这个是获得最相似的那个句子的最大相似值
                    double simSen = 0.0;
                    if (entry.getKey() > MMREntry.getKey()) simSen = simSentArray[MMREntry.getKey()][entry.getKey()];
                    else simSen = simSentArray[entry.getKey()][MMREntry.getKey()];
                    if (simSen > simSentence) {
                        simSentence = simSen;
                    }
                }
                simSentence = λ * entry.getValue() - (1 - λ) * simSentence;
                if (simSentence > maxScore) {
                    maxScore = simSentence;
                    index = entry.getKey();//句子编号
                }
            }
            MMR_SentScore.put(index, maxScore);
            if (MMR_SentScore.size() == sortedLinkedSent.size()) flag = false;
        }
        return MMR_SentScore;
    }

    /**
     * 每个句子的相似度,这里使用简单的jaccard方法,计算所有句子的两两相似度
     */
    private double[][] sentJSimilarity() {
        //System.out.println("sentJSimilarity in...");
        int size = sentSegmentWords.size();
        double[][] simSent = new double[size][size];
        for (Entry<Integer, List<String>> entry : sentSegmentWords.entrySet()) {
            for (Entry<Integer, List<String>> entry1 : sentSegmentWords.entrySet()) {
                if (entry.getKey() >= entry1.getKey()) continue;
                int commonWords = 0;
                double sim = 0.0;
                for (String entryStr : entry.getValue()) {
                    if (entry1.getValue().contains(entryStr)) commonWords++;
                }
                sim = 1.0 * commonWords / (entry.getValue().size() + entry1.getValue().size() - commonWords);
                simSent[entry.getKey()][entry1.getKey()] = sim;
            }
        }
        return simSent;
    }
}

3.2 文件读取工具

public class FileUtils {

    /**
     * 从网络地址读取文件
     *
     * @param path 网络地址
     * @return 读取后的文件
     */
    public File readFileFromNet(String path) throws IOException {
        File temporary = File.createTempFile("SensitiveWord", ".txt");
        URL url = new URL(path);
        InputStream inputStream = url.openStream();
        FileUtils.copyInputStreamToFile(inputStream, temporary);
        return temporary;
    }

    private static void copyInputStreamToFile(InputStream inputStream, File file) throws IOException {
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            int read;
            byte[] bytes = new byte[1024];

            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }
        }

    }

}

3.3 相关依赖

 <dependency>
     <groupId>com.github.magese</groupId>
     <artifactId>ik-analyzer</artifactId>
     <version>8.5.0</version>
</dependency>

3.4 调用算法,生成自动摘要

   public static void main(String[] args) {

        SummaryUtil summary = new SummaryUtil();

        //停顿词我们一般放在OSS服务器上,而不是放在系统文件里面,方便灵活更换
        String[] paths = {"www.xxx.com/xxxxx(停顿词资源路径1)","www.xxx.com/xxxxx(停顿词资源路径2)"};
        //初始化停顿词
        initStopWords(paths);

        String text = "我国古代历史演义小说的代表作。明代小说家罗贯中依据有关三国的历史、杂记,在广泛吸取民间传说和民间艺人创作成果的基础上,加工、再创作了这部长篇章回小说。" +
                "作品写的是汉末到晋初这一历史时期魏、蜀、吴三个封建统治集团间政治、军事、外交等各方面的复杂斗争。通过这些描写,揭露了社会的黑暗与腐朽,谴责了统治阶级的残暴与奸诈," +
                "反映了人民在动乱时代的苦难和明君仁政的愿望。小说也反映了作者对农民起义的偏见,以及因果报应和宿命论等思想。战争描写是《三国演义》突出的艺术成就。这部小说通过惊心动魄的军事、政治斗争,运用夸张、对比、烘托、渲染等艺术手法,成功地塑造了诸葛亮、曹操、关羽、张飞等一批鲜明、生动的人物形象。《三国演义》结构宏伟而又严密精巧,语言简洁、明快、生动。有的评论认为这部作品在艺术上的不足之处是人物性格缺乏发展变化,有的人物渲染夸张过分导致失真。《三国演义》标志着历史演义小说的辉煌成就。在传播政治、军事斗争经验、推动历史演义创作的繁荣等方面都起过积极作用。《三国演义》的版本主要有明嘉靖刻本《三国志通俗演义》和清毛宗岗增删评点的《三国志演义》";
        String mmrSentences = summary.SummaryMMRNTxt(text);
        System.out.println("MMR算法获取到的摘要: " + mmrSentences);
    }

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

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

相关文章

【高质量精品】2024美赛B题22页word版高质量半成品论文+多版保奖思路+数据+前四问思路代码等(后续会更新)

一定要点击文末的卡片&#xff0c;进入后&#xff0c;获取完整论文&#xff01;&#xff01; B 题整体模型构建 1. 潜水器动力系统失效&#xff1a;模型需要考虑潜水器在无推进力情况下的行为。 2. 失去与主船通信&#xff1a;考虑无法从主船接收指令或发送位置信息的情况。…

基于若依的ruoyi-nbcio流程管理系统自定义业务实现一种简单的动态任务标题需求

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/n…

深度学习介绍

对于具备完善业务逻辑的任务&#xff0c;大多数情况下&#xff0c;正常的人都可以给出一个符合业务逻辑的应用程序。但是对于一些包含超过人类所能考虑到的逻辑的任务&#xff0c;例如面对如下任务&#xff1a; 编写一个应用程序&#xff0c;接受地理信息、卫星图像和一些历史…

【51单片机】开发板&开发软件(Keil5&STC-ISP)简介&下载安装破译传送门(1)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

【目标跟踪】相机运动补偿

文章目录 一、前言二、简介三、改进思路3.1、状态定义3.2、相机运动补偿3.3、iou和ReID融合3.4、改进总结 四、相机运动补偿 一、前言 目前 MOT (Multiple Object Tracking) 最有效的方法仍然是 Tracking-by-detection。今天给大家分享一篇论文 BoT-SORT。论文地址 &#xff0…

wireshark分析数据包:追踪流

打开追踪流的界面 方法 1 方法 2 选中数据包&#xff0c;右键弹出菜单 说明&#xff1a; 流内容的显示顺序和它在网络上出现的顺序相同。不可打印的字符被点代替。从客户端到服务器的流量被标记为红色&#xff0c;而从服务器到客户端的流量被标记为蓝色。这些颜色可以通过下…

No matching client found for package name ‘com.unity3d.player‘

2024年2月5日更新 下面的一系列操作最终可能都无用&#xff0c;大致这问题出现原因是我在Unity采用了Android方式接入Firebase&#xff0c;而Android接入实际上和Unity接入方式有配置上的不一样&#xff0c;我就是多做了几步操作如下。https://firebase.google.com/docs/androi…

【计算机网络】Socket的SO_TIMEOUT与连接超时时间

SO_TIMEOUT选项是Socket的一个选项&#xff0c;用于设置读取数据的超时时间。它指定了在读取数据时等待的最长时间&#xff0c;如果在指定的时间内没有数据可读取&#xff0c;将抛出SocketTimeoutException异常。 SO_TIMEOUT的设置 默认情况下&#xff0c;SO_TIMEOUT选项的值…

用Jmeter进行接口测试

web接口测试工具&#xff1a; 手工测试的话可以用postman &#xff0c;自动化测试多是用到 Jmeter&#xff08;开源&#xff09;、soupUI&#xff08;开源&商业版&#xff09;。 下面将对前一篇Postman做接口测试中的接口用Jmeter来实现。 一、Jmeter 的使用步骤 打开Jme…

【vscode】windows11在vscode终端控制台中打印console.log()出现中文乱码问题解决

1. 问题描述 在前端开发过程中使用vscode编写node.js&#xff0c;需要在控制台中打印一些中文信息&#xff0c;但是一直出现中文乱码问题&#xff0c;英文和数字都显示正常。在网上试了很多设置的办法&#xff0c;最终找到windos11设置中解决。 2. 原因 首先打开控制台&…

通过html2canvas和jsPDF将网页内容导出成pdf

jsPDF参考&#xff1a;https://github.com/parallax/jsPDF html2canvas参考&#xff1a;https://github.com/niklasvh/html2canvas 或者 https://html2canvas.hertzen.com 思路 使用html2canvas将选中DOM生成截图对象将截图对象借助jsPDF导出为PDF文件 代码 这是一个示例&a…

《幻兽帕鲁》解锁基地和工作帕鲁数量上限

帕鲁私服的游戏参数通常可通过配置文件 PalWorldSettings.ini 来进行修改&#xff0c;然而这个配置文件有个别参数对游戏不生效&#xff0c;让人很是头疼。没错&#xff01;我说的就是终端最大的帕鲁数量&#xff01; 其实还有另外一种更加高级的参数修改方式&#xff0c;那就…

C#实现坐标系转换

已知坐标系的向量线段AB&#xff0c;旋转指定角度后平移到达坐标AB 获取旋转角度以及新的其他坐标转换。 新建窗体应用程序CoordinateTransDemo&#xff0c;将默认的Form1重命名为FormCoordinateTrans&#xff0c;窗体设计如图&#xff1a; 窗体设计代码如下&#xff1a; 部分…

在windows下安装docker部署环境运行项目----docker-compose.yml

前言 小编我将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识&#xff0c;有兴趣的小伙伴可以关注一下&#xff01; 也许一个人独行&#xff0c;可以走的很快&#xff0c;但是一群人结伴而行&#xff0c;才能走的更远&#xff01;让我们在成长的道路上互相学习&…

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(六)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十四章&#xff1a;使用卷积神经网络进行深度计算机视觉 尽管 IBM 的 Deep Blue 超级计算机在 1996 年击败了国际象棋世界冠军…

力扣 第 383 场周赛 解题报告 | 珂学家 | Z函数/StringHash

前言 谁言别后终无悔 寒月清宵绮梦回 深知身在情长在 前尘不共彩云飞 整体评价 T3是道模拟题&#xff0c;但是感觉题意有些晦涩&#xff0c;T4一眼Z函数&#xff0c;当然StringHash更通用些。 新年快乐, _. T1. 将单词恢复初始状态所需的最短时间 I 思路: 模拟 就是前缀和为…

AI助力农作物自动采摘,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建作物生产场景下番茄采摘检测计数分析系统

去年十一那会无意间刷到一个视频展示的就是德国机械收割机非常高效自动化地24小时不间断地在超广阔的土地上采摘各种作物&#xff0c;专家设计出来了很多用于采摘不同农作物的大型机械&#xff0c;看着非常震撼&#xff0c;但是我们国内农业的发展还是相对比较滞后的&#xff0…

BUUCTF-Real-[PHPMYADMIN]CVE-2018-12613

目录 漏洞背景介绍 漏洞产生 漏洞利用 漏洞验证 漏洞背景介绍 phpMyAdmin 是一个以PHP为基础&#xff0c;以Web-Base方式架构在网站主机上的MySQL的数据库管理工具&#xff0c;让管理者可用Web接口管理MySQL数据库。借由此Web接口可以成为一个简易方式输入繁杂SQL语法的较佳…

GPT如何在一分钟内完成论文数据分析?

数据上传 PPMAN-AI 01 由于技术限制&#xff0c;目前InfinitePaper AI仅支持上传1份文件&#xff0c;且大小不超过10M。但是&#xff0c;在强大的代码解释器面前&#xff0c;这都是小问题。我们只需要将可能用到的文件打包成压缩文件上传即可&#xff0c;之后要求GPT直接解压…

计算机设计大赛 深度学习 机器视觉 车位识别车道线检测 - python opencv

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 车位识别车道线检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) …