In [23]:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import time
sns.set(style='whitegrid', rc={'figure.figsize':(14,8)})

(13.2)

(a) Generate time series (DTMF tone for key 1)

In [92]:
PERIOD = 0.25
N = 2500
x = np.linspace(0, PERIOD, N)

freq1 = 697
wave1 = np.sin(2 * np.pi * freq1 * x)

freq2 = 1209
wave2 = np.sin(2 * np.pi * freq2 * x)

t = wave1 + wave2

plt.plot(t)
plt.show()

(b) Perform Direct Cosine Transform

In [31]:
def DCT(t):
    N = len(t)
    D = np.zeros((N, N))
    D[0, :] = np.sqrt(1 / N)
    for i in range(1, N):
        for j in range(N):
            D[i, j] = np.sqrt(2 / N) * np.cos((np.pi * (2*j + 1) * i) / (2 * N))
            
    f = D.dot(t)
    return f, D
In [32]:
start = time.time()
f, D = DCT(t)
print("Naive DCT took %.1f seconds" % (time.time() - start))
Naive DCT took 11.4 seconds
In [33]:
plt.plot(f)
plt.show()

(c) Inverse DCT

In [35]:
D_inv = D.T
t_hat = D_inv.dot(f)
plt.plot(t_hat)
plt.title("Reconstructed signal after DCT")
plt.show()

diff = np.abs(t - t_hat)
print("Max difference from original signal: ", np.max(diff))
plt.plot(diff)
plt.title("Difference")
plt.show()
Max difference from original signal:  9.023892744153272e-13

(d) Random sample %5 of the signal

In [96]:
M = int(N * 0.05)

indexes = sorted(np.random.choice(N, M, replace=False))
x_sample = [x[i] for i in indexes]
t_sample = [t[i] for i in indexes]
D_sample = D[:, indexes]

print(D_sample.shape)

plt.plot(x_sample, t_sample)
plt.plot(x_sample, t_sample, 'o')
plt.title("5% Sample of Signal")
plt.show()
(2500, 125)

(e) DCT using gradient descent

$$ E = \sum_{k = 0}^{M - 1} \Big( t'_k - \sum_{i = 0}^{N - 1} D_{ik} f'_i \Big)^2 $$

In [99]:
def sample_error(t_sample, f_sample):
    s = 0
    for k in range(M):
        s += (t_sample[k] - np.dot(D_sample[:, k], f_sample)) ** 2
    return s
In [100]:
f_sample = np.random.normal(0, 1, N)

print("Sample error for random guess: ", sample_error(t_sample, f_sample))
Sample error for random guess:  241.27935413761477

$$ \frac{\partial E}{\partial f_i} = -2 \sum_{k = 0}^{M - 1} \Big( t'_k - \sum_{i = 0}^{N - 1} D_{ik} f'_i \Big) D_{ik} $$

In [101]:
def sample_error_grad(t_sample, f_sample):
    g = np.zeros(N)
    for i in range(N):
        for k in range(M):
            g[i] += (t_sample[k] - np.dot(D_sample[:, k], f_sample)) * D_sample[i, k]
    g *= -2
    return g
In [114]:
f_sample = np.random.normal(0, 1, N)
last_error = sample_error(t_sample, f_sample)
learning_rate = 0.1
iterations = 0

while learning_rate > 1e-6:
    iterations += 1
    
    g = sample_error_grad(t_sample, f_sample)
    
    new_f = f_sample - learning_rate * g
    
    new_error = sample_error(t_sample, new_f)
    print(iterations, "\t\t", new_error)
    
    if last_error - new_error < 1e-4:
        learning_rate *= 0.1
        print("New learning rate: ", learning_rate)
        
    if new_error < last_error:
        f_sample = new_f
        last_error = new_error
