AhmedBenElsayedBenAhmed's picture
edits
f88163d
Raw History Blame Contribute Delete
13.9 kB
# ===============================
# 1️⃣ استيراد المكتبات
# ===============================
import os
os.environ["HF_HOME"] = "/tmp"
os.environ["TRANSFORMERS_CACHE"] = "/tmp"
os.environ["PEFT_HOME"] = "/tmp"
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType, PeftModel
from flask import Flask, request, jsonify
from flask_cors import CORS
import threading, torch, json
from datasets import Dataset
import difflib
# ===============================
# 2️⃣ إعداد المتغيرات
# ===============================
MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" # موديل أكبر وأكثر دقة
FLASK_PORT = 7860
SAVE_DIR = "/tmp/lora_model"
DATA_DIR = "/tmp/data"
os.makedirs(DATA_DIR, exist_ok=True)
os.makedirs(SAVE_DIR, exist_ok=True)
DATASET_FILE = os.path.join(DATA_DIR, "dataset.json")
# ===============================
# 3️⃣ تحديد الجهاز
# ===============================
device = "cuda" if torch.cuda.is_available() else "cpu"
print("🔹 الجهاز المستخدم:", device)
# ===============================
# 4️⃣ تحميل الموديل الأساسي
# ===============================
print("🔄 جاري تحميل الموديل الأساسي...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
try:
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
device_map="auto" if device == "cuda" else {"": "cpu"},
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
trust_remote_code=True
)
model.to(device)
print("✅ تم تحميل الموديل الأساسي بنجاح.")
except Exception as e:
print("⚠️ فشل تحميل الموديل:", e)
raise e
# ===============================
# 5️⃣ إعداد LoRA
# ===============================
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=4,
lora_alpha=16,
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"]
)
model = get_peft_model(model, lora_config)
model.to(device)
print("✅ تم تفعيل LoRA على الموديل.")
# ===============================
# 6️⃣ تحميل أوزان LoRA إذا وجدت
# ===============================
if os.path.exists(SAVE_DIR):
try:
base_model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
device_map="auto" if device == "cuda" else {"": "cpu"},
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
trust_remote_code=True
)
model = PeftModel.from_pretrained(base_model, SAVE_DIR)
model.to(device)
print("✅ تم تحميل أوزان LoRA المحفوظة.")
except Exception as e:
print("⚠️ لم يتم تحميل أوزان LoRA:", e)
else:
print("ℹ️ لا يوجد أوزان محفوظة. سيتم استخدام الموديل الأساسي فقط.")
# ===============================
# 7️⃣ إعداد Flask API
# ===============================
app = Flask(__name__)
CORS(app)
trainer = None
current_dataset = None
# قاعدة بيانات تجريبية للمستخدمين
users_db = {
"1": {"name": "أحمد السيد", "role": "طالب"},
"2": {"name": "منى علي", "role": "محامية"},
"3": {"name": "عمر حسن", "role": "عميل"}
}
# ===============================
# 7a️⃣ توليد النصوص
# ===============================
@app.route("/generate", methods=["POST"])
def generate():
try:
data = request.json or {}
prompt = str(data.get("prompt", "")).strip()
user_id = str(data.get("userId", "")).strip()
focus_level = float(data.get("focus_level", 0.5))
n_examples = int(data.get("n_examples", 2))
user_info = users_db.get(user_id)
user_name = user_info["name"] if user_info else "مستخدم"
if not prompt:
return jsonify({"messages": [{"role": "assistant", "content": "⚠️ من فضلك أرسل سؤال أو استفسار صحيح."}]}), 400
# في حالة عدم وجود بيانات تدريبية
if current_dataset is None:
base_prompt = (
f"أنت مساعد قانوني اسمه زين.\n"
f"مهمتك: الرد على الأسئلة القانونية بلغة عربية واضحة وبسيطة "
f"مع الحفاظ على الدقة القانونية.\n\n"
f"السؤال:\n{prompt}\n\nالإجابة:"
)
inputs = tokenizer(base_prompt, return_tensors="pt", truncation=True, max_length=1024)
inputs = {k: v.to(model.device) for k, v in inputs.items()}
input_len = inputs["input_ids"].shape[-1]
outputs = model.generate(
**inputs,
max_new_tokens=300,
temperature=0.6,
top_p=0.9,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
repetition_penalty=1.1
)
reply = tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
return jsonify({
"messages": [
{"role": "assistant", "content": f"مرحباً {user_name}"},
{"role": "assistant", "content": reply}
],
"meta": {"mode": "base"}
})
# في حالة وجود بيانات تدريبية
candidates = []
for item in current_dataset:
text = item.get("text", "")
ratio = difflib.SequenceMatcher(None, prompt.lower(), text.lower()).ratio()
candidates.append((ratio, text))
candidates.sort(reverse=True, key=lambda x: x[0])
top_texts = [t for r, t in candidates[:n_examples] if r > 0.05]
context_parts = []
for t in top_texts:
lines = t.split("\n")
u_lines, a_lines = [], []
for l in lines:
if ":" in l:
role, content = l.split(":", 1)
role = role.strip().lower()
if role.startswith("user"):
u_lines.append(content.strip())
elif role.startswith("assistant") or role.startswith("ai") or role.startswith("system"):
a_lines.append(content.strip())
if u_lines or a_lines:
context_parts.append("المستخدم: " + " ".join(u_lines) + "\nالمساعد: " + " ".join(a_lines))
context_text = "\n\n---\n\n".join(context_parts)
instruction = (
"التعليمات: استخدم الأمثلة التدريبية كمصدر مرجعي، "
"لكن أجب بصياغة جديدة وبأسلوبك الخاص. "
"حافظ على صحة المعلومات القانونية وتجنب النسخ الحرفي."
)
if context_text and focus_level > 0.0:
emphasis = "ركز بشكل كبير على الأمثلة" if focus_level > 0.7 else "خذ الأمثلة كمرجع فقط"
final_prompt = (
f"{instruction}\n\n{emphasis}\n\n"
f"أمثلة تدريبية (مستوى التركيز={focus_level}):\n{context_text}\n\n"
f"السؤال:\n{prompt}\n\nالإجابة:"
)
else:
final_prompt = f"{instruction}\n\nالسؤال:\n{prompt}\n\nالإجابة:"
# توليد النص
inputs = tokenizer(final_prompt, return_tensors="pt", truncation=True, max_length=1024)
inputs = {k: v.to(model.device) for k, v in inputs.items()}
input_len = inputs["input_ids"].shape[-1]
outputs = model.generate(
**inputs,
max_new_tokens=300,
temperature=0.6,
top_p=0.9,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
repetition_penalty=1.1
)
reply = tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
return jsonify({
"messages": [
{"role": "assistant", "content": f"مرحباً {user_name}"},
{"role": "assistant", "content": reply}
],
"meta": {
"mode": "trained",
"used_examples": len(context_parts),
"focus_level": focus_level
}
})
except Exception as e:
return jsonify({"messages": [{"role": "assistant", "content": f"خطأ: {str(e)}"}]})
# ===============================
# 7b️⃣ تحميل البيانات
# ===============================
def load_dataset():
global current_dataset
if os.path.exists(DATASET_FILE):
with open(DATASET_FILE, "r", encoding="utf-8") as f:
try:
data = json.load(f)
except:
data = []
if data:
formatted = []
for item in data:
if "messages" in item:
conversation = ""
for msg in item["messages"]:
conversation += f"{msg['role'].capitalize()}: {msg['content']}\n"
formatted.append({"text": conversation.strip()})
elif "text" in item:
formatted.append({"text": item["text"]})
current_dataset = Dataset.from_list(formatted)
print(f"✅ تم تحميل {len(formatted)} سجل من قاعدة البيانات.")
else:
current_dataset = None
else:
current_dataset = None
load_dataset()
# ===============================
# 7c️⃣ تدريب الموديل (LoRA)
# ===============================
@app.route("/train", methods=["POST"])
def train():
global trainer, current_dataset, model
try:
data = []
epochs = 1
batch_size = 1
if request.is_json:
body = request.get_json()
if isinstance(body, list):
data = body
elif isinstance(body, dict) and "data" in body:
data = body["data"]
else:
return jsonify({"error": "صيغة JSON غير صحيحة"}), 400
epochs = int(body.get("epochs", 1)) if isinstance(body, dict) else 1
batch_size = int(body.get("batch_size", 1)) if isinstance(body, dict) else 1
else:
file = request.files.get("file")
if file:
data = json.load(file)
epochs = int(request.form.get("epochs", 1))
batch_size = int(request.form.get("batch_size", 1))
else:
return jsonify({"error": "لم يتم رفع ملف أو إرسال JSON"}), 400
old_data = []
if os.path.exists(DATASET_FILE):
with open(DATASET_FILE, "r", encoding="utf-8") as f:
try:
old_data = json.load(f)
except:
old_data = []
combined = old_data + data
unique_data, seen_texts = [], set()
for item in combined:
if "messages" in item:
text_key = "\n".join([f"{m['role'].strip().lower()}:{m['content'].strip()}" for m in item["messages"]])
elif "text" in item:
text_key = item["text"].strip()
else:
continue
if text_key not in seen_texts:
seen_texts.add(text_key)
unique_data.append(item)
with open(DATASET_FILE, "w", encoding="utf-8") as f:
json.dump(unique_data, f, ensure_ascii=False, indent=2)
formatted = []
for item in unique_data:
if "messages" in item:
conversation = ""
for msg in item["messages"]:
conversation += f"{msg['role'].capitalize()}: {msg['content']}\n"
formatted.append({"text": conversation.strip()})
elif "text" in item:
formatted.append({"text": item["text"]})
current_dataset = Dataset.from_list(formatted)
def tokenize_function(examples):
return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=256)
tokenized_dataset = current_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
training_args = TrainingArguments(
output_dir=SAVE_DIR,
per_device_train_batch_size=batch_size,
num_train_epochs=epochs,
logging_steps=2,
save_strategy="epoch",
fp16=torch.cuda.is_available(),
report_to=None
)
trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_dataset)
def run_training():
print("🔄 بدأ التدريب...")
trainer.train()
trainer.save_model(SAVE_DIR)
print("✅ انتهى التدريب وتم حفظ الموديل.")
threading.Thread(target=run_training, daemon=True).start()
return jsonify({
"status": "بدأ التدريب",
"epochs": epochs,
"batch_size": batch_size,
"records": len(unique_data)
})
except Exception as e:
return jsonify({"error": str(e)})
# ===============================
# 8️⃣ نقطة البداية
# ===============================
@app.route("/", methods=["GET"])
def home():
return jsonify({
"status": "جاهز",
"message": "واجهة برمجة التخرج تعمل الآن",
"endpoints": ["/generate", "/train"]
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=FLASK_PORT, threaded=True)