Мотивация и постановка задачи
В индустрии путешествий выбор отеля определяется не только ценой или фотографиями, но и уровнем доверия к источнику рекомендаций. Официальные отзывы часто кажутся обезличенными, а алгоритмические подборки - слишком «машинными» и лишёнными человеческого контекста.
В то же время мы знаем, что рекомендации от друзей и знакомых воспринимаются куда более надёжными и релевантными. Однако большинство сервисов бронирования до сих пор не имеют удобных инструментов для сбора и использования такого социально обусловленного опыта. В итоге путешественники спрашивают советы напрямую у друзей, ищут рекомендации на форумах или опираются на случайные видео в соцсетях. Мне хотелось предоставить пользователям социально персонализированные рекомендации, которые увеличивают вовлечённость, доверие и вероятность бронирования.
Функциональность «Друзья рекомендуют» призвана закрыть этот разрыв: она позволяет путешественнику получать персонализированные рекомендации отелей на основе опыта и отзывов его друзей и знакомых. Такой механизм даёт пользователю возможность:
находить отели, в которых действительно останавливались его друзья;
читать свежие, надёжные и лично релевантные отзывы.
Функциональная цель
Мы поставили задачу разработать прототип системы «Друзья рекомендуют» - сервиса, который позволяет пользователю находить отели, в которых бывали его друзья, подписчики или коллеги, и просматривать их отзывы.
User Stories
Как путешественник, я хочу видеть рекомендации отелей от своих друзей, чтобы быть уверенным в их качестве и актуальности.
Как владелец отеля, я хочу, чтобы довольные гости могли легко рекомендовать мой отель своим друзьям.
Поскольку у нас не было реального датасета социальной графовой структуры, мы решили сгенерировать его синтетически и протестировать различные архитектуры для обучения персонализированных рекомендаций. Это привело нас к разработке и оценке Deep & Cross Network with Residual Blocks (DCN-R) - гибридной модели, способной одновременно учитывать явные взаимодействия на основе друзей и скрытые латентные паттерны в поведении «пользователь–объект».
Абстракция
В промышленных рекомендательных системах данные, как правило, огромные, разреженные и табличные - и это создаёт ключевую проблему: необходимость одновременно обеспечивать эффективное запоминание и обобщение.
Запоминание - это обучение частым и явным совместно встречающимся признакам; она критически важна для точных рекомендаций, основанных на прямом, наблюдаемом сигнале.
Обобщение же - это способность обнаруживать новые, более сложные комбинации признаков, что необходимо для устойчивости модели и появления разнообразных, неожиданных рекомендаций.
Стандартные глубокие нейросети (DNN) превосходно справляются с обобщением, но плохо подходят для обучения простым и явным пересечениям признаков - слишком много параметров тратится неэффективно.
Чтобы решить этот конфликт, мы предлагаем Deep & Cross Network with Residual Blocks (DCN-R) - новую гибридную архитектуру, способную явно моделировать взаимодействия признаков всех порядков за счёт параллельной работы двух специализированных подсетей. Такая составная архитектура позволяет DCN-R формировать богатое и комплексное представление данных, достигая оптимального баланса между точностью рекомендаций и их разнообразием в задачах ранжирования.
1. Введение
Как я уже говорил, рекомендательные системы стали повсеместным и критически важным компонентом современного цифрового пространства, и их эффективность напрямую зависит от того, насколько хорошо они способны моделировать сложные пользовательские предпочтения и характеристики объектов в условиях масштабных, разреженных и высокоразмерных табличных данных. В основе этой задачи лежит фундаментальная архитектурная дилемма: баланс между запоминанием и обобщением. Давайте подробнее разберёмся, что именно здесь происходит.
Запоминание - это обучение прямым, часто встречающимся взаимодействиям между признаками, что позволяет эффективно использовать наблюдаемые в исторических данных паттерны. Обобщение, напротив, - это способность обнаруживать скрытые, более сложные комбинации признаков и применять это знание к новым, ранее невстречавшимся ситуациям, что критически важно для разнообразия и устойчивости рекомендаций.
Оригинальная Deep & Cross Network (DCN) и её преемник DCN-V2 успешно решали задачу явного пересечения признаков, однако их «глубокая» часть обычно строится на стандартном многослойном перцептроне (MLP). И хотя такой подход работает, он потенциально создаёт узкое место в представлениях. По мере увеличения сложности скрытых пользовательских предпочтений простой MLP может оказаться недостаточно глубоким и выразительным: ему становится сложно обучать богатые представления, а обучение начинает страдать от деградации градиентов и проблем с оптимизацией.
В этой статье мы представляем Deep & Cross Network with Residual Blocks (DCN-R) -архитектуру, которая напрямую устраняет указанное ограничение. Наша основная гипотеза состоит в том, что замена стандартного MLP на глубокую сеть, построенную из остаточных блоков (Residual Blocks, ResBlocks), позволяет существенно усилить способность модели к обобщению.
Благодаря skip-connection, inherent в ResBlocks, такая архитектура обеспечивает стабильное обучение гораздо более глубоких сетей. Это позволяет модели захватывать более сложные и высокоуровневые абстракции признаков, чем это возможно в рамках обычного MLP.
Наши основные вклад в работе состоит в следующем:
Интеграция Residual Blocks в глубокий компонент DCN-подобной архитектуры, что усиливает способность модели захватывать сложные, высокопорядковые абстракции признаков.
Разработка полного, сквозного описания архитектуры модели, методологии обучения и пайплайна инференса, что обеспечивает воспроизводимость и прозрачность экспериментов.
Эмпирическая проверка архитектурных решений через абляционное исследование, демонстрирующее необходимость как гибридной структуры, так и остаточных связей для достижения оптимальной производительности.
Связанные работы
Архитектура DCN-R опирается на обширную линию исследований в области глубокого обучения для рекомендательных систем, объединяя несколько влиятельных парадигм в единый согласованный подход.
Ранний прогресс в этом направлении был связан с работой Wide & Deep Learning, предложенной Google - одной из ключевых разработок, представившей идею гибридных архитектур, сочетающих простой линейный компонент (Wide, отвечающий за запоминание) с глубокой нейронной сетью (Deep, отвечающей за обобщение). Эта концепция заложила основу для балансировки явных взаимодействий между признаками и обучения скрытых представлений.
Развивая идеи, популяризированные Google, Factorization Machines (FM) предложили элегантный и вычислительно эффективный механизм моделирования взаимодействий второго порядка. Их нейронное расширение - DeepFM - объединило сильные стороны FM и глубоких сетей, позволив модели одновременно изучать низко и высокоуровневые взаимодействия. Однако и эти архитектуры имели ограничения: они не обеспечивали систематичного моделирования взаимодействий более высоких порядков.
Чтобы преодолеть эти ограничения, Deep & Cross Network (DCN) ввела компонент Cross Network - обучаемый и более общий механизм пересечения признаков, фактически заменивший ручное проектирование Wide-части автоматизированной альтернативой. Позднее появились модели вроде AutoInt, которые исследовали подходы, основанные на внимании: используя self-attention, они обучались определять относительную важность различных комбинаций признаков. Несмотря на высокую выразительность, такие механизмы внимания моделируют взаимодействия неявно, что часто снижает интерпретируемость и усложняет структурный контроль.
В нашей работе мы уточняем и расширяем парадигму DCN, усиливая её способность к обобщению за счёт интеграции остаточных соединений - проверенной архитектурной техники, широко применяемой в компьютерном зрении. Объединяя преимущества явного моделирования пересечений признаков и стабильного распространения градиента, DCN-R обеспечивает более сбалансированную и расширяемую структуру для обучения представлений.
3. Общая архитектура и поток данных
DCN-R спроектирована как многопоточный пайплайн обучения признаков, цель которого - сформировать единый, высокоточный ранжирующий скор. Архитектура модели представляет собой последовательный процесс, состоящий из четырёх основных этапов.
Формирование входных данных и эмбеддингов: исходные признаки (коллаборативные, категориальные и числовые) преобразуются в единый плотный вектор
Параллельное извлечение признаков: вектор x0x0 одновременно подаётся в два модуля - Cross Network для обучения явных взаимодействий и глубокую остаточную сеть для обучения скрытых абстракций.
Слияние и объединение: выходные векторы обеих подсетей конкатенируются, формируя итоговое представление признаков.
-
Финальное предсказание: к объединённому вектору применяется линейное преобразование, которое даёт единственный скалярный логит - итоговый ранжирующий скор.
Такой параллельный дизайн позволяет модели одновременно изучать как явные пересечения признаков, так и скрытые глубокие паттерны.

