метод контрольных переменных

advertisement
Государственное образовательное учреждение высшего профессионального образования
«Нижегородский государственный университет им. Н.И. Лобачевского»
Факультет вычислительной математики и кибернетики
Лабораторная работа по курсам «Параллельные численные методы» и
«Технологии параллельного программирования»
Тема:
«Метод Монте-Карло и техника понижения дисперсии для него – метод
контрольных переменных»
Выполнили:
Студенты группы 85М21
Гнатюк Д.В.
Збруев Д.А.
Нижний Новгород
2011
Оглавление
1.
Постановка задачи................................................................................................................... 3
2.
Описание алгоритмов ............................................................................................................. 4
2.1.
Метод Монте-Карло ........................................................................................................ 4
2.1.1. Обычный алгоритм интегрирования Монте-Карло................................................ 4
2.1.2. Геометрический алгоритм интегрирования Монте-Карло .................................... 4
2.2.
3.
4.
Control variates ................................................................................................................. 5
Программные реализации ..................................................................................................... 6
3.1.
Реализация на CPU .......................................................................................................... 6
3.2.
Реализация на CUDA........................................................................................................ 8
3.3.
Реализация на TBB .........................................................................................................10
Вычислительные эксперименты ..........................................................................................12
4.1.
CUDA ...............................................................................................................................12
4.2.
TBB...................................................................................................................................13
2
1. Постановка задачи
Необходимо реализовать вычисление определенного интеграла для многомерной
функции методом Монте-Карло, а также технику понижения дисперсии для него – метод
контрольных переменных (control variate).
Функция должна быть задана в виде обычной функции языка C. Предлагается провести
эксперименты с одномерными и пятимерными функциями. Можно выбрать любую функцию,
включая ту, для которой решение известно. Аналитическое решение можно использовать для
анализа сходимости и погрешности. Кроме того, необходимо провести эксперименты по оценке
эффективности реализации.
3
2. Описание алгоритмов
2.1.
Метод Монте-Карло
2.1.1. Обычный алгоритм интегрирования Монте-Карло
Предположим, требуется вычислить определённый интеграл
𝑏
∫ 𝑓(𝑥)𝑑𝑥.
𝑎
Рассмотрим случайную величину u, равномерно распределённую на отрезке интегрирования
[a,b]. Тогда f(u) так же будет случайной величиной, причём её математическое ожидание
выражается как
𝑏
𝑓(𝑢) = ∫ 𝑓(𝑥)ϕ(x)𝑑𝑥,
𝑎
где ϕ(x) - плотность распределения случайной величины u, равная
1
𝑏−𝑎
на участке [a,b].
Таким образом, искомый интеграл выражается как
𝑏
∫ 𝑓(𝑥)𝑑𝑥 = (𝑏 − 𝑎)Ε𝑓(𝑢).
𝑎
Но математическое ожидание случайной величины f(u) можно легко оценить, смоделировав эту
случайную величину и посчитав выборочное среднее.
Итак, бросаем N точек, равномерно распределённых на [a,b], для каждой точки ui
вычисляем f(ui). Затем вычисляем выборочное среднее:
1
𝑁
𝑁
∑ 𝑓(𝑢𝑖 ).
𝑖=1
В итоге получаем оценку интеграла:
𝑏
∫𝑎 𝑓(𝑥)𝑑𝑥 ≈
𝑏−𝑎
𝑁
∑𝑁𝑖=1 𝑓(𝑢𝑖 ).
Точность оценки зависит только от количества точек N.
2.1.2. Геометрический алгоритм интегрирования Монте-Карло
Для определения площади под графиком функции (вычисления определенного интеграла
функции) можно использовать следующий стохастический алгоритм:
1) ограничим функцию прямоугольником (n-мерным параллелепипедом в случае многих
измерений), площадь которого Spar можно легко вычислить;
2) «набросаем» в этот прямоугольник (параллелепипед) некоторое количество точек (N
штук), координаты которых будем выбирать случайным образом;
3) определим число точек (K штук), которые попадут под график функции;
4) площадь области, ограниченной функцией и осями координат, S даётся выражением 𝑆 =
𝐾
𝑆𝑝𝑎𝑟 𝑁.
4
2.2.
Control variates
Предположим X это случайная величина и мы хотим оценить
Мы можем оценить A сгенерировав L независимых значений X и посчитав
Таким образом ошибка будет составлять
Таким образом, количество значений, необходимое для достижения заданной точности обратно
пропорционально дисперсии.
Control variate - это такая случайная величина W(X), что B=E[W(X)] может быть легко
вычислена. Если W(X) коррелирует с V(X)
тогда случайная величина
может иметь меньшую дисперсию, чем V(X). Теперь мы можем оценить A более точно
Можно выбрать такое α, чтобы минимизировать дисперсию Z
Оптимальным 𝛼 будет
Таким образом, качество метода напрямую зависит от корреляции V и W.
На практике мы не можем сразу знать оптимальное α, но можем его оценить по данным,
полученным в результате работы Монте-Карло. По значениям случайной величины Xk можно
вычислить Vk=V(Xk) и Wk=W(Xk), тогда
5
3. Программные реализации
3.1.
Реализация на CPU
Был реализован классический метод Монте-Карло и метод контрольных переменных на
примере вычисления определенного интеграла двумерной функции. Аргументами методов
являются пределы интегрирования и количество значений случайной величины.
#define PI 3.14159265358979323846264338327950288419716939937510
#define a 124367
#define c 57634
#define m 2147483648
unsigned int x=1234;
float rnd()
{
x=(a*x+c)%m;
return ((float) x)/m;
//return ((float)rand())/RAND_MAX;
}
double f(double x, double y)
{
//функция (3) из лабораторной работы №2 «Вычисление определенного интеграла»
return (exp( sin(PI * x) * cos(PI * y) ) + 1.0) / 256.0;
}
//Monte Carlo without control variates
float MC(int x1, int y1, int x2, int y2, int nsamples)
{
int x=1234;
int good_points = 0;
float denom = (x2-x1)*(y2-y1);
float rnd_x = 0, rnd_y = 0, rnd_z = 0;
float Fxy = 0, Fxy_max = 0;
float result = 0;
float accuracy = 0.1f;
for (float i = x1; i < x2; i += accuracy)
{
for (float j = y1; j < y2; j += accuracy)
{
Fxy = f(i,j);
if (Fxy > Fxy_max)
Fxy_max = Fxy;
}
}
for (int i =
{
rnd_x
rnd_y
rnd_z
0; i < nsamples; i++)
= rnd() * (x2-x1)+x1;
= rnd() * (y2-y1)+y1;
= rnd() * Fxy_max;
Fxy = f(rnd_x, rnd_y);
if (rnd_z < Fxy)
good_points++;
}
result = denom * Fxy_max * good_points / nsamples;
return result;
}
//Monte Carlo with control variates
float MCCV(int x1, int y1, int x2, int y2, int nsamples)
6
{
float *valV = new float[nsamples], *valW = new float[nsamples];
float
float
float
float
float
denom = (x2-x1)*(y2-y1);
rnd_x = 0, rnd_y = 0;
Fxy = 0;
result = 0.0f;
corVW=0.0f, meanV=0.0;
//just W(X1, X2) = (X1 + X2), Mean(W) = (X1max-X1min)/2 + (X2max-X2min)/2
//float meanW = (x2-x1)/2.0f + (y2-y1)/2.0f, sigmaW2=0.0f;
float meanW = 0.5f*(x1+x2+y2+y1), sigmaW2=0.0f;
for (int i =
{
rnd_x
rnd_y
Fxy =
0; i < nsamples; i++)
= rnd() * (x2-x1)+x1;
= rnd() * (y2-y1)+y1;
f(rnd_x, rnd_y);
valV[i] = Fxy;
valW[i] = rnd_x + rnd_y;
sigmaW2 += (valW[i] - meanW)*(valW[i] - meanW);
meanV += valV[i];
}
meanV /= nsamples;
for (int i = 0; i < nsamples; i++)
corVW += (valV[i] - meanV) * (valW[i] - meanW);
float alpha = corVW / sigmaW2;
for (int i = 0; i < nsamples; i++)
{
result += (valV[i] - alpha*(valW[i] - meanW)) * denom;
}
result /= nsamples;
return result;
}
7
3.2.
Реализация на CUDA
Идея распараллеливания заключается в том, чтобы разделить исходную
последовательность значений случайной величины на куски заданного размера и распределить
их между потоками. Каждый поток автономно вычисляет свою оценку и записывает результат в
выходной массив. После синхронизации эти значения обрабатываются и находится
результирующая оценка интеграла. Важным моментом является параллельная генерация
случайных величин потоками, которая являлась основным местом для оптимизации алгоритма и
получения ускорения.
#define PI 3.14159265358979323846264338327950288419716939937510
#define a 124367
#define c 57634
#define m 2147483648
__device__ float rnd2(unsigned int idx)
{
unsigned int x=1234;
int aa=a;
int res = 1;
int t = idx+1;
while (t)
{
if (t & 1)
res *= aa;
t >>= 1;
}
x = (res*x)%m;
return ((float) x)/m;
}
__device__ float f_device(float x, float y)
{
return (exp( sin(PI * x) * cos(PI * y) ) + 1.0) / 256.0;
}
__global__ void MCCVKernel(int x1, int y1, int x2, int y2, int chunkSize, float *S)
{
long ind = blockIdx.x * blockDim.x + threadIdx.x;
__shared__ float valV[CHUNKSIZE];
__shared__ float valW[CHUNKSIZE];
float
float
float
float
float
denom = (x2-x1)*(y2-y1);
rnd_x = 0, rnd_y = 0;
Fxy = 0;
result = 0.0f;
corVW=0.0f, meanV=0.0;
//just W(X1, X2) = (X1 + X2), Mean(W) = (X1max-X1min)/2 + (X2max-X2min)/2
//float meanW = (x2-x1)/2.0f + (y2-y1)/2.0f, sigmaW2=0.0f;
float meanW = 0.5f*(x1+x2+y2+y1), sigmaW2=0.0f;
unsigned int x=1234;/////, aN=0, nsamples = blockDim.x * CHUNKSIZE;
unsigned int num = ind*chunkSize*2;
for (int i =
{
rnd_x
rnd_y
Fxy =
0; i < chunkSize; i++)
= rnd2(ind*chunkSize+2*i) * (x2-x1)+x1;
= rnd2(ind*chunkSize+2*i+1) * (y2-y1)+y1;
f_device(rnd_x, rnd_y);
valV[i] = Fxy;
valW[i] = rnd_x + rnd_y;
sigmaW2 += (valW[i] - meanW)*(valW[i] - meanW);
8
meanV += valV[i];
}
meanV /= chunkSize;
for (int i = 0; i < chunkSize; i++)
corVW += (valV[i] - meanV) * (valW[i] - meanW);
float alpha = corVW / sigmaW2;
for (int i = 0; i < chunkSize; i++)
{
result += (valV[i] - alpha*(valW[i] - meanW)) * denom;
}
result /= chunkSize;
S[ind] = result;
}
int MCCVCuda(int x1, int y1, int x2, int y2, int nsamples, int chunkSize, float
*output, LARGE_INTEGER &time)
{
float *S;
int chunksCount = nsamples / chunkSize;
cudaMalloc(&S, chunksCount * sizeof(float));
MCCVKernel<<<8, chunksCount/8>>>(x1, y1, x2, y2, chunkSize, S);
cudaThreadSynchronize();
QueryPerformanceCounter(&time);
cudaMemcpy(output, S, chunksCount*sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(S);
return 0;
}
9
3.3.
Реализация на TBB
Идея распараллеливания с использованием TBB совпадает с предыдущей реализацией на
CUDA и заключается в том, чтобы разделить исходную последовательность значений случайной
величины на куски заданного размера и распределить их между потоками. В имеющемся
инструментарии TBB для этих целей идеально подходит parallel_for.
// Класс-функтор TBB
class integrationClass
{
private:
int x1;
int y1;
int x2;
int y2;
float Fxy_max;
int chunkSize;
float *resultS;
int denom;
public:
// Конструктор
integrationClass (int tx1, int ty1, int tx2, int ty2, int tchunkSize, float *tres)
{
x1=tx1;
y1=ty1;
x2=tx2;
y2=ty2;
chunkSize=tchunkSize;
denom=(x2-x1)*(y2-y1);
resultS=tres;
}
// Оператор () выполняется над диапазоном из пространства итераций
void operator()(const blocked_range<int> &range) const
{
float rnd_x=0, rnd_y=0;
float Fxy=0;
for (int i = range.begin(); i != range.end(); i++)
{
float valW[CHUNK_SIZE], valV[CHUNK_SIZE];
int ind = i;
float result = 0.0f;
float corVW=0.0f, meanV=0.0;
//just W(X1, X2) = (X1 + X2), Mean(W) = (X1max-X1min)/2 + (X2maxX2min)/2
//float meanW = (x2-x1)/2.0f + (y2-y1)/2.0f, sigmaW2=0.0f;
float meanW = 0.5f*(x1+x2+y2+y1), sigmaW2=0.0f;
unsigned int x=1234;/////, aN=0, nsamples = blockDim.x *
CHUNKSIZE;
unsigned int num = ind*chunkSize*2;
for (int i2 = 0; i2 < CHUNK_SIZE; i2++)
{
rnd_x = rnd2(ind*chunkSize+2*i2) * (x2-x1)+x1;
rnd_y = rnd2(ind*chunkSize+2*i2+1) * (y2-y1)+y1;
//rnd_x = rnd(x) * (x2-x1)+x1;
//rnd_y = rnd(x) * (y2-y1)+y1;
Fxy = f(rnd_x, rnd_y);
valV[i2] = Fxy;
valW[i2] = rnd_x + rnd_y;
////meanWExper += valW[i];
10
sigmaW2 += (valW[i2] - meanW)*(valW[i2] - meanW);
meanV += valV[i2];
}
meanV /= chunkSize;
for (int i2 = 0; i2 < chunkSize; i2++)
corVW += (valV[i2] - meanV) * (valW[i2] - meanW);
float alpha = corVW / sigmaW2;
for (int i2 = 0; i2 < chunkSize; i2++)
{
result += (valV[i2] - alpha*(valW[i2] - meanW)) * denom;
}
result /= chunkSize;
resultS[ind] = result;
}
}
};
//вызов parallel_for
int main(int argc, char *argv[])
{
…
task_scheduler_init init;
int chunkSize = CHUNK_SIZE;
int chunkCount = nsamples / chunkSize;
float *resultTBB = new float[chunkCount];
parallel_for(blocked_range<int>(0, chunkCount), integrationClass(x1, y1, x2, y2,
chunkSize, resultTBB));
result = 0;
for (int i = 0; i < chunkCount; i++)
result += resultTBB[i];
result /= chunkCount;
…
return 0;
}
11
4. Вычислительные эксперименты
4.1.
CUDA
Тестовая конфигурация: Intel Core2 Duo 2.27 GHz, 3Gb RAM, NVIDIA GeForce 9200M GS.
0.18
0.16
Время, сек
0.14
0.12
MC simple
0.1
0.08
MCCV serial
0.06
0.04
MCCV CUDA 8 blocks,
chunksCount/8 threads
0.02
0
Количество точек
Рис.1 Время работы алгоритмов
3.5
3
Ускорение
2.5
2
1.5
1
0.5
0
Количество точек
Рис. 2 Оценка эффективности
Реализация на GPU оправдывает затраты на реализацию и показывает достаточно
хорошее ускорение.
Стоит особо отметить, что в специфике данной задаче большую часть времени работы
CUDA-нитей составляет именно генерация случайных чисел, а не непосредственно вычисления.
Мы опробовали различные способы многопоточной генерации случайных чисел – и leapfrog и
разделение выборки с последовательным пролистыванием до нужного начального значения, они дают замедление работы по сравнению с версией для CPU! На наш взгляд, самым
оптимальным вариантом была бы схема, при которой сначала на CPU вычисляются
12
инициализации генератора СЧ для разных потоков, а затем этот массив передается на GPU; эта
версия позволила бы получить еще большее ускорение по сравнению с текущей реализацией.
4.2.
TBB
Тестовая конфигурация: Intel Core i7 2.8 GHz, 3Gb RAM.
0.12
Время, сек
0.1
0.08
0.06
MC simple
0.04
MCCV serial
MCCV TBB
0.02
0
Количество точек
Рис.1 Время работы алгоритмов
3
Ускорение
2.5
2
1.5
1
0.5
0
Количество точек
Рис. 2 Оценка эффективности
TBB версия показывает достаточно хороший показатель эффективность/затраты, т.к.
реализация алгоритма с использованием данной технологии оказалась гораздо проще, чем,
например, на CUDA.
13
Download