วิธีใช้การสลายตัว Cholesky หรือทางเลือกสำหรับการจำลองข้อมูลที่สัมพันธ์กัน


19

ฉันใช้การสลายตัวของ Cholesky เพื่อจำลองตัวแปรสุ่มที่มีความสัมพันธ์ซึ่งได้รับเมทริกซ์สหสัมพันธ์ สิ่งนี้คือผลลัพธ์ไม่เคยทำซ้ำโครงสร้างความสัมพันธ์ตามที่ได้รับ นี่คือตัวอย่างเล็ก ๆ ใน Python เพื่อแสดงสถานการณ์

import numpy as np    

n_obs = 10000
means = [1, 2, 3]
sds = [1, 2, 3] # standard deviations 

# generating random independent variables 
observations = np.vstack([np.random.normal(loc=mean, scale=sd, size=n_obs)
                   for mean, sd in zip(means, sds)])  # observations, a row per variable

cor_matrix = np.array([[1.0, 0.6, 0.9],
                       [0.6, 1.0, 0.5],
                       [0.9, 0.5, 1.0]])

L = np.linalg.cholesky(cor_matrix)

print(np.corrcoef(L.dot(observations))) 

ภาพพิมพ์นี้:

[[ 1.          0.34450587  0.57515737]
 [ 0.34450587  1.          0.1488504 ]
 [ 0.57515737  0.1488504   1.        ]]

อย่างที่คุณเห็นเมทริกซ์สหสัมพันธ์ประมาณโพสต์เฉพาะกิจนั้นแตกต่างอย่างมากจากก่อนหน้านี้ มีข้อผิดพลาดในรหัสของฉันหรือมีทางเลือกอื่นในการใช้การสลายตัว Cholesky?

แก้ไข

ฉันขอโทษสำหรับความยุ่งเหยิงนี้ ฉันไม่คิดว่ามีข้อผิดพลาดในรหัสและ / หรือวิธีการสลายตัวของ Cholesky ถูกนำไปใช้เนื่องจากความเข้าใจผิดของเนื้อหาที่ฉันได้ศึกษามาก่อน ในความเป็นจริงฉันแน่ใจว่าวิธีการนั้นไม่ได้มีความแม่นยำและฉันก็ไม่เป็นไรจนกระทั่งสถานการณ์ที่ทำให้ฉันโพสต์คำถามนี้ ขอบคุณสำหรับการชี้ไปที่ความเข้าใจผิดที่ฉันมี ฉันได้แก้ไขชื่อเพื่อให้สะท้อนสถานการณ์จริงตามที่เสนอโดย @Silverfish


1
Cholesky ทำงานได้ดีและนี่เป็นคำถามประเภท "คุณสามารถค้นหาข้อบกพร่องในรหัสของฉัน" ได้หรือไม่ ชื่อเรื่องและเนื้อหาของคำถามตามที่เขียนขึ้นครั้งแรกนั้นโดยทั่วไปแล้ว "Cholesky ไม่ทำงานสิ่งที่เป็นทางเลือก"? นั่นจะสร้างความสับสนให้กับผู้ใช้ที่ค้นหาเว็บไซต์นี้ ควรแก้ไขคำถามนี้เพื่อสะท้อนสิ่งนี้หรือไม่ (ข้อเสียคือคำตอบของ javlacalle จะมีความเกี่ยวข้องน้อยกว่าข้อเสียคือข้อความคำถามจะสะท้อนสิ่งที่ผู้ค้นหาจะพบในหน้านี้)
Silverfish

@Antoni Parellada ใช่ฉันคิดว่าคุณได้แปลรหัส MATLAB ของฉันสำหรับ (a) วิธีที่ถูกต้องในการทำมันเป็น Python numpy พร้อมการปรับสำหรับ np.linalg.cholesky เป็นรูปสามเหลี่ยมด้านล่างกับ chol ของ MATLAB ที่เป็นรูปสามเหลี่ยมด้านบน ฉันแปลรหัสที่ไม่ถูกต้องของ OP ไปเป็นเทียบเท่ากับ MATLAB แล้วและทำซ้ำผลลัพธ์ที่ไม่ถูกต้องของเขา
Mark L. Stone