4. Архитектурные компоненты в деталях
4.1. Input & Embedding Layer
Этот слой преобразует разреженный, разнородный вход в единый плотный числовой вектор .
Обработка высокоразмерных категориальных признаков выполняется через эмбеддинги.
Такие признаки, как user_id и item_id, отображаются в обучаемые низкоразмерные векторные представления. Для каждого признака создаётся матрица эмбеддингов, где
- размер словаря, а
- размерность эмбеддинга. Модель выполняет осмотр, извлекая соответствующий вектор для каждого индекса признака. Все матрицы эмбеддингов являются обучаемыми параметрами.
Непрерывные признаки, такие как price_rub, нормализуются с помощью Min–Max масштабирования в единый диапазон по формуле:
Затем идет формирование итогового входного вектора
Все эмбеддинги и нормализованные числовые признаки конкатенируются в единый итоговый вектор:
= [ emb(user_id) ⊕ emb(item_id) ⊕ ... ⊕ norm_numerical_features ]
где символ ⊕ обозначает операцию конкатенацию.
4.2. Компонент запоминания: Cross Network
Cross Network явно и эффективно обучаетcя взаимодействию признаков ограниченной степени. Он состоит из последовательности слоёв CrossLayer, каждый из которых определяется следующей формулой:
Где:
: выходные векторы из
го и
- го слоёв Cross Network.
: исходный входной вектор, полученный из слоя эмбеддингов.
: обучаемые параметры - веса и смещения для
- го слоя.
: поэлементное произведение (произведение Адамара).
Эта изящная формула позволяет Cross Network явно строить взаимодействия признаков порядка на каждом слое
, используя всего
параметров, что делает его чрезвычайно эффективным механизмом запоминания. Завершающий член
выступает как остаточное соединение, сохраняющее ранее выученные взаимодействия.
4.3. Компонент обобщения: глубокая остаточная сеть
Глубокая подсеть служит «движком» обобщения, предназначенным для выявления скрытых, неявных и высокоуровневых паттернов. Чтобы преодолеть проблему затухающего градиента в глубоких моделях, она построена на основе остаточных блоков (Residual Blocks, ResBlocks). Каждый такой блок вычисляет следующее преобразование:
#структура CrossLayer блока
class CrossLayer(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.w = nn.Linear(input_dim, 1, bias=False)
self.b = nn.Parameter(torch.zeros(input_dim))
def forward(self, x):
x_0 = x.unsqueeze(2)
x_t = x.unsqueeze(1)
return x_0.squeeze(2) + torch.matmul(x_0, self.w(x_t)).squeeze(2) + self.b
#структура ResBlock блока
class ResBlock(nn.Module):
def __init__(self, hidden_dim, dropout):
super().__init__()
self.layer1 = nn.Linear(hidden_dim, hidden_dim)
self.bn1 = nn.BatchNorm1d(hidden_dim)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(dropout)
self.layer2 = nn.Linear(hidden_dim, hidden_dim)
self.bn2 = nn.BatchNorm1d(hidden_dim)
def forward(self, x):
identity = x
out = self.layer1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.dropout(out)
out = self.layer2(out)
out = self.bn2(out)
out += identity
out = self.relu(out)
return out
class DCN_RecSys(nn.Module):
def __init__(self, n_users, n_items, cat_dims, n_num_features, params):
super().__init__()
emb_dim = params['emb_dim']
hidden_dim = params['hidden_dim']
n_cross_layers = params['n_cross_layers']
dropout = params['dropout']
# Получить количество блоков ResNet из параметров
n_res_blocks = params.get('n_res_blocks', 2) # По умолчанию 2, если не указано иное
self.user_embedding = nn.Embedding(n_users, emb_dim)
self.item_embedding = nn.Embedding(n_items, emb_dim)
self.cat_embeddings = nn.ModuleList([
nn.Embedding(n_cat, int(np.sqrt(n_cat)) + 1) for n_cat in cat_dims.values()])
cat_emb_sum_dim = sum([int(np.sqrt(n_cat)) + 1 for n_cat in cat_dims.values()])
input_dim = emb_dim * 2 + cat_emb_sum_dim + n_num_features
self.initial_deep_layer = nn.Linear(input_dim, hidden_dim)
#Создаем блоки ResNet в цикле и сохраняем их в ModuleList
self.res_blocks = nn.ModuleList([
ResBlock(hidden_dim, dropout) for _ in range(n_res_blocks)
])
self.cross_network = nn.ModuleList([CrossLayer(input_dim) for _ in range(n_cross_layers)])
final_dim = hidden_dim + input_dim
self.final_linear = nn.Linear(final_dim, 1)
def forward(self, user_ids, item_ids, cat_features, num_features):
user_emb = self.user_embedding(user_ids)
item_emb = self.item_embedding(item_ids)
cat_embs = [emb(cat_features[:, i]) for i, emb in enumerate(self.cat_embeddings)]
x_0 = torch.cat([user_emb, item_emb] + cat_embs + [num_features], dim=1)
deep_out = self.initial_deep_layer(x_0)
#Пропускаем данные через все блоки ResNet в цикле
for res_block in self.res_blocks:
deep_out = res_block(deep_out)
cross_out = x_0
for layer in self.cross_network: cross_out = layer(cross_out)
final_input = torch.cat([deep_out, cross_out], dim=1)
return self.final_linear(final_input).squeeze()
4.4. Слияние и выходной слой
На финальном этапе информация из обеих подсетей объединяется. Выходной вектор из последнего слоя Cross Network (cross_out) и выходной вектор из последнего Residual Block (deep_out) конкатенируются:
Затем объединённый вектор проходит через финальный линейный слой с одним нейроном, который формирует неограниченный скалярный скор (логит):
Финальная функция активации не используется, поскольку для задачи ранжирования важен лишь относительный порядок скора.
5. Методология обучения и экспериментальная проверка
5.1. Функция потерь
Хотя финальный вывод модели используется для ранжирования, обучение проводится как задача бинарной классификации: нужно предсказать вероятность положительного исхода (was_booked = 1). Для этого применяется функция Binary Cross-Entropy with Logits Loss (BCEWithLogitsLoss). Это стандартный выбор для бинарной классификации, обеспечивающий численную стабильность и хорошую сходимость.
Функция потерь L для батча из N примеров вычисляется как:
Где:
- истинная бинарная метка (1 для
was_booked, 0 в остальных случаях).- сырое скалярное значение (логит), выдаваемое моделью(y c крышкой^).
Хотя BCEWithLogitsLoss считается каноническим выбором для бинарной классификации, в нашей задаче финальный выход модели используется исключительно как ранжировочный скор. Для таких моделей, где важен именно порядок, а не калиброванная вероятность, MSE нередко выступает сильной и стабильной прокси-функцией потерь, поскольку цель состоит не в точном предсказании вероятностей, а в обеспечении правильного относительного упорядочивания объектов.
Мы эмпирически обнаружили, что MSE обеспечивает такую же или лучшую эффективность ранжирования на данном датасете.
5.2. Стратегия оптимизации
Мы используем оптимизатор AdamW, поскольку он более корректно обрабатывает регуляризацию через weight decay. Скорость обучения динамически изменяется с помощью планировщика ReduceLROnPlateau, который уменьшает learning rate, когда значение функции потерь на валидации перестаёт улучшаться.
# Цикл обучения и валидации с Optuna + early stopping
n_epochs, patience, best_val_loss, epochs_no_improve = 50, 5, float('inf'), 0
for epoch in range(n_epochs):
model.train()
for X_collab_b, X_cat_b, X_num_b, y_b in train_dl:
user_ids = X_collab_b[:, 0].to(device)
item_ids = X_collab_b[:, 1].to(device)
cat_features = X_cat_b.to(device)
num_features = X_num_b.to(device)
y = y_b.to(device)
optimizer.zero_grad()
preds = model(user_ids, item_ids, cat_features, num_features)
loss = loss_fn(preds, y.float())
loss.backward()
optimizer.step()
# Валидация на полном валидационном сете
model.eval()
with torch.no_grad():
user_ids_val = X_val_collab[:, 0].to(device)
item_ids_val = X_val_collab[:, 1].to(device)
cat_features_val = X_val_cat.to(device)
num_features_val = X_val_num.to(device)
preds = model(user_ids_val, item_ids_val, cat_features_val, num_features_val)
val_loss = loss_fn(preds, y_val.to(device).float()).item()
# Шедулер + репорт в Optuna
scheduler.step(val_loss)
trial.report(val_loss, epoch)
if trial.should_prune():
raise optuna.exceptions.TrialPruned()
# Обновляем лучший чекпоинт
if val_loss < best_val_loss:
best_val_loss = val_loss
epochs_no_improve = 0
model_path = f"checkpoints/best_model_trial_{trial.number}.pth"
torch.save(model.state_dict(), model_path)
trial.set_user_attr("best_model_path", model_path)
else:
epochs_no_improve += 1
# Простейший early stopping по валидации
if epochs_no_improve >= patience:
logging.info(f"Early stopping at epoch {epoch + 1} for trial {trial.number}")
break
Экспериментальная методология и результаты
Чтобы обеспечить надёжность модели и подтвердить корректность архитектурных решений, мы провели строгий экспериментальный процесс, включающий автоматизированный подбор гиперпараметров и комплексное абляционное исследование.
Оптимизация гиперпараметров
def objective(trial, train_data_tensors, val_data_tensors, model_dims):
X_train_collab, X_train_cat, X_train_num, y_train = train_data_tensors
X_val_collab, X_val_cat, X_val_num, y_val = val_data_tensors
n_users, n_items, cat_dims, n_num_features = model_dims
# Поиск гиперпараметров через Optuna
params = {
'emb_dim': trial.suggest_categorical("emb_dim", [16, 24, 32, 48, 64]),
'hidden_dim': trial.suggest_int("hidden_dim", 32, 512, step=32),
'n_cross_layers': trial.suggest_int("n_cross_layers", 1, 6),
'n_res_blocks': trial.suggest_int("n_res_blocks", 1, 4),
'dropout': trial.suggest_float("dropout", 0.1, 0.7, step=0.05),
'lr': trial.suggest_float("lr", 1e-5, 1e-2, log=True),
'batch_size': trial.suggest_categorical("batch_size", [512, 1024, 2048, 4096]),
'weight_decay': trial.suggest_float("weight_decay", 1e-6, 1e-1, log=True),
'optimizer_name': trial.suggest_categorical("optimizer_name", ["AdamW", "Adam"]),
'lr_scheduler_patience': trial.suggest_int("lr_scheduler_patience", 1, 3),
'lr_scheduler_factor': trial.suggest_float("lr_scheduler_factor", 0.1, 0.5, step=0.1)
}
# Датасет и DataLoader под выбранный batch_size
train_ds = TensorDataset(X_train_collab, X_train_cat, X_train_num, y_train)
train_dl = DataLoader(train_ds, batch_size=params['batch_size'], shuffle=True)
# Модель с текущим набором гиперпараметров
model = DCN_RecSys(n_users, n_items, cat_dims, n_num_features, params).to(device)
# Оптимизатор: AdamW или Adam
if params['optimizer_name'] == 'AdamW':
optimizer = torch.optim.AdamW(
model.parameters(),
lr=params['lr'],
weight_decay=params['weight_decay']
)
else:
optimizer = torch.optim.Adam(
model.parameters(),
lr=params['lr'],
weight_decay=params['weight_decay']
)
# Лосс для бинарной классификации по логитам
loss_fn = nn.BCEWithLogitsLoss()
# ReduceLROnPlateau: режем lr, если val_loss не падает
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode='min',
patience=params['lr_scheduler_patience'],
factor=params['lr_scheduler_factor']
)
# дальше идёт цикл обучения/валидации (см. выше)
Оптимальная комбинация архитектурных и тренировочных параметров была найдена с помощью фреймворка Optuna. Мы задали широкий поисковый диапазон для ключевых параметров,такихкак emb_dim, hidden_dim, n_cross_layers, n_res_blocks, dropout, lr и weight_decay. Процесс оптимизации, основанный на сэмплере TPE и механизме Median Pruner, был запущен на 300 итераций с применением ранней остановки.


