All streams
Search
Write a publication
Pull to refresh

Comments 22

В подсекции Функция потерь в формуле её приближения p_{\thetа} обратная косая а нижнем индексе потерялась.

А так -- кратко, чётко и ясно ! Спасибо.

ПыСы. Там где-то должна центральная предельная теорема для мартингалов проявиться. О том, что "стационарные" нормальные процессы с небольшим равномерным отклонением сходятся строго к нормальному распределению. Вроде бы философски все из нее присходит.

Поправил, спасибо!

Ознакомился с мартингалами, да очень похоже на то, что мы получаем при forward процессе. Странно, что ни в одном из пейперов, связанных с диффузией не встречал этого термина.

Спасибо! Есть над чем голову поломать) Я правильно понял, что при генерации картинки для "затравки" используется просто случайный шум, а дальше нейросеть "расшумляет" его до состоянии нужной картинки?

P.S. под спойлером "Еще немного кода" не распарсился код.

Поправил, спасибо!

Если мы говорим о генерации картинки на обученной модели, то да, даем шум, а она его расшумляет последовательно T раз убирая по чуть-чуть шума)

При тренировке мы накладываем шум на картинку и заставляем нейросеть предсказать этот же шум.

Спасибо за статью. У вас осталось за кадром то, как при генерации результат прогона промпта через CLIP гайдит детектор шума в сторону нужного промпта. Не знаете, где можно найти хорошие материалы на эту тему?

Спасибо, приятно. В рамках этой статьи и репозитория я не разрабатывал latent diffusion model(или stable diffusion), который как раз таки использует CLIP. Но по сути это тот же conditional diffusion, где помимо embedding класса, добавляет embedding промпта, полученный от CLIP.
Слои и модули conditional diffusion в простом варианте находиться здесь: https://github.com/juraam/stable-diffusion-from-scratch/tree/main/src/condition_diffusion

А нет никаких рекомендаций типа:

1) Возьмите изображение с дапазоном представления градация яркости (или цветов) [-1,1].

2) Для каждой точки вычислите... таким вот образом... и замените... Получите зашумлённые изображение.

Ну и т.д. А то фиг поймёшь, что делать нужно.

То есть, именно алгоритм по действиям, а не пособие для математиков.

Я вот пытаюсь хотя бы просто зашумлённое изображение получить (и не на питоне - я его не знаю). И что же я вижу тут?

timestep = 800

xs = np.arange(-5, 5, 0.01)

y_results = norm.pdf(xs, 0, 1)

different_schedules = [
    # constant
    np.ones(timestep) * 0.1,
    np.ones(timestep) * 0.3,
    np.ones(timestep) * 0.5,
    np.ones(timestep) * 0.7,
    np.ones(timestep) * 0.9,
    # linear change (from paper)
    np.linspace(1e-4, 0.02, timestep)
]

x0 = 1

for bt in different_schedules:

    # Set xt to zero, which will be used as x(t-1) like in sqrt(1-b) * x(t-1)
    xt = x0

    q_results = None

    # A simple loop by timestep
    for t in range(timestep):

        # Calculate the mean as in the formula
        mean = math.sqrt(1 - bt[t]) * xt

        # Calculate the variance
        variance = bt[t]

        # Q is a normal distribution, so to get the next xt, we just get random element from our distribution
        xt = np.random.normal(mean, math.sqrt(variance))
    q_results = norm.pdf(xs, mean, math.sqrt(variance))
    plt.plot(xs, q_results, label=f"qt for bt {bt[0] if bt[0] == bt[-1] else 'linear'}")
plt.plot(xs, y_results, label="Y")
plt.legend()
plt.show()

Число порчти картинок равно размерности different_schedules. То есть, 6 раз. Число точек в картинке 10/0.01=1000. Хорошо. А что такое timestep=800?

Как считается y_results = norm.pdf(xs, 0, 1) я понял вроде бы. Там формула

