地平線まで行ってくる。

記録あるいは忘備録。時には検討事項。

Difyでunstructuredを・・・(失敗中)

RAGの精度を上げるにはデータベースの質だろう・・・ということで、community版にUnstructuredを組み込む。

 

docker-compose.yamlに追加する。

api環境変数を追加

ETL_TYPE: Unstructured

UNSTRUCTURED_API_URL: http://unstructured:8000/general/v0/general

workerに環境変数を追加

ETL_TYPE: Unstructured

UNSTRUCTURED_API_URL: http://unstructured:8000/general/v0/general

imageを追加

unstructured:

    image: downloads.unstructured.io/unstructured-io/unstructured-api:latest

    restart: always

 

 

うーん。うまく行きませんでした。
何か見落とし・・?

Dify(docker compose)+Ollama(docker)を試す。

WSLを使って動作。おそらく、ubuntuなどのLinux全般同じようにきる・・・と思う。

 

ollamaのdockerでの動かし方:

hub.docker.com

 

GPUを利用するならば、GPU周りの設定を実施した後に、以下で起動。

docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

 

今回使ったのは以下のモデル。小さなモデルでどの程度動作するのか確認したかった。

docker exec -it ollama ollama run hawkclaws/japanese-stablelm-2-instruct-1_6b

 

次に、Difyを動かす。

docs.dify.ai

 

Ollamaと同じマシンで動かしているため、OllamaのBASE URLをDifyに設定しやすいように、Difyのdocker-compose.yamlにhost側を見る様に設定する。

docker-compose.yamlのservices:api:以下にextra_hostsを加える。

    extra_hosts:
      - host.docker.internal:host-gateway

Docker詳しくないけど、これで動いたので、たぶん大丈夫。もちろん、ホストのIPを調べておいて記入しても大丈夫だと思います。これが正しいやり方なのかわかりませんが、他にはdocker compose.yaml中にollama部分も入れ混んじゃうという手もあるのかもしれません。そのあたりは本職さんじゃないので動けばOK。

 

最終的にDify上のモデル設定ではollamaを選んだ後に・・・

Model Name:hawkclaws/japanese-stablelm-2-instruct-1_6b

Base URL:http://host.docker.internal:11434

で、動作しました。

 

japanese-stablelm-2-instruct-1_6bでも、そこそこ動くのも確認できました。もちろん、gpt-4には及びませんが、タスクによっては使えそうです。

 

 

 

 

 

DataPilot-ArrowPro-7B-KUJIRA-gguf+ollamaを試す。

DataPilot-ArrowPro-7B-KUJIRAは、7Bクラスでよい成績をたたき出しているモデル。mmngaさんがさっそくggulfを公開していただいているので、Q4_K_Mで試して見る。もうすっかり皆さんが試しているので、ollamaを使います。

 

ascii.jp

高校生ということでも話題。それも凄いことなんだけれども、恐れずにやってみるという精神は見習いたい。

 

モデルはこちら。

huggingface.co

 

7Bとは思えないしっかりした答えが得られた。このクラスのモデルがどんどん進化していて、ローカルでの活用が要約の指示が反映されにくかったが、言葉として自然な感じで答えてくれる。

 

意地悪して、算数をさせてみた。

1+1*2の答えは何?

 1から始まるこの式、まずは計算する順番に従わなければいけません。日本の算数乗り入れグループでは、かけ算が先に行われるべきだとされています。なので、以下の通り計算します。
正しい順番:1 + (1 * 2)
まずは、かけ算を先に行いましょう。
1 + (1 * 2) = 1 + (2)
最初のかけ算、1 * 2ですが、これはすでに結果が出ているため、簡単に計算しましょう。
1 * 2 = 2
さて、元の式に戻って加えます。
1 + 2 = 3
したがって、1 + 1 * 2の答えは3です。

お、凄い。丁寧に段階的に考えて答えてくれる。

 

さらに・・・

1+1*2-2*5を計算してください。

まずは、親術優先(先に乗算および除算を行う)に従って計算を進めます。与えられた式1+1*2-2*5は少し分かりにくいので、先にそれぞれの乗算と倍率を計算します。
1 + 1 * 2 - 2 * 5を順番に見ていきましょう。
まずは乗算から:
1 * 2 = 2(これはただの1桁の加算なので、先に計算できる)
次に倍率が大きい方の乗算:
2 * 5 = 10(こちらもすぐにわかる値)

