Bjg6742635 commited on
Commit
61ee04b
·
1 Parent(s): 0d50e51

Initial commit with app files

Browse files
Files changed (4) hide show
  1. Dockerfile +17 -10
  2. README.md +41 -12
  3. app.py +167 -0
  4. requirements.txt +11 -2
Dockerfile CHANGED
@@ -1,20 +1,27 @@
1
- FROM python:3.13.5-slim
2
 
3
  WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
- && rm -rf /var/lib/apt/lists/*
10
 
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
13
 
14
- RUN pip3 install -r requirements.txt
 
 
 
 
 
 
 
 
 
 
15
 
16
  EXPOSE 8501
17
 
18
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
 
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
+ # Сначала копируем requirements.txt, чтобы использовать кэш
6
+ COPY requirements.txt .
 
 
 
7
 
8
+ # Устанавливаем зависимости
9
+ RUN pip3 install --no-cache-dir -r requirements.txt
10
 
11
+ # === НОВЫЙ БЛОК: Загрузка моделей при сборке ===
12
+ # Загрузка модели и токенизатора transformers
13
+ RUN python -c "import spacy; spacy.cli.download('ru_core_news_lg')"
14
+ RUN python -c "import nltk; nltk.download('punkt_tab', download_dir='/usr/local/share/nltk_data')"
15
+ RUN python -c "import nltk; nltk.download('stopwords')"
16
+
17
+ # --- НОВАЯ СТРОКА: Загрузка модели spaCy ---
18
+ # Убедитесь, что `ru_core_news_lg` доступна в образе при сборке
19
+
20
+ # Копируем остальные файлы и делаем новую загрузку
21
+ COPY . .
22
 
23
  EXPOSE 8501
24
 
25
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
26
 
27
+ ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,19 +1,48 @@
1
  ---
2
- title: History App
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
  sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
  pinned: false
11
- short_description: Streamlit template space
12
  ---
13
 
14
- # Welcome to Streamlit!
15
 
16
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
19
- forums](https://discuss.streamlit.io).
 
1
  ---
2
+ title: ИИ-ассистент по истории
3
+ emoji: 🤖
4
+ colorFrom: purple
5
+ colorTo: blue
6
  sdk: docker
 
 
 
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
+ # 🤖 ИИ-ассистент по истории
12
 
13
+ Это интерактивное веб-приложение, разработанное с использованием [Streamlit](https://streamlit.io/), которое отвечает на вопросы по истории на русском языке. Приложение использует предобученную модель вопрос-ответа, а также методы поиска похожих вопросов/контекста для генерации ответов.
14
+
15
+ ## 📌 Функциональность
16
+
17
+ - **Вопрос-ответ по истории**: Пользователь вводит вопрос по истории, и приложение пытается найти на него ответ, используя знания из датасета `Romyx/ru_QA_school_history`.
18
+ - **Предобработка текста**: Вопросы проходят очистку, токенизацию, лемматизацию (с использованием `spaCy`) и удаление стоп-слов (`NLTK`).
19
+ - **Поиск по схожести**: Используется TF-IDF векторайзер и косинусное сходство для поиска наиболее релевантного контекста в датасете.
20
+ - **Генерация ответа**: Найденный контекст и вопрос пользователя подаются в предобученную модель вопрос-ответа (["AlexKay/xlm-roberta-large-qa-multilingual-finedtuned-ru"](https://huggingface.co/AlexKay/xlm-roberta-large-qa-multilingual-finedtuned-ru)) для извлечения ответа.
21
+
22
+ ## 🛠️ Технологии и инструменты
23
+
24
+ - **Python**
25
+ - **Streamlit**: для создания веб-интерфейса.
26
+ - **Transformers (Hugging Face)**: для загрузки и использования предобученной модели.
27
+ - **Datasets (Hugging Face)**: для загрузки датасета вопросов-ответов.
28
+ - **spaCy**: для лемматизации русского текста (`ru_core_news_lg`).
29
+ - **NLTK**: для токенизации и стоп-слов.
30
+ - **Scikit-learn**: для вычисления TF-IDF и косинусного сходства.
31
+ - **PyTorch**: бэкенд для моделей `transformers`.
32
+ - **BeautifulSoup**: для очистки HTML-тегов.
33
+ - **Docker**: для контейнеризации приложения.
34
+ - **Hugging Face Spaces**: для хостинга приложения.
35
+
36
+ ## 🚀 Как использовать
37
+
38
+ 1. Введите ваш вопрос по истории в поле ввода.
39
+ 2. Нажмите кнопку **"Получить ответ"**.
40
+ 3. Приложение обработает вопрос и покажет найденный ответ или сообщение о том, что ответ не найден.
41
+
42
+ ## 📁 Структура репозитория
43
+
44
+ - `app.py`: Основной скрипт Streamlit-приложения.
45
+ - `Dockerfile`: Инструкции для сборки Docker-образа.
46
+ - `requirements.txt`: Список зависимостей Python.
47
+ --
48
 
 
 
app.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import numpy as np
6
+ from datasets import load_dataset, concatenate_datasets
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+ import torch
10
+ from transformers import AutoTokenizer, AutoModelForQuestionAnswering
11
+ import spacy
12
+ from nltk.corpus import stopwords
13
+ from nltk.tokenize import word_tokenize
14
+ import re
15
+ from bs4 import BeautifulSoup
16
+
17
+ # === Загрузка и подготовка данных ===
18
+
19
+ @st.cache_resource
20
+ def load_data():
21
+ # Загрузка датасета
22
+ data = load_dataset('Romyx/ru_QA_school_history', split='train')
23
+ df = pd.DataFrame(data)
24
+ df['Pt_question'] = df['question'].apply(preprocess_text)
25
+ df['Pt_answer'] = df['answer'].apply(preprocess_text)
26
+ return df
27
+
28
+ @st.cache_resource
29
+ def load_model_and_tokenizer():
30
+ # Загрузка предобученной модели вопрос-ответа (например, SberQuad)
31
+ model_name = "AlexKay/xlm-roberta-large-qa-multilingual-finedtuned-ru" #"AlexKay/xlm-roberta-large-qa-multilingual-finedtuned-ru"
32
+ # замените на нужную модель, например, "bert-base-uncased"
33
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
34
+ model = AutoModelForQuestionAnswering.from_pretrained(model_name)
35
+ return tokenizer, model
36
+
37
+ @st.cache_resource
38
+ def build_vectorizer(_df):
39
+ combined_texts = _df['Pt_question'].tolist() + _df['Pt_answer'].tolist()
40
+ vectorizer = TfidfVectorizer()
41
+ tfidf_matrix = vectorizer.fit_transform(combined_texts)
42
+ return vectorizer, tfidf_matrix
43
+
44
+ # === Предобработка текста ===
45
+
46
+ # Загрузка Spacy модели
47
+ nlp = spacy.load('ru_core_news_lg') #'ru_core_news_lg'
48
+ stop_words = set(stopwords.words('russian'))
49
+
50
+ cache_dict = {}
51
+
52
+ def get_norm_form(word):
53
+ if word in cache_dict:
54
+ return cache_dict[word]
55
+ norm_form = nlp(word)[0].lemma_
56
+ cache_dict[word] = norm_form
57
+ return norm_form
58
+
59
+ def remove_html_tags(text):
60
+ soup = BeautifulSoup(text, 'html.parser')
61
+ return soup.text
62
+
63
+ def preprocess_text(text):
64
+ if pd.isna(text) or text is None:
65
+ return ""
66
+ text = remove_html_tags(text)
67
+ text = text.lower()
68
+
69
+ # Обработка знаков препинания
70
+ text = re.sub(r'([^\w\s-]|_)', r' \1 ', text)
71
+ text = re.sub(r'\s+', ' ', text)
72
+ text = re.sub(r'(\w+)-(\w+)', r'\1 \2', text)
73
+ text = re.sub(r'(\d+)(г|кг|см|м|мм|л|мл)', r'\1 \2', text)
74
+
75
+ # Удаление всего, кроме букв, цифр и пробелов
76
+ text = re.sub(r'[^\w\s]', '', text)
77
+
78
+ tokens = word_tokenize(text)
79
+ tokens = [token for token in tokens if token not in stop_words]
80
+ tokens = [get_norm_form(token) for token in tokens]
81
+
82
+ words_to_remove = {"ответ", "new"}
83
+ tokens = [token for token in tokens if token not in words_to_remove]
84
+
85
+ return ' '.join(tokens)
86
+
87
+
88
+ # === Основная функция получения ответа ===
89
+ def get_answer_from_qa_model(user_question, df, vectorizer, tfidf_matrix, model, tokenizer):
90
+ processed = preprocess_text(user_question)
91
+ user_vec = vectorizer.transform([processed])
92
+
93
+ similarities = cosine_similarity(user_vec, tfidf_matrix).flatten()
94
+
95
+ # Проверка, что similarities не пустой
96
+ if len(similarities) == 0:
97
+ return "Тема не входит в программу этих классов."
98
+
99
+ best_match_idx = similarities.argmax()
100
+ best_score = similarities[best_match_idx]
101
+
102
+ if best_score > 0.1:
103
+ # Проверка, что индекс не выходит за границы
104
+ if best_match_idx >= len(df):
105
+ return "Тема не входит в программу этих классов."
106
+
107
+ context = df.iloc[best_match_idx]['answer']
108
+ question = user_question
109
+
110
+ inputs = tokenizer(question, context, return_tensors="pt", truncation=True, padding=True)
111
+
112
+ with torch.no_grad():
113
+ outputs = model(**inputs)
114
+
115
+ start_scores = outputs.start_logits
116
+ end_scores = outputs.end_logits
117
+
118
+ # Проверка на корректность размера логитов
119
+ if len(start_scores.shape) == 2:
120
+ start_idx = torch.argmax(start_scores, dim=1)[0].item()
121
+ end_idx = torch.argmax(end_scores, dim=1)[0].item()
122
+ else:
123
+ start_idx = torch.argmax(start_scores).item()
124
+ end_idx = torch.argmax(end_scores).item()
125
+
126
+ # Проверка, что индексы не выходят за пределы
127
+ seq_len = inputs['input_ids'].shape[1]
128
+ if start_idx >= seq_len or end_idx >= seq_len or start_idx > end_idx:
129
+ return "Ответ не найден."
130
+
131
+ answer = tokenizer.decode(inputs['input_ids'][0][start_idx:end_idx+1], skip_special_tokens=True)
132
+ else:
133
+ answer = "Извините, я не понимаю вопрос."
134
+
135
+ return answer
136
+
137
+
138
+ # === Интерфейс Streamlit ===
139
+
140
+ def main():
141
+ st.title("🤖 ИИ-ассистент по истории (на основе вопрос-ответа)")
142
+
143
+ st.write("Задайте вопрос, и я постараюсь найти на него ответ из базы.")
144
+
145
+ # Загрузка данных и модели
146
+ df = load_data()
147
+ tokenizer, model = load_model_and_tokenizer()
148
+ vectorizer, tfidf_matrix = build_vectorizer(df)
149
+
150
+ # Поле ввода вопроса
151
+ user_input = st.text_input("Введите ваш вопрос:")
152
+
153
+ if st.button("Получить ответ"):
154
+ if user_input.strip():
155
+ with st.spinner("Ищем ответ..."):
156
+ response = get_answer_from_qa_model(
157
+ user_input, df, vectorizer, tfidf_matrix, model, tokenizer
158
+ )
159
+ st.success("Ответ:")
160
+ st.write(response)
161
+ else:
162
+ st.warning("Пожалуйста, введите вопрос.")
163
+
164
+ if __name__ == "__main__":
165
+ main()
166
+
167
+
requirements.txt CHANGED
@@ -1,3 +1,12 @@
1
- altair
 
 
2
  pandas
3
- streamlit
 
 
 
 
 
 
 
 
1
+
2
+ streamlit
3
+ openai
4
  pandas
5
+ numpy
6
+ datasets
7
+ scikit-learn
8
+ torch --index-url https://download.pytorch.org/whl/cpu
9
+ transformers
10
+ spacy
11
+ nltk
12
+ beautifulsoup4