То есть, для каждого xs получаем значение y.

Теперь смотрим цикл. x0 -это число или вектор? Вроде как число. А где оно используется? Так нигде же. Каждый раз им инициализируют xt. А почему бы просто xt=1 тогда не написать?

А что есть mean = math.sqrt(1 - bt[t]) * xt - это для каждого значения timestep? Так это одна точка. bt[t] - это же одно число. И xt одно число.

variance = bt[t] - это точно ведь одно число.

Дальше мы получаем случайное число

xt = np.random.normal(mean, math.sqrt(variance)).

И... цикл закончен? А где модификация изображения в q_results? Вне цикла?

q_results = norm.pdf(xs, mean, math.sqrt(variance))

Но с какими же параметрами? Ведь внутри цикла менялось и среднее и дисперсия?

Если Deep Seek не врёт, то добавление шума можно сделать так:

//генератор случайных чисел
 std::random_device rd;
 std::mt19937 gen(rd());
 std::normal_distribution<float> dist(0.0f, 1.0f);
 //делаем изображения с шумом
 static const uint32_t T_COUNTER=20;

 std::vector<float> beta(T_COUNTER);//параметры шума для каждого шага
 std::vector<float> alpha(T_COUNTER);//1-beta
 std::vector<float> alpha_bar(T_COUNTER);//произведение alpha от 0 до t
 //заполняем коэффициенты
 float beta_start=0.01f;
 float beta_end=0.3f;
 for(uint32_t i=0;i<T_COUNTER;i++)
 {
  beta[i]=beta_start+(beta_end-beta_start)*i/(T_COUNTER-1);
  alpha[i]=1-beta[i];
  alpha_bar[i]=(i==0)?alpha[i]:alpha_bar[i-1]*alpha[i];
 }
 std::vector<type_t> &image=RealImage[0];//входное изображение
 std::vector<type_t> output(image.size());
 for(size_t t=0;t<T_COUNTER;t++)
 {
  //генерируем шум
  std::vector<float> noise(image.size());
  for(uint32_t i=0;i<image.size();i++) noise[i]=dist(gen);
  //вычисляем коэффициенты для текущего шага
  float sqrt_alpha_bar=std::sqrt(alpha_bar[t]);
  float sqrt_one_minus_alpha_bar=std::sqrt(1-alpha_bar[t]);
  //применяем шум к изображению
  for(uint32_t i=0;i<image.size();i++) output[i]=sqrt_alpha_bar*image[i]+sqrt_one_minus_alpha_bar*noise[i];

  //выводим картинку
  sprintf(str,"Test/real%03i.tga",static_cast<int>(t));
  type_t *ptr=&output[0];
  uint32_t size=image.size();
  cTensor_Image.CopyItemLayerWToDevice(0,ptr,size);
  SaveImage(cTensor_Image,str,0,IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_DEPTH);
 }

на картинку добавляется шум 800 раз последовательно. В первом случае schedule будет иметь все 0.1, второй - все 0.3, а последний - линейно возрастающая последовательность.

x0 - начальный шаг, это сделано для схожести с формулами. То есть на самом деле здесь описан последовательный процесс получения конечных среднего и дисперсии. Для наглядности я сравниваю полученный график с графиком нормального распределения от 0 до 1. То есть выглядит это так: получаем mean, std из xt -> берем рандомную точку из нормального распределения с этим mean, std -> получаем новый xt -> повторяем.

В случае картинок можно сделать тоже самое, только вместо x0 будет двумерный или 3х мерный массив(если цветное изображение). Также можно заюзать все формулы с последовательн. Последний xt - зашумленное изображение(тк размерность xt = x0, в случае массива np.random.normal(mean, math.sqrt(variance)) будет возвращать новый массив)

А затем мы убираем из этой формулы np.random.normal заменяя просто на сумму со случайной величиной

Спасибо!

