最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java算法之余弦相似度計算字符串相似率

 更新時間:2021年05月11日 11:19:03   作者:雨點的名字  
這篇文章主要介紹了java算法之余弦相似度計算字符串相似率,對算法感興趣的同學,可以參考下

概述

功能需求:最近在做通過爬蟲技術(shù)去爬取各大相關(guān)網(wǎng)站的新聞,儲存到公司數(shù)據(jù)中。這里面就有一個技術(shù)點,就是如何保證你已爬取的新聞,再有相似的新聞

或者一樣的新聞,那就不存儲到數(shù)據(jù)庫中。(因為有網(wǎng)站會去引用其它網(wǎng)站新聞,或者把其它網(wǎng)站新聞拿過來稍微改下內(nèi)容就發(fā)布到自己網(wǎng)站中)。

解析方案:最終就是采用余弦相似度算法,來計算兩個新聞?wù)牡南嗨贫取,F(xiàn)在自己寫一篇博客總結(jié)下。

一、理論知識

先推薦一篇博客,對于余弦相似度算法的理論講的比較清晰,我們也是按照這個方式來計算相似度的。網(wǎng)址:相似度算法之余弦相似度。

1、說重點

我這邊先把計算兩個字符串的相似度理論知識再梳理一遍。

(1)首先是要明白通過向量來計算相識度公式。

(2)明白:余弦值越接近1,也就是兩個向量越相似,這就叫"余弦相似性",
余弦值越接近0,也就是兩個向量越不相似,也就是這兩個字符串越不相似。

2、案例理論知識

舉一個例子來說明,用上述理論計算文本的相似性。為了簡單起見,先從句子著手。

句子A:這只皮靴號碼大了。那只號碼合適。

句子B:這只皮靴號碼不小,那只更合適。

怎樣計算上面兩句話的相似程度?

基本思路是:如果這兩句話的用詞越相似,它們的內(nèi)容就應(yīng)該越相似。因此,可以從詞頻入手,計算它們的相似程度。

第一步,分詞。

句子A:這只/皮靴/號碼/大了。那只/號碼/合適。

句子B:這只/皮靴/號碼/不/小,那只/更/合適。

第二步,計算詞頻。(也就是每個詞語出現(xiàn)的頻率)

句子A:這只1,皮靴1,號碼2,大了1。那只1,合適1,不0,小0,更0

句子B:這只1,皮靴1,號碼1,大了0。那只1,合適1,不1,小1,更1

第三步,寫出詞頻向量。

句子A:(1,1,2,1,1,1,0,0,0)

句子B:(1,1,1,0,1,1,1,1,1)

第四步:運用上面的公式:計算如下:

計算結(jié)果中夾角的余弦值為0.81非常接近于1,所以,上面的句子A和句子B是基本相似的

二、實際開發(fā)案例

我把我們實際開發(fā)過程中字符串相似率計算代碼分享出來。

1、pom.xml

展示一些主要jar包

<!--結(jié)合操作工具包-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.5</version>
</dependency>
<!--bean實體注解工具包-->
   <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--漢語言包,主要用于分詞-->
<dependency>
    <groupId>com.hankcs</groupId>
    <artifactId>hanlp</artifactId>
    <version>portable-1.6.5</version>
</dependency>

2、main方法

/**
 * 計算兩個字符串的相識度
 */
public class Similarity {

    public static final  String content1="今天小小和爸爸一起去摘草莓,小小說今天的草莓特別的酸,而且特別的小,關(guān)鍵價格還貴";

    public static final  String content2="今天小小和媽媽一起去草原里采草莓,今天的草莓味道特別好,而且價格還挺實惠的";


    public static void main(String[] args) {

        double  score=CosineSimilarity.getSimilarity(content1,content2);
        System.out.println("相似度:"+score);

        score=CosineSimilarity.getSimilarity(content1,content1);
        System.out.println("相似度:"+score);
    }
    
}

先看運行結(jié)果:

通過運行結(jié)果得出:

(1)第一次比較相似率為:0.772853 (說明這兩條句子還是挺相似的),第二次比較相似率為:1.0 (說明一模一樣)。

(2)我們可以看到這個句子的分詞效果,后面是詞性。

