youssef commited on
Commit
b6710f4
·
1 Parent(s): 2cc82cb

fixed part 3 backend

Browse files
app.py CHANGED
@@ -1,25 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
 
3
  cache_dir = "/tmp/hf_cache"
4
  os.environ["TRANSFORMERS_CACHE"] = cache_dir
5
  os.makedirs(cache_dir, exist_ok=True)
6
 
7
- import io
8
- import json
9
- import asyncio
10
- from pathlib import Path
11
-
12
- import pandas as pd
13
- import torch
14
- from fastapi import FastAPI, UploadFile, File, Form, HTTPException
15
- from fastapi.middleware.cors import CORSMiddleware
16
- from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
17
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
18
-
19
- from relations.predict_bert import predict_relation
20
- from aba.aba_builder import prepare_aba_plus_framework, build_aba_framework_from_text
21
- from gradual.computations import compute_gradual_semantics
22
- from gradual.models import GradualInput, GradualOutput
23
 
24
  # -------------------- Config -------------------- #
25
 
@@ -46,6 +45,8 @@ app.add_middleware(
46
  )
47
 
48
  # -------------------- Endpoints -------------------- #
 
 
49
  @app.get("/")
50
  def root():
51
  return {"message": "Argument Mining API is running..."}
@@ -73,7 +74,8 @@ async def predict_csv_stream(file: UploadFile):
73
  completed = 0
74
  for _, row in df.iterrows():
75
  try:
76
- result = predict_relation(row["parent"], row["child"], model, tokenizer, device)
 
77
  completed += 1
