|
import os |
|
from groq import Groq |
|
import gradio as gr |
|
import pytesseract |
|
from sentence_transformers import SentenceTransformer, util |
|
from PIL import Image |
|
from typing import List |
|
import torch |
|
from transformers import BertTokenizer, BertModel, T5ForConditionalGeneration, T5Tokenizer |
|
import torch.nn.functional as F |
|
|
|
|
|
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') |
|
bert_model = BertModel.from_pretrained('bert-base-uncased') |
|
sentence_model = SentenceTransformer('paraphrase-MiniLM-L6-v2') |
|
|
|
|
|
grammar_model = T5ForConditionalGeneration.from_pretrained('t5-base') |
|
grammar_tokenizer = T5Tokenizer.from_pretrained('t5-base') |
|
|
|
|
|
client = Groq(api_key=os.environ.get("GROQ_API_KEY")) |
|
|
|
|
|
system_prompt = { |
|
"role": "system", |
|
"content": "You are a useful assistant. You reply with efficient answers." |
|
} |
|
|
|
async def chat_groq(message, history): |
|
messages = [system_prompt] |
|
for msg in history: |
|
messages.append({"role": "user", "content": str(msg[0])}) |
|
messages.append({"role": "assistant", "content": str(msg[1])}) |
|
messages.append({"role": "user", "content": str(message)}) |
|
|
|
response_content = '' |
|
|
|
stream = client.chat.completions.create( |
|
model="llama3-70b-8192", |
|
messages=messages, |
|
max_tokens=1024, |
|
temperature=1.3, |
|
stream=True |
|
) |
|
|
|
for chunk in stream: |
|
content = chunk.choices[0].delta.content |
|
if content: |
|
response_content += chunk.choices[0].delta.content |
|
yield response_content |
|
|
|
def extract_text_from_image(filepath: str, languages: List[str]): |
|
image = Image.open(filepath) |
|
lang_str = '+'.join(languages) |
|
return pytesseract.image_to_string(image=image, lang=lang_str) |
|
|
|
def assign_badge(grade): |
|
if grade == 5: |
|
return "Gold Badge π" |
|
elif grade == 4: |
|
return "Silver Badge π₯" |
|
elif grade == 3: |
|
return "Bronze Badge π₯" |
|
else: |
|
return "Keep Improving Badge πͺ" |
|
|
|
def detailed_feedback(similarity_score): |
|
if similarity_score >= 0.9: |
|
return {"Clarity": "Excellent", "Completeness": "Complete", "Accuracy": "Accurate"} |
|
elif similarity_score >= 0.8: |
|
return {"Clarity": "Good", "Completeness": "Almost Complete", "Accuracy": "Mostly Accurate"} |
|
elif similarity_score >= 0.7: |
|
return {"Clarity": "Fair", "Completeness": "Partial", "Accuracy": "Some Errors"} |
|
else: |
|
return {"Clarity": "Needs Improvement", "Completeness": "Incomplete", "Accuracy": "Inaccurate"} |
|
|
|
def get_grade(similarity_score): |
|
if similarity_score >= 0.9: |
|
return 5 |
|
elif similarity_score >= 0.8: |
|
return 4 |
|
elif similarity_score >= 0.7: |
|
return 3 |
|
elif similarity_score >= 0.6: |
|
return 2 |
|
else: |
|
return 1 |
|
|
|
def get_bert_embedding(text): |
|
inputs = tokenizer(text, return_tensors='pt', truncation=True, padding=True) |
|
with torch.no_grad(): |
|
outputs = bert_model(**inputs) |
|
embeddings = outputs.last_hidden_state.mean(dim=1) |
|
return embeddings |
|
|
|
def calculate_cosine_similarity(embedding1, embedding2): |
|
similarity = F.cosine_similarity(embedding1, embedding2) |
|
return similarity.item() |
|
|
|
def calculate_sentence_similarity(text1, text2): |
|
embedding1 = sentence_model.encode(text1, convert_to_tensor=True) |
|
embedding2 = sentence_model.encode(text2, convert_to_tensor=True) |
|
return util.pytorch_cos_sim(embedding1, embedding2).item() |
|
|
|
|
|
def detect_grammar_errors(text): |
|
input_text = f"grammar: {text}" |
|
inputs = grammar_tokenizer.encode(input_text, return_tensors='pt', max_length=512, truncation=True) |
|
outputs = grammar_model.generate(inputs, max_length=512, num_beams=4, early_stopping=True) |
|
grammar_analysis = grammar_tokenizer.decode(outputs[0], skip_special_tokens=True) |
|
|
|
|
|
corrected_sentence = grammar_analysis |
|
|
|
|
|
|
|
original_words = text.split() |
|
corrected_words = corrected_sentence.split() |
|
|
|
|
|
error_count = abs(len(original_words) - len(corrected_words)) |
|
|
|
return error_count, corrected_sentence |
|
|
|
|
|
def penalize_for_grammar(student_answer): |
|
grammar_errors, _ = detect_grammar_errors(student_answer) |
|
print(f"errors given by grammer:{grammar_errors}") |
|
|
|
penalty = max(0, 1 - 0.25 * grammar_errors) |
|
return penalty |
|
|
|
|
|
def compare_answers(student_answer, teacher_answer): |
|
bert_similarity = calculate_cosine_similarity(get_bert_embedding(student_answer), get_bert_embedding(teacher_answer)) |
|
print(f"BERT similarity: {bert_similarity}") |
|
|
|
sentence_similarity = calculate_sentence_similarity(student_answer, teacher_answer) |
|
print(f"Sentence similarity: {sentence_similarity}") |
|
|
|
|
|
semantic_similarity = (0.50 * bert_similarity + 0.50 * sentence_similarity) |
|
print(f"Semantic similarity: {semantic_similarity}") |
|
|
|
|
|
grammar_penalty = penalize_for_grammar(student_answer) |
|
final_similarity = semantic_similarity - grammar_penalty |
|
print(f"grammar penalty: {grammar_penalty}") |
|
print(f"Final similarity (after grammar penalty): {final_similarity}") |
|
|
|
return final_similarity |
|
|
|
def extract_keywords(text): |
|
return set(text.lower().split()) |
|
|
|
def check_keywords(student_answer, model_answer): |
|
student_keywords = extract_keywords(student_answer) |
|
teacher_keywords = extract_keywords(model_answer) |
|
keyword_overlap = len(student_keywords.intersection(teacher_keywords)) |
|
keyword_similarity = keyword_overlap / (len(teacher_keywords) if len(teacher_keywords) > 0 else 1) |
|
print(f"Keyword similarity: {keyword_similarity}") |
|
return keyword_similarity |
|
|
|
def evaluate_answer(image, languages, model_answer): |
|
student_answer = extract_text_from_image(image, languages) |
|
print(f"Extracted student answer: {student_answer}") |
|
|
|
|
|
semantic_similarity = compare_answers(student_answer, model_answer) |
|
|
|
|
|
keyword_similarity = check_keywords(student_answer, model_answer) |
|
|
|
|
|
combined_similarity = (0.9 * semantic_similarity + 0.1 * keyword_similarity) |
|
print(f"Combined similarity score: {combined_similarity}") |
|
|
|
grade = get_grade(combined_similarity) |
|
feedback = f"Student's answer: {student_answer}\nTeacher's answer: {model_answer}" |
|
badge = assign_badge(grade) |
|
detailed_feedback_msg = detailed_feedback(combined_similarity) |
|
|
|
prompt = f"The student got grade: {grade} when the student's answer is: {student_answer} and the teacher's answer is: {model_answer}. Justify the grade given to the student." |
|
|
|
return grade, combined_similarity * 100, feedback, badge, detailed_feedback_msg, prompt |
|
|
|
async def gradio_interface(image, languages: List[str], model_answer="The process of photosynthesis helps plants produce glucose using sunlight.", prompt="", history=[]): |
|
grade, similarity_score, feedback, badge, detailed_feedback_msg, prompt = evaluate_answer(image, languages, model_answer) |
|
response = "" |
|
async for result in chat_groq(prompt, history): |
|
response = result |
|
return grade, similarity_score, feedback, badge, detailed_feedback_msg, response |
|
|
|
language_choices = pytesseract.get_languages() |
|
|
|
interface = gr.Interface( |
|
fn=gradio_interface, |
|
inputs=[ |
|
gr.Image(type="filepath", label="Input"), |
|
gr.CheckboxGroup(language_choices, type="value", value=['eng'], label='Language'), |
|
gr.Textbox(lines=2, placeholder="Enter your model answer here", label="Model Answer"), |
|
gr.Textbox(lines=2, placeholder="Enter your prompt here", label="Prompt") |
|
], |
|
outputs=[ |
|
gr.Text(label="Grade"), |
|
gr.Number(label="Similarity Score (%)"), |
|
gr.Text(label="Feedback"), |
|
gr.Text(label="Badge"), |
|
gr.JSON(label="Detailed Feedback"), |
|
gr.Text(label="Generated Response") |
|
], |
|
title="Enhanced Automated Grading System", |
|
description="Upload an image of your answer sheet to get a grade from 1 to 5, similarity score, visual feedback, badge, and detailed feedback based on the model answer.", |
|
live=True |
|
) |
|
|
|
if __name__ == "__main__": |
|
interface.queue() |
|
interface.launch() |
|
|