Я вот чего не понял. Вот есть у нас изображение. Мы из него делаем последовательность разных зашумлений.

Как я понимаю, мы их не делаем цепочкой. Мы сразу получаем зашумлённое изображение на нужном шаге t.

Если я запомню для каждого t какой был шум и какое изображение получилось, то могу ли я восстановить изображение из результата, полученного для последнего значения t (Пусть t менялось от 0 до K) ? То есть, взять последнее из серии зашумлений изображение (для K - оно уже сплошной шум) и восстановить его, зная каким шумом оно зашумлялось. По сути, ведь сеть должна предсказывать шум каждой итерации, но в данном случае шум для каждого t мы запомнили и сеть убираем.

И вот тут получается вот что:

Берём изображение Xk. Берём шум для k. Применяем формулу выше. И получаем... шум. Применяем эту формулу k раз каждый раз применяя известный шум для данной итерации (от k до 0) и получаем... шум. Изображения нет. Если же альфы заменить на альфы с чёрточкой, тогда эта формула расшумляет изображение за один последний проход и дальше только портит.

Что за ерунда?

Единственно, тут есть нюанс: "но в данном случае шум для каждого t мы запомнили и сеть убираем.". Формально мы не накладывали этот шум последовательным зашумлением. Мы наложили только один шум, итоговый. Если его мы вычтем, тогда мы и получим изображение. Но вся предшествующая серия шумов как бы живёт сама по себе, отдельно от финального зашумлённого изображения. Возможно, именно в этом и кроется нюанс, почему не получается последовательно расшумлять картинку без сети. Сеть же как знает, что за картинка была зашумлена и под исходную картинку для каждого этапа подгоняет шум, изменяя картинку каждый раз.

Я обучаю на датасете MNIST с 16000 изображений цифр интерполированных с 28x28 до 32x32 (свёртки такого размера удобно получать). 20 стадий зашумления. Итого, 20*16000=320000 изображений. Я даю их сети пакетами вместе с наложенным шумом и шагом времени. Сеть должна запомнить этот шум. Обучение идёт очень медленно. 250 эпох - ошибка (сумма квадратов разности между требуемым шумом и полученным шумом) 600. Начиналась с ошибки 1200. Скорость обучения 0.0001. Да, и всё это на самоделке на C++. Так что возможно, что-то я сделал неправильно. Понять бы, так всё и должно быть или нет.

Сейчас изображения не получаются вообще. Получается пока такое:

Ошибка, конечно, ещё большая, но хоть что-то напоминающее цифры могло бы хоть контуром каким бы и проявиться. Но нифига.

