Sina1138 commited on
Commit
caf5ce4
·
1 Parent(s): 7a14afe

Enhance RSA data handling: parse listener/speaker distributions from CSV, integrate into review metadata, and update visualization logic for improved agreement representation.

Browse files
interface/Demo.py CHANGED
@@ -1067,7 +1067,10 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
1067
  review_sentence_lists.append([s for s, _ in review_item])
1068
  review_items_cache.append((review_item, rebuttal_html))
1069
 
1070
- # For agreement mode, build uniqueness dict upfront for render_agreement_html
 
 
 
1071
  if show_consensuality:
1072
  for idx in range(number_of_displayed_reviews):
1073
  review_item, _ = review_items_cache[idx]
@@ -1080,7 +1083,23 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
1080
  if not is_noise_sentence(sentence) and abs(score) >= HIGHLIGHT_THRESHOLD:
1081
  consensuality_dict[sentence] = score
1082
 
 
 
 
 
 
 
 
 
 
1083
  agreement_updates = []
 
 
 
 
 
 
 
1084
  for i in range(10):
1085
  if i < number_of_displayed_reviews:
1086
  review_item, rebuttal_html = review_items_cache[i]
@@ -1128,10 +1147,13 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
1128
  sentences_for_review = [s for s, _ in review_item]
1129
  agreement_html = render_agreement_html(
1130
  sentences_for_review, consensuality_dict,
1131
- listener={}, speaker={},
1132
  num_reviews=number_of_displayed_reviews,
1133
  label=f"Agreement in Review {i + 1}",
1134
  )
 
 
 
1135
  agreement_updates.append(gr.update(visible=True, value=agreement_html))
1136
  else:
1137
  agreement_updates.append(gr.update(visible=False, value=""))
@@ -1154,13 +1176,31 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
1154
  general_rebuttal_update = gr.update(visible=False, value="")
1155
 
1156
  # Set most common opinions (as HTML cards with context)
1157
- # Most Unique/Divergent is hidden at top level consistent with
1158
- # the interactive tab which embeds divergent cards per-review.
1159
  if show_consensuality and consensuality_dict:
1160
  scores = pd.Series(consensuality_dict)
1161
- most_common = scores.sort_values(ascending=True).head(5).index.tolist()
1162
 
1163
- most_common_html = format_summary_cards(most_common, consensuality_dict, review_sentence_lists, "common")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
 
1165
  most_common_visibility = gr.update(visible=True, value=most_common_html)
1166
  most_unique_visibility = gr.update(visible=False, value="")
 
1067
  review_sentence_lists.append([s for s, _ in review_item])
1068
  review_items_cache.append((review_item, rebuttal_html))
1069
 
1070
+ # For agreement mode, build uniqueness dict and extract RSA distributions
1071
+ # RSA listener/speaker come from metadata (if pipeline saved them)
1072
+ prep_listener = None
1073
+ prep_speaker = None
1074
  if show_consensuality:
1075
  for idx in range(number_of_displayed_reviews):
1076
  review_item, _ = review_items_cache[idx]
 
1083
  if not is_noise_sentence(sentence) and abs(score) >= HIGHLIGHT_THRESHOLD:
1084
  consensuality_dict[sentence] = score
1085
 
1086
+ # Extract listener/speaker from metadata (saved by pipeline)
1087
+ meta_for_year = state.get("metadata_for_year", {})
1088
+ submission_meta = meta_for_year.get(current_id, {})
1089
+ if isinstance(submission_meta, dict):
1090
+ rsa_data = submission_meta.get("rsa", {})
1091
+ if rsa_data:
1092
+ prep_listener = rsa_data.get("listener")
1093
+ prep_speaker = rsa_data.get("speaker")
1094
+
1095
  agreement_updates = []
1096
+ divergent_per_review = {}
1097
+ # Pre-compute per-review divergent cards if we have RSA data
1098
+ if show_consensuality and prep_listener and prep_speaker and consensuality_dict:
1099
+ divergent_per_review = format_divergent_cards(
1100
+ consensuality_dict, review_sentence_lists, prep_listener, prep_speaker,
1101
+ )
1102
+
1103
  for i in range(10):
