GitHubを見ながらself-discoverを試してみた

# 前置き
Google DeepMindが発表したself-discoverをhwchase17さんが実装してGitHubに挙げてくださっていたので試してみました。

自分の備忘録用となるべく英語見たくない方用に作成しました。

本記事では前編と後編に分けています。
前編ではコード中心、後編では出力結果中心に見ていきます。
Qiitaを書き慣れていないので読みづらい箇所はご了承ください。

環境はGoogleColabです。

# 参考
[Self-Discover: Large Language Models Self-Compose Reasoning Structures](https://arxiv.org/abs/2402.03620)
[hwchase17のコード](https://github.com/langchain-ai/langchain/blob/master/cookbook/self-discover.ipynb)

# 前編

### 事前準備
必要なモジュールをインストールしてください。
```
!pip install langchain langchain_openai langchain_core langchainhub
```

モジュールのインポート、モデルのイニシャライズをします。
人によってはAPIキーのセットも行ってください。
```python
import os
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 論文中ではGPT4, GPT3.5-turbo, PaLM2, Llama2-70Bを使用 
os.environ["OPENAI_API_KEY"] = "sk-"
model = ChatOpenAI(temperature=0, model="gpt-4-turbo-preview")
```

### select_prompt
select_promptを定義します。
これは既にhwchase17さんが用意してくれていたのでそれを使用します。
```python
select_prompt = hub.pull("hwchase17/self-discovery-select")
```

promptの内容が見たい方は次のコードを実行してください。
```python
select_prompt.pretty_print()
```

また、自分でpromptを作りたい方は論文中のFigure.10を参考にしてください。
![スクリーンショット 2024-02-15 023254.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/0c30c635-1c02-5fe2-b85a-569305b3bbd4.png)


### adapt_prompt
adapt_promptを定義します。
これも先ほどと同様です。
```python
adapt_prompt = hub.pull("hwchase17/self-discovery-adapt")
```


### structured_prompt
structured_promptを定義します。
GitHubのコードではstructured_promptとしていましたが、論文中ではIMPLEMENTとされている部分です。
また中身もhwchase17さんが少し付け足していました。
（精度が良くなった？）

ここまでで論文中のStep1までのpromptの準備が完了しました。
次にStep2のpromptを準備します。

### reasoning_prompt
reasoning_promptを定義します。
これも先ほどまでと同様です。
```python
reasoning_prompt = hub.pull("hwchase17/self-discovery-reasoning")
```

### Chainの作成
ここまで準備が完了したらChainを作成します。
自作でpromptを作成した場合は、overall_chainの部分は適切な形に直してください。

```python
select_chain = select_prompt | model | StrOutputParser()

adapt_chain = adapt_prompt | model | StrOutputParser()

structure_chain = structured_prompt | model | StrOutputParser()

reasoning_chain = reasoning_prompt | model | StrOutputParser()

overall_chain = (
    RunnablePassthrough.assign(selected_modules=select_chain)
    .assign(adapted_modules=adapt_chain)
    .assign(reasoning_structure=structure_chain)
    .assign(answer=reasoning_chain)
)
```

### reasoning_modules
次にChainに渡すreasoning_modulesを用意するのですが、これはとても長いのでGitHubからコピーを行うか論文中のTable.2をコピーしてください。
最終的にはstr形式に直しておいてください。
（GitHubでは次の様にしてstrに直してます）
```python
reasoning_modules_str = "\n".join(reasoning_modules)
```

### task_example
task_exampleを定義します。
ここには解いてほしいタスクを定義してください。
今回はクイズノックさんの少し難しめの算数問題を解いてもらおうと思います。
[小学生に負けちゃうの？意外と難しい「算数」、京大生がコツ教えます
](https://web.quizknock.com/sansubattle)
```python
task_example = "There is a job that Adam can finish in 16 days and Eve can finish in 20 days. When the two of you do this job together, how many days will it take to finish?"
```

### 結果を得る
推論を行い結果をresultを格納します。
ここまでで前編は終了です。

```pyhton
result = overall_chain.invoke(
    {"task_description": task_example, "reasoning_modules": reasoning_modules_str}
)
````

# 後編
### 結果を見る
resultは辞書型です。
キーとバリューにはoverall_chainを定義したときに指定したものが与えられています。
したがって、結果を見る場合は次の様にします。
（この辺りは自由にして大丈夫です）
```python
print(result.keys())
select_key = input("key?: ")
print(result[select_key])
```

ここからは各キーに対してどのようなバリューが入っているのかと共に論文中のStep1とStep2で何が説明されていたかを考えます。

まず、今回のキーの一覧は['task_description', 'reasoning_modules', 'selected_modules', 'adapted_modules', 'reasoning_structure', 'answer']です。

task_descriptionには先ほどのクイズノックさんの問題が格納され、
reasoning_modulesにはreasoning_modules_strが格納されています。

### selected_modules
selected_modulesにはreasoning_modulesからタスクの解決に必要だと思うものを選択して格納されています。
（そもそもreasoning_modulesとは何かというと高度なヒューリスティックの一覧だと思ってください。）

### adapted_modules
adapted_modulesでは先ほど選んだreasoning_modulesを、与えられたタスクに適応させます。
（私は最初に読んだときに？マークがでましたが、ヒューリスティックについて調べた後に読んだらスッキリしたので同様の方がいらっしゃればお勧めします。）

### reasoning_structure
reasoning_structureではadapted_modulesに従ってjson形式のプランを作成します。
プランは指示やヒントつきの穴埋め問題だと思ってください。

### answer
answerではreasoning_structureで作られた穴埋め問題の穴を埋めていくイメージで回答を作成していきます。

reasoning_structureとanswerの出力を見比べてみます。
reasoning_structureでは問題を解くためのステップと各ステップの詳細、アクションをjson形式で指示しています。

![スクリーンショット 2024-02-15 030937.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/5775b757-c48c-2dae-906e-41a857bee034.png)

answerでは指示に従って穴埋めを行い、最終的な回答を出力しています。

![スクリーンショット 2024-02-15 030959.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/6c440df9-f4a0-264c-d020-9d6e93180b42.png)


以上が内容となります。
論文とGitHubのコードを照らし合わせただけなので浅い内容になりましたが、閲覧いただきありがとうございました。