shukdev3 commited on
Commit
fd87dec
·
verified ·
1 Parent(s): 0d6fa58

Upload 4 files

Browse files
Files changed (4) hide show
  1. .dockerignore +7 -0
  2. Dockerfile +24 -0
  3. app.py +443 -0
  4. requirements.txt +6 -0
.dockerignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .git/
5
+ .gitignore
6
+ .DS_Store
7
+ *.zip
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces (Docker SDK) — see README.md for the YAML config block
2
+ # that tells the Space to build this image and route traffic to port 7860.
3
+ FROM python:3.11-slim
4
+
5
+ # HF Spaces containers run as a non-root user with UID 1000
6
+ RUN useradd -m -u 1000 user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+
9
+ WORKDIR /app
10
+
11
+ COPY --chown=user requirements.txt requirements.txt
12
+ RUN pip install --no-cache-dir --upgrade pip && \
13
+ pip install --no-cache-dir -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+
17
+ USER user
18
+
19
+ ENV PORT=7860 \
20
+ FLASK_DEBUG=0
21
+
22
+ EXPOSE 7860
23
+
24
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Text Vectorization Lab — backend
3
+ =================================
4
+ A small Flask API that actually executes the same scikit-learn / numpy /
5
+ gensim code used to teach One-Hot Encoding, Count Vectorizer, Bag-of-Words,
6
+ N-grams, TF-IDF, and Word2Vec/FastText word embeddings.
7
+
8
+ Every endpoint below is a thin, JSON-friendly wrapper around the exact
9
+ computations from the reference notebook — nothing is hard-coded or faked.
10
+ The frontend (static/js/main.js) calls these endpoints and animates the
11
+ intermediate steps so the visitor can watch each technique build up.
12
+ """
13
+ import math
14
+ import re
15
+ import time
16
+
17
+ import numpy as np
18
+ import pandas as pd
19
+ from flask import Flask, jsonify, render_template, request
20
+ from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
21
+ from sklearn.metrics.pairwise import cosine_similarity
22
+ from sklearn.preprocessing import OneHotEncoder
23
+ from sklearn.decomposition import PCA
24
+
25
+ app = Flask(__name__)
26
+
27
+ # ----------------------------------------------------------------------------
28
+ # Helpers
29
+ # ----------------------------------------------------------------------------
30
+
31
+ def tokenize(text: str):
32
+ """Simple whitespace/punctuation tokenizer used across the manual demos."""
33
+ return re.findall(r"[A-Za-z0-9']+", text.lower())
34
+
35
+
36
+ def clean_corpus(raw):
37
+ """Normalize whatever the frontend sends into a list[str] of non-empty docs."""
38
+ if raw is None:
39
+ return []
40
+ if isinstance(raw, str):
41
+ raw = raw.split("\n")
42
+ return [s.strip() for s in raw if isinstance(s, str) and s.strip()]
43
+
44
+
45
+ DEFAULTS = {
46
+ "onehot": ["I love NLP", "NLP is fun", "I love coding"],
47
+ "count": [
48
+ "I love NLP and I love Python",
49
+ "NLP is amazing and fun",
50
+ "Python is great for NLP",
51
+ ],
52
+ "bow": [
53
+ "the cat sat on the mat",
54
+ "the dog sat on the log",
55
+ "the cat and the dog are friends",
56
+ ],
57
+ "ngrams_sentence": "I love studying Natural Language Processing",
58
+ "ngrams_corpus": [
59
+ "I love NLP and machine learning",
60
+ "machine learning is part of AI",
61
+ "NLP is a branch of AI",
62
+ ],
63
+ "tfidf": [
64
+ "I love NLP and machine learning",
65
+ "machine learning is part of AI",
66
+ "NLP is a branch of AI",
67
+ "I love AI and deep learning",
68
+ ],
69
+ "embeddings_sentences": [
70
+ "the cat sat on the mat",
71
+ "the dog ran on the grass",
72
+ "cats and dogs are pets",
73
+ "i love my cat",
74
+ "i love my dog",
75
+ "king and queen are royalty",
76
+ "man and woman are humans",
77
+ "paris is the capital of france",
78
+ "berlin is the capital of germany",
79
+ ],
80
+ }
81
+
82
+
83
+ # ----------------------------------------------------------------------------
84
+ # Page
85
+ # ----------------------------------------------------------------------------
86
+
87
+ @app.route("/")
88
+ def index():
89
+ return render_template("index.html")
90
+
91
+
92
+ # ----------------------------------------------------------------------------
93
+ # 1. One-Hot Encoding
94
+ # ----------------------------------------------------------------------------
95
+
96
+ @app.route("/api/onehot", methods=["POST"])
97
+ def api_onehot():
98
+ payload = request.get_json(force=True) or {}
99
+ corpus = clean_corpus(payload.get("corpus")) or DEFAULTS["onehot"]
100
+
101
+ tokenized = [tokenize(s) for s in corpus]
102
+ vocabulary = sorted(set(tok for sent in tokenized for tok in sent))
103
+ word_to_idx = {w: i for i, w in enumerate(vocabulary)}
104
+
105
+ # Manual / NumPy one-hot, identical approach to the notebook's
106
+ # one_hot_encode() helper, run once per unique vocabulary word.
107
+ vectors = {}
108
+ for word in vocabulary:
109
+ vec = np.zeros(len(vocabulary), dtype=int)
110
+ vec[word_to_idx[word]] = 1
111
+ vectors[word] = vec.tolist()
112
+
113
+ # sklearn OneHotEncoder cross-check (Method 2 in the notebook) so the
114
+ # numbers are guaranteed to match what scikit-learn itself produces.
115
+ flat_words = np.array([tok for sent in tokenized for tok in sent]).reshape(-1, 1)
116
+ encoder = OneHotEncoder(sparse_output=False)
117
+ sk_matrix = encoder.fit_transform(flat_words) if len(flat_words) else np.empty((0, 0))
118
+
119
+ sentences_out = []
120
+ for sent, toks in zip(corpus, tokenized):
121
+ sentences_out.append({
122
+ "sentence": sent,
123
+ "tokens": toks,
124
+ "vectors": [vectors[t] for t in toks],
125
+ })
126
+
127
+ return jsonify({
128
+ "corpus": corpus,
129
+ "vocabulary": vocabulary,
130
+ "vectorLength": len(vocabulary),
131
+ "vectors": vectors,
132
+ "sentences": sentences_out,
133
+ "sklearnCheck": {
134
+ "categories": encoder.categories_[0].tolist() if len(flat_words) else [],
135
+ "matrix": sk_matrix.astype(int).tolist(),
136
+ "inputWords": flat_words.flatten().tolist(),
137
+ },
138
+ })
139
+
140
+
141
+ # ----------------------------------------------------------------------------
142
+ # 2. Count Vectorizer
143
+ # ----------------------------------------------------------------------------
144
+
145
+ @app.route("/api/count-vectorizer", methods=["POST"])
146
+ def api_count_vectorizer():
147
+ payload = request.get_json(force=True) or {}
148
+ corpus = clean_corpus(payload.get("corpus")) or DEFAULTS["count"]
149
+ use_stopwords = bool(payload.get("stopWords"))
150
+ max_features = payload.get("maxFeatures")
151
+ new_doc = (payload.get("newDoc") or "").strip()
152
+
153
+ kwargs = {}
154
+ if use_stopwords:
155
+ kwargs["stop_words"] = "english"
156
+ if max_features:
157
+ try:
158
+ kwargs["max_features"] = int(max_features)
159
+ except (TypeError, ValueError):
160
+ pass
161
+
162
+ cv = CountVectorizer(**kwargs)
163
+ X = cv.fit_transform(corpus)
164
+ vocabulary = cv.get_feature_names_out().tolist()
165
+ matrix = X.toarray().tolist()
166
+
167
+ new_doc_result = None
168
+ if new_doc:
169
+ X_new = cv.transform([new_doc])
170
+ new_doc_result = {
171
+ "doc": new_doc,
172
+ "vector": X_new.toarray()[0].tolist(),
173
+ }
174
+
175
+ return jsonify({
176
+ "corpus": corpus,
177
+ "tokenizedDocs": [tokenize(s) for s in corpus],
178
+ "vocabulary": vocabulary,
179
+ "matrix": matrix,
180
+ "newDocResult": new_doc_result,
181
+ "settings": {"stopWords": use_stopwords, "maxFeatures": max_features},
182
+ })
183
+
184
+
185
+ # ----------------------------------------------------------------------------
186
+ # 3. Bag-of-Words
187
+ # ----------------------------------------------------------------------------
188
+
189
+ @app.route("/api/bow", methods=["POST"])
190
+ def api_bow():
191
+ payload = request.get_json(force=True) or {}
192
+ corpus = clean_corpus(payload.get("corpus")) or DEFAULTS["bow"]
193
+
194
+ tokenized = [tokenize(s) for s in corpus]
195
+ vocabulary = sorted(set(tok for sent in tokenized for tok in sent))
196
+
197
+ def bow_vector(tokens, vocab):
198
+ counts = {w: 0 for w in vocab}
199
+ for tok in tokens:
200
+ if tok in counts:
201
+ counts[tok] += 1
202
+ return list(counts.values())
203
+
204
+ matrix = [bow_vector(toks, vocabulary) for toks in tokenized]
205
+
206
+ cv_binary = CountVectorizer(binary=True)
207
+ X_binary = cv_binary.fit_transform(corpus)
208
+ binary_vocab = cv_binary.get_feature_names_out().tolist()
209
+ binary_matrix = X_binary.toarray().tolist()
210
+
211
+ cv_sim = CountVectorizer()
212
+ X_sim = cv_sim.fit_transform(corpus)
213
+ sim_matrix = cosine_similarity(X_sim).round(4).tolist()
214
+
215
+ return jsonify({
216
+ "corpus": corpus,
217
+ "tokenizedDocs": tokenized,
218
+ "vocabulary": vocabulary,
219
+ "matrix": matrix,
220
+ "binaryVocabulary": binary_vocab,
221
+ "binaryMatrix": binary_matrix,
222
+ "cosineSimilarity": sim_matrix,
223
+ })
224
+
225
+
226
+ # ----------------------------------------------------------------------------
227
+ # 4. N-grams
228
+ # ----------------------------------------------------------------------------
229
+
230
+ def generate_ngrams(text, n):
231
+ tokens = tokenize(text)
232
+ return [" ".join(tokens[i:i + n]) for i in range(len(tokens) - n + 1)]
233
+
234
+
235
+ @app.route("/api/ngrams", methods=["POST"])
236
+ def api_ngrams():
237
+ payload = request.get_json(force=True) or {}
238
+ sentence = (payload.get("sentence") or "").strip() or DEFAULTS["ngrams_sentence"]
239
+ corpus = clean_corpus(payload.get("corpus")) or DEFAULTS["ngrams_corpus"]
240
+
241
+ manual = {
242
+ "unigrams": generate_ngrams(sentence, 1),
243
+ "bigrams": generate_ngrams(sentence, 2),
244
+ "trigrams": generate_ngrams(sentence, 3),
245
+ }
246
+
247
+ ngram_matrices = {}
248
+ for key, ngram_range, label in [
249
+ ("unigrams", (1, 1), "Unigrams"),
250
+ ("bigrams", (2, 2), "Bigrams"),
251
+ ("uni_bi", (1, 2), "Unigrams + Bigrams"),
252
+ ]:
253
+ cv_ng = CountVectorizer(ngram_range=ngram_range)
254
+ X_ng = cv_ng.fit_transform(corpus)
255
+ ngram_matrices[key] = {
256
+ "label": label,
257
+ "vocabulary": cv_ng.get_feature_names_out().tolist(),
258
+ "matrix": X_ng.toarray().tolist(),
259
+ }
260
+
261
+ cv_char = CountVectorizer(analyzer="char_wb", ngram_range=(2, 3))
262
+ X_char = cv_char.fit_transform(corpus)
263
+ char_vocab = cv_char.get_feature_names_out().tolist()
264
+
265
+ return jsonify({
266
+ "sentence": sentence,
267
+ "manual": manual,
268
+ "corpus": corpus,
269
+ "ngramMatrices": ngram_matrices,
270
+ "charLevel": {
271
+ "vocabularySize": len(char_vocab),
272
+ "sample": char_vocab[:15],
273
+ },
274
+ })
275
+
276
+
277
+ # ----------------------------------------------------------------------------
278
+ # 5. TF-IDF
279
+ # ----------------------------------------------------------------------------
280
+
281
+ def compute_tf(tokens):
282
+ tf = {}
283
+ for w in tokens:
284
+ tf[w] = tf.get(w, 0) + 1
285
+ return {w: c / len(tokens) for w, c in tf.items()} if tokens else {}
286
+
287
+
288
+ def compute_idf(tokenized_docs):
289
+ N = len(tokenized_docs)
290
+ all_words = set(w for doc in tokenized_docs for w in doc)
291
+ idf = {}
292
+ for w in all_words:
293
+ df = sum(1 for doc in tokenized_docs if w in doc)
294
+ idf[w] = math.log(N / (1 + df)) + 1 # sklearn-style smoothing
295
+ return idf
296
+
297
+
298
+ @app.route("/api/tfidf", methods=["POST"])
299
+ def api_tfidf():
300
+ payload = request.get_json(force=True) or {}
301
+ corpus = clean_corpus(payload.get("corpus")) or DEFAULTS["tfidf"]
302
+
303
+ tokenized_docs = [tokenize(s) for s in corpus]
304
+ idf = compute_idf(tokenized_docs)
305
+
306
+ manual_per_doc = []
307
+ for doc, tokens in zip(corpus, tokenized_docs):
308
+ tf = compute_tf(tokens)
309
+ scores = {w: round(tf.get(w, 0) * idf[w], 4) for w in tf}
310
+ manual_per_doc.append({
311
+ "doc": doc,
312
+ "tf": {w: round(v, 4) for w, v in tf.items()},
313
+ "tfidf": dict(sorted(scores.items(), key=lambda x: x[1], reverse=True)),
314
+ })
315
+
316
+ tfidf_vec = TfidfVectorizer()
317
+ X_tfidf = tfidf_vec.fit_transform(corpus)
318
+ feature_names = tfidf_vec.get_feature_names_out().tolist()
319
+ sk_matrix = X_tfidf.toarray().round(4).tolist()
320
+
321
+ top_words = []
322
+ arr = X_tfidf.toarray()
323
+ for i, doc in enumerate(corpus):
324
+ row = arr[i]
325
+ top_idx = row.argsort()[-3:][::-1]
326
+ words = [
327
+ {"word": feature_names[j], "score": round(float(row[j]), 4)}
328
+ for j in top_idx if row[j] > 0
329
+ ]
330
+ top_words.append({"doc": doc, "top": words})
331
+
332
+ return jsonify({
333
+ "corpus": corpus,
334
+ "idf": dict(sorted(idf.items(), key=lambda x: x[1], reverse=True)),
335
+ "manualPerDoc": manual_per_doc,
336
+ "sklearn": {"vocabulary": feature_names, "matrix": sk_matrix},
337
+ "topWords": top_words,
338
+ })
339
+
340
+
341
+ # ----------------------------------------------------------------------------
342
+ # 6. Word Embeddings (Word2Vec + FastText via gensim)
343
+ # ----------------------------------------------------------------------------
344
+
345
+ @app.route("/api/embeddings", methods=["POST"])
346
+ def api_embeddings():
347
+ payload = request.get_json(force=True) or {}
348
+ raw_sentences = clean_corpus(payload.get("sentences")) or DEFAULTS["embeddings_sentences"]
349
+ sentences = [tokenize(s) for s in raw_sentences]
350
+ sentences = [s for s in sentences if s]
351
+
352
+ word_pairs = payload.get("wordPairs") or [
353
+ ["cat", "dog"], ["cat", "mat"], ["king", "queen"], ["paris", "berlin"],
354
+ ]
355
+ plot_words_req = payload.get("plotWords") or [
356
+ "cat", "dog", "king", "queen", "paris", "berlin", "man", "woman",
357
+ ]
358
+
359
+ try:
360
+ from gensim.models import Word2Vec, FastText
361
+ except ImportError:
362
+ return jsonify({"error": "gensim is not installed on the server. "
363
+ "Run: pip install gensim"}), 500
364
+
365
+ t0 = time.time()
366
+ model_sg = Word2Vec(sentences, vector_size=50, window=3, min_count=1,
367
+ sg=1, epochs=200, seed=42)
368
+ model_cbow = Word2Vec(sentences, vector_size=50, window=3, min_count=1,
369
+ sg=0, epochs=200, seed=42)
370
+ train_seconds = round(time.time() - t0, 3)
371
+
372
+ vocab = sorted(model_sg.wv.key_to_index.keys())
373
+
374
+ similarities = []
375
+ for w1, w2 in word_pairs:
376
+ if w1 in model_sg.wv and w2 in model_sg.wv:
377
+ similarities.append({
378
+ "pair": [w1, w2],
379
+ "skipgram": round(float(model_sg.wv.similarity(w1, w2)), 4),
380
+ "cbow": round(float(model_cbow.wv.similarity(w1, w2)), 4),
381
+ })
382
+ else:
383
+ similarities.append({"pair": [w1, w2], "error": "word not in vocabulary"})
384
+
385
+ most_similar = {}
386
+ for w in ["cat", "king", "paris"]:
387
+ if w in model_sg.wv:
388
+ most_similar[w] = [
389
+ {"word": ww, "score": round(float(sc), 4)}
390
+ for ww, sc in model_sg.wv.most_similar(w, topn=3)
391
+ ]
392
+
393
+ plot_words = [w for w in plot_words_req if w in model_sg.wv]
394
+ pca_points = []
395
+ if len(plot_words) >= 2:
396
+ vectors = np.array([model_sg.wv[w] for w in plot_words])
397
+ coords = PCA(n_components=2, random_state=42).fit_transform(vectors)
398
+ pca_points = [
399
+ {"word": w, "x": round(float(c[0]), 4), "y": round(float(c[1]), 4)}
400
+ for w, c in zip(plot_words, coords)
401
+ ]
402
+
403
+ sample_word = plot_words[0] if plot_words else (vocab[0] if vocab else None)
404
+ sample_vector = (
405
+ model_sg.wv[sample_word][:10].round(4).tolist() if sample_word else []
406
+ )
407
+
408
+ fasttext_demo = None
409
+ try:
410
+ ft = FastText(sentences, vector_size=50, window=3, min_count=1,
411
+ epochs=100, seed=42)
412
+ in_vocab_word = sample_word or (vocab[0] if vocab else None)
413
+ oov_word = (in_vocab_word + "like") if in_vocab_word else "catlike"
414
+ fasttext_demo = {
415
+ "inVocabWord": in_vocab_word,
416
+ "inVocabVector": ft.wv[in_vocab_word][:10].round(4).tolist() if in_vocab_word else [],
417
+ "oovWord": oov_word,
418
+ "oovVector": ft.wv[oov_word][:10].round(4).tolist(),
419
+ "note": f"'{oov_word}' never appeared during training, but FastText "
420
+ f"still produces a vector from its character n-grams.",
421
+ }
422
+ except Exception as exc: # pragma: no cover - defensive only
423
+ fasttext_demo = {"error": str(exc)}
424
+
425
+ return jsonify({
426
+ "sentences": raw_sentences,
427
+ "vocabulary": vocab,
428
+ "vocabSize": len(vocab),
429
+ "trainSeconds": train_seconds,
430
+ "sampleWord": sample_word,
431
+ "sampleVector": sample_vector,
432
+ "similarities": similarities,
433
+ "mostSimilar": most_similar,
434
+ "pcaPoints": pca_points,
435
+ "fastText": fasttext_demo,
436
+ })
437
+
438
+
439
+ if __name__ == "__main__":
440
+ import os
441
+ port = int(os.environ.get("PORT", 5000))
442
+ debug = os.environ.get("FLASK_DEBUG", "1") == "1"
443
+ app.run(debug=debug, host="0.0.0.0", port=port)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask>=3.0
2
+ scikit-learn>=1.3
3
+ numpy>=1.24
4
+ pandas>=2.0
5
+ scipy>=1.10
6
+ gensim>=4.3