Расшумляю я так:

 Noise.resize(RealImage[0].size());
 //задаём сети начальный шум
 for(uint32_t b=0;b<BATCH_SIZE;b++)
 {
  GetNoise(Noise);
  //задаём шум
  type_t* ptr=&Noise[0];
  uint32_t size=Noise.size();
  DiffusionNet[0]->GetOutputTensor().CopyItemLayerWToDevice(b,ptr,size);
 }
 for(int32_t t=TIME_COUNTER-1;t>=0;t--)
 {
  //задаём сети момент времени
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->SetTimeStep(b,t);
  }
  //получаем от сети предыдущий шум
  for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->Forward();
  //расшумляем изображение
  float alpha=sDiffusion.Alpha[t];
  float beta=sDiffusion.Beta[t];
  float sqrt_alpha=std::sqrt(alpha);
  float sqrt_alpha_bar=std::sqrt(sDiffusion.AlphaBar[t]);
  float sqrt_one_minus_alpha_bar=std::sqrt(1.0-sDiffusion.AlphaBar[t]);
  type_t k=(1.0/sqrt_alpha);
  CTensor<type_t> &input_noise=DiffusionNet[0]->GetOutputTensor();//входной шум
  CTensor<type_t> &output_noise=DiffusionNet[DiffusionNet.size()-1]->GetOutputTensor();//предсказанный сетью шум
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   uint32_t index=0;
   GetNoise(Noise);//новый добавляемый шум
   for(uint32_t z=0;z<input_noise.GetSizeZ();z++)
   {
    for(uint32_t y=0;y<input_noise.GetSizeY();y++)
    {
     for(uint32_t x=0;x<input_noise.GetSizeX();x++,index++)
     {
      type_t input=input_noise.GetElement(b,z,y,x);
      type_t output=output_noise.GetElement(b,z,y,x);
      type_t net_noise=output*(1.0-alpha)/sqrt_one_minus_alpha_bar;
      type_t prev_noise=k*(input-net_noise);
      if (t>0) prev_noise+=beta*Noise[index];
      //задаём новый шум
      input_noise.SetElement(b,z,y,x,prev_noise);
     }
    }
   }
  }
 }
 //сохраняем изображения (они на входе сети) 
 cTensor_Image=DiffusionNet[0]->GetOutputTensor();
 char str[STRING_BUFFER_SIZE];
 static uint32_t counter=0;
 for(uint32_t n=0;n<BATCH_SIZE;n++)
 {
  sprintf(str,"Test/test%05i-%03i.tga",static_cast<int>(counter),static_cast<int>(n));
  SaveImage(cTensor_Image,str,n,IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_DEPTH);
  if (n==0) SaveImage(cTensor_Image,"Test/test-current.tga",n,IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_DEPTH);
 }

А зашумляю так:

  //параметры диффузии
  struct SDiffusion
  {
   std::vector<type_t> Beta;///<параметры шума для каждого шага
   std::vector<type_t> Alpha;///<1-beta
   std::vector<type_t> AlphaBar;///<произведение alpha от 0 до t

   void Init(uint32_t time_counter)///<инициализация
   {
    Beta.resize(time_counter);
    Alpha.resize(time_counter);
    AlphaBar.resize(time_counter);
    //заполняем коэффициенты зашумления
    float beta_start=0.01f;
    float beta_end=0.3f;
    for(uint32_t i=0;i<time_counter;i++)
    {
     Beta[i]=beta_start+(beta_end-beta_start)*i/(time_counter-1);
     Alpha[i]=1-Beta[i];
     if (i==0) AlphaBar[i]=Alpha[i];
          else AlphaBar[i]=AlphaBar[i-1]*Alpha[i];
    }
   }
  };

...

SDiffusion sDiffusion;///<параметры диффузии
std::vector<type_t> NoisyImage;///<зашумлённое изображение
std::vector<type_t> Noise;///<накладываемый шум

...
  

//----------------------------------------------------------------------------------------------------
//!получить зашумлённое изображение и шум
//----------------------------------------------------------------------------------------------------
template<class type_t>
void CModelBasicDiffusion<type_t>::GetNoisyImageAndNoise(uint32_t time_step,const std::vector<type_t> &input_image,std::vector<type_t> &noisy_image,std::vector<type_t> &noise)
{
 noise.resize(input_image.size());
 noisy_image.resize(input_image.size());

 //генерируем шум
 GetNoise(noise);
 //вычисляем коэффициенты для текущего шага
 type_t sqrt_alpha_bar=std::sqrt(sDiffusion.AlphaBar[time_step]);
 type_t sqrt_one_minus_alpha_bar=std::sqrt(1.0-sDiffusion.AlphaBar[time_step]);
 //применяем шум к изображению
 for(uint32_t i=0;i<input_image.size();i++)
 {
  noisy_image[i]=sqrt_alpha_bar*input_image[i]+sqrt_one_minus_alpha_bar*noise[i];
 }
}

//----------------------------------------------------------------------------------------------------
//!получить  шум
//----------------------------------------------------------------------------------------------------
template<class type_t>
void CModelBasicDiffusion<type_t>::GetNoise(std::vector<type_t> &noise)
{
 //генератор случайных чисел
 std::random_device rd;
 std::mt19937 gen(rd());
 std::normal_distribution<type_t> dist(0.0f,1.0f);

  //генерируем шум
 for(uint32_t i=0;i<noise.size();i++) noise[i]=dist(gen);
}

