背景
記事執筆現在スクールフォトの業界にいて、販売する写真の売上を評価するにあたり、1イベントあたりの写真のバリエーションが購買決定要因になりうるのか検討するのになにか定量的な評価はできないものかと思い、以下の方法を思いつきました。本題的には、結論から言うと写真のバリエーションと売上には相関関係はなかったことを先にお伝えしておきます。
実行環境
- macOS Ventura Ver.13.6.1
- Python 3.12.2
- Pandas 2.2.2
評価方法の流れ
- inputフォルダ直下の写真(ファイル)一覧を取得する。
- 計算結果を格納する用の入れ物(Pandas DataFrame)を用意する。
- 事前学習済みの画像キャプション生成機械学習モデルを利用して、写真のキャプションを生成する。
- 2画像の組み合わせについて、キャプションの類似度を計測する。
- キャプションの類似度を総合評価する。
以上の流れで進めていきます。
コード
inputフォルダ直下の写真(ファイル)一覧を取得する。
import os
dir_path = "input"
files = os.listdir(dir_path)
files = sorted(files)
#画像ファイルの拡張子リスト
img_exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
#読み込むファイルを上の拡張子リストにあるものに厳選する
files = [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f)) and os.path.splitext(f)[1].lower() in img_exts]
実行環境がmacなので、macOSが自動的に生成する隠しファイルで、ディレクトリの表示設定(アイコンの位置、表示形式など)を保存するために使用される「.DS_Store」ファイルが紛れ込まないように弾いておきます。
Jupyter Notebookで実行している場合には、さらに「.ipynb_checkpoints」ファイルが紛れ込む可能性がありますのでご注意ください。
計算結果を格納する用の入れ物(Pandas DataFrame)を用意する。
import pandas as pd
columns = files
b = "caption"
columns = columns + [b]
index_list = files
df = pd.DataFrame(columns = columns,index = index_list)
九九の表を思い浮かべてください。交差する升にキャプションの類似度を、一番右にキャプション(str)を格納しておく列を作成するイメージです。
事前学習済の画像キャプション生成機械学習モデルを利用して、写真のキャプションを生成する。
from transformers import BlipProcessor, BlipForConditionalGeneration
from PIL import Image
import requests
import torch
import os
#プロセッサとモデルの読み込み
processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base", use_fast=True)
model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base")
for f in files:
image_path = os.path.join(dir_path, f)
image = Image.open(image_path).convert('RGB')
#キャプション生成
inputs = processor(images=image, return_tensors="pt")
out = model.generate(**inputs)
caption = processor.decode(out[0], skip_special_tokens=True)
df.at[f, 'caption'] = caption
#以下のprintはお好みで。
print("Generated Caption:",str(f),caption)
キャプションの生成には結構時間が掛かるので、tqdmをforループに組み込んでプログレスバーを表示させると待ち時間のストレスを緩和できます。
2画像の組み合わせについて、キャプションの類似度を計測する。
import Levenshtein
captions = []
[captions.append(df.loc[i,'caption']) for i in files]
for i in tqdm(range(len(files))):
for j in range(i+1,len(files)):
distance = Levenshtein.distance(captions[i],captions[j])
similarity = 1 - distance / max(len(captions[i]),len(captions[j]))
df.iat[i, j] = similarity
キャプションの類似度を総合評価する。
n = len(files)
full = n * (n + 1) / 2 - n
col_sum = df.sum(axis=0)
sum = 0
for i in range(len(col_sum)-1):
sum += col_sum.iloc[i]
sum = sum - n
#スコアを正規化
p = (full - sum) / full * 100
print(round(p,2))
similarityは1.0に近いほどcaptionの文字列が類似していることを示すので、バリエーションはその逆であるところがミソです。写真が類似していなければsimilarityは0に近づくので、そうすると最終的なスコアであるpが大きな値を取ります。pは1~100までの値で正規化されるので、ことなる写真セット同士の比較をするのに有用です。