콘텐츠로 건너뛰기
Home » LLM과 함께 영어퀴즈 생성기를 만들어보자!

LLM과 함께 영어퀴즈 생성기를 만들어보자!

단어시험을 실시하는 이유

장기기억으로 가는 지름길은 주기적인 정보의 인출이다. 이 인출을 가능케 하는 것은 바로 퀴즈다. 학생들은 학원에서 진도를 빨리 나가기에 바쁘지, 배운 내용을 복습하는 습관이 많이 부족하다. 그래서 나는 수업 시작시간 10분을 단어 및 문장 퀴즈와 풀이 시간으로 정하고 매 수업 실시하고 있다. 학생들은 지난 학기 내내 시험을 쳤고, 이제 내가 교실로 들어가면 학급의 절반 정도는 내가 말하지 않아도 시험 문제를 풀고 있다. 이렇듯 매 수업 실시하는 퀴즈이기 때문에 문제를 출제하는데 드는 시간을 줄일 필요가 있어 직접 프로그램을 만들어보았다.

그냥 클래스카드 쓰면 안되나?

클래스카드는 영어교사에게 큰 도움을 주는 서비스이다. 영어 단어 또는 문장을 플래시 카드 형태로 제공하며, 암기한 단어, 중요한 단어를 따로 저장하여 부족한 부분을 메울 수 있는 기능을 갖추고 있다. 유료 계정으로 업그레이드를 하면 학생 계정을 만들어서 각 학생의 학습 내역을 추적할 수 있고 학생은 개별화된 학습 자료를 통해 보다 더 효과적으로 어휘를 학습할 수 있다.

심지어 인쇄가능한 형태의 시험지도 제작이 되는데, 그 기능도 아주 좋다. 객관식, 주관식 선택도 가능하고 문제 섞기 기능도 지원한다. 그럼에도 내가 굳이 영어퀴즈 생성기를 만든 이유는 클래스카드에서는 여러 세트에 걸쳐 문제를 만들 수 없기 때문이었다. 학습한 모든 내용을 하나의 세트로 만든 후 문제를 출제하면 여러 세트에서 출제하는 것과 비슷한 효과를 볼 수 있겠지만 내가 원하는 것은 조금 더 디테일하다. 예를 들어 어휘 파트 세 문제, 문법 두 문제, 본문 세 문제 등과 같이 각 반에 부족한 부분을 반영해서 출제하고 싶었다. 그래야 주기적으로 과거 학습 내용을 인출할 수 있기 때문이다.

직접 만들어 보자!

내가 원하는 요구사항을 정리해서 Google Bard에 코드 작성 요청을 했고, 여러 번의 대화 끝에 프로그램을 완성했다.

준비물 및 제작 방법

준비물은 파이썬과 VSCode이다. 코드를 그대로 붙여 넣고 필요한 라이브러리를 설치한 후 실행하면 바로 사용할 수 있다.

아래에 코드 전문을 첨부하였다. 수정해서 만들 수 있도록 각 함수에 주석을 달아 놓았으니, 자신만의 퀴즈생성기를 만들어보길 바란다.

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import os, random
from openpyxl import load_workbook, Workbook
import pyperclip
from datetime import datetime