1 		 156.88868052343622
2 		 100.40875553499912
3 		 64.26160354239943
4 		 41.12742626713556
5 		 26.32155281096674
6 		 16.84579379901869
7 		 10.781308031371966
8 		 6.90003714007805
9 		 4.416023769649947
10 		 2.8262552125759663
11 		 1.8088033360486175
12 		 1.1576341350711128
13 		 0.7408858464455109
14 		 0.47416694172512636
15 		 0.30346684270408014
16 		 0.19421877933061152
17 		 0.12430001877159101
18 		 0.07955201201381867
19 		 0.05091328768884379
20 		 0.03258450412085978
21 		 0.020854082637350505
22 		 0.01334661288790443
23 		 0.00854183224825876
24 		 0.005466772638885459
25 		 0.0034987344888867586
26 		 0.002239190072887558
27 		 0.001433081646647984
28 		 0.0009171722538547181
29 		 0.0005869902424670755
30 		 0.0003756737551788933
31 		 0.0002404312033144869
32 		 0.00015387597012126998
New learning rate:  0.010000000000000002
33 		 0.00014778248170447964
New learning rate:  0.0010000000000000002
34 		 0.00014719194290756374
New learning rate:  0.00010000000000000003
35 		 0.00014713307201806
New learning rate:  1.0000000000000004e-05
36 		 0.00014712718675406237
New learning rate:  1.0000000000000004e-06
37 		 0.00014712659824590846
New learning rate:  1.0000000000000005e-07
In [115]:
print("Sample error after", iterations, "iterations of GD:", sample_error(t_sample, f_sample))

plt.plot(f_sample)
plt.title("Coefficients")
plt.show()

t_hat = D_inv.dot(f_sample)

plt.plot(t_hat)
plt.title("Reconstructed signal after subsampled DCT")
plt.show()
Sample error after 37 iterations of GD: 0.00014712659824590846

(f) Same, with L2 norm regularizarion

In [104]:
def sample_error_l2(t_sample, f_sample):
    s = np.sum(f_sample ** 2)
    for k in range(M):
        s += (t_sample[k] - np.dot(D_sample[:, k], f_sample)) ** 2
    return s
In [105]:
f_sample = np.random.normal(0, 1, N)

print("Sample error for random guess: ", sample_error_l2(t_sample, f_sample))
Sample error for random guess:  2768.8078123533132

$$ \frac{\partial E_{L2}}{\partial f_i} = -2 \sum_{k = 0}^{M - 1} \Big( t'_k - \sum_{i = 0}^{N - 1} D_{ik} f'_i \Big) D_{ik} + 2f'_i $$

In [106]:
def sample_error_l2_grad(t_sample, f_sample):
    g = np.zeros(N)
    for i in range(N):
        g[i] = 2 * f_sample[i]
        for k in range(M):
            g[i] -= 2 * (t_sample[k] - np.dot(D_sample[:, k], f_sample)) * D_sample[i, k]
    return g
In [112]:
f_sample = np.random.normal(0, 1, N)
last_error = sample_error_l2(t_sample, f_sample)
learning_rate = 0.1
iterations = 0

while learning_rate > 1e-6:
    iterations += 1
    
    g = sample_error_l2_grad(t_sample, f_sample)
    
    new_f = f_sample - learning_rate * g
    
    new_error = sample_error_l2(t_sample, new_f)
    print(iterations, "\t\t", new_error)
    
    if last_error - new_error < 1e-4:
        learning_rate *= 0.1
        print("New learning rate: ", learning_rate)
        
    if new_error < last_error:
        f_sample = new_f
        last_error = new_error
