ahmed-ayman commited on
Commit
e754c5c
·
1 Parent(s): a1a6da3

Upload 8 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ IMDB_Balanced_10000_Rows.csv filter=lfs diff=lfs merge=lfs -text
37
+ train_data.csv filter=lfs diff=lfs merge=lfs -text
IMDB_Balanced_10000_Rows.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c25d2c73fcfd7542e4138150a172bb27d20ccfde564d875f378f7857f94b92f
3
+ size 13205542
app.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import re
4
+ import nltk
5
+ import joblib
6
+ from nltk.corpus import stopwords
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.linear_model import LogisticRegression
9
+
10
+ # Download stopwords if not available
11
+ nltk.download('stopwords')
12
+
13
+ # Paths to datasets
14
+ train_path = r"E:\Projects\Sentiment Analysis Project DEPI\train_data.csv"
15
+ test_path = r"E:\Projects\Sentiment Analysis Project DEPI\test_data.csv"
16
+
17
+ # Load datasets
18
+ train_df = pd.read_csv(train_path)
19
+
20
+ # Text preprocessing function
21
+ def preprocess_text(text):
22
+ text = text.lower() # Convert to lowercase
23
+ text = re.sub(r'\W', ' ', text) # Remove special characters
24
+ text = re.sub(r'\s+', ' ', text).strip() # Remove extra spaces
25
+ stop_words = set(stopwords.words('english')) # Load stopwords
26
+ text = ' '.join(word for word in text.split() if word not in stop_words) # Remove stopwords
27
+ return text
28
+
29
+ # Apply preprocessing
30
+ train_df['cleaned_review'] = train_df['review'].astype(str).apply(preprocess_text)
31
+
32
+ # Train the model
33
+ vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1,2))
34
+ X_train_tfidf = vectorizer.fit_transform(train_df['cleaned_review'])
35
+ y_train = train_df['sentiment']
36
+
37
+ model = LogisticRegression(max_iter=500)
38
+ model.fit(X_train_tfidf, y_train)
39
+
40
+ # Save the model and vectorizer
41
+ joblib.dump(model, "sentiment_model.pkl")
42
+ joblib.dump(vectorizer, "tfidf_vectorizer.pkl")
43
+
44
+ # Load model and vectorizer for prediction
45
+ model = joblib.load("sentiment_model.pkl")
46
+ vectorizer = joblib.load("tfidf_vectorizer.pkl")
47
+
48
+ # Gradio prediction function
49
+ def predict_sentiment(review):
50
+ processed_review = preprocess_text(review) # Preprocess input
51
+ review_tfidf = vectorizer.transform([processed_review]) # Convert to TF-IDF
52
+ prediction = model.predict(review_tfidf)[0] # Get prediction
53
+ return f"Predicted Sentiment: {prediction}"
54
+
55
+ # Gradio UI
56
+ interface = gr.Interface(
57
+ fn=predict_sentiment,
58
+ inputs=gr.Textbox(label="Enter a Review"),
59
+ outputs=gr.Textbox(label="Sentiment Prediction"),
60
+ title="Sentiment Analysis App",
61
+ description="Enter a review, and the model will predict if it's Positive, Negative, or Neutral."
62
+ )
63
+
64
+ # Launch the app
65
+ interface.launch()
preprocessing with stemming.ipynb ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 8,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stderr",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "[nltk_data] Downloading package stopwords to\n",
13
+ "[nltk_data] C:\\Users\\HP\\AppData\\Roaming\\nltk_data...\n",
14
+ "[nltk_data] Package stopwords is already up-to-date!\n"
15
+ ]
16
+ }
17
+ ],
18
+ "source": [
19
+ "import pandas as pd\n",
20
+ "import re\n",
21
+ "from sklearn.model_selection import train_test_split\n",
22
+ "from nltk.stem import PorterStemmer\n",
23
+ "import nltk\n",
24
+ "nltk.download('stopwords')\n",
25
+ "from nltk.corpus import stopwords\n",
26
+ "from nltk.corpus import stopwords\n",
27
+ "from nltk.stem import WordNetLemmatizer\n",
28
+ "from collections import Counter\n",
29
+ "import matplotlib.pyplot as plt\n",
30
+ "lemmatizer = WordNetLemmatizer()\n",
31
+ "stemmer = PorterStemmer()\n"
32
+ ]
33
+ },
34
+ {
35
+ "cell_type": "code",
36
+ "execution_count": 5,
37
+ "metadata": {},
38
+ "outputs": [
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "<bound method NDFrame.head of review sentiment\n",
44
+ "0 Starts really well, nice intro and build up fo... negative\n",
45
+ "1 Terrific movie: If you did not watch yet, you ... positive\n",
46
+ "2 I've seen hundreds of silent movies. Some will... positive\n",
47
+ "3 i had been looking for this film for so long b... positive\n",
48
+ "4 Good: Engaging cinematic firefights, great pre... positive\n",
49
+ "... ... ...\n",
50
+ "9995 I almost made a fool of myself when I was goin... negative\n",
51
+ "9996 I feel it is my duty as a lover of horror film... negative\n",
52
+ "9997 Why was this film made? What were the creators... negative\n",
53
+ "9998 If it is true that sadomasochism is a two-side... positive\n",
54
+ "9999 Now I did watch this when it first came out on... negative\n",
55
+ "\n",
56
+ "[10000 rows x 2 columns]>\n"
57
+ ]
58
+ }
59
+ ],
60
+ "source": [
61
+ "file_path = \"E:\\Projects\\Sentiment Analysis Project DEPI\\IMDB_Balanced_10000_Rows.csv\"\n",
62
+ "df = pd.read_csv(file_path)\n",
63
+ "print(df.head)"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": 6,
69
+ "metadata": {},
70
+ "outputs": [
71
+ {
72
+ "ename": "LookupError",
73
+ "evalue": "\n**********************************************************************\n Resource \u001b[93mstopwords\u001b[0m not found.\n Please use the NLTK Downloader to obtain the resource:\n\n \u001b[31m>>> import nltk\n >>> nltk.download('stopwords')\n \u001b[0m\n For more information see: https://www.nltk.org/data.html\n\n Attempted to load \u001b[93mcorpora/stopwords\u001b[0m\n\n Searched in:\n - 'C:\\\\Users\\\\HP/nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\share\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\lib\\\\nltk_data'\n - 'C:\\\\Users\\\\HP\\\\AppData\\\\Roaming\\\\nltk_data'\n - 'C:\\\\nltk_data'\n - 'D:\\\\nltk_data'\n - 'E:\\\\nltk_data'\n**********************************************************************\n",
74
+ "output_type": "error",
75
+ "traceback": [
76
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
77
+ "\u001b[1;31mLookupError\u001b[0m Traceback (most recent call last)",
78
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\corpus\\util.py:84\u001b[0m, in \u001b[0;36mLazyCorpusLoader.__load\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 83\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m---> 84\u001b[0m root \u001b[38;5;241m=\u001b[39m nltk\u001b[38;5;241m.\u001b[39mdata\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msubdir\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mzip_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 85\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mLookupError\u001b[39;00m:\n",
79
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\data.py:579\u001b[0m, in \u001b[0;36mfind\u001b[1;34m(resource_name, paths)\u001b[0m\n\u001b[0;32m 578\u001b[0m resource_not_found \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00msep\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00msep\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m--> 579\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mLookupError\u001b[39;00m(resource_not_found)\n",
80
+ "\u001b[1;31mLookupError\u001b[0m: \n**********************************************************************\n Resource \u001b[93mstopwords\u001b[0m not found.\n Please use the NLTK Downloader to obtain the resource:\n\n \u001b[31m>>> import nltk\n >>> nltk.download('stopwords')\n \u001b[0m\n For more information see: https://www.nltk.org/data.html\n\n Attempted to load \u001b[93mcorpora/stopwords.zip/stopwords/\u001b[0m\n\n Searched in:\n - 'C:\\\\Users\\\\HP/nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\share\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\lib\\\\nltk_data'\n - 'C:\\\\Users\\\\HP\\\\AppData\\\\Roaming\\\\nltk_data'\n - 'C:\\\\nltk_data'\n - 'D:\\\\nltk_data'\n - 'E:\\\\nltk_data'\n**********************************************************************\n",
81
+ "\nDuring handling of the above exception, another exception occurred:\n",
82
+ "\u001b[1;31mLookupError\u001b[0m Traceback (most recent call last)",
83
+ "Cell \u001b[1;32mIn[6], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# stop words to be removed later\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m custom_stopwords \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m(stopwords\u001b[38;5;241m.\u001b[39mwords(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124menglish\u001b[39m\u001b[38;5;124m'\u001b[39m)) \n\u001b[0;32m 3\u001b[0m \u001b[38;5;66;03m# Make sure 'review' is a valid column name\u001b[39;00m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28mprint\u001b[39m(df\u001b[38;5;241m.\u001b[39mcolumns)\n",
84
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\corpus\\util.py:120\u001b[0m, in \u001b[0;36mLazyCorpusLoader.__getattr__\u001b[1;34m(self, attr)\u001b[0m\n\u001b[0;32m 117\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m attr \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m__bases__\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m 118\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mLazyCorpusLoader object has no attribute \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__bases__\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m--> 120\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__load()\n\u001b[0;32m 121\u001b[0m \u001b[38;5;66;03m# This looks circular, but its not, since __load() changes our\u001b[39;00m\n\u001b[0;32m 122\u001b[0m \u001b[38;5;66;03m# __class__ to something new:\u001b[39;00m\n\u001b[0;32m 123\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, attr)\n",
85
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\corpus\\util.py:86\u001b[0m, in \u001b[0;36mLazyCorpusLoader.__load\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 84\u001b[0m root \u001b[38;5;241m=\u001b[39m nltk\u001b[38;5;241m.\u001b[39mdata\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msubdir\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mzip_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 85\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mLookupError\u001b[39;00m:\n\u001b[1;32m---> 86\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[0;32m 88\u001b[0m \u001b[38;5;66;03m# Load the corpus.\u001b[39;00m\n\u001b[0;32m 89\u001b[0m corpus \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__reader_cls(root, \u001b[38;5;241m*\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__args, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__kwargs)\n",
86
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\corpus\\util.py:81\u001b[0m, in \u001b[0;36mLazyCorpusLoader.__load\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 79\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m 80\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m---> 81\u001b[0m root \u001b[38;5;241m=\u001b[39m nltk\u001b[38;5;241m.\u001b[39mdata\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msubdir\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 82\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mLookupError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m 83\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n",
87
+ "File \u001b[1;32mc:\\Users\\HP\\anaconda3\\Lib\\site-packages\\nltk\\data.py:579\u001b[0m, in \u001b[0;36mfind\u001b[1;34m(resource_name, paths)\u001b[0m\n\u001b[0;32m 577\u001b[0m sep \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m*\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m*\u001b[39m \u001b[38;5;241m70\u001b[39m\n\u001b[0;32m 578\u001b[0m resource_not_found \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00msep\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00msep\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m--> 579\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mLookupError\u001b[39;00m(resource_not_found)\n",
88
+ "\u001b[1;31mLookupError\u001b[0m: \n**********************************************************************\n Resource \u001b[93mstopwords\u001b[0m not found.\n Please use the NLTK Downloader to obtain the resource:\n\n \u001b[31m>>> import nltk\n >>> nltk.download('stopwords')\n \u001b[0m\n For more information see: https://www.nltk.org/data.html\n\n Attempted to load \u001b[93mcorpora/stopwords\u001b[0m\n\n Searched in:\n - 'C:\\\\Users\\\\HP/nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\share\\\\nltk_data'\n - 'c:\\\\Users\\\\HP\\\\anaconda3\\\\lib\\\\nltk_data'\n - 'C:\\\\Users\\\\HP\\\\AppData\\\\Roaming\\\\nltk_data'\n - 'C:\\\\nltk_data'\n - 'D:\\\\nltk_data'\n - 'E:\\\\nltk_data'\n**********************************************************************\n"
89
+ ]
90
+ }
91
+ ],
92
+ "source": [
93
+ "# stop words to be removed later\n",
94
+ "custom_stopwords = set(stopwords.words('english')) \n",
95
+ "# Make sure 'review' is a valid column name\n",
96
+ "print(df.columns) "
97
+ ]
98
+ },
99
+ {
100
+ "cell_type": "code",
101
+ "execution_count": null,
102
+ "metadata": {},
103
+ "outputs": [
104
+ {
105
+ "name": "stdout",
106
+ "output_type": "stream",
107
+ "text": [
108
+ " review \\\n",
109
+ "0 Starts really well, nice intro and build up fo... \n",
110
+ "1 Terrific movie: If you did not watch yet, you ... \n",
111
+ "2 I've seen hundreds of silent movies. Some will... \n",
112
+ "3 i had been looking for this film for so long b... \n",
113
+ "4 Good: Engaging cinematic firefights, great pre... \n",
114
+ "5 I say this. If you want to see art, you go to ... \n",
115
+ "6 Hey, it's only TV. Sure, it's STAR TREK, the m... \n",
116
+ "7 this movie has lot of downsides and thats all ... \n",
117
+ "8 How can you tell that a horror movie is terrib... \n",
118
+ "9 Brian De Palma's undeniable virtuosity can't r... \n",
119
+ "\n",
120
+ " processed_review \n",
121
+ "0 start realli well nice intro build main charac... \n",
122
+ "1 terrif movi watch yet must watch geena davi sa... \n",
123
+ "2 seen hundr silent movi alway classic nosferatu... \n",
124
+ "3 look film long found seen younger love second ... \n",
125
+ "4 good engag cinemat firefight great present veh... \n",
126
+ "5 say want see art go art galleri want see movi ... \n",
127
+ "6 hey tv sure star trek belov bla bla great one ... \n",
128
+ "7 movi lot downsid that could see pain long aw d... \n",
129
+ "8 tell horror movi terribl stop laugh cours plot... \n",
130
+ "9 brian de palma undeni virtuos realli camouflag... \n"
131
+ ]
132
+ }
133
+ ],
134
+ "source": [
135
+ "def preprocess_text(text):\n",
136
+ " if isinstance(text, str): # Ensure text is a string\n",
137
+ " ## CLEANING\n",
138
+ " # remove special characters\n",
139
+ " text = re.sub(r'\\W+', ' ', text) \n",
140
+ " # remove digits\n",
141
+ " text = re.sub(r'\\d+', '', text)\n",
142
+ " ## LOWERCASING\n",
143
+ " text = text.lower()\n",
144
+ " ## TOKENIZATION\n",
145
+ " words = text.split()\n",
146
+ " ## REMOVE STOPWORDS\n",
147
+ " words = [w for w in words if w not in custom_stopwords] \n",
148
+ " ## APPLY LEMMATIZATION\n",
149
+ " words = [lemmatizer.lemmatize(w) for w in words]\n",
150
+ " ## APPLY STEMMING\n",
151
+ " words = [stemmer.stem(w) for w in words]\n",
152
+ " return ' '.join(words)\n",
153
+ " return \"\"\n",
154
+ "\n",
155
+ "# Apply preprocessing\n",
156
+ "df['processed_review'] = df['review'].apply(preprocess_text)\n",
157
+ "# Show original vs processed text\n",
158
+ "print(df[['review', 'processed_review']].head(10)) "
159
+ ]
160
+ },
161
+ {
162
+ "cell_type": "code",
163
+ "execution_count": null,
164
+ "metadata": {},
165
+ "outputs": [
166
+ {
167
+ "data": {
168
+ "image/png": "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31cg9rceHOb3va7Py6+/C/tuCnUF9bkeZHbu3NmqUaOG+SOX9m3//v3zhCH2ce9+r6jp06dbFStWNGWzPWE/9+7x4OnTp62BAwdaF198sQmv99xzjwlaubfj+pyifP+XtjD9j+/GwQAAAAAgsHBOFAAAAADYQIgCAAAAABsIUQAAAABgAyEKAAAAAGwgRAEAAACADYQoAAAAALCBEAUAAAAANhCiAAAAAMAGQhQA4IL9+uuvEhYWJps3bxZ/sX37drn22mulQoUKctVVV/m6OQCAIEKIAoAg8Oijj5oQM3bsWLflixYtMstD0ejRo6VSpUqyY8cOWb58eb7rpaWlSb9+/aR27dpSvnx5qVu3rgwePFgyMjJKtb0AgMBBiAKAIKEjLuPGjZMjR45IsDh37lyxn7t792658cYbTSiKioryuM4vv/wi11xzjezcuVPmzJkju3btkmnTppnQdd1118mff/4p/vTes7OzJScnxyftAQD8f4QoAAgSnTp1kpiYGBkzZky+67z44ot5prZNmTJFLrvsMrdRrbvvvltee+01qVmzplSrVk1efvllOX/+vDz77LNSvXp1qVOnjsycOdPjFLrrr7/eBLpmzZrJN9984/b4Tz/9JLfffrtcdNFFZtsPP/ywpKenOx+/+eabZdCgQTJkyBCJjo6WLl26eHwfGiS0TdqOiIgI856WLl3qfFxH3zZs2GDW0d/1fXuSmJhoRp+++OILad++vcTFxZn2ffXVV/L777/L888/71z37Nmz8txzz0lsbKx5zYYNG8qMGTOcj2/btk26du0qVapUkcqVK0u7du1MkHO8L31PrrSPta8d9DN45ZVX5JFHHjHbeOKJJ2TWrFmm/z/55BOJj483r7tv3z7TlmeeeUYuvfRSM9rWtm1b+frrr53bcjxv2bJlcsUVV5j+vu222+TAgQNubXjvvfekadOmZru1atUyfe9w9OhRefzxx6VGjRqmPbfccov8+OOPzsf19w4dOpj3qo+3atVK1q9f77GfASDYEKIAIEiULVvWBJ8333xTfvvttwva1ooVK2T//v2yatUqmTx5spkapwHh4osvlh9++EGeeuopefLJJ/O8joas4cOHy6ZNm8xIzl133eWcFqcH5XogfvXVV5uDbQ09Bw8elJ49e7ptY/bs2SbYfPfdd2ZUyJM33nhDJk2aJBMnTpQt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",
169
+ "text/plain": [
170
+ "<Figure size 1000x500 with 1 Axes>"
171
+ ]
172
+ },
173
+ "metadata": {},
174
+ "output_type": "display_data"
175
+ }
176
+ ],
177
+ "source": [
178
+ "# BAG OF WORDS ASSUMPTION\n",
179
+ "word_counts = Counter(\" \".join(df[\"processed_review\"]).split())\n",
180
+ "# Get word frequencies\n",
181
+ "occurrences_values = list(word_counts.values())\n",
182
+ "\n",
183
+ "# Plot histogram to help decide the threshold that will be used to remove rare words later\n",
184
+ "# threshold here is the minimum number of occurrences for one word so that it will not be considered as a rare word\n",
185
+ "plt.figure(figsize=(10, 5))\n",
186
+ "plt.hist(occurrences_values, bins=range(1, 21), edgecolor='black') # Only show words appearing ≤ 20 times \n",
187
+ "plt.yscale('log') # Log scale helps visualize better\n",
188
+ "plt.xlabel(\"Number of Occurrences\")\n",
189
+ "plt.ylabel(\"Number of Words\")\n",
190
+ "plt.title(\"Word Occurrences Distribution\")\n",
191
+ "plt.show()"
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "code",
196
+ "execution_count": null,
197
+ "metadata": {},
198
+ "outputs": [
199
+ {
200
+ "name": "stdout",
201
+ "output_type": "stream",
202
+ "text": [
203
+ " processed_review \\\n",
204
+ "0 start realli well nice intro build main charac... \n",
205
+ "1 terrif movi watch yet must watch geena davi sa... \n",
206
+ "2 seen hundr silent movi alway classic nosferatu... \n",
207
+ "3 look film long found seen younger love second ... \n",
208
+ "4 good engag cinemat firefight great present veh... \n",
209
+ "5 say want see art go art galleri want see movi ... \n",
210
+ "6 hey tv sure star trek belov bla bla great one ... \n",
211
+ "7 movi lot downsid that could see pain long aw d... \n",
212
+ "8 tell horror movi terribl stop laugh cours plot... \n",
213
+ "9 brian de palma undeni virtuos realli camouflag... \n",
214
+ "\n",
215
+ " processed_review2 \n",
216
+ "0 start realli well nice intro build main charac... \n",
217
+ "1 terrif movi watch yet must watch geena davi sa... \n",
218
+ "2 seen hundr silent movi alway classic nosferatu... \n",
219
+ "3 look film long found seen younger love second ... \n",
220
+ "4 good engag cinemat firefight great present veh... \n",
221
+ "5 say want see art go art galleri want see movi ... \n",
222
+ "6 hey tv sure star trek belov bla bla great one ... \n",
223
+ "7 movi lot downsid that could see pain long aw d... \n",
224
+ "8 tell horror movi terribl stop laugh cours plot... \n",
225
+ "9 brian de palma undeni realli camouflag fact pl... \n"
226
+ ]
227
+ }
228
+ ],
229
+ "source": [
230
+ "## REMOVE RARE WORDS\n",
231
+ "# Most words appear very few times (1-5 occurrences)\n",
232
+ "# The count drops significantly as frequency increases \n",
233
+ "# we will take a threshold of 4 which Keeps moderately rare words that might still carry meaning but Removes very infrequent words that could add noise\n",
234
+ "\n",
235
+ "threshold = 4\n",
236
+ "# Identify rare words\n",
237
+ "rare_words = {w for w, c in word_counts.items() if c <= threshold} \n",
238
+ "\n",
239
+ "def remove_rare_words(text):\n",
240
+ " if isinstance(text, str): # Ensure text is a string\n",
241
+ " words = text.split()\n",
242
+ " return ' '.join([w for w in words if w not in rare_words])\n",
243
+ " return \"\"\n",
244
+ "\n",
245
+ "# Apply to dataset\n",
246
+ "df['processed_review2'] = df['processed_review'].apply(remove_rare_words)\n",
247
+ "\n",
248
+ "# Check results\n",
249
+ "print(df[['processed_review', 'processed_review2']].head(10))\n"
250
+ ]
251
+ },
252
+ {
253
+ "cell_type": "code",
254
+ "execution_count": null,
255
+ "metadata": {},
256
+ "outputs": [
257
+ {
258
+ "name": "stdout",
259
+ "output_type": "stream",
260
+ "text": [
261
+ "Training set size: 8000\n",
262
+ "Testing set size: 2000\n",
263
+ "Training Data:\n",
264
+ " review sentiment\n",
265
+ "9254 The makers have chosen the best people for the... positive\n",
266
+ "1561 Obviously, there wasn't a huge budget for this... positive\n",
267
+ "1670 I turned this off within the first five minute... negative\n",
268
+ "6087 I just want to say that I am so glad somebody ... positive\n",
269
+ "6669 What was I thinking when I rented this one? Wh... negative\n",
270
+ "\n",
271
+ "Testing Data:\n",
272
+ " review sentiment\n",
273
+ "6252 Wow - Thank god I was on an airplane and could... negative\n",
274
+ "4684 Not only was the plot of this film contrived w... negative\n",
275
+ "1731 This movie has the most beautiful opening sequ... positive\n",
276
+ "4742 For three quarters of an hour, the story gradu... negative\n",
277
+ "4521 It's been a long time since such an original, ... positive\n"
278
+ ]
279
+ }
280
+ ],
281
+ "source": [
282
+ "X = df['review'] \n",
283
+ "y = df['sentiment'] \n",
284
+ "\n",
285
+ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
286
+ "\n",
287
+ "print(\"Training set size:\", len(X_train))\n",
288
+ "print(\"Testing set size:\", len(X_test))\n",
289
+ "\n",
290
+ "# ////////////////////////////////\n",
291
+ "print(\"Training Data:\")\n",
292
+ "print(pd.DataFrame({'review': X_train, 'sentiment': y_train}).head())\n",
293
+ "\n",
294
+ "print(\"\\nTesting Data:\")\n",
295
+ "print(pd.DataFrame({'review': X_test, 'sentiment': y_test}).head())"
296
+ ]
297
+ }
298
+ ],
299
+ "metadata": {
300
+ "kernelspec": {
301
+ "display_name": "base",
302
+ "language": "python",
303
+ "name": "python3"
304
+ },
305
+ "language_info": {
306
+ "codemirror_mode": {
307
+ "name": "ipython",
308
+ "version": 3
309
+ },
310
+ "file_extension": ".py",
311
+ "mimetype": "text/x-python",
312
+ "name": "python",
313
+ "nbconvert_exporter": "python",
314
+ "pygments_lexer": "ipython3",
315
+ "version": "3.11.5"
316
+ }
317
+ },
318
+ "nbformat": 4,
319
+ "nbformat_minor": 2
320
+ }
requirements.txt ADDED
Binary file (2.35 kB). View file
 
sentiment_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e67e56ce47206bd9d232f6bbc65f135d6629aa0ccfa48a768ee6ca9894becf18
3
+ size 41071
test_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
tfidf_vectorizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:af60c3a3696d3e71075bb33e5cdb8c0f028fc548093f3898385131d697b4e51a
3
+ size 185799
train_data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67adc65add490db75b4839e04a3ce8130bf2dedd24fe79ea2bd64cfd10072e5e
3
+ size 10501309