TimInf commited on
Commit
9d04cdc
·
verified ·
1 Parent(s): a436b12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -64
app.py CHANGED
@@ -8,18 +8,16 @@ from fastapi.responses import JSONResponse
8
  from pydantic import BaseModel
9
  from datetime import datetime, timedelta
10
 
11
- # Lade RecipeBERT Modell (für semantische Zutat-Kombination)
12
  bert_model_name = "alexdseo/RecipeBERT"
13
  bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_name)
14
  bert_model = AutoModel.from_pretrained(bert_model_name)
15
- bert_model.eval() # Setze das Modell in den Evaluationsmodus
16
 
17
- # Lade T5 Rezeptgenerierungsmodell
 
18
  MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
19
  t5_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
20
  t5_model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
21
 
22
- # Token Mapping für die T5 Modell-Ausgabe
23
  special_tokens = t5_tokenizer.all_special_tokens
24
  tokens_map = {
25
  "<sep>": "--",
@@ -39,10 +37,10 @@ def get_embedding(text):
39
  sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
40
  return (sum_embeddings / sum_mask).squeeze(0)
41
 
42
- def average_embedding(embedding_list):
43
- """Berechnet den Durchschnitt einer Liste von Embeddings"""
44
- tensors = torch.stack([emb for _, emb in embedding_list])
45
- return tensors.mean(dim=0)
46
 
47
  def get_cosine_similarity(vec1, vec2):
48
  """Berechnet die Cosinus-Ähnlichkeit zwischen zwei Vektoren"""
@@ -86,52 +84,31 @@ def calculate_age_bonus(date_added_str: str, category: str) -> float:
86
  bonus = days_since_added * daily_bonus
87
  return min(bonus, 0.10) # Max 10% (0.10)
88
 
89
- def get_combined_scores(query_vector, embedding_list_with_details, all_good_embeddings, avg_weight=0.6):
90
- """
91
- Berechnet einen kombinierten Score unter Berücksichtigung der Ähnlichkeit zum Durchschnitt und zu einzelnen Zutaten.
92
- Jetzt inklusive Altersbonus.
93
- embedding_list_with_details: Liste von Tupeln (Name, Embedding, DateAddedStr, Category)
94
- """
95
- results = []
96
- for name, emb, date_added_str, category in embedding_list_with_details:
97
- avg_similarity = get_cosine_similarity(query_vector, emb)
98
- individual_similarities = [get_cosine_similarity(good_emb, emb)
99
- for _, good_emb in all_good_embeddings]
100
- avg_individual_similarity = sum(individual_similarities) / len(individual_similarities) if individual_similarities else 0
101
-
102
- base_combined_score = avg_weight * avg_similarity + (1 - avg_weight) * avg_individual_similarity
103
-
104
- # NEU: Altersbonus hinzufügen
105
- age_bonus = calculate_age_bonus(date_added_str, category)
106
- final_combined_score = base_combined_score + age_bonus
107
-
108
- results.append((name, emb, final_combined_score, date_added_str, category))
109
- results.sort(key=lambda x: x[2], reverse=True)
110
- return results
111
-
112
- def find_best_ingredients(required_ingredients_names, available_ingredients_details, max_ingredients=6, avg_weight=0.6):
113
  """
114
- Findet die besten Zutaten basierend auf RecipeBERT Embeddings, jetzt mit Alters- und Kategorie-Bonus.
 
115
  required_ingredients_names: Liste von Strings (nur Namen)
116
  available_ingredients_details: Liste von IngredientDetail-Objekten
117
  """
118
  required_ingredients_names = list(set(required_ingredients_names))
119
 
120
  # Filtern der verfügbaren Zutaten, um sicherzustellen, dass keine Pflichtzutaten dabei sind
121
- # Korrektur hier: Zugriff auf item.name statt item['name']
122
  available_ingredients_filtered_details = [
123
  item for item in available_ingredients_details
124
- if item.name not in required_ingredients_names # <--- KORREKTUR
125
  ]
126
 
127
  # Wenn keine Pflichtzutaten vorhanden sind, aber verfügbare, wähle eine zufällig als Pflichtzutat
128
  if not required_ingredients_names and available_ingredients_filtered_details:
129
  random_item = random.choice(available_ingredients_filtered_details)
130
- required_ingredients_names = [random_item.name] # <--- KORREKTUR
131
  # Entferne die zufällig gewählte Zutat aus den verfügbaren Details
132
  available_ingredients_filtered_details = [
133
  item for item in available_ingredients_filtered_details
134
- if item.name != random_item.name # <--- KORREKTUR
135
  ]
136
  print(f"No required ingredients provided. Randomly selected: {required_ingredients_names[0]}")
137
 
@@ -141,39 +118,67 @@ def find_best_ingredients(required_ingredients_names, available_ingredients_deta
141
  if not available_ingredients_filtered_details:
142
  return required_ingredients_names
143
 
144
- # Erstelle Embeddings für Pflichtzutaten (nur Name und Embedding)
145
- embed_required = [(name, get_embedding(name)) for name in required_ingredients_names]
 
146
 
147
- # Erstelle Embeddings für verfügbare Zutaten, inklusive ihrer Details
148
- # Korrektur hier: Zugriff auf item.name, item.dateAdded, item.category
149
- embed_available_with_details = [
150
- (item.name, get_embedding(item.name), item.dateAdded, item.category) # <--- KORREKTUR
151
- for item in available_ingredients_filtered_details
152
- ]
153
 
154
- num_to_add = min(max_ingredients - len(required_ingredients_names), len(embed_available_with_details))
155
 
156
- final_ingredients_with_embeddings = embed_required.copy() # (Name, Embedding)
157
- final_ingredients_names = required_ingredients_names.copy() # Nur Namen zum Tracken der ausgewählten
 
158
 
159
- for _ in range(num_to_add):
160
- avg = average_embedding(final_ingredients_with_embeddings)
161
- candidates = get_combined_scores(avg, embed_available_with_details, final_ingredients_with_embeddings, avg_weight)
162
-
163
- if not candidates:
164
- break
 
 
 
 
 
 
 
 
 
165
 
166
- best_name, best_embedding, best_score, _, _ = candidates[0] # Holen Sie den besten Kandidaten
167
-
168
- final_ingredients_with_embeddings.append((best_name, best_embedding))
169
- final_ingredients_names.append(best_name)
170
-
171
- # Entferne den besten Kandidaten aus den verfügbaren
172
- # Korrektur hier: Zugriff auf item[0] (den Namen im Tupel)
173
- embed_available_with_details = [item for item in embed_available_with_details if item[0] != best_name]
 
 
 
 
174
 
175
- return final_ingredients_names
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
 
177
 
178
  def skip_special_tokens(text, special_tokens):
179
  """Entfernt spezielle Tokens aus dem Text"""
@@ -341,4 +346,3 @@ async def generate_recipe_api(request_data: RecipeRequest):
341
  async def read_root():
342
  return {"message": "AI Recipe Generator API is running (FastAPI only)!"}
343
 
344
- print("INFO: Pure FastAPI application script finished execution and defined 'app' variable.")
 
8
  from pydantic import BaseModel
9
  from datetime import datetime, timedelta
10
 
 
11
  bert_model_name = "alexdseo/RecipeBERT"
12
  bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_name)
13
  bert_model = AutoModel.from_pretrained(bert_model_name)
 
14
 
15
+
16
+
17
  MODEL_NAME_OR_PATH = "flax-community/t5-recipe-generation"
18
  t5_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME_OR_PATH, use_fast=True)