คำตอบ:


11

วิธีการตามการสลายตัวของ Cholesky ควรใช้งานได้อธิบายไว้ที่นี่ และแสดงในคำตอบโดย Mark L. Stone ที่โพสต์ไว้เกือบจะในเวลาเดียวกันกับคำตอบนี้

อย่างไรก็ตามบางครั้งฉันได้สร้างการจับฉลากจากการแจกแจงปกติหลายตัวแปร ดังนี้:ยังไม่มีข้อความ(μ,Σ)

Y=QX+μ,กับQ=Λ1/2Φ,

โดยที่เป็นจุดสุดท้ายดึงXถูกดึงมาจากมาตรฐาน univariate การแจกแจงปกติΦคือเมทริกซ์ที่มี eigenvectors ที่ทำให้เป็นมาตรฐานของเมทริกซ์เป้าหมายΣและΛเป็นเมทริกซ์แนวทแยงที่บรรจุค่าลักษณะเฉพาะของΣในลำดับเดียวกัน คอลัมน์ของΦYXΦΣΛΣΦ

ตัวอย่างในR(ขออภัยฉันไม่ได้ใช้ซอฟต์แวร์เดียวกับที่คุณใช้ในคำถาม):

n <- 10000
corM <- rbind(c(1.0, 0.6, 0.9), c(0.6, 1.0, 0.5), c(0.9, 0.5, 1.0))
set.seed(123)
SigmaEV <- eigen(corM)
eps <- rnorm(n * ncol(SigmaEV$vectors))
Meps <- matrix(eps, ncol = n, byrow = TRUE)    
Meps <- SigmaEV$vectors %*% diag(sqrt(SigmaEV$values)) %*% Meps
Meps <- t(Meps)
# target correlation matrix
corM
#      [,1] [,2] [,3]
# [1,]  1.0  0.6  0.9
# [2,]  0.6  1.0  0.5
# [3,]  0.9  0.5  1.0
# correlation matrix for simulated data
cor(Meps)
#           [,1]      [,2]      [,3]
# [1,] 1.0000000 0.6002078 0.8994329
# [2,] 0.6002078 1.0000000 0.5006346
# [3,] 0.8994329 0.5006346 1.0000000

คุณอาจสนใจ โพสต์นี้ และโพสต์นี้


ในการทำให้ความสัมพันธ์ของเมทริกซ์สหสัมพันธ์ที่ทำซ้ำถูกต้องควรลบความสัมพันธ์ปลอมในการสุ่มข้อมูลจากเครื่องกำเนิดไฟฟ้าแบบสุ่มก่อนที่จะนำไปใช้กับขั้นตอนการสร้างข้อมูล ตัวอย่างเช่นตรวจสอบความสัมพันธ์ของข้อมูลสุ่มของคุณใน eps เพื่อดูความสัมพันธ์ปลอมที่แรก
หมวกกันน็อกกอทฟริด

17

คนมักจะพบข้อผิดพลาดของคุณเร็วขึ้นมากถ้าคุณอธิบายสิ่งที่คุณทำกับคำและพีชคณิตมากกว่ารหัส (หรืออย่างน้อยเขียนมันโดยใช้ pseudocode)

ดูเหมือนว่าคุณจะทำสิ่งนี้ได้เทียบเท่า

  1. n×kZ

  2. σผมμผม

  3. Y=LX

L

สิ่งที่คุณควรทำคือ:

  1. n×k เมทริกซ์ของบรรทัดฐานมาตรฐาน Z

  2. คำนวณ X=LZ เพื่อรับบรรทัดฐานที่สัมพันธ์กัน

  3. คูณคอลัมน์ด้วย σผม และเพิ่ม μผม เพื่อรับบรรทัดฐานที่ไม่เป็นมาตรฐาน

มีคำอธิบายมากมายของอัลกอริทึมนี้บนเว็บไซต์ เช่น

จะสร้างตัวเลขสุ่มที่มีความสัมพันธ์กันอย่างไร (ให้หมายถึงผลต่างและระดับความสัมพันธ์)

