File size: 7,604 Bytes
1dffecd
57b215d
 
 
 
 
1dffecd
57b215d
 
 
 
 
 
 
 
1dffecd
57b215d
 
 
 
 
 
1dffecd
 
 
 
 
 
 
 
57b215d
1dffecd
57b215d
 
1dffecd
57b215d
 
 
 
1dffecd
 
57b215d
 
 
 
 
 
 
 
293f9ac
 
57b215d
 
 
 
1dffecd
57b215d
 
 
1dffecd
57b215d
 
d6da013
57b215d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1dffecd
57b215d
 
 
 
 
 
d6da013
 
 
57b215d
1dffecd
 
4a9a3c2
 
 
 
 
 
 
 
 
 
1dffecd
 
 
4a9a3c2
 
 
 
 
 
 
 
 
 
1dffecd
 
 
57b215d
d6da013
57b215d
1dffecd
57b215d
1dffecd
d6da013
 
 
 
 
 
 
 
1dffecd
57b215d
 
1dffecd
d82d8c2
 
 
 
1dffecd
d82d8c2
 
 
1dffecd
57b215d
 
d82d8c2
 
 
 
 
 
 
 
 
 
 
 
 
d6da013
57b215d
 
 
 
1dffecd
57b215d
d6da013
 
1dffecd
 
 
d6da013
 
 
 
 
 
1dffecd
 
 
 
 
 
 
d6da013
1dffecd
 
 
 
 
 
 
 
 
 
 
 
4a9a3c2
1dffecd
 
6a2eee4
1dffecd
4a9a3c2
1dffecd
 
 
d6da013
1dffecd
 
 
 
 
e1a952f
d6da013
 
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
# Import Libraries
import streamlit as st
import re
import pickle
import joblib
import nltk
import os
import numpy as np
import pandas as pd
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow import keras
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import PorterStemmer

# --- Setup NLTK ---
nltk_data_path = os.path.join("/tmp", "nltk_data")
os.makedirs(nltk_data_path, exist_ok=True)
nltk.data.path.append(nltk_data_path)
nltk.download("stopwords", download_dir=nltk_data_path)
nltk.download("punkt", download_dir=nltk_data_path)

# --- Loading Info ---
st.markdown(
    '<p style="color:gray; font-size:14px; font-style:italic;">'
    'Loading models and resources from local storage... '
    'Please be patient and DO NOT refresh the page :)'
    '</p>',
    unsafe_allow_html=True
)

# --- Cached Loading Functions ---
@st.cache_resource
def load_sentiment_model():
    path = "./src/best_model.keras"
    return keras.models.load_model(path)

@st.cache_resource
def load_tokenizer_params():
    tokenizer_path = "./src/tokenizer.pkl"
    params_path = "./src/params.pkl"
    with open(tokenizer_path, "rb") as f:
        tokenizer = pickle.load(f)
    with open(params_path, "rb") as f:
        params = pickle.load(f)
    return tokenizer, params

@st.cache_resource
def load_topic_models():
    neg_path = "./src/fastopic_negative_model_10.pkl"
    pos_path = "./src/fastopic_positive_model_10.pkl"
    neg_model = joblib.load(neg_path)
    pos_model = joblib.load(pos_path)
    return neg_model, pos_model

# --- Load all resources once ---
sentiment_model = load_sentiment_model()
tokenizer, params = load_tokenizer_params()
topic_model_neg, topic_model_pos = load_topic_models()

max_len = params["max_len"]

# --- Preprocessing Function (NLTK) ---
negations = {"not", "no", "never"}
stpwrds_en = set(stopwords.words("english")) - negations
stemmer = PorterStemmer()

replacements = {
    "sia": "sq",
    "flown": "fly",
    "flew": "fly",
    "alway": "always",
    "boarding": "board",
    "told": "tell",
    "said": "say",
    "booked": "book",
    "paid": "pay",
    "well": "good",
    "aircraft": "plane"
}

def text_preprocessing(text):
    text = text.lower()
    text = re.sub(r"\\n", " ", text)
    text = text.strip()
    text = re.sub(r'[^a-z0-9\s]', ' ', text)
    tokens = word_tokenize(text)
    tokens = [replacements.get(word, word) for word in tokens]
    tokens = [word for word in tokens if word not in stpwrds_en]
    tokens = [stemmer.stem(word) for word in tokens]
    if len(tokens) == 0:
        return "emptytext"
    return ' '.join(tokens)

