更新於 2024/06/17閱讀時間約 11 分鐘

The Nature of Code閱讀心得與Python實作:0.4 A Normal Distribution...

這一節的標題是

0.4 A Normal Distribution of Random Numbers

因為方格子標題字數限制,所以沒完整顯現

在念統計學時,常態分布(normal distribution),也叫高斯分布(Gaussian distribution),是一定會念到的內容。

常態分布是在自然界中常見的分布方式。例如,以人類的身高而言,大部分人的身高,會集中在某一範圍內,只有少數人,身高會特別高或特別矮。所以,在模擬自然界中的事物時,如果需要使用到亂數,就不能單純的使用會產生uniform distribution亂數的亂數產生器。例如,要模擬一群猴子時,假如我們用random.randint(200, 300)來設定每隻猴子的身高有多少個像素,那將會發現,不同身高猴子的數量,都會差不多。這樣子的設定,很不真實,畢竟在大自然中,應該不會出現像這樣的一群猴子,不然就太詭異了。

常態分布,值會集中在平均值附近,而形成像下圖一樣的鐘形曲線(繪製程式見本節文末):

圖中的曲線,是由機率密度函數(probability density function, PDF)所產生。透過PDF,可以算出隨機變數出現在某個範圍內的機率。計算PDF的值,要先知道該分布的均值(mean)及標準差(standard deviation)。均值,常記為μ,指的就是平均值;而標準差,常記為σ,則跟數值集中在均值附近的程度有關。在左圖中,曲線比較高、尖,數值比較集中在均值附近,所以標準差比較小;而在右圖中,曲線比較矮、平,數值散佈的範圍比較廣,所以標準差比較大。

常態分布有個特性:不管長得高、矮、胖、瘦,所有的數值,有68%會落在距離均值1個標準差的範圍內;有95%會落在在距離均值2個標準差的範圍內;有99.7%會落在在距離均值3個標準差的範圍內。所以,以前面那群猴子的身高設定為例,如果我們讓整群猴子的身高,呈現均值為250個像素,而標準差為5個像素的常態分布,那就只會有0.3%的猴子,身高少於235個像素,或者高於265個像素。這是因為235和265都距離均值3個標準差,身高在這個範圍內的猴子,佔了99.7%。所以,身高在這個範圍外的猴子,就只佔0.3%

在Python中,可用

random.gauss(mu, sigma)

來產生呈常態分布的亂數。下面的例子,便是在寬度為640像素的畫面上,不斷地在水平方向上畫出圓盤,而這些圓盤的位置,會呈現均值320像素、標準差60像素的常態分布。這些圓盤有些許透明,而且畫的時候是不斷地疊上去的。

Example 0.4: A Gaussian Distribution

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 0.4: A Gaussian Distribution")

WHITE = (255, 255, 255)
GRAY = (25, 25, 25, 25) # RGBA

screen_size = WIDTH, HEIGHT = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

disk_radius = 8
disk_center = (8, 8)

surface_size = (2*disk_radius, 2*disk_radius)
surface = pygame.Surface(surface_size, pygame.SRCALPHA)
pygame.draw.circle(surface, GRAY, disk_center, disk_radius)

y = HEIGHT//2

screen.fill(WHITE)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

x = random.gauss(320, 60)

screen.blit(surface, (x, y))

pygame.display.update()
frame_rate.tick(FPS)

因為pygame.draw()所畫出的圖案,並沒有透明度的效果,所以即使在顏色中有設定alpha值,所畫出的圖案,仍然是不透明的。為了讓圖案有透明度,需先造出一個具有per pixel alpha性質的surface,然後把圖畫在這個surface上,最後再把這個surface疊到要顯示的畫面上。這樣子的做法,類似於許多繪圖軟體所提供的圖層功能。

要造出具有per pixel alpha性質的surface,需使用pygame.SRCALPHA參數。

Exercise 0.4

圓點位置之x、y座標,分別由2個常態分布亂數產生。顏色之RGB值,分別由3個常態分布亂數產生。但應注意的是,RGB值的範圍,是在0~255。所以,應該檢查、調整,使亂數值落於這個範圍內。完整的程式碼如下:

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Exercise 0.4")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 360
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

dot_size = 5

mu_x, mu_y = WIDTH/2, HEIGHT/2
sigma_x, sigma_y = WIDTH/8, HEIGHT/8

mu_rgb, sigma_rgb = 255//2, 255//6

rgb = [0, 0, 0]

screen.fill(WHITE)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

for i in range(3):
rgb[i] = random.gauss(mu_rgb, sigma_rgb)
if rgb[i] > 255:
rgb[i] = 255
elif rgb[i] < 0:
rgb[i] = 0

x = random.gauss(mu_x, sigma_x)
y = random.gauss(mu_y, sigma_y)

pygame.draw.circle(screen, rgb, (x, y), dot_size)

pygame.display.update()
frame_rate.tick(FPS)

程式執行結果的截圖如下圖


Exercise 0.5

Walker這個class的__init__()step()修改如下:

def __init__(self, x=0, y=0, mu=0, sigma=1):
# pygame的畫面
self.screen = pygame.display.get_surface()

# walker的位置,預設值是(0, 0)
self.x = x
self.y = y

self.mu = mu
self.sigma = sigma

def step(self):
choice = random.randint(0, 3)
step_size = abs(int(random.gauss(self.mu, self.sigma)))
if choice == 0:
self.y -= step_size
elif choice == 1:
self.y += step_size
elif choice == 2:
self.x -= step_size
elif choice == 3:
self.x += step_size

產生Walker的instance時,可設定步伐大小的均值與標準差。例如:

walker = Walker(WIDTH//2, HEIGHT//2, 0, 3)

程式執行結果的截圖如下圖


常態分布鐘形曲線繪製程式
# python version 3.10.9
import math

import matplotlib.pyplot as plt # version 3.7.1


def pdf(x, mu=0.0, sigma=1.0):
return math.exp(-(x-mu)**2/(2*sigma**2))/(sigma*math.sqrt(2*math.pi))


plt.figure(figsize=(10, 6))

itvl = range(-70, 71)

x1 = [0.1*i for i in itvl]
y1 = [pdf(0.1*i, 0.0, 1) for i in itvl]

x2 = [0.1*i for i in itvl]
y2 = [pdf(0.1*i, 0.0, 2) for i in itvl]

plt.subplot(1, 2, 1)
plt.xticks([])
plt.yticks([])
plt.ylim([0.0, 0.45])
plt.plot(x1, y1)

plt.subplot(1, 2, 2)
plt.xticks([])
plt.yticks([])
plt.ylim([0.0, 0.45])
plt.plot(x2, y2)

plt.show()







分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.