Множества, сочетания и подсчёт
Язык множеств (∈, ∪, ∩, |A|) и искусство подсчёта — правило произведения, факториал, сочетания C(n, k) — на примерах словаря токенов, сходства Жаккара для дедупликации и числа «команд экспертов» в MoE.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Словарь токенизатора — это множество: в нём нет повторов, и порядок не важен (урок 4.2). Чтобы очистить данные для претрейна от дубликатов, документы превращают в множества кусочков текста и сравнивают, какая доля у них общая (урок 5.1.2). В MoE-модели DeepSeek-V3 каждый токен выбирает 8 экспертов из 256 — и полезно уметь посчитать, сколько существует таких «команд» (модуль 5.3). Метрика pass@k для задач по программированию (урок 7.3.2) и биномиальное распределение (урок 1.3.1) тоже сводятся к одному вопросу: сколькими способами можно выбрать?
Этот урок даёт два инструмента. Первый — язык множеств: значки , , , , которые встречаются в статьях постоянно. Второй — подсчёт: правило произведения, факториал и сочетания. На них построена классическая вероятность.
Множество: элементы и размер
Заголовок раздела «Множество: элементы и размер»МножествомножествоEN setНеупорядоченный набор уникальных элементов. Пустое множество — set(), а {} — пустой словарь. — набор различных объектов, его элементов. Записывают перечислением в фигурных
скобках: . Два правила: повторы не считаются ()
и порядок не важен (). В Python это set из урока 0.2.1 — он ведёт себя ровно так же.
| Запись | Читается | Пример | В Python |
|---|---|---|---|
| принадлежит | "чай" in V | ||
| не принадлежит | "кофе" not in V | ||
| размер (мощность) | len(V) | ||
| пустое множество | set() | ||
| — подмножество | A <= B |
ПодмножествоподмножествоEN subsetA ⊆ B — каждый элемент A есть и в B. В Python: A <= B. У множества из n элементов 2ⁿ подмножеств. значит: каждый элемент есть и в . Размер множества — просто число его элементов (те же вертикальные черты, что у модуля в уроке 1.0.1, но смысл другой). Когда в статье пишут «размер словаря », имеют в виду именно это: у Qwen тысячи.
Часто множество задают не перечислением, а условием: читается «все из , такие что
». Например, — номера токенов, которым модель дала вероятность больше 1%. В Python это
генератор множества: {i for i, p in enumerate(probs) if p > 0.01}.
Операции: объединение, пересечение, разность
Заголовок раздела «Операции: объединение, пересечение, разность»Возьмём два текста и превратим их в множества слов: — «я люблю зелёный чай», — «я люблю чёрный чай».
| Операция | Запись | Смысл | Python | Результат |
|---|---|---|---|---|
| объединениеобъединение множествEN unionA ∪ B — элементы, которые есть хотя бы в одном из множеств. В Python: A | B. | есть хотя бы в одном | A | B | я, люблю, зелёный, чёрный, чай | |
| пересечениепересечение множествEN intersectionA ∩ B — элементы, которые есть в обоих множествах. В Python: A & B. | есть в обоих | A & B | я, люблю, чай | |
| разностьразность множествEN set differenceA ∖ B — элементы A, которых нет в B. В Python: A - B. | есть в , но нет в | A - B | зелёный |
Размеры связаны простой формулой. Если сложить и , общие элементы будут посчитаны дважды, поэтому их надо один раз вычесть:
Проверка: . Эту формулу называют формулой включений-исключений.
Где это в ML. Разность множеств отвечает на вопрос «каких слов текста нет в словаре?» — это слова вне словаря (OOVслово вне словаря (OOV)EN out-of-vocabulary word (OOV)Слово, которого нет в словаре; словарный токенизатор заменяет его на <unk>. Byte-level BPE этой проблемы не имеет., урок 4.2.1). Пересечение — «какие примеры теста попали в обучающие данные?»: так ищут утечку тестазагрязнение данных (утечка бенчмарка в обучение)EN data contaminationЗадачи из тестового бенчмарка попали в обучающий корпус LLM — оценка становится завышенной. Родственное явление — «шуабан», подгонка модели под лидерборд. (урок 7.3.1). Объединение словарей двух языков — первый шаг к многоязычному токенизатору.
Сходство Жаккара
Заголовок раздела «Сходство Жаккара»Насколько похожи два документа? Посчитаем долю общих элементов среди всех различных:
Читаем: «сходство Жаккарасходство ЖаккараEN Jaccard similarity|A ∩ B| / |A ∪ B| — доля общих элементов двух множеств. — размер пересечения, делённый на размер объединения». Одинаковые множества дают 1, не имеющие общих элементов — 0. У наших текстов .
a = set("я люблю зелёный чай".split()) # split() режет строку по пробелам → список слов → множествоb = set("я люблю чёрный чай".split())
print(a & b) # {'я', 'люблю', 'чай'} — порядок при печати может отличатьсяprint(len(a | b)) # 5print(a - b, b - a) # {'зелёный'} {'чёрный'}print(len(a & b) / len(a | b)) # 0.6 — сходство ЖаккараВ уроке 5.1.2 элементами будут не слова, а шинглы — все цепочки по 5 слов подряд. Если двух веб-страниц больше 0.7–0.8, это почти-дубликаты, и одну из них выбрасывают. На миллиардах страниц считать для каждой пары невозможно, поэтому придумали MinHash — вероятностный трюк, который мы разберём в конце урока.
Правило произведения
Заголовок раздела «Правило произведения»Перейдём к подсчёту. Главный инструмент — правило произведенияправило произведенияEN rule of productЕсли первый выбор можно сделать a способами, а второй — b способами, то пару выборов — a·b способами. Отсюда Vᵀ последовательностей из T токенов.: если первый выбор можно сделать способами, а второй (при любом первом) — способами, то пару выборов — способами.
Пример: 3 футболки и 4 пары брюк дают нарядов — к каждой футболке подходит любая из 4 пар. ПИН-код из 4 цифр: на каждое место 10 вариантов, всего кодов.
Сколько бывает текстов. Пусть в словаре токенов, а текст состоит из токенов. На каждое место — вариантов, поэтому различных текстов . Числа для (словарь GPT-2 — 50 257 токенов):
| Длина | Число последовательностей |
|---|---|
| 1 | |
| 2 | |
| 10 |
Уже пар токенов 2.5 миллиарда — столько ячеек было бы в таблице биграммной модели (урок 4.1.2). А последовательностей длины 10 так много, что все тексты, на которых когда-либо обучали модели (DeepSeek-V3 — 14.8 триллиона токенов, то есть около ), — ничтожная доля. Модель не может «запомнить все варианты»: ей приходится обобщать. Это одна из причин, почему языковые модели — нейросети, а не таблицы.
Сколько подмножеств. У множества из элементов каждый элемент либо входит в подмножество, либо нет — 2 варианта на элемент. По правилу произведения подмножеств . У их 8: , , , , , , , .
Есть и правило суммы: если варианты делятся на непересекающиеся группы, их количества складываются. Пароль из 3 или 4 цифр: вариантов.
Перестановки и факториал
Заголовок раздела «Перестановки и факториал»Сколькими способами можно выстроить в ряд разных предметов? На первое место — кандидатов, на второе — (один уже стоит), на третье — , и так до последнего, на которое остаётся один. По правилу произведения:
Это факториалфакториалEN factorialn! = 1·2·…·n — число способов выстроить n предметов в ряд; 0! = 1. Растёт быстрее любой экспоненты: 20! ≈ 2.4·10¹⁸. («эн факториал»), а способы выстроить — перестановкиперестановкаEN permutationУпорядочивание набора предметов; перестановок n предметов n!. Упорядоченный выбор k из n (размещение) — n!/(n − k)! способов.. Для трёх токенов «я», «люблю», «чай» перестановок : я люблю чай, я чай люблю, люблю я чай, люблю чай я, чай я люблю, чай люблю я. По соглашению : пустой набор можно упорядочить одним способом — ничего не делать.
Факториал растёт быстрее любой экспоненты (урок 1.0.2):
| 1 | 3 | 5 | 10 | 20 | 52 | |
|---|---|---|---|---|---|---|
| 2 | 8 | 32 | 1024 | |||
| 1 | 6 | 120 | 3 628 800 |
Колоду из 52 карт можно перетасовать примерно способами: почти наверняка ваша тасовка никогда не встречалась в истории. Поэтому задачи «перебрать все порядки» на практике невыполнимы уже при около 20 — и в ML их заменяют сэмплированием.
Упорядоченный выбор части. Если из предметов нужно выбрать по порядку (первое, второе, …, -е место),
вариантов — тот же факториал, но оборванный через множителей. Пьедестал (3 места)
из 5 бегунов: . В Python — math.perm(5, 3).
Сочетания: выбрать k из n без порядка
Заголовок раздела «Сочетания: выбрать k из n без порядка»Теперь главный случай: выбрать предметов из , когда порядок не важен. Роутер MoE выбирает «команду» экспертов — ему всё равно, кого назвать первым. Пусть экспертов 4, а выбрать нужно 2.
Упорядоченных пар . Но пары (1, 2) и (2, 1) — одна и та же команда . Каждую команду из двух мы посчитали раза, значит, команд : , , , , , .
То же рассуждение в общем виде: упорядоченных выборов , и каждый неупорядоченный набор из элементов встречается среди них раз (столько у него перестановок). Получаем число сочетанийчисло сочетаний (биномиальный коэффициент)EN binomial coefficient, "n choose k"C(n, k) = n! / (k!(n − k)!) — сколькими способами выбрать k предметов из n без учёта порядка. В Python: math.comb(n, k).:
Читается «цэ из эн по ка», пишут также или . Вторая форма получается, если домножить числитель и знаменатель на : числитель «дорастает» до полного . Числа: , .
Три свойства, которые легко понять без формул:
- Симметрия: . Выбрать 2 эксперта из 16 в команду — то же, что выбрать 14, которые в неё не войдут.
- Сумма по всем равна : — это все подмножества, разложенные по размеру. Для : .
- Правило треугольника: . Посмотрите на последний элемент: либо он в наборе (тогда остальные выбираем из ), либо нет (тогда все — из ). Если выписать числа строками, каждое окажется суммой двух соседних сверху — это треугольник Паскаля, в Китае — треугольник Ян Хуэя (см. «Китайский угол»).
import mathfrom itertools import combinations
def comb(n, k): """C(n, k) без больших факториалов: после каждого шага результат остаётся целым.""" if k < 0 or k > n: return 0 k = min(k, n - k) # симметрия: C(16, 14) = C(16, 2) — меньше шагов result = 1 for i in range(k): result = result * (n - i) // (i + 1) # C(n, i+1) = C(n, i) · (n − i) / (i + 1) return result
print(comb(4, 2), list(combinations([1, 2, 3, 4], 2)))# 6 [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]print(comb(16, 2), comb(64, 8), math.comb(64, 8)) # 120 4426165368 4426165368print([comb(5, k) for k in range(6)]) # [1, 5, 10, 10, 5, 1] — сумма 32 = 2⁵Почему внутри цикла сначала умножение, потом целочисленное деление //? Произведение подряд идущих чисел всегда
делится на , поэтому на каждом шаге получается точное целое число . А считать через
math.factorial(n) при было бы очень долго: в таком числе больше пяти миллионов цифр.
Сочетания в LLM
Заголовок раздела «Сочетания в LLM»Команды экспертов. В статье DeepSeekMoE разобран пример: 16 экспертов, каждый токен выбирает 2 — всего возможных команд. Разрежем каждого эксперта на 4 маленьких (параметров и вычислений столько же) и будем выбирать 8 из 64: — в 37 миллионов раз больше вариантов. Знания для каждого токена собираются из более точно подобранных «кусочков». У DeepSeek-V3 8 из 256: . Подробно — в уроке 5.3.2.
Пары. Частный случай — число пар. Миллиард документов дают около пар: сравнить каждую пару невозможно, отсюда MinHash и LSH (урок 5.1.2). Внимание в трансформере (урок 4.4) считает оценку для каждой пары токенов, поэтому его стоимость растёт как квадрат длины текста.
От подсчёта к вероятности
Заголовок раздела «От подсчёта к вероятности»Если все исходы опыта равновероятны, вероятность события — доля подходящих исходов:
Здесь («омега») — множество всех исходов, — исходы, при которых событие произошло. Это классическая вероятностьклассическая вероятностьEN classical probabilityКогда все исходы равновероятны, P(A) = |A| / |Ω| — доля подходящих исходов среди всех.. Бросаем два кубика: по правилу произведения исходов , а сумма 7 выпадает в шести: (1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1). Значит, . Вся сложность таких задач — правильно посчитать и , а для этого и нужны правило произведения и сочетания. В уроке 1.3.1 сочетания появятся в биномиальном распределении: там считает, какие именно попыток из оказались удачными.
MinHash: вероятность, равная Жаккару. Перемешаем все элементы в случайном порядке и посмотрим, какой элемент окажется первым и какой элемент окажется первым. Они совпадут, только если самый первый элемент всего объединения лежит в пересечении. Все элементы объединения равновероятно могут оказаться первыми, поэтому
Повторив такое перемешивание 100 раз, мы оценим по доле совпадений, не сравнивая множества целиком. Проверим симуляцией:
import random
A = set(range(0, 60)) # элементы 0…59B = set(range(20, 100)) # элементы 20…99: общих 40, всего разных 100 → J = 0.4union = sorted(A | B)rng = random.Random(0)
hits = 0for _ in range(2000): rank = {x: rng.random() for x in union} # случайный «номер в очереди» для каждого элемента if min(A, key=rank.get) == min(B, key=rank.get): # первые элементы A и B совпали? hits += 1print(len(A & B) / len(A | B), hits / 2000) # 0.4 0.3895 — оценка близка к точному Jpass@k. Модель сгенерировала решений задачи, из них верных. Какова вероятность, что среди случайно выбранных решений хотя бы одно верное? Проще посчитать обратное: «оба неверные». Выбрать 2 из 7 неверных можно способом, а всего пар . Ответ: . Это ровно формула метрики pass@k, которую вы реализуете в уроке 7.3.2.
Практика
Заголовок раздела «Практика»Реализуйте факториал, свои сочетания (без math.comb), сходство Жаккара и вероятность «хотя бы одного верного из ».
def factorial(n):
"""n! = 1 · 2 · … · n. По соглашению 0! = 1. Для n < 0 бросьте ValueError.
Пример: factorial(5) → 120.
"""
# TODO: result = 1 и цикл умножений
...
def comb(n, k):
"""Число сочетаний C(n, k): сколькими способами выбрать k предметов из n без учёта порядка.
Не используйте math.comb — посчитайте сами. Если k < 0 или k > n, верните 0.
Ответ — целое число (int), даже для больших n: comb(256, 8) → 409663695276000.
Примеры: comb(5, 2) → 10, comb(16, 2) → 120.
"""
# TODO: мультипликативная формула C(n, k) = n·(n−1)·…·(n−k+1) / k!
...
def words(text):
"""Множество слов текста в нижнем регистре (слова разделены пробелами).
Пример: words("Я люблю чай") → {"я", "люблю", "чай"}.
"""
# TODO
...
def jaccard(a, b):
"""Сходство Жаккара двух множеств: |A ∩ B| / |A ∪ B|.
Если оба множества пустые, верните 1.0.
Пример: jaccard({1, 2, 3}, {2, 3, 4}) → 0.5.
"""
# TODO
...
def prob_at_least_one(n, c, k):
"""Из n сгенерированных ответов c верных. Выбираем k ответов случайно (без повторов).
Какова вероятность, что среди них есть хотя бы один верный?
Подсказка: посчитайте вероятность обратного события «все k неверные».
Пример: prob_at_least_one(10, 3, 2) → 1 − C(7, 2) / C(10, 2) = 1 − 21/45 ≈ 0.5333.
"""
# TODO
...
# Проверьте себя кнопкой «Запустить»:
print(factorial(5)) # 120
print(comb(16, 2), comb(64, 8)) # 120 4426165368
print(jaccard(words("Я люблю зелёный чай"), words("я люблю чёрный чай"))) # 0.6
print(prob_at_least_one(10, 3, 2)) # ≈ 0.5333
Проверь себя
Заголовок раздела «Проверь себя»- Множество — набор различных элементов без порядка; , , , , , — те же
операции, что
in,<=,len,|,&,-уsetв Python. . - Сходство Жаккара измеряет долю общего; на нём стоит дедупликация данных для претрейна.
- Правило произведения: последовательностей из токенов , подмножеств , перестановок — и все эти числа растут взрывообразно.
- Сочетания — выбор без порядка. Они считают команды экспертов MoE, пары документов и удачные попытки в биномиальном распределении.
- Классическая вероятность — «подходящие исходы / все исходы»; так выводятся pass@k и свойство MinHash.
Дальше: модуль 1.1 «Линейная алгебра». Векторы, скалярное произведение и нормы — то, что стоит за записями и , и за косинусом угла между эмбеддингами.