тут тяжеловато понять код, тк кажется нейронка полностью самописная ? (или какая-то либа юзается)

когда я писал, у меня было много провалившихся экспериментов, тк ошибка в слоях была или в распределнии связей между слоями. Чистый unet должен отработать

Но если здесь и каждый слой с 0 написан, то надо глубже копать, может просто в имплементации ошибка back и forward propagation.

Да, нейронка самодельная с нуля. Сделана она послойно, но без написания autograd. Я её обучал на GAN - работает. Но нейронки тут, собственно, нет.

Тут вот что происходит

//----------------------------------------------------------------------------------------------------
//!получить  шум
//----------------------------------------------------------------------------------------------------
template<class type_t>
void CModelBasicDiffusion<type_t>::GetNoise(std::vector<type_t> &noise)
{
 //генератор случайных чисел
 std::random_device rd;
 std::mt19937 gen(rd());
 std::normal_distribution<type_t> dist(0.0f,1.0f);

  //генерируем шум
 for(uint32_t i=0;i<noise.size();i++) noise[i]=dist(gen);
}

Здесь в вектор noise задаётся шум нормальным распределением со средним 0 и дисперсией 1.

  //параметры диффузии
  struct SDiffusion
  {
   std::vector<type_t> Beta;///<параметры шума для каждого шага
   std::vector<type_t> Alpha;///<1-beta
   std::vector<type_t> AlphaBar;///<произведение alpha от 0 до t

   void Init(uint32_t time_counter)///<инициализация
   {
    Beta.resize(time_counter);
    Alpha.resize(time_counter);
    AlphaBar.resize(time_counter);
    //заполняем коэффициенты зашумления
    float beta_start=0.01f;
    float beta_end=0.3f;
    for(uint32_t i=0;i<time_counter;i++)
    {
     Beta[i]=beta_start+(beta_end-beta_start)*i/(time_counter-1);
     Alpha[i]=1-Beta[i];
     if (i==0) AlphaBar[i]=Alpha[i];
          else AlphaBar[i]=AlphaBar[i-1]*Alpha[i];
    }
   }
  };

Здесь создана структура для хранения параметров диффузии: коэффициентов Alpha, Beta, AlphaBar. Функция Init инициализирует эту структуру на число шагов time_counter.

SDiffusion sDiffusion;///<параметры диффузии
std::vector<type_t> NoisyImage;///<зашумлённое изображение
std::vector<type_t> Noise;///<накладываемый шум

Это общие переменные для хранения параметров диффузии и два вектора для хранения зашумлённого изображения и шума. Они нужны чтобы каждый раз их не создавать в функции обучения.

//----------------------------------------------------------------------------------------------------
//!получить зашумлённое изображение и шум
//----------------------------------------------------------------------------------------------------
template<class type_t>
void CModelBasicDiffusion<type_t>::GetNoisyImageAndNoise(uint32_t time_step,const std::vector<type_t> &input_image,std::vector<type_t> &noisy_image,std::vector<type_t> &noise)
{
 noise.resize(input_image.size());
 noisy_image.resize(input_image.size());

 //генерируем шум
 GetNoise(noise);
 //вычисляем коэффициенты для текущего шага
 type_t sqrt_alpha_bar=std::sqrt(sDiffusion.AlphaBar[time_step]);
 type_t sqrt_one_minus_alpha_bar=std::sqrt(1.0-sDiffusion.AlphaBar[time_step]);
 //применяем шум к изображению
 for(uint32_t i=0;i<input_image.size();i++)
 {
  noisy_image[i]=sqrt_alpha_bar*input_image[i]+sqrt_one_minus_alpha_bar*noise[i];
 }
}

