File size: 9,791 Bytes
15634f7
 
 
 
 
b563b79
 
 
 
 
 
 
15634f7
 
 
 
 
 
 
 
 
 
 
 
2d7589c
b563b79
 
 
2d7589c
 
 
 
15634f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7589c
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"""
NLP Text Lab - Gradio app for Hugging Face Spaces
Tasks: Tokenization, Stop-word removal, Stemming, Lemmatization,
       POS tagging + word frequency, Full preprocessing pipeline.
"""
# NOTE: `spaces` must be imported FIRST (before spacy/torch), otherwise ZeroGPU
# raises "CUDA has been initialized before importing the spaces package".
try:
    import spaces
except ImportError:  # running locally / on CPU hardware
    spaces = None

import string
from collections import Counter

import gradio as gr
import nltk
import pandas as pd
import spacy
from nltk.corpus import stopwords, wordnet
from nltk.stem import (LancasterStemmer, PorterStemmer, SnowballStemmer,
                       WordNetLemmatizer)
from nltk.tokenize import sent_tokenize, word_tokenize

# ------------------------------------------------- ZeroGPU compatibility ----
# ZeroGPU needs at least one @spaces.GPU function. The app does not use a GPU,
# so this is only a harmless placeholder.
if spaces is not None:
    @spaces.GPU
    def _gpu_placeholder():
        return "ok"

# ---------------------------------------------------------------- setup ----
for pkg in ["punkt", "punkt_tab", "stopwords", "wordnet", "omw-1.4",
            "averaged_perceptron_tagger", "averaged_perceptron_tagger_eng"]:
    nltk.download(pkg, quiet=True)


def load_spacy():
    try:
        return spacy.load("en_core_web_sm")
    except OSError:
        from spacy.cli import download
        download("en_core_web_sm")
        return spacy.load("en_core_web_sm")


nlp = load_spacy()
STOP_WORDS = set(stopwords.words("english"))
porter, lancaster = PorterStemmer(), LancasterStemmer()
snowball = SnowballStemmer("english")
wnl = WordNetLemmatizer()

DEFAULT_TEXT = (
    "Consistency is the key to success.\n"
    "Every small step we take today brings us closer to the future we dream of.\n"
    "The journey may be difficult, and sometimes we may feel tired or lost,\n"
    "but we must keep moving forward. Growth takes time, patience, and courage."
)


# -------------------------------------------------------------- helpers ----
def _clean(text):
    return (text or "").strip()


def _is_punct(tok):
    return all(ch in string.punctuation for ch in tok)


def _wn_pos(tag):
    """Convert Penn Treebank tag to WordNet POS."""
    if tag.startswith("J"):
        return wordnet.ADJ
    if tag.startswith("V"):
        return wordnet.VERB
    if tag.startswith("R"):
        return wordnet.ADV
    return wordnet.NOUN


# ---------------------------------------------------------------- tasks ----
def task_tokenize(text):
    text = _clean(text)
    if not text:
        return "Please enter a sentence.", "", "", ""
    words = word_tokenize(text)
    sents = sent_tokenize(text)
    doc = nlp(text)
    spacy_tokens = [t.text for t in doc if not t.is_space]
    spacy_sents = [s.text.strip() for s in doc.sents]

    nltk_out = (f"WORD TOKENS ({len(words)}):\n{words}\n\n"
                f"SENTENCES ({len(sents)}):\n" +
                "\n".join(f"{i+1}. {s}" for i, s in enumerate(sents)))
    spacy_out = (f"WORD TOKENS ({len(spacy_tokens)}):\n{spacy_tokens}\n\n"
                 f"SENTENCES ({len(spacy_sents)}):\n" +
                 "\n".join(f"{i+1}. {s}" for i, s in enumerate(spacy_sents)))
    summary = (f"No. of tokens (NLTK): {len(words)}  |  "
               f"No. of sentences (NLTK): {len(sents)}")
    return summary, nltk_out, spacy_out, ""


def task_stopwords(text, remove_punct, lowercase):
    text = _clean(text)
    if not text:
        return "Please enter a sentence.", "", "", ""
    tokens = word_tokenize(text)
    kept, removed = [], []
    for tok in tokens:
        if remove_punct and _is_punct(tok):
            removed.append(tok)
            continue
        if tok.lower() in STOP_WORDS:   # case-insensitive match
            removed.append(tok)
        else:
            kept.append(tok.lower() if lowercase else tok)
    summary = (f"Original tokens: {len(tokens)}  |  Removed: {len(removed)}  |  "
               f"Remaining: {len(kept)}")
    return summary, str(tokens), str(removed), " ".join(kept)


def task_stemming(text):
    text = _clean(text)
    if not text:
        return pd.DataFrame(columns=["Word", "Porter", "Lancaster", "Snowball"])
    toks = [t for t in word_tokenize(text) if not _is_punct(t)]
    rows = [[t, porter.stem(t), lancaster.stem(t), snowball.stem(t)] for t in toks]
    return pd.DataFrame(rows, columns=["Word", "Porter", "Lancaster", "Snowball"])


def task_lemmatize(text):
    text = _clean(text)
    empty1 = pd.DataFrame(columns=["Word", "POS tag", "Lemma (default=noun)",
                                   "Lemma (POS-aware)"])
    empty2 = pd.DataFrame(columns=["Word", "POS", "spaCy Lemma"])
    if not text:
        return empty1, empty2
    toks = [t for t in word_tokenize(text) if not _is_punct(t)]
    rows = []
    for tok, tag in nltk.pos_tag(toks):
        rows.append([tok, tag, wnl.lemmatize(tok.lower()),
                     wnl.lemmatize(tok.lower(), _wn_pos(tag))])
    doc = nlp(text)
    rows2 = [[t.text, t.pos_, t.lemma_] for t in doc
             if not t.is_space and not t.is_punct]
    return (pd.DataFrame(rows, columns=empty1.columns),
            pd.DataFrame(rows2, columns=empty2.columns))


