random.shuffle(docs) //перемешивает элементы списка `docs` случайным образом прямо на месте**
// Объединяет элементы списка `docs` в одну строку
docs = ["abc", "de", "f"]
"".join(docs) # "abcdef"
// Преобразует строку в **множество уникальных символов**.
set("abcdef")
# {'a', 'b', 'c', 'd', 'e', 'f'}
// matrix x vector multiply
// vector - столбец, каждая строка матрицы умножается на столбец-вектор
def linear(x, w):
return [sum(wi * xi for wi, xi in zip(wo, x)) for wo in w]
/*
Матрица
W
1 2 3
4 5 6
Вектор
x
[10
20
30
]
Результат:
[1⋅10+2⋅20+3⋅30
4⋅10+5⋅20+6⋅30]
[1⋅10+2⋅20+3⋅30
4⋅10+5⋅20+6⋅30]
*/
//Softmax должен выдать:
// числа > 0
// сумма = 1
// Экспонента → нормализация делением → готово.
def softmax(logits):
// find max
max_val = max(val.data for val in logits)
// exp(val - max_val)
// экспонент нормализованных логитов
exps = [(val - max_val).exp() for val in logits]
total = sum(exps)
return [e / total for e in exps]
1. Зачем нужно `(val - max_val)`?
Без этого:
+ большие значения могут вызвать переполнение (exp(1000) → слишком большое число) - numerical stability trick
logits = [3.0, 1.0, -2.0]
max_val = 3.0
val | val - max_val | exp(val - max_val)
3 0 1.0
1 -2 0.1353…
-2 -5 0.0067…
NOTE:
Логит (logit) — это сырое, не нормализованное выходное значение нейронной сети до применения softmax или сигмоиды.
2. зачем exponent:
+ Преобразует логиты в положительные значения
+ Усиливает различия между логитами
логит exp(logit)
1 2.7
2 7.4
3 20
Разница между 1 и 3 была ×3,
после exp стала ×7,5.
Это делает модель более «решительной».
немного больший логит → намного большая вероятность
намного меньший логит → почти 0 вероятность
+ Softmax хорошо работает с градиентами:
функция гладкая
производная легко вычисляется
обучение стабильнее
3. Softmax на NPU - в 3 шага
+ Reduce-max — нахождение максимума
MPU HW делит вектор logits на блоки и на каждом блоке параллельно ищет максимум
выполняется за O(n / параллелизм-на-блоках)
+ Subtract+Exp - вычитание максимума и экспонента
val_i = logits[i] - max_val - параллельно на всех ядрах
exp - на таблице значений - LUT (таблица предвычисленных значений - exponential lookup table)
+ Reduce-sum и деление
sum_exp = Σ exp(val_i) - in parralel - чз дерево суммирования - это бинарная структура данных,
используемая для эффективного параллельного вычисления суммы массива за время, где
— число элементов. В этом дереве каждый узел равен сумме своих двух дочерних узлов,
что позволяет объединять результаты вычислений на каждом уровне параллельно,
начиная с листьев и заканчивая корнем.
деление
p_i = exp(val_i) / sum_exp
+ 5 очень быстрых векторных инструкций
reduce_max
vector_sub
vector_exp
reduce_sum
vector_div