【15分】ベクトルデータベース&RAG爆速入門

# はじめに
本記事では、ベクトルデータベースの構築とRAGでの検索を初心者向けにまとめています。
GoogleColaboratory環境で実装するため、ややこしい環境設定はほとんどありません。全部無料です。

生成AIやRAG、ベクトルデータベースは難しいイメージを持つ人が多いですが、実はとてもシンプルで実装も簡単です。15分程度で実装できるようになり、かつ語れるようになります。

# ベクトルデータベースとは？
ここからざっくり分かりやすく説明します。イメージを掴むことを優先します。
#### まず普通のデータベースについて
普通のデータベースには数字のデータが入っておりSQLで必要な情報を抜き出すことができます。例えば、ある顧客の１年間での売り上げ金額を取得したりできます。

#### ベクトルデータベースとは？
一方で、PDFファイルなどの文字情報を検索したい場合はどうすれば良いでしょうか？例えば、100ページに渡る論文の中から特定の情報を知りたい時などです。一枚一枚目視をしていては日が暮れます。
そのような場合に使うのがベクトルデータベースです。大量の文字情報をチャンクという文節の単位で切り分けデータベースの値として保存します。
#### 何がベクトルなのか？
チャンクにわけた文節をそのまま文字でデータベースに保管するわけではなく、文字をベクトルにしてから保存するためベクトルデータベースと言われています。文字情報のままでも使えますが、重いです。そこでベクトル化をすることで文字データを圧縮しつつ、数字として扱うことで検索の自由度を高めることができます。
#### ベクトルデータベースの中身
こんな感じです。行名にチャンクにした文章が入っており、以降の列はベクトルになっています。見ての通り普通のDBと同じです。文章の検索よりもベクトルの類似度の検索の方がはるかに簡単なためこのような構造になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/a51a7dbc-17f6-d4ab-2505-61f8a7d4cfc7.png)


# 今回の目標
1. ベクトルDBの構築
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/62b9a027-6042-9a21-94ff-7a0b755c3429.png)
1. langchainでのベクトル検索実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/440165bf-e817-6766-c5e5-951abcbadecb.png)

# 1.初期準備(5分)
はじめにライブラリのインストールとOpenAI APIキーの設定を行います。
### インストールするライブラリ
1. langchain：RAGをするために使用
1. langchain_community：langchainのライブラリを使う際に必要
1. openai：生成AIのエンジンとしてOpenAIを使用
1. pinecone-client：ベクトルデータベースとして使用
1. tiktoken：文章データをチャンクにわけベクトル化する際ために使用
1. pdfminer.six：PDFからテキストを抽出するために使用

### ライブラリーのインポート
Google Colaboratoryを開き以下を実行します。
```
!pip install langchain　langchain_community openai pinecone-client tiktoken pdfminer.six
!pip install langchain-openai
```

### OpenAI APIのキー取得
オープンAIのAPIキーを取得し、コラボの環境変数に設定します。

https://platform.openai.com/api-keys

APIキーに慣れていない方はこちらが参考になります↓

https://qiita.com/Isaka-code/items/b50983796636503b44c5

「your OPENAI_API_KEY」の部分を取得したキーに変えて実行します。なお、Colabでは環境変数を設定する機能がありますが、この記事では便宜的にコードで環境変数を設定します。
```set.py
import openai
import os

os.environ["OPENAI_API_KEY"] = "your OPENAI_API_KEY"
openai.api_key = os.getenv("OPENAI_API_KEY")
```

# 2.ベクトルDBの作成(8分)
### 手順
1. 対象データの読み込みとベクトル化
    - STEP①：RAGしたいファイルの読み込み
    - STEP②：PDFのテキスト化
    - STEP③：テキストの分割（＝チャンク）
    - STEP④：分割したテキストデータをベクトル化
1. ベクトルデータベースへの格納
    - STEP①：pinconeへの接続
    - STEP②：pinconeへデータを格納する

### 1.対象データの読み込みとベクトル化
#### STEP①：RAGしたいファイルの読み込み
自分のベクトル化したいファイルをアップロードします。例としてtransformerの論文をアップロードします。

https://arxiv.org/pdf/1706.03762

```vectorize_1.py
# STEP①：RAGしたいファイルの読み込み
from google.colab import files
uploaded = files.upload()
file_name = list(uploaded.keys())[0]
```
#### STEP②：PDFのテキスト化
PDFから文字情報を抜き取る処理です。pdf2txt.pyというライブラリを使わせていただきます。

https://qiita.com/T_Umezaki/items/84086e8da5188ef8ca8d

