[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

FastAPIでファイルダウンロードのAPIを作成するコードのメモ

お疲れ様です。

業務内でFastAPIを使ってcsvファイルやexcelファイルをダウンロードするAPIを作成したので、忘れないうちにメモ。
最近はWeb系のコード作成も増えていて使いどころも多くなってくるのかなと思っています。

作成コード(GitHub

github.com

実行して立ち上がったサーバにアクセスすると下記のようなWebUIが表示されます。
ボタンを押すと適当な内容が記載されたサンプルのcsvファイルかexcelファイルがダウンロードされます。

実装

FastAPIのresponse関係の仕様は公式ドキュメント参照。
fastapi.tiangolo.com

FileResponseを使用したパターン

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get("/download_from_file")
def download_from_file() -> FileResponse:
    """ファイルをダウンロードするAPI
    """
    return FileResponse(filepath, media_type="file/csv", filename=filepath.name)

シンプルにFastAPIの実行環境のローカルに保存されたファイルのパスを指定してそのファイルをダウンロードする形です。Pythonコードで作成したデータを一度ファイル出力してこれで返す形も取れます。

StreamingResponseを使用したパターン

import io
import pandas as pd

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

@app.get("/download_from_df")
def download_from_df(ext: str) -> StreamingResponse:
    """DataFrameを指定ファイル形式でダウンロードするAPI
       (ローカルにファイル保存せず、データをファイル化して返す)
    """
    if ext == "csv":
        stream = io.StringIO()
        sample_df.to_csv(stream, encoding='utf-8', index=False)
        stream.seek(0)
    elif ext == "xlsx":
        stream = io.BytesIO()
        sample_df.to_excel(stream, index=False)
        stream.seek(0)
    else:
        raise HTTPException(status_code=400, detail="Illegal extension.")
        
    filename = f"sample.{ext}"
    media_type = f"file/{ext}"
    
    return StreamingResponse(
        content=stream, 
        media_type=media_type,
        headers={'Content-Disposition': f'attachment; filename={filename}'}
    )

Pythonコード内で保持しているデータをローカルへのファイル出力をせずにダウンロードする方法です。ローカルに無駄なファイルが残らないのがメリットでしょうか。
Pythonの標準モジュールの「io」を使用してfile object?(正式な名前はわかりません)に変換してそれをresponseする形です。csvファイルはio.StringIO()excelファイルはio.BytesIOを使います。ファイルによって変わるようなのでここは要確認です。
コードではpandasのDataFrameをcsvファイルまたはexcelファイルとしてダウンロードできるように作成しています。また、少し捻ってクエリパラメータからダウンロードするファイルを指定する形で作成してみました。

ioモジュールについては以下公式ドキュメント参照。
組み込みのopen関数で使用されているものだとか。(私はそれほど意識したことはなく今回初めて知りました…。)
この辺の標準モジュールについても理解を深めたいなと思う今日この頃です。
docs.python.org

Pythonで簡易的なGUI作成ができるライブラリ「Gooey」

お疲れ様です。
今回はちょっとしたライブラリ紹介的な記事です。

Pythonで使用できる「Gooey」というライブラリをですが、Pythonスクリプトを簡単にGUI化できます。結構前から使っていたのですが日本語の記事がほぼ無いのでせっかくなのでまとめようと思います。
(自分用の備忘録としても残しておきたいといういつもの動機です笑)

Gooeyの公式GitHubリポジトリはこちらです。
github.com

また、公式の実装例もありますので気になる方はこちら。
github.com


Gooeyの概要

Pythonの標準モジュールの「argparse」を使用し、コマンドライン引数にあたる部分を入力するフォームと、実際に実行されるときのコマンドライン上のログを表示するGUIを作成するというライブラリです。
詳しくは下記で実装しているのでご参考に。

例えば、argparseを使って書かれた下記のようなコードがある場合

from argparse import ArgumentParser

def main() -> None:
    parser = ArgumentParser(description="サンプルプログラムです。")

    parser.add_argument("number", type=int, help='数値を入力')
    parser.add_argument("-s", "--string", type=str, default="サンプル文字列", help='文字列を入力')
    parser.add_argument("--flag", action='store_true')    
    parser.add_argument("--choice", default="Google", choices=['Google', 'Apple', 'Facebook', 'Amazon'])
    parser.add_argument("--list", nargs='*', 
                        choices=['Microsoft', 'Amazon', 'Tesla', 'Alphabet', 'NVIDIA', 'Apple'])

    args = parser.parse_args()  

    print("number", args.number)
    print("string", args.string)
    print("flag", args.flag)
    print("choice", args.choice)
    print("list", args.list)

if __name__ == "__main__":
    main()

実行する場合、例えば下記のコマンドなら出力は下図のようになります。

python sample.py 2025 --list Microsoft

これをGooeyを使用してGUI化すると、コマンド入力の部分が下図の[入力画面]、実行結果が下図の[実行画面]となります。
[入力画面]
[実行画面]
実装に関しては先ほどのargparseのサンプルコードのmain関数にデコレータで

@Gooey

と付けるのみです。

from argparse import ArgumentParser
from gooey import Gooey

@Gooey 
def main() -> None:
    parser = ArgumentParser(description="サンプルプログラムです。")

    # 以下同様のコード…

Gooeyを使うメリット・デメリット

基本的には「簡易的にGUI化できる」という1点にメリットが集約されているというのが個人的な認識です。プロトタイプの作成なんかに便利です。
デメリットで一番大きいのが「カスタマイズ性が低い」ことでしょうか…。概要で説明した形の機能しか基本使えません。ベースがwxPythonなのでライブラリのソースを改造することもできるかもしれませんが試したことはないです。

👍ポイント

  • 簡易的なGUIを簡単に作成できる
  • 導入も簡単(pip installするだけ)

👎ポイント

  • カスタマイズ性が低い
  • argparseについての知識は必要
  • ライブラリの更新が止まっている

実際に実装する場合

公式GitHubのREADMEにかなり詳しく書かれているのでそちら参照。 また、参考サイトは海外のサイトですが、各フォームの細かい設定などめちゃくちゃ詳しく解説してくれているので深く知りたい方はこちらを見るのが良いと思います。(ただしちょっと情報が古いので使えない内容もあるかも…。)
基本的には下記のサイトで事足りますが、日本語化の部分だけメモ的な形で残しておきます。

参考サイト

pakstech.com

日本語化

日本語化自体は元から可能ですが日本語がかなり怪しかったり、ところどころ日本語化できてないところがあります。 修正する場合はインストールしたGooeyライブラリのフォルダ内にある「japanese.json」を編集します。

site-packages\gooey\languages\japanese.json

もしくは自身で言語ファイルを作成してプロジェクトフォルダ内に配置しておくことも可能です。下記は実行ファイルと同じ階層に言語フォルダを配置した例です。

from argparse import ArgumentParser
from gooey import Gooey

@Gooey(
    language="japanese",
    language_dir="./language"
)
def main() -> None:
    parser = ArgumentParser(description="サンプルプログラムです。")

    # 以下同様のコード…

ざっくりと直してみました。

ソースコード

私が作成したコードは下記に残していますので、必要に応じてご確認ください。
github.com

話題のRAdamScheduleFreeをざっくり試す

お疲れ様です。

今回は機械学習の界隈で話題のRAdamScheduleFreeという新しいoptimizerを試したいと思います。 なんでもAdamWと同等かそれ以上の性能だとか…!
詳しい内容は作成者さんのZennをご確認ください。今回私がやるのはとりあえずの実装のみ…。

zenn.dev

条件設定(実行環境、使用モデルなど)

実行環境は以下になります。

CPU: 13th Gen Intel(R) Core(TM) i7-13700F 2.10 GHz
メモリ: 32 GB
GPU: NVIDIA GeForce RTX 4060 Ti
VRAM: 16GB
OS: Windows11 Pro

使用するモデルは物体検出モデルのDETRです。これまで私が実装してきたものです。
また、データセットはVOC2012の物体検出用データセットを使用しました。

fallpoke-tech.hatenadiary.jp

ソースコードはこちらにあります。実装も反映済みです。

github.com

学習設定は以下になります。比較のモデルでも同じ設定を使用しています。

[parameters]
num_epoches = 100
batch_size = 32
classes = [
    "person", "bird", "cat", "cow", "dog", "horse", "sheep", 
    "aeroplane", "bicycle", "boat", "bus", "car", "motorbike", 
    "train", "bottle", "chair", "diningtable", 
    "pottedplant", "sofa", "tvmonitor"
]

# 入力画像サイズ: (height, width)
input_size = [512, 512]

# データセットの形式("coco" or "pascal_voc")
dataset_type = "pascal_voc"

[optimizer]
lr = 1e-4
lr_backbone = 1e-5
weight_decay = 1e-4

実装

今回使用するRAdamScheduleFreeはすでにMetaのScheduleFreeのGitHubリポジトリにマージ済みです。実装自体もすごく簡単でしたね…!
MNISTの画像分類に適用したサンプルコードがあるので、こちらを参考に実装しました。

github.com

結果

  • 学習曲線
  • 推論画像の一部

比較(AdamWでの結果)

  • 学習曲線
  • 推論画像の一部

所感

AdamWと比べてめちゃくちゃ安定していたことにまず驚きました。(AdamW側の学習率の設定が適正でなかった可能性もありますが…。)学習が安定した分最終的な精度もAdamWと比べてよかったように思います。ここはちゃんと評価指標で算出した方がよいのですが、実装がまだなので…。
実装は本当に簡単だったので今ある訓練用のソースのoptimizerを変えるだけですぐ試せてしまいますね…!
社内でも結構話題になっており、検証の段階ですが使ってみたいという声も結構出ている印象です。 これまで主流だったAdamにとって代わるのか今から楽しみです。

HuggingFaceの物体検出モデルを片っ端から試す回

お疲れ様です。

以前の記事でHuggingFace(transformersライブラリ)から利用できるDETRという物体検出モデルを実装しました。
今回はDETR以外の物体検出モデルを使ってみようと思います。

前回

fallpoke-tech.hatenadiary.jp

ソースコード

ソースコードは前回作成のものに追加する形で作成しています。
今回紹介するモデルはconfig/train_config.tomlのmodel_nameを変更することで切り替え可能です。

github.com

使用するデータセット

VOCデータセットの画像を使用して人間の顔を新たにアノテーションして作成した「顔検出データセット」を使用します。
(実は前回の段階でチラ見せはしていました…。)
124枚分データを作成し、trainデータを100枚、validationデータを20枚で学習を実行し、残りの4枚をtestデータとして学習済みモデルの評価に使用しました。

PCのスペック

参考程度に実行したPCのスペックも記載しておきます。

CPU: 13th Gen Intel(R) Core(TM) i7-13700F 2.10 GHz
メモリ: 32 GB
GPU: NVIDIA GeForce RTX 4060 Ti
VRAM: 16GB
OS: Windows11 Pro

HuggingFaceの物体検出モデル

使用できるモデルアーキテクチャは以下の4つです。
それぞれの紹介と実際に上記の顔検出データセットを使った学習の結果を載せておきます。上が学習曲線(Loss)のグラフ、下がlatestモデルでのテスト結果になります。
なお、学習のパラメータ設定は統一しています。


DETR

huggingface.co

  • 学習結果


Deformable DETR

huggingface.co

  • 学習結果


DETA

huggingface.co

  • 学習結果


Conditional DETR

huggingface.co

  • 学習結果

所感

学習の進行や実行時間に差はありましたが、テスト結果にほぼほぼ差が無いですね…。
データセットが簡単すぎたのかもしれません🤔
実行時間に関してはDETR<Conditional DETR<Deformable DETR<DETAという感じでした。
ちょっと今回のようなテスト結果だけでの比較だとわかりにくいので、評価指標を使っての評価もするべきだったなぁと思っています。次の課題としますかね。

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も調整されています。