mobile + new models
Gitea Actions Demo / build_and_push (push) Successful in 23s Details

This commit is contained in:
artem 2025-04-24 17:25:56 +03:00
parent 1ac9bc2ecb
commit 60e194b2c9
14 changed files with 194 additions and 112 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.pth filter=lfs diff=lfs merge=lfs -text

View File

@ -24,7 +24,7 @@ jobs:
tags: gitea.webart-tech.ru/webart/beerds/backend:${{ gitea.sha }}
- name: Deploy
run: |
git clone https://gitea.webart-tech.ru/webart/kuber-deploy.git deploy
git clone https://${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }}@gitea.webart-tech.ru/webart/kuber-deploy.git deploy
cd ./deploy/ai
sed -i -E 's/backend:.+/backend:${{ gitea.sha }}/g' dogs.yml
git config --global user.email "deploy@deploy.deploy"

View File

@ -10,9 +10,9 @@ from train import get_labels, load_model, get_loaders, train, show, DEVICE # ty
ImageFile.LOAD_TRUNCATED_IMAGES = True
print(f"Using device: {DEVICE}")
IMG_SIZE = (180, 180)
IMG_SIZE = (224, 224)
INPUT_DIR = "assets/cat"
NUM_EPOCHS = 10
NUM_EPOCHS = 50
MODEL_NAME = "cats_model.pth"
if __name__ == "__main__":

View File

@ -10,9 +10,9 @@ from train import get_labels, load_model, get_loaders, train, show, DEVICE # ty
ImageFile.LOAD_TRUNCATED_IMAGES = True
print(f"Using device: {DEVICE}")
IMG_SIZE = (180, 180)
IMG_SIZE = (224, 224)
INPUT_DIR = "assets/dog"
NUM_EPOCHS = 100
NUM_EPOCHS = 50
MODEL_NAME = "dogs_model.pth"
if __name__ == "__main__":

View File

@ -17,8 +17,9 @@ def get_labels(input_dir, img_size):
transform = transforms.Compose(
[
transforms.Resize(img_size),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]
)
dataset = ImageFolder(root=input_dir, transform=transform)
@ -44,7 +45,10 @@ def load_model(model_path: str, labels_dict: dict, device: str = "cuda") -> nn.M
if not os.path.isfile(model_path):
print("Start new model")
model = torchvision.models.resnet50(weights=ResNet50_Weights.DEFAULT)
model.fc = torch.nn.Linear(model.fc.in_features, len(labels_dict))
model.fc = nn.Sequential(
nn.Dropout(0.5), # Регуляризация
torch.nn.Linear(model.fc.in_features, len(labels_dict))
)
return model
model = torch.load(model_path, map_location=device, weights_only=False)
model.eval()
@ -59,7 +63,7 @@ def train(
val_loader: DataLoader,
) -> Tuple[list[float], list[float], list[float], list[float]]:
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.fc.parameters(), lr=0.001, weight_decay=0.001) # type: ignore[union-attr]
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-4) # type: ignore[union-attr]
# История метрик
train_loss_history = []
train_acc_history = []

View File