Эта функция создаёт зашумлённое изображение и шум, которым оно зашумлялось. Изображения хранятся в формате яркости [-1..1]. -1 - чёрный, 1-белый. Так-то там и цвет можно так же хранить, но для простоты пока чёрно-белое задал. На нём видно лучше.

А дальше процесс расшумления:

Noise.resize(RealImage[0].size());

Вектор шума делаем в размер вектора первого изображения (RealImage - это набор изображений - так-то они все одинакового размера, поэтому берём любое - в данном случае, первое).

Дальше пошли тензоры. Тензоры имеют размерность w,z,y,x. w-номер изображения в минипакете, x,y,z -уже собственная размерность слоя.

//задаём сети начальный шум
 for(uint32_t b=0;b<BATCH_SIZE;b++)
 {
  GetNoise(Noise);
  //задаём шум
  type_t* ptr=&Noise[0];
  uint32_t size=Noise.size();
  DiffusionNet[0]->GetOutputTensor().CopyItemLayerWToDevice(b,ptr,size);
 }

Здесь для каждого элемента минипакета сети задаётся сгенерированный входной шум (разный для всех).

DiffusionNet - это вектор слоёв сети. 0 слой входной. Его выход и есть вход следующего слоя сети, то есть, вход сети. DiffusionNet[0]->GetOutputTensor() - это получение выходного тензора сети. CopyItemLayerWToDevice - копирование в этот тензор в координату w данных x*y*z. В данном случае, копируется шум. То есть, весь этот блок задаёт сети для каждого элемента минипакета вектора шума.

for(int32_t t=TIME_COUNTER-1;t>=0;t--)
 {
  //задаём сети момент времени
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->SetTimeStep(b,t);
  }
  //получаем от сети предыдущий шум
  for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->Forward();
  //расшумляем изображение
  float alpha=sDiffusion.Alpha[t];
  float beta=sDiffusion.Beta[t];
  float sqrt_alpha=std::sqrt(alpha);
  float sqrt_alpha_bar=std::sqrt(sDiffusion.AlphaBar[t]);
  float sqrt_one_minus_alpha_bar=std::sqrt(1.0-sDiffusion.AlphaBar[t]);
  type_t k=(1.0/sqrt_alpha);
  CTensor<type_t> &input_noise=DiffusionNet[0]->GetOutputTensor();//входной шум
  CTensor<type_t> &output_noise=DiffusionNet[DiffusionNet.size()-1]->GetOutputTensor();//предсказанный сетью шум
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   uint32_t index=0;
   GetNoise(Noise);//новый добавляемый шум
   for(uint32_t z=0;z<input_noise.GetSizeZ();z++)
   {
    for(uint32_t y=0;y<input_noise.GetSizeY();y++)
    {
     for(uint32_t x=0;x<input_noise.GetSizeX();x++,index++)
     {
      type_t input=input_noise.GetElement(b,z,y,x);
      type_t output=output_noise.GetElement(b,z,y,x);
      type_t net_noise=output*(1.0-alpha)/sqrt_one_minus_alpha_bar;
      type_t prev_noise=k*(input-net_noise);
      if (t>0) prev_noise+=beta*Noise[index];
      //задаём новый шум
      input_noise.SetElement(b,z,y,x,prev_noise);
     }
    }
   }
  }
 }

Дальше я прохожу от времени TIME_COUNTER (максимальное время) в обратную сторону.

  //задаём сети момент времени
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->SetTimeStep(b,t);
  }

Для учёта времени в сети созданы не слои переноса, а слои времени. Они прибавляют к нейронам ДО активации синусное позиционное кодирование в зависимости от заданного времени. В данном случае здесь задаётся текущее время всем слоям сети. Интерфейс у всех слоёв единый, но это время использует только слой времени.

//получаем от сети предыдущий шум
  for(uint32_t layer=0;layer<DiffusionNet.size();layer++) DiffusionNet[layer]->Forward();

