<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rag開発 on Xainome Blog</title><link>https://xainome-blog.beeskynohito.workers.dev/tags/rag%E9%96%8B%E7%99%BA/</link><description>Recent content in Rag開発 on Xainome Blog</description><generator>Hugo</generator><language>ja</language><lastBuildDate>Fri, 31 May 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://xainome-blog.beeskynohito.workers.dev/tags/rag%E9%96%8B%E7%99%BA/index.xml" rel="self" type="application/rss+xml"/><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part10 完</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-10/</link><pubDate>Fri, 31 May 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-10/</guid><description>&lt;h2 id="rag開発の一旦終了と成果物の共有">RAG開発の一旦終了と成果物の共有&lt;/h2>
&lt;p>とりあえず目標としていたものが完成したので開発はこれで一旦終わりとします。&lt;/p>
&lt;p>完成して動いたものを動画にして3つほどはります。&lt;/p>
&lt;h3 id="動画の内容とモデルの読み込み">動画の内容とモデルの読み込み&lt;/h3>
&lt;p>ファイル数別に動画を取ってみました。ベクトルモデルの読み込みと読み込ませるテキスト量によって時間が変わってきます。&lt;/p>
&lt;p>モデルの読み込みとかはOpenAIに変わったぐらいで大きく変更したところはないですが一部お見せします。&lt;/p>
&lt;pre tabindex="0">&lt;code> def generate_response(self, prompt: str, max_tokens: int = 3000) -&amp;gt; str:
try:
client = OpenAI(
# This is the default and can be omitted
api_key=os.environ.get(&amp;#34;OPENAI_API_KEY&amp;#34;),
)
response = client.chat.completions.create(
model=&amp;#34;gpt-4o&amp;#34;, # 使用するモデルを指定
messages=[
{&amp;#34;role&amp;#34;: &amp;#34;system&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;You are a helpful assistant.&amp;#34;},
{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: prompt}
],
max_tokens=max_tokens,
n=1,
stop=None,
temperature=0.7
)
print(response)
return response.choices[0].message.content
except Exception as e:
logging.error(f&amp;#34;エラーが発生しました: {e}&amp;#34;)
return &amp;#34;申し訳ありませんが、応答を生成する際にエラーが発生しました。&amp;#34;
&lt;/code>&lt;/pre>&lt;p>この辺をGPT-4oに聞いてみたのですが最近のコードを提示してくれなかったので、chatを作成する部分はgit-hubを参考に作りました。&lt;a href="https://github.com/openai/openai-python">こちら&lt;/a>です。&lt;/p>
&lt;h3 id="flaskの設定と変更点">Flaskの設定と変更点&lt;/h3>
&lt;p>それからFlaskの部分ですね。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part9</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-9/</link><pubDate>Fri, 24 May 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-9/</guid><description>&lt;p>一旦行き詰ったのでここまでを記録として残しておきます。&lt;/p>
&lt;p>行き詰った内容としてはローカルで実行できないこと、無料枠のcolabを利用してもダメだったことです。&lt;/p>
&lt;p>今回実施した内容としては以下&lt;/p>
&lt;ul>
&lt;li>
&lt;p>ベクトルデータベースの完成&lt;/p>
&lt;/li>
&lt;li>
&lt;p>生成モデルを利用して質問＋ファイルの内容を分割してプロンプトを投げる&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>‘paraphrase-multilingual-mpnet-base-v2’を利用したベクトルデータベースの作成が完了したのでGithubにあげようとしたのですが、jsonファイルは2GB以上、zip化しても500MB以上あったので断念しました。100MB未満なら簡単にあげられるのですが、それ以上はもう少し手順が必要みたいです。&lt;/p>
&lt;p>このファイル作るのに1週間近くはかかるんだなと思いました。ファイル数が多かったのもあるかもしれませんが、こんなに大変なんだなと実感しました。&lt;a href="https://zenn.dev/knowledgesense/articles/346183afdf750e">この記事&lt;/a>でベクトルデータベースの容量削減について書かれていますが、精度とかはまだ分かってないのでなるほどぐらいで終わりました。&lt;/p>
&lt;p>生成モデルを利用したプロンプトの作成と推論コードはザックリと以下になります。&lt;/p>
&lt;pre tabindex="0">&lt;code> def load_or_download_genmodel(model_name: str, local_dir: str) -&amp;gt; tuple[PreTrainedTokenizer | PreTrainedTokenizerFast, PreTrainedModel|SentenceTransformer]:
# ローカルディレクトリが存在しない場合は作成
if not os.path.exists(local_dir):
os.makedirs(local_dir)
# モデルがローカルに存在するかをチェック
if not (os.path.exists(os.path.join(local_dir, &amp;#39;config.json&amp;#39;)) and
os.path.exists(os.path.join(local_dir, &amp;#39;tokenizer_config.json&amp;#39;))):
# モデルが存在しない場合はダウンロードして保存
print(f&amp;#34;Model not found locally. Downloading {model_name}...&amp;#34;)
tokenizer = AutoTokenizer.from_pretrained(model_name, force_download=True)
model = AutoModelForCausalLM.from_pretrained(model_name, force_download=True)
print(f&amp;#34;Saving model and tokenizer to {local_dir}...&amp;#34;)
tokenizer.save_pretrained(local_dir)
model.save_pretrained(local_dir)
else:
# モデルが存在する場合はローカルからロード
print(f&amp;#34;Loading generative model and tokenizer from {local_dir}...&amp;#34;)
tokenizer = AutoTokenizer.from_pretrained(local_dir)
model = AutoModelForCausalLM.from_pretrained(local_dir)
return tokenizer, model
def process_text_in_chunks(self, question, text, model, tokenizer):
for chunk in chunks:
prompt = f&amp;#34;{question}\n\n{chunk}&amp;#34;
inputs = tokenizer(prompt, return_tensors=&amp;#34;pt&amp;#34;, max_length=50, truncation=True)
input_ids = inputs[&amp;#34;input_ids&amp;#34;]
attention_mask = inputs[&amp;#34;attention_mask&amp;#34;]
output = self.generative_model.generate(input_ids=input_ids, attention_mask=attention_mask, max_new_tokens=100, num_beams=3)
result_text = tokenizer.decode(output[0], skip_special_tokens=True)
combined_result += result_text + &amp;#34;\n&amp;#34;
return combined_result
def main(self, question):
# コサイン類推度から近いファイルを取得し、テキストを抽出→ここでは省く
result = self.process_text_in_chunks(question, text, self.generative_model, self.tokenizer)
return result
if __name__ == &amp;#39;__main__&amp;#39;:
# 生成モデルをロードまたはダウンロード
tokenizer, gen_model = load_or_download_genmodel(gen_model_name, gen_local_dir)
cos_sim_calc = CosSimCalc(gen_model, tokenizer, emb_model)
cos_sim_calc.main(question = &amp;#39;著作権について教えて下さい。&amp;#39;)
&lt;/code>&lt;/pre>&lt;p>いろいろ試して見たんですがローカルだと動かないですし、高性能GCPを買うまではいかないので、クラウドで実行するかOpenAIなどのAPIに頼ることになりそうです。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part8</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-8/</link><pubDate>Tue, 14 May 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-8/</guid><description>&lt;p>前回問題としていたベクトル化についてですが解決して、ようやく2つ目の目標が終わりました！&lt;/p>
&lt;p>まずはこちらをご覧ください。&lt;/p>
&lt;p>こんな感じになりました。&lt;/p>
&lt;p>今はまだファイル数は20~30なのでまだまだ足りませんが、前回に比べるとかなりましになりました。&lt;/p>
&lt;p>今回やったこととしては&lt;/p>
&lt;ul>
&lt;li>
&lt;p>OpenAIのベクトル化からSentence Transformerを使用したベクトル化に変更&lt;/p>
&lt;/li>
&lt;li>
&lt;p>全ファイルをベクトルデータベースとしてjsonファイルに格納&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>になります。そのため一旦別プログラムでファイルを全てベクトル化し、画面に表示をさせるときはそのファイルを読み込んで類似度の計算を行っています。&lt;/p>
&lt;p>前のコードとそこまでは変わってないので変わった部分で言うと&lt;/p>
&lt;pre tabindex="0">&lt;code>class PDFToVector:
def __init__(self, model_name: str):
# モデルを読み込み
self.model = SentenceTransformer(model_name)
def split_text2vector(self, text: str, max_tokens=200) -&amp;gt; List[float]:
vectors = []
for i in range(0, len(text), max_tokens):
segment = text[i:i+max_tokens]
vector = self.model.encode(segment, convert_to_tensor=True)
vectors.append(vector)
average_vector = np.mean(vectors, axis=0).tolist()
def embed_sentence(self, text: str, vector_fol: str, filename: str, vector_database: List) -&amp;gt; List:
embedding = self.split_text2vector(text)
vector = {
&amp;#39;title&amp;#39;: filename,
&amp;#39;body&amp;#39;: text,
&amp;#39;embedding&amp;#39;: embedding
}
vector_database.append(vector)
def main(self):
# ベクトルデータベースを格納
with open(vector_db_json, &amp;#39;w&amp;#39;, encoding=&amp;#39;utf-8&amp;#39;) as file:
json.dump(vector_database, file, ensure_ascii=False, indent=4)
&lt;/code>&lt;/pre>&lt;p>こんなとこだと思います。一部処理を省略していますが大体の変わったとこはここになります。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part6</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-6/</link><pubDate>Sat, 04 May 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/05/build-rag-app-part-6/</guid><description>&lt;p>PCが故障したという話をしたのですが、コンセントから電源が抜けてたというオチでした。&lt;/p>
&lt;p>なのでRAGの開発を再開しました！&lt;/p>
&lt;p>前回開発した部分は&lt;/p>
&lt;ul>
&lt;li>質疑応答画面の作成&lt;/li>
&lt;/ul>
&lt;p>今回開発する部分は&lt;/p>
&lt;ul>
&lt;li>
&lt;p>画面(Next.js)から送られるデータを受け取るエンドポイントをFlaskに設定&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ベクトル化するプログラムをクラス化&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Flaskも触ったことないのでChat-GPTに質問しながら触ってました。一旦中身は以下の内容にしています。&lt;/p>
&lt;pre tabindex="0">&lt;code>from flask import Flask, request, jsonify
from flask_cors import CORS
import pdf_to_model
import logging
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
CORS(app)
@app.route(&amp;#39;/process&amp;#39;, methods=[&amp;#39;POST&amp;#39;])
def process_question():
data = request.get_json()
query = data.get(&amp;#39;query&amp;#39;)
logging.debug(&amp;#34;Received query: %s&amp;#34;, query)
try:
vector = pdf_to_model.PDFToVector()
result = vector.main(query)
# 結果を JSON 形式で返す
return jsonify(result[0])
# return jsonify({&amp;#39;result&amp;#39;: &amp;#39;Processed result of &amp;#39; + query})
except Exception as e:
logging.error(&amp;#34;Error processing query: %s&amp;#34;, str(e))
return jsonify({&amp;#39;error&amp;#39;: str(e)}), 500
if __name__ == &amp;#34;__main__&amp;#34;:
app.run(debug=True)
&lt;/code>&lt;/pre>&lt;p>ログの設定はする必要はないですが、まだ開発段階なので一旦設定しています。それから&amp;quot;CORS(app)&amp;ldquo;ですね。少し調べたのですが異なるオリジン間でのリソース共有を可能にするために必要な設定みたいです。そもそも同一のオリジンとは以下の3つが同じであることが条件です。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part5</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-5/</link><pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-5/</guid><description>&lt;p>さて前回までやったこととしては&lt;/p>
&lt;ul>
&lt;li>
&lt;p>全データのベクトル化(途中で断念)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>画面の作成(Next.jsのインストールと起動)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>というところまでやってみました。今回は&lt;/p>
&lt;ul>
&lt;li>質疑応答画面の作成&lt;/li>
&lt;/ul>
&lt;p>というところまでやってみます。Next.jsは初めて触るので進みが遅々としていますがご了承ください。&lt;/p>
&lt;p>さてインストールするタイミングでこのようなことを聞かれました。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Would you like to use TypeScript? » No / Yes&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Would you like to use ESLint? » No / Yes&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Would you like to use &lt;code>src/&lt;/code> directory? » No / Yes&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>他にもいくつか聞かれたので一応Claudeにどんな意味があって導入することのメリットデメリットを聞きながら質問に答えました。大体Yesにしましたが…&lt;/p>
&lt;p>さて前回の画面表示まで行けたのでここからどう作業を進めればいいのかわかってなかったので聞いてみました。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-5/images/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2024-04-21-144001.png">&lt;/p>
&lt;p>ということで質疑応答画面を作っていきます。本格的なデザインは気にせずに簡易的なものを作っていきます。&lt;/p>
&lt;p>と言ってもからコード書くのは大変ですし、学ぶのにも時間がかかるので今回もClaudeに手伝ってもらおうと思います。それで最初書いてもらったコードがこちら&lt;/p>
&lt;p>page.jsx&lt;/p>
&lt;pre tabindex="0">&lt;code>import QuestionForm from &amp;#39;./components/QuestionForm&amp;#39;;
const HomePage = () =&amp;gt; {
return (
&amp;lt;div&amp;gt;
&amp;lt;h1&amp;gt;RAGシステム&amp;lt;/h1&amp;gt;
&amp;lt;QuestionForm /&amp;gt;
&amp;lt;/div&amp;gt;
)
}
export default HomePage;
&lt;/code>&lt;/pre>&lt;p>QuestionForm.jsx&lt;/p>
&lt;pre tabindex="0">&lt;code>import { useState } from &amp;#39;react&amp;#39;;
const QuestionForm = () =&amp;gt; {
const [question, setQuestion] = useState(&amp;#39;&amp;#39;);
const handleSubmit = (e) =&amp;gt; {
e.preventDefault();
// 質問をサブミットする処理をここに書く
console.log(&amp;#39;Submitted question:&amp;#39;, question);
setQuestion(&amp;#39;&amp;#39;);
}
return (
&amp;lt;form onSubmit={handleSubmit}&amp;gt;
&amp;lt;textarea
value={question}
onChange={(e) =&amp;gt; setQuestion(e.target.value)}
placeholder=&amp;#34;質問を入力してください&amp;#34;
rows={5}
/&amp;gt;
&amp;lt;button type=&amp;#34;submit&amp;#34;&amp;gt;送信&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
)
}
export default QuestionForm;
&lt;/code>&lt;/pre>&lt;p>ちなみに上記の書いてる内容はよくわかってないのでこっちはChat-GPTに解説してもらってました。新卒の時の研修で書いたHTMLやJSを思い出します。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part4</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/</link><pubDate>Fri, 19 Apr 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/</guid><description>&lt;p>前回で一通り作りたいものが作れたのでこの先はWebアプリ化できないかな～？という緩い感じで進めていこうと思います。&lt;/p>
&lt;p>前回行ったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>検索クエリのベクトル化&lt;/p>
&lt;/li>
&lt;li>
&lt;p>コサイン類似度の計算&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>今回行ったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>pdfファイルの全データベクトル化(途中で断念)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Web画面の作成(一部)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>pdfファイルのベクトル化に関しては前回やったのでそれをfor文で回してます。pdfは1つ1つ出力するようにして、後ほど全体を結合しようとしていました。&lt;/p>
&lt;pre tabindex="0">&lt;code>file_path_list = glob.glob(input_pdf+&amp;#39;/*.pdf&amp;#39;)
for pdf_path in file_path_list:
# 前回やったベクトル化
・
・
・
# JSON形式でベクトルデータベースを保存
with open(json_save_name, &amp;#39;w&amp;#39;, encoding=&amp;#39;utf-8&amp;#39;) as file:
json.dump(vector_database, file, ensure_ascii=False, indent=4)
&lt;/code>&lt;/pre>&lt;p>大体2500ファイルぐらいベクトル化したのですが、9$くらいになってました。残り12000ファイルほどあるので少し時間がかかるのと料金も計50$近くになりそうだったのでいったん中断しました。&lt;/p>
&lt;p>別の作業としてWeb画面を作っていこうと思ったのでまずは技術の選定からですね。ここに関してはChat-GPTとClaude両方に聞いてみましたが、Claudeのほうを採用しました。こんな感じです。Googleストアと書いたんですが1ミリも予定はないです（笑）&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/images/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2024-04-19-135322.png">&lt;/p>
&lt;p>返ってきた回答がこちら&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/images/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2024-04-19-135447.png">&lt;/p>
&lt;p>ということでおすすめにしたがってNext.jsにしようと思います。ユーザーのストレスを考えるのは今じゃなくてもいいんですがせっかく自身のスキルにもつながるので選びました。&lt;/p>
&lt;p>ちなみにおススメした理由とかわかってなかったのでいくつか聞いてみました。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>なぜNext.js(React)+Tailwind CSS や Vue.js はreactに比べてユーザーにストレスがかかりにくいのですか？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>単一ページアプリケーション(SPA)とはどういうものでしょうか？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Flash Of Unstyled Content(FOUC)や、SPAでよくみられるバンク画面の発生とはどういったものでしょうか？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>バックエンドではpythonを使用しているのでpythonフレームワークのflaskやdjangoという選択肢もあったと思いますが、なぜ候補から外れたのでしょうか？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>フロントエンドフレームワークとバックエンドWebフレームワークの違いは何ですか？&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>そこからいろいろ聞いていた結果、Next.jsとFlask(python)の組み合わせにしようと思います。Webについては全く分からなかったので色々聞いてみました。流れとしては以下の感じです。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Web画面から入力を受け取る&lt;/p>
&lt;/li>
&lt;li>
&lt;p>画面から受け取った入力を引数としてpythonに渡す&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pythonで引数をベクトル化しコサイン類似度の計算を行う&lt;/p>
&lt;/li>
&lt;li>
&lt;p>pdfファイルを全てベクトル化して結合したものを取得して、入力に類似しているファイルのタイトルと内容を取得する&lt;/p>
&lt;/li>
&lt;li>
&lt;p>取得したタイトルと内容を画面に表示する&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>1と5がNext.js、2がflask、3と4がpythonという流れでやる予定で、今回は1をやっていきます。&lt;/p>
&lt;p>というわけでまたClaudeに使い方を聞いて進めていきます。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/images/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2024-04-19-144643.png">&lt;/p>
&lt;p>これでインストールとプロジェクトの作成が完了して、デフォルトの画面が表示されるようになりました。こんな感じです。&lt;/p>
&lt;p>&lt;img loading="lazy" src="https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-4/images/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2024-04-19-145303-1024x597.png">&lt;/p>
&lt;p>私はvscodeを使っているのですがターミナルからうまく実行できてないので少し調べる必要がありますね。それから&amp;quot;AppData/Roaming&amp;quot;の中にnpmというファイルが必要なのですが、インストールで作成されなかったので手動で作成したりと少し導入で手間取ってしまいました。&lt;/p>
&lt;p>今回はここまでにして次回は簡単な画面を作成していこうと思います。恐らく稚拙な画面になりそうですがまずは作るとこらからです。ではでは。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part3</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-3/</link><pubDate>Sun, 14 Apr 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-3/</guid><description>&lt;p>このpartで目標としていた基本形は作れたことになります。簡単な質問で&amp;quot;タイトル&amp;quot;と&amp;quot;内容&amp;quot;が返ってくるものになります。&lt;/p>
&lt;p>前回やったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>特殊文字の削除&lt;/p>
&lt;/li>
&lt;li>
&lt;p>大文字小文字統一&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ベクトル変換&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>今回行ったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>検索クエリのベクトル化&lt;/p>
&lt;/li>
&lt;li>
&lt;p>コサイン類似度の計算&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>になります。ベクトル化に関しては前回も行ったので同じものになります。&lt;/p>
&lt;pre tabindex="0">&lt;code># これが検索用の文字列
QUERY = &amp;#39;保険金が欲しい&amp;#39;
# 検索用の文字列をベクトル化
query = openai.embeddings.create(
model=&amp;#39;text-embedding-3-large&amp;#39;,
input=QUERY
)
# ベクトル取得
query = query.data[0].embedding
&lt;/code>&lt;/pre>&lt;p>ちなみにモデルの部分に関しては&amp;rsquo;text-embedding-3-large&amp;rsquo;の他に&amp;rsquo;text-embedding-3-small&amp;rsquo;もありますのでお好きな方を使ってください。精度はlargeのほうがいいですが料金は下がると思います。&lt;/p>
&lt;p>次はコサイン類似度の計算になります。こちらは昔OpenAIでも提供されていたみたいですが、前のバージョンで提供されていたみたいなので他のもので代用します。簡単に自作できますがめんどくさい方はsklearnでも提供されているのでそちらを使ってください。&lt;/p>
&lt;pre tabindex="0">&lt;code>import numpy as np
def cosine_similarity(v1, v2):
# ベクトルのドット積
dot_product = np.dot(v1, v2)
# ベクトルのノルム
norm_v1 = np.linalg.norm(v1)
norm_v2 = np.linalg.norm(v2)
# コサイン類似度
similarity = dot_product / (norm_v1 * norm_v2)
return similarity
# 総当りで類似度を計算
results = map(
lambda i: {
&amp;#39;title&amp;#39;: i[&amp;#39;title&amp;#39;],
&amp;#39;body&amp;#39;: i[&amp;#39;body&amp;#39;],
# ここでクエリと各文章のコサイン類似度を計算
&amp;#39;similarity&amp;#39;: cosine_similarity(i[&amp;#39;embedding&amp;#39;], query)
},
vector_database
)
# コサイン類似度で降順（大きい順）にソート
results = sorted(results, key=lambda i: i[&amp;#39;similarity&amp;#39;], reverse=True)
&lt;/code>&lt;/pre>&lt;p>これで関連するファイルと内容が取得できます。&amp;lsquo;result[0]&amp;lsquo;であれば最も関連するもの、&amp;lsquo;result[-1]&amp;lsquo;であれば関連しないものあるいは反対のものになります。反対かどうかはコサイン類似度の値が負の値になってるかで確認できます。&lt;/p></description></item><item><title>RAGを開発してみたい！ので簡単なものでもいいので作ってみよう！part2</title><link>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-2/</link><pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate><guid>https://xainome-blog.beeskynohito.workers.dev/posts/2024/04/build-rag-app-part-2/</guid><description>&lt;p>このpartで基本が8割がた作れました。内容としては簡単な質問をすると&amp;quot;タイトル&amp;quot;と&amp;quot;内容&amp;quot;が返ってくる程度です。まだまだ理想には遠いですね。&lt;/p>
&lt;p>前回やったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>PDFファイル読み込み&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ページ番号取得と削除&lt;/p>
&lt;/li>
&lt;li>
&lt;p>色んな空白を削除&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>今回行ったのは&lt;/p>
&lt;ul>
&lt;li>
&lt;p>特殊文字の削除&lt;/p>
&lt;/li>
&lt;li>
&lt;p>大文字小文字統一&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ベクトル変換&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>この辺も全てChat-GPT任せでした。特殊文字に関してはURLとメールアドレス、特殊文字の削除になります。まあ私が扱ってるものの中にURLやメールアドレスはあまりなさそうですが、一般的には必要だと思います。&lt;/p>
&lt;pre tabindex="0">&lt;code>text = re.sub(r&amp;#39;http[s]?://\S+&amp;#39;, &amp;#39;&amp;#39;, text)
text = re.sub(r&amp;#39;\S*@\S*\s?&amp;#39;, &amp;#39;&amp;#39;, text)
text = re.sub(r&amp;#39;[!#\$%&amp;amp;\&amp;#39;()*+,\-./:;&amp;lt;=&amp;gt;?@\[\\\]^_`{|}~]&amp;#39;, &amp;#39;&amp;#39;, text)
&lt;/code>&lt;/pre>&lt;p>次は大文字小文字の統一ですね。これが少し特殊ですが例えば &amp;ldquo;１２３ａｂｃｱｲｳｴｵ①②③¹²³&amp;quot;という文字があった時に&amp;quot;123abcアイウエオ123123&amp;quot;となるように変換しています。これもあまり使われてないと思いますが、一応変換しています。&lt;/p>
&lt;pre tabindex="0">&lt;code>normalized_text = &amp;#39;&amp;#39;.join([unicodedata.normalize(&amp;#39;NFKC&amp;#39;, char) if not unicodedata.east_asian_width(char) in (&amp;#39;F&amp;#39;, &amp;#39;W&amp;#39;, &amp;#39;A&amp;#39;) else char for char in text])
&lt;/code>&lt;/pre>&lt;p>それからベクトル変換ですね。OpenAIの埋め込みベクトルに変換していきます。課金が必要なので他のやり方がないか考えたいところですが、簡単に実装するために使用します。いつかはTF-IDFかWord2Vecを使ってみたいです。&lt;/p>
&lt;pre tabindex="0">&lt;code>openai.api_key = os.environ[&amp;#39;OPENAI_API_KEY&amp;#39;]
vector_database.append({
&amp;#39;title&amp;#39;: filename,
&amp;#39;body&amp;#39;: text,
&amp;#39;embedding&amp;#39;: vector
})
&lt;/code>&lt;/pre>&lt;p>課金といっても10000字くらい埋め込みベクトルに使っても0.01$もかかりませんので数円くらいならお試しで触ってみてください。&lt;/p>
&lt;p>一つだけ注意点を話すと8192トークンまでしか入力として受け取れないので、もし文字数が多くなる場合は分割して、それぞれの平均を取るといいかと思います。私の場合はとりあえず7000文字くらいで分割しています。&lt;/p>
&lt;pre tabindex="0">&lt;code>for i in range(0, len(text), max_tokens): # max_tokens=7000
segment = text[i:i+max_tokens]
res = openai.embeddings.create(
model=&amp;#39;text-embedding-3-large&amp;#39;,
input=segment
)
vectors.append(res.data[0].embedding)
average_vector = np.mean(vectors, axis=0).tolist()
&lt;/code>&lt;/pre>&lt;p>そういえばいつからかはわかりませんが埋め込みベクトルの数値を取り出す方法が変わってたみたいで、&amp;lsquo;res[data][0][embedding]&amp;rsquo; から &amp;lsquo;res.data[0].embedding&amp;rsquo;に変わっています。前の方法が良ければバージョンを下げていただければ大丈夫です。&lt;/p></description></item></channel></rss>