3、Tokenizer(分詞工具類)

import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.List;
import java.util.stream.Collectors;


/**
 * 中文分詞工具類*/
public class Tokenizer {

    /**
     * 分詞*/
    public static List<Word> segment(String sentence) {

        //1、 采用HanLP中文自然語言處理中標準分詞進行分詞
        List<Term> termList = HanLP.segment(sentence);

        //上面控制臺打印信息就是這里輸出的
        System.out.println(termList.toString());

        //2、重新封裝到Word對象中(term.word代表分詞后的詞語,term.nature代表改詞的詞性)
        return termList.stream().map(term -> new Word(term.word, term.nature.toString())).collect(Collectors.toList());
    }
}

4、Word(封裝分詞結(jié)果)

這里面真正用到的其實就詞名和權(quán)重。

import lombok.Data;

import java.util.Objects;

/**
 * 封裝分詞結(jié)果*/
@Data
public class Word implements Comparable {

    // 詞名
    private String name;
    // 詞性
    private String pos;

    // 權(quán)重,用于詞向量分析
    private Float weight;

    public Word(String name, String pos) {
        this.name = name;
        this.pos = pos;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(this.name);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Word other = (Word) obj;
        return Objects.equals(this.name, other.name);
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        if (name != null) {
            str.append(name);
        }
        if (pos != null) {
            str.append("/").append(pos);
        }

        return str.toString();
    }

    @Override
    public int compareTo(Object o) {
        if (this == o) {
            return 0;
        }
        if (this.name == null) {
            return -1;
        }
        if (o == null) {
            return 1;
        }
        if (!(o instanceof Word)) {
            return 1;
        }
        String t = ((Word) o).getName();
        if (t == null) {
            return 1;
        }
        return this.name.compareTo(t);
    }
}

5、CosineSimilarity(相似率具體實現(xiàn)工具類)

import com.jincou.algorithm.tokenizer.Tokenizer;
import com.jincou.algorithm.tokenizer.Word;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 判定方式:余弦相似度,通過計算兩個向量的夾角余弦值來評估他們的相似度 余弦夾角原理: 向量a=(x1,y1),向量b=(x2,y2) similarity=a.b/|a|*|b| a.b=x1x2+y1y2
 * |a|=根號[(x1)^2+(y1)^2],|b|=根號[(x2)^2+(y2)^2]*/
public class CosineSimilarity {
    protected static final Logger LOGGER = LoggerFactory.getLogger(CosineSimilarity.class);

    /**
     * 1、計算兩個字符串的相似度
     */
    public static double getSimilarity(String text1, String text2) {

        //如果wei空,或者字符長度為0,則代表完全相同
        if (StringUtils.isBlank(text1) && StringUtils.isBlank(text2)) {
            return 1.0;
        }
        //如果一個為0或者空,一個不為,那說明完全不相似
        if (StringUtils.isBlank(text1) || StringUtils.isBlank(text2)) {
            return 0.0;
        }
        //這個代表如果兩個字符串相等那當然返回1了(這個我為了讓它也分詞計算一下,所以注釋掉了)
//        if (text1.equalsIgnoreCase(text2)) {
//            return 1.0;
//        }
        //第一步:進行分詞
        List<Word> words1 = Tokenizer.segment(text1);
        List<Word> words2 = Tokenizer.segment(text2);

        return getSimilarity(words1, words2);
    }

    /**
     * 2、對于計算出的相似度保留小數(shù)點后六位
     */
    public static double getSimilarity(List<Word> words1, List<Word> words2) {

        double score = getSimilarityImpl(words1, words2);

        //(int) (score * 1000000 + 0.5)其實代表保留小數(shù)點后六位 ,因為1034234.213強制轉(zhuǎn)換不就是1034234。對于強制轉(zhuǎn)換添加0.5就等于四舍五入
        score = (int) (score * 1000000 + 0.5) / (double) 1000000;

        return score;
    }

