Разбор второго тура Регионального этапа ВсОШ по ИИ 2025/2026¶

Скачать Jupyter Notebook и датасеты

A. Острова рекомендаций¶

In [ ]:
import pandas as pd
from collections import defaultdict, deque

items = pd.read_csv("items_A.csv")
also = pd.read_csv("also_viewed_A.csv")

items.head(), also.head()
Out[ ]:
(   item_id     category   price  rating     brand  in_stock
 0        1      laptops  161790     4.3     Alpha         1
 1        2         toys    7612     4.5  ReadMore         1
 2        3         home   39088     4.7   WorkPro         0
 3        4        books    2808     3.2     Delta         1
 4        5  accessories    3279     4.8  ReadMore         0,
    item_from  item_to
 0          7       11
 1         11        7
 2         11       22
 3         22       11
 4         22       30)
In [ ]:
# Вопрос 1.
# Сколько товаров категории phones с rating >= 4.5 и in_stock = 1?

q1 = items[
    (items["category"] == "phones") &
    (items["rating"] >= 4.5) &
    (items["in_stock"] == 1)
].shape[0]

print(q1)
22
In [ ]:
# Вопрос 2.
# Среди laptops найти бренд с максимальной средней ценой.

laptops = items[items["category"] == "laptops"]
avg_price_by_brand = laptops.groupby("brand")["price"].mean()

q2_brand = avg_price_by_brand.idxmax()
print(q2_brand)
Gamma
In [ ]:
# Вопрос 3.
# Среди товаров, которые есть в наличии (in_stock = 1), посчитайте, сколько товаров относится к сегменту premium.


def segment(row):
    if row["rating"] >= 4.5 and row["price"] >= 50000:
        return "premium"
    elif row["rating"] >= 4.0 and row["price"] < 50000:
        return "standard"
    else:
        return "budget"


items["segment"] = items.apply(segment, axis=1)

answer3 = items[(items["in_stock"] == 1) & (items["segment"] == "premium")].shape[0]
print(answer3)
25
In [ ]:
# Построим структуру соседей между товарами.
# Два товара считаются соседями, если они стоят парой хотя бы в одной строке also_viewed
# (в любом порядке).

adj = defaultdict(set)
for _, row in also.iterrows():
    a = int(row["item_from"])
    b = int(row["item_to"])
    if a == b:
        continue
    adj[a].add(b)
    adj[b].add(a)

# Убедимся, что для всех item_id есть запись в adj (возможно, пустое множество соседей)
for item_id in items["item_id"]:
    item_id = int(item_id)
    if item_id not in adj:
        adj[item_id] = set()

len(adj)
Out[ ]:
500
In [ ]:
# Вопрос 4.
# Найти все товары, которые хотя бы раз встречаются в item_to.
# Для каждой категории посчитать количество разных товаров этой категории в item_to.
# Сформировать таблицу category,cnt и сохранить в answer3.csv.

unique_item_to = also["item_to"].unique()
items_on_to = items[items["item_id"].isin(unique_item_to)]

counts_by_cat = items_on_to.groupby("category")["item_id"].nunique()

all_cats = sorted(items["category"].unique())
data_rows = []
for cat in all_cats:
    cnt = int(counts_by_cat.get(cat, 0))
    data_rows.append({"category": cat, "cnt": cnt})

answer3 = pd.DataFrame(data_rows)
answer3 = answer3.sort_values("category")
display(answer3)

answer3.to_csv("answer4.csv", index=False)
print("answer4.csv saved")
category cnt
0 accessories 53
1 books 14
2 home 13
3 laptops 16
4 phones 89
5 toys 12
answer4.csv saved
In [ ]:
# Вопрос 5.
# Остров рекомендации = связная компонента по соседям.
# Нужно посчитать, сколько компонентов содержат хотя бы один phones и хотя бы один accessories.

cat_by_id = dict(zip(items["item_id"], items["category"]))

visited = set()
islands = 0

