术语频率介绍

软件工程 2025-08-21

TF-IDF是信息检索中最基本的概念之一。它是2个统计数据的乘积,即术语频率(TF),它是数字值,表示单词在文档中出现多少次。逆文档频率(IDF)说明了一个单词在检查的文档中提供的信息,即(即常见或罕见)。尽管它很简单,但它仍然是最受欢迎的术语加权方案之一。 TD-IDF的差异在搜索引擎中广泛使用,作为评估用户查询和网页之间的相关性的排名功能。

期限频率

让[Math] t [/MATH]是我们要在文档[Math] d [/Math]中找到频率的术语,[MATH] TF(T,D,D)[/MATH]函数的输出只是文档中术语出现在文档总数中的总数的总数:

{tf}(t,d) = frac{f_{t,d}}{{sum_{t' in d}{f_{t',d}}}}

例如,我们拥有以下内容的文档[数学] D [/MATH]:“我正在学习信息检索,并且您也正在学习信息检索, 和[MATH] T [/MATH]一词我们希望找到其频率是检索的,然后[MATH] TF(t,t,d,d,d)= 2/13 = 2/13 = 0.1538 [/MATH]。

请记住,还有其他多种计算术语频率的方法,例如术语本身的计数频率,布尔频率(是否在文档中,是否在文档中)等等。

一袋单词

在计算文档术语频率时,我们通常可以使用单词袋进行数据预处理。在此阶段,一个单词袋描述了文档中出现的每个单词的频率,无视语法细节和单词顺序。在上一个会话中给出的文档,我们可以使用一个简单的单词袋表示形式,如下所示:

{ "as": 1, "are": 1, "and": 1, "retrieval": 2, "I": 1, "well" : 1, "learning": 2, "information": 2, "am": 1, "you": 1 }


 { " as " : 1 , " are " : 1 , " and " : 1 , " retrieval " : 2 , " I " : 1 , " well " : 1 , " learning " : 2 , " information " : 2 , " am " : 1 , " you " : 1}

TF问题

但是,假设我们要确定查询是否与给定文档有关;简单地计算术语频率可能不会那么有效;例如,我们有两个文件:

文档1 [数学] D_1 [/MATH]文档2 [数学] D_2 [/MATH]
懒惰的狐狸跳过快速的棕色狗学习是知识或通过学习或经验获得的信息

查询[数学] Q [/MATH]是“学习过程”,我们通过在[Math] Q [/Math]中总结[Math] D_1 [/MATH]和[MATH] D_2 [/MATH]文档中的每个术语的频率来计算相关性分数。 [MATH]( text {score}(q,d)= sum_ {w in q} tf_ {w,d})[/MATH]。

  • [数学]得分(q,d_1)= 2/9 + 0/9 + 0/9 = 2/9 oft 0.22 [/MATH]

  • [MATH]得分(Q,D_2)= 0/13 + 1/13 + 0/13 = 1/13 大约0.077 [/MATH]

文档[MATH] D_1 [/MATH]的分数较高,而显然我们知道文档[MATH] D_2 [/MATH]更相关。

逆文档频率

如我们所见,如果仅使用术语频率函数,我们就可以遇到一种不重要的术语比重要的情况更重要的情况。在上一节的示例中,“ the”一词是停止单词之一,它是微不足道的,应在处理前过滤掉。

反向文档频率函数衡量一个术语在文档中提供的信息,即文档很少出现或在文档之间是常见的信息。在大多数文档中可能存在停止或常用单词,而不会增加太多价值。同时,一个罕见的术语仅出现在少数文档中,确实为我们提供了更多相关的信息。逆文档频率将为常见的术语分配较低的分数,并为更重要的分数提供更高的分数。 IDF功能显示如下:

mathrm{idf}(t, D) =  log frac{N}{|{d in D: t in d}|}
  • 其中[数学] N [/MATH]是文档的数量

  • 分母是出现[Math] t [/Math]一词的文档数量。如果该术语未出现在任何文档中,则可能导致按零分割;因此,通常将1添加到分母,即[Math] 1 + {| {d in D:t in D} |} [/MATH]