    /**
     * 文本相似度計算 判定方式:余弦相似度,通過計算兩個向量的夾角余弦值來評估他們的相似度 余弦夾角原理: 向量a=(x1,y1),向量b=(x2,y2) similarity=a.b/|a|*|b| a.b=x1x2+y1y2
     * |a|=根號[(x1)^2+(y1)^2],|b|=根號[(x2)^2+(y2)^2]
     */
    public static double getSimilarityImpl(List<Word> words1, List<Word> words2) {

        // 向每一個Word對象的屬性都注入weight(權(quán)重)屬性值
        taggingWeightByFrequency(words1, words2);

        //第二步:計算詞頻
        //通過上一步讓每個Word對象都有權(quán)重值,那么在封裝到map中(key是詞,value是該詞出現(xiàn)的次數(shù)(即權(quán)重))
        Map<String, Float> weightMap1 = getFastSearchMap(words1);
        Map<String, Float> weightMap2 = getFastSearchMap(words2);

        //將所有詞都裝入set容器中
        Set<Word> words = new HashSet<>();
        words.addAll(words1);
        words.addAll(words2);

        AtomicFloat ab = new AtomicFloat();// a.b
        AtomicFloat aa = new AtomicFloat();// |a|的平方
        AtomicFloat bb = new AtomicFloat();// |b|的平方

        // 第三步:寫出詞頻向量,后進行計算
        words.parallelStream().forEach(word -> {
            //看同一詞在a、b兩個集合出現(xiàn)的此次
            Float x1 = weightMap1.get(word.getName());
            Float x2 = weightMap2.get(word.getName());
            if (x1 != null && x2 != null) {
                //x1x2
                float oneOfTheDimension = x1 * x2;
                //+
                ab.addAndGet(oneOfTheDimension);
            }
            if (x1 != null) {
                //(x1)^2
                float oneOfTheDimension = x1 * x1;
                //+
                aa.addAndGet(oneOfTheDimension);
            }
            if (x2 != null) {
                //(x2)^2
                float oneOfTheDimension = x2 * x2;
                //+
                bb.addAndGet(oneOfTheDimension);
            }
        });
        //|a| 對aa開方
        double aaa = Math.sqrt(aa.doubleValue());
        //|b| 對bb開方
        double bbb = Math.sqrt(bb.doubleValue());

        //使用BigDecimal保證精確計算浮點數(shù)
        //double aabb = aaa * bbb;
        BigDecimal aabb = BigDecimal.valueOf(aaa).multiply(BigDecimal.valueOf(bbb));

        //similarity=a.b/|a|*|b|
        //divide參數(shù)說明:aabb被除數(shù),9表示小數(shù)點后保留9位,最后一個表示用標準的四舍五入法
        double cos = BigDecimal.valueOf(ab.get()).divide(aabb, 9, BigDecimal.ROUND_HALF_UP).doubleValue();
        return cos;
    }


    /**
     * 向每一個Word對象的屬性都注入weight(權(quán)重)屬性值
     */
    protected static void taggingWeightByFrequency(List<Word> words1, List<Word> words2) {
        if (words1.get(0).getWeight() != null && words2.get(0).getWeight() != null) {
            return;
        }
        //詞頻統(tǒng)計(key是詞,value是該詞在這段句子中出現(xiàn)的次數(shù))
        Map<String, AtomicInteger> frequency1 = getFrequency(words1);
        Map<String, AtomicInteger> frequency2 = getFrequency(words2);

        //如果是DEBUG模式輸出詞頻統(tǒng)計信息
//        if (LOGGER.isDebugEnabled()) {
//            LOGGER.debug("詞頻統(tǒng)計1:\n{}", getWordsFrequencyString(frequency1));
//            LOGGER.debug("詞頻統(tǒng)計2:\n{}", getWordsFrequencyString(frequency2));
//        }
        // 標注權(quán)重(該詞出現(xiàn)的次數(shù))
        words1.parallelStream().forEach(word -> word.setWeight(frequency1.get(word.getName()).floatValue()));
        words2.parallelStream().forEach(word -> word.setWeight(frequency2.get(word.getName()).floatValue()));
    }

    /**
     * 統(tǒng)計詞頻
     * @return 詞頻統(tǒng)計圖
     */
    private static Map<String, AtomicInteger> getFrequency(List<Word> words) {

        Map<String, AtomicInteger> freq = new HashMap<>();
        //這步很帥哦
        words.forEach(i -> freq.computeIfAbsent(i.getName(), k -> new AtomicInteger()).incrementAndGet());
        return freq;
    }