1 		 1743.266070998318
2 		 1104.7779074194643
3 		 717.1768677971382
4 		 476.683501063379
5 		 325.49341385860356
6 		 229.7129983492869
7 		 168.7667789319503
8 		 129.88836724796005
9 		 105.05196451772423
10 		 89.17314783947981
11 		 79.01663835028178
12 		 72.5186082237512
13 		 68.3606378835318
14 		 65.69981368446507
15 		 63.99698585178493
16 		 62.907211914569736
17 		 62.20976951000405
18 		 61.76341102057276
19 		 61.47774326115338
20 		 61.29491649769901
21 		 61.177907586014825
22 		 61.10302196063054
23 		 61.05509518849831
24 		 61.0244220644546
25 		 61.00479126871013
26 		 60.99222756074533
27 		 60.98418678812013
28 		 60.97904069380995
29 		 60.97574719351262
30 		 60.97363935334439
31 		 60.972290335644665
32 		 60.97142696431966
33 		 60.9708744066727
34 		 60.970520769779036
35 		 60.970294442167194
36 		 60.97014959249564
37 		 60.970056888705884
New learning rate:  0.010000000000000002
38 		 60.970050362359146
New learning rate:  0.0010000000000000002
39 		 60.97004972987068
New learning rate:  0.00010000000000000003
40 		 60.97004966681781
New learning rate:  1.0000000000000004e-05
41 		 60.9700496605145
New learning rate:  1.0000000000000004e-06
42 		 60.97004965988416
New learning rate:  1.0000000000000005e-07
In [113]:
print("Sample error after", iterations, "iterations of GD:", sample_error_l2(t_sample, f_sample))

plt.plot(f_sample)
plt.title("Coefficients")
plt.show()

t_hat = D_inv.dot(f_sample)

plt.plot(t_hat)
plt.title("Reconstructed signal after subsampled DCT (L2 regularized)")
plt.show()
Sample error after 42 iterations of GD: 60.97004965988416
In [109]:
f_l2 = f_sample.copy()

Same, with L1 reguarization

In [82]:
def sample_error_l1(t_sample, f_sample):
    s = np.sum(np.abs(f_sample))
    for k in range(M):
        s += (t_sample[k] - np.dot(D_sample[:, k], f_sample)) ** 2
    return s
In [83]:
f_sample = np.random.normal(0, 1, N)

print("Sample error for random guess: ", sample_error_l1(t_sample, f_sample))
Sample error for random guess:  2264.716346514302

