File size: 13,828 Bytes
9c94650 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 9c94650 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 20ef58c f05e4f5 20ef58c f05e4f5 9945bd1 20ef58c a75112b 20ef58c a75112b f05e4f5 a75112b 20ef58c f05e4f5 a75112b f05e4f5 a75112b f05e4f5 a75112b 9945bd1 f05e4f5 9c94650 f05e4f5 9c94650 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 f05e4f5 a75112b 9c94650 f05e4f5 9945bd1 9c94650 f05e4f5 9c94650 a75112b 9945bd1 9c94650 a75112b 9c94650 a75112b 9c94650 9945bd1 9c94650 a75112b 9c94650 9945bd1 9c94650 f05e4f5 9c94650 f05e4f5 9c94650 a75112b 9c94650 a75112b 9c94650 a75112b 9c94650 f05e4f5 a75112b 9c94650 a75112b 9945bd1 a75112b 9945bd1 a75112b 9945bd1 a75112b f05e4f5 9c94650 a75112b 9c94650 9945bd1 f05e4f5 9c94650 a75112b 9c94650 9945bd1 9c94650 20ef58c 9c94650 a75112b 9945bd1 a75112b 9c94650 a75112b 9c94650 f05e4f5 9c94650 a75112b | 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 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | import matplotlib
matplotlib.use('Agg')
import gradio as gr
import pandas as pd
import joblib
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from src.feature_extraction import FeatureExtractor
import os
import numpy as np
# Load Model
MODEL_PATH = 'best_svm_model.joblib'
try:
model = joblib.load(MODEL_PATH)
print(f"Model loaded from {MODEL_PATH}")
except Exception as e:
print(f"Error loading model: {e}")
print("Please run src/train_deployment_model.py first.")
model = None
feature_extractor = FeatureExtractor()
def predict_summary(title, text):
if not title or not text:
return "Please provide both Title and Content.", pd.DataFrame()
# 1. Extract Features
features_list = feature_extractor.calculate_features(title, text)
if not features_list:
return "Could not extract sentences.", pd.DataFrame()
df_features = pd.DataFrame(features_list)
# 2. Prepare X for prediction
feature_cols = ['f1_lead', 'f2_judul_sim', 'f3_freq_word', 'f4_sim_sent', 'f5_len_norm', 'f6_overlap']
X_pred = df_features[feature_cols]
# 3. Predict
if model:
predictions = model.predict(X_pred)
# Get probabilities for fallback
if hasattr(model, "predict_proba"):
probs = model.predict_proba(X_pred)[:, 1] # Probability of class 1
else:
probs = model.decision_function(X_pred)
else:
return "Model not loaded.", pd.DataFrame()
df_features['prediction'] = predictions
df_features['probability'] = probs
# 4. Generate Summary Text
summary_sentences = df_features[df_features['prediction'] == 1]['teks_kalimat'].tolist()
# Fallback Logic: If no sentences selected
if not summary_sentences:
best_idx = np.argmax(probs)
summary_sentences = [df_features.iloc[best_idx]['teks_kalimat']]
df_features.at[best_idx, 'prediction'] = 1
summary_text = " ".join(summary_sentences)
return summary_text, df_features[['teks_kalimat', 'prediction', 'probability'] + feature_cols]
def process_batch_file(file_path):
# Initialize returns for consistent tuple size (8 items: 7 data + 1 status message)
# output_csv, ui_df, df_results, fig1, fig2, fig3, fig4, status_msg
empty_returns = (None, None, None, None, None, None, None, "No file uploaded.")
if file_path is None:
return empty_returns
if hasattr(file_path, 'name'):
file_path_str = file_path.name
else:
file_path_str = str(file_path)
# Read File
try:
if file_path_str.endswith('.csv'):
df = pd.read_csv(file_path_str)
elif file_path_str.endswith('.xlsx'):
df = pd.read_excel(file_path_str)
else:
return None, None, None, None, None, None, None, "Invalid file format. Please upload .csv or .xlsx"
except Exception as e:
print(f"Error reading file: {e}")
return None, None, None, None, None, None, None, f"Error reading file: {str(e)}"
# --- Column Detection & Logic ---
cols = [c.lower() for c in df.columns]
# Case A: Sentence-level data (raw corpus) -> Needs Aggregation
# Check for ID columns AND Sentence columns
is_sentence_level = False
id_col = next((c for c in df.columns if c.lower() in ['id_berita', 'id', 'article_id']), None)
sent_col = next((c for c in df.columns if c.lower() in ['teks_kalimat', 'sentence', 'kalimat', 'text']), None)
if id_col and sent_col:
is_sentence_level = True
df_processed = pd.DataFrame(columns=['title', 'content'])
if is_sentence_level:
# Check for title column in raw data
title_col = next((c for c in df.columns if c.lower() in ['id_judul', 'judul', 'title', 'headline']), None)
# Aggregate
# Group by ID
grouped = df.groupby(id_col)
processed_rows = []
for name, group in grouped:
# Extract Title (first non-null)
title_text = "Untitled"
if title_col:
possible_titles = group[title_col].dropna().unique()
if len(possible_titles) > 0:
title_text = possible_titles[0]
# Combine sentences
sentences = group[sent_col].dropna().astype(str).tolist()
content_text = " ".join(sentences)
processed_rows.append({'title': title_text, 'content': content_text, 'original_id': name})
df_processed = pd.DataFrame(processed_rows)
status_msg = f"Detected sentence-level data. Aggregated {len(df)} sentences into {len(df_processed)} articles."
else:
# Case B: Article-level data -> Direct Mapping
title_col = next((c for c in df.columns if c.lower() in ['title', 'judul', 'headline']), None)
content_col = next((c for c in df.columns if c.lower() in ['content', 'isi', 'text', 'full_text', 'artikel']), None)
if not title_col or not content_col:
found_cols = ", ".join(df.columns)
return None, None, None, None, None, None, None, f"Could not find required columns. Found: {found_cols}. Need 'title'/'judul' and 'content'/'isi'/'teks_kalimat'."
df_processed = df.rename(columns={title_col: 'title', content_col: 'content'})
status_msg = f"Processed {len(df_processed)} articles."
# --- Summarization Logic ---
all_results = []
for idx, row in df_processed.iterrows():
title_raw = row['title']
content_raw = row['content']
# Validation
if pd.isna(title_raw) or pd.isna(content_raw):
continue
title = str(title_raw).strip()
content = str(content_raw).strip()
if not title or not content or content.lower() == 'nan':
continue
features_list = feature_extractor.calculate_features(title, content)
if not features_list:
continue
df_feat = pd.DataFrame(features_list)
X_pred = df_feat[['f1_lead', 'f2_judul_sim', 'f3_freq_word', 'f4_sim_sent', 'f5_len_norm', 'f6_overlap']]
if model:
preds = model.predict(X_pred)
if hasattr(model, "predict_proba"):
probs = model.predict_proba(X_pred)[:, 1]
else:
probs = model.decision_function(X_pred)
df_feat['prediction'] = preds
df_feat['probability'] = probs
summary_sents = df_feat[df_feat['prediction'] == 1]['teks_kalimat'].tolist()
if not summary_sents:
best_idx = np.argmax(probs)
summary_sents = [df_feat.iloc[best_idx]['teks_kalimat']]
summary_text = " ".join(summary_sents)
result_row = {
'article_id': row.get('original_id', idx),
'title': title,
'original_content': content,
'generated_summary': summary_text,
'original_length': len(content.split()),
'summary_length': len(summary_text.split()),
'compression_ratio': len(summary_text.split()) / len(content.split()) if len(content.split()) > 0 else 0,
'num_sentences_original': len(features_list),
'num_sentences_summary': len(summary_sents)
}
all_results.append(result_row)
if not all_results:
return None, pd.DataFrame(), pd.DataFrame(), None, None, None, None, status_msg + " But no valid rows processed."
df_results = pd.DataFrame(all_results)
output_csv = "batch_results.csv"
df_results.to_csv(output_csv, index=False)
# Analysis Plots
# 1. Ratio Distribution
fig1, ax1 = plt.subplots(figsize=(6, 4))
ax1.hist(df_results['compression_ratio'], bins=10, color='skyblue', edgecolor='black')
ax1.set_title('Distribution of Compression Ratios')
ax1.set_xlabel('Ratio')
ax1.set_ylabel('Frequency')
plt.tight_layout()
# 2. Length Scatter
fig2, ax2 = plt.subplots(figsize=(6, 4))
ax2.scatter(df_results['original_length'], df_results['summary_length'], alpha=0.5)
ax2.set_title('Original vs Summary Length (Words)')
ax2.set_xlabel('Original Length')
ax2.set_ylabel('Summary Length')
plt.tight_layout()
# 3. Sentence Count Comparison
fig3, ax3 = plt.subplots(figsize=(6, 4))
indices = range(len(df_results))
if len(df_results) > 20:
plot_df = df_results.head(20)
indices = range(20)
else:
plot_df = df_results
width = 0.35
# Use string casting for x-axis if article_ids are weird strings
x_labels = [str(x) for x in plot_df['article_id']]
ax3.bar(indices, plot_df['num_sentences_original'], width, label='Original')
ax3.bar([i + width for i in indices], plot_df['num_sentences_summary'], width, label='Summary')
ax3.set_title('Sentence Count Comparison (First 20)')
ax3.set_xticks([i + width/2 for i in indices])
ax3.set_xticklabels(x_labels, rotation=45, ha='right')
ax3.set_ylabel('Count')
ax3.legend()
plt.tight_layout()
# 4. Word Cloud
all_summary_text = " ".join(df_results['generated_summary'].tolist())
if all_summary_text.strip():
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(all_summary_text)
fig4, ax4 = plt.subplots(figsize=(10, 5))
ax4.imshow(wordcloud, interpolation='bilinear')
ax4.axis('off')
ax4.set_title('Word Cloud of Summaries')
else:
fig4 = None
ui_df = df_results[['article_id', 'title', 'generated_summary', 'compression_ratio', 'num_sentences_original', 'num_sentences_summary']]
return output_csv, ui_df, df_results, fig1, fig2, fig3, fig4, status_msg
def get_template():
df = pd.DataFrame([{'title': 'Judul Berita', 'content': 'Isi berita lengkap...'}])
output_path = 'template.xlsx'
df.to_excel(output_path, index=False)
return output_path
def show_details(evt: gr.SelectData, df):
if df is None or evt.index[0] >= len(df):
return "", "", ""
row = df.iloc[evt.index[0]]
return row['title'], row['original_content'], row['generated_summary']
# Gradio Interface
with gr.Blocks(title="Balinese News Summarizer") as demo:
gr.Markdown("# π° Balinese News Summarizer (SVM)")
with gr.Tab("π Manual Input"):
with gr.Row():
with gr.Column():
input_title = gr.Textbox(label="News Title", placeholder="Enter title here...")
input_content = gr.Textbox(label="News Content", placeholder="Enter full news text here...", lines=10)
btn_predict = gr.Button("Summarize", variant="primary")
with gr.Column():
output_summary = gr.Textbox(label="Generated Summary", lines=5, interactive=False)
output_df = gr.Dataframe(label="Sentence Analysis", headers=['teks_kalimat', 'prediction', 'probability', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6'])
btn_predict.click(fn=predict_summary, inputs=[input_title, input_content], outputs=[output_summary, output_df])
with gr.Tab("π Batch Analysis"):
gr.Markdown("Upload a CSV/Excel file. Supports both **Article-level** (columns: title, content) and **Sentence-level** (columns: id_berita, id_judul, teks_kalimat) formats.")
batch_results_state = gr.State()
with gr.Row():
file_upload = gr.File(label="Upload File", file_types=['.csv', '.xlsx'])
with gr.Column():
btn_template = gr.Button("Download Excel Template")
btn_process = gr.Button("Process Batch", variant="primary")
# Status Message
status_output = gr.Textbox(label="Status", interactive=False)
# Output Section
with gr.Row():
download_result = gr.File(label="Download Results (CSV)")
gr.Markdown("### Batch Results Preview (Select a row to view details below)")
batch_output_df = gr.Dataframe(label="Results Table", interactive=False)
# Detailed View
with gr.Group():
gr.Markdown("### Selected Article Details")
with gr.Row():
detail_title = gr.Textbox(label="Title", interactive=False)
with gr.Row():
detail_original = gr.Textbox(label="Original Content", lines=5, interactive=False)
detail_summary = gr.Textbox(label="Generated Summary", lines=5, interactive=False)
# Plots
gr.Markdown("### Analysis Visualization")
with gr.Row():
plot1 = gr.Plot(label="Compression Ratio Distribution")
plot2 = gr.Plot(label="Length Comparison")
with gr.Row():
plot3 = gr.Plot(label="Sentence Count Comparison")
with gr.Row():
plot4 = gr.Plot(label="Word Cloud")
# Event Wiring
btn_template.click(fn=get_template, inputs=None, outputs=file_upload)
btn_process.click(
fn=process_batch_file,
inputs=file_upload,
outputs=[download_result, batch_output_df, batch_results_state, plot1, plot2, plot3, plot4, status_output]
)
# Row Selection Event
batch_output_df.select(
fn=show_details,
inputs=[batch_results_state],
outputs=[detail_title, detail_original, detail_summary]
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)
|