Новая Школа - онлайн-школа подготовки к ЕГЭ
Задача Задача №3646893
А
Аноним
10 сентября 2026 12:53 · публичный вопрос
А
Аноним

Вам предстоит работать над каталогом мобильных приложений RuStore. В день на модерацию попадают сотни новых приложений. Чтобы пользователи могли легко находить нужное, каждое…

Запрос пользователя

Вам предстоит работать над каталогом мобильных приложений RuStore. В день на модерацию попадают сотни новых приложений. Чтобы пользователи могли легко находить нужное, каждое приложение должно быть привязано к одной или нескольким категориям — например, finance, tools, children, arcade. Сейчас модераторы тратят часы, вручную читая описание и определяя, куда отнести каждое приложение. Это долго, скучно и дорого. Ваша задача — автоматизировать этот процесс: научиться по тексту описания приложения определять его категории. Что дано Вам предоставлены тексты приложений в двух вариантах: full_description — полное описание приложения (если есть); shortDescription — краткое описание (если есть). app_name — название приложения; labels_str — целевые категории, перечисленные через “|”. Что нужно сделать По app_name и описаниям для приложений из тестового набора предсказать до трёх категорий, упорядоченных по убыванию уверенности

Ответ нейросети

Краткий ответ

Это задача многометочной классификации текста: у одного приложения может быть несколько категорий. Нужно объединить название и описания, обучить отдельный классификатор для каждой категории, получить оценки уверенности и выбрать максимум три категории.

Без файлов train и test нельзя вывести конкретные предсказания, но можно использовать такой базовый алгоритм.

Основные шаги

  1. Заполнить пропуски в описаниях пустыми строками.
  2. Объединить app_name, shortDescription и full_description.
  3. Преобразовать labels_str из вида
    finance|tools
    в набор отдельных меток.
  4. Представить текст через TF-IDF:
    • словесные n-граммы находят смысловые фразы;
    • символьные n-граммы помогают учитывать названия, опечатки и разные языки.
  5. Обучить модель OneVsRestClassifier: для каждой категории решается отдельная задача «подходит / не подходит».
  6. Отсортировать категории по вероятности и оставить до трёх.

Пример решения на Python

import numpy as np
import pandas as pd

from scipy.sparse import hstack
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score


train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")


def get_column(df, name):
    if name in df.columns:
        return df[name].fillna("").astype(str)
    return pd.Series("", index=df.index)


def make_text(df):
    app_name = get_column(df, "app_name")
    short_description = get_column(df, "shortDescription")
    full_description = get_column(df, "full_description")

    # Название повторяем дважды: оно обычно особенно информативно
    return (
        " app_name " + app_name + " " + app_name
        + " short_description " + short_description
        + " full_description " + full_description
    )


def parse_labels(value):
    if pd.isna(value):
        return []

    return [
        label.strip()
        for label in str(value).split("|")
        if label.strip()
    ]


train_text = make_text(train)
test_text = make_text(test)

target = train["labels_str"].map(parse_labels)

# Превращаем списки категорий в бинарную матрицу
# finance|tools -> [1, 1, 0, ...]
mlb = MultiLabelBinarizer()
y = mlb.fit_transform(target)


def make_vectorizers():
    word_vectorizer = TfidfVectorizer(
        ngram_range=(1, 2),
        sublinear_tf=True,
        min_df=1,
        max_df=0.99
    )

    char_vectorizer = TfidfVectorizer(
        analyzer="char",
        ngram_range=(3, 5),
        sublinear_tf=True,
        min_df=2,
        max_features=200_000
    )

    return word_vectorizer, char_vectorizer


def make_model():
    return OneVsRestClassifier(
        LogisticRegression(
            C=3.0,
            max_iter=1500,
            solver="liblinear",
            class_weight="balanced"
        ),
        n_jobs=-1
    )


# Выбор категорий по уверенности
def choose_indices(probabilities, threshold=0.20, max_labels=3):
    order = np.argsort(probabilities)[::-1]

    selected = [
        index
        for index in order
        if probabilities[index] >= threshold
    ][:max_labels]

    # Если все вероятности ниже порога, оставляем хотя бы одну категорию
    if not selected:
        selected = order[:1].tolist()

    return selected


def to_binary(probabilities, threshold):
    result = np.zeros_like(probabilities, dtype=np.int8)

    for row_number, row in enumerate(probabilities):
        indices = choose_indices(row, threshold)
        result[row_number, indices] = 1

    return result


# ---------- Проверка на отложенной выборке ----------

indices = np.arange(len(train))

train_indices, valid_indices = train_test_split(
    indices,
    test_size=0.2,
    random_state=42
)

word_vectorizer, char_vectorizer = make_vectorizers()

x_train = hstack([
    word_vectorizer.fit_transform(train_text.iloc[train_indices]),
    char_vectorizer.fit_transform(train_text.iloc[train_indices])
]).tocsr()

x_valid = hstack([
    word_vectorizer.transform(train_text.iloc[valid_indices]),
    char_vectorizer.transform(train_text.iloc[valid_indices])
]).tocsr()

model = make_model()
model.fit(x_train, y[train_indices])

valid_probabilities = model.predict_proba(x_valid)

# Подбираем порог по micro-F1
best_score = -1
best_threshold = 0.20

for threshold in np.arange(0.05, 0.66, 0.05):
    valid_prediction = to_binary(valid_probabilities, threshold)

    score = f1_score(
        y[valid_indices],
        valid_prediction,
        average="micro",
        zero_division=0
    )

    if score > best_score:
        best_score = score
        best_threshold = threshold

print("Лучший порог:", best_threshold)
print("Validation micro-F1:", best_score)


# ---------- Обучение на всех данных ----------

word_vectorizer, char_vectorizer = make_vectorizers()

x_all = hstack([
    word_vectorizer.fit_transform(train_text),
    char_vectorizer.fit_transform(train_text)
]).tocsr()

x_test = hstack([
    word_vectorizer.transform(test_text),
    char_vectorizer.transform(test_text)
]).tocsr()

final_model = make_model()
final_model.fit(x_all, y)

test_probabilities = final_model.predict_proba(x_test)

predicted_labels = []

for row in test_probabilities:
    indices = choose_indices(row, best_threshold)

    labels = [
        str(mlb.classes_[index])
        for index in indices
    ]

    # Формат категорий: finance|tools|children
    predicted_labels.append("|".join(labels))


submission = pd.DataFrame({
    "labels_str": predicted_labels
})

submission.to_csv("submission.csv", index=False)

Как работает выбор категорий

Например, модель получила такие оценки:

Категория Уверенность
finance 0.82
tools 0.57
children 0.12
arcade 0.04

При пороге 0.20 результатом будет:

finance|tools

Категории уже упорядочены по убыванию уверенности.

Важно: это именно многометочная, а не обычная многоклассовая классификация. Поэтому вероятности разных категорий не обязаны в сумме давать единицу: приложение может одновременно относиться к finance, tools и children.