@ -48,7 +48,7 @@ with open("server/meta/labels_cats.json", "r") as f:
def predict_image(image, model, device="cuda") -> list[tuple]:
img_size = (180, 180)
img_size = (224, 224)
preprocess = transforms.Compose(
[
transforms.Resize(img_size),
@ -119,12 +119,16 @@ class BaseController(Controller):
path = "/"
@get("/")
async def main(self) -> Template:
return Template(name="index.html")
async def dogs(self) -> Template:
return Template(name="dogs.html")
@get("/cats")
async def cats(self) -> Template:
return Template(name="cats.html")
@get("/contacts")
async def contacts(self) -> Template:
return Template(name="contacts.html")
@get("/sitemap.xml", media_type=MediaType.XML)
async def sitemaps(self) -> bytes:

Binary file not shown.

Binary file not shown.

View File

@ -25,7 +25,9 @@ h1 {
form {
border: 1px solid #696969;
padding: 0 10px 0 10px;
padding: 2px 10px 2px 10px;
display: flex;
justify-content: space-between;
}
#main {
@ -54,4 +56,66 @@ form {
.image-results {
text-align: center;
}
}
#menu {
padding: 0.5rem 0;
border-bottom: 1px solid #dee2e6; /* Серая разделительная линия */
}
#menu ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
gap: 2rem;
}
#menu li {
margin: 0;
padding: 0;
}
#menu a {
text-decoration: none;
color: #696969; /* Темно-серый цвет */
font-size: 1.1rem;
transition: color 0.2s ease;
position: relative;
}
#menu a:hover {
color: #212529; /* Чёрный при наведении */
}
/* Стиль для активной ссылки */
#menu a.active {
color: #212529;
font-weight: 500;
}
#menu a.active::after {
content: "";
position: absolute;
width: 100%;
height: 2px;
background-color: #696969; /* Чёрная линия для активной ссылки */
bottom: -4px;
left: 0;
}
input[type="submit"] {
padding: 10px;
color: #fff;
background-color: #696969;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #212529;
}
input[type="text"]:hover {
margin: 8px 0;
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="yandex-verification" content="2d4efced567f0f7f" />
<meta name="google-site-verification" content="gKPSnPZ1ULUF9amD0vw_JQqkS5GLqc937UxayaN_s-I" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% block meta %}{% endblock %}
<link rel="icon" type="image/x-icon" href="static/favicon.ico">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="static/styles.css">
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(93420354, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/93420354" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<section id="main">
<div id="menu">
<ul>
<li><a href = "/">Собаки</a></li>
<li><a href = "/cats">Кошки</a></li>
<li><a href = "/contacts">Контакты</a></li>
</ul>
</div>
{% block content %}{% endblock %}
{% block form %}{% endblock %}
</body>
</section>
<script src="static/scripts.js"></script>
</html>

View File

@ -1,50 +1,25 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="yandex-verification" content="2d4efced567f0f7f" />
<meta name="google-site-verification" content="gKPSnPZ1ULUF9amD0vw_JQqkS5GLqc937UxayaN_s-I" />
<meta name="description" content="Опередление породы кошки по фото. Определение породы происходит при помощи нейронной сети - точность опеределения составляет 70%." />
<link rel="icon" type="image/x-icon" href="static/favicon.ico">
<title>Определение породы кошки по фото</title>
<link rel="stylesheet" href="static/styles.css">
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
{% extends "base.html" %}
ym(93420354, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/93420354" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<section id="main">
<h1>Определить породу кошки по фото</h1>
<p>Узнать породу <a href = "/">Собаки</a></p>
<p>Загрузите фото, чтобы опеределить породу собаки или щенка. Если порода смешанная (или порода определена неточно), после загрузки будет показана вероятность породы животного.</p>
<p>Определение породы происходит при помощи нейронной сети - точность опеределения составляет 60%, сеть обучена на 65 породах. Если на фото будет неизвестная порода или не кошка - сеть не сможет правильно опеределить, что это.</p>
<p>Для распознования все фото отправляются на сервер, но там не сохраняются</p>
<form enctype="multipart/form-data" method="post" action="/beerds/cats" onsubmit="SavePhoto(this);return false">
<p><input type="file" name="f" id="file-input">
<input type="submit" value="Определить"></p>
</form>
<div>
<div id="upload-image">
<div id="upload-image-text"></div>
<img id="image" style="max-width: 200px;"/>
</div>
<div id="result"></div>
</div>
</body>
</section>
<script src="static/scripts.js"></script>
</html>
{% block meta %}
<meta name="description" content="Опередление породы кошки по фото. Определение породы происходит при помощи нейронной сети - точность опеределения составляет 70%." />
{% endblock %}
{% block title %}Определение породы кошки по фото{% endblock %}
{% block content %}
<h1>Определить породу кошки по фото</h1>
<p>Загрузите фото, чтобы опеределить породу собаки или щенка. Если порода смешанная (или порода определена неточно), после загрузки будет показана вероятность породы животного.</p>
<p>Определение породы происходит при помощи нейронной сети - точность опеределения составляет 60%, сеть обучена на 65 породах. Если на фото будет неизвестная порода или не кошка - сеть не сможет правильно опеределить, что это.</p>
<p>Для распознования все фото отправляются на сервер, но там не сохраняются</p>
{% endblock %}
{% block form %}
<form enctype="multipart/form-data" method="post" action="/beerds/cats" onsubmit="SavePhoto(this);return false">
<input type="file" name="f" id="file-input">
<input type="submit" value="Определить">
</form>
<div>
<div id="upload-image">
<div id="upload-image-text"></div>
<img id="image" style="max-width: 200px;"/>
</div>
<div id="result"></div>
</div>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block meta %}
<meta name="description" content="Опередление породы собаки по фото. Определение породы происходит при помощи нейронной сети - точность опеределения составляет 80%." />
{% endblock %}
{% block title %}Контакты. Определение породы собаки или кошки по фото{% endblock %}
{% block content %}
<h1>Как можно связаться</h1>
<ul>
<li>Email: <i>artem@webart-tech.ru</i></li>
<li>VK: <a href="https://vk.com/dogs_beers?from=groups" target="_blank">Группа VK</a></li>
</ul>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block meta %}
<meta name="description" content="Опередление породы собаки по фото. Определение породы происходит при помощи нейронной сети - точность опеределения составляет 80%." />
{% endblock %}
{% block title %}Определение породы собаки по фото{% endblock %}
{% block content %}
<h1>Определить породу собаки по фото</h1>
<p>Загрузите фото, чтобы опеределить породу собаки или щенка. Если порода смешанная (или порода определена неточно), после загрузки будет показана вероятность породы животного.</p>
<p>Определение породы происходит при помощи нейронной сети - точность опеределения составляет 80%, сеть обучена на <a href="https://vk.com/albums-220240483" target="_blank">125 породах</a>. Если на фото будет неизвестная порода или не собака - сеть не сможет правильно опеределить, что это.</p>
<p>Для распознования все фото отправляются на сервер, но там не сохраняются</p>
{% endblock %}
{% block form %}
<form enctype="multipart/form-data" method="post" action="/beerds/dogs" onsubmit="SavePhoto(this);return false">
<input type="file" name="f" id="file-input">
<input type="submit" value="Определить">
</form>
<div>
<div id="upload-image">
<div id="upload-image-text"></div>
<img id="image" style="max-width: 200px;"/>
</div>
<div id="result"></div>
</div>
{% endblock %}

