Cain / app.py
Sephfox's picture
Update app.py
60ae9bc verified
raw
history blame
13.2 kB
import warnings
import numpy as np
import pandas as pd
import os
import json
import random
import gradio as gr
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from deap import base, creator, tools, algorithms
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, AutoModelForSequenceClassification
import gc
warnings.filterwarnings('ignore', category=FutureWarning, module='huggingface_hub.file_download')
# Initialize Example Emotions Dataset
data = {
'context': [
'I am happy', 'I am sad', 'I am angry', 'I am excited', 'I am calm',
'I am feeling joyful', 'I am grieving', 'I am feeling peaceful', 'I am frustrated',
'I am determined', 'I feel resentment', 'I am feeling glorious', 'I am motivated',
'I am surprised', 'I am fearful', 'I am trusting', 'I feel disgust', 'I am optimistic',
'I am pessimistic', 'I feel bored', 'I am envious'
],
'emotion': [
'joy', 'sadness', 'anger', 'joy', 'calmness', 'joy', 'grief', 'calmness', 'anger',
'determination', 'resentment', 'glory', 'motivation', 'surprise', 'fear', 'trust',
'disgust', 'optimism', 'pessimism', 'boredom', 'envy'
]
}
df = pd.DataFrame(data)
# Encoding the contexts using One-Hot Encoding (memory-efficient)
encoder = OneHotEncoder(handle_unknown='ignore', sparse=True)
contexts_encoded = encoder.fit_transform(df[['context']])
# Encoding emotions
emotions_target = pd.Categorical(df['emotion']).codes
emotion_classes = pd.Categorical(df['emotion']).categories
# Load pre-trained BERT model for emotion prediction
emotion_prediction_model = AutoModelForSequenceClassification.from_pretrained("bhadresh-savani/distilbert-base-uncased-emotion")
emotion_prediction_tokenizer = AutoTokenizer.from_pretrained("bhadresh-savani/distilbert-base-uncased-emotion")
# Lazy loading for the fine-tuned language model
_finetuned_lm_tokenizer = None
_finetuned_lm_model = None
def get_finetuned_lm_model():
global _finetuned_lm_tokenizer, _finetuned_lm_model
if _finetuned_lm_tokenizer is None or _finetuned_lm_model is None:
finetuned_lm_model_name = "microsoft/DialoGPT-large" # Replace with your fine-tuned language model name
_finetuned_lm_tokenizer = AutoTokenizer.from_pretrained(finetuned_lm_model_name)
_finetuned_lm_model = AutoModelForCausalLM.from_pretrained(finetuned_lm_model_name, device_map="auto", low_cpu_mem_usage=True)
return _finetuned_lm_tokenizer, _finetuned_lm_model
# Enhanced Emotional States
emotions = {
'joy': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
'pleasure': {'percentage': 10, 'motivation': 'selfish', 'intensity': 0},
'sadness': {'percentage': 10, 'motivation': 'negative', 'intensity': 0},
'grief': {'percentage': 10, 'motivation': 'negative', 'intensity': 0},
'anger': {'percentage': 10, 'motivation': 'traumatic or strong', 'intensity': 0},
'calmness': {'percentage': 10, 'motivation': 'neutral', 'intensity': 0},
'determination': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
'resentment': {'percentage': 10, 'motivation': 'negative', 'intensity': 0},
'glory': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
'motivation': {'percentage': 'motivation': {'percentage': 10, 'motivation': 'positive', 'intensity': 0},
'ideal_state': {'percentage': 100, 'motivation': 'balanced', 'intensity': 0},
'fear': {'percentage': 10, 'motivation': 'defensive', 'intensity': 0},
'surprise': {'percentage': 10, 'motivation': 'unexpected', 'intensity': 0},
'anticipation': {'percentage': 10, 'motivation': 'predictive', 'intensity': 0},
'trust': {'percentage': 10, 'motivation': 'reliable', 'intensity': 0},
'disgust': {'percentage': 10, 'motivation': 'repulsive', 'intensity': 0},
'optimism': {'percentage': 10, 'motivation': 'hopeful', 'intensity': 0},
'pessimism': {'percentage': 10, 'motivation': 'doubtful', 'intensity': 0},
'boredom': {'percentage': 10, 'motivation': 'indifferent', 'intensity': 0},
'envy': {'percentage': 10, 'motivation': 'jealous', 'intensity': 0}
}
total_percentage = 200
emotion_history_file = 'emotion_history.json'
def load_historical_data(file_path=emotion_history_file):
if os.path.exists(file_path):
with open(file_path, 'r') as file:
return json.load(file)
return []
def save_historical_data(historical_data, file_path=emotion_history_file):
with open(file_path, 'w') as file:
json.dump(historical_data, file)
emotion_history = load_historical_data()
def update_emotion(emotion, percentage, intensity):
"""
Updates the emotional state based on the provided emotion, percentage, and intensity.
Args:
emotion (str): Name of the emotion to update.
percentage (float): Percentage change to apply to the emotion.
intensity (float): Intensity value to update for the emotion.
"""
emotions['ideal_state']['percentage'] -= percentage
emotions[emotion]['percentage'] += percentage
emotions[emotion]['intensity'] = intensity
total_current = sum(e['percentage'] for e in emotions.values())
adjustment = total_percentage - total_current
emotions['ideal_state']['percentage'] += adjustment
def normalize_context(context):
"""
Normalizes the context text by converting it to lowercase and removing whitespace.
Args:
context (str): The context text to normalize.
Returns:
str: The normalized context text.
"""
return context.lower().strip()
# Improved Genetic Algorithm for Emotion Evolution with clear function definition
def evolve_emotions():
"""
Evolves the emotional states using a genetic algorithm to find an optimal balance.
This function utilizes the DEAP library to implement a genetic algorithm. It aims to
find a combination of emotional states that minimizes the following criteria:
1. Difference between the ideal state (100%) and its actual percentage.
2. Sum of all non-ideal emotional percentages.
3. Range of emotional intensities.
The weights for these criteria can be adjusted in the `creator.create("FitnessMulti", ...)` line.
"""
creator.create("FitnessMulti", base.Fitness, weights=(-1.0, -0.5, -0.2)) # Weights for evaluation criteria
creator.create("Individual", list, fitness=creator.FitnessMulti)
toolbox = base.Toolbox()
toolbox.register("attr_float", random.uniform, 0, 20)
toolbox.register("attr_intensity", random.uniform, 0, 10)
toolbox.register("individual", tools.initCycle, creator.Individual,
(toolbox.attr_float,) * (len(emotions) - 1) + # Individual emotions
(toolbox.attr_intensity,) * len(emotions) + # Intensities
(lambda: 100,), n=1)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2
toolbox.register("select", tools.selNSGA2)
toolbox.register("evaluate", evaluate)
population = toolbox.population(n=100)
algorithms.eaMuPlusLambda(population, toolbox, mu=50, lambda_=100, cxpb=0.7, mutpb=0.2, ngen=100,
stats=None, halloffame=None, verbose=False)
best_individual = tools.selBest(population, k=1)[0]
emotion_values = best_individual[:len(emotions) - 1]
intensities = best_individual[-21:-1]
ideal_state = best_individual[-1]
for i, emotion in enumerate(emotions):
emotions[emotion]['percentage'] = emotion_values[i]
emotions[emotion]['intensity'] = intensities[i]
emotions['ideal_state']['percentage'] = ideal_state
def predict_emotion(context):
"""
Predicts the emotion from the provided context using a pre-trained BERT model.
Args:
context (str): The context text for emotion prediction.
Returns:
str: The predicted emotion.
"""
emotion_prediction_pipeline = pipeline('text-classification', model=emotion_prediction_model, tokenizer=emotion_prediction_tokenizer, top_k=None)
predictions = emotion_prediction_pipeline(context)
emotion_scores = {prediction['label']: prediction['score'] for prediction in predictions[0]}
emotion_pred = max(emotion_scores, key=emotion_scores.get)
return emotion_pred
def generate_text(prompt, emotion=None, max_length=100):
"""
Generates text using a fine-tuned language model, optionally incorporating the specified emotion.
Args:
prompt (str): The starting prompt for text generation.
emotion (str, optional): The emotion to consider during generation. Defaults to None.
max_length (int, optional): The maximum length of the generated text. Defaults to 100.
Returns:
str: The generated text.
"""
finetuned_lm_tokenizer, finetuned_lm_model = get_finetuned_lm_model()
input_ids = finetuned_lm_tokenizer.encode(prompt, return_tensors='pt')
attention_mask = torch.ones(input_ids.shape, dtype=torch.long)
if torch.cuda.is_available():
input_ids = input_ids.cuda()
attention_mask = attention_mask.cuda()
finetuned_lm_model = finetuned_lm_model.cuda()
if emotion:
emotion_token = emotion_prediction_tokenizer.encode(emotion, add_special_tokens=False)
input_ids = torch.cat((input_ids, torch.tensor(emotion_token).unsqueeze(0)), dim=1)
outputs = finetuned_lm_model.generate(
input_ids=input_ids,
attention_mask=attention_mask,
max_length=max_length,
pad_token_id=finetuned_lm_tokenizer.eos_token_id,
num_return_sequences=1,
temperature=0.7,
top_p=0.9,
do_sample=True
)
return finetuned_lm_tokenizer.decode(outputs[0], skip_special_tokens=True)
def optimize_ai_model(emotion_history):
"""
Trains a Random Forest classifier to predict emotions based on historical data.
This function is optional and can be used to improve the accuracy of emotion prediction
over time by learning from past interactions.
Args:
emotion_history (list): A list of dictionaries containing context and emotion information.
Returns:
tuple: A tuple containing the trained classifier and its accuracy score (or None if insufficient data).
"""
if len(emotion_history) < 2:
return None, None
contexts = [entry['context'] for entry in emotion_history]
emotions = [entry['emotion'] for entry in emotion_history]
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
X = encoder.fit_transform(np.array(contexts).reshape(-1, 1))
y = np.array(pd.Categorical(emotions).
codes)
clf = RandomForestClassifier(n_estimators=100)
clf.fit(X, y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
accuracy = clf.score(X_test, y_test)
return clf, accuracy
def get_dominant_emotion():
"""
Returns the emotion with the highest percentage.
Returns:
str: The emotion with the highest percentage.
"""
dominant_emotion = max(emotions, key=lambda e: emotions[e]['percentage'])
return dominant_emotion
def respond_to_user(context):
"""
Responds to the user based on the provided context.
This function predicts the user's emotion from the context, generates a response that
considers the user's emotional state, and updates the internal emotional states.
Args:
context (str): The user's input context.
Returns:
str: The AI's response to the user.
"""
predicted_emotion = predict_emotion(context)
dominant_emotion = get_dominant_emotion()
response = f"You seem to be feeling {predicted_emotion}. "
if predicted_emotion == dominant_emotion:
response += f"I understand that you're feeling strongly about {predicted_emotion} right now. "
else:
response += f"Is there anything I can do to help you with {predicted_emotion}? "
if dominant_emotion != 'ideal_state':
adjustment_percentage = min(emotions[dominant_emotion]['percentage'] / 2, 10)
update_emotion(dominant_emotion, -adjustment_percentage, 0)
emotion_history.append({'context': context, 'emotion': predicted_emotion})
save_historical_data(emotion_history)
# Train the emotion prediction classifier if enough data is available
trained_clf, accuracy = optimize_ai_model(emotion_history)
if trained_clf:
print(f"Emotion prediction model accuracy: {accuracy:.2f}")
generated_text = generate_text(prompt=response, emotion=dominant_emotion)
return generated_text
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.")
interface.launch()
# Clean up memory usage
del finetuned_lm_tokenizer
del finetuned_lm_model
gc.collect()