【ナレッジ編】音楽業界の課題解決に向けてLLMを活用した技術検証に取り組んだ話

株式会社レコチョク 新卒3年目の早坂と言います。
LLMを活用したシステム化の技術検証を担当しています。

本記事では、音楽業界の課題を解決するために、株式会社レコチョクが大規模言語モデル（LLM）を活用したシステム開発の取り組みで得たナレッジを書きます。具体的には、メタデータの整備と楽曲のタイトル情報変換作業のシステム化に焦点を当て、技術検証から得られたものを対象としています。

# はじめに
ナレッジを得た背景にある取り組みについては、[こちら](https://qiita.com/k-hayasaka/items/3a30ab1d2d81c3273048)をご覧ください。背景がなくても開発のナレッジなので読み進めることはできますが、背景を知っている前提で説明している文章が随所ありますので、事前にご覧いただくとスムーズに読み進められるかと思います。


## 対象読者
- システム開発者: 特にLLMを活用した開発に関心がある方
- 音楽業界の関係者: 業界特有の課題に対する新しい解決策に興味がある方
- AIと機械学習に興味がある方: LLMの活用事例を知りたい方

事前に以下の知識があると、記事の内容をより深く理解できますが、必須ではありません。

- 基本的なPythonのプログラミング知識
- 機械学習、特に自然言語処理（NLP）に関する基礎知識

私は機械学習に関して専門的な研究を行っているわけではありませんが、半年間Azure Open AIのAPIを利用し、システム開発を行った経験をもとにしたナレッジを記事にします。
特にLLMを活用したシステムを実装中の方、もしくはこれから実装を予定している方の一助となれば幸いです。

## 環境
### モデル
`gpt-4-32k(0613)`

### パラメータ
```python
TIME_OUT=60
TEMPERATURE=0.7
MAX_TOKENS=800
FREQUENCY_PENALTY=0
PRESENCE_PENALTY=0
TOP_P=0.95
N=1
```
### 言語(バージョン)
Python (3.9.6)

# Azure Open AIの活用ナレッジ
## 扱うデータによってはコンテンツフィルターの制限緩和が必要
Azure Open AIには、プロンプトと出力される候補の両方で有害な可能性があるコンテンツを検出し、制限する機能があります。多言語にこのフィルターは対応しています。

> 憎悪、性的、暴力、自傷行為のカテゴリに対するコンテンツ フィルター処理モデルは、英語、ドイツ語、日本語、スペイン語、フランス語、イタリア語、ポルトガル語、中国語でトレーニングおよびテストされています。 サービスはその他の多くの言語でも利用いただけます (品質は言語によって異なる場合があります)。 いずれの場合も、独自のテストを実行して、アプリケーションに対して動作することを確認する必要があります。

[URL](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cpython-new)

なお、危険の分類カテゴリですが、以下があります。
- ヘイトと公平性
- 性的
- 暴力的
- 自傷行為
- 脱獄のリスク(有害なコンテンツを出力させるプロンプトを使う脱獄攻撃のリスク)
- テキスト用保護済み素材(著作権管理されている楽曲の歌詞など)
- コード用保護済み素材(利用条件のあるソースコードなど)

また、コンテンツフィルターには階級が存在します。

![スクリーンショット 2024-03-18 19.40.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269908/a31303f4-56e4-d04d-1270-3eb93eb561ef.png)

[参考](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cpython)

コンテンツフィルターされ、性的なコンテンツ(評価は中)に分類されたレスポンス例が以下です。
フィルタリングされた場合、欲しいアウトプットの情報(本来は以下Jsonに`content`という項目あります)がなくなります。
```json
"choices": [
        {
            "index": 0,
            "finish_reason": "content_filter",
            "message": {
                "role": "assistant"
            },
            "content_filter_results": {
                "hate": {
                    "filtered": false,
                    "severity": "safe"
                },
                "self_harm": {
                    "filtered": false,
                    "severity": "safe"
                },
                "sexual": {
                    "filtered": true,
                    "severity": "medium"
                },
                "violence": {
                    "filtered": false,
                    "severity": "safe"
                }
            }
        }
    ]
```
ちなみに、私が利用していてコンテンツフィルターで制限されたデータは、明らかに制限されると思われるものではありませんでした。(楽曲のタイトル情報を記事に載せることができなかったので、具体例は控えます。。)

前述したように、入力情報と出力の候補で制限対象がある場合なので、出力候補で制限されたと思います。ただ、LLMの出力はユーザーが完全にコントロールできるわけではないので、フィルタリングされるか否かの予測は難しいと思います。

### 緩和申請
コンテンツフィルターを解消するためには、Microsoftに申請を行う必要があります。
[申請フォームURL](https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xUMlBQNkZMR0lFRldORTdVQzQ0TEI5Q1ExOSQlQCN0PWcu)

この申請を行い、受理されるとAzure Open AI Studioの`Content filters`からコンテンツフィルターの制限を変更できるようになります。あくまで変更なので、引き続き申請後もコンテンツフィルターを適用することができます。ちなみに、私が申請した場合は1週間ほどで受理の通知が来ました。

# 開発ナレッジ
## 実装
### 出力チェックとリトライ機構

システムが期待する出力を確実に得るためには、出力のチェックと、条件に合わない場合のリトライ機構を備えることが重要です。これにより、エラーや予期せぬ応答が発生した際にも、システムが自動的に対応し、正確な結果を出力することができます。

例えば、生成させる文章の形式や使用する文字列が決まっている場合は、
リクエストごとに入力文字列から正規表現を作成し、チェックするようにしていました。
もしこのチェックが通らなかった場合はLLMの回答には意図しない文字列が混入していることになるので、リトライさせていました。
これにより、LLMの出力ゆれを抑えることができ、最終的なシステムのアウトプットが期待通りとなることが増えました。明示的な数値は示すことができませんが、試していて数%ほどは向上し、プロンプトを改善するよりも効果がありました。

#### 出力チェック
LLMの出力をチェックする機構についてですが、以下のイメージ実装していました。
![スクリーンショット 2024-03-19 10.19.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2269908/455bb434-9ee0-137f-68de-fb9e3998e414.png)


確認対象は3つです。
- システムメッセージ
- 出力されうるノイズ
- 出力形式

**システムメッセージ**
システムメッセージは、ユーザーが欲しい情報以外にLLMが入れてくる文章のことです。
例えば、`回答は~です`や`〇〇を考慮した結果、~が回答になります`などが該当します。私が実装した場合、LLMには回答のみを出力させたかったので、こういったシステムメッセージは挿入しないようにプロンプトで指示をしました。以下は例です。
```
あなたの補足情報は一切いりません。絶対に入れないでください。
```

**出力されうるノイズ**

`xx`が欲しい情報だった場合、`回答:xx`や`"xx"`などが該当します。これはシステムメッセージのような文章ではないものの、LLMはユーザーがわかりやすくなるようにと親切心でいらない文字列(ノイズ)もセットで出力してきます。このノイズに関しては、同じプロンプトで10回ほど検証し、どのようなノイズのパターンがあるのかを検証したうえで、ルールを策定して削除する機能を実装するようにしていました。

**出力形式**

プロンプトで`[xx]`と出力フォーマットを指定していた場合、そのとおりになっているかを確認します。
これは正規表現で実現できる範囲なので、システムメッセージ、出力されうるノイズへの対策を行った上で正規表現によるチェックを行っていました。

全体としては、以下のような実装イメージです。
各メソッドの詳細は記載していませんが、1つの例として見ていただければと思います。

```python
input = "test"

# 補足情報(システムメッセージ)の混入をプロンプトで抑制 
prompt = """
// 指示内容は省略
// ルールは省略

あなたの補足情報は一切いりません。絶対に入れないでください。
"""
def request_llm()
    # 最大3回リクエスト
    for i in range(3):
        # LLMへのリクエスト
        response = llm.request_task(input, prompt)
        # ノイズの削除
        output = remove_llm_noise(response)

        # 正規表現による形式チェック
        if check_output_format(output):
            return output
            
    # 3回リクエストしても形式チェックが通らなかったので、エラー
    return 'FormatError'
```

## デバック

### AIのプロンプトに回答理由を問う内容を書く

AIシステムをデバッグする際には、AIがどのような理由で特定の回答をしたのかを問うことが有効です。これにより、AIの推論プロセスを理解し、必要に応じて調整することができます。

例えば、以下のようなプロンプトです。
```
// 指示内容は省略

回答を出した後、その回答となった理由を詳細に教えてください。
```

事前にテストをしておくと、LLMがどういった思考で回答を導いたのかがわかるので、プロンプトで制限を加えたり、回答を導くうえで有効そうな観点を見つけて明示的に示したりと応用が聞くので便利です。なお、LLMは回答の理由を考えるので、レスポンス速度は非常に遅くなります。

例えば、私が実施した検証では楽曲のタイトルデータを扱いましたが、タイトルデータの表記を変換する際にLLMは私が出した指示にはない見栄えを考慮していました。見栄えは考慮にいれる必要はなかったので、明示的に`出力するタイトル情報の見やすさは考慮しなくて良いです`というプロンプトを追記しました。

### APIのテストはチャットではなくAPIで実施する

私の観測範囲なので、使用する環境によって異なるかもしれませんが、`Chat playground`とAPIを比較したとき、同じパラメータ、プロンプトでもAPIの方が出力のゆれが大きく感じました。
なので、APIをシステムに組み込む場合、プロンプトの検証は実際のAPIリクエストを使って行うと良いです。これにより、実際の運用環境に近い条件でシステムの動作を確認し、問題を早期に発見することができます。

## プロンプト
### 回答できない場合の出力を与える

LLMが指示に答えられない場合のための出力を用意しておくと、システムがユーザーの期待に応えられない状況でも意図しない出力を抑えることができます。

例えば、以下のプロンプトになります。
```
// 指示内容は省略
ルール---
・もし何らかの理由で答えが出力できない場合、"Error"と回答してください。
---
```
回答できない場合の出力を定義していないと、"必要な情報が提供されてませんので、回答できません"といった旨の内容がされることがあります。指示通りにErrorと答えてくれることは保証できませんが、意図しない出力を抑えるのには有効です。

ちなみに、ルールを`---`で囲んでいるのは、LLMに明示的にタスクのルールを識別させるためです。LLMに与えたい指示とそのための情報を区切ると、LLMがプロンプトの文章を識別しやすくなるため、期待するアウトプットが得られやすいです。

[参考:プロンプトエンジニアリングの手法](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/advanced-prompt-engineering?pivots=programming-language-chat-completions#add-clear-syntax)

### プロンプト以外の思考を与えない

LLMの思考範囲に制限を与えると、システムがユーザーの期待に応えられない状況でも意図しない出力を抑えることができます。

例えば、以下のプロンプトです。
```
// 指示内容は省略
ルール---
// ルールは省略
---
ルールに記載したこと以外は絶対に考えないでください。ルールに確実に従うだけで結構です。
```
デバックの章で記載しましたが、LLMは指示にないことを考慮して回答を作成することがあります。こちらも絶対的な保証はできませんが、プロンプトに記載した事項以外は考えないように思考を制限させることができます。

# LLMをシステムに組み込むことの意味
これまでは実装に関する内容でしたが、システムにLLMを組み込んで検証を行った経験から、LLMを使った課題解決について考えたことを書きます。

大規模言語モデル（LLM）は、人間のように広範な知識を持ち、指示内容に則した出力を予測しています。これにより、多様な問いに対する答えや、複雑なテキスト生成タスクを実行することが可能になります。しかし、LLMが高度な知識や論理を用いて出力を生成することができるとしても、それが人間の代替となるわけではありません。

**一般的にシステム開発では、入力に対して確実な出力を期待し、不確実性を下げることを目指します。** これは、システムを予測可能なものにし、信頼性を保証するためです。しかし、**LLMを導入することは、この不確実性を意図的に上げることにつながってしまいます。** なぜなら、LLMは指示内容に基づいて出力を生成する過程で、複数の可能性から一つを選択するため、その結果は常に一定ではないからです。

しかし、この不確実性こそがLLMの強みでもあります。LLMは「正解らしいもの」を作り出すことができます。完璧な答えを求めるよりも、現実的な解決策やアイデアの提示が重要になる状況で価値を発揮します。たとえLLMが人間のように完璧な回答を提供することができなくても、その出力は新たな視点を提供したり、創造的な解決策を促したりすることで、補助的な役割を果たすことができます。

LLMの不確実性を理解し、それを戦略的に利用することで、問題解決に活かせることができるのだと思います。ですが、生成AI領域で話題になるのは"こんな複雑な作業も簡単にできた！"という切り口から派生した"人間はもういらないのでは?"という内容です。もちろん最近のモデルの発達は凄まじいものですが、急に人間の代わりになることを考えるのではなく、創造的なプロセスの一部を担うツールとしてどんな利用ができるかを考えるところから始めるのが良いのではないかと思いました。

# まとめ
課題解決に向けて半年間LLMをシステムに組み込んだ開発を行ったうえで得たナレッジを書きました。

今後も引き続きLLMを含め生成AIの活用を会社としても、また個人としても続けていきます。
最後は少しポエムじみた内容となってしまいましたが、本記事のナレッジが少しでもお役に立てれば幸いです。

まだ読んでいない方は、ぜひこちらの[取り組み編](https://qiita.com/k-hayasaka/items/3a30ab1d2d81c3273048)もご覧ください。
最後まで読んでいただき、ありがとうございました！