$$ \frac{\partial E_{L1}}{\partial f_i} = -2 \sum_{k = 0}^{M - 1} \Big( t'_k - \sum_{i = 0}^{N - 1} D_{ik} f'_i \Big) D_{ik} + sign(f'_i) $$

Where I define $sign$ as:

sign($x$) = -1 if $x < tolerance$, 1 if $x > tolerance$, else 0

In [127]:
def sample_error_l1_grad(t_sample, f_sample, tol=1e-6):
    f_copy = f_sample.copy()
    f_copy[np.abs(f_copy) < tol] = 0
    g = np.sign(f_copy)
    for i in range(N):
        for k in range(M):
            g[i] -= 2 * (t_sample[k] - np.dot(D_sample[:, k], f_sample)) * D_sample[i, k]
    return g
In [128]:
f_sample = np.random.normal(0, 1, N)
last_error = sample_error_l1(t_sample, f_sample)
learning_rate = 0.1
iterations = 0

while learning_rate > 1e-6:
    iterations += 1
    
    g = sample_error_l1_grad(t_sample, f_sample)
    
    new_f = f_sample - learning_rate * g
    
    new_error = sample_error_l1(t_sample, new_f)
    print(iterations, "\t\t", new_error)
    
    if last_error - new_error < 1e-4:
        learning_rate *= 0.1
        print("New learning rate: ", learning_rate)
        
    if new_error < last_error:
        f_sample = new_f
        last_error = new_error
1 		 1859.542963934629
2 		 1599.0844274835088
3 		 1382.7564520368821
4 		 1195.8559559652213
5 		 1037.9872780623173
6 		 900.7728256180238
7 		 781.2503744433471
8 		 680.7861458414978
9 		 595.6223680032432
10 		 526.4843816856312
11 		 467.30591987154514
12 		 418.21867401840825
13 		 380.147972475801
14 		 345.20167114511236
15 		 321.35766737724543
16 		 298.4495454535612
17 		 285.19289981323834
18 		 274.43837674116827
19 		 261.5990265681589
20 		 255.67539967621744
21 		 250.88493018120255
22 		 242.1549431260718
23 		 240.0321810793154
24 		 241.33688034446143
New learning rate:  0.010000000000000002
25 		 219.35233460186782
26 		 202.10138209853739
27 		 188.5097116889626
28 		 177.61569603600128
29 		 169.49574637001345
30 		 162.6466882043627
31 		 157.97700085175234
32 		 153.82367187474384
33 		 150.7402470906724
34 		 148.09261711227083
35 		 146.23891476737288
36 		 144.59660980656332
37 		 143.39946430932108
38 		 142.35606548933907
39 		 141.51452782669233
40 		 140.6818440607347
41 		 140.04195078263385
42 		 139.2584057142971
43 		 139.17489123463002
44 		 138.21665999833104
45 		 138.11199617567408
46 		 137.73520012244896
47 		 137.36787305973309
48 		 137.18656654132792
49 		 136.68543913246498
50 		 136.4537243119175
51 		 136.268242455635
52 		 135.93195995275084
53 		 135.75928498151237
54 		 135.39865296935582
55 		 135.1877302394161
56 		 134.95146833593932
57 		 134.7846416014395
58 		 134.51886807183308
59 		 134.29121249602568
60 		 134.01884143930553
61 		 133.9774158859857
62 		 133.50946653204687
63 		 133.531317860277
New learning rate:  0.0010000000000000002
64 		 131.6792189210268
65 		 130.15689258269492
66 		 128.90763748719425
67 		 127.8849029913347
68 		 127.0701955233205
69 		 126.45254542547542
70 		 125.96651968265819
71 		 125.55485675319419
72 		 125.26209444491778
73 		 125.01061260337056
74 		 124.81507842870707
75 		 124.63212842819341
76 		 124.521569087951
77 		 124.37117372008545
78 		 124.31306125789855
79 		 124.21715267284401
80 		 124.12020125394642
81 		 124.11624389232033
82 		 123.99323701304395
83 		 123.99854647693995
New learning rate:  0.00010000000000000003
84 		 123.81620024151101
85 		 123.6669133662294
86 		 123.54371193887198
87 		 123.44825818703089
88 		 123.37069296207379
89 		 123.31067212091916
90 		 123.26235077994842
91 		 123.2249590690449
92 		 123.1900503674088
93 		 123.16978422843394
94 		 123.14388108470882
95 		 123.1317848667835
96 		 123.11054370664331
97 		 123.1002626645321
98 		 123.08681644269267
99 		 123.07613708867365
100 		 123.06761044020348
101 		 123.05733139050542
102 		 123.05203396748364
103 		 123.0442148843728
104 		 123.03745100082268
105 		 123.03209372883215
106 		 123.02411231911894
107 		 123.0202218997116
108 		 123.01461615101992
109 		 123.00911615090035
110 		 123.002244648106
111 		 123.00070861337869
112 		 122.99250992100353
113 		 122.98793785102347
114 		 122.98615274873474
115 		 122.97966271380784
116 		 122.9735317994168
117 		 122.9717106444044
118 		 122.96641512291325
119 		 122.96069652209236
120 		 122.95701189660126
121 		 122.95334819769957
122 		 122.94823716705797
123 		 122.94347510599385
124 		 122.94189269626656
125 		 122.93409468457784
126 		 122.93400033443268
New learning rate:  1.0000000000000004e-05
127 		 122.91586928393393
128 		 122.90014300394985
129 		 122.8872561928211
130 		 122.87680265138661
131 		 122.86875945265301
132 		 122.86203989973892
133 		 122.85679722371016
134 		 122.8526498286531
135 		 122.84887041642712
136 		 122.846531959554
137 		 122.84435944426066
138 		 122.84243435169817
139 		 122.84081086622851
140 		 122.83952315692984
141 		 122.83839345067491
142 		 122.83726434746069
143 		 122.83651671612046
144 		 122.83560142596349
145 		 122.83486420592062
146 		 122.83427822931672
147 		 122.83349711337746
148 		 122.83302021264318
149 		 122.83261042111236
150 		 122.83206032935195
151 		 122.83152408523074
152 		 122.8310542741717
153 		 122.83041184394226
154 		 122.830424831854
New learning rate:  1.0000000000000004e-06
155 		 122.82867554626752
156 		 122.82730062711468
157 		 122.82621855426605
158 		 122.82536344318933
159 		 122.824721568092
160 		 122.82424708049497
161 		 122.82388736528767
162 		 122.82360084659562
163 		 122.82336297435562
164 		 122.8231872515471
165 		 122.82302312352724
166 		 122.82291728335254
167 		 122.82280451829573
168 		 122.82272687470817
New learning rate:  1.0000000000000005e-07
In [129]:
print("Sample error after", iterations, "iterations of GD:", sample_error_l1(t_sample, f_sample))

plt.plot(f_sample)
plt.title("Coefficients")
plt.show()

t_hat = D_inv.dot(f_sample)

plt.plot(t_hat)
plt.title("Reconstructed signal after subsampled DCT (L1 regularized)")
plt.show()
Sample error after 168 iterations of GD: 122.82272687470817

Inverse Wavelet Transform

In [159]:
c0 = (1 + np.sqrt(3)) / (4 * np.sqrt(2))
c1 = (3 + np.sqrt(3)) / (4 * np.sqrt(2))
c2 = (3 - np.sqrt(3)) / (4 * np.sqrt(2))
c3 = (1 - np.sqrt(3)) / (4 * np.sqrt(2))

def invert_wavelet_transform(v):
    N = len(v)
    
    split = np.zeros((N, N))
    for i in range(N // 2):
        split[i, i * 2] = 1
        split[i + (N // 2), i * 2 + 1] = 1
        
    v2 = split.T.dot(v)
    
    transform = np.zeros((N, N))
    
    for i in range(0, N, 2):
        transform[i, i] = c0
        transform[i, i + 1] = c1
        transform[i, (i + 2) % N] = c2
        transform[i, (i + 3) % N] = c3
        transform[i + 1, i] = c3
        transform[i + 1, i + 1] = -c2
        transform[i + 1, (i + 2) % N] = c1
        transform[i + 1, (i + 3) % N] = -c0

    v3 = transform.T.dot(v2)
    
    return v3
In [160]:
x = np.zeros(1)
w = np.zeros(2 ** 12)
w[4] = 1
w[29] = 1

while len(x) < 2 ** 12:
    tmp_w = w[:len(x)]
    
    v = np.concatenate([x, tmp_w])
    x = invert_wavelet_transform(v)
In [161]:
plt.plot(x)
plt.show()

(13.3)

(a) Analytically compute covariance:

$$ X_1 \sim N(0, 1) \\ X_2 \sim N(0, 1) \\ X_3 = X1 + X2 $$

The diagonal: $$ C_{11} = Var(X_1) = 1 \\ C_{22} = Var(X_2) = 1 \\ C_{33} = Var(X_3) = Var(X1) + Var(X1) = 2 $$

The rest: $$ C_{12} = \langle(X_1 - \langle X_1\rangle)(X_2 - \langle X_2\rangle\rangle = \langle X_1 X_2\rangle = 0 \\ C_{13} = \langle(X_1 - \langle X_1\rangle)(X_3 - \langle X_3\rangle\rangle = \langle X_1^2 + X_1 X_2\rangle = \langle X_1^2\rangle + \langle X_1 X_2\rangle = 1 \\ C_{23} = \langle(X_2 - \langle X_2\rangle)(X_3 - \langle X_3\rangle\rangle = \langle X_2^2 + X_1 X_2\rangle = \langle X_2^2\rangle + \langle X_1 X_2\rangle = 1 $$

$$ C = \begin{pmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 1 & 2 \end{pmatrix} $$

(b) Eigenvalues

$$ det \begin{pmatrix} 1 - \lambda & 0 & 1 \\ 0 & 1 - \lambda & 1 \\ 1 & 1 & 2 - \lambda \end{pmatrix} = 0 $$

$$ (1 - \lambda)((1 - \lambda)(2 - \lambda) - 1) - (1 - \lambda) = 0 \implies \\ (1 - \lambda)(2 -3 \lambda + \lambda^2 - 2) = 0 \implies \\ \lambda(1 - \lambda)(\lambda - 3) = 0 $$

Eigenvalues: $$ \lambda_1 = 0 \\ \lambda_2 = 1 \\ \lambda_3 = 3 $$

(c) Numerically validate:

In [202]:
np.set_printoptions(precision=2)
np.set_printoptions(suppress=True)

N = 10000
x1 = np.random.normal(0, 1, N)
x2 = np.random.normal(0, 1, N)
x3 = x1 + x2

data = np.array([x1, x2, x3])

cov = np.cov(data)
print("Covariance matrix:")
print(cov)

eigVals, eigVecs = np.linalg.eig(cov)
print("\nEigenvalues:")
print(eigVals)
Covariance matrix:
[[ 1.   -0.01  0.99]
 [-0.01  1.01  1.  ]
 [ 0.99  1.    1.99]]

Eigenvalues:
[ 2.99  1.01 -0.  ]

(d) Transform

In [203]:
trans_data = eigVecs[:, :2].T.dot(data)

trans_cov = np.cov(trans_data)
print("Transformed Covariance matrix:")
print(trans_cov)

trans_eigVals, _ = np.linalg.eig(trans_cov)
print("\nEigenvalues:")
print(trans_eigVals)
Transformed Covariance matrix:
[[2.99 0.  ]
 [0.   1.01]]

Eigenvalues:
[2.99 1.01]

(13.5)

(a)

In [297]:
N = 10000

s1 = np.random.uniform(0, 1, N)
s2 = np.random.uniform(0, 1, N)

plt.plot(s1, s2, 'o')
plt.show()

(b)

In [298]:
s = np.array([s1, s2])
A = np.array([[1, 2], [3, 1]])
x = A.dot(s)

plt.plot(x[0], x[1], 'o')
plt.show()

(c)

In [299]:
# zero mean
x = x - np.mean(x)

# diagnolize
cov = np.cov(x)
eigVals, eigVecs = np.linalg.eig(cov)
x = eigVecs.T.dot(x)

# scale to 1
x = (x.T / np.sqrt(eigVals)).T

# verify:
print("Cov matrix:")
print(np.cov(x))

plt.plot(x[0], x[1], 'o')
plt.show()
Cov matrix:
[[ 1. -0.]
 [-0.  1.]]

(d) ICA

In [333]:
def f(x):
    return np.log(np.cosh(x))

# according to Wolfram Alpha..
def f_tag(x):
    return np.tanh(x)

# again, according to Wolfram Alpha
def f_tag_tag(x):
    return 1 / (np.cosh(x) ** 2)


w = np.random.normal(0, 1, 2)
iterations = 0
while iterations < 100:
    iterations += 1
    w_new = np.mean(x * f_tag(np.dot(w, x))) - w * np.mean(f_tag_tag(np.dot(w, x)))
    w_new = w_new / np.linalg.norm(w_new)

    if np.linalg.norm(w + w_new) < 1e-4:
        break
    
    w = w_new.copy()
    
print("Finished after", iterations, "iterations")
print(w)

ortho_w = np.array([1, -w[0] / w[1]])
ortho_w = ortho_w / np.linalg.norm(ortho_w)

print(ortho_w)
Finished after 11 iterations
[-0.51  0.86]
[0.86 0.51]
In [334]:
plt.plot(x[0], x[1], 'o')
plt.plot([0, w[0]], [0, w[1]], color='red')
plt.plot([0, ortho_w[0]], [0, ortho_w[1]], color='red')
plt.show()