anky2002 commited on
Commit
ceda018
Β·
verified Β·
1 Parent(s): b7d09ad

Upload agents/model_agent.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. agents/model_agent.py +254 -0
agents/model_agent.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FORENSIQ β€” Generative Model Agent
3
+ Detects architecture-specific signatures:
4
+ - Frequency grid artifacts (8Γ—8, 16Γ—16 periodic patterns from GANs)
5
+ - Diffusion residuals (spectral notches at step noise harmonics)
6
+ - Model fingerprinting (frequency-domain generator attribution)
7
+ """
8
+
9
+ import numpy as np
10
+ from PIL import Image
11
+ from scipy.signal import find_peaks
12
+ from scipy.ndimage import gaussian_filter
13
+ from typing import Dict, Any
14
+
15
+ from agents.optical_agent import AgentEvidence
16
+
17
+
18
+ # ─── Frequency Grid Artifacts ────────────────────────────────────────
19
+ def analyze_frequency_grid(img: Image.Image) -> Dict[str, Any]:
20
+ """
21
+ GAN upsampling (deconv layers) creates periodic grid patterns
22
+ visible in 2D FFT as spectral peaks at multiples of 8Γ—8 or 16Γ—16.
23
+ """
24
+ gray = np.array(img.convert("L")).astype(np.float64)
25
+ h, w = gray.shape
26
+
27
+ # 2D FFT
28
+ fft = np.fft.fft2(gray)
29
+ fft_shift = np.fft.fftshift(fft)
30
+ magnitude = np.log(np.abs(fft_shift) + 1)
31
+
32
+ # Center row/column profiles
33
+ cy, cx = h // 2, w // 2
34
+ row_profile = magnitude[cy, :]
35
+ col_profile = magnitude[:, cx]
36
+
37
+ # Find periodic peaks (GAN artifacts show at multiples of N/8, N/16)
38
+ row_peaks, row_props = find_peaks(row_profile, distance=5, prominence=0.3)
39
+ col_peaks, col_props = find_peaks(col_profile, distance=5, prominence=0.3)
40
+
41
+ # Check for regular spacing (grid artifact signature)
42
+ def check_periodic(peaks, size):
43
+ if len(peaks) < 3:
44
+ return 0.0, []
45
+ spacings = np.diff(sorted(peaks))
46
+ # Expected spacing for 8Γ—8 grid: size/8
47
+ expected_8 = size / 8
48
+ expected_16 = size / 16
49
+ matches_8 = np.sum(np.abs(spacings - expected_8) < expected_8 * 0.15)
50
+ matches_16 = np.sum(np.abs(spacings - expected_16) < expected_16 * 0.15)
51
+ best = max(matches_8, matches_16)
52
+ return float(best / max(len(spacings), 1)), spacings.tolist()
53
+
54
+ row_periodic, row_spacings = check_periodic(row_peaks, w)
55
+ col_periodic, col_spacings = check_periodic(col_peaks, h)
56
+ grid_score = (row_periodic + col_periodic) / 2
57
+
58
+ # High-frequency vs mid-frequency energy ratio
59
+ hf_ring = magnitude.copy()
60
+ hf_ring[cy - h // 8:cy + h // 8, cx - w // 8:cx + w // 8] = 0
61
+ hf_energy = float(np.mean(hf_ring))
62
+ mf_mask = np.zeros_like(magnitude)
63
+ mf_mask[cy - h // 4:cy + h // 4, cx - w // 4:cx + w // 4] = 1
64
+ mf_mask[cy - h // 8:cy + h // 8, cx - w // 8:cx + w // 8] = 0
65
+ mf_energy = float(np.mean(magnitude * mf_mask))
66
+
67
+ if grid_score > 0.4:
68
+ score = 0.7
69
+ note = f"Periodic grid artifacts detected (GAN signature, periodicity={grid_score:.2f})"
70
+ elif grid_score > 0.2:
71
+ score = 0.3
72
+ note = f"Weak periodic patterns (possible GAN artifacts, periodicity={grid_score:.2f})"
73
+ else:
74
+ score = -0.2
75
+ note = "No periodic grid artifacts (natural frequency spectrum)"
76
+
77
+ return {
78
+ "test": "Frequency Grid Artifacts",
79
+ "grid_periodicity_score": round(grid_score, 4),
80
+ "row_peaks": len(row_peaks),
81
+ "col_peaks": len(col_peaks),
82
+ "hf_energy": round(hf_energy, 4),
83
+ "mf_energy": round(mf_energy, 4),
84
+ "score": score,
85
+ "note": note,
86
+ "magnitude_spectrum": magnitude,
87
+ }
88
+
89
+
90
+ # ─── Diffusion Residuals ────────────────────────────────────────────
91
+ def analyze_diffusion_residuals(img: Image.Image) -> Dict[str, Any]:
92
+ """
93
+ Diffusion models leave characteristic spectral notches and
94
+ autocorrelation patterns from the denoising step schedule.
95
+ """
96
+ gray = np.array(img.convert("L")).astype(np.float64)
97
+
98
+ # Compute radial power spectrum
99
+ fft = np.fft.fft2(gray)
100
+ fft_shift = np.fft.fftshift(fft)
101
+ power = np.abs(fft_shift) ** 2
102
+
103
+ h, w = power.shape
104
+ cy, cx = h // 2, w // 2
105
+ Y, X = np.mgrid[0:h, 0:w]
106
+ R = np.sqrt((X - cx) ** 2 + (Y - cy) ** 2).astype(int)
107
+
108
+ # Radially averaged power spectrum
109
+ max_r = min(cy, cx)
110
+ radial_power = np.zeros(max_r)
111
+ counts = np.zeros(max_r)
112
+ for r in range(max_r):
113
+ mask = R == r
114
+ if mask.any():
115
+ radial_power[r] = np.mean(power[mask])
116
+ counts[r] = np.sum(mask)
117
+
118
+ radial_power = np.log(radial_power + 1)
119
+
120
+ # Natural images: power ∝ 1/fΒ² β†’ linear decrease in log-log
121
+ freqs = np.arange(1, max_r)
122
+ log_freqs = np.log(freqs + 1)
123
+ log_power = radial_power[1:max_r]
124
+
125
+ # Fit linear model (1/fΒ² slope)
126
+ if len(log_freqs) > 10:
127
+ coeffs = np.polyfit(log_freqs, log_power, 1)
128
+ fitted = np.polyval(coeffs, log_freqs)
129
+ residuals = log_power - fitted
130
+
131
+ # Diffusion models: spectral notches (negative dips in residuals)
132
+ notches, _ = find_peaks(-residuals, prominence=0.5)
133
+
134
+ # Smoothness of spectral rolloff
135
+ smoothness = float(np.std(residuals))
136
+ slope = float(coeffs[0])
137
+ else:
138
+ notches = []
139
+ smoothness = 1.0
140
+ slope = 0.0
141
+
142
+ # Natural 1/fΒ² slope is ~ -2 in log-log
143
+ slope_deviation = abs(slope - (-2.0))
144
+
145
+ if len(notches) > 3:
146
+ score = 0.5
147
+ note = f"Spectral notches detected ({len(notches)} notches, diffusion signature)"
148
+ elif smoothness > 1.0 or slope_deviation > 1.5:
149
+ score = 0.3
150
+ note = f"Unnatural spectral rolloff (slope={slope:.2f}, deviation={slope_deviation:.2f})"
151
+ elif smoothness < 0.5 and slope_deviation < 0.5:
152
+ score = -0.3
153
+ note = f"Natural 1/fΒ² spectral profile (slope={slope:.2f})"
154
+ else:
155
+ score = 0.1
156
+ note = f"Mild spectral deviation (slope={slope:.2f})"
157
+
158
+ return {
159
+ "test": "Diffusion Residuals",
160
+ "spectral_notches": len(notches),
161
+ "spectral_smoothness": round(smoothness, 4),
162
+ "slope": round(slope, 4),
163
+ "slope_deviation": round(slope_deviation, 4),
164
+ "score": score,
165
+ "note": note,
166
+ }
167
+
168
+
169
+ # ─── Model Fingerprinting ───────────────────────────────────────────
170
+ def analyze_model_fingerprint(img: Image.Image) -> Dict[str, Any]:
171
+ """
172
+ Different generators leave unique frequency-domain signatures.
173
+ Analyzes autocorrelation and spectral texture for attribution.
174
+ """
175
+ gray = np.array(img.convert("L")).astype(np.float64)
176
+
177
+ # Autocorrelation map
178
+ fft = np.fft.fft2(gray)
179
+ power = np.abs(fft) ** 2
180
+ autocorr = np.real(np.fft.ifft2(power))
181
+ autocorr = np.fft.fftshift(autocorr)
182
+ autocorr = autocorr / (autocorr.max() + 1e-9)
183
+
184
+ h, w = autocorr.shape
185
+ cy, cx = h // 2, w // 2
186
+
187
+ # Check for periodic peaks in autocorrelation (GAN checkerboard)
188
+ # Exclude center peak
189
+ autocorr_masked = autocorr.copy()
190
+ r_exclude = max(h, w) // 20
191
+ Y, X = np.mgrid[0:h, 0:w]
192
+ center_mask = ((X - cx) ** 2 + (Y - cy) ** 2) < r_exclude ** 2
193
+ autocorr_masked[center_mask] = 0
194
+
195
+ max_secondary = float(np.max(autocorr_masked))
196
+
197
+ # High secondary peak = repetitive structure (GAN artifact)
198
+ if max_secondary > 0.3:
199
+ score = 0.6
200
+ note = f"Strong autocorrelation secondary peak ({max_secondary:.3f}) β€” GAN checkerboard pattern"
201
+ elif max_secondary > 0.15:
202
+ score = 0.3
203
+ note = f"Moderate autocorrelation peak ({max_secondary:.3f}) β€” possible generator artifacts"
204
+ else:
205
+ score = -0.2
206
+ note = f"Natural autocorrelation structure (peak={max_secondary:.3f})"
207
+
208
+ return {
209
+ "test": "Model Fingerprinting",
210
+ "max_secondary_peak": round(max_secondary, 4),
211
+ "score": score,
212
+ "note": note,
213
+ }
214
+
215
+
216
+ # ─── Main Agent Entry Point ─────────────────────────────────────────
217
+ def run_model_agent(img: Image.Image) -> AgentEvidence:
218
+ """Run all generative model detection tests."""
219
+ findings = []
220
+ scores = []
221
+
222
+ for fn in [analyze_frequency_grid, analyze_diffusion_residuals, analyze_model_fingerprint]:
223
+ try:
224
+ result = fn(img)
225
+ findings.append(result)
226
+ scores.append(result["score"])
227
+ except Exception as e:
228
+ findings.append({"test": fn.__name__, "error": str(e), "score": 0})
229
+
230
+ avg_score = float(np.mean(scores)) if scores else 0.0
231
+ confidence = min(1.0, 0.5 + 0.5 * abs(avg_score))
232
+
233
+ violations = [f["test"] for f in findings if f.get("score", 0) > 0.2]
234
+ compliant = [f["test"] for f in findings if f.get("score", 0) < -0.1]
235
+
236
+ if violations:
237
+ rationale = f"Generative model signatures detected: {', '.join(violations)}."
238
+ elif compliant:
239
+ rationale = f"No generator artifacts found: {', '.join(compliant)}."
240
+ else:
241
+ rationale = "Generator analysis inconclusive."
242
+
243
+ for f in findings:
244
+ if f.get("note"):
245
+ rationale += f" [{f['test']}]: {f['note']}."
246
+
247
+ return AgentEvidence(
248
+ agent_name="Generative Model Agent",
249
+ violation_score=np.clip(avg_score, -1, 1),
250
+ confidence=confidence,
251
+ failure_prob=max(0.0, 1.0 - len(scores) / 3),
252
+ rationale=rationale,
253
+ sub_findings=findings,
254
+ )