Курс№27. Кластеризация0%
Теория · номер 27 из 27

Кластеризация

В файле тысячи звёзд, собранных в кучки. Делим их на кучки, находим центр каждой и отвечаем на вопрос. Способ всегда один и состоит из пяти шагов.

9 разделов~60 минут2 первичных балла
  1. 1Что просят сделать
  2. 2Шаг 1. Читаем файл
  3. 3Шаг 2. Делим на кластеры
  4. 4Шаг 3. Подбираем порог
  5. 5Шаг 4. Находим центры
  6. 6Шаг 5. Отвечаем на вопрос
  7. 7Весь порядок на экзамене
  8. 8Ловушки
  9. 9Домашка
Раздел 1 из 9

Что просят сделать

Это самый длинный номер, зато он всегда решается одинаково. Если разобраться один раз, дальше меняются только последние строки кода.

Простыми словами

Представь ночное небо, где звёзды собрались в несколько отдельных кучек. Кучка и есть кластер. Глазом кучки видно на графике, но компьютеру надо дать правило. Правило такое: если две звезды стоят ближе некоторого расстояния t, они в одной кучке. Если до звезды можно дойти по цепочке близких соседей, она тоже в этой кучке.

Что дано

Два файла. В файле А обычно 2 кластера, в файле Б обычно 3. В каждой строке три значения: координата x, координата y и класс звезды.

код
4,924550 4,721530 O7V
3,111640 6,900950 G1V

В ответ идут 4 числа: два для файла А и два для файла Б. Часто просят умножить на 10000 и взять целую часть.

План из пяти шагов

  1. Прочитать файл в список.
  2. Разделить звёзды на кластеры.
  3. Проверить, что кластеров столько, сколько в условии. Если нет, поменять порог.
  4. Найти центр каждого кластера.
  5. Посчитать то, что спрашивают.

Весь код в уроке запускается на маленьком учебном файле 27-demo.txt, в нём 36 звёзд и 3 кластера. Можешь менять код и смотреть, что произойдёт.

Раздел 2 из 9

Шаг 1. Читаем файл

Читаем файл построчно и превращаем каждую строку в удобную запись.

python
from math import dist

data = []
for line in open('27-demo.txt'):
    x, y, h = line.replace(',', '.').split()
    data.append(([float(x), float(y)], h))

print(len(data))
print(data[0])

скачать 27-demo.txt

Разберём строку за строкой:

Значит, у звезды p координаты лежат в p[0], а класс в p[1]. Это пригодится на всех следующих шагах.

Расстояние между звёздами считает dist из модуля math: dist(p[0], q[0]). Формулу писать самому не нужно.

Раздел 3 из 9

Шаг 2. Делим на кластеры

Это главная часть. Идея такая: берём любую звезду и начинаем новую кучку. Находим всех её близких соседей и добавляем в кучку. Потом ищем соседей у соседей, пока новые не закончатся. Кучка готова, берём следующую свободную звезду.

python
from math import dist

data = []
for line in open('27-demo.txt'):
    x, y, h = line.replace(',', '.').split()
    data.append(([float(x), float(y)], h))

t = 1.5
clusters = []
while data:
    clusters.append([data.pop(0)])
    for p in clusters[-1]:
        sosedi = [p1 for p1 in data if dist(p[0], p1[0]) < t]
        clusters[-1].extend(sosedi)
        for p1 in sosedi:
            data.remove(p1)

for cl in clusters:
    print(len(cl))

скачать 27-demo.txt

Что делает каждая строка:

Запусти код. Он напечатает размер каждого кластера.

Раздел 4 из 9

Шаг 3. Подбираем порог

Порог t в условии не дан, его подбираем сами. Вот что происходит с учебным файлом при разных порогах:

порог tсколько кластеровразмеры
0,2331, 1, 1, 1, 1, 1, 1, 1 …
0,5169, 1, 1, 2, 2, 1, 8, 3 …
1514, 12, 2, 7, 1
1,5314, 12, 10
4314, 12, 10
6136

Маленький порог рвёт кучки на части, большой склеивает разные кучки в одну. Нам нужен порог посередине.

Как подбирать

  1. Начни с t = 1 и посмотри на напечатанные размеры.
  2. Кластеров больше, чем в условии, увеличь t.
  3. Кластеров меньше, чем в условии, уменьшай t.
  4. Число совпало и размеры выглядят как настоящие кучки, можно идти дальше.

На настоящих файлах это важно. В одном файле экзамена при t = 1 получается лишний кластер из одной точки, и нужно поднять порог. В другом при t = 1 два кластера склеиваются, и нужно опустить порог примерно до 0,6.

Как понять, что порог верный

