「Beatles abbey rd」と入力したときに「Abbey Road」を見つけるには、PostgreSQL でファジー/セマンティック検索を使用してください。

2026/01/22 3:24

「Beatles abbey rd」と入力したときに「Abbey Road」を見つけるには、PostgreSQL でファジー/セマンティック検索を使用してください。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

記事では、PostgreSQL拡張機能

pg_trgm
(ファジー文字列マッチング)と
pgvector
(埋め込みを用いた語義的類似度計算)を組み合わせて、ノイズの多いユーザー入力をクリーンなカタログに照合する方法を示しています。使用データセットは Hugging Face の「spotify‑tracks‑dataset」で、約 114,000 曲(重複除去後で約 50,000 アルバム)です。データベーススキーマでは各アルバムの生データ名、クリーン化された
album_normalized
列、および 768 次元の
album_embedding
を保存します。
album_normalized
に対しては
gin_trgm_ops
を使用した GIN インデックスを作成し、類似度閾値は
SET pg_trgm.similarity_threshold
(典型的な値は約 0.3)で設定します。
album_embedding
には IVFFlat インデックスを適用し、
vector_cosine_ops
lists = 100
を使用します。ファジー検索では
similarity()
または
%
演算子を使用し、語義的検索では
1 - (album_embedding <=> %s::vector)
によってコサイン類似度を計算し、約 0.6 の閾値でフィルタリングします。正規化パイプラインは名前を小文字に変換し、「(Remastered 2023)」などのノイズパターンを除去し、略語(
feat.
featuring
)を展開し、先頭冠詞を削除し、空白を正規化し、句読点を取り除きます。埋め込み生成は Sentence‑Transformers の
all-mpnet-base-v2
(768 次元)を使用し、バッチサイズ 100 で CPU 上で約 500 アルバム/分の速度になります。推奨されるハイブリッド検索ではまずファジーマッチングを試み、スコアが約 0.65 未満の場合は語義的検索にフォールバックし、より高い信頼度の結果を返します。性能ノートとして、
pg_trgm
のクエリは適切なインデックスでサブミリ秒レベル、
pgvector
のクエリはテーブルサイズとインデックス設定に応じて 1〜10 ms です。今後の課題には代替埋め込みモデルのテスト、特定ユースケース向けトリグラム閾値の調整、および低レイテンシを維持しつつ大規模カタログへのスケールが含まれます。このアプローチは音楽カタログプラットフォーム、ストリーミングサービス、そして高速ファジー/語義検索を必要とするあらゆるドメインに有益です。

本文

問題点:汚い入力ときれいなデータ

検索機能を構築する際、データベースには「Abbey Road」「The Dark Side of the Moon」「OK Computer」のように完全に整備されたアルバム名が入っているかもしれません。
しかしユーザーは次のように入力します。

beatles abbey rd
dark side moon pink floyd
ok computer radiohead 1997

単純な

WHERE name = ?
は通用しません。もっと賢い手段が必要です。


二つのアプローチ、二つの PostgreSQL 拡張

アプローチ拡張機能適したケース
ファジーマッチングpg_trgm文字列を trigrams(3文字連続)で比較タイプミス・略語・単語順の変化
意味検索pgvector埋め込みベクトルで意味的類似度を測定同義語・言い換え・概念的な類似性
  • pg_trgm
    はテキストを 3文字ずつに分割し、重複率を測ります。
    例:
    "Abbey Road"
    {" ab","abb","bbe","bey","ey "," ro","roa","oad","ad "}
  • pgvector
    は機械学習モデルで生成した意味表現(ベクトル)を格納します。

データベースのセットアップ

-- 拡張機能を有効化
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS vector;

-- カタログテーブル
CREATE TABLE album_catalog (
    id SERIAL PRIMARY KEY,
    track_id VARCHAR(50),
    track_name VARCHAR(500) NOT NULL,
    artists VARCHAR(500),
    album_name VARCHAR(500),
    popularity INTEGER,
    album_normalized VARCHAR(500),       -- ファジーマッチ用に整形した文字列
    album_embedding vector(768)          -- 意味検索用埋め込みベクトル
);

