Card | Table | RUSMARC | |
Мищенко, К. А. Интерактивное клиент-серверное приложение для идентификации аккордов с использованием спектрального анализа звука: выпускная квалификационная работа по направлению подготовки 01.04.02 Прикладная математика и информатика. Направленность: Технологии проектирования системного и прикладного программного обеспечения [Электронный ресурс] / К. А. Мищенко; Уфимский университет науки и технологий, Стерлитамакский филиал, факультет математики и информационных технологий, кафедра математического моделирования ; научный руководитель А. А. Акимов. — Стерлитамак, 2025. — 81 с. — <URL:https://elib.bashedu.ru/dl/diplom/SF/2025/FMIT/MishchenkoKA_01.04.02_OZMPMI_mag_2025_VKR.pdf>.Record create date: 2/5/2025 Subject: Техника; магистратура; ВКР проектного типа; мобильное приложение UDC: 004.9 LBC: 32 Collections: Магистерские диссертации; Общая коллекция Allowed Actions: –
*^% Action 'Read' will be available if you login and work on the computer in the reading rooms of the Library
Group: Anonymous Network: Internet |
Document access rights
Network | User group | Action | ||||
---|---|---|---|---|---|---|
Library BashGU Local Network | Authenticated users |
![]() |
||||
Library BashGU Local Network | All | |||||
Internet | Authenticated users |
![]() |
||||
![]() |
Internet | All |
Table of Contents
- ДИПЛОМ.docx
- СОДЕРЖАНИЕ
- ВВЕДЕНИЕ
- ГЛАВА 1. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
- 1.1. Анализ предметной области
- 1.1.1. Обработка звуковых сигналов: подходы и их эволюция
- 1.1.2. Подход спектрального анализа и его применение в нейросетях
- 1.1.4. Анализ существующих методов и решений задачи классификации аккордов
- 1.2. Описание процессов создания нейросети для проектного решения
- 1.2.1. Выбор обучающего набора данных и модели нейросети
- 1.2.2. Реализация обучающего модуля нейросети
- 1.2.3. Логика работы основного модуля нейросети
- 1.3. Описание технологий проектного решения
- 1.3.1 Сервер на базе операционной системы Ubuntu Server
- 1.3.2. Веб-сервер NGINX
- 1.3.3. Docker как средство контейнеризации
- 1.3.4. Dockerized SMTP сервер Mailcow
- 1.3.5. Язык программирования Python
- 1.3.6. Python библиотека TensorFlow
- 1.3.7. Django
- 1.3.8. Librosa
- 1.3.9. Язык HMTL
- 1.3.10. Каскадная таблица стилей
- 1.3.11. JavaScript
- 1.3.12. Графический игровой движок Godot
- 1.1. Анализ предметной области
- ГЛАВА 2. ПРАКТИЧЕСКАЯ ЧАСТЬ
- 2.1. Настройка серверной инфраструктуры
- 2.2. Организация базы данных учетных записей пользователей
- 2.3. Реализация точек доступа к приложению
- 2.4. Функционал регистрации и авторизации пользователей
- 2.5. Реализация клиентской части проектного решения
- 14b4e9e8f35342056d7d03840cca5edd7d8a70cc9e48f5b4ccf1645d2e8403cd.pdf
- ДИПЛОМ.docx
- СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
- 14b4e9e8f35342056d7d03840cca5edd7d8a70cc9e48f5b4ccf1645d2e8403cd.pdf
- ДИПЛОМ.docx
- ПРИЛОЖЕНИЯ
- ПРИЛОЖЕНИЕ № 1. Keras модель нейросети
- from keras import layers, Model, Input
- from keras.regularizers import l2
- from keras.layers import LayerNormalization
- def model_Conv2D(n_classes, n_mels, frames):
- input_shape = (n_mels, frames, 1)
- inputs = Input(shape=input_shape)
- x = LayerNormalization(axis=1, name='batch_norm')(inputs)
- x = layers.Conv2D(8, kernel_size=(7, 7), activation='tanh', padding='same', name='conv2d_tanh')(x)
- x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_1')(x)
- x = layers.Conv2D(16, kernel_size=(5, 5), activation='relu', padding='same', name='conv2d_relu_1')(x)
- x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_2')(x)
- x = layers.Conv2D(16, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_2')(x)
- x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_3')(x)
- x = layers.Conv2D(32, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_3')(x)
- x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_4')(x)
- x = layers.Conv2D(32, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_4')(x)
- x = layers.Flatten(name='flatten')(x)
- x = layers.Dropout(rate=0.2, name='dropout')(x)
- x = layers.Dense(64, activation='relu', activity_regularizer=l2(0.001), name='dense')(x)
- outputs = layers.Dense(n_classes, activation='softmax', name='softmax')(x)
- model = Model(inputs=inputs, outputs=outputs, name='2d_convolution')
- model.compile(optimizer='adam',
- loss='categorical_crossentropy',
- metrics=['accuracy'])
- return model
- ПРИЛОЖЕНИЕ № 2. Обучающий модуль нейросети
- import utils, os
- import model as keras_model
- import matplotlib.pyplot as plt
- import numpy as np
- base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- resources_dir = os.path.join(base_dir, "app", "resources")
- dataset_dir = os.path.join(base_dir, "dataset")
- train_path = os.path.join(dataset_dir, "training")
- test_path = os.path.join(dataset_dir, "test")
- model_path = os.path.join(resources_dir, "trained_model.keras")
- meta_path = os.path.join(resources_dir, "meta.json")
- os.makedirs(resources_dir, exist_ok=True)
- sr = 16000
- dt = 1.0
- N_CLASSES = 8
- n_mels = 64
- hop_length = 256
- n_fft = 1024
- threshold = 0.01
- frames = (sr // hop_length) + 1
- classes = sorted(os.listdir(test_path))
- meta = {
- "sr": sr,
- "dt": dt,
- "n_classes": N_CLASSES,
- "n_mels": n_mels,
- "hop_length": hop_length,
- "n_fft": n_fft,
- "threshold": threshold,
- "frames": frames,
- "classes": classes
- }
- X_train, Y_train = utils.get_data(train_path, sr, dt, N_CLASSES, n_mels, hop_length, n_fft, threshold, frames)
- X_train.shape, Y_train.shape
- model = keras_model.model_Conv2D(N_CLASSES, n_mels, frames)
- batch_size = 32
- epochs = 30
- history = model.fit(X_train, Y_train, batch_size=batch_size, epochs=epochs, verbose=1, shuffle=True)
- model.save(model_path)
- utils.serialize_dict(meta, meta_path)
- ПРИЛОЖЕНИЕ № 3. Модуль прогнозирования
- from keras.saving import load_model
- from .scripts import utils
- import os, sys
- import numpy as np
- if getattr(sys, 'frozen', False):
- application_path = sys._MEIPASS
- else:
- application_path = os.path.dirname(os.path.abspath(__file__))
- model = None
- meta = None
- def predict_chord_from_audio(audio):
- mask = utils.envelope(audio, meta["sr"], meta["threshold"])
- y = audio[mask]
- if len(y) >= meta["sr"]:
- y = y[:meta["sr"]]
- else:
- y = np.pad(y, (0, meta["sr"] - len(y)), mode='constant')
- mel_spec = utils.get_melspectogram(y, meta["sr"], meta["n_fft"], meta["hop_length"], meta["n_mels"])
- mel_spec = mel_spec[np.newaxis, ...]
- return predict_chord_from_mel(mel_spec)
- def predict_chord_from_mel(mel_data):
- y_pred = model.predict(mel_data, verbose=0)
- predicted_label_idx = np.argmax(y_pred)
- chance = y_pred[0][predicted_label_idx]
- predicted_label = meta["classes"][predicted_label_idx]
- return predicted_label, chance
- def start():
- global model
- global meta
- model = load_model(os.path.join(application_path, "resources/trained_model.keras"))
- meta = utils.deserialize_dict(os.path.join(application_path, "resources/meta.json"))
- ПРИЛОЖЕНИЕ № 4. Конфигурационный файл NGINX
- events {}
- http {
- include mime.types;
- client_max_body_size 100M;
- # Сервер для chordfinder.ru
- server {
- server_name chordfinder.ru;
- location / {
- proxy_pass http://127.0.0.1:5000/;
- }
- location /static/ {
- root /var/www/chordfinder;
- }
- listen 443 ssl;
- ssl_certificate /etc/letsencrypt/live/chordfinder.ru/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/chordfinder.ru/privkey.pem;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
- }
- # Сервер для mail.chordfinder.ru
- server {
- server_name mail.chordfinder.ru;
- location / {
- proxy_pass https://127.0.0.1:8443; # Проксируем к Mailcow
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
- listen 443 ssl;
- ssl_certificate /etc/letsencrypt/live/mail.chordfinder.ru/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/mail.chordfinder.ru/privkey.pem;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
- }
- # HTTP редиректы на HTTPS
- server {
- listen 80;
- server_name chordfinder.ru mail.chordfinder.ru;
- return 301 https://$host$request_uri;
- }
- }
- ПРИЛОЖЕНИЕ № 5. Настройки подключения к SMTP
- # Emailing settings
- EMAIL_BACKEND = '' #ENV_OVERRIDE
- EMAIL_HOST = '' #ENV_OVERRIDE
- EMAIL_FROM = '' #ENV_OVERRIDE
- EMAIL_HOST_USER = '' #ENV_OVERRIDE
- EMAIL_HOST_PASSWORD = '' #ENV_OVERRIDE
- EMAIL_PORT = '' #ENV_OVERRIDE
- EMAIL_USE_TLS = '' #ENV_OVERRIDE
- ПРИЛОЖЕНИЕ № 6. Модель профиля пользователя Django
- from django.db import models
- from django.contrib.auth.models import User
- from django.db.models.signals import post_save
- from django.dispatch import receiver
- import secrets
- class Profile(models.Model):
- user = models.OneToOneField(User, on_delete=models.CASCADE)
- password_reset_token = models.CharField(max_length=256, default="")
- api_key = models.CharField(max_length=64, unique=True, default=secrets.token_urlsafe)
- def __str__(self):
- return self.user.username
- @receiver(post_save, sender=User)
- def create_user_profile(sender, instance, created, **kwargs):
- if created:
- Profile.objects.create(user=instance)
- @receiver(post_save, sender=User)
- def save_user_profile(sender, instance, **kwargs):
- instance.profile.save()
- ПРИЛОЖЕНИЕ № 7. Конфигурация маршрутов Django
- from django.contrib import admin
- from django.urls import path, include
- from django.urls import path
- from ai.app_views import activate_account, elements, homepage, profile, sign_up, sign_in, password_reset, predict, docs
- urlpatterns = [
- path('admin/', admin.site.urls),
- path('', homepage.homepage, name='homepage_url'),
- path('docs', docs.docs, name='docs_url'),
- path('api/predict', predict.predict, name='predict_url'),
- path('accounts/', include("django.contrib.auth.urls")),
- path('accounts/profile/', profile.profile, name='profile_url'),
- path('elements', elements.elements, name='elements_url'),
- path('sign_in', sign_in.sign_in, name='sign_in_url'),
- path('sign_up/', sign_up.sign_up, name='sign_up_url'),
- path('sign_up/success', sign_up.sign_up_success, name="sign_up_success_url"),
- path('activate_account/
/ ', activate_account.activate_account, name='activate_account_url'), - path("password_change", profile.password_change, name="password_change_url"),
- path("password_reset", password_reset.password_reset_request, name="password_reset_request_url"),
- path("password_reset/sent", password_reset.password_reset_request_sent, name="password_reset_request_success_url"),
- path('reset/
/ ', password_reset.password_reset_confirm, name='password_reset_confirm_url'), - ]
- ПРИЛОЖЕНИЕ № 8. Точка входа прогнозирования данных
- from librosa import resample
- from django.http import JsonResponse
- from ..neural_network.model import predict_chord_from_audio
- import json
- import numpy as np
- def predict(request):
- label = ""
- chance = ""
- if request.method != 'GET':
- return JsonResponse({'error': 'Only GET requests are allowed'}, status=405)
- try:
- data = json.loads(request.body.decode('utf-8'))
- audio = np.array(data["input"])
- resampled = resample(y=audio, orig_sr=44100, target_sr=16000)
- except json.JSONDecodeError:
- return JsonResponse({'error': 'Missing required parameter: input'}, status=400)
- label, chance = predict_chord_from_audio(resampled)
- return JsonResponse({'label': label, 'chance': str(chance)})
- ПРИЛОЖЕНИЕ № 9. Инициализация объекта нейросети
- from django.apps import AppConfig
- class AiConfig(AppConfig):
- default_auto_field = 'django.db.models.BigAutoField'
- name = 'ai'
- def ready(self):
- from ai.neural_network.model import start
- start()
- ПРИЛОЖЕНИЕ № 10. Реализация регистрации пользователей
- from ..forms import UserRegistrationForm
- from django.contrib.sites.shortcuts import get_current_site
- from ..tokens import account_activation_token
- from django.template.loader import render_to_string
- from django.utils.encoding import force_bytes
- from django.contrib.auth import get_user_model
- from django.utils.http import urlsafe_base64_encode
- from django.db.models import Q
- from django.shortcuts import render, redirect
- from django.core.mail import EmailMessage
- from django.conf import settings
- import json
- def sign_up(request):
- ERROR_CODES = {
- "password_too_similar": "Пароль не должен совпадать с именем пользователя",
- "password_too_short": "Пароль слишком короткий (Минимум 8 символов)",
- "password_mismatch": "Пароли не совпадают",
- "password_too_common": "Пароль слишком простой",
- "unique": "Пользователь с таким именем уже существует",
- }
- if request.method == "POST":
- form = UserRegistrationForm(request.POST)
- form.errors_to_render = []
- if form.is_valid():
- associated_user = get_user_model().objects.filter(Q(email=form.cleaned_data.get('email'))).first()
- if associated_user:
- form.errors_to_render = ["Пользователь с таким Email уже зарегистрирован"]
- else:
- user = form.save(commit=False)
- user.is_active=False
- user.save()
- if send_activation_link(request, user, form.cleaned_data.get('email')):
- return redirect('/sign_up/success')
- else:
- errors = json.loads(form.errors.as_json())
- print("СПИСОК ОШИБОК:")
- for field_error in errors.keys():
- print(field_error, errors[field_error])
- for type_error in errors[field_error]:
- code = type_error["code"]
- if str(code).__contains__("password"):
- print(type_error)
- form.errors_to_render.append(ERROR_CODES[code])
- elif str(code).__contains__("unique"):
- form.errors_to_render.append(ERROR_CODES[code])
- else:
- form.errors_to_render.append(type_error['message'])
- else:
- form = UserRegistrationForm()
- return render(request, "sign_up.html", context={"form": form})
- def send_activation_link(request, user, to_email):
- mail_subject = f"Активация аккаунта {user.username}"
- token = account_activation_token.make_token(user)
- uid = urlsafe_base64_encode(force_bytes(user.pk))
- domain = get_current_site(request).domain if settings.ENV_NAME == 'local' else settings.SITE_URL
- protocol = 'http' if settings.ENV_NAME == 'local' else 'https'
- url = "activate_account"
- message = render_to_string("template_activate_account.html", {
- 'user': user.username,
- 'domain': domain,
- 'url': url,
- 'uid': uid,
- 'token': token,
- "protocol": protocol
- })
- if settings.ENV_NAME == "production":
- email = EmailMessage(mail_subject, message, to=[to_email], from_email=settings.EMAIL_FROM)
- if email.send():
- return True
- else:
- return False
- else:
- print("Токен отправлен", f"{protocol}://{domain}/{url}/{uid}/{token}")
- return True
- def sign_up_success(request):
- data = {}
- return render(request, 'sign_up_success.html', context=data)
- ПРИЛОЖЕНИЕ № 11. Ограничение доступа к API
- from django.http import JsonResponse
- from ai.models import Profile
- class APIKeyMiddleware:
- def __init__(self, get_response):
- self.get_response = get_response
- def __call__(self, request):
- # Проверяем только для API путей
- if request.path.startswith('/api/'):
- # Проверяем, есть ли заголовки
- api_key = request.headers.get('Authorization', '').split('Bearer ')[-1] if hasattr(request, 'headers') else None
- if not api_key or not Profile.objects.filter(api_key=api_key).exists():
- return JsonResponse({'error': 'Invalid or missing API key'}, status=403)
- # Продолжаем обработку запроса
- return self.get_response(request)
- ПРИЛОЖЕНИЕ № 12. Клиентское приложение
- extends Label
- export var api_key: String = ""
- export var host: String = "localhost"
- export var port: int = 8000
- export var use_https: bool = false
- var url = ""
- onready var headers = [
- "Content-Type: application/json",
- "Authorization: Bearer %s" % api_key
- ]
- var http_request: HTTPRequest
- var audio_player: AudioStreamPlayer
- var capture_effect: AudioEffectCapture
- const BUFFER_SIZE: int = 64512
- func _ready():
- configure_http_request()
- configure_audio()
- func configure_http_request():
- if use_https:
- url += "https://"
- else:
- url += "http://"
- url += str(host) + ":" + str(port) + "/api/predict"
- http_request = HTTPRequest.new()
- add_child(http_request, true)
- var _err = http_request.connect("request_completed", self, "_on_request_completed")
- func configure_audio():
- audio_player = AudioStreamPlayer.new()
- add_child(audio_player)
- audio_player.stream = AudioStreamMicrophone.new()
- var bus_index = AudioServer.get_bus_index("Master")
- AudioServer.set_bus_mute(bus_index, true)
- capture_effect = AudioEffectCapture.new()
- capture_effect.buffer_length = 1.0
- AudioServer.add_bus_effect(bus_index, capture_effect)
- audio_player.play()
- func _process(_delta):
- if is_instance_valid(capture_effect):
- if capture_effect.can_get_buffer(BUFFER_SIZE):
- var microphone_data = capture_effect.get_buffer(BUFFER_SIZE)
- var left_channel = get_channel_from_buffer(microphone_data, 0)
- if left_channel[0] != 0:
- send_frames(left_channel)
- if Input.is_action_just_pressed("ui_cancel"):
- get_tree().quit()
- func get_channel_from_buffer(data: PoolVector2Array, channel_id: int) -> Array:
- var _channel_data: Array
- for frame in data:
- _channel_data.append(frame[channel_id])
- return _channel_data
- func send_frames(frames: Array):
- var json_body = JSON.print({"input": frames})
- if frames[0] != 0 and http_request.get_http_client_status() != HTTPClient.STATUS_REQUESTING:
- var _err = http_request.request(url, headers, false, HTTPClient.METHOD_GET, json_body)
- func _on_request_completed(_result, _response_code, _headers, body):
- if body:
- var json_result = JSON.parse(body.get_string_from_utf8()).result
- if json_result:
- text = json_result.get("label") if json_result.get("label") else ""
- ПРИЛОЖЕНИЯ
Usage statistics
|
Access count: 0
Last 30 days: 0 Detailed usage statistics |