ฉันสามารถใช้วิธี Cholesky เพื่อสร้างตัวแปรสุ่มที่มีความสัมพันธ์กับค่าเฉลี่ยที่กำหนดได้หรือไม่

อันนี้พูดถึงมันโดยตรงในแง่ของเมทริกซ์ความแปรปรวนร่วมที่ต้องการและยังให้อัลกอริทึมสำหรับการแปรปรวนตัวอย่างที่ต้องการ:

การสร้างข้อมูลด้วยเมทริกซ์ความแปรปรวนร่วมตัวอย่างที่กำหนด


11

ไม่มีอะไรผิดปกติกับ Cholesky ตัวประกอบ มีข้อผิดพลาดในรหัสของคุณ ดูการแก้ไขด้านล่าง

นี่คือรหัส MATLAB และผลลัพธ์เป็นอันดับแรกสำหรับ n_obs = 10,000 ตามที่คุณมีแล้วสำหรับ n_obs = 1e8 เพื่อความเรียบง่ายเนื่องจากมันไม่ส่งผลต่อผลลัพธ์ดังนั้นฉันจึงไม่ต้องกังวลกับวิธีการเช่นฉันทำให้มันเป็นศูนย์ โปรดทราบว่า chol ของ MATLAB สร้างปัจจัยรูปสามเหลี่ยม Cholesky บน R ของเมทริกซ์ M เช่น R '* R = M. numpy.linalg.cholesky สร้างปัจจัย Cholesky รูปสามเหลี่ยมที่ต่ำกว่าดังนั้นการปรับเทียบกับรหัสของฉันจึงจำเป็น แต่ฉันเชื่อว่ารหัสของคุณใช้ได้ดีในเรื่องนั้น

   >> correlation_matrix = [1.0, 0.6, 0.9; 0.6, 1.0, 0.5;0.9, 0.5, 1.0];
   >> SD = diag([1 2 3]);
   >> covariance_matrix = SD*correlation_matrix*SD
   covariance_matrix =
      1.000000000000000   1.200000000000000   2.700000000000000
      1.200000000000000   4.000000000000000   3.000000000000000
      2.700000000000000   3.000000000000000   9.000000000000000
   >> n_obs = 10000;
   >> Random_sample = randn(n_obs,3)*chol(covariance_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.599105015695768   0.898395949647890
      0.599105015695768   1.000000000000000   0.495147514173305
      0.898395949647890   0.495147514173305   1.000000000000000
   >> n_obs = 1e8;
   >> Random_sample = randn(n_obs,3)*chol(covariance_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.600101477583914   0.899986072541418
      0.600101477583914   1.000000000000000   0.500112824962378
      0.899986072541418   0.500112824962378   1.000000000000000

แก้ไข: ฉันพบข้อผิดพลาดของคุณ คุณใช้ค่าเบี่ยงเบนมาตรฐานไม่ถูกต้อง นี่เท่ากับสิ่งที่คุณทำซึ่งผิด

   >> n_obs = 10000;
   >> Random_sample = randn(n_obs,3)*SD*chol(correlation_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.336292731308138   0.562331469857830
      0.336292731308138   1.000000000000000   0.131270077244625
      0.562331469857830   0.131270077244625   1.000000000000000
   >> n_obs=1e8;
   >> Random_sample = randn(n_obs,3)*SD*chol(correlation_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.351254525742470   0.568291702131030
      0.351254525742470   1.000000000000000   0.140443281045496
      0.568291702131030   0.140443281045496   1.000000000000000

6

CV ไม่เกี่ยวกับรหัส แต่ฉันรู้สึกทึ่งที่เห็นว่าสิ่งนี้จะดูแลคำตอบที่ดีทั้งหมดได้อย่างไรและโดยเฉพาะการบริจาค @Mark L. Stone คำตอบที่แท้จริงสำหรับคำถามนั้นมีอยู่ในโพสต์ของเขา (โปรดเครดิตโพสต์ของเขาในกรณีที่มีข้อสงสัย) ฉันจะย้ายข้อมูลต่อท้ายนี้ที่นี่เพื่อความสะดวกในการดึงโพสต์นี้ในอนาคต โดยไม่ต้องเล่นคำตอบที่ยอดเยี่ยมใด ๆ หลังจากคำตอบของ Mark สิ่งนี้จะสรุปปัญหาโดยแก้ไขโพสต์ใน OP

แหล่ง

ใน PYTHON:

import numpy as np

no_obs = 1000             # Number of observations per column
means = [1, 2, 3]         # Mean values of each column
no_cols = 3               # Number of columns

sds = [1, 2, 3]           # SD of each column
sd = np.diag(sds)         # SD in a diagonal matrix for later operations

observations = np.random.normal(0, 1, (no_cols, no_obs)) # Rd draws N(0,1) in [3 x 1,000]

cor_matrix = np.array([[1.0, 0.6, 0.9],
                       [0.6, 1.0, 0.5],
                       [0.9, 0.5, 1.0]])          # The correlation matrix [3 x 3]

cov_matrix = np.dot(sd, np.dot(cor_matrix, sd))   # The covariance matrix

Chol = np.linalg.cholesky(cov_matrix)             # Cholesky decomposition

array([[ 1.        ,  0.        ,  0.        ],
       [ 1.2       ,  1.6       ,  0.        ],
       [ 2.7       , -0.15      ,  1.29903811]])

sam_eq_mean = Chol .dot(observations)             # Generating random MVN (0, cov_matrix)

s = sam_eq_mean.transpose() + means               # Adding the means column wise
samples = s.transpose()                           # Transposing back

print(np.corrcoef(samples))                       # Checking correlation consistency.

[[ 1.          0.59167434  0.90182308]
 [ 0.59167434  1.          0.49279316]
 [ 0.90182308  0.49279316  1.        ]]

ใน [R]:

no_obs = 1000             # Number of observations per column
means = 1:3               # Mean values of each column
no_cols = 3               # Number of columns

sds = 1:3                 # SD of each column
sd = diag(sds)         # SD in a diagonal matrix for later operations

observations = matrix(rnorm(no_cols * no_obs), nrow = no_cols) # Rd draws N(0,1)

cor_matrix = matrix(c(1.0, 0.6, 0.9,
                      0.6, 1.0, 0.5,
                      0.9, 0.5, 1.0), byrow = T, nrow = 3)     # cor matrix [3 x 3]

cov_matrix = sd %*% cor_matrix %*% sd                          # The covariance matrix

Chol = chol(cov_matrix)                                        # Cholesky decomposition

     [,1] [,2]      [,3]
[1,]    1  1.2  2.700000
[2,]    0  1.6 -0.150000
[3,]    0  0.0  1.299038

sam_eq_mean = t(observations) %*% Chol          # Generating random MVN (0, cov_matrix)

samples = t(sam_eq_mean) + means

cor(t(samples))

          [,1]      [,2]      [,3]
[1,] 1.0000000 0.6071067 0.8857339
[2,] 0.6071067 1.0000000 0.4655579
[3,] 0.8857339 0.4655579 1.0000000

colMeans(t(samples))
[1] 1.035056 2.099352 3.065797
apply(t(samples), 2, sd)
[1] 0.9543873 1.9788250 2.8903964

1

ตามที่คนอื่นได้แสดงแล้ว: cholesky ทำงาน นี่คือโค้ดที่สั้นและใกล้กับ pseudocode มาก: codepiece ใน MatMate:

Co = {{1.0, 0.6, 0.9},  _
      {0.6, 1.0, 0.5},  _
      {0.9, 0.5, 1.0}}           // make correlation matrix


chol = cholesky(co)              // do cholesky-decomposition           
data = chol * unkorrzl(randomn(3,100,0,1))  
                                 // dot-multiply cholesky with random-
                                 // vectors with mean=0, sdev=1  
                                 //(refined by a "decorrelation" 
                                 //to remove spurious/random correlations)   


chk = data *' /100               // check the correlation of the data
list chk

1.0000  0.6000  0.9000
0.6000  1.0000  0.5000
0.9000  0.5000  1.0000
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.