Spotify データセットをロード

# load_spotify_data.py
from datasets import load_dataset
import psycopg2

dataset = load_dataset("maharshipandya/spotify-tracks-dataset", split="train")

conn = psycopg2.connect(host='localhost', database='music_catalog',
                        user='your_user', password='your_password')
cursor = conn.cursor()

seen_albums = set()
inserted = 0
for row in dataset:
    album_key = (row['album_name'], row['artists'])
    if album_key in seen_albums: continue
    seen_albums.add(album_key)

    cursor.execute("""
        INSERT INTO album_catalog (track_id, track_name, artists, album_name, popularity)
        VALUES (%s,%s,%s,%s,%s)
    """, (row['track_id'], row['track_name'],
          row['artists'], row['album_name'], row['popularity']))
    inserted += 1
    if inserted % 5000 == 0:
        print(f"Inserted {inserted} albums...")

conn.commit()
conn.close()

実行方法:

pip install datasets psycopg2-binary
python load_spotify_data.py

約5万件のユニークアルバムが投入されます。


インデックス

-- pg_trgm 用 GIN インデックス
CREATE INDEX idx_album_name_trgm 
ON album_catalog USING gin (album_normalized gin_trgm_ops);

-- pgvector 用 IVFFlat インデックス
CREATE INDEX idx_album_embedding 
ON album_catalog USING ivfflat (album_embedding vector_cosine_ops)
WITH (lists = 100);

lists=100
は約5万行に対して良いスタートポイントです。


1️⃣ pg_trgm を使ったファジーマッチング

基本的な類似度クエリ

SELECT album_name, artists,
       similarity('abbey rd beatles', album_name) AS score
FROM album_catalog
WHERE similarity('abbey rd beatles', album_name) > 0.3
ORDER BY score DESC
LIMIT 5;

例結果:

album_nameartistsscore
Abbey Road (Remastered)The Beatles0.48

GIN インデックスを使う

SET pg_trgm.similarity_threshold = 0.3;

SELECT album_name, artists,
       similarity('abbey rd', album_normalized) AS score
FROM album_catalog
WHERE album_normalized % 'abbey rd'
ORDER BY score DESC;

強み

  • タイプミス(
    Abey Road
    Abbey Road
    )に対処
  • 略語(
    abbey rd
    )を拡張
  • 単語の欠落や順序変更にも柔軟

弱点

  • 同義語・概念的なクエリには弱い
  • 完全に別表現の場合は失敗する

2️⃣ pgvector を使った意味検索

埋め込みベクトルの生成(1度だけ)

# generate_embeddings.py
from sentence_transformers import SentenceTransformer
import psycopg2

model = SentenceTransformer('all-mpnet-base-v2')

conn = psycopg2.connect(host='localhost', database='music_catalog',
                        user='your_user', password='your_password')
cursor = conn.cursor()

cursor.execute("""
    SELECT id, album_name, artists 
    FROM album_catalog
    WHERE album_embedding IS NULL
""")
albums = cursor.fetchall()

batch_size = 100
for i in range(0, len(albums), batch_size):
    batch = albums[i:i+batch_size]
    texts = [f"{album} by {artist}" if artist else album 
             for _, album, artist in batch]

    embeddings = model.encode(texts)

    for j, (album_id, _, _) in enumerate(batch):
        cursor.execute("""
            UPDATE album_catalog
            SET album_embedding = %s
            WHERE id = %s
        """, (embeddings[j].tolist(), album_id))
    conn.commit()

conn.close()

埋め込み生成は CPU が良ければ 1 分あたり約500件です。
一度だけ実行してください。

埋め込みで検索

