🧹

AIで商品名をクレンジングする、Llama2や、Google Cloud Gemini-proとVertex AIを活用した商品名の最適化

2024/04/04に公開

ECサイト上の商品名は、「送料無料」や「ポイント5倍」、「母の日」といった宣伝文句が付加され、本来の商品名が分かりづらくなっています。
これらの不要なキーワードは、自然言語モデルを作成する際の妨げとなり、精度を上がりづらくしている一つの要因となっています。

そこで、本記事では、これら不要なキーワードを効率的に除去する方法について、具体的な手法と事例を紹介します。
データクレンジングの例、LLMオープンモデルを活用したアプローチ、さらにはGoogle Cloud PlatformのVertex AIやGemini-proを用いた解決策について触れます。

3行まとめ

・商品名から頻出単語のリストを作成し、商品名から不要なキーワードを検索して除去する
・オープンモデルでプロンプトを実行し除去する
・Google Cloud Platform のVertex AI、Gemini-proで除去する

頻出単語の除去

まずは、頻出単語の除去についてです。
商品名により多く頻出する単語は、商品名ではない説明するためのものであると想定します。

そこで商品名を単語ごとに区切り、頻出単語のリストを作成する必要があります。
頻出単語のリストを作成し降順で並び替え、多い順から取り除きたいキーワードを選出します。
商品名の半角スペースごとに単語を区切り、それを一つの単語と判定しています。
そのほかにはMecabなど形態素解析を行い、単語として分別します。
これは全体の処理対象数が多いと計算時間が長くなるため、
上述した半角スペースで区切る方法を採用しています。

※事前に商品一覧を管理しているテーブルがあると仮定します。※商品マスタなど
例:ec_site_item_listという商品を管理しているテーブルがあると想定

snowflakeのクエリ例

with stop_word_list as (
select distinct item_name from ec_site_item_list
)
select
    SPLIT_PART(item_name, ' ', 0) as stop_word,
    count(stop_word) as stop_word_count
    from stop_word_list
group by stop_word
order by stop_word_count desc;
stop_word stop_word_count
送料無料 6666
中古 5555
ふるさと納税 4444
iPhone 3333
母の日 2222
ポイント10倍 1111
... ...

上記のようなリストが得られます。
これを、stopword_listという名前でテーブルとして保存しておきます。

続いて、商品名から除去します。
pythonでsnowparkライブラリを利用して
クエリを実行してテーブルに戻しています。

import snowflake.snowpark as snowpark
from snowflake.snowpark.functions import col
from snowflake.snowpark.types import StringType, StructType, StructField
import pandas as pd
import sys
import io
import re

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

def main(session: snowpark.Session): 
    df = session.table('stopword_list').select(col('stop_word'), col('stop_word_count')).sort('stop_word_count', ascending=False).limit(500).collect()
    sql = """select distinct item_name from ec_site_item_list"""
    df2 = session.sql(sql).to_pandas()
    stop_list = []
    item_name = []
    normalized_item_name = []

    for row in df:
        stop_list.append(row['STOP_WORD'])

    stop_word = '|'.join(stop_list)

    for idx in range(df2.shape[0]):
        item_name.append(df2.ITEM_NAME.iloc[idx])
        normalized_item_name.append(re.sub(stop_word, '', df2.ITEM_NAME.iloc[idx]))

    schema = StructType([
        StructField("ITEM_NAME", StringType()), 
        StructField("NORMALIZED_ITEM_NAME", StringType())
        ])

    save_df = pd.DataFrame({"ITEM_NAME": item_name, "NORMALIZED_ITEM_NAME": normalized_item_name})
    df_insert = session.create_dataframe(save_df, schema=schema)
    df_insert.write.mode("append").save_as_table("ec_site_item_name_list_results")

    return f"SUCCESS"

例として一つの結果を見てみます。

before

母の日 【ポイント10倍】プレゼント 花 ギフト 鉢植え アジサイ アレンジ アレンジメント スタンディングブーケ 花束 カーネーション 楽天総合1位 送料無料 メッセージカード付き イベントギフトA

after

花 ギフト 鉢植え アジサイ アレンジ アレンジメント スタンディングブーケ 花束 カーネーション メッセージカード付き イベントギフトA

母の日、ポイント10倍、送料無料などの商品名としては不要と思われるキーワードは消えましたが
ギフト、アレンジ、花束、メッセージカード付き、イベントギフトAなどは残ってしまいました。
この例では、「アレンジメント スタンディングブーケ 花束 カーネーション」を期待します。
このように、実際に処理を実行し他にも除去する必要がありそうなキーワードはstopword_listに追加します。
先のクエリでは上から500の単語を消していますが、最終的には人の目で判断し、消したい単語のリストを作成するのが良いでしょう。

