誰かの技術置き場

GeminiAPI + LangChainでRAGを実装する

お疲れ様です。

最近の会社の勉強会でチャットボットのWebアプリを作成しています。(何故か教える側で…。)
やっている中で返答を返してくれる生成AIでRAG(検索拡張生成)を実装してみたいと思い、実際に作成してみたのでそれをまとめておきます。

やったこととしては以下になります。
生成AIには基本お金がかからず、個人のPCの性能に左右されないようにGemini APIを使用しています。

Gemini APIの準備

Gemini APIAPIキーの取得とPythonで使えるように準備をし確認するところまでやっています。

APIキーの取得

Google AI StudioにアクセスしてAPIキーを取得
https://ai.google.dev/ 2か所チェックをつけて「続行」をクリック。

APIキーを作成」をクリック。

※課金が無効になっているか確認する場合は下記にアクセスして「このプロジェクトには請求先アカウントがありません」となっていればおそらくOKです。
https://console.cloud.google.com/billing/linkedaccount
プロジェクトの部分からAPIキーをコピーしましょう。

SDKをインストール

Pythonの開発環境で"google-generativeai"をインストール
pip install google-generativeai

Pythonコードで動作確認をします。
この時APIキーの取り扱いには注意しましょう。直書きはNG。環境変数などに入れておくのが良いです。
コードは以下のような形で。

import os

import google.generativeai as genai


# APIキーを設定(環境変数に設定するパターン)
genai.configure(api_key=os.environ["GEMINI_API_KEY"]) ## 注意

# モデルを準備
model = genai.GenerativeModel('gemini-1.5-flash')

# 出力してみる
response = model.generate_content("エレファントカシマシはどんなバンドですか?")
print(response.text)


データベース作成

LangChainの機能を使ってChromaDBのデータベースを作成しました。
チャットボットで使うためにデータベースを出力してチャットボット側で読み込む想定で進めました。 必要なライブラリは以下。

langchain
langchain_community
sentence-transformers
unstructured
chromadb

コードは以下のようになっています。(いろいろなサイトを参考にさせていただきました🙇‍♂️)

# Webページの内容を知識として読み込み
urls = ["https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%AC%E3%83%95%E3%82%A1%E3%83%B3%E3%83%88%E3%82%AB%E3%82%B7%E3%83%9E%E3%82%B7"]
loader = UnstructuredURLLoader(urls=urls)
docs = loader.load()
    
# 読込した内容を分割する
text_splitter = JapaneseCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
docs = text_splitter.split_documents(docs)
    
# ベクトル化する準備
embedding = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base"
)
    
# 読込した内容を保存
vectorstore = Chroma.from_documents(
    documents=docs,
    embedding=embedding,
    persist_directory="./chroma",
    collection_name="elephants"
)

今回はwebサイトの中身をデータとして取得するUnstructuredURLLoaderを使用しました。 document_loadersはほかにも種類があるので利用したいデータに応じて使い分けができます。
変数urlsはリストで複数指定が可能です。
あとはChromaDBの保存の部分で、Chroma.from_documentsの引数collection_nameはチャットボットの読み込みの際にも使用するので覚えておきましょう。
こんな感じでsqlite3のファイルで保存されます。一緒に入っているフォルダも必要になります。


チャットボットに返答機能として実装

実際にチャットボットに返答生成できるように実装しました。 チャットボットのWebアプリはPythonライブラリのStreamlitで作成しています。

コード実装

まずは必要なライブラリのインストールから。データベース作成で使用したライブラリに加えて以下をインストールしてください。LangchainでGemini APIを使うためのライブラリです。

langchain_google_genai

そしてコードは以下になります。

TEMPLATE = """
あなたは質問応答のアシスタントです。質問に答えるために、検索された文脈の以下の部分を使用してください。
答えがわからない場合は、わからないと答えましょう。

質問: {question}
コンテキスト: {context}
答え:
"""
DB_PATH = "./rag/chroma"

# モデルを準備
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash")
# プロンプトを設定
prompt = PromptTemplate.from_template(TEMPLATE)
# チェーンを準備
rag_chain = (prompt | llm)