Если число кластеров совпало с условием и не меняется при небольшом изменении порога, ответ будет один и тот же. Кластер из одной-двух точек обычно значит, что порог мал.

Прямоугольник H×W в условии описывает, какого размера кучки. Для нашего кода он нужен только как подсказка: порог обычно гораздо меньше этих размеров.

Попробуй самучебная задача

Точки: [(0, 0), (0.5, 0.5), (1, 0.2), (5, 5), (5.4, 5.3), (9, 1), (9.3, 1.5), (9.8, 1.1), (4, 9)]. Две точки считаем связанными, если расстояние меньше 1. Сколько получится кластеров и сколько точек в самом большом? Запиши через пробел.

Подсказка

Возьми цикл из шага 2 с t = 1, только вместо чтения файла запиши точки списком.

Раздел 5 из 9

Шаг 4. Находим центры

Центр это настоящая звезда из кучки, а не точка посередине. Представь, что все звёзды кучки ходят в гости к одной. Центр это та, к которой всем вместе идти меньше всего, то есть у неё наименьшая сумма расстояний до остальных.

Посчитаем на маленьком примере

Кластер из четырёх точек. Для каждой складываем расстояния до трёх других:

точкасумма расстояний
(1, 1)3,92
(2, 1)3,62
(1,5; 2)2,94
(2; 2,5)4,01

Наименьшая сумма у точки (1,5; 2), она и есть центр.

То же в коде

python
from math import dist

data = []
for line in open('27-demo.txt'):
    x, y, h = line.replace(',', '.').split()
    data.append(([float(x), float(y)], h))

t = 1.5
clusters = []
while data:
    clusters.append([data.pop(0)])
    for p in clusters[-1]:
        sosedi = [p1 for p1 in data if dist(p[0], p1[0]) < t]
        clusters[-1].extend(sosedi)
        for p1 in sosedi:
            data.remove(p1)

def centroids(cl):
    m = []
    for p in cl:
        sm = sum(dist(p[0], p1[0]) for p1 in cl)
        m.append([sm, p])
    return min(m)[1]

centroid = [centroids(cl) for cl in clusters]

for c in centroid:
    print(c)

скачать 27-demo.txt

В итоге centroid[0] это центр кластера clusters[0], и так далее. Номера совпадают, и это удобно на следующем шаге.

Попробуй самучебная задача

Кластер 1: [(1, 1), (2, 1), (1.5, 2), (2, 2.5)]. Кластер 2: [(8, 7), (9, 7), (8, 8), (9.5, 8.5), (8.5, 7.5)]. Найди центр каждого кластера и расстояние между центрами. В ответ запиши целую часть расстояния, умноженного на 1000.

Подсказка

Запиши каждый кластер списком и посчитай для точек сумму расстояний, как в таблице выше. Здесь точка это просто пара чисел, поэтому пиши dist(p, q).

Попробуй самучебная задача

Для кластера 2 из прошлого задания: сколько точек, кроме центра, лежат от центра на расстоянии не больше 1?

Раздел 6 из 9

Шаг 5. Отвечаем на вопрос

Самый творческий шаг, но вопросы повторяются. Сначала пойми, про кого спрашивают.

Как читать класс звезды

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

буквацветсветимостьназвание
OголубойIсверхгигант
Bбело-голубойIIяркий гигант
AбелыйIIIгигант
Fжёлто-белыйIVсубгигант
GжёлтыйVкарлик
KоранжевыйVIсубкарлик
Mкрасный

Белые карлики записаны просто VII, без цвета и цифры.

Проверяем отдельно цвет и светимость: цвет это h[0], светимость это h[2:].

python
h[0] == 'K' and h[2:] == 'III'     # оранжевый гигант
h[0] == 'O' and h[2:] == 'VI'      # голубой субкарлик
Нельзя проверять конец строки

h.endswith('V') примет за карлика и субгиганта K4IV, ведь он тоже оканчивается на V. А h[2:] == 'V' сравнивает всю светимость целиком.

Частые вопросы и готовый код

что спрашиваюткод
расстояние между центрамиdist(centroid[0][0], centroid[1][0])
расстояние от центра до точкиdist(centroid[0][0], (-1, -2))
кластер с наименьшим числом точекmin(clusters, key=len)
сколько нужных звёзд в кластереsum(1 for p in cl if условие)
сколько звёзд не дальше R от центра csum(1 for p in cl if dist(p[0], c[0]) <= R)
ближайшая нужная звезда к центру cmin(dist(p[0], c[0]) for p in cl if условие)
умножить на 10000, целая частьint(x * 10000)

Полный разбор вопроса

