[faiss] データセットから似ている顔を検索する

    はじめに

    モデルの学習に使われていない顔データセットを用い、用意した写真と似ている顔を検索します。

    今回の記事では、顔写真を1枚用意し、faissを用いて直接データセットから検索します。

    顔データセット

    find . -maxdepth 2 -type f -name *.png | wc -l
    57637

    約5万7千枚の顔写真が含まれるデータセットを用意しました。

    顔写真

    実装

    import os
    import time
    import faiss
    import numpy as np
    import sys
    # FACE01ライブラリのインポート
    sys.path.insert(1, '/home/terms/bin/FACE01_IOT_dev')
    from face01lib.api import Dlib_api
    
    api = Dlib_api()
    
    # 処理開始時刻を記録
    start_time = time.time()
    
    # FAISSインデックスの設定
    dimension = 512  # ベクトルの次元数
    nlist = 100  # クラスタ数
    # 量子化器を作成(内積を使用)
    quantizer = faiss.IndexFlatIP(dimension)
    # IVFフラットインデックスを作成
    index = faiss.IndexIVFFlat(quantizer, dimension, nlist, faiss.METRIC_INNER_PRODUCT)
    
    # データのルートディレクトリ
    root_dir = "/media/terms/2TB_Movie/face_data_backup/data"
    # カレントディレクトリを変更
    os.chdir(root_dir)
    
    # 顔写真をロード
    face_image = api.load_image_file("/home/terms/ドキュメント/find_similar_faces/assets/woman.png")
    face_location = api.face_locations(face_image, mode="cnn")
    face_encoding = api.face_encodings(
        deep_learning_model=1,
        resized_frame=face_image,
        face_location_list=face_location,
    )
    face_encoding = np.array(face_encoding[0][0]).reshape(1, 512)
    
    # サブディレクトリのリストを作成
    sub_dir_path_list = [
        os.path.join(root_dir, sub_dir)
        for sub_dir in os.listdir(root_dir)
        if os.path.isdir(os.path.join(root_dir, sub_dir))
    ]
    
    # データ格納用のリスト
    all_model_data = []
    all_name_list = []
    all_dir_list = []  # ディレクトリ情報も保存
    
    # 各サブディレクトリからデータを読み込む
    for dir in sub_dir_path_list:
        npz_file = os.path.join(dir, "npKnown.npz")
        with np.load(npz_file) as data:
            model_data = data['efficientnetv2_arcface']
            name_list = data['name']
            # データの形状を変更し、L2正規化を行う
            model_data = model_data.reshape((model_data.shape[0], -1))
            faiss.normalize_L2(model_data)
            # データをリストに追加
            all_model_data.append(model_data)
            all_name_list.extend(name_list)
            all_dir_list.extend([dir] * len(name_list))
    
    # データをnumpy配列に変換
    all_model_data = np.vstack(all_model_data)
    # # 量子化器を訓練し、データを追加(修正箇所)
    index = faiss.IndexIVFFlat(quantizer, dimension, nlist, faiss.METRIC_INNER_PRODUCT)  # 追加; IVFインデックスを作成
    
    # 量子化器を訓練し、データを追加
    index.train(all_model_data)
    index.add(all_model_data)
    
    # 類似度を検索
    k = 10
    D, I = index.search(face_encoding, k)
    
    # 類似度が高い順に結果を表示
    for i in range(len(I[0])):
        index_i = I[0][i]
        distance_i = D[0][i]
        # コサイン類似度に変換(オプション)
        length_query = np.linalg.norm(face_encoding)
        length_result = np.linalg.norm(all_model_data[index_i])
        cos_similarity = distance_i / (length_query * length_result)
        name_i = all_name_list[index_i]
        dir_i = all_dir_list[index_i]
        print(f"類似度: {cos_similarity:.4f}, 名前: {name_i}, ディレクトリ: {dir_i}")
    
    # 処理時間を計算して出力
    end_time = time.time()
    elapsed_time = end_time - start_time
    minutes, seconds = divmod(elapsed_time, 60)
    print(f"処理時間: {int(minutes)}分 {seconds:.2f}秒")

    実行結果

    類似度: 0.4285, 名前: 大谷直子_vixw.jpg.png_align_resize_default.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/大谷直子
    類似度: 0.3852, 名前: 岸本加世子_QIp1.jpg_default.png.png_0.png_0_align_resize.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/岸本加世子
    類似度: 0.3835, 名前: 岸本加世子_9iBC.jpg.png_align_resize_default.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/岸本加世子
    類似度: 0.3834, 名前: 岸本加世子_vByP.jpg.png_align_resize_default.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/岸本加世子
    類似度: 0.3480, 名前: 宮崎美子_cdzk.jpg.png.png_0.png_0_align_resize.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/宮崎美子
    類似度: 0.3460, 名前: 愛華みれ_rU0K.jpg..png_align_resize_default.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/愛華みれ
    類似度: 0.3454, 名前: 愛華みれ_Trha.jpg..png_align_resize_default.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/愛華みれ
    類似度: 0.3394, 名前: 宮崎美子_l4MT.jpg.png.png_0.png_0_align_resize.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/宮崎美子
    類似度: 0.3333, 名前: 岸本加世子_qjSm.jpg_default.png.png_0.png_0_align_resize.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/岸本加世子
    類似度: 0.3306, 名前: 宮崎美子_neJS.jpg.png.png.png_0.png_0_align_resize.png, ディレクトリ: /media/user/2TB_Movie/face_data_backup/data/宮崎美子
    処理時間: 0分 41.79秒

    大谷直子さん。
    コサイン類似度は0.4285でした。Maxが1.0ですので、大して似ていないですが、データセットの中ではもっとも類似度が大きく出ました。

    まとめ

    実は今回検索対象とした女性の顔は、生成AIによって生成されたものです。
    ですので、検索の結果、ヒットしなかったのはむしろ成功と言えます。

    ただしもととなったLoRA(Low-Rank Adaptation)はaverage face of Japanese MILFs 日本人熟女平均顔です。

    LoRAに実際の人物写真が使われた場合、その特徴を学習します。そのような場合、生成AIではどんな画像でも作れてしまいますから、マズい画像も出回ってしまうわけです。

    今回の実験では、そのへんの調査のための下地作りといえます。

    以上です。ありがとうございました。