    /**
     * 輸出:詞頻統(tǒng)計信息
     */
    private static String getWordsFrequencyString(Map<String, AtomicInteger> frequency) {
        StringBuilder str = new StringBuilder();
        if (frequency != null && !frequency.isEmpty()) {
            AtomicInteger integer = new AtomicInteger();
            frequency.entrySet().stream().sorted((a, b) -> b.getValue().get() - a.getValue().get()).forEach(
                    i -> str.append("\t").append(integer.incrementAndGet()).append("、").append(i.getKey()).append("=")
                            .append(i.getValue()).append("\n"));
        }
        str.setLength(str.length() - 1);
        return str.toString();
    }

    /**
     * 構(gòu)造權(quán)重快速搜索容器
     */
    protected static Map<String, Float> getFastSearchMap(List<Word> words) {
        if (CollectionUtils.isEmpty(words)) {
            return Collections.emptyMap();
        }
        Map<String, Float> weightMap = new ConcurrentHashMap<>(words.size());

        words.parallelStream().forEach(i -> {
            if (i.getWeight() != null) {
                weightMap.put(i.getName(), i.getWeight());
            } else {
                LOGGER.error("no word weight info:" + i.getName());
            }
        });
        return weightMap;
    }

}

這個具體實現(xiàn)代碼因為思維很緊密所以有些地方寫的比較繞,同時還手寫了AtomicFloat原子類。

6、AtomicFloat原子類

import java.util.concurrent.atomic.AtomicInteger;

/**
 * jdk沒有AtomicFloat,寫一個
 */
public class AtomicFloat extends Number {

    private AtomicInteger bits;

    public AtomicFloat() {
        this(0f);
    }

    public AtomicFloat(float initialValue) {
        bits = new AtomicInteger(Float.floatToIntBits(initialValue));
    }

    //疊加
    public final float addAndGet(float delta) {
        float expect;
        float update;
        do {
            expect = get();
            update = expect + delta;
        } while (!this.compareAndSet(expect, update));

        return update;
    }

    public final float getAndAdd(float delta) {
        float expect;
        float update;
        do {
            expect = get();
            update = expect + delta;
        } while (!this.compareAndSet(expect, update));

        return expect;
    }

    public final float getAndDecrement() {
        return getAndAdd(-1);
    }

    public final float decrementAndGet() {
        return addAndGet(-1);
    }

    public final float getAndIncrement() {
        return getAndAdd(1);
    }

    public final float incrementAndGet() {
        return addAndGet(1);
    }

    public final float getAndSet(float newValue) {
        float expect;
        do {
            expect = get();
        } while (!this.compareAndSet(expect, newValue));

        return expect;
    }

    public final boolean compareAndSet(float expect, float update) {
        return bits.compareAndSet(Float.floatToIntBits(expect), Float.floatToIntBits(update));
    }

    public final void set(float newValue) {
        bits.set(Float.floatToIntBits(newValue));
    }

    public final float get() {
        return Float.intBitsToFloat(bits.get());
    }

    @Override
    public float floatValue() {
        return get();
    }

    @Override
    public double doubleValue() {
        return (double) floatValue();
    }

    @Override
    public int intValue() {
        return (int) get();
    }

    @Override
    public long longValue() {
        return (long) get();
    }

    @Override
    public String toString() {
        return Float.toString(get());
    }
}

三、總結(jié)

把大致思路再捋一下:

(1)先分詞:分詞當然要按一定規(guī)則,不然隨便分那也沒有意義,那這里通過采用HanLP中文自然語言處理中標準分詞進行分詞。

(2)統(tǒng)計詞頻:就統(tǒng)計上面詞出現(xiàn)的次數(shù)。

(3)通過每一個詞出現(xiàn)的次數(shù),變成一個向量,通過向量公式計算相似率。