今度は計算しやすいように式を変形できます:
原始的な順序の通りに行うと、以下の手順になります。

1 1をそのまま残す。
2 2 * 5 = 10(乗算)
3 1 * 2 = 2(乗算)
4 最後に残った加算、2 (結果の乗算) - 10 (もうひとつの乗算) - 2を計算する。

実際に計算します:
残りの加算、2 + 10(2 * 5から1サイクル前の結果) + 2を加える前の状態で、私たちはすでに持っている乗算の結果だけで合計があります。つまり、

2(1 * 2の結果)と10(2 * 5の結果)を加えて、合計を出します。

2 + 10 = 12(加算後の結果)

頑張るけど厳しい。

 

悔しいので上記を見直してもらいます。

正しい答えを導き出すために、先に行った乗算の計算結果を式に戻してください。

申し訳ありませんが、前の説明では誤解を招く部分がありました。正しい手順で計算すると以下の通りです:

1 + 1 * 2の計算順に進めます。
1 + (2 * 5)の式が正しいです。先に乗算を行います。

1 * 2 = 2(これは既に計算済み)
2 * 5 = 10(こちらも計算済み)

次に、正しい順序で加減算を行います。
1 + (2 + 10)の式になりますが、先に括弧内の乗算を計算することがルールです。修正して、正しい順序で計算します。

2 + 10 = 12(既に計算済み)
そして、1 + (2 + 10)の式を使って、さらに加算を行います。

実際に行う加算は、もう一度2に12を加えることになります。

2 + 12 = 14

したがって、正しい答えは14です。

おー、頑張りましたが、計算途中は怪しいところも。

 

楽しい!

 

gist.github.com

ollamaをさらに気軽に試す。

ローカルLLMで遊ぶのにとても便利なollama。Colabでもお試ししたいと思っていたら、ラッパーを見つけました。

 

github.com

 

ollamaではモデルファイルを定義しておいて、それぞれの言語モデルの設定を行います。この機能が当初なかったので要望を出したら通りました。早速、試しました。

 

新しいモデルをいち早くollama経由で使う前に味見やモデルファイルの確認ができるのは良い点だと思います。

 

gist.github.com

ollamaとstreamlitを使ってローカルLLMで遊ぶ

簡単にローカルでLLMが動かせるollamaとstreamlitを使ったチャットボットを作ってみた。ごくごく基本的なものだけど、作ってみないと感触も分からない。

 

そこで、

github.com

作ってみた。

 

確認できたのは、Streamのやり方と、いつ追加になったのか分からないStreamlitChatMessageHistoryという便利な機能が勉強になりました。この辺りは、当初からするとどんどんすっきりした実装になりました。

Phi-3-mini-4k-instruct-dpoを動かしてみる #2

弱々環境でもどうにか動いて結構賢そうだったphi3の追加学習したモデルでもう少し遊んでみました。モデル作った方々ありがとうございます。

 

huggingface.co

 

ggufモデルは、こちらの方が良いようなので、こちらがらモデルをダウンロードしました。

huggingface.co

 

GPUNVIDIA GeForce GTX 1650 (4GB)で、動作中は3677MiB/4096MiBと言った利用率でした。フレームワークはstreamlitを使いました。OpenWebuiとか使うとコードを書く必要ありませんが、今回は勉強のためにlangchainを使ってみました。こんな感じで正解なのかどうか・・・。

 

github.com

 

とりあえず、会話はできるようになった。これは楽しいぞ。

 

このモデルは、時々素に戻って英語がまじちゃう時がある。今後もファインチューニングが進むのだろうから、非常に楽しみ。

 

Phi-3-mini-4k-instruct-dpoをためしてみる。

LOCAL AI HACKATHON #001でPhi-3-miniを鍛えなおしたモデルをためしてみました。

 

huggingface.co

 

知識は流石にいまいちの様ですが、サクサク答えてくれます。

与えられた質問に対して英語で思考し、日本語で答えてください。日本で二番目に高い山を検討して答えてください。