メリット・デメリット
実装はしやすいですが、ストップワードリストの運用の必要性がある点、
消えずに残るワードがある点が気になります。ある程度人の力は必要になる手法と考えます。

オープンモデル (Llama2)

次に、Meta社が提供しているオープンモデルであるLlama2を利用します。
Llama2は日本語対応しておりませんので、日本語対応をしている(ELYZA-japanese-Llama-2-7b)を
利用します。
ChatGPTでも同様なことはできますが、費用面、速度面の兼ね合いでオープンモデルを採用することにしました。

ELYZA-japanese-Llama-2-7b-fast-instruct

このモデルは非常に大きいモデルとなっており、比較的大きめのインスタンスで動作させました。

スペック
n1-standard-16(16 vCPU、8 コア、60 GB メモリ)
NVIDIA T4 x 1

N/A   77C    P0              69W /  70W |  13983MiB / 15360MiB |     99%

タスク実行時はGPUのメモリをかなり占有します。
A100 40GBや、A100 80GBが利用できる環境だとなお良いと思います。

処理概要
haggingfaceのtransformers用のサンプルコードを修正します。
GCSから商品リストを取得
プロンプト文を作成
tokenizer.decodeでプロンプトを実行し、結果を取得
リストに格納する
結果をGCSにputする

といった内容にします。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

from io import BytesIO
from google.cloud import storage
import pandas as pd

B_INST, E_INST = "[INST]", "[/INST]"
B_SYS, E_SYS = "<<SYS>>\n", "\n<</SYS>>\n\n"
DEFAULT_SYSTEM_PROMPT = "あなたはECサイトを運営していて商品にとても詳しい人物です。"

BUCKET_NAME = "your-project-name"
file_name = "item_name_list.csv"

storage_client = storage.Client()
bucket = storage_client.get_bucket(BUCKET_NAME)
blob = bucket.blob(file_name)
bytes_object = blob.download_as_bytes()

stream = BytesIO(bytes_object)
df = pd.read_csv(stream, usecols=['ITEM_NAME'])

model_name = "elyza/ELYZA-japanese-Llama-2-7b-fast-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype="auto")

if torch.cuda.is_available():
    model = model.to("cuda")

def do_prompt(item_name):
    text = f"このタスクでは、商品名から不要なワードを除去する必要があります。不要なワードとは、プロモーションに関連する言葉 や、商品名には不適切な追加情報(例:「送料無料」、「特価」、【中古】、【ポイント10倍】など)を指します。タスク:以下の商品名から 不要ワードを除去してください。返却時は、除去後の商品名を1つだけ返却してください。応答文も不要です。 商品名: {item_name}  "
    prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} ".format(
        bos_token=tokenizer.bos_token,
        b_inst=B_INST,
        system=f"{B_SYS}{DEFAULT_SYSTEM_PROMPT}{E_SYS}",
        prompt=text,
        e_inst=E_INST,
    )

    with torch.no_grad():
        token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")

        output_ids = model.generate(
            token_ids.to(model.device),
            max_new_tokens=256,
            pad_token_id=tokenizer.pad_token_id,
            eos_token_id=tokenizer.eos_token_id,
        )
    
    output = tokenizer.decode(output_ids.tolist()[0][token_ids.size(1) :], skip_special_tokens=True)
    return output

prompt_result = []
for i in range(df.shape[0]): 
    prompt_result.append(do_prompt(df.ITEM_NAME.iloc[i]))
    
df['prompt_result'] = prompt_result
df.to_csv('prompt_result.csv', index=False)

file_name = 'prompt_result.csv'
blob = bucket.blob(file_name)
blob.upload_from_filename(file_name)
print("done.")

DEFAULT_SYSTEM_PROMPT

これは、プロンプトをする際のシステムの値で役割を指定します。
この指定により精度が上がる可能性があります。

text

ここにプロンプト文を作成します。今回は商品名から不要なキーワードという意味を説明し除去してもらうことを期待します。

model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype="auto")

ここの準備に時間がかかる場合があります。気長に待ちます。

blob.upload_from_filename(file_name)

成果物をGCSのバケットに送信します。

結果