Так как вход сети с шумом мы уже задали, делаем прямой проход по сети.

//расшумляем изображение
  float alpha=sDiffusion.Alpha[t];
  float beta=sDiffusion.Beta[t];
  float sqrt_alpha=std::sqrt(alpha);
  float sqrt_alpha_bar=std::sqrt(sDiffusion.AlphaBar[t]);
  float sqrt_one_minus_alpha_bar=std::sqrt(1.0-sDiffusion.AlphaBar[t]);
  type_t k=(1.0/sqrt_alpha);
  CTensor<type_t> &input_noise=DiffusionNet[0]->GetOutputTensor();//входной шум
  CTensor<type_t> &output_noise=DiffusionNet[DiffusionNet.size()-1]->GetOutputTensor();//предсказанный сетью шум
  for(uint32_t b=0;b<BATCH_SIZE;b++)
  {
   uint32_t index=0;
   GetNoise(Noise);//новый добавляемый шум
   for(uint32_t z=0;z<input_noise.GetSizeZ();z++)
   {
    for(uint32_t y=0;y<input_noise.GetSizeY();y++)
    {
     for(uint32_t x=0;x<input_noise.GetSizeX();x++,index++)
     {
      type_t input=input_noise.GetElement(b,z,y,x);
      type_t output=output_noise.GetElement(b,z,y,x);
      type_t net_noise=output*(1.0-alpha)/sqrt_one_minus_alpha_bar;
      type_t prev_noise=k*(input-net_noise);
      if (t>0) prev_noise+=beta*Noise[index];
      //задаём новый шум
      input_noise.SetElement(b,z,y,x,prev_noise);
     }
    }
   }
  }

Ответ сети получился в DiffusionNet[DiffusionNet.size()-1]->GetOutputTensor(), а входной вектор шума лежит в DiffusionNet[0]->GetOutputTensor() - на входе сети, куда мы его и положили.

Дальше просто читаем для каждого элемента минипакета значение вектора и расшумляем, получая новый вход сети input_noise=DiffusionNet[0]->GetOutputTensor(); И снова делаем проход для следующего времени.

 //сохраняем изображения (они на входе сети) 
 cTensor_Image=DiffusionNet[0]->GetOutputTensor();
 char str[STRING_BUFFER_SIZE];
 static uint32_t counter=0;
 for(uint32_t n=0;n<BATCH_SIZE;n++)
 {
  sprintf(str,"Test/test%05i-%03i.tga",static_cast<int>(counter),static_cast<int>(n));
  SaveImage(cTensor_Image,str,n,IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_DEPTH);
  if (n==0) SaveImage(cTensor_Image,"Test/test-current.tga",n,IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_DEPTH);
 }

После всех циклов, забираем вход сети (туда мы положили очередное расшумление) и сохраняем как картинку.

Вот и всё.

последняя формула выводится на основе мат ожидания для нормального распределения кажется, то есть зная шум на к шаге, можно 1ю формулу представите как x0 = (xt - sqrt(1 - alphat) * noise) / sqrt(at)

это просто для вычисления того же изображения

тк мы говорим о распределении, а не о конкретной точке, то мы обобщая этот момент и понимая, что вычислить обратное распределение(там интеграл будет по неизвестной) - нереально, получаем 2ю формулу.

Но я это не тестил, но кажется это сработает.

Вообще, эту же формулу предлагает и ИИ. Полагаю, она должна работать. Правильно ли я понимаю, что вы в каждом минипакете берёте случайные изображения, случайное время, генерируете ЗАНОВО (это принципиально!) шум и накладываете на изображения. Получаете набор для входа сети (как зашумлённое изображение) и шум. Делаете проход по сети: прямой и обратный (используя MSE для шума - по сути градиет dW/dL получается просто разность между требуемым шумом и полученным от сети). Обновляете веса сети. Идёте с следующему минипакету. В таком варианте обучения сеть каждый раз видит новые зашумлённые изображения по которым делаем всего лишь один шаг для обучения. Видимо, поэтому, сеть и учится очень долго.

