<?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/zh-cn/tags/metrics/</link>
    <description>关于软件工程与研发团队领导力的短文——绩效评估、招聘与团队扩张、CI/CD、DevOps 监控、智能体 AI。写于首尔。</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <copyright>© 2026 仁才德</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/zh-cn/tags/metrics/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>内容排序算法：浏览量和点赞之外，还该看哪些信号</title>
      <link>https://jared.lynskey.co.nz/zh-cn/posts/2023/2023-10-06-reinenforcement-learning/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/zh-cn/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>内容排序算法到底在做什么，为什么只按浏览量和点赞排会让热门的越来越热，还有哪些信号值得一起算进同一个分数，附一段加权求和再按时间衰减的示例代码。</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="#%e7%9c%8b%e8%a1%8c%e4%b8%ba%e5%88%ab%e7%9c%8b%e7%82%b9%e5%87%bb" 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="#%e8%97%8f%e5%9c%a8%e5%86%85%e5%ae%b9%e6%9c%ac%e8%ba%ab%e7%9a%84%e4%bf%a1%e5%8f%b7" 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="#%e4%bb%8e%e5%b9%b3%e5%8f%b0%e5%a4%96%e9%9d%a2%e5%80%9f%e4%bf%a1%e5%8f%b7" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>现在什么最有共鸣，外面的互联网会告诉你。和热门搜索词对上的内容，天生就带时效性；对热点话题下手快的作者，值得给点加成。Twitter 和 Reddit 上在热什么，往往就是你的用户接下来想看什么的预告片——稍微做点预测，就能赶在浪头之前把对应的新内容推上去，而不是浪打过了才跟。自家的搜索框也被低估了：用户反复搜某个主题，或者某篇文章持续从搜索引擎带来流量，这种时效性根本不用猜。</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%8a%8a%e4%bf%a1%e5%8f%b7%e6%8f%89%e6%88%90%e4%b8%80%e4%b8%aa%e5%88%86%e6%95%b0" aria-label="锚点">#</a>
    </span>
    
</h3>
<p>把思路写成几行 Python 大概是这样。这只是示意，不是线上真在跑的系统。每个信号先归一化到 0 到 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/zh-cn/posts/2023/2023-09-27-simulating-views/" >信息流排序算法如何决定什么时候埋掉旧帖子</a>里专门写过。真实的系统通常让模型去学权重，而不是手工拍定，但形状是一样的：很多信号进去，一个数字出来，然后排序。</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%90%8e%e8%80%81%e8%80%81%e5%ae%9e%e5%ae%9e%e5%81%9a%e5%ae%9e%e9%aa%8c" 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>
