<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Metrics · 仁才徳</title>
    <link>https://jared.lynskey.co.nz/ja/tags/metrics/</link>
    <description>ソフトウェアエンジニアリングと開発組織のリーダーシップについて。評価、採用とチームの拡大、CI/CD、DevOps モニタリング、エージェンティック AI。ソウルより。</description>
    <generator>Hugo</generator>
    <language>ja</language>
    <copyright>© 2026 仁才徳</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ja/tags/metrics/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>コンテンツランキングのアルゴリズムとは：閲覧数といいね以外のシグナル</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2023/2023-10-06-reinenforcement-learning/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2023/2023-10-06-reinenforcement-learning/</guid>
      <pubDate>Fri, 06 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>content ranking</category>
      <category>metrics</category>
      <category>SEO</category>
      <category>machine learning</category>
      <category>user engagement</category>
      <category>content strategy</category>
      <description>コンテンツランキングのアルゴリズムが何をしているのか、閲覧数といいねだけで並べると人気者がさらに人気になる理由、そして1つのスコアに混ぜる価値のあるシグナルをまとめました。</description>
      <content:encoded><![CDATA[<p>コンテンツランキングのアルゴリズムは、フィードや検索結果、「人気の投稿」一覧に何をどの順番で出すかを決める仕組みです。アイテムごとに複数のシグナル（どう反応されているか、コンテンツそのものの中身、どれだけ新しく関連性があるか）からスコアを計算し、そのスコアで並べ替えます。ところが閲覧数といいねだけで順位を付けると、「富める者はますます富む」ループができあがります。上位にあるものは人の目に触れるので閲覧数といいねがさらに集まり、だからまた上位に居座る、という具合です。</p>
<p>これが閲覧数といいねの分かりやすい欠陥です。すでに人気のあるものに報酬を与えてしまう。新しいコンテンツはフィードの底で飢え、プラットフォームはじわじわと再放送チャンネルになっていく。フィードのランキングは、いま働いている職場でも地味に難しい問題のひとつなので、他にどんなシグナルが信用に値するのか、けっこう時間をかけて考えてきました。</p>

<h3 class="relative group">タップではなく行動を見る
    <div id="タップではなく行動を見る" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%bf%e3%83%83%e3%83%97%e3%81%a7%e3%81%af%e3%81%aa%e3%81%8f%e8%a1%8c%e5%8b%95%e3%82%92%e8%a6%8b%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>明示的なフィードバックは数が少なく、偏っています。一方、行動データは豊富です。滞在時間とスクロール深度を見れば、実際に読まれたのか、最初の段落で離脱されたのかが分かります。再訪はさらに強いシグナルで、同じ記事に戻ってくるのはどんないいねよりも確かな支持です。逆に、すぐの離脱は有用なネガティブシグナルです。コンテンツが約束したものを、読者は受け取れなかったということですから。</p>

<h3 class="relative group">コンテンツ自体に埋まっているシグナル
    <div id="コンテンツ自体に埋まっているシグナル" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b3%e3%83%b3%e3%83%86%e3%83%b3%e3%83%84%e8%87%aa%e4%bd%93%e3%81%ab%e5%9f%8b%e3%81%be%e3%81%a3%e3%81%a6%e3%81%84%e3%82%8b%e3%82%b7%e3%82%b0%e3%83%8a%e3%83%ab" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>良いコンテンツが最初からいいねを集めるとは限りませんが、質は別の形で表れます。ひとつは新規性。プラットフォームでまだ扱われていない領域をカバーしているか。もうひとつは構造で、整ったフォーマット、まともな文法、明快さは、認めたくないくらい品質と相関します。書き手の実績も妥当な事前情報になります。ただし生のいいね数ではなく、過去の作品がどれだけ読者を掴んだかで判断するのが筋です。初期のコメントも役立ちます。心のこもったポジティブなコメント3件は、反射的ないいね30件に勝ります。さらに意味解析を使えば、プラットフォームに欠けていた穴を埋めるコンテンツを拾い上げられます。</p>

<h3 class="relative group">外の世界からシグナルを借りる
    <div id="外の世界からシグナルを借りる" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%a4%96%e3%81%ae%e4%b8%96%e7%95%8c%e3%81%8b%e3%82%89%e3%82%b7%e3%82%b0%e3%83%8a%e3%83%ab%e3%82%92%e5%80%9f%e3%82%8a%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>いま何が響いているかは、広いインターネットが教えてくれます。検索エンジンのトレンドキーワードに合致するコンテンツには、最初から関連性が備わっています。旬の話題への素早い投稿にはブーストをかける価値があります。TwitterやRedditで盛り上がっているものは、自分たちのユーザーが次に求めるものの予告編であることが多く、少し予測を効かせれば、波が来る前に該当する新着コンテンツを押し上げられます。自前の検索ボックスも過小評価されがちです。ユーザーが同じトピックを検索し続けている、あるいはある記事が検索エンジンから安定して流入を集めているなら、それは推測抜きで手に入る関連性の証拠です。</p>

<h3 class="relative group">シグナルを1つのスコアにまとめる
    <div id="シグナルを1つのスコアにまとめる" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b7%e3%82%b0%e3%83%8a%e3%83%ab%e3%82%921%e3%81%a4%e3%81%ae%e3%82%b9%e3%82%b3%e3%82%a2%e3%81%ab%e3%81%be%e3%81%a8%e3%82%81%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>考え方を数行のPythonで書くとこうなります。あくまで説明のための例で、本番のシステムではありません。各シグナルを0〜1の範囲に正規化して生の数値が大きいシグナルが他をかき消さないようにし、重み付きの和で1つのスコアにまとめ、時間減衰で新しい投稿にも先月のヒット作と戦えるチャンスを与えます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">WEIGHTS</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;read_depth&#34;</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">,</span> <span class="s2">&#34;revisits&#34;</span><span class="p">:</span> <span class="mf">0.25</span><span class="p">,</span> <span class="s2">&#34;quality&#34;</span><span class="p">:</span> <span class="mf">0.25</span><span class="p">,</span> <span class="s2">&#34;early_comments&#34;</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">HALF_LIFE_HOURS</span> <span class="o">=</span> <span class="mi">48</span>  <span class="c1"># a post loses half its score every two days</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">normalise</span><span class="p">(</span><span class="n">values</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">values</span><span class="p">),</span> <span class="nb">max</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[(</span><span class="n">v</span> <span class="o">-</span> <span class="n">lo</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">hi</span> <span class="o">-</span> <span class="n">lo</span><span class="p">)</span> <span class="k">if</span> <span class="n">hi</span> <span class="o">&gt;</span> <span class="n">lo</span> <span class="k">else</span> <span class="mf">0.0</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">rank</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;items: dicts holding raw signal values plus &#34;age_hours&#34;.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">norm</span> <span class="o">=</span> <span class="p">{</span><span class="n">name</span><span class="p">:</span> <span class="n">normalise</span><span class="p">([</span><span class="n">item</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">])</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">WEIGHTS</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">scores</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">weight</span> <span class="o">*</span> <span class="n">norm</span><span class="p">[</span><span class="n">name</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">weight</span> <span class="ow">in</span> <span class="n">WEIGHTS</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="n">scores</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">base</span> <span class="o">*</span> <span class="mf">0.5</span> <span class="o">**</span> <span class="p">(</span><span class="n">item</span><span class="p">[</span><span class="s2">&#34;age_hours&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">HALF_LIFE_HOURS</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">order</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">items</span><span class="p">)),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">scores</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">items</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">order</span><span class="p">]</span></span></span></code></pre></div></div>
<p>重みは、このあと書くテストで確かめるまではただの当て推量です。閲覧数やいいねを入れてもかまいません。スコアのすべてではなく、いくつかある入力のひとつになるだけです。半減期は古い投稿がどれだけ早く沈むかを決めるもので、これはこれで大きなテーマなので、<a href="https://jared.lynskey.co.nz/ja/posts/2023/2023-09-27-simulating-views/" >フィードのランキングアルゴリズムは古い投稿をいつ埋もれさせるのか</a>で詳しく書きました。実際のシステムでは重みを手で決めずにモデルに学習させることが多いですが、形は同じです。たくさんのシグナルを入れて、1つの数字を出して、並べ替える。</p>

<h3 class="relative group">最後は正直にテストする
    <div id="最後は正直にテストする" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%9c%80%e5%be%8c%e3%81%af%e6%ad%a3%e7%9b%b4%e3%81%ab%e3%83%86%e3%82%b9%e3%83%88%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>シグナルがどれだけ賢くても、A/Bテストは欠かせません。新しいコンテンツを一部のユーザーに見せて、既存の人気コンテンツとエンゲージメントを比べる。そして広がり方を観察する。プラットフォームの内外でのシェア、言及、埋め込みは、閲覧カウンターよりずっと多くのことを語ってくれます。</p>
<p>現実に耐えられる単一の指標は存在しません。実際に機能するランキングは、行動、コンテンツ固有の質、時事性、正直な実験をブレンドしたもので、どのシグナルにも最終決定権は与えられていません。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-06-reinenforcement-learning/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
