大規模言語モデル(LLM)による文書要約

## 概要
本記事では、大規模言語モデル（LLM）を使用した **文書要約の手法を3つ** 紹介します。
記事中に登場する手法はいずれも[LangChainの公式ドキュメント](https://python.langchain.com/docs/modules/chains/document/)にも掲載されており、代表的な要約手法となっています。要約処理を自動化できれば、文書整理の時間は削減され、書式や体裁の一貫した可読性の高い文書を得ることができます。

## 必要なもの
本記事ではローカル環境上でPythonとLangChainを動作させ、LLMにはAzureOpenAI Service上のGPT-4を採用します。
- Python
- LangChain
- AzureOpenAI Service

:::note warn
AzureOpenAI Serviceや、GPT-4の利用申請方法については触れません。
:::

文書要約に使うデータは[青空文庫](https://www.aozora.gr.jp/)からダウンロード可能なものを採用します。

|タイトル |著者 |文字数 |
|--|--|--|
| 蜘蛛の糸 | 芥川竜之介 | 約5000字 |
| 注文の多い料理店 | 宮沢賢治 | 約6000字 |

## 要約手法
LLMを使用した代表的な要約手法として、**Stuff**、**Map Reduce**、**Refine**の3つが提案されています。
ここでは、各手法の処理のステップと特徴を紹介していきます。

### Stuff
```plantuml
file Document
rectangle "LLM (gpt-4)" as llm1 #palegreen
file summary1

Document -> llm1
llm1 -> summary1
```
Stuffは要約したい文書全体を、単純にLLMに投入する手法です。
難しい仕組みを実装する必要がなく、最も手軽に試すことができる手法といえます。
しかし、Stuffを採用した場合、投入できる文書の長さは利用するLLMの最大トークン数に依存します。AzureOpenAI Serviceのgpt-3.5-turboであれば4096トークンまでとなります。（2024/01/09 現在）

※実際のLLMモデルと最大トークン数の関係は[こちら](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models?source=recommendations)からご確認ください。

### Map Reduce
```plantuml
file Document

rectangle splitter #palegreen [
text splitter
]

rectangle "分割文書" as chunked {
file chunk1
file chunk2
file chunk3
}

rectangle "LLM (gpt-4)" as llm1 #palegreen
rectangle "LLM (gpt-4)" as llm2 #palegreen
rectangle "LLM (gpt-4)" as llm3 #palegreen
rectangle "LLM (gpt-4)" as llm4 #palegreen

rectangle "要約文書" as summarized {
file summary1
file summary2
file summary3
}
file summary4

Document -> splitter : "分割して！"
splitter -> chunk1
splitter -> chunk2
splitter -> chunk3

chunk1 -> llm1: "要約して！"
chunk2 -> llm2: "要約して！"
chunk3 -> llm3: "要約して！"
chunk1 -[hidden]-> chunk2
chunk2 -[hidden]-> chunk3

llm1 -> summary1
llm2 -> summary2
llm3 -> summary3
summary1 -[hidden]-> summary2
summary2 -[hidden]-> summary3

summary1 -> llm4: "まとめて！"
summary2 -> llm4: "まとめて！"
summary3 -> llm4: "まとめて！"

llm4 -> summary4
```
Map-Reduceは分割した文書を個別に要約し、最後に要約結果をまとめあげる手法です。処理を並列化することができ、後述のRefineに比べて高速に処理することができます。
「Map-Reduce」という名前はビッグデータにアクセスするためのプログラミングモデル（パターン）にも同一の名前がつけられており、基本的な考え方は同じです。

Map-Reduceは以下のステップで実行されます。
1. 文書をチャンクに分割する
1. それぞれのチャンクをLLMに要約させる
1. 要約済みの文書を集約してLLMにまとめさせる

### Refine
```plantuml
file Document

rectangle splitter #palegreen [
text splitter
]

rectangle "分割文書" as chunked {
file chunk1
file "chunk2" as chunk2_1
file "chunk3" as chunk3_1
}

rectangle " " as block1 #line.dashed {
file summary1
file "chunk2" as chunk2_2 #line.dashed
}

rectangle " " as block2 #line.dashed {
file summary2
file "chunk3" as chunk3_2 #line.dashed
}

file summary3

rectangle "LLM (gpt-4)" as llm1 #palegreen
rectangle "LLM (gpt-4)" as llm2 #palegreen
rectangle "LLM (gpt-4)" as llm3 #palegreen

Document -> splitter : "分割して！"
splitter -> chunk1
splitter -> chunk2_1
splitter -> chunk3_1

chunk1 -> llm1: "要約して！"
chunk2_1 .> chunk2_2
chunk3_1 .> chunk3_2
chunk1 -[hidden]-> chunk2_1
chunk2_1 -[hidden]-> chunk3_1

llm1 -> summary1
llm2 -> summary2
llm3 -> summary3

summary1 -[hidden]-> chunk2_2
summary2 -[hidden]-> chunk3_2

block1 --> llm2: "要約して！"
block2 --> llm3: "要約して！"
```
Refineは分割したチャンクを順番に要約していく手法です。このとき、次に要約させたい分割文書と、直前にLLMで処理した要約文書を一緒に投入します。こうすることで、チャンク間の文脈や文意を損なわずに要約することができます。
一方で、

Refineは以下のステップで実行されます。
1. 文書をチャンクに分割する
1. 1つ目のチャンクをLLMに与えて要約させる
1. 直前に要約した文書と次のチャンクをLLMに与えて要約させる
1. 以降、チャンクがなくなるまで3.を繰り返す

## 要約結果
本記事ではMap ReduceとRefineの2つの要約処理を下記の設定で行いました。

| 項目 | 値 | 詳細 |
|---|---|---|
| チャンクサイズ | 1000 | 文書を分割するときの文字数 |
| オーバーラップサイズ | 100 | 文書を分割するときに前後で重複させる文字数 |
| 出力文の長さ | 500 | 最終的に出力される要約文の文字数 |

### 蜘蛛の糸
```
# Map Reduce
芥川龍之介の「蜘蛛の糸」は、極楽の蓮池から地獄を覗いた釈迦が、罪人である陀多が一度だけ蜘蛛を助けたことを思い出し、彼を救うために蜘蛛の糸を下ろすという物語です。陀多は糸を登り始めますが、他の罪人たちも糸を登ろうとしたため、自分だけを救おうとして糸を切り、再び地獄へ落ちます。この物語は、芥川龍之介全集2を底本に、筑摩全集類聚版芥川龍之介全集を親本として作成され、青空文庫で公開されています。
```
```
# Refine
「蜘蛛の糸」は、地獄で苦しむ陀多が極楽から垂れ下がる蜘蛛の糸を見つけ、これにより地獄から逃れ極楽に行けるかもしれないと喜ぶところから始まる物語である。しかし、他の罪人たちも同じ糸を使って上昇し始めることに気づき、自分だけでさえ断れそうな細い糸が、あれだけの人数の重みに耐えられるか疑問に思い、もし糸が切れたら自分も地獄に落ちてしまうと恐怖する。陀多は他の罪人たちに対して、この糸は自分のものだと主張し、下りるように叫ぶ。しかし、その途端、糸は切れ、陀多は地獄へ落ちてしまう。御釈迦様はこれを見て、自分だけを救おうとする陀多の無慈悲な心が罰を受けたことを悲しく思う。この物語は芥川龍之介によって書かれ、青空文庫で公開されている。
```

### 注文の多い料理店
```
# Map Reduce
二人の紳士が山奥を探検し、犬たちが死んでしまい、彼ら自身も迷子になる。彼らは空腹を感じ、西洋料理店を見つける。店内には様々な指示が書かれており、それに従って行動する。しかし、その指示は彼らを食事にするためのものであった。彼らは恐怖に震え、逃げようとするが、扉は動かない。最終的に、犬たちが戻ってきて、猟師が現れ、彼らは安心する。しかし、彼らの顔は元に戻らなかった。
```

```
# Refine
「注文の多い料理店」は、二人の紳士が山奥で狩りを楽しむが、山は怪しく、鳥や獣は一匹もいない。彼らは鹿を撃つことを夢見ていたが、犬がめまいを起こし死んでしまう。彼らはそれぞれ数千円の損害を被り、帰ろうと決めるが、どの方向に行けばいいのか分からなくなる。その時、彼らの後ろに立派な西洋造りの家が現れ、その家には「RESTAURANT 西洋料理店 WILDCAT HOUSE 山猫軒」という看板が出ている。彼らはその家に入り、さまざまな扉を通過しながら、髪を整え、靴の泥を落とし、鉄砲や帽子、外套、靴を置き、ネクタイピン、カフスボタン、眼鏡、財布などの金物類を金庫に入れ、クリームを顔や手足に塗るなど、様々な指示に従う。彼らはその過程で、この料理店にはよほど偉い人が来ていると推測し、自分たちも貴族と接近するかもしれないと期待する。その後、彼らはさらにクリームを耳に塗り、香水を頭に振りかけ、塩を体にもみ込むなど、さまざまな指示に従いつつ、料理が出てくるのを待つ。しかし、その過程で彼らは、自分たちが注文を受ける側になっていることに気づく。そして、彼らは恐怖に震えながら、自分たちが食事にされることを悟る。その後、白熊のような犬が二匹、扉をつきやぶって室の中に飛び込んできた。犬たちは次の扉に飛びつき、その扉の向うのまっくらやみの中で何かが鳴り、室は消え、二人は寒さに震えて草の中に立つ。彼らの上着や靴や財布やネクタイピンは、枝にぶらさがったり、根元に散らばったりしていた。その後、犬が戻ってきて、「旦那、旦那」と叫ぶものがあり、二人は元気を取り戻し、「おおい、おおい、ここだぞ、早く来い」と叫んだ。そして、簔帽子をかぶった専門の猟師が、草を分けてやってきた。二人はやっと安心し、猟師のもってきた団子を食べ、途中で十円だけ山鳥を買って東京に帰った。しかし、一度紙くずのようになった二人の顔は、東京に帰っても、お湯に入っても、もう元のとおりにはならなかった。
```

## 考察
Map Reduceは「蜘蛛の糸」を1分52秒、「注文の多い料理店」を1分56秒で要約処理を終えました。物語を端的にまとめており、プロンプトで指示した500文字以内で要約するという制約に忠実に従っています。
一方、Refineは「蜘蛛の糸」を1分56秒、「注文の多い料理店」を4分9秒かけて要約処理を終えました。物語内の描写を引用しながら出来事の羅列に近いまとめ方を行った印象です。また、「注文の多い料理店」については500字以内の制約を逸脱して要約文を出力しました。
処理時間の違いは、各アルゴリズムの特性をよく表しています。Map Reduceは処理を並列化することができるのに対し、Refineは並列化できません。数千字程度では違いが顕在化しずらいですが、大規模な文書やコーパスを扱う場合は処理時間もアルゴリズムを検討するうえで重要なファクターになります。
プロンプトやパラメータ、問題設定次第でアルゴリズムに対する評価は変わるものですので、本記事で行った実験で一概に一方のアルゴリズム優れているといった結論付けはできませんが、今回の条件ではMap Reduceの方が指示に忠実で、高速で、簡潔な要約文を提示しました。
実際に特定の問題をこれらのアルゴリズムに解かせる場合、パラメータ値やプロンプトを作りこみながら比較実験をして、慎重にアルゴリズムを比較検討する必要があるでしょう。

## まとめ
- 要約処理のアルゴリズムには3種類ある
- 各アルゴリズムには得手、不得手がある
- 本記事の条件では、Map Reduceが「忠実」、「高速」、「簡潔」に要約文を出力した
- 実問題を解かせる場合は、より詳細にパラメータ値やプロンプトを作りこんでアルゴリズムを比較する必要がある