1104
  if i < number_of_displayed_reviews:
1105
  review_item, rebuttal_html = review_items_cache[i]
 
1147
  sentences_for_review = [s for s, _ in review_item]
1148
  agreement_html = render_agreement_html(
1149
  sentences_for_review, consensuality_dict,
1150
+ listener=prep_listener, speaker=prep_speaker,
1151
  num_reviews=number_of_displayed_reviews,
1152
  label=f"Agreement in Review {i + 1}",
1153
  )
1154
+ # Append per-review divergent cards (if RSA data available)
1155
+ if i in divergent_per_review:
1156
+ agreement_html += divergent_per_review[i]
1157
  agreement_updates.append(gr.update(visible=True, value=agreement_html))
1158
  else:
1159
  agreement_updates.append(gr.update(visible=False, value=""))
 
1176
  general_rebuttal_update = gr.update(visible=False, value="")
1177
 
1178
  # Set most common opinions (as HTML cards with context)
1179
+ # Uses entropy-based ranking when listener data is available (like interactive tab)
 
1180
  if show_consensuality and consensuality_dict:
1181
  scores = pd.Series(consensuality_dict)
 
1182
 
1183
+ if prep_listener:
1184
+ # Entropy-based ranking: same logic as interactive tab
1185
+ n_seed = min(15, len(scores))
1186
+ seed = scores.nsmallest(n_seed).index.tolist()
1187
+
1188
+ def _listener_entropy(sent):
1189
+ dist = prep_listener.get(sent, {})
1190
+ ent = 0.0
1191
+ for p in dist.values():
1192
+ if p > 0:
1193
+ ent -= p * math.log(p)
1194
+ return ent
1195
+ seed.sort(key=_listener_entropy, reverse=True)
1196
+ most_common = seed[:5]
1197
+ else:
1198
+ most_common = scores.sort_values(ascending=True).head(5).index.tolist()
1199
+
1200
+ most_common_html = format_summary_cards(
1201
+ most_common, consensuality_dict, review_sentence_lists, "common",
1202
+ listener=prep_listener, speaker=prep_speaker,
1203
+ )
1204
 
1205
  most_common_visibility = gr.update(visible=True, value=most_common_html)
1206
  most_unique_visibility = gr.update(visible=False, value="")