У меня сейчас обучение буксует. Ошибка снижается, но очень, очень медленно. По X - эпохи. Уже 380 эпох прошло, но ошибка всё ещё около 600.

Модель у меня вот какая:

--------------------------------------------------

Уменьшение разрешения:

--------------------------------------------------

0) Вход сети

1) Свёртка 16 ядер 5x5, шаг 2, дополнение 2

2) Слой временного кодирования

3) Активация Leaky ReLu

--------------------------------------------------

4) Свёртка 32 ядра 5x5, шаг 2, дополнение 2

5) Слой пакетной нормализации

6) Слой временного кодирования

7) Активация Leaky ReLu

--------------------------------------------------

8) Свёртка 64 ядра 5x5, шаг 2, дополнение 2

9) Слой пакетной нормализации

10) Слой временного кодирования

11) Активация Leaky ReLu

--------------------------------------------------

Увеличение разрешения:

--------------------------------------------------

12) Удвоение разрешения дублированием.

13) Свёртка 64 ядра 3x3, шаг 1, дополнение 1

14) Слой пакетной нормализации

15) Слой временного кодирования

16) Активация Leaky ReLu

--------------------------------------------------

17) Удвоение разрешения дублированием.

18) Свёртка 32 ядра 3x3, шаг 1, дополнение 1

19) Слой пакетной нормализации

20) Слой временного кодирования

21) Активация Leaky ReLu

--------------------------------------------------

22) Удвоение разрешение дублированием.

23) Свёртка 16 ядер 3x3, шаг 1, дополнение 1

24) Слой пакетной нормализации

25) Слой временного кодирования

26) Активация Leaky ReLu

--------------------------------------------------

27) Свёртка 1 ядро 3x3, шаг 1, дополнение 1

28) Активация Leaky ReLu

--------------------------------------------------

28 слой - выходной.

--------------------------------------------------

P.S. Интересно, зачем убогий редактор стирает пустые строки, которыми я отделяю блоки? Какой идиот такой редактор придумал и ещё и обозвал WYSIWYG! Пришлось -------------------------------------------------- ставить.

Похоже, слишком мало ядер свёртки. Увеличил в 4 раза и сеть обучилась до 150 за 1000 итераций (на одном изображении).

@da-nie, нажимайте shift+enter, так как просто enter это новый параграф а не новая строка.

Взял одно-единственное изображение, убрал слои пакетной нормализации, увеличил скорость обучения до 0.0005 (было 0.0001). Задал время как 32 картинки (было 20). Итого, у сети на эпоху 32 картинки. Задал батч как 8 картинок. Стало быть, в эпохе 4 батча. И сеть начала обучаться. Дошла до ошибки 300. И вот тут начало появляться изображение. Когда ошибка стала 200, картинка стала похожа на цифру 5 (шум не добавлялся при расшумлении - в нём нет сейчас смысла, так как учится одному-единственному изображению). На картинке сгенерированные по количеству элементов минипакета картинки (8 штук в минипакете+ текущее (одинаковое с 0):

То есть, формулы рабочие. Только вот заняло это 20 000 эпох! 20 тысяч! (они прошли минут за 10). Впрочем, это ожидаемо - учится-то не изображению, а шуму с изображением. А этого шума полным-полно, так что ожидать что число проходов по сети будет много меньше, чем когда картинок много не приходится. А вот дальше ошибка падать пока не планирует. Застряла на 200.

Вот что вышло с датасетом MNIST на 16000 изображений после 2 эпох (обучалось 2 часа).

Ошибка сейчас меньше 100.

Как я заметил, с большим размером минипакета обучение идёт хуже. Видимо, метания туда-сюда (стохастические) играют большую роль при обучении шумом.

Sign up to leave a comment.

Articles