TF - IDFを用いた関連記事


私の新しいブログから欠落していた重要な機能の一つEleventy 関連記事のウィジェットです.私はすでにタグを実装していました.そして、それは同様の目的に役立つことができます、しかし、私はポストの内容に基づいてより自動化された何かを実験したかったです.

マニュアル実装


より多くの手動実現のためのアプローチのカップル例です.
  • Using frontmatter data
  • Matching on tag similarity
  • 期間周波数逆文書周波数


    私は自然言語の専門家から離れています、しかし、TF IDFは基本的に特定のコレクションの向こう側に単語の重要性を計算します.例えば、「the」は信じられないほど一般的で、どんな特定のドキュメントにも重要ではありません.これの逆は、まれであるが、ドキュメントの向こう側に共有される語が類似性の良い指標を提供するということです.そこに着くことは、しかし、二段ステップを必要とします.

    ドキュメントから関連するステップ


    シリアライズポスト


    一般的に、ブログ記事、記事、またはいくつかの他のドキュメントが変換され、標準化する必要があります複数の部分(タイトル、抜粋、タグ、ボディなど)があります.たとえば、次のコンポーネントの一部を単一の文字列として結合します.
    [data.title, data.excerpt, ...data.tags].join(" ")
    

    時分化する


    ドキュメント文字列をトークン化します.例えば、スペースを分割するのは文字列をトークン化するナイーブな方法です.

    ステミングと補修


    各単語の部分をチョップするためにステマーを使用してください.この場合、cars , car's , cars' すべてがなるcar . これは一般的にかなり粗暴なヒューリスティックです.
    Lemmatizationは、次のステップとより高度です.例えば、am , are , is すべてがなるbe 例えば.

    tfを計算する


    この仕事のために、私はパッケージを使用しています.natural . 以下に簡単な例を示します.
    import { TfIdf } from 'natural';
    
    const tfidf = new TfIdf();
    
    tfidf.addDocument('this document is about node.');
    tfidf.addDocument('this document is about ruby.');
    tfidf.addDocument('this document is about ruby and node.');
    
    tfidf.tfidfs('node ruby', function(i, measure) {
        console.log('document #' + i + ' is ' + measure);
    });
    

    つのライブラリにラッピング


    パッケージ中.natural , 自然言語処理に最適ですが、それは本当に私が共有したいインターフェイスと抽象化を提供していませんでした.

    関連文書


    最初の層はパッケージですrelated-documents を返します.
    import { Related } from "related-documents";
    
    const documents = [
      { title: "ruby", text: "this lorem ipsum blah foo" },
      ...
    ];
    
    const options = {
      serializer: (document: any) => [document.title, document.text],
      weights: [10, 1],
    };
    
    const related = new Related(documents, options);
    
    related.rank(documents[0]); 
    // [{absolute: 0-1, relative: 0-INF, document}]
    
    この出力は以下の通りである.
    {
        "absolute": 4.849271710192877,
        "document": {
            "text": "this document is about ruby and node.",
            "title": "ruby and node",
        },
        "relative": 0.20786000940717744,
    }
    
    パッケージの上に階層化されている重要な機能は以下のようになります.
  • 文書の異なる部分の重み
  • シリアル化サポート
  • 時分化とステミングのための賢明なデフォルト
  • ソースはGitHub , インストールすることができます.
    npm i related-documents
    
    Reference docs もあります.

    プラグイン関連


    開発者の経験を改善するために、タマネギに1つ以上の層を加えました.eleventy-plugin-related . JavaScriptの世界でもう一つの依存関係は何ですか?😄
    基本的な使い方は以下の通りです.
    eleventyConfig.addFilter(
      "related",
      require("eleventy-plugin-related").related({
        serializer: (doc) => [doc.title, doc.description ?? "", doc.text ?? ""],
        weights: [10, 1, 3],
      })
    );
    
    何百もの柔軟性を与えます、そして、何度か、プラグインであるものの境界とちょうどノード・ライブラリであるものは非常に明確でありません.それは確かにここのケースです、そして、さらなる用法と機能要求は若干のより良い定義を与えます.

    実装


    それで、どのように、私はこれらの2つの図書館を私のブログで使っていますか?
  • タイトルと抜粋を一つの文字列に結合する
  • タイトル/抜粋とタグの間の等しい重さ
  • const related = require("eleventy-plugin-related").related({
        serializer: ({ data: { title, excerpt, tags } }) => [
            [title, excerpt].join(" "),
            (tags || []).join(" "),
        ],
        weights: [1, 1],
    });
    
    eleventyConfig.addFilter("relatedPosts", function (doc, docs) {
        return related(doc, docs).filter(({ relative }) => relative > 0.1);
    });
    
    明らかに、ここでタグを1つのストリングに結合して、それから後でトークン化する非効率性があります.
    しかし、結果はかなり有望です、しかし、タグはおそらく少数のポストでこの点で仕事の多くをしています.