File size: 7,534 Bytes
3d9ba5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import argparse
import sys
import os
import json

if sys.stdout.encoding != 'utf-8':
    try:
        sys.stdout.reconfigure(encoding='utf-8')
    except Exception:
        pass

from nlp_core.language_detector import LanguageDetector
from nlp_core.tokenizer import BilingualTokenizer
from models.extractive.textrank import TextRankSummarizer
from models.extractive.lsa import LSASummarizer
from models.extractive.hybrid_scorer import HybridSummarizer
from models.abstractive.seq2seq_model import Seq2SeqSummarizer
from models.abstractive.transformer_wrap import TransformerSummarizer
from evaluation.metrics_manager import MetricsManager

def main():
    parser = argparse.ArgumentParser(
        description="Bilingual Arabic-English Natural Language Processing System for Automatic Text Summarization"
    )
    parser.add_argument("--file", type=str, help="Path to text document file to summarize")
    parser.add_argument("--text", type=str, help="Direct raw input text string to summarize")
    parser.add_argument("--mode", type=str, default="extractive", choices=["extractive", "abstractive", "hybrid"], help="Summarization paradigm")
    parser.add_argument("--method", type=str, default="textrank", choices=["textrank", "lsa", "hybrid", "seq2seq", "transformer"], help="Specific summarization algorithm")
    parser.add_argument("--lang", type=str, default="auto", choices=["auto", "ar", "en"], help="Target language (auto-detected if 'auto')")
    parser.add_argument("--sentences", type=int, default=3, help="Number of sentences for extractive summary")
    parser.add_argument("--ratio", type=float, default=None, help="Compression ratio (e.g. 0.3 for 30%% length)")
    parser.add_argument("--beam", type=int, default=3, help="Beam size for Seq2Seq abstractive decoding")
    parser.add_argument("--ref", type=str, default=None, help="Reference/Gold standard summary string for evaluation")
    parser.add_argument("--ref-file", type=str, default=None, help="Path to reference summary file")
    parser.add_argument("--json", action="store_true", help="Output results formatted in JSON")

    args = parser.parse_args()

    # Read input text
    input_text = ""
    if args.file:
        if not os.path.exists(args.file):
            print(f"Error: File not found: {args.file}", file=sys.stderr)
            sys.exit(1)
        with open(args.file, "r", encoding="utf-8") as f:
            input_text = f.read()
    elif args.text:
        input_text = args.text
    else:
        print("Error: Please provide --file <filepath> or --text '<raw text>'", file=sys.stderr)
        parser.print_help()
        sys.exit(1)

    # Detect Language
    lang_detector = LanguageDetector()
    detected_lang = lang_detector.detect_language(input_text)
    active_lang = detected_lang if args.lang == "auto" else args.lang
    lang_confidence = lang_detector.get_language_confidence(input_text)

    # Read reference text if provided
    ref_text = args.ref
    if args.ref_file and os.path.exists(args.ref_file):
        with open(args.ref_file, "r", encoding="utf-8") as f:
            ref_text = f.read()

    summary_text = ""
    extra_details = {}

    # Run Summarizer
    if args.mode == "extractive" or args.method in ["textrank", "lsa", "hybrid"]:
        if args.method == "lsa":
            summarizer = LSASummarizer()
        elif args.method == "hybrid":
            summarizer = HybridSummarizer()
        else:
            summarizer = TextRankSummarizer()

        result = summarizer.summarize(
            input_text,
            num_sentences=args.sentences,
            ratio=args.ratio,
            lang=active_lang
        )
        summary_text = result["summary"]
        extra_details["selected_indices"] = result["selected_indices"]
        extra_details["sentence_scores"] = result["sentence_scores"]

    elif args.mode == "abstractive" or args.method in ["seq2seq", "transformer"]:
        if args.method == "transformer":
            transformer_wrap = TransformerSummarizer(lang=active_lang)
            summary = transformer_wrap.summarize(input_text)
            if summary:
                summary_text = summary
            else:
                # Fallback to extractive if transformer weights not available locally
                fallback = TextRankSummarizer().summarize(input_text, num_sentences=args.sentences, lang=active_lang)
                summary_text = fallback["summary"]
        else:
            # Custom Seq2Seq from scratch
            ckpt_path = f"checkpoints/seq2seq_{'arabic' if active_lang == 'ar' else 'english'}.pt"
            if os.path.exists(ckpt_path):
                model = Seq2SeqSummarizer.load_checkpoint(ckpt_path)
                tokenizer = BilingualTokenizer()
                tokens = tokenizer.tokenize_words(input_text, lang=active_lang)
                summary_tokens = model.summarize_beam(tokens, beam_width=args.beam, max_len=60)
                summary_text = ' '.join(summary_tokens)
            else:
                # Fallback to TextRank
                fallback = TextRankSummarizer().summarize(input_text, num_sentences=args.sentences, lang=active_lang)
                summary_text = fallback["summary"]
                extra_details["note"] = f"Trained checkpoint not found at {ckpt_path}. Used TextRank fallback."

    # Compute Metrics
    metrics_mgr = MetricsManager()
    eval_results = metrics_mgr.evaluate_summary(
        original_text=input_text,
        generated_summary=summary_text,
        reference_summary=ref_text,
        lang=active_lang
    )

    if args.json:
        output_payload = {
            "language": active_lang,
            "language_confidence": lang_confidence,
            "mode": args.mode,
            "method": args.method,
            "summary": summary_text,
            "metrics": eval_results,
            "extra": extra_details
        }
        print(json.dumps(output_payload, ensure_ascii=False, indent=2))
    else:
        print("\n============================================================")
        print(f"📖 Bilingual Summarization System [{active_lang.upper()}] (Detected: {detected_lang})")
        print(f"Method: {args.method.upper()} | Mode: {args.mode.upper()}")
        print("============================================================")
        print("\n--- GENERATED SUMMARY ---")
        print(summary_text)
        print("\n--- SUMMARY METRICS ---")
        print(f"Original Words: {eval_results['original_stats']['word_count']} | Summary Words: {eval_results['summary_stats']['word_count']}")
        print(f"Compression Ratio: {eval_results['compression_ratio']} (Reduction: {eval_results['reduction_percentage']})")
        print(f"Vocabulary Diversity (TTR): {eval_results['summary_stats']['type_token_ratio']}")

        if "rouge" in eval_results:
            print("\n--- EVALUATION BENCHMARKS ---")
            r = eval_results["rouge"]
            b = eval_results["bleu"]
            print(f"ROUGE-1: F1 = {r['rouge-1']['f1']} (P: {r['rouge-1']['precision']}, R: {r['rouge-1']['recall']})")
            print(f"ROUGE-2: F1 = {r['rouge-2']['f1']} (P: {r['rouge-2']['precision']}, R: {r['rouge-2']['recall']})")
            print(f"ROUGE-L: F1 = {r['rouge-l']['f1']} (P: {r['rouge-l']['precision']}, R: {r['rouge-l']['recall']})")
            print(f"BLEU Cumulative: {b['bleu_cumulative']} (BLEU-1: {b['bleu-1']}, BLEU-2: {b['bleu-2']})")
        print("============================================================\n")

if __name__ == "__main__":
    main()