Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
f6f24b3ac3 | |||
90b819d1a3 | |||
0c7f18fe00 | |||
920c3d4831 | |||
08f047a720 | |||
d2fe2f04f7 | |||
d5184e4419 | |||
0d41dfa2f2 | |||
28a9d3fa1f | |||
842c1e4395 | |||
63db8db63b | |||
ee01e85053 | |||
2934ae7c32 | |||
ed344b201d | |||
c6ad3a213a | |||
c3c6a8588e | |||
0de563e865 | |||
65dfaec1a5 | |||
07f60e449b | |||
28d22ea47e | |||
e83da8582d | |||
6cbcb76867 | |||
7507a56237 | |||
98d2b8d19c | |||
847bb0694e | |||
a23b880375 | |||
f294a3d33d |
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@ -1,4 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (venv)" project-jdk-type="Python SDK" />
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (price-builder-backend)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (price-builder-backend)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
2
.idea/price-builder-backend.iml
generated
2
.idea/price-builder-backend.iml
generated
@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.9 (venv)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.12 (price-builder-backend)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
69
controllers/controller.py
Normal file
69
controllers/controller.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from schemas.schemas import LaptopCreate, TVCreate, PredictPriceResponse
|
||||||
|
from services.service import LaptopService, TVService
|
||||||
|
import os
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
LAPTOP_MODEL_PATH = "services/ml/laptopML/laptop_price_model.pkl"
|
||||||
|
LAPTOP_FEATURE_COLUMNS_PATH = "services/ml/laptopML/feature_columns.pkl"
|
||||||
|
LAPTOP_POLY_PATH = "services/ml/laptopML/poly_transformer.pkl"
|
||||||
|
LAPTOP_SCALER_PATH = "services/ml/laptopML/scaler.pkl"
|
||||||
|
|
||||||
|
laptop_service = LaptopService(
|
||||||
|
model_path=LAPTOP_MODEL_PATH,
|
||||||
|
feature_columns_path=LAPTOP_FEATURE_COLUMNS_PATH,
|
||||||
|
poly_path=LAPTOP_POLY_PATH,
|
||||||
|
scaler_path=LAPTOP_SCALER_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
TV_MODEL_PATH = "services/ml/tvML/tv_price_model.pkl"
|
||||||
|
TV_FEATURE_COLUMNS_PATH = "services/ml/tvML/feature_columns.pkl"
|
||||||
|
TV_POLY_PATH = "services/ml/tvML/poly_transformer.pkl"
|
||||||
|
TV_SCALER_PATH = "services/ml/tvML/scaler.pkl"
|
||||||
|
|
||||||
|
tv_service = TVService(
|
||||||
|
model_path=TV_MODEL_PATH,
|
||||||
|
feature_columns_path=TV_FEATURE_COLUMNS_PATH,
|
||||||
|
poly_path=TV_POLY_PATH,
|
||||||
|
scaler_path=TV_SCALER_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/predict_price/laptop/", response_model=PredictPriceResponse, summary="Predict laptop price", description="Predict the price of a laptop based on its specifications.", response_description="The predicted price of the laptop.")
|
||||||
|
def predict_price(data: LaptopCreate):
|
||||||
|
"""
|
||||||
|
Predict the price of a laptop given its specifications.
|
||||||
|
|
||||||
|
- **processor**: Type of processor (e.g., i5, i7)
|
||||||
|
- **ram**: Amount of RAM in GB
|
||||||
|
- **os**: Operating system (e.g., Windows, MacOS)
|
||||||
|
- **ssd**: Size of SSD in GB
|
||||||
|
- **display**: Size of the display in inches
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return laptop_service.predict_price(data.dict())
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/predict_price/tv/", response_model=PredictPriceResponse, summary="Predict TV price", description="Predict the price of a TV based on its specifications.", response_description="The predicted price of the TV.")
|
||||||
|
def predict_price(data: TVCreate):
|
||||||
|
try:
|
||||||
|
return tv_service.predict_price(data.dict())
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/get_unique_data_laptop', summary="Get unique data for laptops species")
|
||||||
|
def get_unique_laptops():
|
||||||
|
try:
|
||||||
|
return laptop_service.get_unique_data()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
@router.get('/get_unique_data_tv', summary="Get unique data for tvs species")
|
||||||
|
def get_unique_tvs():
|
||||||
|
try:
|
||||||
|
return tv_service.get_unique_data()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
@ -1,9 +0,0 @@
|
|||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
DATABASE_URL = "postgresql://postgres:postgres@localhost/price-builder"
|
|
||||||
|
|
||||||
engine = create_engine(DATABASE_URL)
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
||||||
Base = declarative_base()
|
|
17
main.py
Normal file
17
main.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from controllers import controller
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Настройка CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # Замените на список допустимых доменов, например: ["http://localhost:8080"]
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"], # Разрешить все методы (GET, POST, PUT и т.д.)
|
||||||
|
allow_headers=["*"], # Разрешить все заголовки
|
||||||
|
)
|
||||||
|
|
||||||
|
# Подключение маршрутов
|
||||||
|
app.include_router(controller.router)
|
@ -1,13 +0,0 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Float
|
|
||||||
from database.database import Base
|
|
||||||
|
|
||||||
class Laptop(Base):
|
|
||||||
__tablename__ = "laptops"
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
|
||||||
title = Column(String, index=True)
|
|
||||||
price = Column(Float)
|
|
||||||
processor = Column(String)
|
|
||||||
ram = Column(Integer)
|
|
||||||
ssd = Column(Integer)
|
|
||||||
display = Column(Float)
|
|
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
48
schemas/schemas.py
Normal file
48
schemas/schemas.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class LaptopCreate(BaseModel):
|
||||||
|
processor: str
|
||||||
|
ram: int
|
||||||
|
os: str
|
||||||
|
ssd: int
|
||||||
|
display_size: float
|
||||||
|
resolution: str
|
||||||
|
matrix_type: str
|
||||||
|
gpu: str
|
||||||
|
|
||||||
|
class TVCreate(BaseModel):
|
||||||
|
display: str
|
||||||
|
tuners: str
|
||||||
|
features: str
|
||||||
|
os: str
|
||||||
|
power_of_volume: str
|
||||||
|
screen_size: int
|
||||||
|
color: str
|
||||||
|
|
||||||
|
class LaptopResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
processor: str
|
||||||
|
ram: int
|
||||||
|
os: str
|
||||||
|
ssd: int
|
||||||
|
display_size: float
|
||||||
|
resolution: str
|
||||||
|
matrix_type: str
|
||||||
|
gpu: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
class TVResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
display: str
|
||||||
|
tuners: str
|
||||||
|
features: str
|
||||||
|
os: str
|
||||||
|
power_of_volume: str
|
||||||
|
screen_size: int
|
||||||
|
color: str
|
||||||
|
|
||||||
|
class PredictPriceResponse(BaseModel):
|
||||||
|
predicted_price: float
|
148
scraping/scrapingMain.py
Normal file
148
scraping/scrapingMain.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import csv
|
||||||
|
import re
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.service import Service
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from webdriver_manager.chrome import ChromeDriverManager
|
||||||
|
|
||||||
|
def init_driver():
|
||||||
|
options = webdriver.ChromeOptions()
|
||||||
|
options.add_argument("--start-maximized")
|
||||||
|
return webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
|
||||||
|
|
||||||
|
def extract_screen_diagonal(name):
|
||||||
|
"""Извлекает диагональ экрана из названия товара."""
|
||||||
|
match = re.search(r'(\d{2})["”]', name)
|
||||||
|
return match.group(1) if match else "Не указана"
|
||||||
|
|
||||||
|
def scrape_all_pages(base_url, max_pages=5):
|
||||||
|
driver = init_driver()
|
||||||
|
all_tv_specs = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
driver.get(base_url)
|
||||||
|
current_page = 1
|
||||||
|
|
||||||
|
while current_page <= max_pages:
|
||||||
|
# Ожидание загрузки товаров
|
||||||
|
WebDriverWait(driver, 15).until(
|
||||||
|
EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.app-catalog-1o4umte.ec53oil0'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Скрапинг характеристик
|
||||||
|
tv_blocks = driver.find_elements(By.CSS_SELECTOR, 'div.app-catalog-1o4umte.ec53oil0')
|
||||||
|
specs_list = []
|
||||||
|
|
||||||
|
for tv in tv_blocks:
|
||||||
|
specs = {}
|
||||||
|
spec_items = tv.find_elements(By.CSS_SELECTOR, 'li.app-catalog-12y5psc.e4qu3682')
|
||||||
|
for item in spec_items:
|
||||||
|
try:
|
||||||
|
label_elem = item.find_element(By.CSS_SELECTOR, 'span')
|
||||||
|
label = label_elem.text.strip()
|
||||||
|
value = item.text.replace(label, '').strip()
|
||||||
|
specs[label] = value
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка в характеристике: {e}")
|
||||||
|
|
||||||
|
specs_list.append(specs)
|
||||||
|
|
||||||
|
|
||||||
|
# Скрапинг названий и извлечение диагоналей
|
||||||
|
name_blocks = driver.find_elements(By.CSS_SELECTOR, 'div.app-catalog-1tp0ino.e1k5a7g60')
|
||||||
|
|
||||||
|
for idx, diagonal_elem in enumerate(name_blocks):
|
||||||
|
try:
|
||||||
|
diagonal_element = diagonal_elem.find_element(By.CSS_SELECTOR, 'a.app-catalog-9gnskf.e1259i3g0')
|
||||||
|
diagonal = extract_screen_diagonal(diagonal_element.text.strip())
|
||||||
|
|
||||||
|
# Если соответствующий specs существует, добавляем цену
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Диагональ"] = diagonal
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при скрапинге цены: {e}")
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Диагональ"] = "Не указана"
|
||||||
|
|
||||||
|
|
||||||
|
# # Скрапинг цен
|
||||||
|
price_blocks = driver.find_elements(By.CSS_SELECTOR, 'div.app-catalog-817h00.ean5xps0')
|
||||||
|
|
||||||
|
for idx, laptop in enumerate(price_blocks):
|
||||||
|
try:
|
||||||
|
price_element = laptop.find_element(By.CSS_SELECTOR, 'span.e1j9birj0')
|
||||||
|
price = price_element.text.strip()
|
||||||
|
|
||||||
|
# Если соответствующий specs существует, добавляем цену
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Цена"] = price
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при скрапинге цены: {e}")
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Цена"] = "Не указана"
|
||||||
|
|
||||||
|
|
||||||
|
# Итоговые данные
|
||||||
|
all_tv_specs.extend(specs_list)
|
||||||
|
|
||||||
|
# Переход на следующую страницу
|
||||||
|
try:
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH,
|
||||||
|
'//*[@id="__next"]/div/main/section/div[2]/div/div/section/div[2]/div[3]/div/div[2]/div[3]/a/div'))
|
||||||
|
)
|
||||||
|
# Прокрутка к кнопке
|
||||||
|
driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", next_button)
|
||||||
|
next_button.click()
|
||||||
|
WebDriverWait(driver, 10).until(EC.staleness_of(tv_blocks[0]))
|
||||||
|
current_page += 1 # Переход на следующую страницу
|
||||||
|
except Exception as e:
|
||||||
|
print("Кнопка 'Следующая' не найдена или конец каталога:", e)
|
||||||
|
break
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
return all_tv_specs
|
||||||
|
|
||||||
|
def save_to_csv(data, filename, ignore_fields=None):
|
||||||
|
# Устанавливаем игнорируемые поля, если они не заданы
|
||||||
|
if ignore_fields is None:
|
||||||
|
ignore_fields = []
|
||||||
|
|
||||||
|
# Фиксированные заголовки
|
||||||
|
fieldnames = [
|
||||||
|
"screen_size", "display", "tuners", "features", "os",
|
||||||
|
"power_of_volume", "color", "price"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Сохранение данных в CSV
|
||||||
|
with open(filename, mode='w', newline='', encoding='utf-8') as file:
|
||||||
|
writer = csv.DictWriter(file, fieldnames=fieldnames)
|
||||||
|
writer.writeheader() # Записываем заголовок
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
# Формируем полный набор данных с необходимыми полями
|
||||||
|
complete_row = {
|
||||||
|
"screen_size": row.get("Диагональ", ""),
|
||||||
|
"display": row.get("Экран", ""),
|
||||||
|
"tuners": row.get("Тюнеры", ""),
|
||||||
|
"features": row.get("Особенности", ""),
|
||||||
|
"os": row.get("Операционная система", ""),
|
||||||
|
"power_of_volume": row.get("Мощность акустики", ""),
|
||||||
|
"color": row.get("Цвет", ""),
|
||||||
|
"price": row.get("Цена", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Убираем поля, которые нужно игнорировать
|
||||||
|
filtered_row = {k: v for k, v in complete_row.items() if k not in ignore_fields}
|
||||||
|
writer.writerow(filtered_row)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
url = 'https://www.citilink.ru/catalog/televizory/?ref=mainpage'
|
||||||
|
tvs = scrape_all_pages(url, max_pages=8)
|
||||||
|
ignore_fields = ["Работает с"]
|
||||||
|
save_to_csv(tvs, 'datasets/tv.csv', ignore_fields)
|
||||||
|
print(f"Данные сохранены в файл 'tv.csv'.")
|
123
scraping/scrappingLaptop.py
Normal file
123
scraping/scrappingLaptop.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import csv
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.service import Service
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from webdriver_manager.chrome import ChromeDriverManager
|
||||||
|
|
||||||
|
def init_driver():
|
||||||
|
options = webdriver.ChromeOptions()
|
||||||
|
options.add_argument("--start-maximized")
|
||||||
|
return webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
|
||||||
|
|
||||||
|
def scrape_all_pages(base_url, max_pages=5):
|
||||||
|
driver = init_driver()
|
||||||
|
all_laptops_specs = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
driver.get(base_url)
|
||||||
|
current_page = 1
|
||||||
|
|
||||||
|
while current_page <= max_pages:
|
||||||
|
# Ожидание загрузки товаров
|
||||||
|
WebDriverWait(driver, 15).until(
|
||||||
|
EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.app-catalog-1o4umte.ec53oil0'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Скрапинг характеристик
|
||||||
|
laptop_blocks = driver.find_elements(By.CSS_SELECTOR, 'div.app-catalog-1o4umte.ec53oil0')
|
||||||
|
specs_list = []
|
||||||
|
|
||||||
|
for laptop in laptop_blocks:
|
||||||
|
specs = {}
|
||||||
|
spec_items = laptop.find_elements(By.CSS_SELECTOR, 'li.app-catalog-12y5psc.e4qu3682')
|
||||||
|
for item in spec_items:
|
||||||
|
try:
|
||||||
|
label_elem = item.find_element(By.CSS_SELECTOR, 'span')
|
||||||
|
label = label_elem.text.strip()
|
||||||
|
value = item.text.replace(label, '').strip()
|
||||||
|
specs[label] = value
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка в характеристике: {e}")
|
||||||
|
|
||||||
|
specs_list.append(specs)
|
||||||
|
|
||||||
|
# Скрапинг цен
|
||||||
|
price_blocks = driver.find_elements(By.CSS_SELECTOR, 'div.app-catalog-817h00.ean5xps0')
|
||||||
|
|
||||||
|
for idx, laptop in enumerate(price_blocks):
|
||||||
|
try:
|
||||||
|
price_element = laptop.find_element(By.CSS_SELECTOR, 'span.e1j9birj0')
|
||||||
|
price = price_element.text.strip()
|
||||||
|
|
||||||
|
# Если соответствующий specs существует, добавляем цену
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Цена"] = price
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при скрапинге цены: {e}")
|
||||||
|
if idx < len(specs_list):
|
||||||
|
specs_list[idx]["Цена"] = "Не указана"
|
||||||
|
|
||||||
|
# Итоговые данные
|
||||||
|
all_laptops_specs.extend(specs_list)
|
||||||
|
|
||||||
|
# Переход на следующую страницу
|
||||||
|
try:
|
||||||
|
next_button = WebDriverWait(driver, 10).until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH,
|
||||||
|
'//*[@id="__next"]/div/main/section/div[2]/div/div/section/div[2]/div[3]/div/div[2]/div[3]/a/div'))
|
||||||
|
)
|
||||||
|
# Прокрутка к кнопке
|
||||||
|
driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", next_button)
|
||||||
|
next_button.click()
|
||||||
|
WebDriverWait(driver, 10).until(EC.staleness_of(laptop_blocks[0]))
|
||||||
|
current_page += 1 # Переход на следующую страницу
|
||||||
|
except Exception as e:
|
||||||
|
print("Кнопка 'Следующая' не найдена или конец каталога:", e)
|
||||||
|
break
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
return all_laptops_specs
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_csv(data, filename, ignore_fields=None):
|
||||||
|
# Устанавливаем игнорируемые поля, если они не заданы
|
||||||
|
if ignore_fields is None:
|
||||||
|
ignore_fields = []
|
||||||
|
|
||||||
|
# Фиксированные заголовки
|
||||||
|
fieldnames = [
|
||||||
|
"processor", "ram", "os", "ssd",
|
||||||
|
"display", "gpu", "price"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Сохранение данных в CSV
|
||||||
|
with open(filename, mode='w', newline='', encoding='utf-8') as file:
|
||||||
|
writer = csv.DictWriter(file, fieldnames=fieldnames)
|
||||||
|
writer.writeheader() # Записываем заголовок
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
# Убираем игнорируемые поля и добавляем пропущенные характеристики
|
||||||
|
complete_row = {
|
||||||
|
"processor": row.get("Процессор", ""),
|
||||||
|
"ram": row.get("Оперативная память", ""),
|
||||||
|
"os": row.get("Операционная система", ""),
|
||||||
|
"ssd": row.get("Диск", ""),
|
||||||
|
"display": row.get("Экран", ""),
|
||||||
|
"gpu": row.get("Графический процессор", ""),
|
||||||
|
"price": row.get("Цена", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Убираем поля, которые нужно игнорировать
|
||||||
|
filtered_row = {k: v for k, v in complete_row.items() if k not in ignore_fields}
|
||||||
|
writer.writerow(filtered_row)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
url = 'https://www.citilink.ru/catalog/noutbuki/?ref=mainpage'
|
||||||
|
laptops = scrape_all_pages(url, max_pages=20)
|
||||||
|
ignore_fields = ["Технология Intel", "Комплектация", "Клавиатура"]
|
||||||
|
save_to_csv(laptops, 'datasets/laptops.csv', ignore_fields)
|
||||||
|
print(f"Данные сохранены в файл 'laptops.csv'.")
|
@ -0,0 +1,178 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Установка случайного зерна для воспроизводимости
|
||||||
|
np.random.seed(42)
|
||||||
|
random.seed(42)
|
||||||
|
|
||||||
|
# Определение возможных значений для категориальных признаков
|
||||||
|
brands = ['Dell', 'HP', 'Lenovo', 'Apple', 'Asus', 'Acer', 'MSI', 'Microsoft', 'Samsung', 'Toshiba']
|
||||||
|
processors = [
|
||||||
|
'Intel Core i3 10th Gen', 'Intel Core i5 10th Gen', 'Intel Core i7 10th Gen',
|
||||||
|
'AMD Ryzen 3 4000 Series', 'AMD Ryzen 5 4000 Series', 'AMD Ryzen 7 4000 Series'
|
||||||
|
]
|
||||||
|
oss = ['Windows 10', 'Windows 11', 'macOS', 'Linux']
|
||||||
|
gpus = ['Integrated', 'NVIDIA GeForce GTX 1650', 'NVIDIA GeForce RTX 3060', 'AMD Radeon RX 5600M']
|
||||||
|
display_sizes = [13.3, 14.0, 15.6, 17.3]
|
||||||
|
display_types = ['HD', 'Full HD', '4K', 'OLED']
|
||||||
|
ram_options = [4, 8, 16, 32] # в GB
|
||||||
|
ssd_options = [0, 256, 512, 1024] # в GB
|
||||||
|
weights = [1.2, 1.5, 2.0, 2.5, 3.0] # в кг
|
||||||
|
battery_sizes = [45, 60, 70, 90, 100] # в Вт⋅ч
|
||||||
|
release_years = list(range(2015, datetime.now().year + 1)) # от 2015 до текущего года
|
||||||
|
|
||||||
|
|
||||||
|
# Функции для генерации признаков
|
||||||
|
def generate_brand():
|
||||||
|
return random.choice(brands)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_processor():
|
||||||
|
return random.choice(processors)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_os():
|
||||||
|
return random.choice(oss)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gpu():
|
||||||
|
return random.choice(gpus)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_display():
|
||||||
|
return random.choice(display_sizes)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_display_type():
|
||||||
|
return random.choice(display_types)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ram():
|
||||||
|
return random.choice(ram_options)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ssd():
|
||||||
|
return random.choice(ssd_options)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_weight():
|
||||||
|
return random.choice(weights)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_battery_size():
|
||||||
|
return random.choice(battery_sizes)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_release_year():
|
||||||
|
return random.choice(release_years)
|
||||||
|
|
||||||
|
|
||||||
|
# Функция для расчёта цены
|
||||||
|
def calculate_price(brand, processor, ram, os, ssd, display, gpu, weight, battery_size, release_year, display_type):
|
||||||
|
base_price = 30000 # базовая цена в условных единицах
|
||||||
|
|
||||||
|
# Бренд
|
||||||
|
brand_premium = {
|
||||||
|
'Apple': 40000, 'MSI': 35000, 'Dell': 15000, 'HP': 12000, 'Lenovo': 10000,
|
||||||
|
'Microsoft': 18000, 'Asus': 8000, 'Acer': 7000, 'Samsung': 9000, 'Toshiba': 8500
|
||||||
|
}
|
||||||
|
base_price += brand_premium.get(brand, 10000)
|
||||||
|
|
||||||
|
# Процессор
|
||||||
|
processor_premium = {
|
||||||
|
'Intel Core i3': 5000, 'Intel Core i5': 10000, 'Intel Core i7': 15000,
|
||||||
|
'AMD Ryzen 3': 5000, 'AMD Ryzen 5': 10000, 'AMD Ryzen 7': 15000
|
||||||
|
}
|
||||||
|
for key, value in processor_premium.items():
|
||||||
|
if key in processor:
|
||||||
|
base_price += value
|
||||||
|
break
|
||||||
|
|
||||||
|
# RAM - уменьшаем его коэффициент
|
||||||
|
base_price += ram * 1000
|
||||||
|
|
||||||
|
# SSD - также уменьшаем его коэффициент
|
||||||
|
base_price += ssd * 50
|
||||||
|
|
||||||
|
# Дисплей
|
||||||
|
base_price += (display - 13) * 5000
|
||||||
|
|
||||||
|
# Тип дисплея
|
||||||
|
display_type_premium = {'HD': 0, 'Full HD': 12000, '4K': 30000, 'OLED': 35000}
|
||||||
|
base_price += display_type_premium.get(display_type, 0)
|
||||||
|
|
||||||
|
# GPU
|
||||||
|
gpu_premium = {'Integrated': 0, 'NVIDIA GeForce GTX 1650': 25000, 'NVIDIA GeForce RTX 3060': 40000, 'AMD Radeon RX 5600M': 35000}
|
||||||
|
base_price += gpu_premium.get(gpu, 0)
|
||||||
|
|
||||||
|
# Вес
|
||||||
|
base_price += (3.0 - weight) * 8000 # Чем легче, тем дороже
|
||||||
|
|
||||||
|
# Батарея
|
||||||
|
base_price += battery_size * 250
|
||||||
|
|
||||||
|
# Год выпуска
|
||||||
|
current_year = datetime.now().year
|
||||||
|
base_price += (current_year - release_year) * 5000
|
||||||
|
|
||||||
|
# Добавление случайного шума
|
||||||
|
noise = np.random.normal(0, 5000) # Шум для увеличения разброса
|
||||||
|
final_price = base_price + noise
|
||||||
|
|
||||||
|
return max(round(final_price, 2), 5000)
|
||||||
|
|
||||||
|
|
||||||
|
# Функция для генерации синтетических данных
|
||||||
|
def generate_synthetic_data(num_samples=100000):
|
||||||
|
data = []
|
||||||
|
for _ in range(num_samples):
|
||||||
|
brand = generate_brand()
|
||||||
|
processor = generate_processor()
|
||||||
|
os = generate_os()
|
||||||
|
gpu = generate_gpu()
|
||||||
|
display = generate_display()
|
||||||
|
display_type = generate_display_type()
|
||||||
|
ram = generate_ram()
|
||||||
|
ssd = generate_ssd()
|
||||||
|
weight = generate_weight()
|
||||||
|
battery_size = generate_battery_size()
|
||||||
|
release_year = generate_release_year()
|
||||||
|
|
||||||
|
price = calculate_price(
|
||||||
|
brand, processor, ram, os, ssd, display, gpu, weight, battery_size, release_year, display_type
|
||||||
|
)
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
'brand': brand,
|
||||||
|
'processor': processor,
|
||||||
|
'ram': ram,
|
||||||
|
'os': os,
|
||||||
|
'ssd': ssd,
|
||||||
|
'display': display,
|
||||||
|
'gpu': gpu,
|
||||||
|
'weight': weight,
|
||||||
|
'battery_size': battery_size,
|
||||||
|
'release_year': release_year,
|
||||||
|
'display_type': display_type,
|
||||||
|
'price': price
|
||||||
|
})
|
||||||
|
return pd.DataFrame(data)
|
||||||
|
|
||||||
|
|
||||||
|
print("Генерация синтетических данных...")
|
||||||
|
synthetic_df = generate_synthetic_data(num_samples=100000)
|
||||||
|
|
||||||
|
# Просмотр первых нескольких строк
|
||||||
|
print("\nПример данных после генерации:")
|
||||||
|
print(synthetic_df.head())
|
||||||
|
|
||||||
|
# Проверка распределения цен
|
||||||
|
print("\nСтатистика по ценам:")
|
||||||
|
print(synthetic_df['price'].describe())
|
||||||
|
|
||||||
|
# Сохранение в CSV
|
||||||
|
synthetic_df.to_csv('../../../../datasets/synthetic_laptops.csv', index=False)
|
||||||
|
print("\nСинтетические данные сохранены в 'synthetic_laptops.csv'.")
|
107
services/ml/scripts/dataGenerators/generate_synthetic_data_tv.py
Normal file
107
services/ml/scripts/dataGenerators/generate_synthetic_data_tv.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Установка случайного зерна для воспроизводимости
|
||||||
|
np.random.seed(42)
|
||||||
|
random.seed(42)
|
||||||
|
|
||||||
|
# Определение возможных значений для категориальных признаков
|
||||||
|
displays = ['LED', 'OLED', 'QLED', 'LCD', 'Plasma']
|
||||||
|
screen_sizes = [32, 40, 43, 50, 55, 65, 75, 85] # в дюймах
|
||||||
|
tuners = ['DVB-T2', 'DVB-C', 'DVB-S2', 'ATSC', 'ISDB-T']
|
||||||
|
features = ['Smart TV', 'HDR', '3D', 'Voice Control', 'Bluetooth', 'WiFi', 'Ambient Mode']
|
||||||
|
oss = ['WebOS', 'Android TV', 'Tizen', 'Roku', 'Fire TV']
|
||||||
|
power_of_volume = ['10W', '20W', '30W', '40W', '50W'] # мощность динамиков
|
||||||
|
colors = ['Black', 'Silver', 'White', 'Gray', 'Metallic']
|
||||||
|
|
||||||
|
# Функции для генерации признаков
|
||||||
|
def generate_display():
|
||||||
|
return random.choice(displays)
|
||||||
|
|
||||||
|
def generate_screen_size():
|
||||||
|
return random.choice(screen_sizes)
|
||||||
|
|
||||||
|
def generate_tuners():
|
||||||
|
return random.choice(tuners)
|
||||||
|
|
||||||
|
def generate_features():
|
||||||
|
return ', '.join(random.sample(features, random.randint(1, 4))) # случайный набор фич
|
||||||
|
|
||||||
|
def generate_os():
|
||||||
|
return random.choice(oss)
|
||||||
|
|
||||||
|
def generate_power_of_volume():
|
||||||
|
return random.choice(power_of_volume)
|
||||||
|
|
||||||
|
def generate_color():
|
||||||
|
return random.choice(colors)
|
||||||
|
|
||||||
|
# Функция для расчёта цены
|
||||||
|
def calculate_price(display, screen_size, tuners, features, os, power_of_volume, color):
|
||||||
|
base_price = 20000 # базовая цена
|
||||||
|
|
||||||
|
# Тип дисплея
|
||||||
|
display_premium = {'LED': 0, 'OLED': 40000, 'QLED': 30000, 'LCD': 10000, 'Plasma': 15000}
|
||||||
|
base_price += display_premium.get(display, 0)
|
||||||
|
|
||||||
|
# Размер экрана
|
||||||
|
base_price += (screen_size - 32) * 1000
|
||||||
|
|
||||||
|
# Функции
|
||||||
|
base_price += len(features.split(', ')) * 5000
|
||||||
|
|
||||||
|
# ОС
|
||||||
|
os_premium = {'WebOS': 10000, 'Android TV': 15000, 'Tizen': 12000, 'Roku': 8000, 'Fire TV': 7000}
|
||||||
|
base_price += os_premium.get(os, 5000)
|
||||||
|
|
||||||
|
# Мощность звука
|
||||||
|
power_value = int(power_of_volume.rstrip('W'))
|
||||||
|
base_price += power_value * 500
|
||||||
|
|
||||||
|
|
||||||
|
# Добавление случайного шума
|
||||||
|
noise = np.random.normal(0, 3000)
|
||||||
|
final_price = base_price + noise
|
||||||
|
|
||||||
|
return max(round(final_price, 2), 5000)
|
||||||
|
|
||||||
|
# Функция для генерации синтетических данных
|
||||||
|
def generate_synthetic_data(num_samples=100000):
|
||||||
|
data = []
|
||||||
|
for _ in range(num_samples):
|
||||||
|
display= generate_display()
|
||||||
|
screen_size = generate_screen_size()
|
||||||
|
tuners = generate_tuners()
|
||||||
|
features = generate_features()
|
||||||
|
os = generate_os()
|
||||||
|
power_of_volume = generate_power_of_volume()
|
||||||
|
color = generate_color()
|
||||||
|
|
||||||
|
price = calculate_price(
|
||||||
|
display, screen_size, tuners, features, os, power_of_volume, color
|
||||||
|
)
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
'display': display,
|
||||||
|
'screen_size': screen_size,
|
||||||
|
'tuners': tuners,
|
||||||
|
'features': features,
|
||||||
|
'os': os,
|
||||||
|
'power_of_volume': power_of_volume,
|
||||||
|
'color': color,
|
||||||
|
'price': price
|
||||||
|
})
|
||||||
|
return pd.DataFrame(data)
|
||||||
|
|
||||||
|
print("Генерация синтетических данных для телевизоров...")
|
||||||
|
synthetic_df = generate_synthetic_data(num_samples=100000)
|
||||||
|
|
||||||
|
# Просмотр первых строк
|
||||||
|
print("\nПример данных после генерации:")
|
||||||
|
print(synthetic_df.head())
|
||||||
|
|
||||||
|
# Сохранение в CSV
|
||||||
|
synthetic_df.to_csv('../../../../datasets/synthetic_tvs.csv', index=False)
|
||||||
|
print("\nСинтетические данные сохранены в 'synthetic_tvs.csv'.")
|
190
services/ml/scripts/modelBuilders/modelBuilderLaptop.py
Normal file
190
services/ml/scripts/modelBuilders/modelBuilderLaptop.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.model_selection import train_test_split, GridSearchCV
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import joblib
|
||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Шаг 1: Поиск датасета
|
||||||
|
# Путь к датасету
|
||||||
|
dataset_path = 'datasets/laptops.csv'
|
||||||
|
# Абсолютный путь к скрипту для создания датасета
|
||||||
|
scraping_script_path = os.path.join(os.getcwd(), 'scraping', 'scrappingLaptop.py')
|
||||||
|
|
||||||
|
# Проверяем, существует ли файл
|
||||||
|
if not os.path.exists(dataset_path):
|
||||||
|
print(f"Файл {dataset_path} не найден. Запускаем скрипт для его создания...")
|
||||||
|
if os.path.exists(scraping_script_path):
|
||||||
|
# Запускаем скрипт для создания датасета
|
||||||
|
subprocess.run(['python', scraping_script_path], check=True)
|
||||||
|
else:
|
||||||
|
print(f"Скрипт {scraping_script_path} не найден.")
|
||||||
|
raise FileNotFoundError(f"Не удалось найти скрипт для создания датасета: {scraping_script_path}")
|
||||||
|
|
||||||
|
# Теперь, когда файл есть, можно продолжить выполнение скрипта
|
||||||
|
df = pd.read_csv(dataset_path)
|
||||||
|
|
||||||
|
# Шаг 2: Проверка и очистка имен столбцов
|
||||||
|
df.columns = df.columns.str.strip().str.lower()
|
||||||
|
|
||||||
|
# Шаг 3: Проверка наличия необходимых столбцов
|
||||||
|
required_columns = [
|
||||||
|
'processor', 'ram', 'os', 'ssd', 'display',
|
||||||
|
'gpu', 'price'
|
||||||
|
]
|
||||||
|
missing_columns = [col for col in required_columns if col not in df.columns]
|
||||||
|
if missing_columns:
|
||||||
|
raise Exception(f"Отсутствуют столбцы: {missing_columns}")
|
||||||
|
|
||||||
|
# Шаг 4: Удаление строк с пропущенными значениями
|
||||||
|
df = df.dropna(subset=required_columns)
|
||||||
|
|
||||||
|
# Извлечение только чисел из строк
|
||||||
|
df['ram'] = df['ram'].str.extract(r'(\d+)').astype(float)
|
||||||
|
df['ssd'] = df['ssd'].str.extract(r'(\d+)').astype(float)
|
||||||
|
|
||||||
|
# Преобразование цен в числовой формат
|
||||||
|
df['price'] = df['price'].astype(str).str.replace(' ', '').astype(int)
|
||||||
|
|
||||||
|
# Шаг 5: Очистка и преобразование колонок
|
||||||
|
def clean_numeric_column(column, remove_chars=['₹', ',', ' ', 'ГБ', 'МГц', '']):
|
||||||
|
if column.dtype == object:
|
||||||
|
for char in remove_chars:
|
||||||
|
column = column.str.replace(char, '', regex=False)
|
||||||
|
return pd.to_numeric(column, errors='coerce')
|
||||||
|
else:
|
||||||
|
return column
|
||||||
|
|
||||||
|
numerical_columns = ['ram', 'ssd']
|
||||||
|
for col in numerical_columns:
|
||||||
|
df[col] = clean_numeric_column(df[col])
|
||||||
|
|
||||||
|
df = df.dropna(subset=['price'])
|
||||||
|
|
||||||
|
# Шаг 5.1: Разбиение display на три отдельных признака
|
||||||
|
def process_display_column(display_column):
|
||||||
|
# Регулярное выражение для извлечения информации
|
||||||
|
display_column = display_column.fillna("").str.replace(r'[^\w\sxX;]', '', regex=True)
|
||||||
|
size_pattern = r"(\d+(?:\.\d+)?)" # Размер экрана (например, 16 или 16.5)
|
||||||
|
resolution_pattern = r"(\d{3,4})\s?([xXх])\s?(\d{3,4})" # Разрешение экрана (например, 1920x1080)
|
||||||
|
matrix_type_pattern = r"(IPS|TN|VA|OLED|AMOLED|Retina|LTPS|LTPO)" # Тип матрицы
|
||||||
|
|
||||||
|
sizes = display_column.str.extract(size_pattern, expand=False).astype(float)
|
||||||
|
resolutions = display_column.str.extract(resolution_pattern, expand=False)
|
||||||
|
matrix_types = display_column.str.extract(matrix_type_pattern, expand=False)
|
||||||
|
|
||||||
|
# Делим на 10, если значение больше 100 (для исправления некорректных размеров)
|
||||||
|
sizes = sizes.apply(lambda x: x / 10 if x > 100 else x)
|
||||||
|
|
||||||
|
# Добавляем новые колонки
|
||||||
|
return pd.DataFrame({
|
||||||
|
'display_size': sizes, # Размер экрана
|
||||||
|
'resolution': resolutions[0] + resolutions[1] + resolutions[2], # Разрешение экрана
|
||||||
|
'matrix_type': matrix_types # Тип матрицы
|
||||||
|
})
|
||||||
|
|
||||||
|
# Обработка столбца display
|
||||||
|
display_features = process_display_column(df['display'])
|
||||||
|
|
||||||
|
# Добавляем обработанные данные обратно в DataFrame
|
||||||
|
df = pd.concat([df, display_features], axis=1)
|
||||||
|
|
||||||
|
# Удаляем исходный столбец display
|
||||||
|
df = df.drop('display', axis=1)
|
||||||
|
|
||||||
|
# Шаг 5.2: Сохранение уникальных значений для фронтенда
|
||||||
|
|
||||||
|
# Создаем список уникальных значений для категориальных признаков
|
||||||
|
unique_values = {
|
||||||
|
'processor': df['processor'].dropna().unique().tolist(),
|
||||||
|
'ram': df['ram'].dropna().unique().tolist(),
|
||||||
|
'os': df['os'].dropna().unique().tolist(),
|
||||||
|
'ssd': df['ssd'].dropna().unique().tolist(),
|
||||||
|
'gpu': df['gpu'].dropna().unique().tolist(),
|
||||||
|
'display_size': df['display_size'].dropna().unique().tolist(),
|
||||||
|
'resolution': df['resolution'].dropna().unique().tolist(),
|
||||||
|
'matrix_type': df['matrix_type'].dropna().unique().tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Создание директории, если она не существует
|
||||||
|
output_dir = 'services/ml/scripts/modelBuilders/columns'
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
with open(os.path.join(output_dir, 'unique_values_laptop.json'), 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(unique_values, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
# Шаг 6: Преобразование категориальных переменных с помощью One-Hot Encoding
|
||||||
|
categorical_features = ['processor', 'os', 'gpu', 'resolution', 'matrix_type']
|
||||||
|
df = pd.get_dummies(df, columns=categorical_features, drop_first=True)
|
||||||
|
|
||||||
|
# Шаг 7: Разделение данных на X и y
|
||||||
|
X = df.drop('price', axis=1)
|
||||||
|
y = df['price']
|
||||||
|
|
||||||
|
# Шаг 8: Создание полиномиальных и интерактивных признаков степени 2
|
||||||
|
poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
|
||||||
|
X_poly = poly.fit_transform(X)
|
||||||
|
|
||||||
|
# Шаг 9: Масштабирование признаков
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_poly_scaled = scaler.fit_transform(X_poly)
|
||||||
|
|
||||||
|
# Шаг 10: Разделение на обучающую и тестовую выборки
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_poly_scaled, y, test_size=0.5, random_state=42)
|
||||||
|
|
||||||
|
# Шаг 11: Настройка гиперпараметров с использованием GridSearchCV
|
||||||
|
param_grid = {
|
||||||
|
'n_estimators': [100, 200],
|
||||||
|
'max_depth': [10, 20],
|
||||||
|
'max_features': ['sqrt', 'log2', 0.5],
|
||||||
|
'min_samples_split': [5, 10],
|
||||||
|
'min_samples_leaf': [2, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
print('Процесс обучения модели...')
|
||||||
|
|
||||||
|
grid_search = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring='neg_mean_absolute_error')
|
||||||
|
grid_search.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Лучшая модель
|
||||||
|
best_model = grid_search.best_estimator_
|
||||||
|
|
||||||
|
# Шаг 12: Предсказания и оценка
|
||||||
|
y_pred = best_model.predict(X_test)
|
||||||
|
|
||||||
|
# Шаг 13: Сохранение модели
|
||||||
|
feature_columns = X.columns.tolist()
|
||||||
|
joblib.dump(feature_columns, 'services/ml/laptopML/feature_columns.pkl')
|
||||||
|
joblib.dump(best_model, 'services/ml/laptopML/laptop_price_model.pkl')
|
||||||
|
joblib.dump(poly, 'services/ml/laptopML/poly_transformer.pkl')
|
||||||
|
joblib.dump(scaler, 'services/ml/laptopML/scaler.pkl')
|
||||||
|
print("Модель, трансформер и скейлер сохранены.")
|
||||||
|
|
||||||
|
# Шаг 15: Важность признаков
|
||||||
|
# Количество признаков, которые нужно отобразить
|
||||||
|
top_n = 15
|
||||||
|
|
||||||
|
# Важность признаков
|
||||||
|
importances = best_model.feature_importances_
|
||||||
|
indices = np.argsort(importances)[::-1]
|
||||||
|
|
||||||
|
# Отображаем только топ-N признаков
|
||||||
|
top_indices = indices[:top_n]
|
||||||
|
top_importances = importances[top_indices]
|
||||||
|
top_features = np.array(poly.get_feature_names_out())[top_indices]
|
||||||
|
|
||||||
|
# Построение графика
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
plt.title(f"Топ-{top_n} признаков по важности (Random Forest)")
|
||||||
|
plt.bar(range(top_n), top_importances, align='center')
|
||||||
|
plt.xticks(range(top_n), top_features, rotation=45, ha='right')
|
||||||
|
plt.xlabel("Признаки")
|
||||||
|
plt.ylabel("Важность")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
114
services/ml/scripts/modelBuilders/modelBuilderLaptopSynthetic.py
Normal file
114
services/ml/scripts/modelBuilders/modelBuilderLaptopSynthetic.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from sklearn.model_selection import train_test_split, GridSearchCV
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import joblib
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Шаг 1: Загрузка данных
|
||||||
|
df = pd.read_csv('../../../../datasets/synthetic_laptops.csv')
|
||||||
|
|
||||||
|
# Шаг 2: Проверка и очистка имен столбцов
|
||||||
|
df.columns = df.columns.str.strip().str.lower()
|
||||||
|
|
||||||
|
# Шаг 3: Проверка наличия необходимых столбцов
|
||||||
|
required_columns = [
|
||||||
|
'brand', 'processor', 'ram', 'os', 'ssd', 'display',
|
||||||
|
'gpu', 'weight', 'battery_size', 'release_year', 'display_type', 'price'
|
||||||
|
]
|
||||||
|
missing_columns = [col for col in required_columns if col not in df.columns]
|
||||||
|
if missing_columns:
|
||||||
|
raise Exception(f"Отсутствуют столбцы: {missing_columns}")
|
||||||
|
|
||||||
|
# Шаг 4: Удаление строк с пропущенными значениями
|
||||||
|
df = df.dropna(subset=required_columns)
|
||||||
|
|
||||||
|
# Шаг 5: Очистка и преобразование колонок
|
||||||
|
def clean_numeric_column(column, remove_chars=['₹', ',', ' ']):
|
||||||
|
if column.dtype == object:
|
||||||
|
for char in remove_chars:
|
||||||
|
column = column.str.replace(char, '', regex=False)
|
||||||
|
return pd.to_numeric(column, errors='coerce')
|
||||||
|
else:
|
||||||
|
return column
|
||||||
|
|
||||||
|
numerical_columns = ['ram', 'ssd', 'display', 'weight', 'battery_size', 'release_year']
|
||||||
|
for col in numerical_columns:
|
||||||
|
df[col] = clean_numeric_column(df[col])
|
||||||
|
|
||||||
|
df = df.dropna(subset=['price'] + numerical_columns)
|
||||||
|
|
||||||
|
# Шаг 6: Преобразование категориальных переменных с помощью One-Hot Encoding
|
||||||
|
categorical_features = ['brand', 'processor', 'os', 'gpu', 'display_type']
|
||||||
|
df = pd.get_dummies(df, columns=categorical_features, drop_first=True)
|
||||||
|
|
||||||
|
# Шаг 7: Разделение данных на X и y
|
||||||
|
X = df.drop('price', axis=1)
|
||||||
|
y = df['price']
|
||||||
|
|
||||||
|
# Шаг 8: Создание полиномиальных и интерактивных признаков степени 2
|
||||||
|
poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
|
||||||
|
X_poly = poly.fit_transform(X)
|
||||||
|
|
||||||
|
# Шаг 9: Масштабирование признаков
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_poly_scaled = scaler.fit_transform(X_poly)
|
||||||
|
|
||||||
|
# Шаг 10: Разделение на обучающую и тестовую выборки
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_poly_scaled, y, test_size=0.5, random_state=42)
|
||||||
|
|
||||||
|
# Шаг 11: Настройка гиперпараметров с использованием GridSearchCV
|
||||||
|
param_grid = {
|
||||||
|
'n_estimators': [100, 200],
|
||||||
|
'max_depth': [10, 20],
|
||||||
|
'max_features': ['sqrt', 'log2', 0.5],
|
||||||
|
'min_samples_split': [5, 10],
|
||||||
|
'min_samples_leaf': [2, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_search = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring='neg_mean_absolute_error')
|
||||||
|
grid_search.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# Лучшая модель
|
||||||
|
best_model = grid_search.best_estimator_
|
||||||
|
|
||||||
|
# Шаг 12: Предсказания и оценка
|
||||||
|
y_pred = best_model.predict(X_test)
|
||||||
|
mae = mean_absolute_error(y_test, y_pred)
|
||||||
|
rmse = mean_squared_error(y_test, y_pred, squared=False)
|
||||||
|
r2 = r2_score(y_test, y_pred)
|
||||||
|
print(f"Лучшие параметры: {grid_search.best_params_}")
|
||||||
|
print(f"Random Forest - MAE: {mae}, RMSE: {rmse}, R²: {r2}")
|
||||||
|
|
||||||
|
# Шаг 13: Сохранение модели
|
||||||
|
feature_columns = X.columns.tolist()
|
||||||
|
joblib.dump(feature_columns, '../../laptopML/feature_columns.pkl')
|
||||||
|
joblib.dump(best_model, '../../laptopML/laptop_price_model.pkl')
|
||||||
|
joblib.dump(poly, '../../laptopML/poly_transformer.pkl')
|
||||||
|
joblib.dump(scaler, '../../laptopML/scaler.pkl')
|
||||||
|
print("Модель, трансформер и скейлер сохранены.")
|
||||||
|
|
||||||
|
# Шаг 14: Важность признаков
|
||||||
|
# Количество признаков, которые нужно отобразить
|
||||||
|
top_n = 15
|
||||||
|
|
||||||
|
# Важность признаков
|
||||||
|
importances = best_model.feature_importances_
|
||||||
|
indices = np.argsort(importances)[::-1]
|
||||||
|
|
||||||
|
# Отображаем только топ-N признаков
|
||||||
|
top_indices = indices[:top_n]
|
||||||
|
top_importances = importances[top_indices]
|
||||||
|
top_features = np.array(poly.get_feature_names_out())[top_indices]
|
||||||
|
|
||||||
|
# Построение графика
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
plt.title(f"Топ-{top_n} признаков по важности (Random Forest)")
|
||||||
|
plt.bar(range(top_n), top_importances, align='center')
|
||||||
|
plt.xticks(range(top_n), top_features, rotation=45, ha='right')
|
||||||
|
plt.xlabel("Признаки")
|
||||||
|
plt.ylabel("Важность")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
117
services/ml/scripts/modelBuilders/modelBuilderTV.py
Normal file
117
services/ml/scripts/modelBuilders/modelBuilderTV.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.model_selection import train_test_split, GridSearchCV
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import joblib
|
||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Путь к датасету
|
||||||
|
dataset_path = 'datasets/tvs.csv'
|
||||||
|
# Абсолютный путь к скрипту для создания датасета
|
||||||
|
scraping_script_path = os.path.join(os.getcwd(), 'scraping', 'scrapingMain.py')
|
||||||
|
|
||||||
|
# Проверяем, существует ли файл
|
||||||
|
if not os.path.exists(dataset_path):
|
||||||
|
print(f"Файл {dataset_path} не найден. Запускаем скрипт для его создания...")
|
||||||
|
if os.path.exists(scraping_script_path):
|
||||||
|
# Запускаем скрипт для создания датасета
|
||||||
|
subprocess.run(['python', scraping_script_path], check=True)
|
||||||
|
else:
|
||||||
|
print(f"Скрипт {scraping_script_path} не найден.")
|
||||||
|
raise FileNotFoundError(f"Не удалось найти скрипт для создания датасета: {scraping_script_path}")
|
||||||
|
|
||||||
|
# Теперь, когда файл есть, можно продолжить выполнение скрипта
|
||||||
|
df = pd.read_csv(dataset_path)
|
||||||
|
|
||||||
|
# Проверка и очистка данных
|
||||||
|
required_columns = ['display', 'tuners', 'features', 'os', 'power_of_volume', 'color', 'screen_size', 'price']
|
||||||
|
missing_columns = [col for col in required_columns if col not in df.columns]
|
||||||
|
if missing_columns:
|
||||||
|
raise Exception(f"Отсутствуют столбцы: {missing_columns}")
|
||||||
|
|
||||||
|
df = df.dropna(subset=required_columns)
|
||||||
|
|
||||||
|
# Преобразование цен в числовой формат
|
||||||
|
df['price'] = df['price'].astype(str).str.replace(' ', '').astype(int)
|
||||||
|
|
||||||
|
# Создаем список уникальных значений для категориальных признаков
|
||||||
|
unique_values = {
|
||||||
|
'display': df['display'].dropna().unique().tolist(),
|
||||||
|
'tuners': df['tuners'].dropna().unique().tolist(),
|
||||||
|
'features': df['features'].dropna().unique().tolist(),
|
||||||
|
'os': df['os'].dropna().unique().tolist(),
|
||||||
|
'power_of_volume': df['power_of_volume'].dropna().unique().tolist(),
|
||||||
|
'color': df['color'].dropna().unique().tolist(),
|
||||||
|
'screen_size': df['screen_size'].dropna().unique().tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Создание директории, если она не существует
|
||||||
|
output_dir = 'services/ml/scripts/modelBuilders/columns'
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
with open(os.path.join(output_dir, 'unique_values_tv.json'), 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(unique_values, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
# Преобразование категориальных переменных
|
||||||
|
categorical_features = ['display', 'tuners', 'features', 'os', 'power_of_volume','color']
|
||||||
|
df = pd.get_dummies(df, columns=categorical_features, drop_first=True)
|
||||||
|
|
||||||
|
# Разделение на X и y
|
||||||
|
X = df.drop('price', axis=1)
|
||||||
|
y = df['price']
|
||||||
|
|
||||||
|
# Полиномиальные признаки
|
||||||
|
poly = PolynomialFeatures(degree=1, interaction_only=True, include_bias=False)
|
||||||
|
X_poly = poly.fit_transform(X)
|
||||||
|
|
||||||
|
# Масштабирование
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_poly_scaled = scaler.fit_transform(X_poly)
|
||||||
|
|
||||||
|
# Разделение на обучающую и тестовую выборки
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_poly_scaled, y, test_size=0.5, random_state=42)
|
||||||
|
|
||||||
|
# Настройка Random Forest
|
||||||
|
param_grid = {
|
||||||
|
'n_estimators': [100, 200],
|
||||||
|
'max_depth': [10, 20],
|
||||||
|
'max_features': ['sqrt', 'log2', 0.5],
|
||||||
|
'min_samples_split': [5, 10],
|
||||||
|
'min_samples_leaf': [2, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_search = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring='neg_mean_absolute_error')
|
||||||
|
grid_search.fit(X_train, y_train)
|
||||||
|
best_model = grid_search.best_estimator_
|
||||||
|
|
||||||
|
# Сохранение модели
|
||||||
|
feature_columns = X.columns.tolist()
|
||||||
|
joblib.dump(feature_columns, 'services/ml/tvML/feature_columns.pkl')
|
||||||
|
joblib.dump(best_model, 'services/ml/tvML/tv_price_model.pkl')
|
||||||
|
joblib.dump(poly, 'services/ml/tvML/poly_transformer.pkl')
|
||||||
|
joblib.dump(scaler, 'services/ml/tvML/scaler.pkl')
|
||||||
|
print("Модель для телевизоров сохранена.")
|
||||||
|
|
||||||
|
# Вывод важности признаков
|
||||||
|
feature_importances = best_model.feature_importances_
|
||||||
|
feature_names = poly.get_feature_names_out(X.columns)
|
||||||
|
|
||||||
|
# Построение графика важности признаков (снизу вверх)
|
||||||
|
sorted_indices = np.argsort(feature_importances)[::-1] # Сортировка индексов по важности
|
||||||
|
top_features = [feature_names[i] for i in sorted_indices[:15]]
|
||||||
|
top_importances = feature_importances[sorted_indices[:15]]
|
||||||
|
|
||||||
|
plt.figure(figsize=(12, 10)) # Увеличиваем размер графика
|
||||||
|
plt.bar(range(len(top_features)), top_importances, tick_label=top_features)
|
||||||
|
plt.xticks(rotation=45, ha='right', fontsize=10) # Наклон подписей и выравнивание
|
||||||
|
plt.ylabel('Важность')
|
||||||
|
plt.xlabel('Признаки')
|
||||||
|
plt.title('Топ 20 Важнейших параметров')
|
||||||
|
plt.tight_layout() # Добавляем автоматическое выравнивание элементов графика
|
||||||
|
plt.show()
|
73
services/ml/scripts/modelBuilders/modelBuilderTVSynthetic.py
Normal file
73
services/ml/scripts/modelBuilders/modelBuilderTVSynthetic.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from sklearn.model_selection import train_test_split, GridSearchCV
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import joblib
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Загрузка данных
|
||||||
|
df = pd.read_csv('../../../../datasets/synthetic_tvs.csv')
|
||||||
|
|
||||||
|
# Проверка и очистка данных
|
||||||
|
required_columns = ['display', 'tuners', 'features', 'os', 'power_of_volume', 'color', 'screen_size', 'price']
|
||||||
|
missing_columns = [col for col in required_columns if col not in df.columns]
|
||||||
|
if missing_columns:
|
||||||
|
raise Exception(f"Отсутствуют столбцы: {missing_columns}")
|
||||||
|
|
||||||
|
df = df.dropna(subset=required_columns)
|
||||||
|
|
||||||
|
# Преобразование категориальных переменных
|
||||||
|
categorical_features = ['display', 'tuners', 'features', 'os', 'power_of_volume','color']
|
||||||
|
df = pd.get_dummies(df, columns=categorical_features, drop_first=True)
|
||||||
|
|
||||||
|
# Разделение на X и y
|
||||||
|
X = df.drop('price', axis=1)
|
||||||
|
y = df['price']
|
||||||
|
|
||||||
|
# Полиномиальные признаки
|
||||||
|
poly = PolynomialFeatures(degree=1, interaction_only=True, include_bias=False)
|
||||||
|
X_poly = poly.fit_transform(X)
|
||||||
|
|
||||||
|
# Масштабирование
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_poly_scaled = scaler.fit_transform(X_poly)
|
||||||
|
|
||||||
|
# Разделение на обучающую и тестовую выборки
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X_poly_scaled, y, test_size=0.5, random_state=42)
|
||||||
|
|
||||||
|
# Настройка Random Forest
|
||||||
|
param_grid = {
|
||||||
|
'n_estimators': [100, 200],
|
||||||
|
'max_depth': [10, 20],
|
||||||
|
'max_features': ['sqrt', 'log2', 0.5],
|
||||||
|
'min_samples_split': [5, 10],
|
||||||
|
'min_samples_leaf': [2, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_search = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring='neg_mean_absolute_error')
|
||||||
|
grid_search.fit(X_train, y_train)
|
||||||
|
best_model = grid_search.best_estimator_
|
||||||
|
|
||||||
|
# Вывод важности признаков
|
||||||
|
feature_importances = best_model.feature_importances_
|
||||||
|
feature_names = poly.get_feature_names_out(X.columns)
|
||||||
|
|
||||||
|
# Построение графика важности признаков
|
||||||
|
sorted_indices = np.argsort(feature_importances)[::-1]
|
||||||
|
plt.figure(figsize=(10, 8))
|
||||||
|
plt.barh([feature_names[i] for i in sorted_indices[:20]], feature_importances[sorted_indices[:20]])
|
||||||
|
plt.xlabel('Importance')
|
||||||
|
plt.ylabel('Feature')
|
||||||
|
plt.title('Top 20 Feature Importances')
|
||||||
|
plt.gca().invert_yaxis()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# Сохранение модели
|
||||||
|
feature_columns = X.columns.tolist()
|
||||||
|
joblib.dump(feature_columns, '../../tvML/feature_columns.pkl')
|
||||||
|
joblib.dump(best_model, '../../tvML/tv_price_model.pkl')
|
||||||
|
joblib.dump(poly, '../../tvML/poly_transformer.pkl')
|
||||||
|
joblib.dump(scaler, '../../tvML/scaler.pkl')
|
||||||
|
print("Модель для телевизоров сохранена.")
|
209
services/service.py
Normal file
209
services/service.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import subprocess
|
||||||
|
import pandas as pd
|
||||||
|
import joblib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import List, Dict
|
||||||
|
from schemas.schemas import LaptopCreate, LaptopResponse, PredictPriceResponse
|
||||||
|
|
||||||
|
class LaptopService:
|
||||||
|
def __init__(self, model_path: str, feature_columns_path: str, poly_path: str, scaler_path: str):
|
||||||
|
self.script_path = "services/ml/scripts/modelBuilders/modelBuilderLaptop.py"
|
||||||
|
|
||||||
|
# Проверка наличия модели, если её нет — создание
|
||||||
|
if not os.path.exists(model_path) or not os.path.exists(feature_columns_path) or not os.path.exists(poly_path) or not os.path.exists(scaler_path):
|
||||||
|
print("Необходимые файлы модели отсутствуют. Запускаем построение модели...")
|
||||||
|
self.run_model_builder()
|
||||||
|
|
||||||
|
# Загрузка модели и связанных файлов
|
||||||
|
try:
|
||||||
|
self.model = joblib.load(model_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(f"Model file not found at {model_path}")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading model: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.feature_columns = joblib.load(feature_columns_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(f"Feature columns file not found at {feature_columns_path}")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading feature columns: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.poly_transformer = joblib.load(poly_path)
|
||||||
|
self.scaler = joblib.load(scaler_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception("Polynomial transformer or scaler file not found.")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading polynomial transformer or scaler: {str(e)}")
|
||||||
|
|
||||||
|
def run_model_builder(self):
|
||||||
|
# Убедитесь, что путь к скрипту корректен
|
||||||
|
if not os.path.exists(self.script_path):
|
||||||
|
raise FileNotFoundError(f"Скрипт {self.script_path} не найден.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Запускаем скрипт {self.script_path} для создания модели ноутбуков...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['python', self.script_path],
|
||||||
|
stdout=subprocess.PIPE, # Перенаправляем stdout
|
||||||
|
stderr=subprocess.PIPE, # Перенаправляем stderr
|
||||||
|
text=True # Декодируем вывод в текст
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Ошибка выполнения скрипта: {result.stderr}")
|
||||||
|
else:
|
||||||
|
print("Модель успешно создана.")
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Не удалось выполнить скрипт с ноутбуками: {str(e)}")
|
||||||
|
|
||||||
|
def predict_price(self, data: Dict[str, any]) -> PredictPriceResponse:
|
||||||
|
# Преобразование данных в DataFrame
|
||||||
|
input_df = pd.DataFrame([data])
|
||||||
|
|
||||||
|
print("До One-Hot Encoding:")
|
||||||
|
print(input_df.head())
|
||||||
|
print("Колонки:", input_df.columns)
|
||||||
|
|
||||||
|
# Применение One-Hot Encoding к категориальным признакам
|
||||||
|
input_df = pd.get_dummies(input_df, columns=['processor', 'os', 'resolution', 'gpu', 'matrix_type'], drop_first=False)
|
||||||
|
|
||||||
|
print("После One-Hot Encoding:")
|
||||||
|
print(input_df.head())
|
||||||
|
print("Колонки:", input_df.columns)
|
||||||
|
|
||||||
|
# Добавление отсутствующих признаков
|
||||||
|
for col in self.feature_columns:
|
||||||
|
if col not in input_df.columns and col != 'price':
|
||||||
|
input_df[col] = 0
|
||||||
|
|
||||||
|
# Упорядочивание колонок
|
||||||
|
input_df = input_df[self.feature_columns]
|
||||||
|
|
||||||
|
# Преобразование с использованием PolynomialFeatures
|
||||||
|
input_poly = self.poly_transformer.transform(input_df)
|
||||||
|
|
||||||
|
# Масштабирование данных
|
||||||
|
input_scaled = self.scaler.transform(input_poly)
|
||||||
|
|
||||||
|
# Предсказание цены
|
||||||
|
predicted_price = self.model.predict(input_scaled)[0]
|
||||||
|
|
||||||
|
return PredictPriceResponse(predicted_price=round(predicted_price, 2))
|
||||||
|
|
||||||
|
def get_unique_data(self):
|
||||||
|
# Указываем путь к файлу
|
||||||
|
file_path = 'services/ml/scripts/modelBuilders/columns/unique_values_laptop.json'
|
||||||
|
|
||||||
|
# Открываем и читаем данные из файла
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
data = json.load(file) # Загружаем данные из JSON
|
||||||
|
|
||||||
|
# Возвращаем данные, которые будут переданы в ответ
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class TVService:
|
||||||
|
def __init__(self, model_path: str, feature_columns_path: str, poly_path: str, scaler_path: str):
|
||||||
|
self.script_path = "services/ml/scripts/modelBuilders/modelBuilderTV.py"
|
||||||
|
|
||||||
|
# Проверка наличия модели, если её нет — создание
|
||||||
|
if not os.path.exists(model_path) or not os.path.exists(feature_columns_path) or not os.path.exists(
|
||||||
|
poly_path) or not os.path.exists(scaler_path):
|
||||||
|
print("Необходимые файлы модели отсутствуют. Запускаем построение модели...")
|
||||||
|
self.run_model_builder()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.model = joblib.load(model_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(f"Model file not found at {model_path}")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading model: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.feature_columns = joblib.load(feature_columns_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(f"Feature columns file not found at {feature_columns_path}")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading feature columns: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.poly_transformer = joblib.load(poly_path)
|
||||||
|
self.scaler = joblib.load(scaler_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception("Polynomial transformer or scaler file not found.")
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error loading polynomial transformer or scaler: {str(e)}")
|
||||||
|
|
||||||
|
def predict_price(self, data: Dict[str, any]) -> PredictPriceResponse:
|
||||||
|
input_df = pd.DataFrame([data])
|
||||||
|
|
||||||
|
print("До One-Hot Encoding:")
|
||||||
|
print(input_df.head())
|
||||||
|
print("Колонки:", input_df.columns)
|
||||||
|
|
||||||
|
# Применение One-Hot Encoding
|
||||||
|
input_df = pd.get_dummies(input_df,
|
||||||
|
columns=['display', 'tuners', 'features', 'os', 'color', 'power_of_volume'],
|
||||||
|
drop_first=False)
|
||||||
|
|
||||||
|
# Преобразование булевых значений в числовые
|
||||||
|
input_df = input_df.astype(int)
|
||||||
|
|
||||||
|
print("После One-Hot Encoding:")
|
||||||
|
print(input_df.head())
|
||||||
|
print("Колонки:", input_df.columns)
|
||||||
|
|
||||||
|
# Добавление отсутствующих признаков
|
||||||
|
missing_columns = [col for col in self.feature_columns if col not in input_df.columns and col != 'price']
|
||||||
|
missing_df = pd.DataFrame(0, index=input_df.index, columns=missing_columns)
|
||||||
|
input_df = pd.concat([input_df, missing_df], axis=1)
|
||||||
|
|
||||||
|
# Упорядочение столбцов
|
||||||
|
input_df = input_df.reindex(columns=self.feature_columns, fill_value=0)
|
||||||
|
|
||||||
|
# Полиномиальные и масштабированные данные
|
||||||
|
input_poly = self.poly_transformer.transform(input_df)
|
||||||
|
input_scaled = self.scaler.transform(input_poly)
|
||||||
|
|
||||||
|
# Предсказание
|
||||||
|
predicted_price = self.model.predict(input_scaled)[0]
|
||||||
|
return PredictPriceResponse(predicted_price=round(predicted_price, 2))
|
||||||
|
|
||||||
|
def run_model_builder(self):
|
||||||
|
# Убедитесь, что путь к скрипту корректен
|
||||||
|
if not os.path.exists(self.script_path):
|
||||||
|
raise FileNotFoundError(f"Скрипт {self.script_path} не найден.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Запускаем скрипт {self.script_path} для создания модели телевизоров...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['python', self.script_path],
|
||||||
|
stdout=subprocess.PIPE, # Перенаправляем stdout
|
||||||
|
stderr=subprocess.PIPE, # Перенаправляем stderr
|
||||||
|
text=True # Декодируем вывод в текст
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Ошибка выполнения скрипта: {result.stderr}")
|
||||||
|
else:
|
||||||
|
print("Модель успешно создана.")
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Не удалось выполнить скрипт с телевизорами: {str(e)}")
|
||||||
|
|
||||||
|
def get_unique_data(self):
|
||||||
|
# Указываем путь к файлу
|
||||||
|
file_path = 'services/ml/scripts/modelBuilders/columns/unique_values_tv.json'
|
||||||
|
|
||||||
|
# Открываем и читаем данные из файла
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
data = json.load(file) # Загружаем данные из JSON
|
||||||
|
|
||||||
|
# Возвращаем данные, которые будут переданы в ответ
|
||||||
|
return data
|
Loading…
x
Reference in New Issue
Block a user