我们可以看到,逆文档频率函数获得了其名称,因为我们不必将术语出现的文档数量除以文档总数,而是将其倒置。然后我们在末尾使用对数(在这里,我们使用基础[Math] E [/Math])来计算频率分数。

现在,让我们计算这两个文档的[数学] IDF [/MATH] [MATH] D [/MATH]:

文档1 [数学] D_1 [/MATH]文档2 [数学] D_2 [/MATH]
学习一些东西的最好方法是教它我一直在寻找改善我的写作的东西
  • [MATH] IDF(“某物”,D)= log frac {2} {2} = 0 [/MATH]

  • [MATH] IDF(“学习”,D)= log frac {2} {1} = 0.69314 [/MATH]

频率倒数文档频率

可以合并两个重量方案TF和IDF,以计算相关分数如下:

{tfidf}(t,d,D) = mathrm{tf}(t,d) cdot mathrm{idf}(t, D)

如果[MATH] TF(t,d)[/MATH]和[MATH] IDF(T,D)[/MATH]提供的两个值都很高,则此功能给出的高加权分数很高,即,当术语在给定文档中的频率很高,并且现有文档总数中的频率很低。如果[Math] t [/MATH]术语t [/MATH]在每个文档中出现(因为[MATH] IDF(T,D,D)= 0 [/MATH]),则[MATH] TDIDF(t,d,d)[/MATH] [/MATH]将为我们提供0分数,因此我们可以使用[MATH] TFIDF [/MATH],我们可以在文档之间过滤常见的术语。

一个代码示例

现在,我们准备创建一个简单的程序,该程序使用[Math] TDIDF [/Math]函数来计算文档与用户查询的相关性。我们首先创建一个对文档进行建模的类:

record Document(int docId, String doc) {}
 record Document ( int docId , String doc ) {}

然后,我们创建一个计算术语频率分数的函数:

public static double getTermFrequencyScore(String term, Document document) { String[] words = document.doc().split("\s+"); int size = words.length; int freq = 0; for(final String word : words) { if (word.equals(term)) { freq++; } } return freq / (double) size; }
 public static double getTermFrequencyScore ( String term , Document document ) {    String [] words = document . doc (). split ( " \ s+ " ) ;    int size = words . length ;    int freq = 0 ;    for ( final String word : words ) {        if ( word . equals ( term )) {           freq ++;        }    }    return freq / ( double ) size ;}

接下来,我们制作另一个函数来计算逆文档频率分数;输入将是文档列表的术语:

public static double getInverseDocumentFrequencyScore(String term, List< Document>documents) { int size = documents.size(); int freq = 0; for(final Document document : documents) { String[] words = document.doc().split("\s+"); for(final String word : words) { if (word.equals(term)) { freq++; break; } } } return Math.log(size / (double) (1 + freq)); }
 public static double getInverseDocumentFrequencyScore ( String term , List <  Document  > documents ) {     int size = documents . size () ;     int freq = 0 ;     for ( final Document document : documents ) {         String [] words = document . doc (). split ( " \ s+ " ) ;         for ( final String word : words ) {             if ( word . equals ( term )) {                 freq ++;                 break;             }         }     }     return Math . log ( size / ( double ) ( 1 + freq )) ;}

最后,我们可以拥有函数getTfidfscore,这是从前两个函数中返回值的产物:

public static double getTfIdfScore(String term, List< Document>allDocs, Document doc) { double termFrequencyScore = getTermFrequencyScore(term, doc); double inverseDocumentFrequencyScore = getInverseDocumentFrequencyScore(term, allDocs); return termFrequencyScore * inverseDocumentFrequencyScore; }
 public static double getTfIdfScore ( String term , List <  Document  > allDocs , Document doc ) {     double termFrequencyScore = getTermFrequencyScore ( term , doc ) ;     double inverseDocumentFrequencyScore = getInverseDocumentFrequencyScore ( term , allDocs ) ;     return termFrequencyScore * inverseDocumentFrequencyScore ;}

我们已经准备好利用我们的方法,并将其放入实用程序类RelevancyScoreUtils中;接下来,我们创建另一个名为documentRelevancyCalculator的类,该函数将用户查询作为输入并返回有关文档的列表:

