Elasticsearch: ベクトル検索を使用した音楽情報検索

アレックス・サルガド

機械学習、ベクトル データベース、オーディオ データ分析が連携して刺激的な新しい可能性を開く、音楽情報検索の未来へようこそ。音楽データ分析の分野に興味がある場合、またはテクノロジーが音楽業界にどのような革命をもたらしているかに情熱を持っている場合は、このガイドが最適です。

ここでは、ベクトル検索方法を使用して音楽データを検索する旅に連れて行きます。世界のデータの 80% 以上は構造化されていないため、テキスト以外のさまざまな種類のデータの処理方法を知っておくとよいでしょう。

コードを読みながら手順を確認したい場合は、この記事の最後にリストされているGitHub上のファイルにアクセスしてください。次のコマンドを使用してコードのクローンを作成します。

git clone https://github.com/liu-xiao-guo/music-search

建築

思い出そうとしている曲の曲を口ずさむことができたら、その口ずさんだ曲が突然画面に表示された場合を想像してみてください。もちろん、必要な労力とデータ モデルの調整を考慮すると、それが私たちが現在行っていることです。

結果を達成するには、次のようなスキーマを作成します。

ここでの主な役割は埋め込みです。モデルによって生成された音声エンベディングをベクトル検索の検索キーワードとして使用します。

インストール

独自の Elasticsearch と Kibana をインストールしていない場合は、以前の記事を参照して個別にインストールしてください。

インストールの際は、Elastic Stack 8.xのインストールガイドを参照してインストールしてください。次の演習では、デモンストレーションのために最新の Elastic Stack 8.9.0 を使用します。

オーディオ埋め込みを生成する方法

エンベディング生成の中心となるのは、より関連性が高く正確な結果を提供するために数百万の例でトレーニングされたモデルです。オーディオの場合、これらのモデルは大量のオーディオ データでトレーニングできます。これらのモデルの出力は、オーディオの高密度のデジタル表現 (つまり、オーディオの埋め込み) です。この高次元ベクトルはオーディオ クリップの主要な特徴をキャプチャし、埋め込み空間での類似性の計算と効率的な検索を可能にします。

この作業では、librosa (オープンソースの Python パッケージ) を使用してオーディオ埋め込みを生成します。これには通常、メル周波数ケプストラム係数 (MFCC)、クロミナンス、メルスケール スペクトログラム特徴などの意味のある特徴をオーディオ ファイルから抽出することが含まれます。では、Elasticsearch® を使用して音声検索を実装するにはどうすればよいでしょうか?

ステップ 1: オーディオ データを保存するためのインデックスを作成する

まず、ベクター データベースに音楽データを入力する前に、Elasticsearch でインデックスを作成する必要があります。簡単にするために、Python コードを使用して Jupyter Notebook で実行します。

1.1 音声データセットのインデックスを作成する

接続を確立したので、音声情報を保存するためのインデックスを作成しましょう。jupyter ノートブックを使用して elastic_music_search.ipynb ファイルを開きます。

!pip install elasticsearch
!pip install Config

上記では、必要な Python ライブラリに従います。Elasticsearch へのリンクについては、「Elasticsearch: Python での Elasticsearch の使用について知っておくべきことすべて - 8.x」を参照してください。ダウンロードしたコード内の次のファイル simple.cfg を変更します。

シンプル.cfg

ES_PASSWORD: "p1k6cT4a4bF+pFYf37Xx"
ES_FINGERPRINT: "633bf7f6e4bf264e6a05d488af3c686b858fa63592dc83999a0d77f7e9fe5940"

上記の ES_PASSWORD は Elasticsearch が最初に起動したときに表示されたパスワードで、ES_FINGERPRINT の値は http_ca.crt のフィンガープリントです。これは、Elasticsearch が初めて起動されたときにも確認できます。この表示がまだ見つからない場合は、記事「Elasticsearch: Python での Elasticsearch の使用について知っておくべきこと - 8.x」を参照して、これを取得する方法を学習してください。もう 1 つの比較的簡単な方法は、config/kibana.yml ファイルを開くことです。

#index data in elasticsearch
from elasticsearch import Elasticsearch
from config import Config
 
with open('simple.cfg') as f:
    cfg = Config(f)
 