pipeline/run_glimpse_scoring.py CHANGED
@@ -153,7 +153,8 @@ def convert_pk_to_csv(pickle_path: Path,
153
  if not isinstance(results, list):
154
  raise ValueError("Unexpected pickle structure")
155
 
156
- # Extract and flatten results
 
157
  csv_data = []
158
  for index, result in enumerate(tqdm(results, desc="Converting")):
159
  row = {
@@ -163,6 +164,22 @@ def convert_pk_to_csv(pickle_path: Path,
163
  'consensuality_scores': json.dumps(result.get('consensuality_scores').to_dict())
164
  if isinstance(result.get('consensuality_scores'), pd.Series) else None,
165
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  csv_data.append(row)
167
 
168
  # Save to expected location
 
153
  if not isinstance(results, list):
154
  raise ValueError("Unexpected pickle structure")
155
 
156
+ # Extract and flatten results — include listener/speaker distributions
157
+ # for rich agreement visualization in the UI (R% bars, divergent cards)
158
  csv_data = []
159
  for index, result in enumerate(tqdm(results, desc="Converting")):
160
  row = {
 
164
  'consensuality_scores': json.dumps(result.get('consensuality_scores').to_dict())
165
  if isinstance(result.get('consensuality_scores'), pd.Series) else None,
166
  }
167
+
168
+ # Save listener_df: DataFrame (N_reviews × K_sentences) of log-probs
169
+ # Stored as JSON: {sentence: {R1: logprob, R2: logprob, ...}}
170
+ listener_df = result.get('listener_df')
171
+ if listener_df is not None and isinstance(listener_df, pd.DataFrame):
172
+ row['listener_df'] = listener_df.to_json()
173
+ else:
174
+ row['listener_df'] = None
175
+
176
+ # Save speaker_df: DataFrame (N_reviews × K_sentences) of log-probs
177
+ speaker_df = result.get('speaker_df')
178
+ if speaker_df is not None and isinstance(speaker_df, pd.DataFrame):
179
+ row['speaker_df'] = speaker_df.to_json()
180
+ else:
181
+ row['speaker_df'] = None
182
+
183
  csv_data.append(row)
184
 
185
  # Save to expected location
pipeline/scored_reviews_builder.py CHANGED
@@ -23,6 +23,57 @@ BASE_DIR = Config.BASE_DIR
23
  # return sentences
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  def preprocessed_scores(
27
  original_csv_path: Path,
28
  scored_csv_path: Path,
@@ -47,6 +98,9 @@ def preprocessed_scores(
47
  except Exception as e:
48
  print(f"Warning: Could not load rebuttals from {raw_data_csv_path}: {e}")
49
 
 
 
 
50
  scored_reviews = {}
51
  submission_review_counters = {} # Track which review # we're on for each submission
52
 
@@ -77,6 +131,10 @@ def preprocessed_scores(
77
  print("Problematic string:", consensuality_scores_str)
78
  continue # skip this problematic entry
79
 
 
 
 
 
80
  # Get polarity scores
81
  polarity_rows = polarity_df[polarity_df["id"] == review_id]
82
  polarity_dict = dict(zip(polarity_rows["sentence"], polarity_rows["polarity"]))
@@ -116,7 +174,7 @@ def preprocessed_scores(
116
  "rebuttal": rebuttal
117
  })
118
 
119
- return scored_reviews
120
 
121
 
122
  def save_all_scored_reviews(
@@ -140,7 +198,7 @@ def save_all_scored_reviews(
140
  polarity_csv_path = polarity_dir / f"polarity_scored_reviews_{year}.csv"
141
  topic_csv_path = topic_dir / f"topic_scored_reviews_{year}.csv"
142
  scored_csv_path = scored_csv_dir / f"GLIMPSE_results_{year}.csv"
143
- scored_reviews = preprocessed_scores(
144
  original_csv_path,
145
  scored_csv_path,
146
  polarity_csv_path,
@@ -213,7 +271,7 @@ def build_dataset(
213
  raw_data_csv_path = BASE_DIR / "data" / f"all_reviews_{year}.csv"
214
 
215
  # Use existing preprocessed_scores function
216
- scored_reviews = preprocessed_scores(
217
  original_csv_path,
218
  scored_csv_path,
219
  polarity_csv_path,
@@ -253,6 +311,11 @@ def build_dataset(
253
  'has_rebuttal': bool(rebuttal_str.strip()) if rebuttal_str else False,
254
  }
255
 
 
 
 
 
 
256
  all_scored_reviews.append({
257
  "year": year,
258
  "scored_dict": scored_reviews,
 
23
  # return sentences
24
 
25
 
26
+ def _parse_rsa_distributions(scored_df: pd.DataFrame, review_id: str) -> dict:
27
+ """
28
+ Parse listener/speaker DataFrames from the GLIMPSE results CSV.
29
+
30
+ Returns dict with:
31
+ listener: {sentence: {R1: prob, R2: prob, ...}} — normalized probabilities
32
+ speaker: {R1: {sentence: prob}, ...} — normalized probabilities
33
+ Returns empty dict if data not available (backward compat with older CSVs).
34
+ """
35
+ import numpy as np
36
+
37
+ row = scored_df[scored_df["id"] == review_id].iloc[0]
38
+
39
+ listener_json = row.get("listener_df") if "listener_df" in scored_df.columns else None
40
+ speaker_json = row.get("speaker_df") if "speaker_df" in scored_df.columns else None
41
+
42
+ if not listener_json or not speaker_json or pd.isna(listener_json) or pd.isna(speaker_json):
43
+ return {}
44
+
45
+ try:
46
+ listener_df = pd.read_json(listener_json)
47
+ speaker_df = pd.read_json(speaker_json)
48
+ except Exception:
49
+ return {}
50
+
51
+ num_reviews = len(listener_df)
52
+ review_labels = [f"R{i+1}" for i in range(num_reviews)]
53
+
54
+ # Listener: exponentiate log-probs, normalize per column (per sentence)
55
+ listener_probs = np.exp(listener_df.values)
56
+ col_sums = listener_probs.sum(axis=0, keepdims=True)
57
+ col_sums = np.where(col_sums > 0, col_sums, 1.0)
58
+ listener_probs = listener_probs / col_sums
59
+ listener = {
60
+ sent: {review_labels[i]: float(listener_probs[i, j]) for i in range(num_reviews)}
61
+ for j, sent in enumerate(listener_df.columns)
62
+ }
63
+
64
+ # Speaker: exponentiate log-probs, normalize per row (per review)
65
+ speaker_probs = np.exp(speaker_df.values)
66
+ row_sums = speaker_probs.sum(axis=1, keepdims=True)
67
+ row_sums = np.where(row_sums > 0, row_sums, 1.0)
68
+ speaker_probs = speaker_probs / row_sums
69
+ speaker = {
70
+ review_labels[i]: {sent: float(speaker_probs[i, j]) for j, sent in enumerate(speaker_df.columns)}
71
+ for i in range(num_reviews)
72
+ }
73
+
74
+ return {"listener": listener, "speaker": speaker}
75
+
76
+
77
  def preprocessed_scores(
78
  original_csv_path: Path,
79
  scored_csv_path: Path,
 
98
  except Exception as e:
99
  print(f"Warning: Could not load rebuttals from {raw_data_csv_path}: {e}")
100
 
101
+ # Pre-parse RSA distributions per submission (listener/speaker are shared across reviews)
102
+ rsa_cache = {}
103
+
104
  scored_reviews = {}
105
  submission_review_counters = {} # Track which review # we're on for each submission
106
 
 
131
  print("Problematic string:", consensuality_scores_str)
132
  continue # skip this problematic entry
133
 
134
+ # Parse RSA distributions (once per submission)
135
+ if review_id not in rsa_cache:
136
+ rsa_cache[review_id] = _parse_rsa_distributions(scored_df, review_id)
137
+
138
  # Get polarity scores
139
  polarity_rows = polarity_df[polarity_df["id"] == review_id]
140
  polarity_dict = dict(zip(polarity_rows["sentence"], polarity_rows["polarity"]))
 
174
  "rebuttal": rebuttal
175
  })
176
 
177
+ return scored_reviews, rsa_cache
178
 
179
 
180
  def save_all_scored_reviews(
 
198
  polarity_csv_path = polarity_dir / f"polarity_scored_reviews_{year}.csv"
199
  topic_csv_path = topic_dir / f"topic_scored_reviews_{year}.csv"
200
  scored_csv_path = scored_csv_dir / f"GLIMPSE_results_{year}.csv"
201
+ scored_reviews, _ = preprocessed_scores(
202
  original_csv_path,
203
  scored_csv_path,
204
  polarity_csv_path,
 
271
  raw_data_csv_path = BASE_DIR / "data" / f"all_reviews_{year}.csv"
272
 
273
  # Use existing preprocessed_scores function
274
+ scored_reviews, rsa_cache = preprocessed_scores(
275
  original_csv_path,
276
  scored_csv_path,
277
  polarity_csv_path,
 
311
  'has_rebuttal': bool(rebuttal_str.strip()) if rebuttal_str else False,
312
  }
313
 
314
+ # Merge RSA distributions into metadata (listener/speaker per submission)
315
+ for review_id, rsa_data in rsa_cache.items():
316
+ if rsa_data and review_id in review_metadata:
317
+ review_metadata[review_id]['rsa'] = rsa_data
318
+
319
  all_scored_reviews.append({
320
  "year": year,
321
  "scored_dict": scored_reviews,