CoolFace
Modelpublic

skatzR/RQA-R2

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes13downloads
inference.py417 linesDownload Raw Back to root
1# requirements2# Для inference в Colab достаточно этого стека.3!pip install transformers==4.48.3 tokenizers sentencepiece accelerate4 5 6# ============================================================7# RQA UX Inference — R2 Interactive Version8# Google Colab + CLI friendly9# ============================================================10 11import os12import json13import csv14import torch15from typing import List, Optional16from transformers import AutoTokenizer, AutoModel17 18 19# ============================================================20# Константы21# ============================================================22 23ERROR_TYPES = [24    "false_causality",25    "unsupported_claim",26    "overgeneralization",27    "missing_premise",28    "contradiction",29    "circular_reasoning",30]31 32ERROR_NAMES_RU = {33    "false_causality": "Ложная причинно-следственная связь",34    "unsupported_claim": "Неподкрепленное утверждение",35    "overgeneralization": "Чрезмерное обобщение",36    "missing_premise": "Отсутствующая предпосылка",37    "contradiction": "Противоречие",38    "circular_reasoning": "Круговое рассуждение",39}40 41 42# ============================================================43# RQA Judge44# ============================================================45 46class RQAJudge:47    def __init__(self, model_name="skatzR/RQA-R2", device=None, max_length: int = 512):48        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")49        self.max_length = int(max_length)50 51        self.tokenizer = AutoTokenizer.from_pretrained(52            model_name,53            trust_remote_code=True54        )55        self.model = AutoModel.from_pretrained(56            model_name,57            trust_remote_code=True58        ).to(self.device)59 60        self.model.eval()61 62        cfg = self.model.config63        self.error_types = list(getattr(cfg, "error_types", ERROR_TYPES))64 65        self.temp_issue = float(getattr(cfg, "temperature_has_issue", 1.0))66        self.temp_hidden = float(getattr(cfg, "temperature_is_hidden", 1.0))67        self.temp_errors = list(68            getattr(cfg, "temperature_errors", [1.0] * len(self.error_types))69        )70 71        self.threshold_issue = float(getattr(cfg, "threshold_has_issue", 0.5))72        self.threshold_hidden = float(getattr(cfg, "threshold_is_hidden", 0.5))73        self.threshold_error = float(getattr(cfg, "threshold_error", 0.5))74        self.threshold_errors = list(75            getattr(cfg, "threshold_errors", [self.threshold_error] * len(self.error_types))76        )77 78    # ----------------------79    # Core inference80    # ----------------------81 82    @torch.no_grad()83    def infer(84        self,85        text: str,86        issue_threshold: Optional[float] = None,87        hidden_threshold: Optional[float] = None,88        error_threshold: Optional[float] = None,89        error_thresholds: Optional[List[float]] = None,90        issue_uncertain_margin: float = 0.05,91        hidden_uncertain_margin: float = 0.05,92        error_uncertain_margin: float = 0.05,93    ):94        issue_threshold = self.threshold_issue if issue_threshold is None else float(issue_threshold)95        hidden_threshold = self.threshold_hidden if hidden_threshold is None else float(hidden_threshold)96        error_threshold = self.threshold_error if error_threshold is None else float(error_threshold)97        error_thresholds = self.threshold_errors if error_thresholds is None else list(error_thresholds)98 99        inputs = self.tokenizer(100            text,101            truncation=True,102            max_length=self.max_length,103            padding="max_length",104            return_tensors="pt"105        ).to(self.device)106 107        outputs = self.model(**inputs)108 109        # ----- has_issue -----110        issue_logit = outputs["has_issue_logits"] / self.temp_issue111        issue_prob = torch.sigmoid(issue_logit).item()112        has_issue = issue_prob >= issue_threshold113 114        result = {115            "text": text,116            "class": None,             # logical / hidden / explicit117            "status": "ok",            # ok / uncertain118            "review_required": False,119            "has_issue": has_issue,120            "issue_probability": issue_prob,121            "hidden_problem": False,122            "hidden_probability": None,123            "errors": [],124            "num_errors": 0,125            "schema_version": getattr(self.model.config, "schema_version", "unknown"),126            "threshold_issue": issue_threshold,127            "threshold_hidden": hidden_threshold,128            "threshold_error": error_threshold,129            "threshold_errors": error_thresholds,130        }131 132        if abs(issue_prob - issue_threshold) <= issue_uncertain_margin:133            result["status"] = "uncertain"134            result["review_required"] = True135 136        # ----- Gate 1: logical -----137        if not has_issue:138            result["class"] = "logical"139            return result140 141        # ----- hidden -----142        hidden_logit = outputs["is_hidden_logits"] / self.temp_hidden143        hidden_prob = torch.sigmoid(hidden_logit).item()144        is_hidden = hidden_prob >= hidden_threshold145 146        result["hidden_problem"] = is_hidden147        result["hidden_probability"] = hidden_prob148 149        if abs(hidden_prob - hidden_threshold) <= hidden_uncertain_margin:150            result["status"] = "uncertain"151            result["review_required"] = True152 153        # ----- Gate 2: hidden -----154        if is_hidden:155            result["class"] = "hidden"156            return result157 158        # ----- explicit errors -----159        raw_error_logits = outputs["errors_logits"][0].clone()160        error_probs = {}161 162        for i, logit in enumerate(raw_error_logits):163            calibrated = logit / self.temp_errors[i]164            prob = torch.sigmoid(calibrated).item()165            error_probs[self.error_types[i]] = prob166 167        explicit_errors = []168        for i, err_name in enumerate(self.error_types):169            prob = float(error_probs[err_name])170            threshold_i = float(error_thresholds[i] if i < len(error_thresholds) else error_threshold)171 172            if abs(prob - threshold_i) <= error_uncertain_margin:173                result["status"] = "uncertain"174                result["review_required"] = True175 176            if prob >= threshold_i:177                explicit_errors.append((err_name, prob))178 179        explicit_errors.sort(key=lambda x: x[1], reverse=True)180 181        result["class"] = "explicit"182        result["errors"] = explicit_errors183        result["num_errors"] = len(explicit_errors)184        return result185 186    # ============================================================187    # UX output188    # ============================================================189 190    def pretty_print(self, r):191        print("\n" + "=" * 72)192        print("📄 Текст:")193        print(r["text"])194 195        print(196            f"\n🔎 Обнаружена проблема: {'ДА' if r['has_issue'] else 'НЕТ'} "197            f"({r['issue_probability'] * 100:.2f}%)"198        )199        print(f"🧠 Класс: {r['class']}")200 201        if r["status"] == "uncertain":202            print("⚠️ Пограничный случай: review recommended")203 204        if r["hidden_probability"] is not None:205            print(206                f"🟡 Hidden-проблема: {'ДА' if r['hidden_problem'] else 'НЕТ'} "207                f"({r['hidden_probability'] * 100:.2f}%)"208            )209 210        if r["errors"]:211            print("\n❌ Явные логические ошибки:")212            for name, prob in r["errors"]:213                print(f"  • {ERROR_NAMES_RU.get(name, name)} — {prob * 100:.2f}%")214        else:215            print("\n✅ Явных логических ошибок не обнаружено")216 217        print("=" * 72)218 219 220# ============================================================221# Loaders222# ============================================================223 224def load_texts_from_file(path: str) -> List[str]:225    ext = os.path.splitext(path)[1].lower()226 227    if ext == ".txt":228        with open(path, encoding="utf-8") as f:229            return [line.strip() for line in f if line.strip()]230 231    if ext == ".csv":232        with open(path, encoding="utf-8") as f:233            reader = csv.DictReader(f)234            return [row["text"] for row in reader if row.get("text")]235 236    if ext == ".json":237        with open(path, encoding="utf-8") as f:238            data = json.load(f)239        if isinstance(data, list):240            if all(isinstance(item, str) for item in data):241                return data242            texts = []243            for item in data:244                if isinstance(item, dict) and "text" in item:245                    texts.append(str(item["text"]))246            return texts247 248    raise ValueError("Неподдерживаемый формат файла")249 250 251# ============================================================252# Interactive CLI Interface253# ============================================================254 255class InteractiveCLI:256    def __init__(self, model_name="skatzR/RQA-R2"):257        self.judge = RQAJudge(model_name=model_name)258 259    def clear_screen(self):260        print("\n" * 2)261 262    def show_mode_menu(self):263        self.clear_screen()264        print("=" * 60)265        print("🤖 RQA-R2 — АНАЛИЗ ЛОГИЧЕСКИХ ОШИБОК")266        print("=" * 60)267        print("\nВыберите режим работы:")268        print("1. 📝 Одиночный ввод (одна фраза для анализа)")269        print("2. 📄 Множественный ввод (несколько фраз, каждая с новой строки)")270        print("3. 📂 Загрузка из файла (.txt, .csv, .json)")271        print("\nНажмите Enter без ввода для выхода.")272        print("-" * 60)273 274    def process_single_mode(self):275        self.clear_screen()276        print("[📝 РЕЖИМ: ОДИНОЧНЫЙ ВВОД]")277        print("Введите текст для анализа:")278        print("(Нажмите Enter без ввода для возврата в меню)")279        print("-" * 40)280 281        text = input("> ").strip()282        if not text:283            return True284 285        result = self.judge.infer(text)286        self.judge.pretty_print(result)287 288        print("\n" + "-" * 40)289        input("Нажмите Enter для продолжения...")290        return False291 292    def process_multiline_mode(self):293        self.clear_screen()294        print("[📄 РЕЖИМ: МНОЖЕСТВЕННЫЙ ВВОД]")295        print("Введите тексты для анализа (каждый с новой строки).")296        print("Оставьте строку пустой для завершения ввода.")297        print("(Нажмите Enter без ввода для возврата в меню)")298        print("-" * 40)299 300        texts = []301        print("Ввод текстов:")302        while True:303            line = input("> ").strip()304            if not line:305                if not texts:306                    return True307                break308            texts.append(line)309 310        self.clear_screen()311        print(f"[📄 РЕЖИМ: МНОЖЕСТВЕННЫЙ ВВОД] — найдено {len(texts)} текстов")312        print("-" * 40)313 314        for i, text in enumerate(texts, 1):315            print(f"\n🔍 Текст #{i}:")316            result = self.judge.infer(text)317            self.judge.pretty_print(result)318 319        print("\n" + "=" * 60)320        input("Нажмите Enter для продолжения...")321        return False322 323    def process_file_mode(self):324        self.clear_screen()325        print("[📂 РЕЖИМ: ЗАГРУЗКА ИЗ ФАЙЛА]")326        print("Поддерживаемые форматы: .txt, .csv, .json")327        print("Укажите путь к файлу:")328        print("(Нажмите Enter без ввода для возврата в меню)")329        print("-" * 40)330 331        file_path = input("Путь к файлу> ").strip()332        if not file_path:333            return True334 335        try:336            if not os.path.exists(file_path):337                print(f"\n❌ Ошибка: Файл '{file_path}' не найден!")338                input("\nНажмите Enter для продолжения...")339                return False340 341            texts = load_texts_from_file(file_path)342            if not texts:343                print(f"\n⚠️ Файл '{file_path}' пуст или не содержит текстов!")344                input("\nНажмите Enter для продолжения...")345                return False346 347            self.clear_screen()348            print(f"[📂 РЕЖИМ: ЗАГРУЗКА ИЗ ФАЙЛА] — загружено {len(texts)} текстов")349            print(f"Файл: {file_path}")350            print("-" * 40)351 352            for i, text in enumerate(texts, 1):353                print(f"\n🔍 Текст #{i}:")354                result = self.judge.infer(text)355                self.judge.pretty_print(result)356 357            print("\n" + "=" * 60)358            input("Нажмите Enter для продолжения...")359 360        except Exception as e:361            print(f"\n❌ Ошибка при обработке файла: {str(e)}")362            input("\nНажмите Enter для продолжения...")363 364        return False365 366    def run_interactive(self):367        current_mode = None368 369        while True:370            if not current_mode:371                self.show_mode_menu()372                choice = input("Ваш выбор (1-3)> ").strip()373 374                if not choice:375                    print("\n👋 Выход из программы...")376                    break377 378                if choice == "1":379                    current_mode = "single"380                elif choice == "2":381                    current_mode = "multiline"382                elif choice == "3":383                    current_mode = "file"384                else:385                    print("\n❌ Неверный выбор! Попробуйте снова.")386                    input("Нажмите Enter для продолжения...")387                    continue388 389            should_return_to_menu = False390 391            if current_mode == "single":392                should_return_to_menu = self.process_single_mode()393            elif current_mode == "multiline":394                should_return_to_menu = self.process_multiline_mode()395            elif current_mode == "file":396                should_return_to_menu = self.process_file_mode()397 398            if should_return_to_menu:399                current_mode = None400 401 402# ============================================================403# Точка входа404# ============================================================405 406def main():407    cli = InteractiveCLI()408    cli.run_interactive()409 410 411# ============================================================412# Запуск413# ============================================================414 415if __name__ == "__main__":416    main()417