# 直前のユーザの入力を取得(実際はWebアプリからの入力を取得)
user_input = "エレファントカシマシはどんなバンドですか?"

# ベクトル化する準備
embedding = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base"
)

# DBを読み込んで知識データ取得
vectorstore = Chroma(collection_name="elephants", persist_directory=DB_PATH, embedding_function=embedding)
docs = vectorstore.similarity_search(query=user_input, k=5)
context = "\n".join([f"Content:\n{doc.page_content}" for doc in docs])

# 返答を取得
response = rag_chain.invoke({"question": user_input, "context": context}).content

ここでもGemini APIを使いますがAPIキーはGOOGLE_API_KEYという名前で環境変数に入れておきます。
データベースの読み込みの部分では先ほど指定したcollection_nameとsqlite3ファイルが入ったフォルダのパスを指定します。
後は一般的なLangChainでのRAGの実装と同じかなと思います。

チャットボットアプリ上での表示

ここまで実装したアプリを使ってみました。アプリの機能として通常のGeminiの返答生成とRAGを使ったGeminiの返答生成を機能として実装しているので、出力結果を比較してみます。
ちなみにRAGで使用したデータベースはエレファントカシマシ関係のwikipediaをいくつか読み込んで作成したものになります。
質問文はいずれも「「俺たちの明日」という曲について教えてください。」としました。

  • 通常のGeminiの返答 そもそも誰の曲やねん、って感じですね。

  • RAGを使ったGeminiの返答 wikipediaに書かれていた情報を使ってそれらしき返答が出力されました。

今回のソースコードは下記のGitHubリポジトリにありますのでご参考ください。
といっても、まだ開発の途中段階なので修正される可能性はあります。(そのため今回は記事内にソースコードを書き込むように作成したのですが…。)

github.com

また今回参考にした記事は勉強段階のメモでTwitter(X)に残してあるのでそちらも参考になるかもです。

まだいろいろ試したいことはあるので、何かネタになりそうなら記事にしたいですね…!

HaggingFaceの物体検出モデルを試してみようの回

お疲れ様です。

HuggingFace(transformersライブラリ)から利用できる物体検出のDeepLearningモデルを試してみたのでその紹介です。
物体検出についてはざっくりというと画像内の物体を矩形(Bounding Box)で囲って検出するものになります。
下図の例では人間の顔を検出しています。

作成したソースコードはテンプレート化して公開しているので気になる方はこちらもご参照ください。

github.com

さて、HuggingFaceといえば自然言語処理のモデルが多いイメージかなと思いますが、VisionTransformerなどtransfomerアーキテクチャが使用された画像処理のモデルも多くあります。
transfomerを使った物体検出モデルでもおそらく一番有名であろうDETRを実装しました。 以下はHuggingFaceのDETRの紹介ページですがかなり参考になりました。DETRについての詳細な説明もこちらを見ていただければと思います。

huggingface.co

一応こちらにも説明を…(生成AIに書かせたやつです笑)

DETR (DEtection TRansformer) は、Facebook AI Research が開発した物体検出モデルで、Transformer アーキテクチャを使用する点が特徴です。従来の物体検出モデルは、複数の手法(アンカーボックス生成やNMSなど)を組み合わせる必要がありましたが、DETR はこれらを不要にし、シンプルなエンドツーエンドの学習を実現しています。

DETRは主に以下の2つのパートから構成されます:

  1. CNNバックボーン:ResNet などのCNNを用いて画像特徴量を抽出します。
  2. Transformer エンコーダ・デコーダ:エンコーダで画像の特徴をエンコードし、デコーダがクエリベースで物体の位置とカテゴリを予測します。

このアプローチにより、アンカーボックスや後処理の必要がなく、訓練が単純化され、様々な検出タスクで高い精度が得られるようになっています。

実装に関してはHuggingFaceモデルを使用した解説はほぼないのでライブラリのソースコードを解読することが多かったです。
以前作成したAlbumentationsのDataAugmentationを使いたかったのですが、これをうまく利用しようとするとめちゃくちゃ大変でした…。 参考になったサイトは以下の2サイト。とはいっても公式のチュートリアルとtorch.hubから読み込めるpretrainedモデルを使用した実装なので基本的には参考程度でした。