for v in items["item_id"]:
    v = int(v)
    if v in visited:
        continue
    stack = [v]
    visited.add(v)
    comp = []
    while stack:
        cur = stack.pop()
        comp.append(cur)
        for nei in adj[cur]:
            if nei not in visited:
                visited.add(nei)
                stack.append(nei)
    has_phone = any(cat_by_id[i] == "phones" for i in comp)
    has_acc = any(cat_by_id[i] == "accessories" for i in comp)
    if has_phone and has_acc:
        islands += 1

print(islands)
11

B. Кластеризация¶

In [ ]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

data = pd.read_csv('data_B.csv')
X = data.drop('ID', axis=1)

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
In [ ]:
X_scaled_df = pd.DataFrame(X_scaled)
sns.pairplot(X_scaled_df, diag_kind='hist')
Out[ ]:
<seaborn.axisgrid.PairGrid at 0x11f9abce0>
No description has been provided for this image
In [ ]:
kmeans = KMeans(n_clusters=4, random_state=777)

X_scaled_new = X_scaled[:, [1, 6]]
kmeans_clusters = kmeans.fit_predict(X_scaled_new)

result = pd.DataFrame({
    'ID': data['ID'],
    'cluster': kmeans_clusters
})

result.to_csv('my_submission.csv', index=False)

print("Кластеризация завершена. Результат сохранен в my_submission.csv")
Кластеризация завершена. Результат сохранен в my_submission.csv
In [ ]:
sns.scatterplot(x=X_scaled_new[:, 0], y=X_scaled_new[:, 1], hue=kmeans_clusters)
plt.hlines(y=0, linestyle='--', xmin=-1.5, xmax=2)
plt.vlines(x=0, linestyle='--', ymin=-1.5, ymax=2)
Out[ ]:
<matplotlib.collections.LineCollection at 0x12b97f110>
No description has been provided for this image

это решение получает 0.9435

видно, что 2 класс немного заходит на букву С, но это можно легко подправить вручную

In [ ]:
y_line = -0.05
def adjust_clusters(X, kmeans_clusters):
    clusters = kmeans_clusters.copy()
    n = X.shape[0]
    for i in range(n):
        if X[i, 0] > 0:
            if X[i, 1] < y_line:
                clusters[i] = 2
            else:
                clusters[i] = 3
    return clusters


clusters = adjust_clusters(X_scaled_new, kmeans_clusters)
sns.scatterplot(x=X_scaled_new[:, 0], y=X_scaled_new[:, 1], hue=clusters)
plt.hlines(y=y_line, linestyle='--', xmin=-0, xmax=2)
plt.vlines(x=0, linestyle='--', ymin=-2, ymax=2)


result = pd.DataFrame({
    'ID': data['ID'],
    'cluster': clusters
})
result.to_csv('my_submission.csv', index=False)
No description has been provided for this image

После небольшой правки получается 0.9814

C. Стоимость аренды квартир¶

