17 · Версия материала 4
Воспроизводимо инициализируйте обучаемые веса
Воспроизводимо инициализируйте матрицы весов с учётом ширины, сравните нулевую инициализацию, равномерную выборку с удвоенной границей и масштаб по схеме Ксавье, а затем проследите ожидаемую дисперсию по глубине.
Предскажите масштаб при заданном начальном значении и проявление симметрии
Инициализируйте проекцию формы [2,2], задав начальное значение генератора 17,
входную ширину и выходную ширину . До запуска примера предскажите целевую
дисперсию , стандартное отклонение и границу равномерного
распределения с центром в нуле, равную . После округления до двенадцати
знаков генератор выдаёт в построчном порядке такие значения:
0.004950883736 -0.265932089217
-0.420504358848 -0.676313443233
Если совпадают начальное значение генератора, форма, входная и выходная ширина и
порядок создания, тот же тензор должен воспроизводиться побитно. В выбранном примере начальное значение 18
даёт другой тензор. Эти свойства можно проверить, хотя вручную вычислять
последовательность псевдослучайных чисел учащемуся не требуется.
Теперь сопоставьте неодинаковые столбцы выбранной матрицы с вычислительным путём
из двух нейронов, начальные веса которых равны нулю. Пусть , входная матрица
формы [2,2] нулевая, после неё применяется SiLU, а выходные веса равны [1,1].
Скалярный выход равен нулю. Производная SiLU в нуле равна , поэтому оба
столбца градиента входных весов равны . Одинаковое обновление
сохраняет равенство скрытых нейронов.
rust/demos/ch17-parameter-initialization/src/lib.rs#zero-symmetry-probe pub fn zero_symmetry_probe() -> Result<SymmetryProbe, Box<dyn Error>> {
let input = TensorValue::constant(tensor(&[1, 2], &[1.0, -1.0])?)?;
let input_weights = TensorValue::parameter(tensor(&[2, 2], &[0.0; 4])?)?;
let output_weights = TensorValue::constant(tensor(&[2, 1], &[1.0, 1.0])?)?;
let hidden = input.matmul(&input_weights)?.silu()?;
let output = hidden.matmul(&output_weights)?;
let seed = tensor(&[1, 1], &[1.0])?;
output.backward_with_seed(&seed.view(), GraphRetention::Retain)?;
let output_value = output.value().as_slice()[0];
let gradient = input_weights
.gradient_snapshot()
.expect("the input weights are a trainable leaf");
let columns_equal = gradient.as_slice()[0] == gradient.as_slice()[1]
&& gradient.as_slice()[2] == gradient.as_slice()[3];
Ok(SymmetryProbe {
output: output_value,
gradient,
columns_equal,
})
} Это пример одного конкретного случая сохранения симметрии, а не правило, запрещающее любые нулевые начальные значения. Нулевые смещения и единичные коэффициенты нормализации могут быть осознанным выбором: они не создают двух одинаково обрабатываемых обучаемых столбцов признаков.
Задайте целевое распределение, а не точную статистику одной выборки
Основная формула главы:
Для равномерного распределения на интервале с центром в нуле дисперсия равна . Приравняв её к значению из основной формулы, получаем .
Слово «целевая» здесь существенно. Четыре выбранных числа не обязаны иметь дисперсию, в точности равную теоретической дисперсии распределения. Правило также не гарантирует сохранения масштаба сигнала: при его выводе используются упрощающие допущения о независимости и почти линейном режиме, тогда как будущий декодер содержит нелинейные функции, нормализацию, остаточные пути, данные и оптимизацию.
Точнее, при таком уравновешивании предполагаются независимые веса плотных слоёв, одинаковая дисперсия входных признаков и симметричная функция активации вблизи линейного режима с единичным наклоном. SiLU не вполне удовлетворяет последнему допущению. В создаваемом здесь декодере для обучаемых матриц используется инициализация по схеме Ксавье, необязательные смещения начинаются с нуля, а коэффициенты RMSNorm — с единицы. Позже матричный инициализатор будет использован для таблицы токенов с размером словаря и шириной признаков в качестве двух размеров формы. Это принятое правило реализации, а не следствие дисперсии операции выбора строки.
Обозначьте вес и обе ширины
| Символ | Практический смысл |
|---|---|
| Одна матрица весов до обучения. | |
| Индекс входной координаты одного веса. | |
| Индекс выходной координаты одного веса. | |
| Вес, соединяющий входную координату с выходной координатой . | |
| Целевая дисперсия распределения инициализации, а не измеренная дисперсия одной конечной матрицы. | |
| Число входных значений, суммируемых в одном выходе. | |
| Число выходов, в которые поступает каждое входное значение. | |
| Компромисс между условиями сохранения дисперсии в прямом проходе по входной ширине и в обратном проходе по выходной ширине. |
При входной ширине и выходной ширине целевое стандартное отклонение равно , а граница равномерного распределения — . Если увеличить входную ширину до , оставив выходную ширину равной , эти величины уменьшатся до и . Ширина входит в запрос на инициализацию: её нельзя определять задним числом после выбора значений.
От признаков слов в нейросетевой модели к параметрам декодера с учётом ширины
Bengio и соавторы совместно обучают признаки слов и матрицы нейросети для предсказания следующего слова и сообщают, что признаки слов инициализируются случайно, подобно весам нейросети. В работе не задано ни учитывающее размерности, ни воспроизводимое правило инициализации; при последовательном применении обучаемых преобразований через много слоёв произвольный масштаб становится всё более существенным.
Начальный этап развития нейросетевых языковых моделей представлен работой Bengio et al., A Neural Probabilistic Language Model. Bengio и соавторы задают обучаемую матрицу признаков слов и матрицы параметров нейросети для предсказания следующего слова, оптимизируют их совместно и сообщают, что признаки слов инициализируются случайно, подобно весам нейросети.
Эта работа подтверждает использование случайных начальных значений для обучаемых признаков и матриц языковой модели. Она не задаёт масштаб с учётом размерностей, точное распределение, начальное значение генератора, сам генератор, стабильные имена или порядок проверок.
Glorot и Bengio выводят компромисс для нормированной дисперсии глубокой сети прямого распространения при явно сформулированных допущениях о почти линейном режиме и независимости. Позже Vaswani и соавторы размещают обучаемые эмбеддинги на границах модели, а проекции внимания, выходную проекцию внимания и матрицы сети прямого распространения повторяют в слоях Transformer, поэтому в одной языковой модели появляется множество обучаемых матриц, масштаб которых зависит от ширины.
Переход к масштабу с учётом ширины описан в работе Glorot and Bengio, Understanding the difficulty of training deep feedforward neural networks. Glorot и Bengio при сформулированных упрощающих допущениях уравновешивают условия по дисперсии для входной и выходной ширины. Получается целевая дисперсия 2, делённая на сумму этих ширин, и нормированная равномерная инициализация, симметричная относительно нуля. Эти допущения обосновывают выбор масштаба, но не описывают точно декодер с SiLU, RMSNorm и остаточными связями.
Повторение обучаемых проекций в слоях Transformer описано в работе Vaswani et al., Attention Is All You Need. Vaswani и соавторы используют обучаемые эмбеддинги на границах модели, а в слоях Transformer повторяют проекции запросов, ключей и значений, выходную проекцию внимания и две проекции сети прямого распространения; работа не предписывает инициализатор параметров. Масштабирование оценок внимания и эмбеддингов относится к прямому вычислению, а не служит свидетельством инициализации по схеме Ксавье.
В этой главе матрицы весов будущего декодера получают значения из воспроизводимой выборки, стабильные имена и явно заданные целевые дисперсии, учитывающие ширину. В создаваемом здесь декодере матрицы весов инициализируются равномерно по схеме Ксавье, необязательные смещения — нулями, коэффициенты RMSNorm — единицами, а для таблицы токенов принято отдельное правило на основе её формы. Это явные правила реализации, а не утверждение, что исходная работа о Transformer предписывает их или что дисперсия любого сигнала сохраняется точно.
Так случайно инициализированные признаки слов в ранних нейросетевых моделях связываются с учитывающими дисперсию глубокими преобразованиями, а затем — с повторяющимися проекциями Transformer.
Создавайте и именуйте параметры без частичного изменения состояния
В реализации используется компактный и полностью определённый алгоритм SplitMix64 без внешних зависимостей. Начальное значение — это исходное состояние до первого приращения; ноль допустим. При каждом обращении старшие 53 перемешанных бита отображаются в число формата binary64, после чего выполняется аффинное преобразование к нужному равномерному распределению. Это детерминированная псевдослучайная выборка, а не криптографическая случайность.
rust/crates/llm-from-scratch/src/nn/init.rs#parameter-init-errors /// A deterministic rejection while constructing or collecting a parameter.
#[derive(Clone, Debug, PartialEq)]
pub enum InitializationError {
EmptyName,
EmptyNameSegment {
index: usize,
},
InvalidNameCharacter {
index: usize,
byte: u8,
},
ZeroFanIn,
ZeroFanOut,
FanSumOverflow {
fan_in: usize,
fan_out: usize,
},
ShapeProductOverflow {
fan_in: usize,
fan_out: usize,
},
AllocationFailed {
elements: usize,
},
DuplicateName {
name: String,
first: usize,
repeated: usize,
},
Tensor(TensorError),
Autodiff(TensorAutodiffError),
}
impl fmt::Display for InitializationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyName => formatter.write_str("parameter name must not be empty"),
Self::EmptyNameSegment { index } => write!(
formatter,
"parameter name has an empty dot-separated segment at byte {index}"
),
Self::InvalidNameCharacter { index, byte } => write!(
formatter,
"parameter name byte {index} must be lowercase ASCII, a digit, underscore, or dot; got 0x{byte:02x}"
),
Self::ZeroFanIn => formatter.write_str("fan-in must be greater than zero"),
Self::ZeroFanOut => formatter.write_str("fan-out must be greater than zero"),
Self::FanSumOverflow { fan_in, fan_out } => write!(
formatter,
"fan-in {fan_in} plus fan-out {fan_out} does not fit usize"
),
Self::ShapeProductOverflow { fan_in, fan_out } => write!(
formatter,
"matrix shape [{fan_in},{fan_out}] does not fit usize"
),
Self::AllocationFailed { elements } => write!(
formatter,
"could not reserve storage for {elements} initialized values"
),
Self::DuplicateName {
name,
first,
repeated,
} => write!(
formatter,
"parameter name {name:?} first appears at index {first} and repeats at index {repeated}"
),
Self::Tensor(error) => error.fmt(formatter),
Self::Autodiff(error) => error.fmt(formatter),
}
}
}
impl Error for InitializationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Tensor(error) => Some(error),
Self::Autodiff(error) => Some(error),
_ => None,
}
}
}
impl From<TensorError> for InitializationError {
fn from(error: TensorError) -> Self {
Self::Tensor(error)
}
}
impl From<TensorAutodiffError> for InitializationError {
fn from(error: TensorAutodiffError) -> Self {
Self::Autodiff(error)
}
} rust/crates/llm-from-scratch/src/nn/init.rs#deterministic-prng /// A small deterministic generator with an explicit resumable 64-bit state.
///
/// This generator is suitable for reproducible teaching fixtures. It is not a
/// cryptographically secure random-number generator.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
/// Treats `seed` as the raw state before the first increment and draw.
pub const fn from_seed(seed: u64) -> Self {
Self { state: seed }
}
/// Resumes directly from a previously recorded raw state.
pub const fn from_state(state: u64) -> Self {
Self { state }
}
/// Returns the raw state that the next draw will advance.
pub const fn state(&self) -> u64 {
self.state
}
/// Advances and mixes one exactly specified 64-bit value.
pub fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(SPLITMIX_INCREMENT);
let mut value = self.state;
value = (value ^ (value >> 30)).wrapping_mul(SPLITMIX_MIX_ONE);
value = (value ^ (value >> 27)).wrapping_mul(SPLITMIX_MIX_TWO);
value ^ (value >> 31)
}
/// Maps the high 53 bits of one draw to the binary64 interval [0,1).
pub fn next_unit_f64(&mut self) -> f64 {
((self.next_u64() >> 11) as f64) * BINARY64_UNIT_SCALE
}
} rust/crates/llm-from-scratch/src/nn/init.rs#xavier-initialization /// The formula-derived scale for one [fan-in, fan-out] matrix.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct XavierScale {
fan_in: usize,
fan_out: usize,
target_variance: f64,
standard_deviation: f64,
uniform_limit: f64,
}
impl XavierScale {
pub const fn fan_in(self) -> usize {
self.fan_in
}
pub const fn fan_out(self) -> usize {
self.fan_out
}
pub const fn target_variance(self) -> f64 {
self.target_variance
}
pub const fn standard_deviation(self) -> f64 {
self.standard_deviation
}
pub const fn uniform_limit(self) -> f64 {
self.uniform_limit
}
}
/// Calculates the Xavier variance, standard deviation, and uniform bound.
pub fn xavier_scale(fan_in: usize, fan_out: usize) -> Result<XavierScale, InitializationError> {
if fan_in == 0 {
return Err(InitializationError::ZeroFanIn);
}
if fan_out == 0 {
return Err(InitializationError::ZeroFanOut);
}
let fan_sum = fan_in
.checked_add(fan_out)
.ok_or(InitializationError::FanSumOverflow { fan_in, fan_out })?;
let target_variance = 2.0 / fan_sum as f64;
Ok(XavierScale {
fan_in,
fan_out,
target_variance,
standard_deviation: target_variance.sqrt(),
uniform_limit: (6.0 / fan_sum as f64).sqrt(),
})
}
fn checked_element_count(fan_in: usize, fan_out: usize) -> Result<usize, InitializationError> {
fan_in
.checked_mul(fan_out)
.ok_or(InitializationError::ShapeProductOverflow { fan_in, fan_out })
}
pub(crate) fn validate_name(name: &str) -> Result<(), InitializationError> {
if name.is_empty() {
return Err(InitializationError::EmptyName);
}
let mut previous_was_dot = true;
for (index, byte) in name.bytes().enumerate() {
if byte == b'.' {
if previous_was_dot {
return Err(InitializationError::EmptyNameSegment { index });
}
previous_was_dot = true;
continue;
}
if !(byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_') {
return Err(InitializationError::InvalidNameCharacter { index, byte });
}
previous_was_dot = false;
}
if previous_was_dot {
return Err(InitializationError::EmptyNameSegment { index: name.len() });
}
Ok(())
}
fn initialized_values(
rng: &mut SplitMix64,
scale: XavierScale,
) -> Result<Vec<f64>, InitializationError> {
let elements = checked_element_count(scale.fan_in, scale.fan_out)?;
let mut values = Vec::new();
values
.try_reserve_exact(elements)
.map_err(|_| InitializationError::AllocationFailed { elements })?;
for _ in 0..elements {
let centered = 2.0 * rng.next_unit_f64() - 1.0;
values.push(scale.uniform_limit * centered);
}
Ok(values)
} rust/crates/llm-from-scratch/src/nn/init.rs#named-parameters /// One immutable external name paired with one trainable tensor-tape leaf.
#[derive(Clone, Debug)]
pub struct NamedParameter {
name: String,
tensor: TensorValue,
}
impl NamedParameter {
/// Wraps an already-created tensor as a named trainable leaf.
pub fn from_tensor(
name: impl Into<String>,
tensor: Tensor,
) -> Result<Self, InitializationError> {
let name = name.into();
validate_name(&name)?;
Ok(Self {
name,
tensor: TensorValue::parameter(tensor)?,
})
}
/// Samples one [fan-in, fan-out] trainable matrix transactionally.
pub fn xavier_uniform(
name: impl Into<String>,
fan_in: usize,
fan_out: usize,
rng: &mut SplitMix64,
) -> Result<Self, InitializationError> {
let name = name.into();
validate_name(&name)?;
let scale = xavier_scale(fan_in, fan_out)?;
let elements = checked_element_count(fan_in, fan_out)?;
let mut trial = rng.clone();
let values = initialized_values(&mut trial, scale)?;
debug_assert_eq!(values.len(), elements);
let tensor = Tensor::from_vec(vec![fan_in, fan_out], values)?;
let parameter = Self {
name,
tensor: TensorValue::parameter(tensor)?,
};
*rng = trial;
Ok(parameter)
}
/// Returns the stable external identity used by layers and checkpoints.
pub fn name(&self) -> &str {
&self.name
}
/// Borrows the trainable tape leaf without duplicating its tensor storage.
pub fn tensor(&self) -> &TensorValue {
&self.tensor
}
}
/// A duplicate-checked, declaration-ordered set of named parameters.
#[derive(Clone, Debug, Default)]
pub struct NamedParameters {
parameters: Vec<NamedParameter>,
}
impl NamedParameters {
pub fn try_new(parameters: Vec<NamedParameter>) -> Result<Self, InitializationError> {
for repeated in 0..parameters.len() {
if let Some(first) = parameters[..repeated]
.iter()
.position(|parameter| parameter.name() == parameters[repeated].name())
{
return Err(InitializationError::DuplicateName {
name: parameters[repeated].name().to_owned(),
first,
repeated,
});
}
}
Ok(Self { parameters })
}
pub fn len(&self) -> usize {
self.parameters.len()
}
pub fn is_empty(&self) -> bool {
self.parameters.is_empty()
}
pub fn as_slice(&self) -> &[NamedParameter] {
&self.parameters
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &NamedParameter> {
self.parameters.iter()
}
pub fn get(&self, name: &str) -> Option<&NamedParameter> {
self.parameters
.iter()
.find(|parameter| parameter.name() == name)
}
} Имена состоят из непустых сегментов, разделённых точками. В каждом сегменте допустимы строчные латинские буквы ASCII, цифры и знаки подчёркивания. В заданном порядке проверяются имя, нулевая входная ширина, нулевая выходная ширина, переполнение суммы ширин, переполнение произведения размеров формы, выделение памяти и создание тензора. Для выборки используется клон генератора, а его новое состояние переносится в исходный генератор только после успешного создания обучаемого листового узла-параметра. Поэтому любая возвращённая ошибка оставляет состояние генератора вызывающего кода неизменным.
Имя служит стабильным внешним идентификатором. Клон NamedParameter сохраняет
тот же узел ленты операций, тогда как независимо созданный равный
тензор получает другой узел. Числовые ID параметров, абстракции слоёв, группы
оптимизатора и формат контрольной точки в этой главе не вводятся.
rust/demos/ch17-parameter-initialization/src/lib.rs#fixed-seed-parameter let scale = xavier_scale(2, 2)?;
let mut rng = SplitMix64::from_seed(FIXTURE_SEED);
let projection = projection_parameter(&mut rng)?;
let weights = projection.tensor().value_snapshot();
let mut matching_rng = SplitMix64::from_seed(FIXTURE_SEED);
let matching = projection_parameter(&mut matching_rng)?;
let mut alternate_rng = SplitMix64::from_seed(ALTERNATE_SEED);
let alternate = projection_parameter(&mut alternate_rng)?; rust/demos/ch17-parameter-initialization/src/lib.rs#named-parameter-enumeration let token_table = NamedParameter::xavier_uniform("token_embedding.weight", 4, 2, &mut rng)?;
let parameters = NamedParameters::try_new(vec![projection.clone(), token_table])?;
let projection_clone = projection.clone();
let clone_same_node = projection.tensor().is_same_node(projection_clone.tensor());
let recreated_same_node = projection.tensor().is_same_node(matching.tensor()); rust/demos/ch17-parameter-initialization/src/lib.rs#initialization-errors-example let state_before_errors = rng.state();
let invalid_name =
NamedParameter::xavier_uniform("Decoder.weight", 2, 2, &mut rng).unwrap_err();
let zero_fan_in =
NamedParameter::xavier_uniform("decoder.invalid.weight", 0, 2, &mut rng).unwrap_err();
let duplicate_name =
NamedParameters::try_new(vec![projection.clone(), projection.clone()]).unwrap_err();
let rng_unchanged = rng.state() == state_before_errors; rust/demos/ch17-parameter-initialization/src/main.rs#learner-parameter-initialization-output let report = learner_report()?;
println!("seed: 17");
println!("projection: shape=2x2 fan_in=2 fan_out=2");
println!("target variance: {}", fixed(report.scale.target_variance()));
println!("uniform limit: {}", fixed(report.scale.uniform_limit()));
println!(
"weights: {}",
report
.weights
.as_slice()
.iter()
.map(|value| fixed(*value))
.collect::<Vec<_>>()
.join(",")
);
println!("same seed reproduces: {}", report.same_seed_reproduces);
println!("different seed differs: {}", report.different_seed_differs);
println!(
"zero symmetry: output={} columns-equal={} gradient={}",
fixed(report.symmetry.output),
report.symmetry.columns_equal,
report
.symmetry
.gradient
.as_slice()
.iter()
.map(|value| fixed(*value))
.collect::<Vec<_>>()
.join(",")
);
println!(
"parameters: {}",
report
.parameters
.iter()
.map(|parameter| format!(
"{}[{}]",
parameter.name(),
shape(¶meter.tensor().shape())
))
.collect::<Vec<_>>()
.join(" | ")
);
println!(
"identity: clone-same-node={} recreated-same-node={}",
report.clone_same_node, report.recreated_same_node
);
println!(
"validation: invalid-name | duplicate-name | zero-fan-in; rng-unchanged={}",
report.rng_unchanged
);
println!("chapter 18 handoff: initialize a trainable token table"); Запустите полный пример из корня репозитория:
./course run cargo run --quiet --locked -p ch17-parameter-initialization
В выводе видны теоретический масштаб, выбранные значения, сопоставление симметрии, стабильные имена параметров и ошибки, при которых состояние генератора остаётся прежним.
Сопоставьте распределения при общем начальном значении и ожидаемую дисперсию
В диагностическом примере форма увеличена до [64,64], поэтому для каждой
стратегии получается 4096 весов. При нулевой инициализации генератор не
используется. Равномерные выборки с удвоенной границей и с границей по схеме
Ксавье используют одни и те же исходные значения генератора с начальным
состоянием 17; первая граница ровно вдвое больше второй. Такое сопоставление
позволяет отделить влияние масштаба: программа записывает интервалы гистограммы,
количество и отображаемую долю значений в них, а также статистики по всей
совокупности, рассчитанные за два прохода.
Рассуждение во второй части схемы справедливо только при сформулированных допущениях. Начиная с единичной дисперсии входа, она показывает ожидаемую дисперсию после четырёх независимых линейных слоёв: нулевые веса обращают её в ноль, удвоенная граница умножает дисперсию на четыре в каждом слое, а при масштабе Ксавье она остаётся равной единице. Это ожидаемые значения при допущениях о линейности и независимости, а не измерения или гарантии для нелинейного декодера с остаточными связями.
rust/demos/ch17-parameter-initialization/src/diagram_trace.rs#parameter-initialization-trace pub fn render_trace() -> Result<String, Box<dyn Error>> {
let scale = xavier_scale(WIDTH, WIDTH)?;
let mut rng = SplitMix64::from_seed(FIXTURE_SEED);
let xavier = NamedParameter::xavier_uniform("diagnostic.weight", WIDTH, WIDTH, &mut rng)?
.tensor()
.value()
.as_slice()
.to_vec();
let oversized: Vec<_> = xavier.iter().map(|value| value * 2.0).collect();
let zero = vec![0.0; SAMPLE_COUNT];
let zero_statistics = statistics(&zero);
let oversized_statistics = statistics(&oversized);
let xavier_statistics = statistics(&xavier);
let zero_histogram = histogram(&zero);
let oversized_histogram = histogram(&oversized);
let xavier_histogram = histogram(&xavier);
let mut same_seed_rng = SplitMix64::from_seed(FIXTURE_SEED);
let same_seed =
NamedParameter::xavier_uniform("diagnostic.weight", WIDTH, WIDTH, &mut same_seed_rng)?
.tensor()
.value()
.as_slice()
.to_vec();
let mut alternate_rng = SplitMix64::from_seed(ALTERNATE_SEED);
let alternate =
NamedParameter::xavier_uniform("diagnostic.weight", WIDTH, WIDTH, &mut alternate_rng)?
.tensor()
.value()
.as_slice()
.to_vec();
let mut trace = String::new();
writeln!(trace, "TRACE parameter-initialization-v2 BEGIN")?;
writeln!(
trace,
"FIXTURE name=fixed-seed-width64 generator=splitmix64 mapping=top53-affine seed=17 shape=64x64 samples=4096 fan-in=64 fan-out=64 statistic=population-two-pass layers=0,1,2,3,4 propagation=expected-linear-independent input-variance=1.000000000000 display-input-variance=1"
)?;
writeln!(
trace,
"BINNING edges={} display-edges={} width=0.100000000000 display-width=0.10 closure=left-closed-right-open-last-closed",
fixed_list(&EDGES),
DISPLAY_EDGES.join(",")
)?;
writeln!(
trace,
"{}",
distribution_line("zero", "none", 0.0, &zero_statistics)
)?;
writeln!(trace, "{}", histogram_line("zero", &zero_histogram))?;
writeln!(
trace,
"{}",
distribution_line(
"oversized",
"17",
scale.uniform_limit() * 2.0,
&oversized_statistics,
)
)?;
writeln!(
trace,
"{}",
histogram_line("oversized", &oversized_histogram)
)?;
writeln!(
trace,
"{}",
distribution_line("xavier", "17", scale.uniform_limit(), &xavier_statistics)
)?;
writeln!(trace, "{}", histogram_line("xavier", &xavier_histogram))?;
writeln!(
trace,
"PAIRING seed=17 base-draws-equal=yes oversized-to-xavier-limit=2.000000000000"
)?;
writeln!(
trace,
"PROPAGATION kind=zero variances={} display-variances={}",
fixed_list(&expected_variances(0.0)),
display_integer_list(&expected_variances(0.0))
)?;
writeln!(
trace,
"PROPAGATION kind=oversized variances={} display-variances={}",
fixed_list(&expected_variances(4.0)),
display_integer_list(&expected_variances(4.0))
)?;
writeln!(
trace,
"PROPAGATION kind=xavier variances={} display-variances={}",
fixed_list(&expected_variances(1.0)),
display_integer_list(&expected_variances(1.0))
)?;
writeln!(
trace,
"REPRODUCIBILITY seed=17 same-seed-equal={} alternate-seed=18 alternate-seed-different={}",
if xavier == same_seed { "yes" } else { "no" },
if xavier != alternate { "yes" } else { "no" },
)?;
writeln!(trace, "TRACE parameter-initialization-v2 END")?;
Ok(trace)
} Сопоставьте нулевые веса с двумя связанными масштабами
Сравните конечные выборки нулевой инициализации, равномерной инициализации с удвоенной границей и схемы Ксавье; затем проследите теоретическую дисперсию через четыре независимых линейных слоя.
- Общее начальное значение
- 17
- Форма матрицы
64x64- Число весов
- 4096
- Входная ширина
- 64
- Выходная ширина
- 64
- Дисперсия входа
- Генератор и отображение
splitmix64 / top53-affine- Статистика
- Дисперсия совокупности, рассчитанная в два прохода
Сопоставьте распределения весов
Левая граница входит во все интервалы, правая — только в последний.
Нулевые веса
- Начальное значение
- Без выборки
- Граница распределения
- 0.0000
- Минимум выборки
- 0.0000
- Максимум выборки
- 0.0000
- Среднее выборки
- 0.0000
- Дисперсия совокупности
- 0.0000
Равномерные веса с удвоенной границей
- Начальное значение
- 17
- Граница распределения
- 0.4330
- Минимум выборки
- -0.4330
- Максимум выборки
- 0.4326
- Среднее выборки
- -0.0067
- Дисперсия совокупности
- 0.0632
Равномерные веса по схеме Ксавье
- Начальное значение
- 17
- Граница распределения
- 0.2165
- Минимум выборки
- -0.2165
- Максимум выборки
- 0.2163
- Среднее выборки
- -0.0034
- Дисперсия совокупности
- 0.0158
Сравнение на общей выборке
Общие исходные значения.
- Начальное значение
- 17
- Отношение границ
- 2.000000000000
| Правило | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| 0 0.0% | 0 0.0% | 0 0.0% | 0 0.0% | 4096 100.0% | 0 0.0% | 0 0.0% | 0 0.0% | 0 0.0% | |
| 409 10.0% | 498 12.2% | 482 11.8% | 472 11.5% | 469 11.5% | 445 10.9% | 476 11.6% | 443 10.8% | 402 9.8% | |
| 0 0.0% | 0 0.0% | 674 16.5% | 962 23.5% | 919 22.4% | 930 22.7% | 611 14.9% | 0 0.0% | 0 0.0% |
Проследите ожидаемую дисперсию по глубине
Это ожидаемые дисперсии для независимых линейных слоёв с единичной дисперсией входа, а не измеренные гарантии для нелинейного декодера с остаточными связями.
| Глубина | |||
|---|---|---|---|
| 0 | |||
| 1 | |||
| 2 | |||
| 3 | |||
| 4 |
Проверьте границы воспроизводимости
Тот же запрос при том же начальном значении даёт точное совпадение
- Начальное значение
- 17
Другое выбранное начальное значение даёт иной результат
- Начальное значение
- 18
Обе равномерные выборки используют одинаковые исходные значения, поэтому их гистограммы и статистики различаются только из-за удвоенного масштаба. Дисперсии на гистограммах измерены по значениям; таблица глубины, напротив, следует теоретическим множителям целевой дисперсии при сформулированных допущениях о линейности.
Предскажите результат до запуска Rust
- При входной ширине и выходной ширине вычислите целевую дисперсию, стандартное отклонение и границу равномерного распределения.
- Вычислите эти три величины заново, если входная ширина станет равной , а выходная останется равной .
- Выведите два столбца градиента входных весов для примера с нулевыми весами и SiLU.
- Какие значения должны совпасть, если одинаковы начальное значение генератора, форма, обе ширины и порядок создания?
- Почему запрос с выбранным начальным значением
18даёт другой результат, но это не доказывает различие для любой пары возможных начальных значений? - Почему измеренная дисперсия одного конечного тензора
[2,2]не обязана в точности равняться ? - В каком порядке перечисляются
decoder.block.0.attention.query.weightиtoken_embedding.weight? - Изменяют ли состояние генератора отклонённое недопустимое имя или нулевая ширина и о каком повторе сообщает коллекция?
- Предписывают ли Vaswani и соавторы инициализацию Transformer по схеме Ксавье?
- Гарантирует ли формула точную измеренную дисперсию или сохранение масштаба любого сигнала?
Проверьте предсказания
- Целевая дисперсия равна , стандартное отклонение — , а граница — .
- Новые значения равны соответственно , и .
- Оба столбца равны : из-за одинаковых выходных весов и производной SiLU оба скрытых нейрона получают один и тот же множитель обратного прохода.
- Для такого полностью одинакового запроса совпадают поток генератора и биты итогового тензора.
- В этом примере запрос с начальным значением
18даёт другой тензор. Генератор псевдослучайных чисел детерминирован, но наблюдаемое различие не является математической гарантией уникальности для любого начального значения и запроса. - Формула описывает распределение. Статистика небольшой конечной выборки подвержена выборочному разбросу.
- Сохраняется порядок объявления: первой идёт проекция запросов, за ней — таблица токенов.
- Ни одна из этих ошибок не продвигает генератор. Коллекция сообщает первое повторяющееся имя вместе с индексами первого и повторного вхождения.
- Нет. В работе описаны обучаемые параметры Transformer, но способ их инициализации не предписан.
- Нет. Схема Ксавье задаёт целевую дисперсию распределения при упрощающих допущениях; конечная выборка, нелинейные функции, нормализация, остаточные пути, глубина, данные и оптимизация по-прежнему влияют на распространение сигнала.
Главное заблуждение — считать целевую дисперсию распределения точной измеренной дисперсией одной матрицы или обещанием, что масштаб активаций никогда не затухнет и не возрастёт чрезмерно. Инициализация по схеме Ксавье — ясная исходная точка с ограниченным диапазоном, но такой гарантии она не даёт.
Придайте инициализированной матрице смысл таблицы токенов
Теперь совокупная реализация умеет воспроизводимо создавать именованные обучаемые матрицы с явно заданным масштабом, учитывающим входную и выходную ширину. В главе 18 матрица станет таблицей эмбеддингов: ID токенов выбирают строки, повторяющиеся ID обращаются к одной строке, а их градиентные вклады суммируются. Для этой таблицы матричный инициализатор получает размер словаря и ширину признаков как два размера формы — это отдельное принятое правило, а не следствие дисперсии операции выбора строки.
Если несколько параметров используют общий поток генератора, порядок их создания влияет на результат: вставка более раннего параметра изменит все последующие выбранные значения. Поэтому порядок следует задавать осознанно и проверять. Глава 18 добавит семантику выбора токенов, в главе 21 перемешивание данных получит отдельный поток генератора, глава 22 введёт состояние оптимизатора, а глава 35 сохранит значения параметров и сведения о происхождении состояния обучения.