日本で二番目に高い山は富士山の北山です。富士山が最高い山であり、その南山はより高く、円山、成峰、竜巻などの頂上がそれにあたりますが、全体を通して二番目の高さは北山です。しかし、富士山は一つの山ですので、実際の二番目に高い山という意味ではありませんが、この質問での解釈が説明されています。適切な答えを提供するためには、実際の二番目に高い山があれば必要ですが、富士山の北山をこの質問の文脈で提示しました。

要約もさせてみます。

与えられた質問に対して英語で思考し、日本語で答えてください。\n以下を簡単にまとめてください。ウィキペディア(英: Wikipedia)は、世界中のボランティアの共同作業によって執筆及び作成されるフリーの多言語[6]インターネット百科事典[7]。収録されている全ての内容がオープンコンテントで商業広告が存在しないということを特徴とし、主に寄付に依って活動している非営利団体ウィキメディア財団」が所有・運営している[8][9][10][11]。「ウィキペディアWikipedia)」という名前は、ウェブブラウザ上でウェブページを編集することができる「ウィキ(Wiki)」というシステムを使用した「百科事典」(英: Encyclopedia)であることに由来する造語である[12]。設立者の1人であるラリー・サンガーにより命名された[13][14]。\n\n

 Wikipediaウィキペディア)は、世界各国の多言語ユーザーが編集するフリーのオープンコンテンツ百科事典です。ウィキメディア財団が管理しており、ウィキペディアの名前は、ウィキ型システムを用いた百科事典の概念から来ています。この共同作業が続いているのは、ウィキメディア財団の支援を得たボランティアです。

Wikipediaから引用した文章を要約させました。)

少し不自然ですが、いい感じに要約できます。

 

算数は苦手だろうと思って意地悪をしてみます。

与えられた質問に対して英語で思考し、日本語で答えてください。1+1*2の答えは?

1加1乗2は、先頭に1を足し、その後に2乗する規則に従います。したがって、1加1は2です。そして、その結果2乗すると2^2、つまり4になります。よって、1+1*2の答えは4です。

間違っちゃいました。自ら間違いを訂正してもらいます。

'与えられた質問に対して英語で思考し、日本語で答えてください。以下の考えは正しいのかチェックしてください。: 1加1乗2は、先頭に1を足し、その後に2乗する規則に従います。したがって、1加1は2です。そして、その結果2乗すると2^2、つまり4になります。よって、1+1*2の答えは4です。

いいえ、その考え方は間違っています。1加1の計算は1+1ですが、これは2になります。次に、その結果2を2乗すると、2^2です。したがって、1+1*2の計算を正しく行うと、計算順序に注意を払う必要があります。1加1*2は次のように計算されます:1+1*2 = 1+(2) = 3です。この計算では、括弧内の計算が先に行われ、その結果に基づいて最終的な計算が行われます。したがって、正しい答えは3です。<|end|>'

正しい答えを導きました。なかなかやるなぁ・・・。

 

日本語での受け答えも安定している印象です。いつか、こういうローカルLLM作りに挑戦したくなってきました。

 

gist.github.com

Ninja-v1-NSFW-128kを試してみる。

小説を書かせるローカルモデルであるNinjaを試して見ました。

 

huggingface.co

 

スクリプトはサンプルそのままで実行してみると・・・。

 

冒頭はこの調子でいい感じです。

大航海時代に海に出る若き海賊の英雄物語を書いてください。
西暦1500年代、大海原には海賊がうようにいた。彼らは資源を持ち去り、略奪行為を繰り返す悪名高き集団だったが、その中にも一人、違った道を歩む若き海賊がいた。
その名はウィリアム・キッド。彼はただの海賊ではない、才能と正義感に溢れた海賊であった。
ウィリアムは小さい頃から航海への憧れが強く、生まれ育った町の海に今日も漕ぎ出た。しかし、彼の心には決意があった。それは、弱者を助け、真っ当な海賊となることだった。
ある日のこと、彼は通りすがりの貧乏な漁師たちが、自分たちの舟を奪われる場面に遭遇した。彼らは自分たちの生活を守ろうと必死に抵抗したが、相手は数人の海賊たち。勝ち目はなかった。
だが、ウィリアムは違った。強い正義感に駆られ、彼は救援に向かった。自らの舟を寄越し、彼らを守ろうと決めたのだ。
「お前たち、そこにいるな!」
ウィリアムは力強い声で叫び、海賊たちへと突撃した。手には剣を握り、海賊たちを撃退するべく戦った。戦いは激しかったが、ウィリアムの才能と勇気、そして正義感が勝った。見事に海賊たちを打ち倒し、漁師たちは自分たちの舟を取り戻すことができた。
(まだまだ文章は続きます)