class QuizApp:
    #문제 출제할 파일 불러오는 함수
    def load_file(self): 
        self.file_path = filedialog.askopenfilename(filetypes=[('Excel Files', '*.xlsx')]) 
        if self.file_path:   
            xls = pd.ExcelFile(self.file_path)  
            row_index = 6  #파일을 불러오면 6번 행부터 시트 이름과 숫자기입 칸이 시트 수 만큼 생성된다. 
            self.sheet_vars_dict={}
            for sheet_name in xls.sheet_names:  # 각 시트 이름에 대해 반복 처리
                sheet_label = ttk.Label(self.window, text=sheet_name)  # 시트 이름을 딴 레이블 생성
                sheet_label.grid(row=row_index,column=0) 
                question_count_var = tk.StringVar(value='0')  
                question_count_entry = ttk.Entry(self.window,
                                                textvariable=question_count_var)
                question_count_entry.grid(row=row_index,column=1)
                self.sheet_vars_dict[sheet_name] = question_count_var
                question_count_var.trace('w', lambda *args: self.update_total_questions())
                row_index += 1
    
    #파일 자동 저장 함수: 문제 생성이 완료되면 출제파일이 있던 경로에 결과물을 자동으로 생성한다.
    def save_to_file(self, wb_new): 
      directory=os.path.dirname(self.file_path) 
      date_str=datetime.now().strftime("%Y%m%d")
      filename=f"생성된 시험지({date_str}).xlsx"
      filepath=os.path.join(directory,filename)
      wb_new.save(filepath) 
      return filepath #경로를 반환하여 파일을 바로 실행할 때 사용

    #각 시트당 문제 개수 설정하면 전체 문제 개수 업데이트 해주는 함수
    def update_total_questions(self): 
        total_questions=sum(int(var.get()) for var in self.sheet_vars_dict.values())
        self.question_count.set(str(total_questions))

    #클립보드에 복사하는 함수: 한번에 여러개 만들고 싶을 때 생성>붙여넣기를 반복할 수 있다.
    def copy_to_clipboard(self): 
      selected_item_id=self.tree.selection()[0] 
      selected_item=self.tree.item(selected_item_id)["values"]
      selected_text="\t".join(selected_item) 
      pyperclip.copy(selected_text) 

    #퀴즈 생성 함수
    def generate_quiz(self): 
        mix_questions = self.shuffle_var.get()  #문제 순서 섞을지 여부 불러오기
        question_language = self.quiz_type_var.get() #영어, 한글, 섞어서 중 출제방식 불러오기
        wb = load_workbook(self.file_path) #시험 자료 불러오기
        new_wb = Workbook() #시험지 만들 새 엑셀파일 생성
        new_ws = new_wb.active
        new_ws.title = "시험지"
        new_ws.append(["순번", "문제", "정답"])
        question_pool = []
        
        # 각 시트별 문제 추출하는 반복문
        for sheet_name,var in self.sheet_vars_dict.items():
            try:
                count=int(var.get())
            except:
                count=0
            ws=wb[sheet_name]
            questions=list(ws.iter_rows(min_row=2,values_only=True))
            random.shuffle(questions)
            selected_questions=questions[:count]
            question_pool.extend(selected_questions)

        
        if mix_questions: #순서 섞기 체크했을 경우 셔플 실행
           random.shuffle(question_pool)

        #출제 조건에 따라 문제 변환(영어, 한글, 섞어서 제시)
        for i,(_,eng,kor) in enumerate(question_pool,start=1):
            if question_language=='mixed':
                if random.choice([True,False]):
                    question=eng
                    answer=kor
                else:
                    question=kor
                    answer=eng
            elif question_language=='english':
                question=eng
                answer=kor
            else: 
                question=kor
                answer=eng
            new_ws.append([i,question,answer]) #새 시트에 문제 작성

        newfilepath = self.save_to_file(new_wb) #파일 저장 후 경로 반환


        data = new_ws.values
        cols = next(data)
        data_rows = list(data)
        df_new_wb=pd.DataFrame(data_rows, columns=cols)
        df_new_wb.to_clipboard(index=False) #엑셀의 표를 데이터프레임으로 바꾼 뒤 클립보드로 복사

     

        #treeview라는 객체에 생성된 문제 미리보기 생성
        for row in self.tree.get_children():
                self.tree.delete(row)
        for row in new_ws.iter_rows(min_row = 2, values_only=True): 
            try:
                tuple_row=tuple(map(str,row))
                tuple_row=tuple(map(str,tuple_row))
                self.tree.insert("", "end", values=tuple_row)
            except:
                print("오류")
        #파일 실행여부 팝업. 예를 선택하면 저장했던 엑셀 바로 실행.
        if messagebox.askyesno("파일열기", "문제 생성이 완료되었습니다. 파일을 여시겠습니까? 파일을 열지 않고 바로 붙여넣기할 수 있습니다."):
            os.startfile(newfilepath)


    #프로그램의 UI생성하는 함수이자, 각 버튼에 위에서 생성했던 함수를 매칭. QuizApp이 실행되면 __init__이 가장 먼저 실행됨.
    def __init__(self):
        self.file_path = ""
        self.window = tk.Tk()
        self.window.title("영어 퀴즈 생성기")
        #tree는 시험지 미리보기임. 필요없다면 관련 코드 모두 삭제 가능
        self.tree = ttk.Treeview(self.window)
        self.tree["columns"] = ("순번", "문제", "정답")
        self.tree["show"] = "headings"
        for column in self.tree["columns"]:
            self.tree.heading(column, text=column) 
        self.tree.grid(row=5, column=0, columnspan=3)

        # 파일 불러오기 버튼에 기능 추가
        self.load_button = ttk.Button(self.window, text="파일 불러오기", command = self.load_file)
        self.load_button.grid(row=0, column=0)

        # 문제 생성하기 버튼에 기능 추가
        self.generate_button = ttk.Button(self.window, text="퀴즈 생성하기", command = self.generate_quiz)
        self.generate_button.grid(row=1, column=0)

        # 문항 개수 텍스트박스 출제 문항 수를 입력하면 자동으로 계산됨
        self.question_count = tk.StringVar(value='0')
        self.question_count_entry = ttk.Entry(self.window, textvariable=self.question_count)
        self.question_count_entry.grid(row=2, column=0)

        #출제유형 변수: 한글제시 / 영어제시 / 섞어서 제시 (mixed를 기본 값으로 세팅)
        self.quiz_type_var = tk.StringVar(value="mixed")      
        quiz_type_frame = tk.Frame(self.window)
        quiz_type_frame.grid(row=3,column=0)
        korean_option_rb = ttk.Radiobutton(quiz_type_frame,
                                            text='한글제시',
                                            value='korean',
                                            variable=self.quiz_type_var)                     
        english_option_rb = ttk.Radiobutton(quiz_type_frame,
                                             text='영어제시',
                                             value='english',
                                             variable=self.quiz_type_var)
        mixed_option_rb = ttk.Radiobutton(quiz_type_frame,
                                             text='섞어서제시',
                                             value='mixed',
                                             variable=self.quiz_type_var)
        korean_option_rb.grid(row=0,column=0) 
        english_option_rb.grid(row=0,column=1) 
        mixed_option_rb.grid(row=0,column=2) 

        #문항순서 섞기 체크박스
        self.shuffle_var = tk.BooleanVar()
        shuffle_checkbutton = ttk.Checkbutton(self.window,
                                               text='문항 순서 섞기',
                                               variable=self.shuffle_var)
        shuffle_checkbutton.grid(row=4,column=0) 