def search_by_embedding(query, cursor, model, threshold=0.6):
    query_emb = model.encode(query).tolist()
    cursor.execute("""
        SELECT album_name, artists,
               1 - (album_embedding <=> %s::vector) AS similarity
        FROM album_catalog
        WHERE album_embedding IS NOT NULL
          AND 1 - (album_embedding <=> %s::vector) > %s
        ORDER BY similarity DESC
        LIMIT 5;
    """, (query_emb, query_emb, threshold))
    return cursor.fetchall()

強み

  • 同義語・言い換えに強い
  • 自然言語クエリを扱える
  • 概念的に似たアルバムを探せる

弱点

  • 意味的には近いが誤ったアルバムが上位になる可能性
  • 略語(
    rd
    Road
    )は理解できない場合も
  • 学習済みモデルが必要

📌 テキスト正規化 ― 秘訣

両手法ともクリーンな入力で最良の結果を得られます。以下は簡易正規化パイプラインです。

import re

ABBREVIATIONS = {
    r'\bfeat\.?\b': 'featuring',
    r'\bft\.?\b'  : 'featuring',
    r'\bvol\.?\b' : 'volume',
    r'\bpt\.?\b'  : 'part',
    r'\bv\.?\s*(\d)': r'volume \1',
    r'\bst\.?\b'   : 'saint',
    r'\b&\b'      : 'and',
}

NOISE_PATTERNS = [
    r'\(remaster(ed)?\s*\d*\)',            # (Remastered 2023)
    r'\(\d{4}\s*remaster(ed)?\)',          # (2011 Remaster)
    r'\[deluxe(\s+edition)?\]',            # [Deluxe Edition]
    r'\(deluxe(\s+edition)?\)',            # (Deluxe Edition)
    r'\s*-\s*single\b',                    # - Single
    r'\s*\[\d+[-/]\d+\]',                  # [Disc 1/2]
    r'\(anniversary(\s+edition)?\)',       # (Anniversary Edition)
    r'\(expanded(\s+edition)?\)',          # (Expanded Edition)
    r'\(bonus\s+track.*?\)',               # (Bonus Track Version)
    r'\(super\s+deluxe\)',                 # (Super Deluxe)
    r'\(\d{4}\s+re-?issue\)',              # (2021 Reissue)
    r'\s+OKNOTOK\s+\d{4}\s+\d{4}',          # OKNOTOK 1997 2017
    r'\s*\(original\s+motion\s+picture.*?\)',# (Original Motion Picture Soundtrack)
]

LEADING_ARTICLES = ['the', 'a', 'an']

def normalize_album(text):
    if not text: return ''
    s = text.lower().strip()
    for pat in NOISE_PATTERNS:
        s = re.sub(pat, '', s, flags=re.IGNORECASE)
    for pat, repl in ABBREVIATIONS.items():
        s = re.sub(pat, repl, s, flags=re.IGNORECASE)
    for art in LEADING_ARTICLES:
        if s.startswith(art + ' '):
            s = s[len(art)+1:]
            break
    s = re.sub(r'\s+', ' ', s).strip()
    s = re.sub(r'[^\w\s]', '', s)  # punctuation を除去
    return s

album_normalized
列を埋めるスクリプト:

# normalize_albums.py
import psycopg2

conn = psycopg2.connect(host='localhost', database='music_catalog',
                        user='your_user', password='your_password')
cursor = conn.cursor()
cursor.execute("SELECT id, album_name FROM album_catalog")
albums = cursor.fetchall()

for album_id, name in albums:
    norm = normalize_album(name)
    cursor.execute("""
        UPDATE album_catalog
        SET album_normalized = %s
        WHERE id = %s;
    """, (norm, album_id))
conn.commit()

これでファジーマッチが正確に機能します。


🤝 両手法を組み合わせる

ハイブリッド検索:まずファジー、スコアが低ければ埋め込みへフォールバック。