続きは、以下のgithub

 

意味的には結構繋がっていて読める文章が生成されます。プロンプトのノウハウが溜まれば、もっと思い通りの世界がかけそう。ストーリーが変化するアドベンチャーゲームなんてすぐできそうな気がする。ローカルでも動かせるとは・・・。

 

 

gist.github.com

Colab AIさん、ライセンスに目覚める。

Colab AIさんが提案したコードの元ネタを表示するように・・・。

短く当たり前でかつ他にも類似コードは数多くあるので問題ないだろうと自己責任で進める。でも、こんなコード過去にも沢山見たし、指摘された元ネタ見るとライブラリそのもののgithub。サンプルコードっぽい。

 

判断を任せるのはいいと思うけれども、元ネタのライセンスも表示してほしい。便利な反面、世知辛さもありますね・・・。

RAGとRetriever精度比較とHybrid Search

詳しい記事があったので、気になっていたHybrid Searchを試した。

 

参考記事

www.jiang.jp

 

FAISSと同じだろうと思いつつChromaも評価用のコードに加えました。embeddingモデルはmultilingual-e5-largeを使いました。

 

from langchain.vectorstores import FAISS
from langchain.vectorstores import Chroma
# from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import KNNRetriever
from langchain.retrievers import SVMRetriever
from langchain.retrievers import TFIDFRetriever
from langchain.retrievers import ElasticSearchBM25Retriever

embedding = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-large"
    )

# Vectorstore
faiss_vectorstore = FAISS.from_texts(documents, embedding)
chroma_vectorstore = Chroma.from_texts(documents, embedding)
svm_retriever = SVMRetriever.from_texts(documents, embedding)
svm_retriever.k = DOC_NUM

# Evaluate
# Faiss
faiss_similarity_result = evaluate(query_list, lambda q: faiss_vectorstore.similarity_search(q, k=DOC_NUM))
faiss_mmr_result = evaluate(query_list, lambda q: faiss_vectorstore.max_marginal_relevance_search(q, k=DOC_NUM))
# Chroma
chroma_similarity_result = evaluate(query_list, lambda q: chroma_vectorstore.similarity_search(q, k=DOC_NUM))
chroma_mmr_result = evaluate(query_list, lambda q: chroma_vectorstore.max_marginal_relevance_search(q, k=DOC_NUM))
# SVM
svm_result = evaluate(query_list, lambda q: svm_retriever.get_relevant_documents(q))

result_df = pd.DataFrame(
  [
    ["faiss_similarity", faiss_similarity_result.mrr, faiss_similarity_result.recall_at_1, faiss_similarity_result.recall_at_5],
    ["faiss_mmr", faiss_mmr_result.mrr, faiss_mmr_result.recall_at_1, faiss_mmr_result.recall_at_5],
    ["chroma_similarity", chroma_similarity_result.mrr, chroma_similarity_result.recall_at_1, chroma_similarity_result.recall_at_5],
    ["chroma_mmr", chroma_mmr_result.mrr, chroma_mmr_result.recall_at_1, chroma_mmr_result.recall_at_5],
    ["svm", svm_result.mrr, svm_result.recall_at_1, svm_result.recall_at_5],
    ],
  columns = ["model_id","mrr","recall_at_1","recall_at_5"]
  ).sort_values("mrr", ascending=False)

 

結果

 

記事通りにHybridが良い。faissとchromaは大きな違いはないだろうとおもったが、mmrについてはFaissとchrmaの大きく異なる結果になった。気になるくらい大きな差だ。試して見て良かった。個々の部分は、たまたま当方のスクリプトの問題かもしないけれども、気に留めておくことにする。