nuriyev commited on
Commit
526b256
·
1 Parent(s): ec9b0c9

implement Z-number decision matrix extraction and MCDM methods; add command line argument parsing

Browse files
Files changed (2) hide show
  1. main_simple.py +155 -6
  2. utils.py +5 -1
main_simple.py CHANGED
@@ -1,7 +1,87 @@
1
  from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TextStreamer
2
  import torch
3
  import warnings
4
- from utils import SYSTEM_PROMPT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  warnings.filterwarnings(
7
  "ignore",
@@ -9,6 +89,14 @@ warnings.filterwarnings(
9
  category=UserWarning,
10
  )
11
 
 
 
 
 
 
 
 
 
12
  qconfig = BitsAndBytesConfig(
13
  load_in_8bit=True,
14
  )
@@ -24,12 +112,9 @@ model = AutoModelForCausalLM.from_pretrained(
24
  )
25
  print("Model loaded successfully!\n")
26
 
27
- USER_QUERY = "Okay so I've been going crazy trying to figure out if I should get a dog, a cat, or maybe just stick with a fish tank, and honestly I'm not even sure what matters most to me anymore. Like, companionship is huge for me, probably the most important thing, and I'm pretty confident dogs are amazing for that, cats are okay but kind of aloof sometimes, fish are basically just decoration let's be real. Then there's the whole time commitment thing which I think matters a lot but I'm not totally sure how much—I work weird hours so dogs seem like a nightmare there, cats are supposedly independent but my friend's cat is super needy so who knows, fish are definitely easy but maybe too easy? Cost is something I should care about more than I do, I guess it's moderately important, and I've heard dogs are expensive with vet bills and food, cats are cheaper I think, fish setups can actually cost a lot upfront but then nothing after. Oh and allergies—my roommate might be slightly allergic to cats, we're not 100% sure, dogs seem fine, fish obviously no issue there, so that's pretty important actually. And like, the whole lifestyle flexibility thing where I want to travel sometimes, dogs are terrible for that obviously, cats you can leave for a weekend maybe, fish just need an auto-feeder so that's nice, but I'm not sure how much I'll actually travel so maybe this doesn't matter that much?"
28
-
29
-
30
  messages = [
31
  {"role": "system", "content": SYSTEM_PROMPT},
32
- {"role": "user", "content": USER_QUERY},
33
  ]
34
 
35
  # Prepare the prompt using the chat template
@@ -42,4 +127,68 @@ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
42
  streamer = TextStreamer(tokenizer, skip_special_tokens=True)
43
 
44
  # Generate with streaming
45
- model.generate(**inputs, max_length=8192, streamer=streamer, temperature=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TextStreamer
2
  import torch
3
  import warnings
4
+ import re
5
+ import logging
6
+ import argparse
7
+ from znum import Znum, Topsis, Promethee, Beast
8
+ from utils import SYSTEM_PROMPT, DEFAULT_QUERY
9
+
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Z-number mappings: value/confidence (1-5) to fuzzy trapezoidal numbers
14
+ A_MAP = {
15
+ 1: [2, 3, 3, 4],
16
+ 2: [4, 5, 5, 6],
17
+ 3: [6, 7, 7, 8],
18
+ 4: [8, 9, 9, 10],
19
+ 5: [10, 11, 11, 12],
20
+ }
21
+
22
+ B_MAP = {
23
+ 1: [0.2, 0.3, 0.3, 0.4],
24
+ 2: [0.3, 0.4, 0.4, 0.5],
25
+ 3: [0.4, 0.5, 0.5, 0.6],
26
+ 4: [0.5, 0.6, 0.6, 0.7],
27
+ 5: [0.6, 0.7, 0.7, 0.8],
28
+ }
29
+
30
+
31
+ def parse_znum_pair(pair_str: str) -> Znum | None:
32
+ """Convert 'N:M' string to Znum object using A_MAP and B_MAP (abs value for A)."""
33
+ try:
34
+ parts = pair_str.strip().split(':')
35
+ if len(parts) != 2:
36
+ return None
37
+ a_val = abs(int(parts[0]))
38
+ b_val = int(parts[1])
39
+ if a_val not in A_MAP or b_val not in B_MAP:
40
+ logger.warning(f"Invalid Z-number pair: {pair_str}")
41
+ return None
42
+ return Znum(A_MAP[a_val], B_MAP[b_val])
43
+ except (ValueError, KeyError) as e:
44
+ logger.warning(f"Failed to parse Z-number pair '{pair_str}': {e}")
45
+ return None
46
+
47
+
48
+ def parse_markdown_table(text: str) -> dict:
49
+ """Parse markdown table from model output into structured dict."""
50
+ lines = [l.strip() for l in text.strip().split('\n') if l.strip() and '|' in l]
51
+ lines = [l for l in lines if not re.match(r'^\|[-:\s|]+\|$', l)]
52
+
53
+ if len(lines) < 4:
54
+ logger.warning("Table has fewer than expected rows")
55
+ return {}
56
+
57
+ def split_row(row: str) -> list:
58
+ cells = [c.strip() for c in row.split('|')]
59
+ return [c for c in cells if c]
60
+
61
+ headers = split_row(lines[0])
62
+ criteria = headers[1:] if headers else []
63
+
64
+ types_row = split_row(lines[1])
65
+ types = types_row[1:] if len(types_row) > 1 else []
66
+
67
+ weights_row = split_row(lines[-1])
68
+ weights = weights_row[1:] if len(weights_row) > 1 else []
69
+
70
+ alternatives = {}
71
+ for line in lines[2:-1]:
72
+ row = split_row(line)
73
+ if row:
74
+ alt_name = row[0]
75
+ values = row[1:]
76
+ alternatives[alt_name] = values
77
+
78
+ result = {
79
+ 'criteria': criteria,
80
+ 'types': types,
81
+ 'alternatives': alternatives,
82
+ 'weights': weights
83
+ }
84
+ return result
85
 
86
  warnings.filterwarnings(
87
  "ignore",
 
89
  category=UserWarning,
90
  )
91
 
92
+ # Parse command line arguments
93
+ parser = argparse.ArgumentParser(description='Z-number decision matrix extraction and MCDM')
94
+ parser.add_argument('--method', type=str, choices=['topsis', 'promethee'], default='topsis',
95
+ help='MCDM method to use (default: topsis)')
96
+ parser.add_argument('--query', '-q', type=str, default=DEFAULT_QUERY,
97
+ help='Decision query to process')
98
+ args = parser.parse_args()
99
+
100
  qconfig = BitsAndBytesConfig(
101
  load_in_8bit=True,
102
  )
 
112
  )
113
  print("Model loaded successfully!\n")
114
 
 
 
 
115
  messages = [
116
  {"role": "system", "content": SYSTEM_PROMPT},
117
+ {"role": "user", "content": args.query},
118
  ]
119
 
120
  # Prepare the prompt using the chat template
 
127
  streamer = TextStreamer(tokenizer, skip_special_tokens=True)
128
 
129
  # Generate with streaming
130
+ output_ids = model.generate(**inputs, max_length=8192, streamer=streamer, temperature=1)
131
+
132
+ # Decode the generated output (excluding input tokens)
133
+ generated_ids = output_ids[0][inputs['input_ids'].shape[1]:]
134
+ generated_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
135
+
136
+ # Parse and log the decision matrix
137
+ logger.info("Parsing decision matrix from model output...")
138
+ matrix = parse_markdown_table(generated_text)
139
+
140
+ if matrix:
141
+ logger.info(f"Criteria: {matrix['criteria']}")
142
+ logger.info(f"Types: {matrix['types']}")
143
+
144
+ # Convert weights to Znum
145
+ znum_weights = [parse_znum_pair(w) for w in matrix['weights']]
146
+ logger.info("Weights as Znum:")
147
+ for i, (name, zw) in enumerate(zip(matrix['criteria'], znum_weights)):
148
+ logger.info(f" {name}: {zw}")
149
+
150
+ # Convert alternatives to Znum
151
+ znum_alternatives = {}
152
+ for alt_name, values in matrix['alternatives'].items():
153
+ znum_values = [parse_znum_pair(v) for v in values]
154
+ znum_alternatives[alt_name] = znum_values
155
+ logger.info(f"Alternative '{alt_name}' as Znum:")
156
+ for i, (crit, zv) in enumerate(zip(matrix['criteria'], znum_values)):
157
+ logger.info(f" {crit}: {zv}")
158
+
159
+ # Store converted data
160
+ matrix['znum_weights'] = znum_weights
161
+ matrix['znum_alternatives'] = znum_alternatives
162
+
163
+ # Build criteria types for MCDM
164
+ criteria_types = [
165
+ Beast.CriteriaType.BENEFIT if t.lower() == 'benefit' else Beast.CriteriaType.COST
166
+ for t in matrix['types']
167
+ ]
168
+
169
+ # Build decision table: [weights, *alternatives, criteria_types]
170
+ alt_names = list(znum_alternatives.keys())
171
+ alt_rows = [znum_alternatives[name] for name in alt_names]
172
+ table = [znum_weights] + alt_rows + [criteria_types]
173
+
174
+ # Apply MCDM method
175
+ logger.info(f"\nApplying {args.method.upper()} method...")
176
+
177
+ if args.method == 'topsis':
178
+ solver = Topsis(table)
179
+ else:
180
+ solver = Promethee(table)
181
+
182
+ solver.solve()
183
+
184
+ # Log results
185
+ logger.info(f"\n{'='*50}")
186
+ logger.info(f"RESULTS ({args.method.upper()})")
187
+ logger.info(f"{'='*50}")
188
+ logger.info(f"Best alternative: {alt_names[solver.index_of_best_alternative]}")
189
+ logger.info(f"Worst alternative: {alt_names[solver.index_of_worst_alternative]}")
190
+ logger.info(f"\nRanking (best to worst):")
191
+ for rank, idx in enumerate(solver.ordered_indices, 1):
192
+ logger.info(f" {rank}. {alt_names[idx]}")
193
+ else:
194
+ logger.error("Failed to parse decision matrix")
utils.py CHANGED
@@ -26,4 +26,8 @@ Return ONLY a Markdown table in this exact format:
26
  4. Last row: "weight", then importance weights as VALUE:CONFIDENCE (always use positive values 1-5 for weights)
27
  5. VALUE must be positive (1-5) for benefits, negative (-1 to -5) for costs
28
  6. CONFIDENCE is always positive (1-5) regardless of criterion type
29
- """
 
 
 
 
 
26
  4. Last row: "weight", then importance weights as VALUE:CONFIDENCE (always use positive values 1-5 for weights)
27
  5. VALUE must be positive (1-5) for benefits, negative (-1 to -5) for costs
28
  6. CONFIDENCE is always positive (1-5) regardless of criterion type
29
+ """
30
+
31
+
32
+
33
+ DEFAULT_QUERY = "Okay so I've been going crazy trying to figure out if I should get a dog, a cat, or maybe just stick with a fish tank, and honestly I'm not even sure what matters most to me anymore. Like, companionship is huge for me, probably the most important thing, and I'm pretty confident dogs are amazing for that, cats are okay but kind of aloof sometimes, fish are basically just decoration let's be real. Then there's the whole time commitment thing which I think matters a lot but I'm not totally sure how much—I work weird hours so dogs seem like a nightmare there, cats are supposedly independent but my friend's cat is super needy so who knows, fish are definitely easy but maybe too easy? Cost is something I should care about more than I do, I guess it's moderately important, and I've heard dogs are expensive with vet bills and food, cats are cheaper I think, fish setups can actually cost a lot upfront but then nothing after. Oh and allergies—my roommate might be slightly allergic to cats, we're not 100% sure, dogs seem fine, fish obviously no issue there, so that's pretty important actually. And like, the whole lifestyle flexibility thing where I want to travel sometimes, dogs are terrible for that obviously, cats you can leave for a weekend maybe, fish just need an auto-feeder so that's nice, but I'm not sure how much I'll actually travel so maybe this doesn't matter that much?"