def search_catalog(query, cursor, model,
                   fuzzy_thr=0.3, embed_thr=0.6):
    norm_q = normalize_album(query)

    # 1️⃣ ファジーマッチ
    cursor.execute("""
        SELECT id, album_name, artists,
               similarity(%s, album_normalized) AS score,
               'fuzzy' AS method
        FROM album_catalog
        WHERE similarity(%s, album_normalized) > %s
        ORDER BY score DESC
        LIMIT 1;
    """, (norm_q, norm_q, fuzzy_thr))
    fuzzy = cursor.fetchone()

    if fuzzy and fuzzy[3] >= 0.65:
        return fuzzy

    # 2️⃣ 埋め込みマッチ
    emb_q = model.encode(query).tolist()
    cursor.execute("""
        SELECT id, album_name, artists,
               1 - (album_embedding <=> %s::vector) AS score,
               'embedding' AS method
        FROM album_catalog
        WHERE album_embedding IS NOT NULL
          AND 1 - (album_embedding <=> %s::vector) > %s
        ORDER BY score DESC
        LIMIT 1;
    """, (emb_q, emb_q, embed_thr))
    embed = cursor.fetchone()

    return embed or fuzzy

例:

print(search_catalog("abbey rd", cursor, model))
# → ファジー: Abbey Road (Remastered)

print(search_catalog("beatles last studio album", cursor, model))
# → 埋め込み: Abbey Road (Remastered)

📈 パフォーマンスノート

機能インデックス型典型的速度
pg_trgmGIN適切にインデックスがあればミリ秒未満
pgvectorIVFFlatテーブルサイズと
lists
により 1–10 ms
  • 新しい行を大量に追加した場合(元の行数の >10%)はベクトルインデックスを再構築してください。
  • pg_size_pretty(pg_relation_size('idx_album_name_trgm'))
    でインデックスサイズ(約2–3倍程度)が確認できます。

📚 いつどちらを使うか

シナリオ推奨
タイプミス補正pg_trgm
略語展開(正規化付き)pg_trgm
自然言語クエリpgvector
「似たアイテムを探す」pgvector
オートコンプリート/タイプヘッドpg_trgm
多言語対応多言語モデル付き pgvector
計算資源が限られるpg_trgm
ML モデルなしのコールドデータpg_trgm

🔑 埋め込みモデル選択

モデル次元数速度品質用途
all-MiniLM-L6-v2
384高速良好大規模カタログ、オートコンプリート
all-mpnet-base-v2
768より良い汎用
ドメイン固有モデル可変可変最適化済み医療・法務・科学分野

🎉 結論

  • pg_trgm は文字レベルで高速にファジーマッチ。
  • pgvector は意味ベースの類似検索を実現。
  • 正規化は両手法のパフォーマンスを最大限に引き上げます。
  • ハイブリッドアプローチが最良の結果を提供します。

Spotify の約5万件の実データでテストしたとおり、同じパターンは書籍・商品など他のカタログでも適用できます。

すべては標準 PostgreSQL と

pg_trgm
/
pgvector
拡張だけで完結します。外部検索エンジン不要です。素敵なマッチングをお楽しみください!

同じ日のほかのニュース

一覧に戻る →

2026/01/27 7:03

**Windows の状況:** Windows 11 は現在どんな状態になっているのでしょうか?