print(cfg['ES_FINGERPRINT'])
print(cfg['ES_PASSWORD'])
 
es = Elasticsearch(
    'https://localhost:9200',
    ssl_assert_fingerprint = cfg['ES_FINGERPRINT'],
    basic_auth=('elastic', cfg['ES_PASSWORD'])
)
 
es.info()

上記のコードは、Python コードが Elasticsearch に正常に接続されたことを示しています。

次に、my-audio-index というインデックスを作成します。

index_name = "my-audio-index"

if(es.indices.exists(index=index_name)):
    print("The index has already existed, going to remove it")
    es.options(ignore_status=404).indices.delete(index=index_name)

# Specify index configuration
mappings = {
    "_source": {
      "excludes": ["audio-embedding"]
    },
    "properties": {
      "audio-embedding": {
        "type": "dense_vector",
        "dims": 2048,
        "index": True,
        "similarity": "cosine"
      },
      "path": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "timestamp": {
        "type": "date"
      },
      "title": {
        "type": "text"
      },
      "genre": {
        "type": "text"
      }
    }
}

# Create index
if not es.indices.exists(index=index_name):
    index_creation = es.options(ignore_status=400).indices.create(index=index_name, mappings = mappings)
    print("index created: ", index_creation)
else:
    print("Index  already exists.")

提供された Python コードは、Elasticsearch Python クライアントを使用して、特定の構成でインデックスを作成します。このインデックスの目的は、高密度ベクトル フィールドでの検索操作を可能にする構造を提供することです。高密度ベクトル フィールドは、特定のエンティティ (この場合はオーディオ ファイルなど) のベクトル表現または埋め込みを保存するためによく使用されます。

マッピング オブジェクトは、オーディオ埋め込み、パス、タイムスタンプ、タイトル フィールドなど、このインデックスのマッピング プロパティを定義します。オーディオ埋め込みフィールドは、2048 次元に適したdense_vectorタイプとして指定され、検索操作中にベクトル間の距離を計算するために使用される方法を決定するコサイン類似度によってインデックスが付けられます。パス フィールドには、オーディオが再生されるパスが保存されます。2048 の埋め込みディメンションに対応するには、Elasticsearch 8.8.0 以降を使用する必要があることに注意してください。

次にスクリプトは、インデックスが Elasticsearch インスタンスに存在するかどうかを確認します。インデックスが存在しない場合は、指定された構成を使用して新しいインデックスが作成されます。このタイプのインデックス付け構成は、オーディオ検索などのシナリオで使用できます。オーディオ ファイルは、インデックス付けとその後の類似性に基づく検索のためにベクトル表現に変換されます。

ステップ 2: Elasticsearch に音声データを入力する

このステップの最後に、インデックスを読み取り、それに音声データを入力してデータ ストアを作成します。音声検索を続行するには、まずデータベースにデータを入力する必要があります。

2.1 取り込む音声データを選択する

多くの音声データセットには特定の目標があります。この例では、 Google Music LMページ、特にテキストとメロディック コンディショニング セクションで生成されたファイルを利用します。オーディオ ファイル *.wav を特定のディレクトリに置きます。この例では、/Users/liuxg/python/music-search/dataset を選択しました。

$ pwd
/Users/liuxg/python/music-search/dataset
$ ls
a-cappella-chorus.wav                        bella_ciao_tribal-drums-and-flute.wav
bella_ciao_a-cappella-chorus.wav             mozart_symphony25_electronic-synth-lead.wav
bella_ciao_electronic-synth-lead.wav         mozart_symphony25_guitar-solo.wav
bella_ciao_guitar-solo.wav                   mozart_symphony25_jazz-with-saxophone.wav
bella_ciao_humming.wav                       mozart_symphony25_opera-singer.wav
bella_ciao_jazz-with-saxophone.wav           mozart_symphony25_piano-solo.wav
bella_ciao_opera-singer.wav                  mozart_symphony25_prompt.wav
bella_ciao_piano-solo.wav                    mozart_symphony25_string-quartet.wav
bella_ciao_string-quartet.wav                mozart_symphony25_tribal-drums-and-flute.wav
import os