Вопрос: найди центр кластера, в котором меньше всего карликов, и выведи модули его координат, умноженные на 10000. Здесь обычный шаблон не подойдёт: нужно считать по каждому кластеру и запоминать, какой номер лучший.

python
from math import dist

data = []
for line in open('27-demo.txt'):
    x, y, h = line.replace(',', '.').split()
    data.append(([float(x), float(y)], h))

t = 1.5
clusters = []
while data:
    clusters.append([data.pop(0)])
    for p in clusters[-1]:
        sosedi = [p1 for p1 in data if dist(p[0], p1[0]) < t]
        clusters[-1].extend(sosedi)
        for p1 in sosedi:
            data.remove(p1)

def centroids(cl):
    m = []
    for p in cl:
        sm = sum(dist(p[0], p1[0]) for p1 in cl)
        m.append([sm, p])
    return min(m)[1]

centroid = [centroids(cl) for cl in clusters]

def is_dwarf(h):
    return h[0] in 'OBAFGKM' and h[2:] == 'V'

counts = [sum(1 for p in cl if is_dwarf(p[1])) for cl in clusters]
print(counts)
i = counts.index(min(counts))
c = centroid[i]
print(int(abs(c[0][0]) * 10000), int(abs(c[0][1]) * 10000))

скачать 27-demo.txt

counts хранит число карликов по кластерам, а counts.index(min(counts)) находит номер кластера с наименьшим числом. Номер i подходит и для clusters, и для centroid.

Попробуй самучебная задача

В учебном файле 27-demo.txt три кластера. Найди наибольшее расстояние между центрами кластеров. В ответ запиши целую часть расстояния, умноженного на 10000.

Подсказка

Перебери все пары: 0 и 1, 0 и 2, 1 и 2. Шаги 1–4 скопируй из кода выше.

Попробуй самучебная задача

В том же файле возьми кластер с наименьшим числом звёзд. Сколько в нём звёзд? Затем возьми кластер, центр которого дальше всего от начала координат: сколько в нём звёзд, кроме центра, не дальше 1 от центра? Запиши два числа через пробел.

Подсказка

min(clusters, key=len). Для второго числа найди номер через dist(c[0], (0, 0)) и не считай сам центр.

Раздел 7 из 9

Весь порядок на экзамене

Собери всё вместе. Удобно держать один файл с кодом и менять в нём только имя файла, порог и последние строки.

  1. Копируешь шаблон из шага 4 и пишешь имя файла А.
  2. Запускаешь и смотришь размеры. Подбираешь порог, пока число кластеров не совпадёт с условием.
  3. Читаешь вопрос для А и дописываешь последние строки, сверяясь с таблицей из шага 5.
  4. Записываешь два числа.
  5. Меняешь имя файла на Б и снова проверяешь порог: для него он может быть другим.
  6. Дописываешь вопрос для Б. Он обычно другой, поэтому код для А не подойдёт.

Файл Б большой, код может считаться несколько минут. Это нормально: не прерывай его, пока печатаются размеры кластеров.

Если кластеризуют не звёзды, а числа

Иногда кучки строятся не по расстоянию на плоскости, а по одному числу, например по энергии E = m(Vx² + Vy²) / 2. Код почти тот же: положи в начало записи список из одного числа, [E], а координаты и признак отдельно. dist прекрасно работает и со списками из одного числа. Порог снова подбираем по числу кластеров.

Раздел 8 из 9

Ловушки

Ловушка

Запятые в числах: replace(',', '.'), иначе float падает.

Ловушка

Порог для файлов А и Б бывает разный. Проверяй размеры кластеров каждый раз.

Ловушка

Лишний кластер из одной-двух точек значит, что порог мал. Если таких точек не убрать, номера кластеров и ответ будут неверными.

Ловушка

Центр это звезда из кластера, а не среднее арифметическое координат.

Ловушка

Целая часть: int(x * 10000), без округления. Если просят модуль координаты, не забудь abs.

Ловушка

При подсчёте «не дальше R от центра» сам центр тоже попадает в выборку, ведь расстояние до себя равно 0. Прочитай условие: считать его или нет.

Ловушка

Светимость сравнивай целиком: h[2:] == 'V'. По концу строки субгигант IV примется за карлика.

Раздел 9 из 9

Домашка

Задачи с настоящих экзаменов и из банка ФИПИ. Открой задачу и нажми «Решать здесь». Файлы задачи уже лежат рядом с кодом, поэтому в шаблоне поменяй только имя файла: оно написано в строке «Скачать файл». В ответ записывай сначала числа для файла А, затем для файла Б.

Задачи «Попробуй сам» учебные и считаются по учебному файлу, в домашке задачи с настоящих экзаменов и из банка ФИПИ. Код в блоках можно менять и запускать, он выполняется прямо в браузере.