## 日本語訳: **概要:** Microsoft の最新 Windows 更新は、安定したオペレーティングシステムよりも AI 主導の機能―特に Copilot ―を優先しているため、広範なバグ、パフォーマンス問題、およびセキュリティ欠陥が発生しています。2026 年 1 月の KB パッチは、新しい Intel プロセッサでシャットダウン失敗、クラウド PST を使用した Outlook のハング、メモ帳、RDP、およびデバイスドライバーのクラッシュを引き起こしました。同じ更新パッケージは、大規模 AI モデルのダウンロードによりサイズが 4 倍になり、バイナリが肥大化し、「Copilot‑ification」による UI が導入されました。暗号化されていないスクリーンショットへの懸念から削除を余儀なくされた事例もあります。 このシフトは、2023 年 12 月に Edge、メモ帳、設定、Office 等へ Copilot を投入したことから始まりました。以前のローカルアカウント用修正(OOBE.exe /bypassNRO)は後続の更新で閉じられ、Microsoft アカウントを持たないユーザーの使い勝手が低下しました。RDP のロールバック、DRM 失敗、およびエクスプローラーの遅延に関する月次報告は、24H2/25H2 リリースに起因しています。 Microsoft は、大規模な AI 重視更新を継続しつつ、重要なバグについては迅速な KB パッチで対処すると考えられます。この戦略はユーザーの信頼を損ない、企業のサポートコストを増大させ、一部の企業が代替 OS プラットフォームやデスクトップ AI 機能の採用をより慎重に検討するきっかけになる可能性があります。 **概要スケルトン** - **メインメッセージ(何を伝えたいか)** Microsoft の最近の Windows 更新は、安定した OS よりも Copilot などの AI 主導機能を積極的に推進する方向転換が原因で、広範なバグ・パフォーマンス低下・セキュリティ問題を引き起こしています。 - **証拠 / 推論(何故そう言えるか)** - 2026 年 1 月の KB 更新により、新しい Intel チップでシャットダウン失敗、クラウド PST を使った Outlook のハング、およびアプリ(メモ帳、RDP、デバイスドライバー)のクラッシュが発生。 - 同じ更新パッケージは AI モデルのダウンロードにより以前のリリースの 4 倍のサイズになり、バイナリが肥大化。 - Copilot の統合で UI が「Copilot‑ification」され、暗号化されていないスクリーンショットへの懸念から削除を強いられた。 - **関連ケース / 背景(文脈・過去の出来事)** - このシフトは、2023 年 12 月に Microsoft Copilot が Edge、メモ帳、設定、Office 等へ投入されることで始まりました。 - ローカルアカウント用ワークアラウンド(OOBE.exe /bypassNRO)は後続の更新で閉じられ、ローカル アカウント利用者の使い勝手が低下。 - RDP のロールバック、DRM 失敗、およびエクスプローラーの遅延に関する月次報告は、24H2/25H2 リリースに起因。 - **今後何が起こるか(将来の展開・予測)** Microsoft は大規模な AI 重視更新を継続しつつ、重要バグについては迅速な KB パッチで対処すると見込まれます。Copilot のアップセルを優先することで、毎月のスキャンダルが増え、安定性が回復されるまでユーザー信頼が徐々に低下する可能性があります。 - **影響(ユーザー / 企業 / 業界)** - ユーザーは頻繁なシステムクラッシュ、生産性の損失、および暗号化されていないデータ取得へのセキュリティ懸念に直面。 - 企業はサポートコストが増大し、新しい Windows リリース採用を再検討する可能性。 - 業界全体では、競合 OS プラットフォームへのシフトやデスクトップ環境での AI 機能採用に対するより慎重な姿勢が広まる可能性があります。

2026/01/26 23:41

テレビは本日で百周年を迎えました。

## Japanese Translation: テレビは本日100周年を迎え、ジョン・ログィー・ベアードの先駆的な業績に特に焦点が当てられました。1926年1月26日、ベアードはフリス通り22番地のソーホウワークショップで回転ディスクとレンズ、穴付きシートを用いたライブテレビの最初の公開デモンストレーションを行いました。その後、光感受性セルを追加して影の階調を送信できるようにしました。最初に送信された画像は、現在ヘイスティングス博物館で展示されている聖ジョンズ救急隊メダルの影でした。 ベアードの初期デモには、バラエティ・ドミー「ストゥッキー・ビル」と最初の人間被写体ウィリアム・テイントンが含まれます。1927年にフォノヴィジョンを導入し、画像をグラムフォンレコードに記録しました。1928年にはノクトヴィジョン(赤外線テレビ)、カラーテレビ、および立体テレビの実験を行いました。また、1930–31年にBBC放送用の30ライン機械式システムも開発しました。 1932年にEMIによって電子エミトロンカメラが導入され、ベアードの240ライン機械式システムとマルコニ-EMIの405ライン電子システムは一時的にロンドンテレビで採用されました。三か月後、最終的にはマルコニ・システムが優位になりました。 ベアードは1924年11月にフリス通り22番地へ移転し、ゴードン・セルフリッジのパーム・コートデモで60ポンドを稼ぎ、第二次世界大戦で会社が破産手続きになるまで発明の展示を続けました。爆撃被害により後期のカラー作業は中断され、1946年に57歳で脳卒中で亡くなりました。 初期の成功にもかかわらず、英国家庭へのテレビ機器の普及には数十年が必要でした。カラーTVは1960年代まで一般的になりませんでした。今日ではほぼすべての人がテレビまたは同等のデバイスを所有しており、これはベアードの回転ディスクと機械式画像伝送に関する先駆的な業績のおかげです