def list_audio_files(directory):
    # The list to store the names of .wav files
    audio_files = []

    # Check if the path exists
    if os.path.exists(directory):
        # Walk the directory
        for root, dirs, files in os.walk(directory):
            for file in files:
                # Check if the file is a .wav file
                if file.endswith('.wav'):
                    # Extract the filename from the path
                    filename = os.path.splitext(file)[0]
                    print(filename)

                    # Add the file to the list
                    audio_files.append(file)
    else:
        print(f"The directory '{directory}' does not exist.")

    # Return the list of .mp3 files
    return audio_files

# Use the function
audio_path = "/Users/liuxg/python/music-search/dataset"
audio_files = list_audio_files(audio_path)

このコードでは、ディレクトリを引数として受け取る list_audio_files という関数を定義しています。この関数の目的は、指定されたディレクトリとそのサブディレクトリを反復処理して、拡張子「.wav」を持つオーディオ ファイルを検索することです。.mp3 ファイルをサポートする必要がある場合は、この関数を変更する必要があります。

2.2 ベクトル検索埋め込みの力

このステップで魔法が起こります。ベクトル類似性検索は、特定のクエリに対する類似性に基づいてベクトルを保存、取得、検索するためのメカニズムであり、画像検索自然言語処理、レコメンダー システムなどのアプリケーションで一般的に使用されます。この概念は、深層学習の台頭と埋め込みを使用したデータの表現により、広く使用されています。基本的に、エンベディングは高次元データのベクトル表現です。

基本的な考え方は、データ項目 (画像、ドキュメント、ユーザー プロファイルなど) を高次元空間のベクトルとして表現することです。次に、コサイン類似度やユークリッド距離などの距離メトリックを使用してベクトル間の類似度を測定し、最も類似したベクトルを検索結果として返します。テキストの埋め込みは言語特徴を使用して抽出されますが、オーディオの埋め込みはスペクトログラムまたは他のオーディオ信号の特徴を使用して生成されることがよくあります。

テキストおよびオーディオ データの埋め込みを作成するプロセスには、特徴抽出または埋め込み技術を使用してデータをベクトルに変換し、ベクトル検索データベースでこれらのベクトルにインデックスを付けることが含まれます。

2.2.3 音声特徴の抽出

次のステップでは、オーディオ ファイルを分析し、意味のある特徴を抽出します。このステップは、機械学習モデルが音声データを理解し、そこから学習するのに役立つため、非常に重要です。

スペクトログラムからの特徴抽出のプロセスは、オーディオ信号処理の機械学習のコンテキストにおいて重要なステップです。スペクトログラムは、時間の経過に伴うオーディオ信号の周波数成分を視覚的に表現したものです。このケースで特定された特性には、次の 3 つの特定のタイプが含まれます。

  • メル周波数ケプストラム係数(MFCC): MFCC は、人間の聴覚知覚により密接に関連した方法でオーディオ信号のスペクトル特性を捕捉する係数です。
  • クロマチック機能: クロマチック機能は、音楽オクターブの 12 の異なるピッチ レベルを表し、音楽関連のタスクで特に役立ちます。
  • スペクトル コントラスト: スペクトル コントラストは、オーディオ信号内のさまざまな周波数帯域の知覚される明るさに焦点を当てます。

現実世界のテキスト ファイルに対するこれらの機能セットの有効性を分析および比較することで、研究者や実践者は、音声の分類や分析など、さまざまな音声ベースの機械学習アプリケーションへの適用可能性について洞察を得ることができます。

  • まず、音声ファイルを分析に適した形式に変換する必要があります。Python のlibrosaなどのライブラリはこの変換に役立ち、オーディオ ファイルをスペクトログラムに変換できます。
  • 次に、これらのスペクトログラムから特徴を抽出します。
  • 次に、これらの特徴を保存し、機械学習モデルへの入力として送信します。

ここでは、オーディオのラベル付けとサウンド イベントの検出のタスク用に設計された Python ライブラリであるpanns_inferenceを使用します。このライブラリで使用されるモデルは、音声パターン認識の手法である Large-Scale Pretrained Audio Neural Network の略である PANN でトレーニングされています。

!pip install -qU panns-inference librosa
from panns_inference import AudioTagging

# load the default model into the gpu.
model = AudioTagging(checkpoint_path=None, device='cuda') # change device to cpu if a gpu is not available

