コンテンツランキングのアルゴリズムは、フィードや検索結果、「人気の投稿」一覧に何をどの順番で出すかを決める仕組みです。アイテムごとに複数のシグナル(どう反応されているか、コンテンツそのものの中身、どれだけ新しく関連性があるか)からスコアを計算し、そのスコアで並べ替えます。ところが閲覧数といいねだけで順位を付けると、「富める者はますます富む」ループができあがります。上位にあるものは人の目に触れるので閲覧数といいねがさらに集まり、だからまた上位に居座る、という具合です。
これが閲覧数といいねの分かりやすい欠陥です。すでに人気のあるものに報酬を与えてしまう。新しいコンテンツはフィードの底で飢え、プラットフォームはじわじわと再放送チャンネルになっていく。フィードのランキングは、いま働いている職場でも地味に難しい問題のひとつなので、他にどんなシグナルが信用に値するのか、けっこう時間をかけて考えてきました。
タップではなく行動を見る#
明示的なフィードバックは数が少なく、偏っています。一方、行動データは豊富です。滞在時間とスクロール深度を見れば、実際に読まれたのか、最初の段落で離脱されたのかが分かります。再訪はさらに強いシグナルで、同じ記事に戻ってくるのはどんないいねよりも確かな支持です。逆に、すぐの離脱は有用なネガティブシグナルです。コンテンツが約束したものを、読者は受け取れなかったということですから。
コンテンツ自体に埋まっているシグナル#
良いコンテンツが最初からいいねを集めるとは限りませんが、質は別の形で表れます。ひとつは新規性。プラットフォームでまだ扱われていない領域をカバーしているか。もうひとつは構造で、整ったフォーマット、まともな文法、明快さは、認めたくないくらい品質と相関します。書き手の実績も妥当な事前情報になります。ただし生のいいね数ではなく、過去の作品がどれだけ読者を掴んだかで判断するのが筋です。初期のコメントも役立ちます。心のこもったポジティブなコメント3件は、反射的ないいね30件に勝ります。さらに意味解析を使えば、プラットフォームに欠けていた穴を埋めるコンテンツを拾い上げられます。
外の世界からシグナルを借りる#
いま何が響いているかは、広いインターネットが教えてくれます。検索エンジンのトレンドキーワードに合致するコンテンツには、最初から関連性が備わっています。旬の話題への素早い投稿にはブーストをかける価値があります。TwitterやRedditで盛り上がっているものは、自分たちのユーザーが次に求めるものの予告編であることが多く、少し予測を効かせれば、波が来る前に該当する新着コンテンツを押し上げられます。自前の検索ボックスも過小評価されがちです。ユーザーが同じトピックを検索し続けている、あるいはある記事が検索エンジンから安定して流入を集めているなら、それは推測抜きで手に入る関連性の証拠です。
シグナルを1つのスコアにまとめる#
考え方を数行のPythonで書くとこうなります。あくまで説明のための例で、本番のシステムではありません。各シグナルを0〜1の範囲に正規化して生の数値が大きいシグナルが他をかき消さないようにし、重み付きの和で1つのスコアにまとめ、時間減衰で新しい投稿にも先月のヒット作と戦えるチャンスを与えます。
WEIGHTS = {"read_depth": 0.3, "revisits": 0.25, "quality": 0.25, "early_comments": 0.2}
HALF_LIFE_HOURS = 48 # a post loses half its score every two days
def normalise(values):
lo, hi = min(values), max(values)
return [(v - lo) / (hi - lo) if hi > lo else 0.0 for v in values]
def rank(items):
"""items: dicts holding raw signal values plus "age_hours"."""
norm = {name: normalise([item[name] for item in items]) for name in WEIGHTS}
scores = []
for i, item in enumerate(items):
base = sum(weight * norm[name][i] for name, weight in WEIGHTS.items())
scores.append(base * 0.5 ** (item["age_hours"] / HALF_LIFE_HOURS))
order = sorted(range(len(items)), key=lambda i: scores[i], reverse=True)
return [items[i] for i in order]重みは、このあと書くテストで確かめるまではただの当て推量です。閲覧数やいいねを入れてもかまいません。スコアのすべてではなく、いくつかある入力のひとつになるだけです。半減期は古い投稿がどれだけ早く沈むかを決めるもので、これはこれで大きなテーマなので、フィードのランキングアルゴリズムは古い投稿をいつ埋もれさせるのかで詳しく書きました。実際のシステムでは重みを手で決めずにモデルに学習させることが多いですが、形は同じです。たくさんのシグナルを入れて、1つの数字を出して、並べ替える。
最後は正直にテストする#
シグナルがどれだけ賢くても、A/Bテストは欠かせません。新しいコンテンツを一部のユーザーに見せて、既存の人気コンテンツとエンゲージメントを比べる。そして広がり方を観察する。プラットフォームの内外でのシェア、言及、埋め込みは、閲覧カウンターよりずっと多くのことを語ってくれます。
現実に耐えられる単一の指標は存在しません。実際に機能するランキングは、行動、コンテンツ固有の質、時事性、正直な実験をブレンドしたもので、どのシグナルにも最終決定権は与えられていません。