```vectorize_2.py
# STEP②：PDFのテキスト化
import os
import urllib.request

#　pdf2txt.pyのダウンロード
py_url ='https://github.com/pdfminer/pdfminer.six/blob/master/tools/pdf2txt.py'
py_fn='pdf2txt.py'
os.system("wget -O " + str(py_fn) + " " + str(py_url))

#　pdf2txt.pyを用いてpdfファイルをテキスト変換
lines = !pdf2txt.py { file_name }
text = '\n'.join(lines)
```
#### STEP③：テキストの分割（＝チャンク）
調整してみてください。
separator="." → 区切り位置
chunk_size=1000　→ チャンクに含まれる文字数。1000文字で一塊という意味。
chunk_overlap=15　→ チャンク間の重複部分のサイズ。15文字までは重複OKの意味。

https://qiita.com/ymgc3/items/44d1638711dc76f129fd

```vectorize_3.py
### STEP③：テキストの分割
from langchain.text_splitter import CharacterTextSplitter

text_splitter = CharacterTextSplitter(
    separator=".",
    chunk_size=1000,
    chunk_overlap=15
    )

docs = text_splitter.split_text(text)
len(docs)
```
>43

→今回は、43個のチャンクに分けらました。各チャンクはlistに保存されています。

### STEP④：分割したテキストデータをベクトル化
チャンクをベクトルに変換します。それに伴い、listではなくdataframeに変更します。
```vectorize_4.py
from langchain.embeddings.openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()

# テキストデータをベクトル化
import pandas as pd
vectorized_docs = [embeddings.embed_query(x) for x in docs]
vectorized_docs = pd.DataFrame(vectorized_docs)

docs_ = pd.DataFrame(docs)
output = pd.concat([docs_,vectorized_docs], axis=1)
output.to_excel('vector_dataset.xlsx',index=False, header=False)
            
output.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/14ce841d-f434-b553-54e7-0165f3107d72.png)

### 2.ベクトルデータベースの構築と格納（Pinecone）
#### STEP⓪：ベクトルDBのクリエイト
無料で使えるPineconeというベクトルデータベースを使います。下記のまとめが非常に参考になります。

https://note.com/npaka/n/n37a6543542fa

Dtabaseタブからcreate a new indexを選択します。
ここでdimensionsの設定に注意する必要があります。chatgptやopenaiで使われているベクトルの次元は1,536なので、これに合わせる必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/6916eaa4-376f-58b1-2bbb-1719b36888b9.png)

うまく構築すると以下の画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/9d12fcbc-ccc9-3243-ff2c-c9437e694be8.png)

#### STEP①：pinconeへ接続できるようにする
pineconeとの接続を作ります。「YourAPI_KEY」はpineconeから値を取得してください。
```pinecone_1.py
from pinecone import Pinecone
pc = Pinecone(api_key='YourAPI_KEY')
pinecone_index = pc.Index("vector-db")
pc.list_indexes()
```
#### STEP②：pinconeへデータを格納する
先ほど作成したベクトル化したPDFのデータ（vectorized_docs）を一行ずつ読み取りpineconeへ格納していきます。
```pinecone_2.py
for i in range(len(vectorized_docs)):

    pinecone_index.upsert(
        vectors = [
            {
                'id': str(i+1),
                'values': vectorized_docs.T[i],
                'metadata': {"source": file_name,"text": docs[i], "memo": "memo_"+str(i+1)}
            }
        ]
    )
```
pineconeの画面を確認するとVECTOR COUNTが43になっており、先ほどのデータが全て格納されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/b1d7429d-6b08-c740-282b-26f3a4079561.png)


# 3.ベクトル検索(2分)
### セマンティック検索（質問に対して尤もらしい文を抽出する）
##### STEP①：langchainを使う準備をする
```rag.py
os.environ["PINECONE_API_KEY"] = "your pinecone api key"

env="aws"
index_name = "vector-db"

from langchain.vectorstores import Pinecone as PineconeStore
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
docsearch = PineconeStore.from_existing_index(index_name, embeddings,"text")
```
##### STEP②：langchainでベクトルデータベースに問い合わせる（RAG）
今回は質問に対して、最も関係する文章を抽出します。43個のチャンクのベクトルから最も質問のベクトルと類似しているベクトルを抽出してます。
```rag_2.py
query = "トランスフォーマーとはなんですか？"
answer = docsearch.similarity_search(query, k=1)
print(answer[0].page_content)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770170/d35e0cad-2936-04bd-c164-53a2c190fff7.png)



# 参考資料

https://note.com/noa813/n/n6065209e7bf0

https://qiita.com/Fortinbras/items/b892841109487f0f666b

https://zenn.dev/kun432/scraps/927885cd23d085


# 終わりに
2024年4月からアウトプットのためにQiitaとnoteを始めました。「フォロー」や「いいね」頂けると励みになります！

https://qiita.com/zukki2

https://note.com/zuki2