github.com

zenn.dev

紆余曲折あって実装したのが最初に公開したGitHubリポジトリにあります。
とりあえず完成したので実際に学習を回してみようということで、VOC2012の物体検出データセットを使ってお試しの学習をしてみました。

学習曲線
訓練データが5000枚、検証データが2500枚で100epoch回してみた結果です。
ベンチマーク用のデータセットなのでepoch数が少ないとあまり学習が進まないのでしょうか…。それ以前にあまり安定していないのも気になりますね。 パラメータチューニングはやっていないのでそれも影響はありそうです。
検出結果をいくつか載せておきます。ここには物体を検出できた結果のみを載せていますが、そもそも未検出の画像も結構ありました。

ちなみにDETRの改良版としてDeformable-DETRというモデルもあり、そちらも使えるように実装はしています。
ただVOC2012データセットで学習するとめちゃくちゃ時間がかかるため、学習は断念しました…。1epoch学習を完了するのに2時間はさすがに…笑 モデルの比較もしたいということで現在簡単なデータセットを作成中です。
またそちらも結果がでたら記事にしたいなと思っています。

Albumentationsの物体検出のDataAugmentationをいろいろ試す

お疲れ様です。

前々から試してみたかったAlbumentationsの物体検出用の処理を今回実際にやってみました。
Albumentationsとは何ぞや?という方は以下のサイトを参照ください。
albumentations.ai また、具体的にできることは以下をみると大体わかるかと思います。
(私もいつも見させてもらってます…!)
qiita.com

端的に言うと画像系AIの前処理(DataAugmentation)をまとめたライブラリという感じです。
基本的には画像系AIの中でも画像分類のタスクに使われることの多いイメージですが、物体検出やセグメンテーションも対応しています。(私は最近まで知らなくて物体検出の前処理を自作していました…。)
物体検出の前処理は画像への変換の適用に加えてアノテーションされたBBoxの座標も変換しないといけないので実装するとなるとかなりめんどくさいです。そこをライブラリ一つでできるとなればこれは使うしかない!…と。

以下、実際に試した結果の一部を載せていきます。

使用したソースコード(NoteBook)

作成の際に使用したコードはこちらにありますので以下にある前処理以外を試したい場合など必要に応じてご確認ください。 github.com

処理適用前の画像


幾何変換以外の処理をまとめて

幾何変換でない場合はBBoxの座標に影響がないのでまとめています。 今回は「ぼかし」、「ノイズ付加」、「輝度コントラストの調整」を適用しています。

幾何変換

Flip(反転)

今回は左右反転が適用されています。物体の反転に合わせてちゃんとBBoxの座標も動いていますね。

Crop(切り出し)

RandomCropでちょっと極端ですが元画像の半分のサイズに切り出すように設定しています。この場合はBBoxが見切れてしまっています…。

少し調べるとBBoxSafeRandomCropという物体検出用のRandomCropを発見しました。BBoxが見切れない範囲でランダムに切り出す処理になっているようです。実際に使う場合はこちらがベストですね。

Rotate(回転)

画像を回転する処理を適用。昔からの問題ではありますが、BBoxの座標も回転する影響でBBoxの範囲が広くなっていますね。 あまり大きく回転させると無駄な情報を学習してしまうのでそこは注意です。

Resize(サイズ変更)

モデルへの入力に合わせてサイズ変更はよくやる処理ですね。元画像からアスペクト比が変わってもそれに合わせてBBoxも調整されています。

古いバージョンのpytorch-lightningをインストールしようとして詰まったのでメモ【備忘録】

お疲れ様です。

業務内で古いバージョンのpytorch-lightningをインストールするときにエラー発生したので備忘録として残しておく。
原因としてはライブラリの問題ではなく、pipのバージョンが新しくなったことによるもののようです。
結論、pipをダウングレードしたらインストールできたという話なんですが…。
pytorch-lightningに限らずバージョンの古いライブラリをインストールする際に頭の片隅に置いておきたいです。

エラー内容

注目すべきは黄色で表示された警告の方です。
”*(アスタリスク)”が使えるのは"=="と"!="の演算子のみだという内容ですね。