def task_pos_freq(text):
    text = _clean(text)
    if not text:
        return (pd.DataFrame(columns=["Word", "POS tag"]),
                pd.DataFrame(columns=["Word", "Count"]))
    toks = [t for t in word_tokenize(text) if not _is_punct(t)]
    pos_df = pd.DataFrame(nltk.pos_tag(toks), columns=["Word", "POS tag"])
    words = [t.lower() for t in toks if t.lower() not in STOP_WORDS]
    freq_df = pd.DataFrame(Counter(words).most_common(),
                           columns=["Word", "Count"])
    return pos_df, freq_df


def task_pipeline(text):
    text = _clean(text)
    if not text:
        return ["Please enter a sentence."] + [""] * 5
    tokens = word_tokenize(text.lower())
    no_punct = [t for t in tokens if not _is_punct(t)]
    no_stop = [t for t in no_punct if t not in STOP_WORDS]
    stems = [porter.stem(t) for t in no_stop]
    lemmas = [wnl.lemmatize(t, _wn_pos(tag)) for t, tag in nltk.pos_tag(no_stop)]
    return (f"1. Lowercase + Tokenize\n{tokens}", f"2. Remove punctuation\n{no_punct}",
            f"3. Remove stop words\n{no_stop}", f"4. Stemming (Porter)\n{stems}",
            f"5. Lemmatization (POS-aware)\n{lemmas}",
            " ".join(lemmas))


LEARN_MD = """
## Stemming ("trimming") vs Lemmatization

**Stemming** chops the ending (suffix) off a word using simple rules, so the
result may *not* be a real word. It is fast but rough.

| Word | Stem (Porter) |
|---|---|
| studies | studi |
| running | run |
| difficult | difficult |
| happily | happili |

**Lemmatization** uses a dictionary (WordNet) and the word's part of speech to
return the real base form (the *lemma*). Slower but accurate.

| Word | POS | Lemma |
|---|---|---|
| studies | noun/verb | study |
| running | verb | run |
| better | adjective | good |
| mice | noun | mouse |

**When to use what?** Stemming for search engines/speed, lemmatization when
meaning matters (chatbots, sentiment analysis, NLP models).

## Stop words
Very common words (the, is, to, we ...) that carry little meaning. They are
removed to keep only the useful words.
"""

# ------------------------------------------------------------------- UI ----
with gr.Blocks(title="NLP Text Lab") as demo:
    gr.Markdown("# 🧪 NLP Text Lab\nType a sentence/paragraph, then open any "
                "tab and press the button.")
    text_in = gr.Textbox(label="Your text", lines=5, value=DEFAULT_TEXT)

    with gr.Tab("1. Tokenization"):
        b1 = gr.Button("Tokenize", variant="primary")
        s1 = gr.Textbox(label="Summary")
        with gr.Row():
            n1 = gr.Textbox(label="NLTK", lines=12)
            sp1 = gr.Textbox(label="spaCy", lines=12)
        hid = gr.Textbox(visible=False)
        b1.click(task_tokenize, text_in, [s1, n1, sp1, hid])

    with gr.Tab("2. Stop Words Removal"):
        with gr.Row():
            rp = gr.Checkbox(True, label="Also remove punctuation")
            lc = gr.Checkbox(False, label="Lowercase result")
        b2 = gr.Button("Remove stop words", variant="primary")
        s2 = gr.Textbox(label="Summary")
        t2 = gr.Textbox(label="All tokens", lines=3)
        r2 = gr.Textbox(label="Removed (stop words / punctuation)", lines=3)
        f2 = gr.Textbox(label="Filtered sentence", lines=3)
        b2.click(task_stopwords, [text_in, rp, lc], [s2, t2, r2, f2])

    with gr.Tab("3. Stemming"):
        b3 = gr.Button("Apply stemming", variant="primary")
        o3 = gr.Dataframe(label="Stemming results (Porter / Lancaster / Snowball)")
        b3.click(task_stemming, text_in, o3)

    with gr.Tab("4. Lemmatization"):
        b4 = gr.Button("Apply lemmatization", variant="primary")
        o4a = gr.Dataframe(label="NLTK WordNet Lemmatizer")
        o4b = gr.Dataframe(label="spaCy Lemmatizer")
        b4.click(task_lemmatize, text_in, [o4a, o4b])

    with gr.Tab("5. POS Tags + Word Frequency"):
        b5 = gr.Button("Analyse", variant="primary")
        with gr.Row():
            o5a = gr.Dataframe(label="Part-of-speech tags")
            o5b = gr.Dataframe(label="Word frequency (without stop words)")
        b5.click(task_pos_freq, text_in, [o5a, o5b])

    with gr.Tab("6. Full Pipeline"):
        b6 = gr.Button("Run full pipeline", variant="primary")
        p = [gr.Textbox(lines=3, show_label=False) for _ in range(5)]
        final = gr.Textbox(label="Final cleaned text", lines=2)
        b6.click(task_pipeline, text_in, p + [final])

    with gr.Tab("📖 Learn"):
        gr.Markdown(LEARN_MD)

if __name__ == "__main__":
    demo.launch()