View File

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="yandex-verification" content="2d4efced567f0f7f" />
<meta name="google-site-verification" content="gKPSnPZ1ULUF9amD0vw_JQqkS5GLqc937UxayaN_s-I" />
<meta name="description" content="Опередление породы собаки по фото. Определение породы происходит при помощи нейронной сети - точность опеределения составляет 80%." />
<link rel="icon" type="image/x-icon" href="static/favicon.ico">
<title>Определение породы собаки по фото</title>
<link rel="stylesheet" href="static/styles.css">
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(93420354, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/93420354" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<section id="main">
<h1>Определить породу собаки по фото</h1>
<p>Узнать породу <a href = "/cats">Кошки</a></p>
<p>Загрузите фото, чтобы опеределить породу собаки или щенка. Если порода смешанная (или порода определена неточно), после загрузки будет показана вероятность породы животного.</p>
<p>Определение породы происходит при помощи нейронной сети - точность опеределения составляет 60%, сеть обучена на <a href="https://vk.com/albums-220240483" target="_blank">125 породах</a>. Если на фото будет неизвестная порода или не собака - сеть не сможет правильно опеределить, что это.</p>
<p>Для распознования все фото отправляются на сервер, но там не сохраняются</p>
<form enctype="multipart/form-data" method="post" action="/beerds/dogs" onsubmit="SavePhoto(this);return false">
<p><input type="file" name="f" id="file-input">
<input type="submit" value="Определить"></p>
</form>
<div>
<div id="upload-image">
<div id="upload-image-text"></div>
<img id="image" style="max-width: 200px;"/>
</div>
<div id="result"></div>
</div>
</body>
</section>
<script src="static/scripts.js"></script>
</html>