In [ ]:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
In [ ]:
train_df = pd.read_csv('train_C.csv')
test_df = pd.read_csv('test_C.csv')
test_ids = test_df['ID']
test_df = test_df.drop(['ID'], axis=1)
train_df.head()
Out[ ]:
Price Apartment_type Metro_station Minutes_to_metro Region Number_of_rooms Area Living_area Kitchen_area Floor Number_of_floors Renovation NotLiving_Area json
0 106.250000 Старый фонд Октябрьское поле станция 25.0 Moscow 3.0 74.80 46.0 10.3 15.0 16 Косметика NaN {"Area": 74.8, "Living_area": 46.0, "Kitchen_a...
1 26.416667 Вторичка Дом НА Москва: Бауманская 15.0 Moscow 0.0 15.40 9.9 NaN 1.0 4 Дизайн -2.4 {"Area": 15.4, "Living_area": 9.9, "Kitchen_ar...
2 51.458333 Старый фонд Москва: Метро Свиблово 13.0 Moscow 1.0 43.40 21.1 8.6 8.0 12 Космтичский ремонт NaN {"Area": 43.4, "Living_area": 21.1, "Kitchen_a...
3 23.900000 Новостройка Москва: Депо 19.0 Moscow 0.0 28.68 18.6 9.0 10.0 11 Космтичский ремонт NaN {"Area": 28.68, "Living_area": 18.6, "Kitchen_...
4 55.416667 Вторичный рынок Дом НА Москва: Селигерская 18.0 Moscow 0.0 38.00 NaN 10.0 4.0 23,0 Дизайн 9.0 {"Area": 38.0, "Living_area": NaN, "Kitchen_ar...
In [ ]:
train_df.shape, test_df.shape
Out[ ]:
((5775, 14), (5776, 13))
In [ ]:
train_df.isna().sum()
Out[ ]:
Price                  0
Apartment_type         0
Metro_station          0
Minutes_to_metro       0
Region                 0
Number_of_rooms        0
Area                1576
Living_area         1566
Kitchen_area        1523
Floor                  0
Number_of_floors       0
Renovation             0
NotLiving_Area      1600
json                   0
dtype: int64
In [ ]:
test_df.isna().sum()
Out[ ]:
Apartment_type         0
Metro_station          0
Minutes_to_metro       0
Region                 0
Number_of_rooms        0
Area                1540
Living_area         1547
Kitchen_area        1569
Floor                  0
Number_of_floors       0
Renovation             0
NotLiving_Area      1515
json                   0
dtype: int64
In [ ]:
train_df.dtypes
Out[ ]:
Price               float64
Apartment_type       object
Metro_station        object
Minutes_to_metro    float64
Region               object
Number_of_rooms     float64
Area                float64
Living_area         float64
Kitchen_area        float64
Floor               float64
Number_of_floors     object
Renovation           object
NotLiving_Area      float64
json                 object
dtype: object
In [ ]:
train_df['Number_of_floors'] = train_df['Number_of_floors'].transform(lambda x: x.replace(",", '.')).astype(float)
test_df['Number_of_floors'] = test_df['Number_of_floors'].transform(lambda x: x.replace(",", '.')).astype(float)
In [ ]:
cat_cols = train_df.select_dtypes(include=['object']).columns.tolist()
num_cols = train_df.select_dtypes(exclude=['object']).columns.tolist()[1:]

cat_cols, num_cols
Out[ ]:
(['Apartment_type', 'Metro_station', 'Region', 'Renovation', 'json'],
 ['Minutes_to_metro',
  'Number_of_rooms',
  'Area',
  'Living_area',
  'Kitchen_area',
  'Floor',
  'Number_of_floors',
  'NotLiving_Area'])
In [ ]:
for col in cat_cols:
    print(f"----{col}-------")
    print(train_df[col].value_counts())
----Apartment_type-------
Apartment_type
Новостройка        2068
Вторичка           1675
Вторичный рынок     443
Первичка            410
Вторичное жилье     400
Старый фонд         399
От застройщика      380
Name: count, dtype: int64
----Metro_station-------
Metro_station
Красногвардейская            226
Депо                         164
Братиславская                114
Москва: Красногвардейская    100
Котельники                    98
                            ... 
Коптево станция                1
Кузьминки станция              1
Москва: Беляево                1
Красногорская станция          1
Метро Хорошёво                 1
Name: count, Length: 1606, dtype: int64
----Region-------
Region
Moscow           3846
Moscow region    1929
Name: count, dtype: int64
----Renovation-------
Renovation
Косметика             1923
Космтичский ремонт    1916
Евроремонт             498
Евро                   489
Без ремонта            319
Нет Ремонта            300
Дизайнерский           167
Дизайн                 163
Name: count, dtype: int64
----json-------
json
{"Area": 60.6, "Living_area": 32.8, "Kitchen_area": 11.7, "NotLiving_Area": 16.100000000000005}    8
{"Area": 82.9, "Living_area": NaN, "Kitchen_area": 13.5, "NotLiving_Area": 25.400000000000006}     7
{"Area": 46.7, "Living_area": 25.7, "Kitchen_area": NaN, "NotLiving_Area": 10.500000000000004}     7
{"Area": 44.08, "Living_area": NaN, "Kitchen_area": 10.3, "NotLiving_Area": 9.379999999999999}     7
{"Area": NaN, "Living_area": 32.8, "Kitchen_area": 11.7, "NotLiving_Area": 16.100000000000005}     7
                                                                                                  ..
{"Area": 16.7, "Living_area": NaN, "Kitchen_area": 8.0, "NotLiving_Area": -1.9000000000000004}     1
{"Area": NaN, "Living_area": 16.0, "Kitchen_area": 10.0, "NotLiving_Area": 12.64}                  1
{"Area": 44.6, "Living_area": 20.0, "Kitchen_area": NaN, "NotLiving_Area": 14.100000000000001}     1
{"Area": 42.34, "Living_area": NaN, "Kitchen_area": 11.8, "NotLiving_Area": 15.640000000000004}    1
{"Area": 78.1, "Living_area": 41.6, "Kitchen_area": NaN, "NotLiving_Area": 27.89999999999999}      1
Name: count, Length: 4971, dtype: int64
In [ ]:
for col in cat_cols:
    print(f"----{col}-------")
    print(test_df[col].value_counts())
----Apartment_type-------
Apartment_type
Новостройка             1026
Вторичка                 825
New building             723
Secondary housing        706
Первичка                 211
Старый фонд              210
Вторичный рынок          209
От застройщика           207
Вторичное жилье          200
Новая                    157
Б/У квартира             155
Новое строительство      149
Свежая постройка         148
Старая квартира          147
Не новостройка           144
Новая квартира           142
Современный комплекс     142
Вторичное                138
Вторичная квартира       137
Name: count, dtype: int64
----Metro_station-------
Metro_station
Красногвардейская                  256
Депо                               143
Котельники                         104
Дом НА Красногвардейская           100
Братиславская                       94
                                  ... 
Чертановская станция                 1
Крымская                             1
Верхние Лихоборы станция             1
Менделеевская                        1
Москва: Улица 1905 года станция      1
Name: count, Length: 1617, dtype: int64
----Region-------
Region
Moscow           3770
Moscow region    2006
Name: count, dtype: int64
----Renovation-------
Renovation
Космтичский ремонт    1947
Косметика             1913
Евро                   469
Евроремонт             457
Без ремонта            312
Нет Ремонта            305
Дизайн                 190
Дизайнерский           183
Name: count, dtype: int64
----json-------
json
{"Area": 45.27, "Living_area": 14.2, "Kitchen_area": 18.3, "NotLiving_Area": 12.770000000000003}    8
{"Area": 41.8, "Living_area": 23.3, "Kitchen_area": 10.1, "NotLiving_Area": 8.399999999999997}      7
{"Area": NaN, "Living_area": 24.4, "Kitchen_area": 10.3, "NotLiving_Area": 9.250000000000004}       6
{"Area": 43.95, "Living_area": 24.4, "Kitchen_area": 10.3, "NotLiving_Area": NaN}                   6
{"Area": 43.95, "Living_area": NaN, "Kitchen_area": 10.3, "NotLiving_Area": 9.250000000000004}      5
                                                                                                   ..
{"Area": 64.0, "Living_area": NaN, "Kitchen_area": 9.0, "NotLiving_Area": 6.0}                      1
{"Area": 65.15, "Living_area": 35.1, "Kitchen_area": 12.0, "NotLiving_Area": 18.050000000000004}    1
{"Area": 107.0, "Living_area": 56.0, "Kitchen_area": 13.0, "NotLiving_Area": NaN}                   1
{"Area": 34.14, "Living_area": 14.9, "Kitchen_area": 8.7, "NotLiving_Area": NaN}                    1
{"Area': 37.16, "Living_area": 24.0, "Kitchen_area": 5.0, "NotLiving_Area': NaN}                    1
Name: count, Length: 4996, dtype: int64

Видно, что в Apartment_type, Metro_station и Renovation по сути одинаковые категории называются по разному, нужно привести их к единому названию

In [ ]:
test_df['Metro_station'].value_counts()
Out[ ]:
Metro_station
Красногвардейская                  256
Депо                               143
Котельники                         104
Дом НА Красногвардейская           100
Братиславская                       94
                                  ... 
Чертановская станция                 1
Крымская                             1
Верхние Лихоборы станция             1
Менделеевская                        1
Москва: Улица 1905 года станция      1
Name: count, Length: 1617, dtype: int64
In [ ]:
def prepoc_station(s: str):
    s = s.lower()
    delete = ['метро', "станция", 'дом на', 'москва:']
    for d in delete:
        s = s.replace(d, "")
    s = s.replace('ё', 'е')
    return s.strip().rstrip()


train_df['Metro_station'] = train_df['Metro_station'].apply(prepoc_station)
test_df['Metro_station'] = test_df['Metro_station'].apply(prepoc_station)
train_df['Metro_station'].value_counts()
Out[ ]:
Metro_station
красногвардейская    787
депо                 521
братиславская        374
котельники           295
жулебино             222
                    ... 
верхние лихоборы       1
пионерская             1
краснопресненская      1
воробьевы горы         1
кузнецкий мост         1
Name: count, Length: 275, dtype: int64
In [ ]:
apartments_new = ['Новостройка', 'Первичка', 'От застройщика', 'New building', 'Новая', 'Новое строительство',
                  'Свежая постройка', 'Новая квартира', 'Современный комплекс']
apartments_all = train_df['Apartment_type'].unique().tolist() + test_df['Apartment_type'].unique().tolist()
apartments_dict = {k: "new" if k in apartments_new else "old" for k in apartments_all}
apartments_all, apartments_dict
Out[ ]:
(['Старый фонд',
  'Вторичка',
  'Новостройка',
  'Вторичный рынок',
  'От застройщика',
  'Первичка',
  'Вторичное жилье',
  'Старая квартира',
  'Secondary housing',
  'Новое строительство',
  'От застройщика',
  'Б/У квартира',
  'Вторичное жилье',
  'Новостройка',
  'Старый фонд',
  'Новая квартира',
  'Вторичный рынок',
  'New building',
  'Вторичная квартира',
  'Вторичка',
  'Первичка',
  'Новая',
  'Не новостройка',
  'Вторичное',
  'Свежая постройка',
  'Современный комплекс'],
 {'Старый фонд': 'old',
  'Вторичка': 'old',
  'Новостройка': 'new',
  'Вторичный рынок': 'old',
  'От застройщика': 'new',
  'Первичка': 'new',
  'Вторичное жилье': 'old',
  'Старая квартира': 'old',
  'Secondary housing': 'old',
  'Новое строительство': 'new',
  'Б/У квартира': 'old',
  'Новая квартира': 'new',
  'New building': 'new',
  'Вторичная квартира': 'old',
  'Новая': 'new',
  'Не новостройка': 'old',
  'Вторичное': 'old',
  'Свежая постройка': 'new',
  'Современный комплекс': 'new'})
In [ ]:
train_df['Apartment_type'] = train_df['Apartment_type'].map(apartments_dict)
test_df['Apartment_type'] = test_df['Apartment_type'].map(apartments_dict)
In [ ]:
train_df['Renovation'].value_counts()
Out[ ]:
Renovation
Косметика             1923
Космтичский ремонт    1916
Евроремонт             498
Евро                   489
Без ремонта            319
Нет Ремонта            300
Дизайнерский           167
Дизайн                 163
Name: count, dtype: int64
In [ ]:
renovation_all = train_df['Renovation'].unique().tolist() + test_df['Renovation'].unique().tolist()
set(renovation_all)
Out[ ]:
{'Без ремонта',
 'Дизайн',
 'Дизайнерский',
 'Евро',
 'Евроремонт',
 'Косметика',
 'Космтичский ремонт',
 'Нет Ремонта'}
In [ ]:
renovation_dict = {"Без ремонта": 'no', 'Нет Ремонта': 'no', 'Дизайн': 'design', 'Дизайнерский': 'design',
                   'Евро': 'euro', 'Евроремонт': 'euro', 'Косметика': 'cosmetic', 'Космтичский ремонт': 'cosmetic'}
train_df['Renovation'] = train_df['Renovation'].map(renovation_dict)
test_df['Renovation'] = test_df['Renovation'].map(renovation_dict)

Еще тут есть непонятный json с колонками. Можно попробовать распарсить его и заполнить nan в основной таблице

In [ ]:
import json

def open_json(s: str):
    s = s.replace("'", '"')
    try:
        s = json.loads(s)
    except Exception as e:
        print(f"ERR {s}, {e}")
    df = pd.Series(s)
    df =  df.round(3)
    df.replace("nan", np.nan, inplace=True)
    return df

train_json_df = train_df['json'].apply(open_json)
test_json_df = test_df['json'].apply(open_json)
In [ ]:
for col in train_json_df.columns:
    train_df[col] = train_df[col].fillna(train_json_df[col])

for col in test_json_df.columns:
    test_df[col] = test_df[col].fillna(test_json_df[col])

Посмотрим на числовые переменные

In [ ]:
# from ydata_profiling import ProfileReport
# train_report = ProfileReport(train_df, title='Train')
# test_report = ProfileReport(test_df, title='Test')
# comp = train_report.compare(test_report)
# comp.to_file('report.html')

Из подозрительного есть NotLiving_Area < 0 и выбросы в test Minutes_to_metro и дубликаты в train

In [ ]:
# train_df[train_df['NotLiving_Area'] < 0] = 0
# test_df[test_df['NotLiving_Area'] < 0] = 0
train_df.drop_duplicates(inplace=True)
In [ ]:
train_df.drop("json", axis=1, inplace=True)
test_df.drop("json", axis=1, inplace=True)

можно предположить, что некоторые записи были по ошибке умножены или поделены на 60, тк считалось что оно в часах, а на самом деле в минутах

In [ ]:
for df in (train_df, test_df):
    mask = df['Minutes_to_metro'] >= 60
    df.loc[mask, 'Minutes_to_metro'] /= 60

    mask = df['Minutes_to_metro'] < 1
    df.loc[mask, 'Minutes_to_metro'] *= 60
In [ ]:
sns.histplot(test_df['Minutes_to_metro'])
Out[ ]:
<Axes: xlabel='Minutes_to_metro', ylabel='Count'>
No description has been provided for this image
In [ ]:
sns.histplot(train_df['Minutes_to_metro'])
Out[ ]:
<Axes: xlabel='Minutes_to_metro', ylabel='Count'>
No description has been provided for this image
In [ ]:
train_df[num_cols] = train_df[num_cols].fillna(train_df[num_cols].median())
test_df[num_cols] = test_df[num_cols].fillna(test_df[num_cols].median())
In [ ]:
TARGET = "Price"
X = train_df.drop(TARGET, axis=1)
y = train_df[TARGET]
cat_cols = train_df.select_dtypes(include=['object']).columns.tolist()
In [ ]:
from catboost import CatBoostRegressor

model = CatBoostRegressor(
    loss_function="RMSE",
    random_seed=42,
    iterations=3000,
    verbose=500,
)

model.fit(
    X, y,
    cat_features=cat_cols,
)

y_pred = model.predict(test_df)

submission = pd.DataFrame({
    "ID": test_ids,
    "Price": y_pred
})
submission.to_csv("submission_catboost_school.csv", index=False)
print("Saved submission_catboost_school.csv")
Learning rate set to 0.022104
0:	learn: 38.4070122	total: 1.95ms	remaining: 5.85s
500:	learn: 13.1534266	total: 841ms	remaining: 4.19s
1000:	learn: 11.4980210	total: 1.63s	remaining: 3.26s
1500:	learn: 10.2615017	total: 2.44s	remaining: 2.44s
2000:	learn: 9.2590510	total: 3.26s	remaining: 1.63s
2500:	learn: 8.5420549	total: 4.08s	remaining: 814ms
2999:	learn: 7.9523116	total: 4.96s	remaining: 0us
Saved submission_catboost_school.csv
In [ ]:
ytest_df = pd.read_csv('ytest_C.csv')
y_true = ytest_df["Price"]
((y_pred - y_true) ** 2).mean() ** 0.5
Out[ ]:
np.float64(13.918415837644208)

D. Марсианский Архивариус¶

In [ ]:
import numpy as np
import pandas as pd
from pathlib import Path
from sklearn.metrics import accuracy_score

TRAIN_FILE = Path("train_D.csv")
TEST_FILE = Path("test_D.csv")
SEED = 123
K = 25
In [ ]:
def set_seed(seed=SEED):
    np.random.seed(seed)


def parse_label_sets(series):
    # "1 5 10" -> [1,5,10], пусто/NaN -> []
    out = []
    for s in series.astype("string"):
        if s is pd.NA:
            out.append([])
            continue
        s = s.strip()
        out.append(list(map(int, s.split())) if s else [])
    return out
In [ ]:
def split_clean_noisy(X, label_sets):
    # clean: ровно 1 метка; noisy: 2+ меток
    X_clean, y_clean = [], []
    X_noisy, y_noisy_sets = [], []

    for x, labs in zip(X, label_sets):
        if len(labs) == 1:
            X_clean.append(x)
            y_clean.append(labs[0])
        else:
            X_noisy.append(x)
            y_noisy_sets.append(labs)

    return (
        np.asarray(X_clean, dtype=X.dtype),
        np.asarray(y_clean, dtype=np.int64),
        np.asarray(X_noisy, dtype=X.dtype),
        y_noisy_sets,
    )
In [ ]:
def fit_centroids(X, y, k=K):
    dim = X.shape[1]
    sums = np.zeros((k, dim), dtype=np.float64)
    cnt = np.zeros(k, dtype=np.int64)

    for x, lab in zip(X, y):
        sums[lab] += x
        cnt[lab] += 1

    centroids = sums
    m = cnt > 0
    centroids[m] /= cnt[m, None]
    return centroids, cnt


def predict_nearest_centroid(X, centroids):
    # argmin ||x-c||^2 == argmin (||c||^2 - 2 x·c)
    c2 = (centroids * centroids).sum(axis=1)  # (k,)
    d2 = c2[None, :] - 2.0 * (X @ centroids.T)  # (n, k)
    return d2.argmin(axis=1).astype(np.int32)
In [ ]:
set_seed()

print("Загрузка данных...")
train_df = pd.read_csv(TRAIN_FILE)
test_df = pd.read_csv(TEST_FILE)

feature_cols = [c for c in train_df.columns if c.startswith("f")]
X_train = train_df[feature_cols].to_numpy(dtype=np.float32, copy=False)
X_test = test_df[feature_cols].to_numpy(dtype=np.float32, copy=False)

print("Train:", X_train.shape)
print("Test :", X_test.shape)
Загрузка данных...
Train: (50000, 16)
Test : (10000, 16)
In [ ]:
print("Парсинг меток...")
label_sets = parse_label_sets(train_df["labels"])

X_clean, y_clean, X_noisy, _ = split_clean_noisy(X_train, label_sets)

print("Clean:", X_clean.shape, y_clean.shape)
print("Noisy:", X_noisy.shape)
Парсинг меток...
Clean: (4298, 16) (4298,)
Noisy: (45702, 16)
In [ ]:
centroids_clean, cnt_clean = fit_centroids(X_clean, y_clean, K)
y_noisy_pred = predict_nearest_centroid(X_noisy, centroids_clean)

print("Noisy pseudo-labels:", y_noisy_pred.shape)
Noisy pseudo-labels: (45702,)
In [ ]:
X_full = np.vstack([X_clean, X_noisy])
y_full = np.concatenate([y_clean, y_noisy_pred])

centroids_full, cnt_full = fit_centroids(X_full, y_full, K)

print("Full:", X_full.shape, y_full.shape)
Full: (50000, 16) (50000,)
In [ ]:
print("[Baseline] Метод ближайшего центроида")
y_test_pred = predict_nearest_centroid(X_test, centroids_full)

y_test_pred[:10], y_test_pred.shape
[Baseline] Метод ближайшего центроида
Out[ ]:
(array([24, 13, 11, 23,  0,  9,  5,  5,  0,  0], dtype=int32), (10000,))
In [ ]:
submission = pd.DataFrame({"id": test_df["id"], "label": y_test_pred})

submission.to_csv("submission_embed_1.csv", index=False)
print("Saved submission_embed_1.csv:", submission.shape)

submission.head(10)
Saved submission_embed_1.csv: (10000, 2)
Out[ ]:
id label
0 0 24
1 1 13
2 2 11
3 3 23
4 4 0
5 5 9
6 6 5
7 7 5
8 8 0
9 9 0
In [ ]:
ytest = pd.read_csv("ytest_D.csv")
acc = accuracy_score(ytest["label"], submission["label"])
acc
Out[ ]:
0.7747