WARNING: Ignoring version 1.6.0 of pytorch-lightning since it has invalid metadata:
Requested pytorch-lightning==1.6.0 from https://files.pythonhosted.org/packages/09/18/cee67f4849dea9a29b7af7cdf582246bcba9eaa73d9443e138a4172ec786/pytorch_lightning-1.6.0-py3-none-any.whl has invalid metadata: .* suffix can only be used with `==` or `!=` operators
    torch (>=1.8.*)
           ~~~~~~^
Please use pip<24.1 if you need to use this version.

実際pytorch-lightningの公式のGitHubを見るといろんなところでこの書き方がされているみたいです。 github.com

対処方法は上でも少し述べた通りpipのバージョンを下げることです。
v24.1以降で問題があるっぽいので、v23にでも下げましょう。

ダウングレード後、再度インストールでいけました。
同じように警告は出ます。内容としてはv24以降だと非推奨の書き方がされているよという内容。
とりあえずこちらとしてはインストールできればOKです。

DEPRECATION: pytorch-lightning 1.6.0 has a non-standard dependency specifier torch>=1.8.*. pip 24.0 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of pytorch-lightning or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063

ライブラリ経由で事前学習済みモデルをダウンロードする際のフォルダ指定

お疲れ様です。

Pythonディープラーニングのプログラムを書く際、その際に様々なライブラリにお世話になるかと思います。
その中で、モデルアーキテクチャの定義と同時に特定の事前学習済みモデルの重みファイルを自動でダウンロードしてくれる機能があるライブラリもあります。
それらダウンロードされる事前学習済みモデルのダウンロード先フォルダをこちらで指定する方法を調べたのでこちらにも備忘録としておいておきます。

私はなんとなくプロジェクトごとに使ったモデルがわかるようにしたくて、プロジェクトフォルダの直下にダウンロードした重みファイル用のフォルダを作って管理しています。
GitHubのリモートリポジトリにはpushされないように.gitignoreで指定はしていますが…。


目次


ダウンロードフォルダを指定しない場合

Windowsの場合、ユーザフォルダ直下に「.cache」というフォルダが作成され、その中に保存されます。
これはpytorch系やhuggingfaceいずれの場合も同じです。

ダウンロードフォルダの指定

torchvision, torch.hub

環境変数"TORCH_HOME"か、torch.hub.set_dirに保存先フォルダのパスを指定することで可能です。 環境変数の場合はコンソールでsetコマンドを使って指定することも可能かと思います。

os.environ["TORCH_HOME"] = "weights"
torch.hub.set_dir("weights")

余談ですが、torchvisionで事前学習モデルをダウンロードする際の指定の仕方が最近のバージョンでは変更されているようです。(最近になって知りました…。)

from torchvision import models

models.resnet50(pretrained=True) # 非推奨(2024/8現在は実行すると警告が出る)

models.resnet50(weights=models.ResNet50_Weights.DEFAULT) # 推奨の指定方法


Huggingface

環境変数"HF_HOME"を指定します。環境変数なのでsetコマンドでも可能です。

os.environ["HF_HOME"] = "weights"

こちらの場合少し注意が必要で、huggingfaceのライブラリ(transformers等)をimportする前にあらかじめ指定しておかないと反映されません。

import os
os.environ["HF_HOME"] = "weights" # 先に事前学習モデルの保存先を指定

from transformers import DetrConfig, DetrForObjectDetection

model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")


timm

ダウンロード先がPytorch系の場合とHuggingfaceの場合の2パターンがあるので、利用したいモデルに合わせて上記の方法をお試しください。
(最近だとHuggingfaceからのダウンロードが多いのかな…。)

Pythonで三目並べ対戦GUIを作ってみた

お疲れ様です。

「三目並べ」、いわゆる「〇×ゲーム」の対戦GUIPythonで作ったので簡単に紹介します。

GUIのイメージ

背景