以上就是java算法之余弦相似度計算字符串相似率的詳細內(nèi)容,更多關(guān)于java算法的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java List集合排序?qū)崿F(xiàn)方法解析

    Java List集合排序?qū)崿F(xiàn)方法解析

    這篇文章主要介紹了Java List集合排序?qū)崿F(xiàn)方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • Java如何利用線程池和Redis實現(xiàn)高效數(shù)據(jù)入庫

    Java如何利用線程池和Redis實現(xiàn)高效數(shù)據(jù)入庫

    文章介紹了如何利用線程池和Redis在高并發(fā)環(huán)境中實現(xiàn)高效的數(shù)據(jù)入庫,通過將數(shù)據(jù)首先存儲在Redis緩存中,然后利用線程池定期批量入庫處理,確保系統(tǒng)的性能和穩(wěn)定性,主要組件包括BatchDataStorageService、CacheService和RedisUtils等
    2025-02-02
  • Java中的while循環(huán)語句詳細講解

    Java中的while循環(huán)語句詳細講解

    這篇文章主要給大家介紹了關(guān)于Java中while循環(huán)語句的相關(guān)資料,while循環(huán)是一種在編程中常見的控制流語句,它允許代碼在特定條件下(通常是一個布爾表達式)重復(fù)執(zhí)行一段代碼,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-03-03
  • Spring-boot結(jié)合Shrio實現(xiàn)JWT的方法

    Spring-boot結(jié)合Shrio實現(xiàn)JWT的方法

    這篇文章主要介紹了Spring-boot結(jié)合Shrio實現(xiàn)JWT的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • Java設(shè)計模式之橋接模式的示例詳解

    Java設(shè)計模式之橋接模式的示例詳解

    橋梁模式是對象的結(jié)構(gòu)模式。又稱為柄體(Handle and Body)模式或接口(Interface)模式。本文將通過示例來詳細講解一下這個模式,感興趣的可以學習一下
    2022-02-02
  • java讀取文件顯示進度條的實現(xiàn)方法

    java讀取文件顯示進度條的實現(xiàn)方法

    當讀取一個大文件時,一時半會兒無法看到讀取結(jié)果,就需要顯示一個進度條,是程序員明白已經(jīng)讀了多少文件,可以估算讀取還需要多少時間,下面的代碼可以實現(xiàn)這個功能
    2014-01-01
  • Idea工具中使用Mapper對象有紅線的解決方法

    Idea工具中使用Mapper對象有紅線的解決方法

    mapper對象在service層有紅線,項目可以正常使用,想知道為什么會出現(xiàn)這種情,接下來通過本文給大家介紹下Idea工具中使用Mapper對象有紅線的問題,需要的朋友可以參考下
    2022-09-09
  • 一文秒懂springboot druid 配置

    一文秒懂springboot druid 配置

    Druid是阿里巴巴開發(fā)的一個連接池,他提供了一個高效、功能強大、可擴展性好的數(shù)據(jù)庫連接池,區(qū)別于hikari,今天通過本文給大家分享springboot druid 配置教程,需要的朋友參考下吧
    2021-08-08
  • Spring MVC利用Swagger2如何構(gòu)建動態(tài)RESTful API詳解

    Spring MVC利用Swagger2如何構(gòu)建動態(tài)RESTful API詳解

    這篇文章主要給大家介紹了關(guān)于在Spring MVC中利用Swagger2如何構(gòu)建動態(tài)RESTful API的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧。
    2017-10-10
  • java selenium 操作彈出窗口示例代碼

    java selenium 操作彈出窗口示例代碼

    本文主要介紹java selenium 操作彈出窗口,這里實例講解操作彈出窗口詳細流程,并附示例代碼,有需要的小伙伴可以參考下
    2016-08-08

最新評論

商洛市| 乾安县| 元谋县| 南雄市| 延寿县| 蕲春县| 阆中市| 长顺县| 郯城县| 宜丰县| 隆林| 康平县| 宜宾县| 兴国县| 新野县| 许昌市| 磐安县| 宜君县| 望城县| 阳信县| 明光市| 太仆寺旗| 五寨县| 新源县| 陇南市| 琼结县| 南安市| 鄂托克旗| 莲花县| 浮山县| 平顶山市| 康马县| 和政县| 鄂尔多斯市| 厦门市| 吉首市| 永昌县| 蓬安县| 石柱| 调兵山市| 额尔古纳市|