: PANNS 推論モデルのダウンロードには数分かかる場合があります。

import numpy as np
import librosa

# Function to normalize a vector. Normalizing a vector means adjusting the values measured in different scales to a common scale.
def normalize(v):
   # np.linalg.norm computes the vector's norm (magnitude). The norm is the total length of all vectors in a space.
   norm = np.linalg.norm(v)
   if norm == 0:
        return v

   # Return the normalized vector.
   return v / norm

# Function to get an embedding of an audio file. An embedding is a reduced-dimensionality representation of the file.
def get_embedding (audio_file):

  # Load the audio file using librosa's load function, which returns an audio time series and its corresponding sample rate.
  a, _ = librosa.load(audio_file, sr=44100)

  # Reshape the audio time series to have an extra dimension, which is required by the model's inference function.
  query_audio = a[None, :]

  # Perform inference on the reshaped audio using the model. This returns an embedding of the audio.
  _, emb = model.inference(query_audio)

  # Normalize the embedding. This scales the embedding to have a length (magnitude) of 1, while maintaining its direction.
  normalized_v = normalize(emb[0])

  # Return the normalized embedding required for dot_product elastic similarity dense vector
  return normalized_v

2.3 Elasticsearchに音声データを挿入する

これで、オーディオ データを Elasticsearch インデックスに挿入するために必要なものがすべて揃いました。

from datetime import datetime

#Storing Songs in Elasticsearch with Vector Embeddings:
def store_in_elasticsearch(song, embedding, path, index_name, genre, vec_field):
  body = {
      'audio-embedding' : embedding,
      'title': song,
      'timestamp': datetime.now(),
      'path' : path,
      'genre' : genre

  }

  es.index(index=index_name, document=body)
  print ("stored...",song, embedding, path, genre, index_name)

# Initialize a list genre for test
genre_lst = ['jazz', 'opera', 'piano','prompt', 'humming', 'string', 'capella', 'eletronic', 'guitar']

for filename in audio_files:
  audio_file = audio_path + "/" + filename

  emb = get_embedding(audio_file)

  song = filename.lower()

  # Compare if genre list exists inside the song
  genre = next((g for g in genre_lst if g in song), "generic")

  store_in_elasticsearch(song, emb, audio_file, index_name, genre, 2 )

2.4 Kibana での結果の視覚化

この時点で、オーディオ埋め込み高密度ベクトル フィールドに埋め込まれたオーディオ データを使用してインデックス付けを確認できます。Kibana® Dev Tools、特にコンソール機能は、Elasticsearch クラスターと対話するための強力なインターフェイスです。RESTful コマンドを Elasticsearch に直接送信し、結果を使いやすい形式で表示する方法を提供します。

この機能に関して注意すべき点の 1 つは、ここではオーディオ埋め込みフィールドを省略していることです。これはマッピングで定義されます。データ量が比較的大きいため、スペースを節約できます。

ステップ 3: 音楽で検索する

生成された埋め込みを使用してベクトル類似性検索を実行できるようになりました。システムに入力曲を入力すると、その曲をエンベディングに変換し、データベースで同様のエンベディングを検索し、同様の特徴を持つ曲を返します。

# Define a function to query audio vector in Elasticsearch
def query_audio_vector(es, emb, field_key, index_name):
    # Initialize the query structure
    # It's a bool filter query that checks if the field exists
    query = {
        "bool": {
            "filter": [{
                "exists": {
                    "field": field_key
                }
            }]
        }
    }

    # KNN search parameters
    # field is the name of the field to perform the search on
    # k is the number of nearest neighbors to find
    # num_candidates is the number of candidates to consider (more means slower but potentially more accurate results)
    # query_vector is the vector to find nearest neighbors for
    # boost is the multiplier for scores (higher means this match is considered more important)
    knn = {
        "field": field_key,
        "k": 2,
        "num_candidates": 100,
        "query_vector": emb,
        "boost": 100
    }

    # The fields to retrieve from the matching documents
    fields = ["title", "path", "genre", "body_content", "url"]

    # The name of the index to search
    index = index_name

    # Perform the search
    # index is the name of the index to search
    # query is the query to use to find matching documents
    # knn is the parameters for KNN search
    # fields is the fields to retrieve from the matching documents
    # size is the maximum number of matches to return
    # source is whether to include the source document in the results
    resp = es.search(index=index,
                     query=query,
                     knn=knn,
                     fields=fields,
                     size=5,
                     source=False)

    # Return the search results
    return resp