# --- Topic Labels ---
topic_labels_neg = {
    0: "Service Attitude",
    1: "Ticket Price",
    2: "In-Flight Accommodation",
    3: "Boarding & Luggage Issues",
    4: "Refund & Payment Difficulties",
    5: "Meal Quality",
    6: "Accessibility & Assistance",
    7: "Safety & Hygiene",
    8: "Seat Comfort",
    9: "Quality of Amenities"
}

topic_labels_pos = {
    0: "Destination-based compliment",
    1: "Seat & cabin comfort",
    2: "Destination-based compliment",
    3: "Transit accommodation",
    4: "Meals & in-flight services",
    5: "Meals & in-flight services",
    6: "Seat & cabin comfort / Aircraft condition",
    7: "Destination-based compliment",
    8: "Miscellaneous experiences",
    9: "Destination-based compliment"
}

# --- Streamlit App ---
def run():
    # st.title("ACRE - Automated Customer Review Analysis")
    st.subheader("Sentiment & Topic Prediction for SQ Customer Reviews")

    st.markdown(
    """

    This section will help you understand how the **ACRE** system works.  

    Simply fill in the form below with either a dummy or real customer review, and the system will:  



    1. **Preprocess** your review text (cleaning, tokenization, and stemming).  

    2. **Predict sentiment** (Positive or Negative) along with a confidence score.  

    3. **Identify the most relevant topic** associated with the review, based on the predicted sentiment.  



    Use this tool to simulate how Singapore Airlines can transform raw customer feedback into **structured, data-driven insights**.

    """
    )

    with st.form(key='SQ-sentiment-analysis'):
        date = st.date_input("Review Date")
        platform = st.selectbox('Review Platform', ('Mobile', 'Desktop'), index=0)
        rating = st.number_input('Rating', min_value=0, max_value=5, value=3, step=1)
        st.markdown('---')
        text = st.text_input('Customer Review', value='--customer review--')
        title = st.text_input('Review Title', value='--review title--')
        vote = st.slider('Helpful Vote', min_value=0, max_value=200, value=50, step=1)
        st.markdown('---')
        submitted = st.form_submit_button('Predict')

    if submitted:
        st.markdown("---")
        st.write("### Input Data")
        data_inf = {
            'published_date': date,
            'published_platform': platform,
            'rating': rating,
            'type': 'Review',
            'text': text,
            'title': title,
            'helpful_votes': vote
        }
        st.dataframe(pd.DataFrame([data_inf]))

        # Preprocess (pakai kolom 'text')
        processed = text_preprocessing(text)
        seq = tokenizer.texts_to_sequences([processed])
        padded = pad_sequences(seq, maxlen=max_len, padding="post", truncating="post")

        # Sentiment Prediction
        pred_probs = sentiment_model.predict(padded)

        if pred_probs.shape[1] == 1:  
            # Binary sigmoid
            p_pos = float(pred_probs[0][0])
            p_neg = 1 - p_pos
            if p_pos >= 0.5:
                sentiment_label = "Positive"
                confidence = p_pos
            else:
                sentiment_label = "Negative"
                confidence = p_neg
        else:
            # Softmax
            pred_class = np.argmax(pred_probs, axis=1)[0]
            label_map = {0: "Negative", 1: "Positive"}
            sentiment_label = label_map[pred_class]
            confidence = float(pred_probs[0][pred_class])

        # --- Sentiment Output with Color ---
        color = "green" if sentiment_label == "Positive" else "red"
        st.markdown(
            f"<p style='font-size:22px; font-weight:bold; color:{color};'>"
            f"Predicted Sentiment: {sentiment_label} "
            f"(Confidence: {confidence:.2f})</p>",
            unsafe_allow_html=True
        )

        # Topic Prediction
        st.write("### Topic Modeling")
        if sentiment_label == "Negative":
            probs = topic_model_neg.transform([text])[0]
            topic_id = int(np.argmax(probs))
            topic_name = topic_labels_neg.get(topic_id, "Unknown Topic")
            st.write("**Using Negative Model**")
        else:
            probs = topic_model_pos.transform([text])[0]
            topic_id = int(np.argmax(probs))
            topic_name = topic_labels_pos.get(topic_id, "Unknown Topic")
            st.write("**Using Positive Model**")

        # --- Topic Output with Color ---
        st.markdown(
            f"<p style='font-size:20px; font-weight:bold; color:{color};'>"
            f"Topic {topic_id}: {topic_name}</p>",
            unsafe_allow_html=True
        )

        # Probabilities tetap ditampilkan
        st.write("**Probabilities:**", probs.tolist())