2026/01/21 1:52

滑走路に秘められた設計技術

## 日本語訳: ### 要約: 本文は、2025年9月に発生した最近の滑走路オーバーランが、EMAS(Energy‑Management and Arresting System)とFAAガイドラインに従った慎重な設計などの高度な安全システムによって防止されたことを説明しています。これらのガイドラインは、航空場で使用される最大機体を基準に滑走路長を決定しつつ、風向き・温度・標高も考慮して十分な停止距離を確保します。Embraer 145、Gulfstream、Bombardier機が関与した米国の3件の事故では、EMASが衝撃エネルギーを効果的に吸収し、航空機が滑走路外の危険箇所に着地することを防ぎました。FAAの詳細ガイダンスは、風向き(約95%)に合わせて滑走路を配置し、ディスプレースド・スレッショルド、ブラストパッド、ランウェイセーフティエリア(RSA)を追加して安全マージンを高めることを推奨しています。今後は、EMASの活用と機体種別に応じた舗装材の調整、単一方向が支配的でない場合の風向き戦略の精緻化が継続されます。これらの改善はパイロットの怪我リスクを減らし、空港が安全インフラへの投資を促進させ、航空業界に滑走路設計がオーバーランを防ぐことへの信頼感を与え、保険費用の低減につながる可能性があります。 ### 要約骨格 **本文が主に伝えようとしていること(メインメッセージ)** 2025年9月の滑走路オーバーランは、EMASなどの設計上の安全機能のおかげで回避され、滑走路設計はFAAガイドラインに従い、主要機体を基準に長さが決定され環境要因も調整されています。 **証拠/根拠(なぜそう言われているのか)** 米国で起きた3件の事故では航空機が滑走路を超えて衝突したものの、EMASがエネルギーを吸収し危険箇所に着地しませんでした。FAAの40ページの文書は、長さ・風・温度が設計を決定する方法を説明しており、舗装層と表面処理は摩擦と構造的健全性を維持するよう指定されています。 **関連事例/背景(コンテキスト、過去の出来事)** オーバーランはEmbraer 145、Gulfstream、Bombardier機で2025年9月24–3日に発生。FAAガイドラインは滑走路を主流風に合わせる(約95%)とし、ディスプレースド・スレッショルド、ブラストパッド、RSAを標準安全対策として含めています。 **今後何が起こりうるか(将来の展開/予測)** 将来的な滑走路計画ではEMAS使用の強調、機体種別に応じた舗装材の適用、単一風向きが支配的でない場合の風向き戦略の改善が継続されるでしょう。設計更新は温度・標高調整をより正確に取り入れる可能性があります。 **これらが与える影響(利用者/企業/業界)** パイロットは安全なオーバーランと怪我リスクの低減を享受し、空港はEMASや表面メンテナンスへの投資を検討。航空業界は滑走路長計算が事故防止に十分であるという自信を得て、保険・責任コストの削減につながる可能性があります。

「Beatles abbey rd」と入力したときに「Abbey Road」を見つけるには、PostgreSQL でファジー/セマンティック検索を使用してください。 | そっか~ニュース