Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -50,6 +50,8 @@ emotion_prediction_tokenizer = AutoTokenizer.from_pretrained("bhadresh-savani/di
|
|
50 |
# Lazy loading for the fine-tuned language model
|
51 |
_finetuned_lm_tokenizer = None
|
52 |
_finetuned_lm_model = None
|
|
|
|
|
53 |
def get_finetuned_lm_model():
|
54 |
global _finetuned_lm_tokenizer, _finetuned_lm_model
|
55 |
if _finetuned_lm_tokenizer is None or _finetuned_lm_model is None:
|
@@ -58,6 +60,7 @@ def get_finetuned_lm_model():
|
|
58 |
_finetuned_lm_model = AutoModelForCausalLM.from_pretrained(finetuned_lm_model_name, device_map="auto", low_cpu_mem_usage=True)
|
59 |
return _finetuned_lm_tokenizer, _finetuned_lm_model
|
60 |
|
|
|
61 |
# Enhanced Emotional States
|
62 |
emotions = {
|
63 |
'joy': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
@@ -69,7 +72,7 @@ emotions = {
|
|
69 |
'determination': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
70 |
'resentment': {'percentage': 10, 'motivation': 'negative', 'intensity': 0},
|
71 |
'glory': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
72 |
-
'motivation': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
73 |
'ideal_state': {'percentage': 100, 'motivation': 'balanced', 'intensity': 0},
|
74 |
'fear': {'percentage': 10, 'motivation': 'defensive', 'intensity': 0},
|
75 |
'surprise': {'percentage': 10, 'motivation': 'unexpected', 'intensity': 0},
|
@@ -84,19 +87,32 @@ emotions = {
|
|
84 |
total_percentage = 200
|
85 |
emotion_history_file = 'emotion_history.json'
|
86 |
|
|
|
87 |
def load_historical_data(file_path=emotion_history_file):
|
88 |
if os.path.exists(file_path):
|
89 |
with open(file_path, 'r') as file:
|
90 |
return json.load(file)
|
91 |
return []
|
92 |
|
|
|
93 |
def save_historical_data(historical_data, file_path=emotion_history_file):
|
94 |
with open(file_path, 'w') as file:
|
95 |
json.dump(historical_data, file)
|
96 |
|
|
|
97 |
emotion_history = load_historical_data()
|
98 |
|
|
|
99 |
def update_emotion(emotion, percentage, intensity):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
emotions['ideal_state']['percentage'] -= percentage
|
101 |
emotions[emotion]['percentage'] += percentage
|
102 |
emotions[emotion]['intensity'] = intensity
|
@@ -105,41 +121,58 @@ def update_emotion(emotion, percentage, intensity):
|
|
105 |
adjustment = total_percentage - total_current
|
106 |
emotions['ideal_state']['percentage'] += adjustment
|
107 |
|
|
|
108 |
def normalize_context(context):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
return context.lower().strip()
|
110 |
|
111 |
-
|
|
|
112 |
def evolve_emotions():
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
122 |
creator.create("Individual", list, fitness=creator.FitnessMulti)
|
123 |
|
124 |
toolbox = base.Toolbox()
|
125 |
toolbox.register("attr_float", random.uniform, 0, 20)
|
126 |
toolbox.register("attr_intensity", random.uniform, 0, 10)
|
127 |
toolbox.register("individual", tools.initCycle, creator.Individual,
|
128 |
-
(toolbox.attr_float,) * (len(emotions) - 1) +
|
129 |
-
(toolbox.attr_intensity,) * len(emotions) +
|
130 |
(lambda: 100,), n=1)
|
131 |
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
|
132 |
toolbox.register("mate", tools.cxTwoPoint)
|
133 |
-
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2
|
134 |
toolbox.register("select", tools.selNSGA2)
|
135 |
toolbox.register("evaluate", evaluate)
|
136 |
|
137 |
population = toolbox.population(n=100)
|
138 |
-
algorithms.eaMuPlusLambda(population, toolbox, mu=50, lambda_=100, cxpb=0.7, mutpb=0.2, ngen=100,
|
139 |
-
|
140 |
|
141 |
best_individual = tools.selBest(population, k=1)[0]
|
142 |
-
emotion_values = best_individual[:len(emotions)-1]
|
143 |
intensities = best_individual[-21:-1]
|
144 |
ideal_state = best_individual[-1]
|
145 |
|
@@ -149,18 +182,42 @@ def evolve_emotions():
|
|
149 |
|
150 |
emotions['ideal_state']['percentage'] = ideal_state
|
151 |
|
|
|
152 |
def predict_emotion(context):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
emotion_prediction_pipeline = pipeline('text-classification', model=emotion_prediction_model, tokenizer=emotion_prediction_tokenizer, top_k=None)
|
154 |
predictions = emotion_prediction_pipeline(context)
|
155 |
emotion_scores = {prediction['label']: prediction['score'] for prediction in predictions[0]}
|
156 |
emotion_pred = max(emotion_scores, key=emotion_scores.get)
|
157 |
return emotion_pred
|
158 |
|
|
|
159 |
def generate_text(prompt, emotion=None, max_length=100):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
finetuned_lm_tokenizer, finetuned_lm_model = get_finetuned_lm_model()
|
161 |
input_ids = finetuned_lm_tokenizer.encode(prompt, return_tensors='pt')
|
162 |
attention_mask = torch.ones(input_ids.shape, dtype=torch.long)
|
163 |
-
|
164 |
if torch.cuda.is_available():
|
165 |
input_ids = input_ids.cuda()
|
166 |
attention_mask = attention_mask.cuda()
|
@@ -183,7 +240,21 @@ def generate_text(prompt, emotion=None, max_length=100):
|
|
183 |
|
184 |
return finetuned_lm_tokenizer.decode(outputs[0], skip_special_tokens=True)
|
185 |
|
|
|
186 |
def optimize_ai_model(emotion_history):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
if len(emotion_history) < 2:
|
188 |
return None, None
|
189 |
|
@@ -192,54 +263,73 @@ def optimize_ai_model(emotion_history):
|
|
192 |
|
193 |
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
|
194 |
X = encoder.fit_transform(np.array(contexts).reshape(-1, 1))
|
195 |
-
y = np.array(pd.Categorical(emotions).
|
|
|
|
|
|
|
196 |
|
197 |
-
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2
|
198 |
-
|
199 |
-
classifier = RandomForestClassifier(n_estimators=100, random_state=42)
|
200 |
-
classifier.fit(X_train, y_train)
|
201 |
|
202 |
-
|
203 |
-
return classifier, score
|
204 |
|
205 |
-
def respond(context, conversation_history, emotion_history_state):
|
206 |
-
normalized_context = normalize_context(context)
|
207 |
-
emotion_pred = predict_emotion(normalized_context)
|
208 |
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
})
|
213 |
-
save_historical_data(emotion_history)
|
214 |
|
215 |
-
|
|
|
|
|
216 |
|
217 |
-
|
|
|
218 |
|
219 |
-
response_prompt = f"{normalized_context}\n\n{conversation_history}\n\n{emotion_pred}"
|
220 |
-
generated_response = generate_text(response_prompt, emotion=emotion_pred)
|
221 |
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
-
|
225 |
-
|
226 |
-
context_input = gr.Textbox(label="Context")
|
227 |
-
response_output = gr.Textbox(label="Generated Response")
|
228 |
-
emotion_output = gr.Textbox(label="Detected Emotion")
|
229 |
-
|
230 |
-
conversation_history = gr.State([])
|
231 |
-
emotion_history_state = gr.State([])
|
232 |
-
|
233 |
-
def respond_wrapper(context, conversation_history, emotion_history_state):
|
234 |
-
response, emotion = respond(context, conversation_history, emotion_history_state)
|
235 |
-
conversation_history.append(response)
|
236 |
-
emotion_history_state.append(emotion)
|
237 |
-
return response, emotion, conversation_history, emotion_history_state
|
238 |
|
239 |
-
submit_button = gr.Button("Submit")
|
240 |
-
submit_button.click(respond_wrapper, [context_input, conversation_history, emotion_history_state], [response_output, emotion_output, conversation_history, emotion_history_state])
|
241 |
|
242 |
-
|
|
|
243 |
|
|
|
|
|
|
|
244 |
gc.collect()
|
245 |
-
torch.cuda.empty_cache()
|
|
|
50 |
# Lazy loading for the fine-tuned language model
|
51 |
_finetuned_lm_tokenizer = None
|
52 |
_finetuned_lm_model = None
|
53 |
+
|
54 |
+
|
55 |
def get_finetuned_lm_model():
|
56 |
global _finetuned_lm_tokenizer, _finetuned_lm_model
|
57 |
if _finetuned_lm_tokenizer is None or _finetuned_lm_model is None:
|
|
|
60 |
_finetuned_lm_model = AutoModelForCausalLM.from_pretrained(finetuned_lm_model_name, device_map="auto", low_cpu_mem_usage=True)
|
61 |
return _finetuned_lm_tokenizer, _finetuned_lm_model
|
62 |
|
63 |
+
|
64 |
# Enhanced Emotional States
|
65 |
emotions = {
|
66 |
'joy': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
|
|
72 |
'determination': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
73 |
'resentment': {'percentage': 10, 'motivation': 'negative', 'intensity': 0},
|
74 |
'glory': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
75 |
+
'motivation': {'percentage': 'motivation': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
|
76 |
'ideal_state': {'percentage': 100, 'motivation': 'balanced', 'intensity': 0},
|
77 |
'fear': {'percentage': 10, 'motivation': 'defensive', 'intensity': 0},
|
78 |
'surprise': {'percentage': 10, 'motivation': 'unexpected', 'intensity': 0},
|
|
|
87 |
total_percentage = 200
|
88 |
emotion_history_file = 'emotion_history.json'
|
89 |
|
90 |
+
|
91 |
def load_historical_data(file_path=emotion_history_file):
|
92 |
if os.path.exists(file_path):
|
93 |
with open(file_path, 'r') as file:
|
94 |
return json.load(file)
|
95 |
return []
|
96 |
|
97 |
+
|
98 |
def save_historical_data(historical_data, file_path=emotion_history_file):
|
99 |
with open(file_path, 'w') as file:
|
100 |
json.dump(historical_data, file)
|
101 |
|
102 |
+
|
103 |
emotion_history = load_historical_data()
|
104 |
|
105 |
+
|
106 |
def update_emotion(emotion, percentage, intensity):
|
107 |
+
"""
|
108 |
+
Updates the emotional state based on the provided emotion, percentage, and intensity.
|
109 |
+
|
110 |
+
Args:
|
111 |
+
emotion (str): Name of the emotion to update.
|
112 |
+
percentage (float): Percentage change to apply to the emotion.
|
113 |
+
intensity (float): Intensity value to update for the emotion.
|
114 |
+
"""
|
115 |
+
|
116 |
emotions['ideal_state']['percentage'] -= percentage
|
117 |
emotions[emotion]['percentage'] += percentage
|
118 |
emotions[emotion]['intensity'] = intensity
|
|
|
121 |
adjustment = total_percentage - total_current
|
122 |
emotions['ideal_state']['percentage'] += adjustment
|
123 |
|
124 |
+
|
125 |
def normalize_context(context):
|
126 |
+
"""
|
127 |
+
Normalizes the context text by converting it to lowercase and removing whitespace.
|
128 |
+
|
129 |
+
Args:
|
130 |
+
context (str): The context text to normalize.
|
131 |
+
|
132 |
+
Returns:
|
133 |
+
str: The normalized context text.
|
134 |
+
"""
|
135 |
+
|
136 |
return context.lower().strip()
|
137 |
|
138 |
+
|
139 |
+
# Improved Genetic Algorithm for Emotion Evolution with clear function definition
|
140 |
def evolve_emotions():
|
141 |
+
"""
|
142 |
+
Evolves the emotional states using a genetic algorithm to find an optimal balance.
|
143 |
+
|
144 |
+
This function utilizes the DEAP library to implement a genetic algorithm. It aims to
|
145 |
+
find a combination of emotional states that minimizes the following criteria:
|
146 |
+
|
147 |
+
1. Difference between the ideal state (100%) and its actual percentage.
|
148 |
+
2. Sum of all non-ideal emotional percentages.
|
149 |
+
3. Range of emotional intensities.
|
150 |
+
|
151 |
+
The weights for these criteria can be adjusted in the `creator.create("FitnessMulti", ...)` line.
|
152 |
+
"""
|
153 |
+
|
154 |
+
creator.create("FitnessMulti", base.Fitness, weights=(-1.0, -0.5, -0.2)) # Weights for evaluation criteria
|
155 |
creator.create("Individual", list, fitness=creator.FitnessMulti)
|
156 |
|
157 |
toolbox = base.Toolbox()
|
158 |
toolbox.register("attr_float", random.uniform, 0, 20)
|
159 |
toolbox.register("attr_intensity", random.uniform, 0, 10)
|
160 |
toolbox.register("individual", tools.initCycle, creator.Individual,
|
161 |
+
(toolbox.attr_float,) * (len(emotions) - 1) + # Individual emotions
|
162 |
+
(toolbox.attr_intensity,) * len(emotions) + # Intensities
|
163 |
(lambda: 100,), n=1)
|
164 |
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
|
165 |
toolbox.register("mate", tools.cxTwoPoint)
|
166 |
+
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2
|
167 |
toolbox.register("select", tools.selNSGA2)
|
168 |
toolbox.register("evaluate", evaluate)
|
169 |
|
170 |
population = toolbox.population(n=100)
|
171 |
+
algorithms.eaMuPlusLambda(population, toolbox, mu=50, lambda_=100, cxpb=0.7, mutpb=0.2, ngen=100,
|
172 |
+
stats=None, halloffame=None, verbose=False)
|
173 |
|
174 |
best_individual = tools.selBest(population, k=1)[0]
|
175 |
+
emotion_values = best_individual[:len(emotions) - 1]
|
176 |
intensities = best_individual[-21:-1]
|
177 |
ideal_state = best_individual[-1]
|
178 |
|
|
|
182 |
|
183 |
emotions['ideal_state']['percentage'] = ideal_state
|
184 |
|
185 |
+
|
186 |
def predict_emotion(context):
|
187 |
+
"""
|
188 |
+
Predicts the emotion from the provided context using a pre-trained BERT model.
|
189 |
+
|
190 |
+
Args:
|
191 |
+
context (str): The context text for emotion prediction.
|
192 |
+
|
193 |
+
Returns:
|
194 |
+
str: The predicted emotion.
|
195 |
+
"""
|
196 |
+
|
197 |
emotion_prediction_pipeline = pipeline('text-classification', model=emotion_prediction_model, tokenizer=emotion_prediction_tokenizer, top_k=None)
|
198 |
predictions = emotion_prediction_pipeline(context)
|
199 |
emotion_scores = {prediction['label']: prediction['score'] for prediction in predictions[0]}
|
200 |
emotion_pred = max(emotion_scores, key=emotion_scores.get)
|
201 |
return emotion_pred
|
202 |
|
203 |
+
|
204 |
def generate_text(prompt, emotion=None, max_length=100):
|
205 |
+
"""
|
206 |
+
Generates text using a fine-tuned language model, optionally incorporating the specified emotion.
|
207 |
+
|
208 |
+
Args:
|
209 |
+
prompt (str): The starting prompt for text generation.
|
210 |
+
emotion (str, optional): The emotion to consider during generation. Defaults to None.
|
211 |
+
max_length (int, optional): The maximum length of the generated text. Defaults to 100.
|
212 |
+
|
213 |
+
Returns:
|
214 |
+
str: The generated text.
|
215 |
+
"""
|
216 |
+
|
217 |
finetuned_lm_tokenizer, finetuned_lm_model = get_finetuned_lm_model()
|
218 |
input_ids = finetuned_lm_tokenizer.encode(prompt, return_tensors='pt')
|
219 |
attention_mask = torch.ones(input_ids.shape, dtype=torch.long)
|
220 |
+
|
221 |
if torch.cuda.is_available():
|
222 |
input_ids = input_ids.cuda()
|
223 |
attention_mask = attention_mask.cuda()
|
|
|
240 |
|
241 |
return finetuned_lm_tokenizer.decode(outputs[0], skip_special_tokens=True)
|
242 |
|
243 |
+
|
244 |
def optimize_ai_model(emotion_history):
|
245 |
+
"""
|
246 |
+
Trains a Random Forest classifier to predict emotions based on historical data.
|
247 |
+
|
248 |
+
This function is optional and can be used to improve the accuracy of emotion prediction
|
249 |
+
over time by learning from past interactions.
|
250 |
+
|
251 |
+
Args:
|
252 |
+
emotion_history (list): A list of dictionaries containing context and emotion information.
|
253 |
+
|
254 |
+
Returns:
|
255 |
+
tuple: A tuple containing the trained classifier and its accuracy score (or None if insufficient data).
|
256 |
+
"""
|
257 |
+
|
258 |
if len(emotion_history) < 2:
|
259 |
return None, None
|
260 |
|
|
|
263 |
|
264 |
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
|
265 |
X = encoder.fit_transform(np.array(contexts).reshape(-1, 1))
|
266 |
+
y = np.array(pd.Categorical(emotions).
|
267 |
+
codes)
|
268 |
+
clf = RandomForestClassifier(n_estimators=100)
|
269 |
+
clf.fit(X, y)
|
270 |
|
271 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
|
272 |
+
accuracy = clf.score(X_test, y_test)
|
|
|
|
|
273 |
|
274 |
+
return clf, accuracy
|
|
|
275 |
|
|
|
|
|
|
|
276 |
|
277 |
+
def get_dominant_emotion():
|
278 |
+
"""
|
279 |
+
Returns the emotion with the highest percentage.
|
|
|
|
|
280 |
|
281 |
+
Returns:
|
282 |
+
str: The emotion with the highest percentage.
|
283 |
+
"""
|
284 |
|
285 |
+
dominant_emotion = max(emotions, key=lambda e: emotions[e]['percentage'])
|
286 |
+
return dominant_emotion
|
287 |
|
|
|
|
|
288 |
|
289 |
+
def respond_to_user(context):
|
290 |
+
"""
|
291 |
+
Responds to the user based on the provided context.
|
292 |
+
|
293 |
+
This function predicts the user's emotion from the context, generates a response that
|
294 |
+
considers the user's emotional state, and updates the internal emotional states.
|
295 |
+
|
296 |
+
Args:
|
297 |
+
context (str): The user's input context.
|
298 |
+
|
299 |
+
Returns:
|
300 |
+
str: The AI's response to the user.
|
301 |
+
"""
|
302 |
+
|
303 |
+
predicted_emotion = predict_emotion(context)
|
304 |
+
dominant_emotion = get_dominant_emotion()
|
305 |
+
|
306 |
+
response = f"You seem to be feeling {predicted_emotion}. "
|
307 |
+
|
308 |
+
if predicted_emotion == dominant_emotion:
|
309 |
+
response += f"I understand that you're feeling strongly about {predicted_emotion} right now. "
|
310 |
+
else:
|
311 |
+
response += f"Is there anything I can do to help you with {predicted_emotion}? "
|
312 |
+
|
313 |
+
if dominant_emotion != 'ideal_state':
|
314 |
+
adjustment_percentage = min(emotions[dominant_emotion]['percentage'] / 2, 10)
|
315 |
+
update_emotion(dominant_emotion, -adjustment_percentage, 0)
|
316 |
+
|
317 |
+
emotion_history.append({'context': context, 'emotion': predicted_emotion})
|
318 |
+
save_historical_data(emotion_history)
|
319 |
+
|
320 |
+
# Train the emotion prediction classifier if enough data is available
|
321 |
+
trained_clf, accuracy = optimize_ai_model(emotion_history)
|
322 |
+
if trained_clf:
|
323 |
+
print(f"Emotion prediction model accuracy: {accuracy:.2f}")
|
324 |
|
325 |
+
generated_text = generate_text(prompt=response, emotion=dominant_emotion)
|
326 |
+
return generated_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
|
|
|
|
|
328 |
|
329 |
+
interface = gr.Interface(respond_to_user, inputs="textbox", outputs="textbox", title="AI Assistant with Evolving Emotions", description="Talk to an AI that adapts to your emotions.")
|
330 |
+
interface.launch()
|
331 |
|
332 |
+
# Clean up memory usage
|
333 |
+
del finetuned_lm_tokenizer
|
334 |
+
del finetuned_lm_model
|
335 |
gc.collect()
|
|