1/9~全品P10倍確定 タイヤチェーン スノーソックス 布製 非金属 タイヤ滑り止め 車 雪道 スノーチェーン 非金属 ア… タイヤチェーン スノーソックス 布製 非金属 タイヤ滑り止め
12月4日20:00-11日01:59楽天ス-パ-SALEレゴ 互換 ブロック 500ピース クラシックブロック 男の子 500ピース クリエイ… レゴ クラシックブロック
(着後レビューで500円OFFクーポン)【ネコポス15枚までOK】( mou mou ペーパー エアー フレッシュナー ウール ) ムーム… Mou Mou ペーパー エアー フレッシュナー ウール
1/30はP5倍★ ジビエ 【 鹿 焼肉セット 】<合計200g(1〜2人前)>ジビエの宝庫石川県で狩猟された 天然ジビエ ジビエ… 1/30はP5倍★ 鹿焼肉セット
20%OFFクーポンあり【公式】【2022 ホリデーコレクション】ホリデージェジュパフュームドハンドクリーム 3種セット… 20%OFFクーポンあり*ホリデージェジュパフュームドハンドクリーム 3種セット

日付や、ポイント表記は消えましたが、P5倍は残っています。
プロンプトの例に追加すると消えますが、追加すると入力長が大きくなるデメリットもあります。

処理時間としてはモデルの準備時間を除けば、1,000件あたり41.26秒でした。

メリット・デメリット
処理時間は3つの中で最も早いですが、返却されるテキストに応答文が入る場合があります
また、予期せぬ文章が入ってくるのも考慮する必要があり、精度向上は必要です。

Gemini-pro

Gemini-proはGoogle DeepMindが開発したマルチモーダルな大規模言語モデルです
BigQueryにモデルをデプロイし、外部関数として利用することができます。

まずは次のシェルスクリプトを作成し、実行します。
コネクションを作成します。

bq mk

#!/bin/bash
PROJECT_ID=your-project-name
REGION=us
CONNECTION_ID=sample-connection-gemini-pro
bq mk --connection --location=$REGION --project_id=$PROJECT_ID \
   --connection_type=CLOUD_RESOURCE $CONNECTION_ID

次にモデルをデプロイします。

CREATE MODEL statement

CREATE OR REPLACE MODEL
`your-project-name.text_cleansing.model_cloud_ai_gemini_pro` REMOTE
WITH CONNECTION `projects/your-project-name/locations/us/connections/sample-connection-gemini-pro` OPTIONS(endpoint = 'gemini-pro');

デプロイしたモデルを利用してクレンジングのクエリを実行します。

SELECT
*
FROM
ML.GENERATE_TEXT( MODEL`your-project-name.text_cleansing.model_cloud_ai_gemini_pro`,
(
SELECT
*,
CONCAT("概要:このタスクでは、商品名から不要なワードを除去する必要があります。不要なワードとは、プロモーションに関連する言葉 や、商品名には不適切な追加情報(例:「送料無料」、「特価」、【中古】など)を指します。以下に具体的な例を示します。例:入力: \"新鮮なトマト 500g 特 価\" 除去後: \"新鮮なトマト 500g\"入力: \"高品質な日本製ノートPC 最新モデル 送料無料\" 除去後: \"高品質な日本製ノートPC 最新モデル\" 入力: \"エコフレンドリー再生紙 A4 500枚セット 大量購入割引\" 除去後: \"エコフレンドリー再生紙 A4 500枚セット\"タスク:以下の商品名から 不要ワードを除去してください。入力: \"", item_name, "\" 除去後:") AS prompt
FROM
`your-project-name.text_cleansing.sample_item_name_list_data`), STRUCT(0.1 AS temperature,
3 AS top_k,
TRUE AS flatten_json_output));

処理時間は、1,000件で、1分30秒ほどかかりました。料金は入力長、出力長あたりで変動しますが
およそ2円弱です。

usリージョンでは1分あたりのAPI実行数は初期で300となっています。
より多くのデータを処理したい場合は申請をして上限を解放してもらうと良いでしょう。

API 使用の上限設定

メリット・デメリット
実行APIに対する費用面は安く抑えられますが、処理時間が長い印象があります。
大量データを一気に処理させたい場合は、上限設定を上げる必要があります。

さいごに

商品名から不要なキーワードを除去するタスクではいくつか手法があります。

パターンマッチングの例では、事前に不要キーワードの選定作業が必要で、運用としてキーワードの追加や見直しの対応が発生します。

オープンモデルの例では、使用するインスタンスの稼働時間によって金額が発生します。
※今回利用したインスタンスは1日あたり7,000円弱かかります。
また、プロンプトの結果によっては適切に商品名が返ってこない可能性が見込まれるため、
結果の文字列の加工が別途必要になる場合があります。

最後のGemini-proを外部関数としてBigQueryから呼び出す方法については、APIの使用量や
処理時間、BigQueryのクエリ実行時間が長くなる場合を十分考慮し検討する必要があります。

進化するAI、Sakana AIでの検証結果を記事にいたしました。
https://zenn.dev/minedia/articles/fbfd7c0284c01a

株式会社マインディア テックブログ

Discussion