QEUR23_CHRLTM40 : (予備実験)セクシーなCONTEXT抽出のための新しい戦略
~ BONSAI3は、「過激(セクシー)」です! ~
D先生(設定年齢65歳): “今回から、「BONSAI3」に移行します。・・・で、FOUNDER・・・。BONSAI3のキーワードは何ですか?”
QEU:FOUNDER(設定年齢65歳) : “BONSAI3のキャッチフレーズは、「より過激に、よりセクシーに」です。まずは、BONSAI2の解析フロー図をみてみましょう。”
QEU:FOUNDER(設定年齢65歳) : “これが、BONSAI3では、以下のように変わります。”
D先生(設定年齢65歳): “おっ!?**「セクシー(SEXY)」とな?”
QEU:FOUNDER : “小生は、「わくわく」したいの・・・・(笑)。そこで、以下のようなPOV(POINT_OF_VEW)を設定しました。”
D先生: “このPOV群の内容は、今回のテーマ(「G7 vs BRICS」)とは文脈が外れていませんか?”
QEU:FOUNDER : “いや、テーマから外れているからこそ「セクシー」なんです。ユーザーが自分の第六感で、「ようわからんが関係あるかもしれん・・・」と思ったものならば、なんでもPOVに入れてもいいんです。それでは、プログラムにいくよ。Unslothを使います。インプット情報は、STEP1で生成したQuestion群です。”
# Installs Unsloth, Xformers (Flash Attention) and all other packages!
!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
!pip install --no-deps "xformers<0.0.27" "trl<0.9.0" peft accelerate bitsandbytes
!pip install -U langchain chromadb sentence-transformers cohere
!pip install -U langchain-core langchain-community
D先生: “例によって、unslothのインストール方法は、ユーザーの計算環境によって変わりますね。これは参考にすぎません。あと、今回はLangChainですね。Llama-indexでもいいとは思うけど・・・。”
# -----
# EXCELファイルから必要な情報を読み込む
# -----
import re, math
import pandas as pd
import numpy as np
import json
# Excelファイルからデータフレームを作成する(FACT BASE QUESTION)
df_FBQ = pd.read_excel('qq_GVB_step1_FBQ_similarity_BONSAI3_DIFF.xlsx')
df_FBQ
# ---
num_df_FBQ = len(df_FBQ)
mx_FBQA = df_FBQ.loc[:,"FBQA1":"FBQA5"].values
mx_FBQB = df_FBQ.loc[:,"FBQB1":"FBQB5"].values
arr_QInitial = df_FBQ.loc[:,"QInitial"].values
arr_FBQ_A = df_FBQ.loc[:,"FBQ_A"].values
arr_FBQ_B = df_FBQ.loc[:,"FBQ_B"].values
print(mx_FBQA[0:5,0])
# ---
arr_FBQ = []
for i in range(num_df_FBQ):
index_FBQ_A = arr_FBQ_A[i]
index_FBQ_B = arr_FBQ_B[i]
str_FBQA = mx_FBQA[i,index_FBQ_A]
str_FBQB = mx_FBQB[i,index_FBQ_B]
arr_FBQ.append(str_FBQA)
arr_FBQ.append(str_FBQB)
print(arr_FBQ[0:5])
# ---
# Excelファイルからデータフレームを作成する(POINT OF VIEW)
df_POV = pd.read_excel('list_of_POV.xlsx')
df_POV
# ---
num_df_POV = len(df_POV)
arr_POV = df_POV.loc[:,"POV"].values
#print(arr_POV[0:5])
QEU:FOUNDER : “ここまでで、EXCELファイルから必要な情報を読み込んでいます。次にUnslothによる推論にいきましょう。”
# ----
from unsloth import FastLanguageModel
max_seq_length = 2048 # Choose any! We auto support RoPE Scaling internally!
dtype = None # None for auto detection. Float16 for Tesla T4, V100, Bfloat16 for Ampere+
load_in_4bit = False # Use 4bit quantization to reduce memory usage. Can be False.
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "drive/MyDrive/unsloth_lora_nemo_0810", # YOUR MODEL YOU USED FOR TRAINING
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
attn_implementation="flash_attention_2",
)
FastLanguageModel.for_inference(model)
# ---
from transformers import TextStreamer
# ---
def unsloth_answer(str_instruction, str_input):
# alpaca_prompt_without_context
alpaca_prompt_wo_context = """You are an excellent AI assistant. Below is an instruction that describes a task, paired with an input that provides further information. The instruction and input are question provided by your user. Write a response appropriately your user's question.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
# ---
inputs = tokenizer(
[
alpaca_prompt_wo_context.format(
str_instruction, # instruction
str_input, # input
"", # output - leave this blank for generation!
)
], return_tensors = "pt").to("cuda")
# ---
text_streamer = TextStreamer(tokenizer, skip_prompt = True)
outputs = model.generate(input_ids = inputs.input_ids, attention_mask = inputs.attention_mask,
streamer = text_streamer, max_new_tokens = 512, pad_token_id = tokeniz-er.eos_token_id)
response = tokenizer.batch_decode(outputs)
# 「### Response:」以降の文字列のみを出力
response = response[0]
response_start = response.find("### Response:") + len("### Response:")
response_text = response[response_start:].strip()
return alpaca_prompt_wo_context, response_text
# ---
# 質問に対して回答する
str_instruction = arr_QInitial[2]
str_input = ""
prompt, response = unsloth_answer(str_instruction, str_input)
print("--- str_instruction ---")
print(str_instruction)
print("\n\n")
print("--- prompt ---")
print(prompt)
print("\n\n")
print("--- response ---")
print(response)
D先生: “推論の結果の紹介は?”
QEU:FOUNDER : “ないよ。今回の本題とは関係がないからです。これからが本番です。”
# ---
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader, DirectoryLoader
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain import HuggingFacePipeline
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate
# ---
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=512)
llm = HuggingFacePipeline(pipeline=pipe)
# ---
# paths
TXTs_path = 'drive/MyDrive/input_txts'
Embeddings_path = 'drive/MyDrive/input_txts/vectordb'
# ---
# [TXT]Load data
text_loader_kwargs = {'autodetect_encoding': True}
loader = DirectoryLoader(
TXTs_path,
glob = "./*.txt",
loader_cls = TextLoader,
show_progress = True,
use_multithreading = True,
loader_kwargs=text_loader_kwargs
)
documents = loader.load()
# ---
print(f'We have {len(documents)} pages in total')
from langchain.embeddings import HuggingFaceBgeEmbeddings
embeddings = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-base-en",
model_kwargs={'device': 'cuda'},
encode_kwargs={'normalize_embeddings': True}
)
# ---
# (↓)2回目以降は不要(↓)
# ---
# Split data into chunks
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 1000,
chunk_overlap = 60,
length_function = len,
add_start_index = True,
)
chunks = text_splitter.split_documents(documents)
# ---
# Store data into database
db=Chroma.from_documents(chunks,embedding=embeddings,persist_directory=Embeddings_path)
# ---
# Helper function for printing docs
def pretty_print_docs(docs):
print(
f"\n{'-' * 100}\n".join(
[f"Document {i+1}:\n\n" + d.page_content for i, d in enumerate(docs)]
)
)
# ---
# Load the database
vectordb = Chroma(persist_directory=Embeddings_path, embedding_function = embeddings)
# ---
# Load the retriver
retriever = vectordb.as_retriever(search_kwargs = {"k" : 10})
query = arr_QInitial[2]
docs = retriever.invoke(query)
print("--- query ---")
print(query)
print("\n\n")
print("--- answer ---")
pretty_print_docs(docs)
QEU:FOUNDER : “出力をEXCELファイルにまとめました。RAG文書から抜き出したCHUNKは10件です。スコアで並べ替えています。J語翻訳しているが、本来はE語入出力だからね。”
D先生: “このスコアは、「CHUNKとクエリの類似度」ですね。普通のRAG推論は、この上位スコアのCHUNKを1個~3個ぐらいCONTEXTとしてプロンプトに挿入して推論します。じゃあ、FOUNDERが提案する「セクシーRAG」は、次にどうするんですか?”
QEU:FOUNDER : “今回は、特別に精度が良いことで定評のあるCohereのEmbeddingをつかいます。POV関連のプログラムをドン!!”
###################################
# RAG DOCUMENTのEMBEDDINGを生成する
###################################
# This snippet shows and example how to use the Cohere Embed V3 models for semantic search.
# Make sure to have the Cohere SDK in at least v4.30 install: pip install -U cohere
# Get your API key from: www.cohere.com
import cohere
co = cohere.Client()
# ---
docs_txt = []
for i in range(len(docs)):
print(docs[i].page_content)
docs_txt.append(docs[i].page_content)
# ---
# Encode your documents with input type 'search_document'
doc_emb = co.embed(texts=docs_txt, input_type="search_document", model="embed-english-v3.0").embeddings
doc_emb = np.asarray(doc_emb)
#print(doc_emb.shape)
###################################
# POV DOCUMENTのEMBEDDINGを生成する
###################################
#Encode your query with input type 'search_query'
query = arr_POV.tolist()
# ----
pov_emb = co.embed(texts=query, input_type="search_query", model="embed-english-v3.0").embeddings
pov_emb = np.asarray(pov_emb)
#print(pov_emb.shape)
###################################
# 類似度を評価して並べ替える(マルチ)
###################################
# -----
# compare two vectors
def calculate_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# -----
# 類似度の評価
num_doc = len(doc_emb)
num_pov = len(pov_emb)
mx_similarity = np.zeros([num_doc, num_pov])
for i in range(num_doc):
for j in range(num_pov):
mx_similarity[i,j] = calculate_similarity(doc_emb[i,:], pov_emb[j,:]) # 0.85 - very similar!
###################################
# ヒートマップを生成する(マルチ)
###################################
# -----
import seaborn as sns
import matplotlib.pyplot as plt
# -----
def heatmap(mx_similarity, ax, str_label):
# print(labels)
sns.heatmap(mx_similarity, ax= ax, cmap="YlOrRd", annot=True)
ax.set_title(f"--- {str_label} ---")
# -----
# グラフ化する(ヒートマップ : ANSWER-A)
# -----
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(111)
# 描画
heatmap(mx_similarity, ax1, "TESTING Cohere embedding")
# ---
fig.suptitle("HEATMAP OF SIMILARITY : RAG_DOC vs POV")
fig.tight_layout()
plt.show()
QEU:FOUNDER : “CHUNKとPOVの相関度をマトリックスにしました。ヒートマップで「見える化」してみましょう。横軸は、ユーザーが設定したPOV(23種類)であり、縦軸は、LangChainにより抽出したChunk(10種類)です。”
D先生: “なるほどね。各POVごとに色の分布にバラツキがあります。それでも、POVの10番、11番、12番がすべてのChunkに共通して類似度が高いです。おそらく、それらのPOVは、クエリーに近い文脈になっているはずです。”
- クエリー: 植民地支配の記憶は、搾取された地域の現状にどのような影響を与えているのでしょうか?
- POV11番: モンロー主義によるアメリカの南アメリカ支配は苛烈なモノであった。南アメリカ諸国は、アメリカ合衆国を敵視している。
- POV12番: 奴隷貿易やコンゴ自由国に代表するヨーロッパのアフリカ支配は苛烈なモノであった。アフリカ諸国は、西ヨーロッパ諸国を敵視している。
QEU:FOUNDER : “D先生の推理は大当たりです!それでは、Cohereの類似度からスコアを生成して、Chunkをならべかえましょう。”
###################################
# 類似度でスコアを生成し、並べ替える(マルチ)
###################################
# -----
# スコアの計算
def calc_score(mx_similarity):
num_docs = len(mx_similarity)
arr_score = []
for i in range(num_docs):
arr_similarity = mx_similarity[i,:]
val_score = np.dot(arr_similarity, arr_similarity)
val_score = math.sqrt(val_score)
arr_score.append(val_score)
return np.array(arr_score)
# ----
# create score array
scores = calc_score(mx_similarity)
#print(scores)
# ----
# Find the highest scores
max_idx = np.argsort(-scores)
# --
doc_ordered = []
score_ordered = []
print(f"Query: {query}")
for idx in max_idx:
print(f"Score: {scores[idx]:.2f}")
print(docs_txt[idx])
print("--------")
doc_ordered.append(docs_txt[idx])
score_ordered.append(scores[idx])
# ---
mx_doc = np.array([doc_ordered]).T
mx_score = np.array([score_ordered]).T
mx_output = np.concatenate([mx_doc, mx_score], axis=1)
arr_column = ["doc","score"]
df_output = pd.DataFrame(mx_output, columns=arr_column)
df_output
# -----
# EXCELファイルに変換して保存する
df_output.to_excel('doc_similarity.xlsx', sheet_name='new_sheet_name')
QEU:FOUNDER : “セクシー・スコアで並べ替えましょう。”
D先生: “セクシー・スコアで内容がどれだけ表現がかわるんですか?事例を紹介してください。”
(セクシー・スコア;トップ)
植民地化は、独立して植民地から離脱した後も、南半球の多くの国々に劇的な影響を及ぼしてきました。過去の植民地化国と植民地の共生関係は、どちらか一方だけが実際に利益を得るという、いわば寄生的な関係だったようです。天然資源と人的資源の両方が戦略的に南から北に吸い上げられ、南はほとんど利益を得ませんでした。不公平なトレードオフです。植民地化は、「北半球」と「南半球」の区別の主な理由の 1 つです。植民地化は終わったと主張したい人が多いですが、植民地化の遺産がこれらの旧植民地国の国内および国際政策に影響を与え続けているのが現実です。私たちの読書で述べられているように、南半球は、植民地化されていた 500 年間の「遅れを取り戻し」始めたばかりのようです。不公平なプレーではありませんか? 私も、植民地から来たので、これを直接経験しました。
(セクシー・スコア;ビリ)
権力の植民地主義と言語の植民地主義という 2 つの概念がラテンアメリカの文脈で紹介され、説明されたので、リテラシーがこれらのプロセスに対する抵抗の形、つまり「脱植民地主義」の転換となった経緯を説明します。続きを読む 人文地理学者を含む社会科学者は、現在の「植民地の持続性」と植民地主義の微妙な違いに注目してきました。つまり、ポストコロニアル思想家に従うと、植民地主義は 1940 年代、1960 年代、またはそれ以降の脱植民地化で「終わった」のではなく、修正された形で存続しているということです。批判的な人文科学と社会科学では、植民地主義が何らかの形で存続し、植民地主義の影響が文化、関係、領土、地理、政治、経済を構成し、影響を与え続けているという点で、かなりの合意があります。
D先生: “確かに、スコアが低い回答はセクシーじゃないですね。この類似性評価メトリックス(スコア)はとても有用です。”
QEU:FOUNDER : “このセクシー・スコアが上位のChunkを使って、RAG推論をするのが、今回の戦略です。”
D先生: “Unslothをわざわざ使う必要があるかなあ・・・。”
QEU:FOUNDER : “Unslothは基本的には必要ないです。ただし、今回はRAGの文書量が多く、ベクトルストア処理を加速させるためにGPUが必要なので、ついでにUnslothを使って推論もしようと思ったんです。それに、Unslothではプロンプトの改造も思いのままですから・・・。次は、上記のプログラムを改造して、実際にStep2で使うAnswerとFact群を作りましょう。”
~ まとめ ~
D先生: “いやあ、楽しみですねえ。ここは、「セクシー」が行きますか?”
QEU:FOUNDER : “ぜひ、行って欲しいねえ・・・。我々の方向性もセクシーだし、相性はばっちり・・・(笑)。”
D先生: “・・・ですが、彼の次が心配になります。次にはお偉い人の人選が厳しくなります。もっと年齢がわかくなるでしょう?”
QEU:FOUNDER : “まだまだ、次の人材がありますよ。こちらの方が有能そうだ。”