Разбор второго тура Регионального этапа ВсОШ по ИИ 2025/2026¶
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>
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>
это решение получает 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)
После небольшой правки получается 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'>
In [ ]:
sns.histplot(train_df['Minutes_to_metro'])
Out[ ]:
<Axes: xlabel='Minutes_to_metro', ylabel='Count'>
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