19
  t5_model = FlaxAutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME_OR_PATH)
20
 
 
21
  special_tokens = t5_tokenizer.all_special_tokens
22
  tokens_map = {
23
  "<sep>": "--",
 
37
  sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
38
  return (sum_embeddings / sum_mask).squeeze(0)
39
 
40
+ def format_ingredients_for_bert(ingredients_list):
41
+ """Formatiert Zutatenliste für BERT"""
42
+ return f"Ingredients: {', '.join(ingredients_list)}"
43
+
44
 
45
  def get_cosine_similarity(vec1, vec2):
46
  """Berechnet die Cosinus-Ähnlichkeit zwischen zwei Vektoren"""
 
84
  bonus = days_since_added * daily_bonus
85
  return min(bonus, 0.10) # Max 10% (0.10)
86
 
87
+
88
+
89
+ def find_best_ingredients(required_ingredients_names, available_ingredients_details, max_ingredients=6):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  """
91
+ Findet die besten Zutaten basierend auf RecipeBERT Embeddings
92
+
93
  required_ingredients_names: Liste von Strings (nur Namen)
94
  available_ingredients_details: Liste von IngredientDetail-Objekten
95
  """
96
  required_ingredients_names = list(set(required_ingredients_names))
97
 
98
  # Filtern der verfügbaren Zutaten, um sicherzustellen, dass keine Pflichtzutaten dabei sind
 
99
  available_ingredients_filtered_details = [
100
  item for item in available_ingredients_details
101
+ if item.name not in required_ingredients_names
102
  ]
103
 
104
  # Wenn keine Pflichtzutaten vorhanden sind, aber verfügbare, wähle eine zufällig als Pflichtzutat
105
  if not required_ingredients_names and available_ingredients_filtered_details:
106
  random_item = random.choice(available_ingredients_filtered_details)
107
+ required_ingredients_names = [random_item.name]
108
  # Entferne die zufällig gewählte Zutat aus den verfügbaren Details
109
  available_ingredients_filtered_details = [
110
  item for item in available_ingredients_filtered_details
111
+ if item.name != random_item.name
112
  ]
113
  print(f"No required ingredients provided. Randomly selected: {required_ingredients_names[0]}")
114
 
 
118
  if not available_ingredients_filtered_details:
119
  return required_ingredients_names
120
 
121
+ print(f"\n=== Suche passende Zutaten für Basis: {required_ingredients_names} ===")
122
+ print(f"Verfügbare Zutaten: {[item.name for item in available_ingredients_filtered_details]}")
123
+ print("-" * 50)
124
 
125
+ current_combination = required_ingredients_names.copy()
126
+ remaining_ingredients_details = available_ingredients_filtered_details.copy()
 
 
 
 
127
 
128
+ num_to_add = min(max_ingredients - len(required_ingredients_names), len(remaining_ingredients_details))
129
 
130
+ for round_num in range(num_to_add):
131
+ best_ingredient_detail = None
132
+ best_score = -1
133
 
134
+ # Formatiere aktuelle Kombination für BERT
135
+ current_text = format_ingredients_for_bert(current_combination)
136
+ current_embedding = get_embedding(current_text)
137
+
138
+ print(f"\nRunde {round_num + 1} - Aktuelle Kombination: {current_combination}")
139
+ print("Teste verbleibende Zutaten:")
140
+
141
+ for ingredient_detail in remaining_ingredients_details:
142
+ # Berechne semantische Ähnlichkeit mit BERT
143
+ ingredient_text = format_ingredients_for_bert([ingredient_detail.name])
144
+ ingredient_embedding = get_embedding(ingredient_text)
145
+ similarity = get_cosine_similarity(current_embedding, ingredient_embedding)
146
+
147
+ # Berechne Altersbonus
148
+ age_bonus = calculate_age_bonus(ingredient_detail.dateAdded, ingredient_detail.category)
149
 
150
+ # Kombiniere Ähnlichkeit und Altersbonus
151
+ final_score = similarity + age_bonus
152
+
153
+ print(f" - '{ingredient_detail.name}': Ähnlichkeit = {similarity:.4f}, Altersbonus = {age_bonus:.4f}, Gesamt = {final_score:.4f}")
154
+
155
+ if final_score > best_score:
156
+ best_score = final_score
157
+ best_ingredient_detail = ingredient_detail
158
+
159
+ if best_ingredient_detail:
160
+ current_combination.append(best_ingredient_detail.name)
161
+ remaining_ingredients_details.remove(best_ingredient_detail)
162
 
163
+ # Berechne die Komponenten für die Ausgabe
164
+ best_similarity = get_cosine_similarity(
165
+ current_embedding,
166
+ get_embedding(format_ingredients_for_bert([best_ingredient_detail.name]))
167
+ )
168
+ best_age_bonus = calculate_age_bonus(best_ingredient_detail.dateAdded, best_ingredient_detail.category)
169
+
170
+ print(f"\n-> Runde {round_num + 1} abgeschlossen: Beste Zutat ist '{best_ingredient_detail.name}' mit Gesamtscore {best_score:.4f}")
171
+ print(f" (Ähnlichkeit: {best_similarity:.4f} + Altersbonus: {best_age_bonus:.4f})")
172
+ print(f" Neue Kombination: {current_combination}")
173
+ print("-" * 50)
174
+ else:
175
+ print("Keine weiteren passenden Zutaten gefunden.")
176
+ break
177
+
178
+ print(f"\nEndgültige Zutatenkombination: {current_combination}")
179
+ return current_combination
180
 
181
+ # --- Chef Transformer-spezifische Funktionen ---
182
 
183
  def skip_special_tokens(text, special_tokens):
184
  """Entfernt spezielle Tokens aus dem Text"""
 
346
  async def read_root():
347
  return {"message": "AI Recipe Generator API is running (FastAPI only)!"}
348