record DocumentTermScore(String term, Document doc, double relScore) { } class DocumentRelevancyCalculator { private final List< Document>documents; public DocumentRelevancyCalculator(List< Document>documents) { this.documents = documents; } public List< DocumentTermScore>getRelevantDocuments(String query, int k) { List< DocumentTermScore>relatedDocs = new ArrayList< >(); for(final Document doc : documents) { String[] terms = query.split("\s+"); double tfidfScore = 0; for(final String term : terms) { tfidfScore += RelevancyScoreUtils.getTfIdfScore(term, documents, doc); } if (tfidfScore >0) { DocumentTermScore dtc = new DocumentTermScore(query, doc, tfidfScore); relatedDocs.add(dtc); } } Comparators.comparingDouble(DocumentTermScore::relScore).reversed() return relatedDocs.subList(0, k); } }
 record DocumentTermScore ( String term , Document doc , double relScore ) { }class DocumentRelevancyCalculator {   private final List <  Document  > documents ;   public DocumentRelevancyCalculator ( List <  Document  > documents ) {       this . documents = documents ;   }   public List <  DocumentTermScore  > getRelevantDocuments ( String query , int k ) {       List <  DocumentTermScore  > relatedDocs = new ArrayList <  >() ;       for ( final Document doc : documents ) {           String [] terms = query . split ( " \ s+ " ) ;           double tfidfScore = 0 ;           for ( final String term : terms ) {               tfidfScore += RelevancyScoreUtils . getTfIdfScore ( term , documents , doc ) ;           }           if ( tfidfScore  > 0 ) {               DocumentTermScore dtc = new DocumentTermScore ( query , doc , tfidfScore ) ;               relatedDocs . add ( dtc ) ;           }       }       Comparators . comparingDouble ( DocumentTermScore :: relScore ). reversed ()       return relatedDocs . subList ( 0 , k ) ;   }}

最后,我们可以创建一个测试类,以利用我们刚刚创建的功能:

class DocumentRelevancyCalculatorTest { static DocumentRelevancyCalculator documentRelevancyCalculator; @BeforeEach public void init() { documentRelevancyCalculator = new DocumentRelevancyCalculator(List.of( new Document(1, "the chains of habit are too weak to be felt until they are too strong to be broken."), new Document(2, "think before you speak. read before you think."), new Document(3, "what do you think about our improvement plan?"), new Document(4, "if you can do something in under 2 minutes, then just doing it right away might save you a lot of time."), new Document(5, "15 minutes of direct sunlight in the morning will save you hours of insomnia in the evening.")) ); } @Test void testGetRelevantDocuments() { List< DocumentTermScore>documents = documentRelevancyCalculator.getRelevantDocuments("think", 2); assertEquals(2, documents.get(0).doc().docId()); assertEquals(3, documents.get(1).doc().docId()); } }
 class DocumentRelevancyCalculatorTest {   static DocumentRelevancyCalculator documentRelevancyCalculator ;   @ BeforeEach   public void init () {       documentRelevancyCalculator = new DocumentRelevancyCalculator ( List . of (               new Document ( 1 , " the chains of habit are too weak to be felt until they are too strong to be broken. " ),               new Document ( 2 , " think before you speak. read before you think. " ),               new Document ( 3 , " what do you think about our improvement plan? " ),               new Document ( 4 , " if you can do something in under 2 minutes, then just doing it right away might save you a lot of time. " ),               new Document ( 5 , " 15 minutes of direct sunlight in the morning will save you hours of insomnia in the evening. " ))       ) ;   }   @ Test   void testGetRelevantDocuments () {       List <  DocumentTermScore  > documents = documentRelevancyCalculator . getRelevantDocuments ( " think " , 2 ) ;       assertEquals ( 2 , documents . get ( 0 ). doc (). docId ()) ;       assertEquals ( 3 , documents . get ( 1 ). doc (). docId ()) ;   }}

为了总结,通常将项频率和反向文档频率分数一起计算,以评估给定术语与文档或语料库的相关性。当一个术语出现在文档中,很少在其他文档中,当术语出现更多时,[MATH] TFIDF [/MATH]功能给出了更高的加权分数,当该术语出现在每个文档中时,它给我们0分数。

代码示例可在此处提供。