78
  payload = {
79
  "parent": row["parent"],
@@ -88,7 +90,6 @@ async def predict_csv_stream(file: UploadFile):
88
  yield f"data: {json.dumps({'error': str(e), 'parent': row.get('parent'), 'child': row.get('child')})}\n\n"
89
  await asyncio.sleep(0)
90
 
91
-
92
  return StreamingResponse(event_generator(), media_type="text/event-stream")
93
 
94
 
@@ -159,14 +160,37 @@ def get_aba_example(filename: str):
159
 
160
  # --- Gradual semantics --- #
161
 
 
 
 
 
 
 
 
 
 
 
162
  @app.post("/gradual", response_model=GradualOutput)
163
  def compute_gradual(input_data: GradualInput):
164
- """API endpoint to compute Weighted h-Categorizer samples and convex hull."""
165
- return compute_gradual_semantics(
166
- A=input_data.A,
 
 
 
167
  R=input_data.R,
168
  n_samples=input_data.n_samples,
169
- max_iter=input_data.max_iter
 
 
 
 
 
 
 
 
 
 
170
  )
171
 
172
 
@@ -176,8 +200,10 @@ def list_gradual_examples():
176
  List all available gradual semantics example files.
177
  Each example must be a JSON file with structure:
178
  {
179
- "args": ["A", "B", "C"],
180
- "relations": [["A", "B"], ["B", "C"]]
 
 
181
  }
182
  """
183
  if not GRADUAL_EXAMPLES_DIR.exists():
@@ -196,7 +222,7 @@ def list_gradual_examples():
196
  @app.get("/gradual-examples/{example_name}")
197
  def get_gradual_example(example_name: str):
198
  """
199
- Return the content of a specific gradual example.
200
  Example: GET /gradual-examples/simple.json
201
  """
202
  file_path = GRADUAL_EXAMPLES_DIR / example_name
@@ -208,4 +234,5 @@ def get_gradual_example(example_name: str):
208
  content = json.load(f)
209
  return JSONResponse(content=content)
210
  except json.JSONDecodeError:
211
- raise HTTPException(status_code=400, detail="Invalid JSON format in example file")
 
 
1
+ from gradual.models import GradualInput, GradualOutput
2
+ # from gradual.computations import compute_gradual_semantics
3
+ from gradual.computations import compute_gradual_space
4
+ from aba.aba_builder import prepare_aba_plus_framework, build_aba_framework_from_text
5
+ from relations.predict_bert import predict_relation
6
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
7
+ from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
10
+ import torch
11
+ import pandas as pd
12
+ from pathlib import Path
13
+ import asyncio
14
+ import json
15
+ import io
16
  import os
17
 
18
  cache_dir = "/tmp/hf_cache"
19
  os.environ["TRANSFORMERS_CACHE"] = cache_dir
20
  os.makedirs(cache_dir, exist_ok=True)
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  # -------------------- Config -------------------- #
24
 
 
45
  )
46
 
47
  # -------------------- Endpoints -------------------- #
48
+
49
+
50
  @app.get("/")
51
  def root():
52
  return {"message": "Argument Mining API is running..."}
 
74
  completed = 0
75
  for _, row in df.iterrows():
76
  try:
77
+ result = predict_relation(
78
+ row["parent"], row["child"], model, tokenizer, device)
79
  completed += 1
80
  payload = {
81
  "parent": row["parent"],
 
90
  yield f"data: {json.dumps({'error': str(e), 'parent': row.get('parent'), 'child': row.get('child')})}\n\n"
91
  await asyncio.sleep(0)
92
 
 
93
  return StreamingResponse(event_generator(), media_type="text/event-stream")
94
 
95
 
 
160
 
161
  # --- Gradual semantics --- #
162
 
163
+ # @app.post("/gradual", response_model=GradualOutput)
164
+ # def compute_gradual(input_data: GradualInput):
165
+ # """API endpoint to compute Weighted h-Categorizer samples and convex hull."""
166
+ # return compute_gradual_semantics(
167
+ # A=input_data.A,
168
+ # R=input_data.R,
169
+ # n_samples=input_data.n_samples,
170
+ # max_iter=input_data.max_iter
171
+ # )
172
+
173
  @app.post("/gradual", response_model=GradualOutput)
174
  def compute_gradual(input_data: GradualInput):
175
+ """
176
+ API endpoint to compute Weighted h-Categorizer samples
177
+ and their convex hull (acceptability degree space).
178
+ """
179
+ num_args, hull_volume, hull_area, hull_points, samples, axes = compute_gradual_space(
180
+ num_args=input_data.num_args,
181
  R=input_data.R,
182
  n_samples=input_data.n_samples,
183
+ axes=input_data.axes,
184
+ controlled_args=input_data.controlled_args,
185
+ )
186
+
187
+ return GradualOutput(
188
+ num_args=num_args,
189
+ hull_volume=hull_volume,
190
+ hull_area=hull_area,
191
+ hull_points=hull_points,
192
+ samples=samples,
193
+ axes=axes,
194
  )
195
 
196
 
 
200
  List all available gradual semantics example files.
201
  Each example must be a JSON file with structure:
202
  {
203
+ # "args": ["A", "B", "C"],
204
+ # "relations": [["A", "B"], ["B", "C"]]
205
+ "num_args": 3,
206
+ "R": [["A", "B"], ["B", "C"], ["C", "A"]],
207
  }
208
  """
209
  if not GRADUAL_EXAMPLES_DIR.exists():
 
222
  @app.get("/gradual-examples/{example_name}")
223
  def get_gradual_example(example_name: str):
224
  """
225
+ Return the content of a specific gradual example file.
226
  Example: GET /gradual-examples/simple.json
227
  """
228
  file_path = GRADUAL_EXAMPLES_DIR / example_name
 
234
  content = json.load(f)
235
  return JSONResponse(content=content)
236
  except json.JSONDecodeError:
237
+ raise HTTPException(
238
+ status_code=400, detail="Invalid JSON format in example file")
gradual/computations.py CHANGED
@@ -1,21 +1,142 @@
1
- from scipy.spatial import ConvexHull
2
  import numpy as np
3
- from .h_categorizer import sample_and_compute_X
4
-
5
- def compute_gradual_semantics(A, R, n_samples=1000, max_iter=1000):
6
- """Compute samples and convex hull information for the given argumentation framework."""
7
- X_res = sample_and_compute_X(A, R, max_iter=max_iter, n_samples=n_samples)
8
- result = {"num_args": len(A)}
9
-
10
- if len(A) > 1:
11
- hull = ConvexHull(X_res)
12
- result["hull_volume"] = float(hull.volume)
13
- result["hull_area"] = float(hull.area)
14
- result["hull_points"] = hull.points[hull.vertices].tolist()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  else:
16
- result["hull_volume"] = None
17
- result["hull_area"] = None
18
- result["hull_points"] = X_res.tolist()
19
 
20
- result["samples"] = X_res.tolist()
21
- return result
 
 
1
  import numpy as np
2
+ from scipy.spatial import ConvexHull, QhullError
3
+ from .h_categorizer import h_categorizer
4
+
5
+
6
+ def dict_to_vector(A, d):
7
+ """Converts a dictionary {arg: value} into a numpy vector following the order of A."""
8
+ return np.array([d[a] for a in A], dtype=float)
9
+
10
+
11
+ def sample_and_compute_X(
12
+ A,
13
+ R,
14
+ epsilon=1e-4,
15
+ max_iter=1000,
16
+ n_samples=10000,
17
+ seed=42,
18
+ controlled_args=None
19
+ ):
20
+ """Generates n_samples random weight vectors and computes corresponding h-Categorizer results."""
21
+ rng = np.random.default_rng(seed)
22
+ X = np.zeros((n_samples, len(A)), dtype=float)
23
+
24
+ for i in range(n_samples):
25
+ w = dict(zip(A, rng.random(len(A))))
26
+
27
+ # Override controlled arguments if specified
28
+ if controlled_args:
29
+ for arg, value in controlled_args.items():
30
+ w[arg] = value
31
+
32
+ HC = h_categorizer(A, R, w, max_iter, epsilon)
33
+ X[i, :] = dict_to_vector(A, HC)
34
+
35
+ return X
36
+
37
+
38
+ def _safe_hull(points, qhull_opts="QJ", jitter=1e-8):
39
+ """
40
+ Try to compute a convex hull robustly.
41
+ Uses 'QJ' (joggle) and adds slight random jitter if needed.
42
+ Returns None if still degenerate.
43
+ """
44
+ try:
45
+ return ConvexHull(points, qhull_options=qhull_opts)
46
+ except QhullError:
47
+ try:
48
+ pts = points + jitter * np.random.randn(*points.shape)
49
+ return ConvexHull(pts, qhull_options=qhull_opts)
50
+ except QhullError:
51
+ return None
52
+
53
+
54
+ # def compute_gradual_semantics(
55
+ # A,
56
+ # R,
57
+ # n_samples=1000,
58
+ # val_axes=None,
59
+ # controlled_args=None,
60
+ # epsilon=1e-4,
61
+ # max_iter=1000
62
+ # ):
63
+ # """Compute samples and convex hull information for the given argumentation framework."""
64
+ # X_res = sample_and_compute_X(
65
+ # A, R, epsilon, max_iter, n_samples, controlled_args=controlled_args
66
+ # )
67
+
68
+ # # Case 1D
69
+ # if len(A) == 1:
70
+ # axes = [A[0]]
71
+ # hull = _safe_hull(X_res)
72
+ # dim = 1
73
+ # return dim, axes, X_res, hull
74
+
75
+ # # Case 2D
76
+ # if len(A) == 2:
77
+ # axes = A[:2]
78
+ # hull = _safe_hull(X_res)
79
+ # dim = 2
80
+ # return dim, axes, X_res, hull
81
+
82
+ # # Case ≥ 3D → project on chosen axes
83
+ # axes = val_axes if val_axes else A[:3]
84
+ # idx = [A.index(ax) for ax in axes]
85
+ # Xp = X_res[:, idx]
86
+ # hull = _safe_hull(Xp)
87
+ # dim = 3
88
+ # return dim, axes, Xp, hull
89
+
90
+
91
+ def compute_gradual_space(num_args, R, n_samples, axes=None, controlled_args=None, epsilon=1e-4, max_iter=1000):
92
+ """
93
+ Compute the convex hull (acceptability degree space) for the weighted h-categorizer.
94
+ Returns (num_args, hull_volume, hull_area, hull_points, samples, axes)
95
+ """
96
+ # Generate argument labels A, B, C, ...
97
+ A = [chr(ord("A") + i) for i in range(num_args)]
98
+
99
+ # 1. Sample and compute semantics
100
+ X_res = sample_and_compute_X(
101
+ A, R, epsilon, max_iter, n_samples, controlled_args=controlled_args
102
+ )
103
+
104
+ # 2. Handle projections depending on argument count
105
+ if num_args == 1:
106
+ dim = 1
107
+ axes_used = [A[0]]
108
+ hull_points = np.array([[np.min(X_res)], [np.max(X_res)]])
109
+ hull_volume = float(np.max(X_res) - np.min(X_res))
110
+ hull_area = None
111
+ return num_args, hull_volume, hull_area, hull_points.tolist(), X_res.tolist(), axes_used
112
+
113
+ if num_args == 2:
114
+ dim = 2
115
+ axes_used = A[:2]
116
+ hull = _safe_hull(X_res)
117
+ if hull is None:
118
+ hull_volume = 0.0
119
+ hull_area = 0.0
120
+ hull_points = []
121
+ else:
122
+ hull_volume = float(hull.volume)
123
+ hull_area = float(hull.area)
124
+ hull_points = hull.points[hull.vertices].tolist()
125
+ return num_args, hull_volume, hull_area, hull_points, X_res.tolist(), axes_used
126
+
127
+ # num_args >= 3
128
+ axes_used = axes if axes else A[:3]
129
+ idx = [A.index(ax) for ax in axes_used]
130
+ Xp = X_res[:, idx]
131
+
132
+ hull = _safe_hull(Xp)
133
+ if hull is None:
134
+ hull_volume = 0.0
135
+ hull_area = 0.0
136
+ hull_points = []
137
  else:
138
+ hull_volume = float(hull.volume)
139
+ hull_area = float(hull.area)
140
+ hull_points = hull.points[hull.vertices].tolist()
141
 
142
+ return num_args, hull_volume, hull_area, hull_points, Xp.tolist(), axes_used
 
gradual/examples/complex.json CHANGED
@@ -1,4 +1,44 @@
1
  {
2
- "args": ["A", "B", "C", "D", "E"],
3
- "relations": [["A", "B"], ["B", "C"], ["C", "D"], ["D", "E"], ["E", "A"]]
4
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {
2
+ "num_args": 6,
3
+ "R": [
4
+ [
5
+ "A",
6
+ "B"
7
+ ],
8
+ [
9
+ "B",
10
+ "A"
11
+ ],
12
+ [
13
+ "A",
14
+ "C"
15
+ ],
16
+ [
17
+ "C",
18
+ "D"
19
+ ],
20
+ [
21
+ "D",
22
+ "B"
23
+ ],
24
+ [
25
+ "E",
26
+ "A"
27
+ ],
28
+ [
29
+ "F",
30
+ "E"
31
+ ]
32
+ ],
33
+ "n_samples": 12000,
34
+ "axes": [
35
+ "A",
36
+ "B",
37
+ "C"
38
+ ],
39
+ "controlled_args": {
40
+ "D": 0.42,
41
+ "E": 0.67,
42
+ "F": 0.35
43
+ }
44
+ }
gradual/examples/simple.json CHANGED
@@ -1,4 +1,18 @@
1
  {
2
- "args": ["A", "B", "C"],
3
- "relations": [["A", "B"], ["B", "C"]]
4
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {
2
+ "num_args": 3,
3
+ "R": [
4
+ [
5
+ "A",
6
+ "B"
7
+ ],
8
+ [
9
+ "B",
10
+ "C"
11
+ ]
12
+ ],
13
+ "n_samples": 800,
14
+ "axes": [
15
+ "A",
16
+ "B"
17
+ ]
18
+ }
gradual/h_categorizer.py CHANGED
@@ -1,5 +1,6 @@
1
  import numpy as np
2
 
 
3
  def build_att(A, R):
4
  """Builds a dictionary listing attackers for each argument."""
5
  att_list = {a: [] for a in A}
@@ -27,21 +28,3 @@ def h_categorizer(A, R, w, max_iter, epsi=1e-4):
27
  break
28
 
29
  return hc
30
-
31
-
32
- def dict_to_vector(A, d):
33
- """Converts a dictionary {arg: value} into a numpy vector following the order of A."""
34
- return np.array([d[a] for a in A], dtype=float)
35
-
36
-
37
- def sample_and_compute_X(A, R, epsilon=1e-4, max_iter=1000, n_samples=10000, seed=42):
38
- """Generates n_samples random weight vectors and computes corresponding h-Categorizer results."""
39
- rng = np.random.default_rng(seed)
40
- X = np.zeros((n_samples, len(A)), dtype=float)
41
-
42
- for i in range(n_samples):
43
- w = dict(zip(A, rng.random(len(A))))
44
- HC = h_categorizer(A, R, w, max_iter, epsilon)
45
- X[i, :] = dict_to_vector(A, HC)
46
-
47
- return X
 
1
  import numpy as np
2
 
3
+
4
  def build_att(A, R):
5
  """Builds a dictionary listing attackers for each argument."""
6
  att_list = {a: [] for a in A}
 
28
  break
29
 
30
  return hc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gradual/models.py CHANGED
@@ -1,5 +1,6 @@
1
- from pydantic import BaseModel
2
- from typing import List, Tuple, Optional
 
3
 
4
  class GradualInput(BaseModel):
5
  """
@@ -26,10 +27,21 @@ class GradualInput(BaseModel):
26
  "max_iter": 1000
27
  }
28
  """
29
- A: List[str]
30
- R: List[Tuple[str, str]]
31
- n_samples: int = 1000
32
- max_iter: int = 1000
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  class GradualOutput(BaseModel):
35
  """
@@ -72,3 +84,4 @@ class GradualOutput(BaseModel):
72
  hull_area: Optional[float]
73
  hull_points: List[List[float]]
74
  samples: List[List[float]]
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Tuple, Dict, Optional
3
+
4
 
5
  class GradualInput(BaseModel):
6
  """
 
27
  "max_iter": 1000
28
  }
29
  """
30
+ num_args: int = Field(..., ge=1, le=10,
31
+ description="Number of arguments (|A|)")
32
+
33
+ R: List[Tuple[str, str]
34
+ ] = Field(..., description="Attack relations (A->B format)")
35
+
36
+ n_samples: int = Field(
37
+ 1000, ge=10, description="Number of samples for convex hull computation")
38
+
39
+ axes: Optional[List[str]] = Field(
40
+ None, description="Chosen arguments for 3D plot axes (X,Y,Z)")
41
+
42
+ controlled_args: Optional[Dict[str, float]] = Field(
43
+ None, description="Values for non-axis arguments")
44
+
45
 
46
  class GradualOutput(BaseModel):
47
  """
 
84
  hull_area: Optional[float]
85
  hull_points: List[List[float]]
86
  samples: List[List[float]]
87
+ axes: Optional[List[str]] = None