dropout, weight_decay) и скорость обучения оказались наиболее критичными для достижения хорошей обобщающей способности на нашем высоко-сигнальном датасете.Процесс оптимизации сошёлся на элегантной, эффективной и сильно регуляризованной архитектуре (dropout = 0.6, emb_dim = 16, n_res_blocks = 1), а не на модели «чем больше - тем лучше». Это идеально соответствует соотношению сигнал/шум в обучающих данных.
5.3. Абляционные исследования
Чтобы количественно оценить вклад каждого архитектурного компонента, мы провели серию абляционных экспериментов. Для каждого варианта модели выполнялся отдельный независимый подбор гиперпараметров (по 100 итераций на вариант).
Вариант модели |
Описание |
Best Val Logloss ↓ |
Best Val AUC ↑ |
DCN - R (Full Model) |
Полная гибридная архитектура |
0.1686 |
0.9526 |
Cross Network Only |
Удалён компонент Deep Network |
0.1886 |
0.9310 |
Deep Network Only |
Удалён компонент Cross Network |
0.1901 |
0.9204 |
DCN with standard MLP |
ResBlocks заменены на стандартный MLP |
0.1901 |
0.9292 |
(Примечание: более низкий LogLoss означает лучший результат, более высокий AUC - лучший результат.)
Анализ результатов
Абляционное исследование выявило несколько ключевых выводов:
1. Преобладание явных взаимодействий признаков.
Вариант «Cross Network Only» (Модель 2) продемонстрировал сильные результаты, что подчёркивает: предсказательный сигнал в нашем датасете действительно доминируется эксплицитными взаимодействиями признаков. Наша стратегия предварительной обработки данных сформировала «чистое» пространство, ориентированное на запоминание, в котором хорошо настроенный Cross Network показывает высокую эффективность.
2. Негативный эффект использования стандартного MLP.
Вариант «DCN with standard MLP» (Модель 4) показал худший AUC среди всех тестируемых архитектур. Это свидетельствует о том, что включение обычного MLP ухудшает работу Cross Network, вероятно вводя дополнительный шум и не обеспечивая достаточной выразительности.
3. Подтверждение эффективности архитектуры на основе Residual Blocks.
Полный вариант модели DCN-R (Модель 1) стал лучшей моделью среди всех протестированных конфигураций, значительно опередив как Cross-only, так и MLP-базовые варианты. Это демонстрирует устойчивость и выразительность глубокого компонента, построенного на Residual Blocks.
В отличие от стандартного MLP, ResNet-блоки способны усиливать обобщающую способность модели без деградации производительности, что подтверждает их преимущество в составе гибридной архитектуры.
5.4. Анализ оптимальных гиперпараметров
Процесс оптимизации гиперпараметров сошёлся к конфигурации, которая позволяет сделать важные выводы о поведении модели на нашем датасете:
dropout = 0.6Выбор настолько высокого dropout подчёркивает высокую ёмкость модели и её склонность к переобучению на «чистом», предварительно отфильтрованном датасете. Optuna корректно определила, что сильная регуляризация является ключевым фактором для достижения хорошей обобщающей способности.emb_dim = 16Оптимизатор предпочёл минимальный доступный размер эмбеддинга. Это говорит о том, что компактного представления оказалось достаточно для передачи семантики пользователей и объектов, что делает модель параметрически более эффективной.n_res_blocks = 1Оптимальной оказалась сравнительно неглубокая модель с одним residual-блоком. Это означает, что для данной задачи не требуются чрезмерно глубокие абстракции, и одного мощного нелинейного преобразования достаточно.batch_size = 512Использование наименьшего размера батча выступает в роли стохастической регуляризации, помогая оптимизатору находить более устойчивый локальный минимум - что согласуется с общей необходимостью в сильной регуляризации.
Итог: процесс оптимизации не привёл к «максимально большой» модели, а наоборот - к элегантной, эффективной и сильно регуляризованной архитектуре, которая идеально соответствует соотношению сигнал/шум в данных.
6. Деплоймент-архитектура и API
Чтобы сделать обученную модель DCN-R доступной для реального использования, мы разработали и внедрили продакшн-готовую архитектуру деплоймента, основанную на принципах микросервисов.
6.1. Архитектура системы
Система состоит из двух основных сервисов, управляемых через Docker Compose:
1. ML-сервис (FastAPI)
Статлесс-сервис, инкапсулирующий весь ML-конвейер.
Его единственная задача - выполнять вычислительно сложные операции.
Он предоставляет два ключевых эндпоинта:
POST
/recommendations
Выполняет полный двухэтапный пайплайн рекомендаций
(candidate generation → ranking)
и возвращает персонализированный отсортированный списокhotel_id.GET
/similar_items
Вспомогательный эндпоинт, который находит отели, похожие на заданныйitem_id,
используя k-NN-индекс, построенный на эмбеддингах отелей.
2. База данных (PostgreSQL)
Персистентное хранилище, содержащее все ключевые данные:
Пользователей
Отели
Отзывы
Связи друзей (friendships)
Такое раздельное построение сервисов гарантирует, что ML-логика остаётся полностью изолированной от основного backend-приложения. Это позволяет:
независимо масштабировать вычислительные мощности ML-сервиса
обновлять модель без остановки остальной системы
упрощать сопровождение и развёртывание
6.2. API и поток данных
Когда пользователь запрашивает рекомендации, процесс выглядит следующим образом:
Браузер пользователя отправляет запрос в основной backend приложения (который не входит в этот репозиторий).
Основной backend аутентифицирует пользователя и затем отправляет запрос POST в наш ML-сервис по адресу
/recommendations, передаваяuser_idиcity.ML-сервис обращается к базе данных PostgreSQL, чтобы получить необходимые данные: - список друзей пользователя, - их отзывы, - признаки отелей.
ML-сервис выполняет полный пайплайн рекомендаций (генерация кандидатов + ранжирование) и возвращает отсортированный список
hotel_id.Основной backend принимает этот список, дополняет его дополнительной информацией (например, названия отелей, фотографии, цены из БД) и отправляет полностью сформированный ответ обратно в браузер пользователя.
6.3. Управление артефактами
Для обеспечения надёжности, версионирования и масштабируемости мы разделили хранение метаданных моделии хранение крупных бинарных артефактов.
В качестве основы мы реализовали паттерн Model Registry, используя основную базу данных PostgreSQL.
Создана отдельная таблица ml_models, которая служит централизованным каталогом всех обученных моделей. Для каждой версии модели в таблице хранятся:
Метаданные: версия модели, время создания, метрики качества (AUC, LogLoss), оптимальные гиперпараметры.
Указатели на артефакты: вместо хранения самих файлов таблица содержит пути к весам модели (
.pth), эмбеддингам (.npy) и другим сериализованным объектам (.gz).
Крупные артефакты сохраняются в выделенном высокопроизводительном Object Storage (например, Amazon S3, Google Cloud Storage). Такое раздельное хранение даёт несколько ключевых преимуществ:
Масштабируемость
API-сервис может быстро скачивать артефакты из объектного хранилища, не нагружая транзакционную базу данных.
Версионирование и контроль
Model Registry становится единственным источником правды, фиксируя полную историю экспериментов и продакшн-моделей.
Атомарные деплойменты
Промоут новой модели в продакшн сводится к атомарному обновлению флага is_active в таблице.Это делает обновление безопасным и легко обратимым.
При запуске ML-сервис:
Делает запрос в реестр моделей
Получает информацию об активной версии
Скачивает артефакты из объектного хранилища
Загружает их в память
7. Заключение
В данной работе мы представили DCN-R - гибридную нейросетевую архитектуру, эффективно решающую ключевую задачу баланса между запоминанием и обобщением.
Путём тщательной оптимизации гиперпараметров и проведения обширных абляционных экспериментов мы продемонстрировали, что модель достигает высоких результатов на валидации: (AUC: 0.9526, LogLoss: 0.1686).
Основным выводом исследования является то, что для рекомендационных задач с высоким уровнем сигнала и низким уровнем шума хорошо настроенный Cross Network выступает основным источником предсказательной мощности.
Кроме того, мы эмпирически показали, что выбор архитектуры глубокого компонента имеет критическое значение: Residual Network обеспечивает устойчивое и надёжное обобщение, тогда как использование стандартного MLP может снижать качество модели.
Комбинируя преимущества двух специализированных компонент - Cross Network и Residual Network - архитектура DCN-R формирует мощный и адаптируемый фундамент для построения высокопроизводительных recommendation-систем, готовых к применению в реальных продуктах.