楽しい部分から始めましょう!

3.1 検索する音楽を選択する

以下のコードでは、データセットのオーディオ ディレクトリから音楽を直接選択し、オーディオ ミュージックを使用して jupyter で結果を再生します。

# Import necessary modules for audio display from IPython
from IPython.display import Audio, display

# Provide the URL of the audio file
my_audio = "/Users/liuxg/python/music-search/dataset/bella_ciao_humming.wav"

# Display the audio file in the notebook
Audio(my_audio)

再生」ボタンをクリックすると音楽を再生できます。

3.2 音楽の検索

ここで、コードを実行して、Elasticsearch で音楽 my_audio を検索してみましょう。検索には音声ファイルのみを使用します。

audio_file = "/Users/liuxg/python/music-search/dataset/bella_ciao_humming.wav"
# Generate the embedding vector from the provided audio file
# 'get_embedding' is a function that presumably converts the audio file into a numerical vector
emb = get_embedding(audio_file)

# Query the Elasticsearch instance 'es' with the embedding vector 'emb', field key 'audio-embedding',
# and index name 'my-audio-index'
# 'query_audio_vector' is a function that performs a search in Elasticsearch using a vector embedding.
# 'tolist()' method is used to convert numpy array to python list if 'emb' is a numpy array.
resp = query_audio_vector (es, emb.tolist(), "audio-embedding","my-audio-index")
resp['hits']

Elasticsearch は、ヒット曲に類似したすべての音楽を返します。

NUM_MUSIC = 5  # example value

for i in range(NUM_MUSIC):
    path = resp['hits']['hits'][i]['fields']['path'][0]
    print(path)

結果の再生に役立つコードをいくつか示します。

Audio("/Users/liuxg/python/music-search/dataset/bella_ciao_opera-singer.wav")

「再生」ボタンをクリックすると、結果を確認できます。

3.3 分析結果

では、このコードを実稼働環境にデプロイして、アプリを販売できますか? いいえ、確率モデルとして、確率聴覚ニューラル ネットワーク (PANN) およびその他の機械学習モデルを現実世界のシナリオに効果的に適用するには、データ量の増加と追加の微調整が必​​要です。

これは、18 曲のサンプルに関連付けられた埋め込み可視化グラフによって明確に示されており、kNN メソッドの誤検知につながる可能性があります。しかし、将来のデータ エンジニアは依然として、クエリに最適なモデルをハムによって特定するという重要な課題に直面しています。これは機械学習と聴覚認知の興味深い交差点を表しており、厳密な研究と革新的な問題解決が必要です。

3.4 UI を使用して POC を改善する (オプション)

少し変更した後、コード全体をコピーしてStreamlitに貼り付けました。Streamlit は、データ サイエンスおよび機械学習プロジェクト用のインタラクティブな Web アプリケーションの作成プロセスを簡素化する Python ライブラリです。これにより、Web 開発に関する広範な知識がなくても、初心者でもデータ スクリプトを共有可能な Web アプリケーションに簡単に変換できます。

その結果がこのアプリです:

Windows によるオーディオ検索の未来

Elasticsearch ベクターを使用して、Python で音楽検索システムを実装することに成功しました。これはオーディオ検索分野の出発点であり、このアーキテクチャ アプローチを活用することで、より革新的なコンセプトを生み出す可能性があります。モデルを変更することで、さまざまなアプリケーションを開発できます。また、推論を Elasticsearch に移植すると、パフォーマンスが向上する可能性があります。詳細については、Elastic の機械学習ページにアクセスしてください。

これは、テキストを超えたさまざまな検索アプリケーションに対するこの技術の大きな可能性と適応性を示しています。

すべてのコードは、GitHubの単一ファイル elastic-music_search.ipynb にあります。

翻訳:音楽による検索: オーディオ情報の検索にベクトル検索を活用 | エラスティックブログ

おすすめ

転載: blog.csdn.net/UbuntuTouch/article/details/132576443