#실제로 프로그램을 실행하는 코드                                     
if __name__ == "__main__":
    app = QuizApp()
    app.window.mainloop()


사용 방법

출제 파일 준비

출제할 어휘는 엑셀에 저장해두어야 한다. 각 범위는 시트로 구분한다. 각 시트 속에는 순번, 영어, 한글 칼럼을 1행에 두고 그 아래 내용을 작성해두어야 한다.

영어 시험 출제용 파일

파일 불러오기

파일을 불러오면 아래 사진과 같이 시트 이름이 나열된다. 시트 이름 옆 칸에 출제를 원하는 만큼 숫자를 기입한다. 숫자를 자동으로 계산해서 퀴즈 생성하기 아래 칸에 결과가 반영된다. 예시 사진에는 18 문제가 생성될 것이라고 나온다. 시험 유형(한글, 영어, 섞어)을 정하고 순서를 섞을 것인지 정한 후 퀴즈 생성하기를 누르면 문제가 생성된다.

영어 퀴즈 생성기 실행 화면
실행 초기 화면
영어 퀴즈 생성기 파일 업로드 후 화면
파일 불러오기 후 시트별 출제문항 기입

퀴즈 생성하기

프로그램 화면에 생성된 문제를 미리 볼 수 있는 창이 나타나고 엑셀파일을 열 것인지 묻는 팝업이 나온다. 이미 클립보드에는 문제가 복사된 상태이기 때문에 아니요를 누르고 다른 곳에 붙여 넣을 수 있다. 를 누르면 방금 생성한 문제 파일이 열린다.

영어퀴즈 생성기 문제 생성 후
퀴즈 생성 후 화면
영어퀴즈 생성기로 만들어진 엑셀파
생성된 엑셀 파일

이렇게 생성된 시험지를 원노트에 붙여 넣고 배부하면 시험을 치를 준비가 완료된다. 원노트를 사용하지 않아도, 표 형식으로 된 시험지 양식에 바로 붙여 넣고 정답을 지워 사용할 수 있다.

원노트에 대해 알고 싶다면 원노트로 종이 없는 수업 만들기 참고

원노트에 첨부한 영어퀴즈
실제 학생에게 나눠준 시험지. 오른쪽 답안은 학생이 작성한 것이다.

마치며(다운로드 링크 포함)

영어 단어시험을 위해 만들었지만 다른 교과 퀴즈에도 충분히 사용이 가능하다. 지금 양식 파일은 순번, 영어, 한글로 되어있다. 과목에 따라 “순번, 용어, 정의” 또는 “순번, 한자, 독음”으로 양식을 만들면 개념 암기, 한자 암기용으로도 사용 할 수 있겠다.

챗지피티, 바드 등 여러 LLM의 등장으로 누구나 프로그래머가 될 수 있는 시대가 도래했다. 다수를 위한 서비스를 개발하기엔 물론 힘이 들겠지만, 나만의 문제점을 해결하기 위한 프로그램을 개발하기엔 충분하다. 정말 똑똑하지만 말귀를 잘 못 알아먹는 프로그래머가 늘 옆에서 도와주니 얼마나 좋은가. 여러분도 LLM을 활용하여 자신만의 프로그램을 만들어보길 추천한다.

위 링크 속에 지금 내가 사용하는 시험 출제 파일과 프로그램 파일이 있다. 프로그램은 압축되어있으니 압축을 풀고 exe파일을 실행하면 된다. 악성코드는 물론 없지만! 걱정이 된다면 위 코드를 활용해 직접 만들어 볼 수 있다.