元々は会社の技術系の勉強会で三目並べのAIを作ったことが始まりです。
教化学習の手法の1つであるQ学習を適用して作成したAIとMiniMax法を使ったルールベースの方法の両方を作成していました。
Q学習の方は下記のサイトのソースコードを使用しました。
Q学習の仕組みについても下記のサイトは非常に参考になりました。
qiita.com なお、MiniMax法に関してはChatGPTに元となるソースコードを作ってもらったので参考サイトはありません。

作成

それぞれ作ってみて実際にAI同士の対戦やAIと人間の対戦をしてみたい!ということで作成したQ学習とMiniMax法を組み込んだ対戦GUIを個人的に作成しました。
GUIの作成には、業務でよく使用するwxPtrhonを使用しました。

ソースコード

作成したコードは以下になります↓
勉強会で作成した学習や評価用のスクリプトも一緒に格納しています。
github.com

GUIのイメージ(初期)
上部のラジオボタンで各プレイヤー(〇、×)の対戦エージェントを指定し、開始でゲームを開始します。
Q学習AI、MiniMax法の他、ランダムで入力するエージェントと人手での入力を選択可能です。
人間が操作する場合は、盤面の区画をクリックすることでその部分に"〇"or"×"が入力されます。

所感

それぞれの対戦エージェントで試してみた感想ですが、やはりMiniMax法最強ですね…笑
というか、実装がすべての状態を総当たりで見てるので弱いはずがないんですが。

Q学習に関しては、100万回学習でもまだ人間が勝てる程度の強さにしかなりませんでした。
パラメータを調整したり学習回数を増やしたりしてもそれほど変化が無かったので、これくらいが限界なのか…。
この辺は機会があれば改良なんかもしてみたいです。

opencv-pythonの日本語の扱いについてメモ

お疲れ様です。

 

PythonOpenCVを使った画像処理のプログラムを書くことが多いのですが、日本語が絡んでくるとうまく動作しないことがよくあります。

その対処法をメモ的に残しておきます。

 

画像読み込み・保存(imread, imwrite)

cv2.imreadで画像を読み込むときやcv2.imwriteで画像を保存するときに、入力したパスに日本語が含まれていると読み込みor保存が正常にできない問題。

対処法は以下の通り。

‐ 画像読み込み

  • PillowのImage.openで読み込み、numpy.ndarrayに変換
  • numpy.fromfilecv2.imdecodeを使用して読み込み

- 画像保存

  • numpy.ndarrayからPillow形式に変換し、Image.saveで保存
  • cv2.imencodeとnumpyのtofileを使用して保存

【参考】

imagingsolution.net

 

文字列書き込み(puttext)

cv2ではputtextを使って画像内に文字を書き込むことができますが、これは英数字のみで日本語を書き込もうとすると文字化けします…。

対処法は以下の通り。

  1. 一度Pillow形式に変換
  2. ImageDrawDrawクラスのインスタンスを作成
  3. textメソッドを使用して文字列書き込み
  4. numpy.ndarrayに戻す

フォントもインストールされているもので日本語対応していれば変更可能です。

Windowsであれば"C:\Windows\Fonts"に格納されているかと思います。

ImageFont.truetypeでフォントのttcファイルとフォントサイズを設定して指定します。

Windowsのフォント格納場所

 

【参考】

monomonotech.jp

 

ウィンドウ表示(namedwindow, imshow)

ウィンドウを開いて画像を表示する機能ですが、ウィンドウタイトルを日本語で書くと文字化けしてしまいます。

OpenCV

調べた限りではcv2の設定で対処する方法はなさそうです。

ウィンドウタイトルのみの問題で画像に関しては普通に表示されるので、使用上は全く問題はないですが…。

どうしても必要という場合はPythonGUIライブラリでウィンドウ表示すればなんとかなります。ただ、クリックイベントなどcv2にある機能はGUIライブラリのイベントを使って個別に作成する必要があるのでそちらも勉強する必要がでてきます。

tkinter」と「wxPython」で作成してみたのでどうしても必要な場合はご参考ください。

コードはこちらを参照↓

github.com

【使用例】

tkinter

wxPython

tkinterのほうはPythonの標準ライブラリなので別でインストールする必要がないのでやるならこちらでしょうか…。

私個人としては、wxPythonに慣れているのでこちらのほうが作りやすいです。