final
Assessment Question Difficulty Classifier 🧠📊
Описание ✨
Проект для автоматической классификации сложности вопросов тестов/экзаменов с использованием машинного обучения.
Модель на основе текста вопроса, времени ответа и процента правильных ответов определяет уровень сложности: (легкий),
(средний) или (сложный).
🎯 Цель проекта
- Классифицировать вопросы на три уровня сложности:
,easy,medium.hard - Сравнить эффективность разных моделей ML:
- Random Forest 🌳
- Support Vector Machine (SVM) ⚡
- Naive Bayes 📦
- Визуализировать метрики моделей и важность признаков.
- Сохранить метрики и модели для дальнейшего использования.
Зачем это нужно? 🤔
-
Автоматизация оценки сложности вопросов
-
Оптимизация создания сбалансированных тестов
-
Анализ эффективности образовательных материалов
-
Сокращение времени ручной оценки на 80%
Установка ⚙️
Предварительные требования
-
Python 3.8+ 🐍
-
pip (менеджер пакетов)
Настройка окружения
bash
1. Клонируйте репозиторий
git clone <repo-url>
cd question-difficulty-classifier
2. Создайте виртуальное окружение
python -m venv venv
3. Активируйте окружение
На Windows:
venv\Scripts\activate
На macOS/Linux:
source venv/bin/activate
4. Установите зависимости
pip install -r requirements.txt
Использование 🚀
Быстрый старт
- Подготовьте CSV файл с вопросами:
csv
question_text,avg_time_sec,correct_rate,difficulty
"What is 2+2?",15,0.95,easy
"Explain quantum entanglement",120,0.35,hard
"Define photosynthesis",45,0.70,medium
- Запустите обучение модели:
bash
python src/main.py
При запуске скрипт:
-
Проверяет наличие файла questions.csv ✅
-
Извлекает признаки из текста вопроса 📝
-
Делит данные на train/test наборы 🔀
-
Обучает три модели: Random Forest, SVM, Naive Bayes 🤖
-
Выводит метрики и сохраняет их в reports/metrics.json 📊
-
Строит графики и сохраняет их в папку images:
-
Confusion Matrix
-
Class Distribution
-
Feature Importance (для Random Forest)
-
Сравнение моделей по точности
-
-
Все модели сохраняются в папку models/ с уникальными именами:
-
RandomForest_model_20251229_153045.pkl
-
SVM_model_20251229_153045.pkl
-
NaiveBayes_model_20251229_153045.pkl
-
Пример работы кода
python src/main.py
🖥️ Вывод в консоли
Данные загружены успешно. Размер: (43, 4)
Колонки: ['question_text', 'avg_time_sec', 'correct_rate', 'difficulty']
=== Training RandomForest ===
RandomForest CV Accuracy: 0.98 ± 0.02
RandomForest model saved as RandomForest_model_20240512_153210.pkl
=== Training SVM ===
SVM CV Accuracy: 0.96 ± 0.03
SVM model saved as SVM_model_20240512_153215.pkl
=== Training NaiveBayes ===
NaiveBayes CV Accuracy: 0.89 ± 0.04
NaiveBayes model saved as NaiveBayes_model_20240512_153218.pkl
All metrics saved to metrics.json
Пример результатов
reports/metrics.json:
{
"RandomForest": {
"confusion_matrix": [
[
2,
0,
0
],
[
0,
3,
0
],
[
1,
0,
3
]
],
"precision_macro": 0.8888888888888888,
"recall_macro": 0.9166666666666666,
"classification_report": " precision recall f1-score support\n\n 0 0.67 1.00 0.80 2\n 1 1.00 1.00 1.00 3\n 2 1.00 0.75 0.86 4\n\n accuracy 0.89 9\n macro avg 0.89 0.92 0.89 9\nweighted avg 0.93 0.89 0.89 9\n",
"cv_accuracy_mean": 1.0,
"cv_accuracy_std": 0.0
},
"SVM": {
"confusion_matrix": [
[
2,
0,
0
],
[
0,
3,
0
],
[
0,
0,
4
]
],
"precision_macro": 1.0,
"recall_macro": 1.0,
"classification_report": " precision recall f1-score support\n\n 0 1.00 1.00 1.00 2\n 1 1.00 1.00 1.00 3\n 2 1.00 1.00 1.00 4\n\n accuracy 1.00 9\n macro avg 1.00 1.00 1.00 9\nweighted avg 1.00 1.00 1.00 9\n",
"cv_accuracy_mean": 1.0,
"cv_accuracy_std": 0.0
},
"NaiveBayes": {
"confusion_matrix": [
[
2,
0,
0
],
[
0,
3,
0
],
[
1,
0,
3
]
],
"precision_macro": 0.8888888888888888,
"recall_macro": 0.9166666666666666,
"classification_report": " precision recall f1-score support\n\n 0 0.67 1.00 0.80 2\n 1 1.00 1.00 1.00 3\n 2 1.00 0.75 0.86 4\n\n accuracy 0.89 9\n macro avg 0.89 0.92 0.89 9\nweighted avg 0.93 0.89 0.89 9\n",
"cv_accuracy_mean": 0.9777777777777779,
"cv_accuracy_std": 0.04444444444444447
}
}
models/NaiveBayes_model_20251229_005744.pkl:
Pickled Data
0: \x80 PROTO 4
2: \x95 FRAME 1178
11: \x8c SHORT_BINUNICODE 'sklearn.naive_bayes'
32: \x94 MEMOIZE (as 0)
33: \x8c SHORT_BINUNICODE 'GaussianNB'
45: \x94 MEMOIZE (as 1)
46: \x93 STACK_GLOBAL
47: \x94 MEMOIZE (as 2)
48: ) EMPTY_TUPLE
49: \x81 NEWOBJ
50: \x94 MEMOIZE (as 3)
51: } EMPTY_DICT
52: \x94 MEMOIZE (as 4)
53: ( MARK
54: \x8c SHORT_BINUNICODE 'priors'
62: \x94 MEMOIZE (as 5)
63: N NONE
64: \x8c SHORT_BINUNICODE 'var_smoothing'
79: \x94 MEMOIZE (as 6)
80: G BINFLOAT 1e-09
89: \x8c SHORT_BINUNICODE 'classes_'
99: \x94 MEMOIZE (as 7)
100: \x8c SHORT_BINUNICODE 'numpy._core.multiarray'
124: \x94 MEMOIZE (as 8)
125: \x8c SHORT_BINUNICODE '_reconstruct'
139: \x94 MEMOIZE (as 9)
140: \x93 STACK_GLOBAL
141: \x94 MEMOIZE (as 10)
142: \x8c SHORT_BINUNICODE 'numpy'
149: \x94 MEMOIZE (as 11)
150: \x8c SHORT_BINUNICODE 'ndarray'
159: \x94 MEMOIZE (as 12)
160: \x93 STACK_GLOBAL
161: \x94 MEMOIZE (as 13)
162: K BININT1 0
164: \x85 TUPLE1
165: \x94 MEMOIZE (as 14)
166: C SHORT_BINBYTES b'b'
169: \x94 MEMOIZE (as 15)
170: \x87 TUPLE3
171: \x94 MEMOIZE (as 16)
172: R REDUCE
173: \x94 MEMOIZE (as 17)
174: ( MARK
175: K BININT1 1
177: K BININT1 3
179: \x85 TUPLE1
180: \x94 MEMOIZE (as 18)
181: h BINGET 11
183: \x8c SHORT_BINUNICODE 'dtype'
190: \x94 MEMOIZE (as 19)
191: \x93 STACK_GLOBAL
192: \x94 MEMOIZE (as 20)
193: \x8c SHORT_BINUNICODE 'i8'
197: \x94 MEMOIZE (as 21)
198: \x89 NEWFALSE
199: \x88 NEWTRUE
200: \x87 TUPLE3
201: \x94 MEMOIZE (as 22)
202: R REDUCE
203: \x94 MEMOIZE (as 23)
204: ( MARK
205: K BININT1 3
207: \x8c SHORT_BINUNICODE '<'
210: \x94 MEMOIZE (as 24)
211: N NONE
212: N NONE
213: N NONE
214: J BININT -1
219: J BININT -1
224: K BININT1 0
226: t TUPLE (MARK at 204)
227: \x94 MEMOIZE (as 25)
228: b BUILD
229: \x89 NEWFALSE
230: C SHORT_BINBYTES b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
256: \x94 MEMOIZE (as 26)
257: t TUPLE (MARK at 174)
258: \x94 MEMOIZE (as 27)
259: b BUILD
260: \x8c SHORT_BINUNICODE 'feature_names_in_'
279: \x94 MEMOIZE (as 28)
280: h BINGET 10
282: h BINGET 13
284: K BININT1 0
286: \x85 TUPLE1
287: \x94 MEMOIZE (as 29)
288: h BINGET 15
290: \x87 TUPLE3
291: \x94 MEMOIZE (as 30)
292: R REDUCE
293: \x94 MEMOIZE (as 31)
294: ( MARK
295: K BININT1 1
297: K BININT1 8
299: \x85 TUPLE1
300: \x94 MEMOIZE (as 32)
301: h BINGET 20
303: \x8c SHORT_BINUNICODE 'O8'
307: \x94 MEMOIZE (as 33)
308: \x89 NEWFALSE
309: \x88 NEWTRUE
310: \x87 TUPLE3
311: \x94 MEMOIZE (as 34)
312: R REDUCE
313: \x94 MEMOIZE (as 35)
314: ( MARK
315: K BININT1 3
317: \x8c SHORT_BINUNICODE '|'
320: \x94 MEMOIZE (as 36)
321: N NONE
322: N NONE
323: N NONE
324: J BININT -1
329: J BININT -1
334: K BININT1 63
336: t TUPLE (MARK at 314)
337: \x94 MEMOIZE (as 37)
338: b BUILD
339: \x89 NEWFALSE
340: ] EMPTY_LIST
341: \x94 MEMOIZE (as 38)
342: ( MARK
343: \x8c SHORT_BINUNICODE 'text_length'
356: \x94 MEMOIZE (as 39)
357: \x8c SHORT_BINUNICODE 'word_count'
369: \x94 MEMOIZE (as 40)
370: \x8c SHORT_BINUNICODE 'flesch_kincaid'
386: \x94 MEMOIZE (as 41)
387: \x8c SHORT_BINUNICODE 'punct_count'
400: \x94 MEMOIZE (as 42)
401: \x8c SHORT_BINUNICODE 'digit_count'
414: \x94 MEMOIZE (as 43)
415: \x8c SHORT_BINUNICODE 'upper_count'
428: \x94 MEMOIZE (as 44)
429: \x8c SHORT_BINUNICODE 'avg_time_sec'
443: \x94 MEMOIZE (as 45)
444: \x8c SHORT_BINUNICODE 'correct_rate'
458: \x94 MEMOIZE (as 46)
459: e APPENDS (MARK at 342)
460: t TUPLE (MARK at 294)
461: \x94 MEMOIZE (as 47)
462: b BUILD
463: \x8c SHORT_BINUNICODE 'n_features_in_'
479: \x94 MEMOIZE (as 48)
480: K BININT1 8
482: \x8c SHORT_BINUNICODE 'epsilon_'
492: \x94 MEMOIZE (as 49)
493: h BINGET 8
495: \x8c SHORT_BINUNICODE 'scalar'
503: \x94 MEMOIZE (as 50)
504: \x93 STACK_GLOBAL
505: \x94 MEMOIZE (as 51)
506: h BINGET 20
508: \x8c SHORT_BINUNICODE 'f8'
512: \x94 MEMOIZE (as 52)
513: \x89 NEWFALSE
514: \x88 NEWTRUE
515: \x87 TUPLE3
516: \x94 MEMOIZE (as 53)
517: R REDUCE
518: \x94 MEMOIZE (as 54)
519: ( MARK
520: K BININT1 3
522: h BINGET 24
524: N NONE
525: N NONE
526: N NONE
527: J BININT -1
532: J BININT -1
537: K BININT1 0
539: t TUPLE (MARK at 519)
540: \x94 MEMOIZE (as 55)
541: b BUILD
542: C SHORT_BINBYTES b'\x06|\xbbMp\xd7\xd2>'
552: \x94 MEMOIZE (as 56)
553: \x86 TUPLE2
554: \x94 MEMOIZE (as 57)
555: R REDUCE
556: \x94 MEMOIZE (as 58)
557: \x8c SHORT_BINUNICODE 'theta_'
565: \x94 MEMOIZE (as 59)
566: h BINGET 10
568: h BINGET 13
570: K BININT1 0
572: \x85 TUPLE1
573: \x94 MEMOIZE (as 60)
574: h BINGET 15
576: \x87 TUPLE3
577: \x94 MEMOIZE (as 61)
578: R REDUCE
579: \x94 MEMOIZE (as 62)
580: ( MARK
581: K BININT1 1
583: K BININT1 3
585: K BININT1 8
587: \x86 TUPLE2
588: \x94 MEMOIZE (as 63)
589: h BINGET 54
591: \x89 NEWFALSE
592: C SHORT_BINBYTES b"\x00\x00\x00\x00\x000A@\x00\x00\x00\x00\x00\x80\x1b@\xa8\x9ePpk\xbd\x08@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xd0?\x00\x00\x00\x00\x00\x00\x02@\x00\x00\x00\x00\x00\x80&@\xeaQ\xb8\x1e\x85\xeb\xec?\x8a\x9d\xd8\x89\x9d\xd8D@;\xb1\x13;\xb1\x13\x17@\xf4\xb4\xe8$\xe8\xdb'@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x8a\x9d\xd8\x89\x9d\xd8\xf9?'vb'v\x02e@\xd7\xa3p=\n\xd7\xd3?O\xec\xc4N\xec\xc4A@\xec\xc4N\xec\xc4N\x18@\xcfV\x06\xff\x899\x19@\x00\x00\x00\x00\x00\x00\xf0?\x14;\xb1\x13;\xb1\xb3?O\xec\xc4N\xec\xc4\xfe?O\xec\xc4N\xec\xc4J@\xdc\xc8\x8d\xdc\xc8\x8d\xe4?"
786: \x94 MEMOIZE (as 64)
787: t TUPLE (MARK at 580)
788: \x94 MEMOIZE (as 65)
789: b BUILD
790: \x8c SHORT_BINUNICODE 'var_'
796: \x94 MEMOIZE (as 66)
797: h BINGET 10
799: h BINGET 13
801: K BININT1 0
803: \x85 TUPLE1
804: \x94 MEMOIZE (as 67)
805: h BINGET 15
807: \x87 TUPLE3
808: \x94 MEMOIZE (as 68)
809: R REDUCE
810: \x94 MEMOIZE (as 69)
811: ( MARK
812: K BININT1 1
814: K BININT1 3
816: K BININT1 8
818: \x86 TUPLE2
819: \x94 MEMOIZE (as 70)
820: h BINGET 54
822: \x89 NEWFALSE
823: C SHORT_BINBYTES b'Np\xd7\x12\x00/[@o\x13\xdc\xb5\x04\xc0\xfd?JL\xc5\xf4\xd7\xe5"@\x06|\xbbMp\xd7\xd2>\xbbMp\xd7\x12\x00\xdc?o\x13\xdc\xb5\x04\x00\xfb?n\x82\xbb\x96\x00\xe0&@}UV\x8buqY?\xed\xaf\xbd;\xdd\xbe\\@\xd1Gm\'T\xba\r@/\xdf\xa5\xee4\x10@@\x06|\xbbMp\xd7\xd2>\x06|\xbbMp\xd7\xd2>\x90\xed\xcc\xafh\x8e\xf2?\x11uYy\xfbJ{@\x0e\xdd\xb6\xddq\nr?\xec\xe1\x04%\xd4TT@;\xfe9\xa8e\xc0\xf9?\x0e\xf2~{\xd7\xa0$@\x06|\xbbMp\xd7\xd2>\x17F\xc7\xf9\xbc-\xb2?Eu\x7f\xfb\xa8B\x04@M\t{\x87\xfbJT@\x97^\xc5\xce\xee\xd8\\?'
1017: \x94 MEMOIZE (as 71)
1018: t TUPLE (MARK at 811)
1019: \x94 MEMOIZE (as 72)
1020: b BUILD
1021: \x8c SHORT_BINUNICODE 'class_count_'
1035: \x94 MEMOIZE (as 73)
1036: h BINGET 10
1038: h BINGET 13
1040: K BININT1 0
1042: \x85 TUPLE1
1043: \x94 MEMOIZE (as 74)
1044: h BINGET 15
1046: \x87 TUPLE3
1047: \x94 MEMOIZE (as 75)
1048: R REDUCE
1049: \x94 MEMOIZE (as 76)
1050: ( MARK
1051: K BININT1 1
1053: K BININT1 3
1055: \x85 TUPLE1
1056: \x94 MEMOIZE (as 77)
1057: h BINGET 54
1059: \x89 NEWFALSE
1060: C SHORT_BINBYTES b'\x00\x00\x00\x00\x00\x00 @\x00\x00\x00\x00\x00\x00*@\x00\x00\x00\x00\x00\x00*@'
1086: \x94 MEMOIZE (as 78)
1087: t TUPLE (MARK at 1050)
1088: \x94 MEMOIZE (as 79)
1089: b BUILD
1090: \x8c SHORT_BINUNICODE 'class_prior_'
1104: \x94 MEMOIZE (as 80)
1105: h BINGET 10
1107: h BINGET 13
1109: K BININT1 0
1111: \x85 TUPLE1
1112: \x94 MEMOIZE (as 81)
1113: h BINGET 15
1115: \x87 TUPLE3
1116: \x94 MEMOIZE (as 82)
1117: R REDUCE
1118: \x94 MEMOIZE (as 83)
1119: ( MARK
1120: K BININT1 1
1122: K BININT1 3
1124: \x85 TUPLE1
1125: \x94 MEMOIZE (as 84)
1126: h BINGET 54
1128: \x89 NEWFALSE
1129: C SHORT_BINBYTES b'\x1e\x1e\x1e\x1e\x1e\x1e\xce?xxxxxx\xd8?xxxxxx\xd8?'
1155: \x94 MEMOIZE (as 85)
1156: t TUPLE (MARK at 1119)
1157: \x94 MEMOIZE (as 86)
1158: b BUILD
1159: \x8c SHORT_BINUNICODE '_sklearn_version'
1177: \x94 MEMOIZE (as 87)
1178: \x8c SHORT_BINUNICODE '1.8.0'
1185: \x94 MEMOIZE (as 88)
1186: u SETITEMS (MARK at 53)
1187: b BUILD
1188: . STOP
highest protocol among opcodes = 4
Load Full Readable Pickle
Структура проекта 📁
final/
├─ .github/
│ └─ workflows/
│ ├─ tests.yml
├─ .gitverse/
│ └─ workflows/
│ └─ gitverse-ci.yaml
├─ data/
│ └─ questions.csv
├─ docs/
│ ├─ ANSWER.md
│ ├─ evaluation-matrix.md
│ ├─ llm-checker-guide.md
│ ├─ project-ideas.md
│ ├─ quick-start.md
│ ├─ rubric-15-points.md
│ └─ student-guide.md
├─ images/
│ ├─ class_distribution_NaiveBayes.png
│ ├─ class_distribution_RandomForest.png
│ ├─ class_distribution_SVM.png
│ ├─ confusion_matrix_NaiveBayes.png
│ ├─ confusion_matrix_RandomForest.png
│ ├─ confusion_matrix_SVM.png
│ ├─ feature_importance.png
│ └─ model_comparison.png
├─ models/
│ ├─ NaiveBayes_model_20251229_005744.pkl
│ ├─ RandomForest_model_20251229_005735.pkl
│ └─ SVM_model_20251229_005740.pkl
├─ reports/
│ └─ metrics.json
├─ src/
│ ├─ init.py
│ └─ main.py
├─ tests/
│ ├─ init.py
│ └─ test_my.py
├─ .gitignore
├─ README-package.md
├─ README.md
└─ requirements.txt
Требования 📦
-
Python 3.8+ 🐍
-
NumPy >= 1.21.0 - численные вычисления
-
Pandas >= 1.3.0 - обработка данных
-
Scikit-learn >= 1.0.0 - машинное обучение
-
Matplotlib >= 3.5.0 - визуализация
-
Seaborn >= 0.11.0 - статистические графики
-
TextStat >= 0.7.0 - анализ читабельности текста
Возможности модели 🔧
Извлекаемые признаки:
-
📏 Длина текста вопроса
-
📝 Количество слов
-
🎓 Уровень читабельности (Flesch-Kincaid)
-
🔢 Количество цифр и знаков препинания
-
⏱️ Среднее время ответа
-
✅ Процент правильных ответов
Используемые алгоритмы:
-
🌲 Random Forest - ансамблевый метод
-
🎯 SVM - метод опорных векторов
-
🧪 Naive Bayes - вероятностный классификатор
Тестирование 🧪
Запуск тестов
pytest tests/test_my.py
Входные данные 📋
CSV файл должен содержать следующие колонки:
| Колонка | Тип | Описание | Пример |
|---|---|---|---|
| string | Текст вопроса | "What is the capital of France?" |
| float | Среднее время ответа в секундах | 45.2 |
| float | Процент правильных ответов (0-1) | 0.75 |
| string | Уровень сложности | "easy"/"medium"/"hard" |
Визуализации 📊
Проект автоматически генерирует:
-
Матрицы ошибок для каждой модели
-
Распределение классов (фактические vs предсказанные)
-
Важность признаков для Random Forest
-
Сравнение моделей по точности
📈 Графики
— матрица ошибок для каждой моделиconfusion_matrix_<model>.png



— распределение фактических и предсказанных классовclass_distribution_<model>.png



— важность признаков для Random Forest 🌳feature_importance.png

— сравнение моделей по макро-прецизионной точности 📊model_comparison.png

Будущие улучшения 🚀
-
Добавление нейросетевых моделей (BERT)
-
Веб-интерфейс для удобства использования
-
API для интеграции с LMS системами
-
Анализ мультиязычных вопросов
-
Автоматическая генерация отчетов
Автор 💫
Бажеева Алисия Владимировна
студентка 1 курса ИРИТ-РТФ группы РИ-150942/1.