작성중...


INDEX



Uniform Random Variable

Exponential Random Variable

Erlang Random Variable

Gaussian Random Variable

Bivariate Gaussian Random Variable

Gaussian Random Vector

Beta Random Vector




Uniform Random Variable


\(X\) is a \(Uniform(a, b)\) random variable if the PDF of \(X\) is

$$f_{X}(x) = \begin{cases} 1/(b-a) & \quad a\leq x \leq b\\ 0 & \quad \text{otherwise}\\ \end{cases} $$

where the two parameters are \(b > a\).

$$F_{X}(x) = \begin{cases} 0 & \quad x \leq a\\ (x - a)/(b - a) & \quad a < x \leq b\\ 1 & \quad x > b\\ \end{cases} $$

$$\text{Expected value} \quad E[X] = (b + a)/2.$$

$$\text{Variance} \quad Var[X] = (b - a)^2/12.$$


PDF of Uniform DistributionPDF of Uniform Distribution (WIKIPEDIA)

CDF of Uniform DistributionCDF of Uniform Distribution (WIKIPEDIA)



Exponential Random Variable


\(X\) is a \(exponential(\lambda)\) random variable if the PDF of \(X\) is

$$f_{X}(x) = \begin{cases} \lambda e^{-\lambda x} & \quad x \geq 0\\ 0 & \quad \text{otherwise.}\\ \end{cases} $$

where the parameter \(\lambda > 0\).

$$F_{X}(x) = \begin{cases} 1-e^{-\lambda x} & \quad x \geq 0\\ 0 & \quad \text{otherwise.}\\ \end{cases} $$

$$\text{Expected value} \quad E[X] = 1/\lambda.$$

$$\text{Variance} \quad Var[X] = 1/\lambda^2.$$


PDF of Exponential DistributionPDF of Exponential Distribution (WIKIPEDIA)

CDF of Exponential DistributionCDF of Exponential Distribution (WIKIPEDIA)



Erlang Random Variable


\(X\) is a \(Erlang(n, \lambda)\) random variable if the PDF of \(X\) is

$$f_{X}(x) = \begin{cases} \frac{\lambda^n x^{n-1} e^{-\lambda x}}{(n - 1)!} & \quad x \geq 0\\ 0 & \quad \text{otherwise.}\\ \end{cases} $$

where the parameter \(\lambda > 0 \)and the parameter \(n \geq 1\) is an integer.

$$\text{Expected value} \quad E[X] = \frac{n}{\lambda}.$$

$$\text{Variance} \quad Var[X] = \frac{n}{\lambda^2}.$$


PDF of Erlang DistributionPDF of Erlang Distribution (WIKIPEDIA)

CDF of Erlang DistributionCDF of Erlang Distribution (WIKIPEDIA)



Gaussian Random Variable


\(X\) is a \(Gaussian(\mu, \sigma)\) random variable if the PDF of \(X\) is

$$f_X(x) = \frac{1}{\sqrt{2\pi \sigma^2}}e^{-(x - \mu)^2/2\sigma^2}$$

where the parameter \(\mu\) can be any real number and the parameter \(\sigma > 0\)

$$\text{Expected value} \quad E[X] = \mu.$$

$$\text{Variance} \quad Var[X] = \sigma^2.$$


PDF of Normal DistributionPDF of Normal Distribution (WIKIPEDIA)

CDF of Normal DistributionCDF of Normal Distribution (WIKIPEDIA)



Bivariate Gaussian Random Variable


Random variables \(X\) and \(Y\) have a bivariate Gaussian PDF with parameters \(\mu_1, \sigma_1, \mu_2, \sigma_2,\) and \(\rho\) if

$$f_{X, Y}(x, y) = \frac{\exp(-\frac{\Big(\frac{x - \mu_1}{\sigma_1}\Big)^2 - \frac{2\rho (x - \mu_1)(y - \mu_2)}{\sigma_1 \sigma_2} + \Big(\frac{y - \mu_2}{\sigma_2}\Big)^2}{2(1 - \rho^2)})}{2\pi \sigma_1 \sigma_2 \sqrt{1 - \rho^2}} $$

where \(\mu_1\) and \(\mu_2\) can be any real numbers, \(\sigma_1 >0, \sigma_2 >0 \), and \( -1 < \rho < 1\) .





Gaussian Random Vector


\(\mathbf{X}\) is the \(Gaussian(\mathbf{\mu_X, C_X})\) random vector with expected value \(\mathbf{\mu_X}\) and covariance \(\mathbf{C_X}\) if and only if

$$f_{\mathbf{X}}(\mathbf{x}) = \frac{1}{(2\pi)^{n/2}[det(\mathbf{C_X})]^{1/2}}\exp\bigg(-\frac{1}{2}(\mathbf{x}-\mathbf{\mu_X})^T \mathbf{C_X^{-1}} (\mathbf{x} - \mathbf{\mu_X}) \bigg) $$

where \(det(\mathbf{C_X})\), the determinant of \(\mathbf{C_X}\), satisfies \(det(\mathbf{C_X}) > 0\).




Beta Random Variable


\(X\) is a \(Beta(\alpha, \beta)\) random variable if the PDF of \(X\) is

$$f_{X}(x) = \frac{\Gamma (\alpha + \beta)}{\Gamma (\alpha) \Gamma (\beta)}x^{\alpha - 1}(1 - x)^{\beta - 1} = \frac{1}{B(\alpha, \beta)}x^{\alpha - 1}(1 - x)^{\beta - 1} \quad \quad (\because B(\alpha, \beta) = \frac{\Gamma(\alpha) \Gamma(\beta)}{\Gamma (\alpha + \beta)}) $$

where the parameter \( 0 \leq x \leq 1\), and shape parameters \(\alpha, \beta > 0\).

\(\Gamma(z) = \int_0^{\infty}x^{z - 1}e^{-x}\mathrm{d}x \quad \quad (\Gamma(n) = (n - 1)! , \text{if n is positive integer}).\).


$$\text{Expected value} \quad E[X] = \frac{\alpha}{\alpha + \beta}.$$

$$\text{Variance} \quad Var[X] = \frac{\alpha \beta}{(\alpha + \beta )^2(\alpha + \beta + 1)}.$$


PDF of Beta DistributionPDF of Beta Distribution (WIKIPEDIA)

CDF of Beta DistributionCDF of Beta Distribution (WIKIPEDIA)



Proof - Gaussian




\(X\) is a \(Gaussian(\mu, \sigma)\) random variable if the PDF of \(X\) is

$$f_X(x) = \frac{1}{\sqrt{2\pi \sigma^2}}e^{-(x - \mu)^2/2\sigma^2}$$


Expected Value

\( \int_{-\infty}^{\infty}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x\\ \)


\( \text{Let}\quad \omega = \frac{x - \mu}{\sqrt{2\sigma^2}}, \mathrm{d}\omega = \frac{\mathrm{d}x}{\sqrt{2\sigma^2}}\\ \)


\( \int_{-\infty}^{\infty}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\omega^2}\sqrt{2\sigma^2}\mathrm{d}\omega\\ =\frac{1}{\sqrt{\pi}}\int_{-\infty}^{\infty}e^{-\omega^2}\mathrm{d}\omega\\ \)


\( \begin{align} \bigg\{\int_{-\infty}^{\infty}e^{-\omega^2}\mathrm{d}\omega\bigg\}^2 &= \int_{-\infty}^{\infty}e^{-x^2}\mathrm{d}x \cdot \int_{-\infty}^{\infty}e^{-y^2}\mathrm{d}y\\ &= \int_{-\infty}^{\infty}\int_{-\infty}^{\infty}e^{-(x^2 + y^2)}\mathrm{d}x\mathrm{d}y\\ \end{align} \)


\( \text{Let}\quad x = r\cos{\theta}, \quad y = r\sin{\theta}\\ \)


\( \begin{align} \int_{0}^{2\pi}\int_{0}^{\infty}e^{-r^2}det(J(r,\theta))\mathrm{d}r\mathrm{d}\theta &= \int_{0}^{2\pi}\int_{0}^{\infty}e^{-r^2}r\mathrm{d}r\mathrm{d}\theta\\ &= 2\pi\int_{0}^{\infty}re^{-r^2}r\mathrm{d}r\\ \end{align} \)


\( \text{Let}\quad s = -r^2, \quad \mathrm{d}s = -2r\mathrm{d}r\\ \)


\( \begin{align} 2\pi\int_{0}^{-\infty}-\frac{1}{2}e^{s}\mathrm{d}s &= 2\pi\int_{-\infty}^{0}\frac{1}{2}e^{s}\mathrm{d}s\\ &= \pi\int_{-\infty}^{0}e^{s}\mathrm{d}s\\ &=\pi e^s \Big|_{-\infty}^{0}\\&=\pi \end{align} \)


\( \begin{align} \bigg\{\int_{-\infty}^{\infty}e^{-\omega^2}\mathrm{d}\omega\bigg\}^2 = \pi, \quad \int_{-\infty}^{\infty}e^{-\omega^2}\mathrm{d}\omega = \sqrt{\pi} \end{align} \)


\( \begin{align} \frac{1}{\sqrt{\pi}}\int_{-\infty}^{\infty}e^{-\omega^2}\mathrm{d}\omega = 1 \end{align} \)


\( \begin{align} \therefore \int_{-\infty}^{\infty}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x = 1 \end{align} \)


\( \begin{align} E[X] &= \int_{-\infty}^{\infty}x\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x \\ &= \int_{-\infty}^{\infty}[x - \mu + \mu]\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x\\ &= \int_{-\infty}^{\infty}(x - \mu)\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x + \mu\int_{-\infty}^{\infty}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x\\ &= \int_{-\infty}^{\infty}(x - \mu)\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x + \mu \quad \quad \bigg( \because \int_{-\infty}^{\infty}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x = 1 \bigg) \\ &= \frac{-\sigma^2}{\sqrt{2\pi\sigma^2}}\int_{-\infty}^{\infty}-\frac{(x - \mu)}{\sigma^2}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x + \mu\\ &= \frac{-\sigma^2}{\sqrt{2\pi\sigma^2}}\bigg[ e^{-(x - \mu)^2/2\sigma^2}\bigg]_{-\infty}^{\infty} + \mu\\ &= \frac{-\sigma^2}{\sqrt{2\pi\sigma^2}}\bigg[ 0 - 0\bigg] + \mu\\ &= \mu \end{align} \)


Variance

\( Var[X] = \int_{-\infty}^{\infty}(x - \mu)^2\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}x\\ \)


\( \text{Let}\quad \omega = \frac{x - \mu}{\sqrt{2\sigma^2}}, \mathrm{d}\omega = \frac{\mathrm{d}x}{\sqrt{2\sigma^2}}\\ \)


\( \begin{align} Var[X] = \int_{-\infty}^{\infty}2\sigma^2 \omega^2 \frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x - \mu)^2/2\sigma^2}\mathrm{d}\omega\\ =\frac{2\sigma^2}{\sqrt{\pi}}\int_{-\infty}^{\infty}\omega^2 e^{-\omega^2}\mathrm{d}\omega\\ \end{align} \)


\( \text{Intergration by parts by}\quad u = \omega , \quad v = -\frac{1}{2}e^{-\omega^2}, u^{\prime} = 1, v^{\prime} = \omega e^{-\omega^2}\quad \quad \bigg( \int uv^{\prime} = uv - \int u^{\prime}v \bigg)\\ \)


\( \begin{align} Var[X] &= \frac{2\sigma^2}{\sqrt{\pi}}\bigg\{ \bigg[-\frac{1}{2}\omega e^{-\omega^2}\bigg]_{-\infty}^{\infty} - \int_{-\infty}^{\infty} - \frac{1}{2} e^{- \omega^2} \mathrm{d} \omega \bigg\}\\ &= \frac{2\sigma^2}{\sqrt{\pi}}\bigg\{ \bigg[0 - 0\bigg] - \frac{1}{2}\sqrt{\pi} \bigg\}\\ &= \sigma^2 \end{align} \)



'Probability' 카테고리의 다른 글

Families of Discrete Random Variables  (0) 2017.03.22

작성중...


INDEX



Bernoulli Random Variable

Geometric Random Variable

Binomial Random Variable

Negative Binomial Random Variable (Pascal Random Variable)

Discrete Uniform Random Variable

Poisson Random Variable




Bernoulli Random Variable


\(X\) is a \(Bernoulli(p)\) random variable if the PMF of \(X\) h 

$$P_{X}(x) = \begin{cases} 1-p & \quad x = 0\\ p & \quad x = 1\\ 0 & \quad \text{otherwise}\\ \end{cases} $$

where the parameter \(p\) is in the range \(0 < p < 1\).

$$\text{Expected value} \quad E[X] = p.$$

$$\text{Second moment} \quad E[X^2] = p.$$

$$\text{Variance} \quad Var[X] = p(1-p).$$


ex) 성공할 확률은 \(p\), 실패할 확률은 \(1 - p\), \(X = \) 성공 또는 실패.



Geometric Random Variable


\(X\) is a \(Geomatric(p)\) random variable if the PMF of \(X\) has the form

$$P_{X}(x) = \begin{cases} p(1-p)^{x-1} & \quad x = 1, 2, ...\\ 0 & \quad \text{otherwise.}\\ \end{cases} $$

where the parameter \(p\) is in the range \(0 < p < 1\).

$$\text{Expected value} \quad E[X] = 1/p.$$

$$\text{Second moment} \quad E[X^2] = (2-p)/p.$$

$$\text{Variance} \quad Var[X] = (1-p)/p^2.$$


ex) 성공할 확률은 \(p\), 실패할 확률은 \(1 - p\), \(X = \) 성공할때까지 시행한 횟수(첫 번째 성공까지 걸린 횟수), \(P_X(x) = \) x번째에 처음으로 성공할 확률. \(E[X] = \) 성공할때까지 시행한 횟수의 기댓값. 각 시행은 독립임.



Binomial Random Variable


\(X\) is a \(Binomial(n, p)\) random variable if the PMF of \(X\) has the form

$$P_{X}(x) = \binom{n}{x}p^x(1-p)^{n-x}$$

where \(0 < p < 1\) and \(n\) is an integer such that \(n \geq 1\).

$$\text{Expected value} \quad E[X] = np.$$

$$\text{Second moment} \quad E[X^2] = np(np-p+1).$$

$$\text{Variance} \quad Var[X] = np(1-p).$$


ex) 성공할 확률은 \(p\), 실패할 확률은 \(1 - p\), \(X = \) 성공한 횟수, \(n = \) 시도한 횟수, \(P_X(x) = \) n번 시행했을때 x번 성공할 확률. \(E[X] = \) n번 시행했을때, 성공한 횟수의 기댓값. 각 시행은 독립임.



Negative Binomial Random Variable (Pascal Random Variable)


\(X\) is a \(NB(r; p)\) random variable if the PMF of \(X\) has the form

$$f(k;r,p)\equiv \Pr(X=k)={\binom {k+r-1}{k}}p^{k}(1-p)^{r}\quad {\text{for }}k=0,1,2,\dotsc$$

where \(k\) is number of successes, \(r\) is number of failures, and \(p\) is the probability of success.

$$\text{Expected value} \quad E[X] = pr/(1-p).$$

$$\text{Second moment} \quad E[X^2] = pr(pr+1)/(1-p)^2.$$

$$\text{Variance} \quad Var[X] = pr/(1-p)^2.$$

$$\text{Skewness (Third normalised moment)} \quad E[X^2] = \frac{1+p}{\sqrt{pr}}$$


ex) 성공할 확률은 \(p\), 실패할 확률은 \(1 - p\), \(k = \) 성공한 횟수, \(r = \) 실패한 횟수, \(P_X(x) = \) r+k번 시행했고, 마지막 시행이 실패면서 k번 성공할 확률.


위의 Negative Binomial Random Variable을 마지막 시행을 성공했을 경우로 바꾸고, 시행횟수를 \(x\)로 정하면, 다음과 같은 PMF를 만들 수 있다. 1번의 성공을 가정했기 때문에 k는 1보다 크거나 같아야 한다.

\(X\) is a \(Pascal(k, p)\) random variable if the PMF of \(X\) has the form

$$P_{X}(x) = \binom{x-1}{k-1}p^k(1-p)^{x-k}$$

where \(0 < p < 1\) and \(k\) is an integer such that \(k \geq 1\).

$$\text{Expected value} \quad E[X] = k/p.$$

$$\text{Second moment} \quad E[X^2] = k(1-p)+k^2/p^2.$$

$$\text{Variance} \quad Var[X] = k(1-p)/p^2.$$


ex) 성공할 확률은 \(p\), 실패할 확률은 \(1 - p\), \(k = \) 성공한 횟수, \(x = \) 시행한 횟수, \(P_X(x) = \) x번 시행했고, 마지막 시행이 성공이면서 k번 성공 했을 확률.



Discrete Uniform Random Variable


\(X\) is a \(discrete uniform (k, l)\) random variable if the PMF of \(X\) has the form

$$P_{X}(x) = \begin{cases} l/(l-k+1) & \quad x = k, k +1, k+2, \dotsc , l\\ 0 & \quad otherwise\\ \end{cases} $$

where the parameters \(k\) and \(l\) are integers such that \(k < l\).

$$\text{Expected value} \quad E[X] = (l + k)/2$$

$$\text{Variance} \quad Var[X] = (l - k)(l - k +2)/12.$$





Poisson Random Variable


\(X\) is a \(Poisson(\alpha)\) random variable if the PMF of \(X\) has the form

$$P_{X}(x) = \begin{cases} \alpha^xe^{-\alpha}/x! & \quad x = 0, 1, 2, \dotsc, \\ 0 & \quad \text{otherwise}\\ \end{cases} $$

where the parameter \(\alpha\) is in the range \(\alpha > 0\).

$$\text{Expected value} \quad E[X] = \alpha$$

$$\text{Variance} \quad Var[X] = \alpha$$

$$\text{Skewness (Third normalised moment)} \quad E[X^2] = \alpha^{-1/2}$$


ex) \(\alpha = \lambda T = \) T시간동안 이벤트가 발생한 횟수,\(\lambda = \) T의 단위에 대한 평균 이벤트 발생율.(T가 시간일 경우 단위 시간당 평균 이벤트 발생 횟수). \(T = \) 관찰한 시간, \(P_X(x) = \) 관찰 시간 T 동안 j번 이벤트가 발생할 확률.




Proof - Bernoulli



PMF of \(Bernoulli(p)\) is

$$P_{X}(x) = \begin{cases} 1-p & \quad x = 0\\ p & \quad x = 1\\ 0 & \quad \text{otherwise}\\ \end{cases} $$


Expected Value

\( E[X] = \mu_X = \displaystyle\sum_{x \in S_X} x P_X(x)\\ E[X] = 0 \times (1 - p) + 1 \times p = p\\ \quad \\ \therefore E[X] = p \)


Variance

\( Var[X] = E[X^2] - (E[X])^2\\ E[X^2] = 0^2 \times (1 - p) + 1^2 \times p = p\\ Var[X] = p - p^2\\ \quad \\ \therefore Var[X] = p(1 - p) \)



Proof - Geometric



PMF of \(Geometric(p)\) is

$$P_{X}(x) = \begin{cases} p(1-p)^{x-1} & \quad x = 1, 2, ...\\ 0 & \quad \text{otherwise.}\\ \end{cases} $$


Expected Value

\( \begin{align} E[X] &= \displaystyle\sum_{x = 1}^{\infty} x p(1-p)^{x - 1}\\ &= p + 2p(1-p) + 3p(1-p)^2 + \dots\\ \end{align} \)


\( \begin{align} (1-p)E[X] &= \displaystyle\sum_{x = 1}^{\infty} x p(1-p)^{x}\\ &= p(1-p) + 2p(1-p)^2 + 3p(1-p)^3 + \dots\\ \end{align} \)


\( \begin{align} E[X] - (1-p)E[X] &= p E[X] = p + p(1 - p) + p(1 - p)^2 + \dots\\ &= \displaystyle\sum_{x = 1}^{\infty} p(1 - p)^{x - 1} = \frac{p}{1 - (1 - p)} = 1\\ \end{align} \)


\(\therefore E[X] = \frac{1}{p}\)


Variance

\( \begin{align} E[X^2] &= \displaystyle\sum_{x = 1}^{\infty} x^2 p(1-p)^{x - 1}\\ &= p + 4p(1-p) + 9p(1-p)^2 + \dots\\ \end{align} \)


\( \begin{align} E[X^2] - (1-p)E[X^2] &= p E[X^2]\\ &= 1 + 2(1-p) + 2(1-p)^2 + 2(1-p)^3 + \dots\\ &= 1 + \frac{2(1 - p)}{1 - (1 - p)} = \frac{2 - p}{p} \quad \because 1 - p < 1 \\ \end{align} \)


\( E[X^2] = \frac{2 - p}{p^2} \)


\( \begin{align} \therefore Var[X] &= E[X^2] - (E[X])^2\\ &=\frac{2-p}{p^2} - \frac{1}{p^2}\\ &=\frac{1-p}{p^2} \end{align} \)



Proof - Binnomial



PMF of \(Binomial(n, p)\) is

$$P_{X}(x) = \binom{n}{x}p^x(1-p)^{n-x}$$


Expected Value

\( \begin{align} E[X] &= \displaystyle\sum_{x = 0}^{n} x p^x(1 - p)^{n-x}\\ &= \displaystyle\sum_{x = 0}^{n} \frac{n!}{(n-x)!x!}x \cdot p^x (1 - p)^{n-x}\\ &= \displaystyle\sum_{x = 1}^{n} \frac{n!}{(n-x)!(x - 1)!} p^x (1 - p)^{n-x}\quad \quad \because \binom{n}{0} 0 \times p^0 (1 - p)^{n}\\ &= \displaystyle\sum_{x = 1}^{n} \frac{n (n - 1)!}{(n-x)!(x - 1)!} p \cdot p^{x - 1} (1 - p)^{n-x}\\ &= np\displaystyle\sum_{x = 1}^{n} \frac{(n - 1)!}{(n-x)!(x - 1)!} p^{x - 1} (1 - p)^{n-x}\\ \\ &\text{Let } a = x - 1, b = n - 1 \text{ then},\\ \\ &= np\displaystyle\sum_{a = 0}^{n} \frac{b!}{(b - a)!a!} p^{a} (1 - p)^{b - a}\\ &= np \quad \quad \because \displaystyle\sum_{a = 0}^{n} \frac{b!}{(b - a)!a!} p^{a} (1 - p)^{b - a} = 1\\ \end{align} \)


\( \begin{align} \therefore E[X] = np\\ \end{align} \)


Variance

\( \begin{align} E[X(X - 1)] &= E[X^2] - \mu = E[X^2] - \mu^2 + \mu^2 - \mu\\ &= Var[X] + \mu^2 - \mu \end{align} \)


\( Var[X] = E[X(X - 1)] - \mu^2 + \mu\\ \)


\( \begin{align} E[X(X - 1)] &= \displaystyle\sum_{x = 0}^{n} x (x - 1) \binom{n}{x}p^{x}(1 - p)^{n - x}\\ &= \displaystyle\sum_{x = 2}^{n} x (x - 1) \binom{n}{x}p^{x}(1 - p)^{n - x} \quad \quad \because x (x - 1) \binom{n}{x}p^{x}(1 - p)^{n - x} = 0 \quad \text{ when } x = 0, 1\\ &= \displaystyle\sum_{x = 2}^{n} x (x - 1) \frac{n!}{(n-x)!x!} p^{x} (1 - p)^{n-x}\\ &= n(n - 1) p^2\displaystyle\sum_{x = 2}^{n} x (x - 1) \frac{(n - 2)!}{(n-x)!x!} p^{x - 2} (1 - p)^{n-x}\\ &= n(n - 1) p^2\displaystyle\sum_{x = 2}^{n} \frac{(n - 2)!}{(n-x)!(x - 2)!} p^{x - 2} (1 - p)^{n-x}\\ \\ &\text{Let } a = x - 2, b = n - 2 \text{ then},\\ \\ &= n(n - 1) p^2\displaystyle\sum_{a = 0}^{b} \frac{b!}{(b - a)!a!} p^{a} (1 - p)^{b - a}\\ &= n(n - 1) p^2 \quad \quad \because \displaystyle\sum_{a = 0}^{b} \frac{b!}{(b - a)!a!} p^{a} (1 - p)^{b - a} = 1\\ \end{align} \)


\( \begin{align} \therefore Var[X] &= n(n-1)p^2 - n^2p^2 + np = np - np^2\\ &= np(1 - p) \end{align} \)

'Probability' 카테고리의 다른 글

Families of Continuous Random Variables  (0) 2017.04.19

Android studio 2.2 버전부터 NDK가 정식으로 지원되기 시작하면서 기존보다 훨씬 더 간단하게 NDK를 이용하여 네이티브 어플리케이션을 작성할 수 있게 되었습니다. 연구실에서 간단한 사용법을 작성하라고 해서 3달 전에 작성했는데, 요즘 바빠서 글쓸 시간도 없고 이거라도 올리자는 생각이 들어서 그 문서를 여기에 올립니다. 이 글에서는 NDK를 이용하는 것에 초점을 맞췄기 때문에 아주 간단한 앱을 만들어 보겠습니다.

Pre – Condition

1.    Android studio 2.2이상이어야 함. (2.1 이하 버전에서도 가능하지만 2.2 버전에서는 NDK가 정식으로 포함되어 매우 편리함.)

2.    SDK Tool이 설치되어 있어야함.

 

1.     Android studio 2.1 이하 버전을 사용하고 있다면 Android studio 2.2 버전으로 업그레이드를 한다.


[Help] – [Check for Updates] Click하여 Android 2.2 버전으로 업그레이드.

2.     SDK tool을 설치한다.

2-1. SDK manager를 연다.


2-1-(a). 프로젝트 화면에서 [Tool] – [Android] – [SDK Manager]를 클릭 하면 SDK manager가 열린다.


2-1-(b). 위의 빨간 박스 아이콘을 누르면 SDK manager가 열린다.


2-1-(c). [File] – [Close Project]를 누르면 나오는 Welcome 화면에서 [Configure] – [SDK Manager]를 누르면 SDK Manager가 나온다.

 

2-2. SDK tools를 클릭하고 오른쪽 하단의 Show Package Details 체크 박스를 체크한다.


2-3. 필요한 Tool들인 LLDB, CMake, NDK를 체크한다.



 

NDK – JNI Test

1.     Welcome 화면에서 [Start a new Android Studio project]를 누르거나 프로젝트 화면에서 [File] – [New] – [New project]를 눌러 project 마법사를 실행한다.

2.     Project 마법사를 실행하면 Android 2.2 Version에서는 2.1까지는 없던 Include C++ Support라는 체크박스가 생긴다. NDK Test 할 것이므로 체크를 하고 Next를 누른다.


3.     다음 화면에서 만들려는 앱의 플랫폼을 선택하고 minimum SDK를 선택한 후 Next를 누른다.


4.     [Empty Activity]를 선택하고 Next를 누른다. Add No Activity를 선택하면 기본 layout도 추가가 안돼서 직접 추가해야 하니 귀찮다. Basic Activity는 별로 원하지 않는 메뉴바 등이 추가되니 Empty Activity를 선택한다.


5.     Activity 이름과 Layout 이름을 입력하고 다음을 누른다.


6.     [Include C++ Support]를 체크 했으므로 Customize C++ support 화면이 나온다. 그냥 Finish를 누른다. 그러면 프로젝트가 만들어진다.

a.    C++ Standard : 사용할 C 언어의 표준을 정할 수 있다. 드롭 다운 메뉴를 보면 Toolchain Default가 있고, C++11을 선택할 수 있다. 만약 cpp에서 Smart pointerThread Class C++11에서 지원하는 기능을 사용하거나 사용할 예정이라면 C++11을 선택하고 아니라면 Toolchain Default를 사용한자. Toolchain Default를 선택하면 default CMake setting을 사용한다. Default로는 32-bit, ARM-based GCC 4.8 toolchain을 사용하는데, Processor Architecture 마다 사용할 Toolchain을 선택할 수 있다. 자세한 내용은 Standalone Toolchain을 참고하면 좋다.

b.    Exception Support : 이 박스를 check하면 C++ exception handling이 가능하다. 이 박스를 체크하면 Android Studiomodule level(일반적으로 [app] level)build.gradle파일에 cmake cppFlags-fexceptions라는 속성을 추가하고 Gradle Cmake에게 저 속성값들을 전달한다.

c.     Runtime Type Information Support: RTTI를 사용하고 싶으면 이 checkbox를 체크한다. 체크 안하고 나중에 필요하다 싶으면 moule level build.gradle파일에 cmake cppFlags-frtti라는 속성을 추가하면 된다. (RTTI는 프로그램 실행중에 클래스의 정확한 타입을 알아내는 기능이다. C++dynamic_cast 등에서 사용된다. RTTI에 대한 자세한 정보를 알고 싶다면 RTTI를 참고하면 좋다.


7.     나오는 프로젝트를 보면 Hello from C++textBox에 출력되는 기능이 구현되어 있는것을 볼수 있다. 이제부터 원하는 기능을 C++로 구현하여 사용하는 방법에 대한 설명이다. 이 예제에서는 간단한 피보나치 함수를 만들어 테스트한다. 왼쪽의 Project Pane에서 app을 클릭하면 일반적인 안드로이드 프로젝트와는 다르게 cpp폴더가 보일 것이다. cpp폴더를 클릭하고 마우스 오른쪽 클릭 [New] – [C/C++ Source File]을 클릭하면 New C/C++ Source File 창이 열린다.



 

8.     Cpp file의 이름을 입력하고 OK를 눌러준다. 아래의 Type의 드롭 다운 메뉴에서 cppheader 중 선택 할 수 있으며, Create an associated header check box를 체크하면 header도 자동으로 만들어진다.


9.     아직 sync가 되지 않았으므로 왼쪽 android project pane에서는 보이지 않는다. 왼쪽의 project paneProject로 바꿔준다.



 

10.  Project로 바꾼 다음 [app] – [src] – [main] – [cpp]를 보면 pibonacci.cpp가 추가된 것을 볼 수 있다. 이제 이 cpp파일을 cmake에게 알려줘야 한다. [app] – [CMakeLists.txt]를 더블 클릭한다.



 

11.  내용을 보면 cmake_minimum_required(VERSION 3.4.1)이 있는데 말 그대로 cmake의 최소 버전을 명시하는 것이다. 우리가 주목할 부분은 add_library() 부분이다. Add_library의 사용법은 다음과 같다.

add_library(<name> [STATIC | SHARED | MODULE]

            [EXCLUDE_FROM_ALL]

            source1 [source2 ...])

<name>에는 library의 이름을 정해주고 두번째 인자는 shared library로 할 지 static library로 할지 정해준다. 두번째 인자는 생략해도 상관이 없다. 그리게 세번째 인자는 source의 경로를 적어준다. CmakeLists.txt 파일이 있는 위치를 기준으로 상대경로로 작성하는 것에 유의 한다. 이 예제 같은 경우에는 add_library 부분을 다음과 같이 수정해준다.

add_library( # Specifies the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp src/main/cpp/pibonacci.cpp)

 

만약 만든 cpp source를 새로운 라이브러리로 만들고 싶으면 다음과 같이 libraryadd_library 함수를 이용하여 추가해주면 된다. 예를 들면 다음과 같다.

add_library(pibonacci-lib SHARED src/main/cpp/pibonacci.cpp)

 

대신에 나중에 자바에서 사용할 때 해당 라이브러리를 System. Loadlibrary()를 이용하여 다음과 같이 해당 라이브러리를 load해줘야 한다.


Add_library 함수에 대하여 더 자세히 알고 싶으면 링크를 클릭한다.


 

12.  CMakeLists.txt 수정을 완료했으면 [Build] – [Refresh Linked C++ Projects]를 누른다.


13.  Project PaneAndroid로 바꾼 다음 [app] – [cpp]를 보면 pibonacci.cpp가 생긴 것을 볼 수 있다.



 

14.  이제 자바에서 native method를 정의해줘야 한다. MainActivity.java에 정의 할 수도 있지만 개인적으로 MainActivity에 이런 저런 method를 정의하는 것을 좋아하지 않기때문에 Pibonacci라는 class를 따로 만들기로 했다. 다음과 같이 자바의 패키지 디렉토리에 Pibonacci라는 자바 클래스를 만들어준다. (androidTest) (test)가 아닌 아무것도 안 써져 있는 디렉토리에 만들어 준다.



15.  다음과 같이 작성을 해준다.

public class Pibonacci {
   
public native static int pibonacci(int input);

   
public static String getPibonacci(int input){
       
return String.valueOf(pibonacci(input));
    }
}

Static을 붙인 이유는 MainActivity에서 객체를 만들지 않고 함수를 콜해주기 위해서 이다. 객체를 만들어서 사용할 거면 static은 생략해도 좋다.

여기서 public native static int pibonacci (int input) 에서 native가 중요한데 저 native는 이 함수가 JNI 함수라는 것을 알려준다. (함수에 대한 자세한 설명은 생략.)

현 상태에서는 pibonacci가 빨간색으로 표시된다. 아직 저 함수를 pibonacci 함수로 구현을 하지 않았기 때문이다. 이제 아까의 pibonacci.cpp로 가서 JNI 함수를 작성해준다. JNI 함수명을 작성할 때는 규칙이 있다.

 Java_Package Name_ClassName_functionName 이다. 패키지 이름의 .은 언더바(_)로 대체해서 써준다.

이 프로젝트의 경우는 Java_com_example_medialab_ndktest_Pibonacci_pibonacci 이다.

헷갈리는 경우는 빨간색 글씨 위에 마우스를 살포시 얹으면 다음과 같이 있어야 할 JNI function name과 함께 오류 메시지가 뜬다. 이제 cpp function을 작성하자.



 

16.  이제 pibonacci 함수를 다음과 같이 구현하면 된다.

#include <jni.h>

extern "C"
jint Java_com_example_medialab_ndktest_Pibonacci_pibonacci(JNIEnv *env, jobject callingObj, jint input){
   
if(input<=1) return input;
   
return Java_com_example_medialab_ndktest_Pibonacci_pibonacci(env,callingObj,input-1)
    +Java_com_example_medialab_ndktest_Pibonacci_pibonacci(env,callingObj,input-
2);
}

함수명을 왜 저렇게 작성하는 지는 전 단계에서 설명을 했다. 여기서 주목해야 할 것은 jint인데 여기서 int 대신에 jint를 써야 함수가 제대로 작동한다. Jint Java Native Method (JNI)의 데이터 타입이다. JNI는 다른 언어로 작성된 코드를 자바에서 호출할 수 있도록 만들어진 규약이다. pibonacci 함수는 Java에서 호출할 용도로 제작하는 것이므로 각 입력 parameterreturn 값은 JNI 데이터 타입을 이용해야 한다. (JNI Data Type에 대해서는 링크를 참조함다.) 함수 안에서는 C/C++ data type을 이용해도 되고, 만약 JAVA에서 호출되지 않고 같은 C/C++함수끼리만 호출되는 함수는 입출력 데이터 타입을 JNI DataType으로 하지 않아도 된다. 대신 입출력이 JNI DataType이 아닌 함수들은 Java에서 사용은 불가하고 같은 C/C++로 구현된 함수에서만 호출이 가능하다. 즉 위의 코드를 다음과 같이 작성해도 완전 똑같이 아주 잘 작동이 된다.

#include <jni.h>

int pib(int input){
   
if(input<=1) return input;
   
return pib(input-1)+pib(input-2);
}

extern "C"
jint Java_com_example_medialab_ndktest_Pibonacci_pibonacci(JNIEnv *env, jobject callingObj, jint input){
   
jint returnVal = pib(input);
   
return returnVal;
}


extern “C”라고 jni를 쓰는 함수 앞에 붙여줘야 한다. 필요시 Block을 지정하여 사용해도 된다. Extern “C”를 붙이는 이유는 우리가 C#같은 언어에서 C/C++ dll 이나 static library를 사용할 때 extern “C”를 붙여주는 것과 같은 이치인 것 같다. 아니면 JNICbase로 해서일 수도 있다. 보통 extern의 의미는 분류를 할 때 external linkage를 갖게 하기 위해서다. , 외부 모듈이 링크를 할 수 있게 해주기 위해서다. 하지만 extern “C”를 하면 name mangling을 하지 않기 때문에 다형성 구현에 문제가 생기는데 이 점에 대해서는 더 조사할 필요가 있다. 어쨌든 다음은 JNI에서 C/C++을 구현하는 각각의 예이다.

Code Example 2-1 Implementing a Native Method Using C

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */

{

     /* Obtain a C-copy of the Java string */

     const char *str = (*env)->GetStringUTFChars(env, s, 0);

 

     /* process the string */

     ...

 

     /* Now we are done with str */

     (*env)->ReleaseStringUTFChars(env, s, str);

 

     return ...

}

 

 

Code Example 2-2 Implementing a Native Method Using C++

 

extern "C" /* specify the C calling convention */  

 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

 

     JNIEnv *env,        /* interface pointer */

 

     jobject obj,        /* "this" pointer */

 

     jint i,             /* argument #1 */

 

     jstring s)          /* argument #2 */

 

{

 

     const char *str = env->GetStringUTFChars(s, 0);

 

     ...

 

     env->ReleaseStringUTFChars(s, str);

 

     return ...

 

}

JNIEnvJNI interface pointer로써 여러가지 유용한 변환 함수들을 제공한다. 또한 그 다음 오는 jobject 변수는 C/C++에서 this로 생각하면 된다. Python class에서 this를 위해 함수의 parameterself를 넘기는 것과 같다. JNI에 대한 자세한 내용은 링크를 참조한다.


 

17.  이제 MainActivity로 온다. 우리는 native-lib source 추가를 했으므로 따로 더 libraryload할 필요가 없다. 다음과 같이 코드를 작성한다. 그리고 [Run] – [Run app]을 누른다.

public class MainActivity extends AppCompatActivity {

   
// Used to load the 'native-lib' library on application startup.
   
static {
        System.loadLibrary(
"native-lib");
    }

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);

       
// Example of a call to a native method
       
TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(
"Pibonacci 10 : "+Pibonacci.getPibonacci(10));
    }

   
/**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
   
public native String stringFromJNI();
}

18.  결과 화면


이것 저것 하느라 블로그에 글 쓸 시간이 없었는데, 오늘 시간 내서 써봅니다.

먼저 저번 포스팅에서 데이터 수집과 라벨링까지 했었는데요. 이번에는 수집한 데이터를 가지고 custom model을 만들어 봅시다.

caffe는 설치했다고 가정하겠습니다. caffe 설치는 caffe home page 가면 자세히 나와있으니, 보고 하시면 됩니다.


1. Custom CNN을 만들자!


흠. 말 그대로 일단 Custom CNN model을 만들어야 하니 학습시킬 CNN 구조를 만들어줘야 합니다. Custom CNN 구조를 만드는 방법은 Caffe Tutorial에 자세히 나와있습니다. 하지만 이 포스팅에서는 귀찮으니 bvlc_reference_caffenet의 모델을 사용하도록 하겠습니다. caffe/models/bvlc_reference_caffenet directory에 들어가보면 deploy.prototxt, solver.prototxt, train_val.prototxt 파일이 있을겁니다. 만약 없으신분은 caffe github에서 다운받으시면 됩니다. 그럼 이 세개의 파일을 복사해서 자신이 원하는 폴더에 붙여넣기를 합니다. 저같은 경우는 caffe/models directory에 model_test_5라는 directory를 만들어서 붙여넣기를 하였습니다. 여기서 우리가 수정해줘야 할 파일은 solver.prototxt, train_val.prototxt 입니다. 먼저 train_val.prototxt를 살펴 보겠습니다.

빨간색으로 박스를 쳐 놓은 곳이 우리가 수집한 이미지로 만든 학습 데이터 경로입니다. 물론 아직 수집한 이미지를 데이터화 시키진 않았지만 수집한 이미지를 데이터화 시키는 작업은 Custom CNN의 구조와 밀접한 관계가 있으므로 train_val.prototxt 분석 먼저 하고 설명하도록 하겠습니다. (물론 진정한 CNN custom model을 만드는 작업을 하려면 이 CNN 구조도 스스로 만들어야겠죠. 하지만 여기서 그런것까지 일일히 설명하기에는 CNN에 대한 설명까지 나와야 하기 때문에 생략하도록 하겠습니다.) 

일단 위 그림에서 보면 data layer가 두개가 있죠.  위에 있는 data layer는 train data layer이고 아래에 있는 data layer는 valid data layer입니다. 빨간 박스는 위에서부터 순서대로 image mean data, train data, image mean data, valid data 입니다. image mean 값은 train data나 valid data나 같으므로 두 레이어에 모두 같은 값을 넣어주고, train layer에는 train data를, valid layer에서는 valid data를 사용하겠다고 각각 경로를 설정해 준 것입니다. 물론 이 파일들은 아직 만들어지지 않았습니다. 우리가 저렇게 설정을 했으니 train data와 valid data, image mean data를 각각 저 경로에 만들어 줘야겠죠? 그리고 각각 layer에 data param을 보면 batch_size라는 parameter가 있는데, 저 값이 의미하는 바는 한번에 처리하려는 input의 갯수입니다. 또 backend는 data layer source로 LEVELDB를 사용할지 LMDB를 사용할지 선택하는 것입니다. Default값은 LEVELDB입니다. 우리는 LMDB를 사용할 것이므로 저렇게 설정되어 있습니다. 흠... 하나하나 설명하려니 점점 설명해야 하는게 늘어가는 군요. 직접 CNN을 만드실려면 Caffe Tutorial을 참조하세요. 나중에 시간나면 하나하나 자세히 설명하는 시간을 가지도록 하겠습니다.(언제가 될지는 확실하지 않습니다.) 어쨋든 우리는 caffenet model을 그대로 사용할 것이기 때문에 그냥 mean_file과 source 값만 바꿔줍시다. 

마지막으로 여기서 한가지 더 짚고 넘어갈 부분이 있습니다. train_val.prototxt의 마지막 InnerProduct layer를 보면 output number가 1000으로 되어 있는데 이는 이 CNN은 imagenet의 data set에 맞춰서 설계되었고, imagenet data set의 class가 1000개이기 때문입니다. 우리가 수집한 데이터의 class가 몇개인지는 모르겠지만 어차피 나중에 synset_words.txt만 수정해주면 되므로 그냥 pass합시다. 자신이 학습시킬 모델의 class 갯수에 맞추어 CNN을 디자인 하는 것이 옳지만 저 output number 값을 바꿀거면 그냥 CNN을 직접 설계하는게 나을듯 합니다. 그대로 써도 어느정도 성능은 나옵니다. (물론 over fitting이 될 확률이 있지만...)

deploy.prototxt는 바꿀 필요가 없습니다. 당연히 deploy.prototxt도 output이 1000개 입니다. (train_val.prototxt는 학습을 시키기 위한 구조고, deply.prototxt는 학습이 완료된 모델을 이용하여 classifying 할때 쓰는 구조입니다.)

이제 solver.prototxt를 봅시다. solver.prototxt는 두 곳만 수정하면 됩니다.

먼저 net은 학습시킬때 사용할 CNN의 경로를 적어주면 됩니다. 바로 금방 전까지 다룬 파일의 경로를 위와 같이 적어줍니다. snapshot_prefix는 중간 중간 학습시킨 모델들을 저장할때, 어디에 앞에 어떤 이름으로 저장할지 설정하는 것입니다. snapshot 이 10000으로 설정되어 있는데, 이는 iteration이 10000번 돌때마다 그때까지 학습시킨 모델을 저장하겠다는 것입니다. max_iter은 말 그대로 iteration의 최대값입니다. 즉, iteration이 450000을 돌면 학습을 그만 두겠다는 거죠. 이 solver.prototxt file을 가지고 학습을 시키면 caffe/models/model_test_5 directory에 model5_train_iter_10000.caffemodel, model5_train_20000.caffemodel ~ model5_train_iter_450000.caffemodel이 저장되겠죠.(solverstate도 같이 저장됩니다.) solver_mode는 gpu로 연산하겠다는 것입니다. cpu로 하면 엄청 오래 걸리니까 gpu로 하시는게 좋습니다.



2. 수집한 Image data를 이용해 LMDB를 만들자!


이제 우리가 수정한 CNN에 학습시키기 위해 수집한 image를 LMDB 파일로 만들어야 합니다. 앞선 train_val.prototxt에서 data layer의 layer type이 data고, backend값이 LMDB이기 때문입니다. 물론 Data layer의 layer type을 ImageData로 설정하고 source를 학습시킬 image의 filename이 적혀있는 text file로 설정을 했으면 이 단계가 필요 없지만, 그러지 않았기 때문에 image data들을 LMDB file로 만들어야 합니다. (나중에 시간이 나면 layer type들에 대하여 설명해 드리도록 하죠. 궁금하신 분은 caffe layers를 보시기 바랍니다.)

다행스럽게도 caffe에서 create_imagenet.sh라는 파일을 제공해 주고 있습니다. 보통 caffe/examples/imagenet/create_imagenet.sh에 있으며 만약 없다면 BVLC/caffe github에서 다운받으시길 바랍니다. caffe/examples/imagenet directory를 보면 create_imagenet.sh, make_imagenet_mean.sh, train_caffenet.sh, resume_training.sh가 있습니다. create_imagenet.sh는 LMDB 파일을 만드는 bash file이고 만약 image를 resize하지 않았다면 이 파일을 통하여 resize도 할 수 있습니다. (정확히 말하면 convert_imageset이 하는거지만 그냥 각 bash file의 기능만 말하겠습니다.) make_imagenet_mean.sh는 말 그대로 image의 평균값을 계산해 주는 파일입니다. 마지막으로 train_caffenet.sh는 CNN을 training시키는 기능을 합니다. 저는 이 세 파일들을 caffe/examples/model_test_5라는 directory를 만들어 복사했으며, 이 전 포스팅에서 만든 train.txt와 val.txt는 caffe/data/model_test_5 directory에 넣어놨습니다. 그럼 create_imagenet.sh를 보겠습니다.

먼저 EXAMPLE 경로와 DATA 경로를 수정해야합니다. LMDB는 EXAMPLE 경로에 만들어지며, DATA는 train.txt와 val.txt가 있는 경로로 설정해 주면 됩니다. TOOLS는 convert_imageset이 있는 경로입니다. TRAIN_DATA_ROOT와 VAL_DATA_ROOT 값도 살포시 변경해줍니다. train.txt에 라벨링한 학습할 이미지 경로는 n0/n_*.JPEG로 작성했었습니다. 저는 train image 파일들을 caffe/imageSet/train2에 넣었으므로 이 이미지 파일들을 제대로 참조하려면 TRAIN_DATA_ROOT값을 imageSet/train2/로 설정해줘야 n0/n_*.JPEG와 합쳐져 온전한 경로가 될것입니다. (절대 경로로 써도 당연히 됩니다.) RESIZE는 256*256 size로 resize 할 것인가 설정하는 것인데, 현재 제가 모은 image들은 크기가 제각각이므로 이 값을 true로 설정했습니다. default는 false로 되어있습니다. image들을 어떤값을 어떤 순서로 데이터화 시키느냐에 따라서 모델의 성능이 은근히 달라지기 때문에 BVLC에서는 각자 알아서 data 변환 하는것을 추천하는 편입니다. 하지만 여기선 그냥 resize합니다. 마지막으로 $EXAMPLE/model5_train_lmdb와 $EXAMPLE/model5_val_lmdb로 값을 변경해줬습니다. 저는 train_val.prototxt에서 위와 같은 directory로 경로를 설정했기 때문입니다. --shuffle은 학습시킬 이미지들을 섞겠다는 건데, train.txt 작성할때 그냥 순서대로 써서 --shuffle은 그냥 놔두는게 좋습니다. train.txt 와 val.txt 작성할 때 shuffle을 했다면 저 option은 지워도 상관 없습니다. (shuffle을 안해주면 학습이 제대로 안될수도 있습니다. 이게 무엇인지 계속 학습시키다가 그다음 이게 무엇인지 계속 학습을 시키면 어느 한 class에만 over fitting 될 수가 있기 때문입니다. 때문에 그 다음에 학습시키는 class들이 제대로 학습되지 않을 수 있습니다.)

make_imagenet_mean.sh에서도 EXAMPLE, DATA 경로를 바꿔주고, $EXAMPLE/ilsvrc12_train_lmdb값을 앞서 설정한 train_lmdb 경로로 바꿔주면 됩니다. 저 같은 경우는 EXAMPLE = examples/model_test_5, DATA = data/model_test_5, $EXAMPLE/ilsvrc12_train_lmdb -> $EXAMPLE/model5_train_lmdb가 되겠군요. 

train_caffenet.sh는 solver의 경로만 바꿔주면 됩니다. 저같은 경우는 --solver = models/bvlc_reference_caffenet/solver.prototxt -> --solver = models/model_test_5/solver.prototxt가 되겠군요.

이제 create_imagenet.sh, make_imagenet_mean.sh, train_caffenet.sh를 순서대로 실행시켜주시면 됩니다. 저는 이마저도 make_model.sh라는 파일을 만들어서 했습니다. 어쨋든 $bash examples/model_test_5/create_imagenet.sh를 하면 다음과 같이 LMDB 파일이 만들어 집니다.

/home/san/caffe/examples/model_test_5

create_imagenet.sh를 실행시킨 모습

잘못된 image file들이 몇개 있네요.

graphic memory 사용량


$bash examples/model_test_5/make_imagenet_mean.sh를 하면 mean값이 금방 계산됩니다. 마지막으로 train_caffenet.sh를 실행합니다.

train_caffenet.sh를 실행한 모습

graphic memory 사용량


이제 그냥 컴퓨터 켜놓고 다른거 하시면 됩니다. 제 컴퓨터 성능으로는 450000 iteration 도는데 36시간 정도 걸리더군요. 시간 없으신 분들은 solver.prototxt 에서 max_iter를 50000 정도만 해서 학습시키세요. class가 적으면 50000번만 돌아도 그럭저럭 학습이 됩니다. 어쨋든 학습이 완료되고나서 models/model_test_5에 가면 다음과 같이 학습된 caffemodel file들이 저장되어 있는 것을 확인할 수 있습니다. 그러면 이제 인터넷에서 이미지를 하나 다운받아서 정말 분류가 되나 확인해봅시다. 

먼저 output의 갯수가 1000이므로 output되었을때 출력될 word들도 1000 라인이어야 합니다. 저같은 경우는 총 12개의 class를 학습시켰는데, CNN은 caffenet의 구조를 그대로 사용했으므로 output node가 1000개가 되어 버렸습니다. 물론 학습시키지 않은 output이 나올리가 없지만 그래도 output이 1000개 이므로 word들도 1000로 맞춰줘야합니다. 저는 제가 학습시킨 class에 대해서는 직접쓰고 나머지는 synset_words.txt에서 복사 붙여넣기 해서 1000라인을 채웠습니다.

1번째 줄에서 12줄 까지는 제가 학습시킨 class들이기 때문에 직접 해당하는 node 번호에 맞춰서 출력할 text를 써주고 나머지는 아무렇게나 채웠습니다. 이제 분류기를 이용해서 image 분류를 해봅시다.

분류는 caffe/build/examples/cpp_classification.classification.bin file을 이용해서 합니다. 사용방법은 다음과 같습니다.


~/caffe$ ./build/exaples/cpp_classification/classification.bin deploy.prototxt network.caffemodel mean.binaryproto labels.txt img.jpg

제 경우는 다음과 같이 사용합니다.


~/caffe$ ./build/exaples/cpp_classification/classification.bin ./models/model_test_5/deply.prototxt 
             ./models/model_test_5/model5_train_iter_450000.caffemodel
             ./data/model_test_5/imagenet_mean.bynaryproto 
             ./data/model_test_5/sysset_words2.txt 
             ./imageSet/test/1.JPG

결과


뭐 그럭저럭 학습이 된거 같긴 하네요. 분류율을 높이기 위해서는 CNN을 잘 만들고, 학습 데이터도 잘 모으는 등 여러가지 인자들을 바꿔가며 좋은 모델을 만들어야 합니다. 언제가 될지 모르겠지만 내킬때 caffe tool에 대하여 자세히 이야기 하도록 하겠습니다. caffe tool에 대하여 자세히 설명하려면 어쩔 수 없이 CNN에 대하여 설명을 하면서 진행해야 겠네요. 이 포스팅이 도움되실 분들이 얼마나 있을지 모르겠지만 몇몇 분들에게라도 도움이 되길 바라면서 이번 포스팅은 여기까지입니다.

    얼마전에 Deep Learning 중 Convolutional Neural Network (CNN)에 대한 강의를 연다는 소식을 메일로 받게 되었습니다. 그런데 강의 내용 계획을 보니 Caffe 설치 방법이랑 Caffe 예제 실습이더군요. 처음에는 Caffe home page에만 가봐도 자세히 써져 있는 내용을 왜 강의를 할까 생각했습니다. 그러나 의외로 Caffe 사용방법에 대한 내용을 궁금해 하시는 분들이 있을 것 같아서 제가 해본 것 중 Caffe를 이용하여 CNN custom model 만드는 방법을 이 글에서는 소개하려고 합니다. 이 글에서는 DNN과 CNN에 대한 설명들은 안하고 정말 그냥 custom model 만드는 방법 중 한가지만 소개하려고 합니다. 뭐 CNN에 대한 내용이나, Neural Network에 대한 내용은 여기 저기 자세히 설명해 놓으신 블로그나 논문이 많으니 읽어보시기 바랍니다.



1. Training 시킬 데이터를 수집하자!


    먼저, CNN을 학습시키기 위해서는 많은 양의 image data가 필요합니다. 사실 이 작업 육체적으로나 정신적으로나 어마어마하게 힘듭니다. Custom model을 만들 필요가 없이 그저 실습을 해보실 생각이라면 ImageNet에서 데이터를 다운받는 것을 추천합니다. (저는 3년 전에 가입해서 기억이 가물가물 합니다만, 제 기억상으로는 연구소 mail이나 학교 mail로 연구용으로 사용한다는 것을 인증하면 바로 다운 받을 수 있었던 것 같습니다. image net data를 사용할 경우 data의 크기가 매우 크니 HDD 용량이 넉넉하지 않으시다면 용량을 확보하시고 다운 받으시기 바랍니다. 해외서버라서 좀 느릴 수도 있습니다.)

먼저 데이터를 모으는 방법은 크게 다음과 같이 2가지가 있습니다.


1. 직접 찍는다.

2. 웹에서 다운로드를 받는다.


    사실 위의 두 방법은 너무 당연한 이야기죠. 개인적으로 제가 custom model을 만들때는 직접 찍은 데이터를 적절하게 섞어줘야 인식할때 원하는 결과가 나왔습니다. 예를 들어 제가 다니는 학교 문은 웹상에 올라와 있는 일반적인 문들과는 조금 생김새가 다르기 때문에 웹에서 다운받은 데이터로만 하다가는 학습이 잘 안 될 수도 있습니다. (제가 다니는 학교 문은 image net data로 학습시킨 모델로 classification을 진행하면 금고로 나옵니다. 사실 image net data에는 1000개의 class만 있기 때문에 우리가 원하는 class가 없을 수도 있습니다. 예를들어 ILSVRC2012 data set에서 door 종류는 sliding door밖에 없고, elevator도 없죠. 우리가 custom model을 만드는 이유 중 하나이기도 합니다. 하지만 atm과 cat, vending machine같은 class는 있기 때문에 만약 training 시키고자 하는 class가 image net data set에 있다면 적절히 활용하는 것이 image data를 수집하는 과정에서 발생하는 stress를 줄여 줄 수 있을 것 같습니다.) 

    직접 image를 찍는 것도 힘든 일이지만 웹에서 다운로드 받는 것도 많은 시간이 듭니다. MNIST 데이터 셋은 60000장의 image data를 training set으로 사용하고 Image Net은 class당 1300장의 데이터를 사용 하죠. 한 class 당 1000장만 수집하고 class가 10개만 되도 10000장의 image를 수집해야 합니다. 때문에 저 같은 경우는 python을 이용하여 crawler를 만들어서 image를 수집했습니다. crawler를 이용해 image를 수집하니 연관없는 사진들도 많이 수집되었습니다. 몇몇 분들은 image에 존재하는 pixel값을 이용하여 필터링을 하실 수도 있겠지만, 사실 pixel값을 이용해서 필터링을 해도 연관없는 사진들은 여전히 많습니다. 이러한 연관없는 사진을 없애는 일은 사람이 직접 할 수 밖에 없습니다. 완벽하게 연관없는 사진을 코드로 필터링 할 수 있다면 training set을 이용하여 CNN을 학습시킬 이유도 없죠^^

    다음은 제가 image를 수집하기 위해 작성한 간단한 python code입니다. 정말 image 수집만을 목적으로 했기 때문에 매우 간단하게 만들었습니다. 아마 직접 작성하시는게 더 좋을 겁니다. 하지만 그냥 빨리 custom data set을 수집하고, 실습을 하고 싶으신 분들이 사용하거나, 참조하실 분들은 참조하시라고 올립니다.


#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import requests
import urllib
import urllib2
import os
from os.path import os
import html5lib
import codecs

count = 28
index = 1
max_count = 1000 #number that you want to download

imageDataDir = "F:/image/"
#imageDataDir = "/home/san/image/" #linux directory
querySet = ['door','현관문','cat','fire extinguisher','tiger'] #query for searching


def get_soup(url,header):
    return BeautifulSoup(urllib2.urlopen(urllib2.Request(url,headers=header)))

if not os.path.isdir(imageDataDir):
    os.mkdir(imageDataDir)
    
for query in querySet:
    image_naming = query #image name
    query= query.split()
    tempDirName = '_'.join(query)
    query='+'.join(query)
    isQueryKorean = False

    targetDir = imageDataDir+tempDirName

    try:
        if not os.path.isdir(targetDir):
            os.mkdir(targetDir)
    except:
        isQueryKorean = True
        image_naming="temp"
        targetDir = imageDataDir+"temp"
        dirNum=0
        while(True):
            tempTargetDir = targetDir+str(dirNum)
            dirNum+=1
            if not os.path.isdir(tempTargetDir):
                targetDir = tempTargetDir
                os.mkdir(targetDir)
                break
        
    index = 1 #init index
    
    for i in range(max_count/count):
        url="http://www.bing.com/images/search?q="+query+"&first="+str(index)+"&count="+str(count)+"&FORM=HDRSC2"
        index = index+count
        print url #print query url

        page = urllib.urlopen(url).read()
        soup = BeautifulSoup(page, 'html5lib')

        for img_temp in soup.find_all("a", "thumb"):
            img = img_temp.get('href')
            try:
                print img
                raw_img = urllib2.urlopen(img).read()
                cntr = len([i for i in os.listdir(targetDir) if image_naming in i]) + 1
                print cntr
                f = open(targetDir+"/" + image_naming + "_"+ str(cntr)+".jpg", 'wb')
                f.write(raw_img)
                f.close()
            except:
                print("fail to download")

    제가 주석쓰는걸 귀찮아 하고 python이 주로 사용하는 언어가 아니라서 이상한 점이 있더라도 양해 부탁드립니다. 먼저 query url을 bing을 사용한 이유는 bing source code 분석 결과, bing이 thumbnail이 아닌 원본 image 다운로드가 편했기 때문입니다. 구글로 검색하시고 싶으신 분은 google source code 보고 download 받으시면 됩니다. 개인적으로 google은  thumbnail은 받기 쉬운데 원본 source image 받기가 힘들더군요. (google에서 원본 image 받는 방법 아시는 분은 알려주시면 감사하겠습니다. 뭐 들리는 이야기로는 구글에 돈내면 한달에 1000 query인가 검색할 수 있다고 합니다. 확실하진 않습니다. selenium, mechanize 제외) max_count 변수는 다운로드할 image의 갯수입니다. 하지만 실제로 저 만큼 받아지진 않습니다. 제가 올린 코드에서는 jpg format만 받게 해놔서 jpg가 아닌 이미지들은 안받아지거든요. (가끔식 jpg가 아닌 파일도 받아지긴 합니다.) 때문에 필요한 image 갯수보다 넉넉하게 설정하시는게 좋습니다.

    모아진 데이터를 보면 cat같은 데이터는 noise 없이 잘 모아지는 편이나, fire extinguisher 같은 경우에는 noise가 껴있는 경우가 있습니다. class에 따라서 제대로 된 데이터가 별로 없는 경우도 있습니다. (ex. door sign, elevator 등)

    데이터 필터링을 하시면 됩니다. 이것 추가하고 저건 지우고 하는 방식으로 데이터 셋을 변경해가며 실험하는 것도 좋을거 같습니다. 이게 또 데이터가 많다보면 하기 귀찮을 때가 있는데 그냥 대충하세요^^. CNN을 학습시킬때 목적에 따라 좋은 데이터가 있고 좋지 않은 데이터가 있을 수 있습니다. 저 같은 경우는 일상생활 중에 찍은 사진을 분류하기 위해 모델을 만들었으므로 위 그림에서 fire extinguisher_12.jpg 같은 경우에는 좋은 데이터였습니다. 왜냐하면 2~6번 image와 같이 배경이 흰색인 경우는 일상생활에서 별로 접하기 힘들기 때문이죠. 이 부분도 개인의 목적에 맞게 데이터를 선별하시면 됩니다. (Image Net data set들도 download해서 보시면 이건 좀 이상한데 싶은 이미지들 섞여 있습니다. 필터링 하다가 귀찮았나 보죠?) 저같은 경우는 이 데이터에 제가 직접 찍은 사진들도 추가했습니다.

    이제 train set과 valid set을 분류해 주세요. 필요할 경우 test set도 마련하면 좋지만 필수 사항은 아닙니다. 저같은 경우는 train set의 라벨링을 먼저 진행 한다음에 train set 중에 valid 용으로 사용할 이미지들을 따로 선별했습니다.



2. Labeling을 하자!


    이렇게 데이터를 모았으면 이제 라벨링을 해야합니다. 솔직히 caffe에서는 file path명만 써주면 되서 그냥 listdir() 함수를 이용해서 train list 작성을 해도 됩니다만 저는 원본 파일은 놔두고 라벨링을 한 set을 따로 만들었습니다.


from os import listdir
from os.path import isfile, join
import os
from shutil import copyfile

dirSet = ['atm','door','door_sign','elevator','emergency_exit','extinguisher', 'outlet','trash_bin', 'vending_machine']
sourcePath = '/home/san/imageSet/'
destPath = '/home/san/caffe/imageSet/'

newDirName = 0

for targetDir in dirSet:
    onlyFiles = [f for f in listdir(sourcePath+targetDir) if isfile(join(sourcePath+targetDir),f))]

    newName = 0
    newTargetDir = destPath+'n'+str(newDirName)
    os.mkdir(newTargetDir)

    for name in onlyFiles:
        print ('copy '+sourcePath+targetDir+'/'+name)
        copyfile(sourcePath+targetDir+'/'+name,newTargetDir+'/'+str(newDirName)+'_'+str(newName)+'.JPEG')
        newName+=1
    newDirName+=1

    갑자기 linux로 바뀌었죠? 저는 그냥 이 작업부터 linux로 진행했습니다. (data 모으는 것은 오래 걸리기 때문에 linux로 하면 그 오랜 시간동안 카카오톡을 못하거든요^^) 일단 저는 위와 같이 python을 이용하여 labeling을 진행하였습니다. 라벨링을 하고 나서 val 폴더에 valid set을 선별하여 넣었습니다. 저 같은 경우는 그냥 30개 씩했는데 원하는 만큼씩 valid set을 선별하세요.

~/caffe/imageSet Directory의 모습

train set의 모습

valid set의 모습


라벨링을 하고 나면 train list가 적혀 있는 train.txt와 valid.txt를 작성해야 합니다. 저는 다음과 같이 작성하였습니다.

이것도 사람이 작성하기엔 힘들어서 있어서 code로 작성했습니다. 이 코드는 굳이 올릴 필요성을 못느끼겠네요. 파일 경로 옆에 써져있는 숫자들은 index입니다. 이 index는 몇번 output node로 해당 클래스를 학습시킬지 알려주는 것입니다. 저같은 경우는 n0 class(atm)는 0번, n1 class(door)는 1번으로 학습시켰습니다. 이것도 자신이 정하기 나름입니다. 하지만 아직 언급하진 않았지만 자신이 만든 CNN의 output node 숫자보다는 적어야 합니다. 또한 여러 클래스를 같은 index number로 학습시켜도 상관 없지만 그러면 그 클래스들 간에 구분이 되지 않으니 의미가 없겠죠? 

오늘 포스트는 여기까지입니다. 사실 저는 이렇게 학습 데이터를 모으는게 가장 힘들었습니다. 학습은 CNN구조를 바꾸거나, 여러가지 인자들을 변경한 다음 돌려놓고 30~40시간 동안 다른 일을 하면 되서 별로 안힘들었거든요. 어쨋든 다음 포스팅에서는 imagenet lmdb data만드는 것에서 부터 CNN training 시키는 것과 classification 하는 데까지 써보도록 하겠습니다.

오늘은 좀 뜬금없지만 엔디언에 대해서 잠시 포스팅을 하려고 합니다.

흠 먼저 엔디안이란 무엇이냐! 바로 연속된 숫자를 배열하는 방법인데요. 크게 빅 엔디안과 리틀 엔디안이 있습니다. 위키피디아 정의를 잠깐 보도록 하죠.

엔디언(Endianness)은 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대사을 배열하는 방법을 뜻하며, 바이트를 배열하는 방법을 특히 바이트 순서(Byte order)라 한다.

엔디언은 보통 큰 단위가 앞에 나오는 빅 엔디언(Big-endian)과 작은 단위가 앞에 나오는 리틀 엔디언(Little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 미들 엔디언(Middle-endian)이라 부르기도 한다.


출처: 위키피디아 엔디언

그림을 보니 이해가 되죠? 솔직히 우리가 일반적으로 쓰는 컴퓨터는 대부분 리틀 엔디언이랍니다. 그런데 이런 리틀 엔디언과 빅 엔디언이 어떤 차이가 있을까요? 먼저 빅 엔디언은 사람이 숫자를 읽고 쓰는 방법과 같기 때문에 디버깅과정에서 사람이 메모리의 값을 이해하기가 더 편하답니다. 반면에 리틀 엔디언은 메모리에 저장된 하위 바이트들을 컴퓨터가 떼어 내서 쓸때 별도의 연산과정이 없기 때문에 컴퓨터가 연산에 이점이 있죠. 다음 그림을 보면서 마저 이야기를 해 봅시다.

int형 포인터와 char포인터, short포인터를 이용해서 각각 같은 주소를 가리키게 만든 다음, intPtr[0]에 -20을 저장하였다고 가정하여 봅시다. 컴퓨터는 -값을 다룰때 보통 2의 보수로 저장하기 때문에 -20은 11111111 11111111 11111111 11101100 으로 변환하여 메모리에 저장하게 됩니다. 여기서 리틀 엔디안과 빅엔디안이 저장되는 방식을 표에서 한번 보죠. 리틀 엔디안은 바이트를 거꾸로 정렬해서 메모리에 집어넣고 빅엔디안은 우리가 보는 그대로 메모리에 집어넣죠? 그래서 빅 엔디안은 우리가 디버깅하며 메모리 값을 보기에 편한겁니다. 그러나 만약에 우리가 short변수로 -20을 떼어낸 다고 생각해 봅시다. 그렇다면 빅 엔디안은 shortPtr[1]을 떼어내야 -20이 정상적으로 출력 될테고, 리틀 엔디안은 그냥 short[10]를 떼어내야겠죠? 즉 리틀 엔디안은 주소의 시작값부터 스택에 넣어 읽어들이면 되지만 빅 엔디안은 저장되어 있는 범위의 맨 뒤에서부터 역방향으로 읽어야 한다는 이야기 입니다. 각각 장점과 단점이 존재하죠. 근데 사실 오늘날의 프로세서는 여러개의 바이트를 동시에 읽어들여 동시에 계산을 수행하기 때문에 두 엔디언 사이에 사실상 차이가 없다는 군요. 바이엔디언과 미들 엔디언이 또 있는데 간단하게 요약만 하고 넘어가도록 하겠습니다.

바이 엔디언: 빅 엔디언과 리틀 엔디언 둘 중 하나를 선택 할 수 있는 것.

미들 엔디언: 빅 엔디언과 리틀 엔디언 둘 다 사용하는 것.(예를 들면 32비트 정수가 2바이트 단위로는 빅 엔디언이고 그 안에서 1바이트 단위로는 리틀 엔디언인 경우)

다음은 이를 확인해 보는 c++코드 입니다. 여러분의 컴퓨터가 리틀 엔디언이라고 가정하였습니다. 만약에 두번째 줄이 위 그림의 빅엔디언 처럼 나오면 여러분의 컴퓨터는 빅 엔디언인 거겠죠^^

#include <iostream>
#include <bitset>
using namespace std;

int main() {
	int *intPtr = new int [1];
	short *shortPtr;
	char *charPtr;
	*intPtr = -20;
	shortPtr = (short*)intPtr;
	charPtr = (char*)intPtr;
	cout << "-20을 2의 보수로 표현: ";
	for (int i = 3; i >= 0; i--) {
		bitset<8> tempBit;
		tempBit = charPtr[i];
		cout << tempBit << " ";
	}
	cout << endl << "저장되어 있는 비트\t";
	for (int i = 0; i < sizeof(int); i++) {
		bitset<8> tempBit;
		tempBit = charPtr[i];
		cout << i << ":" <<tempBit << "\t";
	}
	cout << endl;
	cout << "int로 4byte를 읽었을때: "<<*intPtr<< endl;

	cout <<"shortPtr[0]: ";
	bitset<8> bitSet0(charPtr[0]);
	cout << bitSet0 << "\t";
	bitset<8> bitSet1(charPtr[1]);
	cout <<bitSet1 << "\t";
	cout << "shortPtr[1]: ";
	bitset<8> bitSet2(charPtr[2]);
	cout << bitSet2 << "\t";
	bitset<8> bitSet3(charPtr[3]);
	cout << bitSet3 << "\t";

	cout << endl << "short로 2byte씩 읽었을때: " << endl;
	for (int i = 0; i < sizeof(int) / sizeof(short); i++) {
		cout << i << "번째: " << shortPtr[i] << "\t";
	}
	cout << endl;

	return 0;
}


DSP 실험 2번째 주에는 행렬연산에 대하여 배웠습니다. 수업시간에는 그냥 포인터에 배열을 할당하여, 행렬을 만들고, transpose한 행렬을 하나 더 만들어 두 행렬을 곱하는 연산을 해보았는데요. 사실 처음에 프로그래밍 언어에 익숙해지고, 내용에 익숙해지기 위해서는 직접 만들어서 실행해 보는 것이 좋지만, 익숙해지고 나서도 직접 만들어 쓰는 것은 피곤한 일이기도 하고, (물론 완벽하게 코드를 쓰시는 분에게는 다른 이야기겠지만)프로그램의 에러 확률이 높아지는 원인이 되기도 하죠. (옛날에 자료구조를 배우고 난 직후 스택이랑 큐, 벡터 등 자료구조들을 일일히 직접 만들어 쓰던때가 생각나네요 ㅋㅋ)

서론이 길었네요 ㅎㅎ. 어쨋든 이 포스팅에는 이러한 매트릭스 연산 뿐만 아니라 선형연산에 대하여 많은 함수 및 클래스를 지원하는 라이브러리인 Eigen library에 대하여 소개를 하고자 합니다.

먼저 Eigen library를 쓰려면 Eigen library를 다운받아야 겠죠? 구글에 Eigen이라고 검색을 하거나 아래의 링크를 클릭하여 Eigen library를 받아줍니다.

Eigen Main Page

링크를 클릭하면 아래와 같은 화면이 나타날 겁니다.

캬 Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.

말 그래도 입니다. 그냥 선형대수학에 관한 것들은 거의 다 있죵 ㅎㅎ. 흠.. latest stable release가 현재로써는 3.2.5버젼이네요 ㅋㅋ Get it에서 library를 다운로드 해줍니다. 압축포맷이 여러가지가 있는데, 다 똑같은 겁니다. 따로 압축 프로그램이 안깔려 있다면 그냥 zip파일을 다운로드 받아서 원하는 곳에 압축을 풀어줍시다!(용량이 얼마 안돼서 금방 다운로드 될겁니다!)

압축푼 폴더를 보면

캬 이것저것 많이 있네요 ㅎㅎ cmake 어쩌고 저쩌고 써져있는 것들은 cmake로 라이브러리를 따로 만들때 사용되는 것들이랍니다. 뭐 지금 단계에선 알필요 없어요. 아, 그리고 eigen library는 따로 설치 같은게 필요없습니다. 그냥 간단하게 파일 경로만 인클루드 시켜주면 되요!

어쨋든... 다운로드 받아서 압축을 풀어주셨다면 이제 Eigen library를 사용할 준비가 다 된 것입니다. ㅋㅋ 이제 Eigen을 이용한 프로그램을 만들기 위해 Visual studio를 실행하도록 하죠!

즐거운 마음으로 콘솔 어플리케이션 프로젝트를 만들어 줍니다.

프로젝트를 클릭한 후 alt+enter 또는 우클릭 properties 또는 메뉴바 Project-properties클릭을 하여 속성창을 열어줍니다!

VC++ Directories에서 Include Directories 편집창을 열어줍니다. 직접 path쓰셔도 됩니다.


저같은 경우는 Downloads에 압축을 풀었습니다. eigen library 압축푼 폴더로 경로를 설정해줍니다. Select Forder누르고 ok ok!

그럼 이제 cpp파일에 Eigen library를 이용한 간단한 프로그램을 만들어 봅시다.


#include <iostream>
#include <Eigen/Dense>
#define PI 3.14159265359
using namespace Eigen;
using namespace std;

int main()
{
	MatrixXd m1 = MatrixXd(8, 8);
	MatrixXd m2, m3;

	//8*8행렬 m1에 값을 넣어줍니다.
	double* alpha = new double[8];
	for (int i = 0; i < 8; i++) {
		if (i == 0)
			alpha[i] = 1. / (2.*sqrt(2));
		else {
			alpha[i] = 0.5;
		}
	}

	for (int i = 0; i < 8; i++) {
		for (int j = 0; j < 8; j++) {
			m1(i,j) = alpha[i] * cos((PI*(2.*j + 1)*i) / 16);
		}
	}
	delete alpha;

	m2 = m1.transpose();//m2에 m1을 transpose한 값을 넣어줍니다.
	m3 = m1*m2;//m3에 m1과 m2를 곱한 값을 넣어줍니다.
	cout <<"m1"<<endl<< m1 << endl <<"m2"<<endl<< m2 << endl <<"m3"<<endl<< m3 << endl;//출력!
	return 0;
}

코드를 실행해보면!

m1과 m2와 m3가 출력된 것을 볼 수 있습니다. m3는 identity matrix이군요!(아주 작은 값들은 0이라고 봅시다 ㅎㅎ 1,2,3의 10의 -13승이니 0이라고 봐도 무방하죠.) 그렇다면 m1행렬을 A라 했을때, 

행렬 A는 orthogonal matrix(직교행렬)라고 볼 수 있겠군요!

Eigen library에 대한 자세한 documentation은 Eigen 페이지에 가면 있습니다.

오늘 포스팅은 여기까지 하겠습니다 ㅎㅎ

DSP 실험 첫번째 주에는 sin 곡선 그리기를 배웠는데요. 실험 내용은 간단했습니다. 그냥 c / c++에서 sin함수를 써서 데이터를 파일 출력으로 데이터를 저장한 다음 excel 외부데이터 가져오기를 통해 그래프를 그리고, 이를 통해 앨리어싱을 확인하는 실험을 했습니다.

하지만 이렇게 그냥 넘어가기는 아쉬우니 Microsoft Chart Controls과 C#을 이용하여 직접 앨리어싱을 확인하는 프로그램을 한번 짜보도록 하죠.

(Microsoft Chart controls는 VB와 C#으로 이용할 수 있습니다.)

일단 windows Forms Application에서 ms chart를 사용하려면 ms chart control을 설치해 줘야 합니다.

Microsoft.NET Framework 3.5용 Microsoft Chart Controls

위 링크를 클릭하여 설치를 하시거나 구글에 microsoft chart controls를 검색하여 chart controls를 설치해 줍니다.


1. 다운로드를 살포시 눌러줍니다.


2. MSChart.exe를 체크한 뒤 다음을 누르면 설치 파일이 다운로드 됩니다. 다운로드가 완료된 후 설치를 합니다. 뭐 체크하고 그런거 없으니 그냥 다음 다음 눌러주시면 설치가 완료됩니다.

이제 MSChart가 설치되었으니 확인해볼까요?


3. Visual Studio를 실행한 뒤, Windows Forms Application 프로젝트를 만들어 줍니다.


4. 툴박스의 Data탭을 보면 Chart control이 생긴것을 볼 수 있습니다.


흠 이제 프로그래밍을 해볼까요?

먼저 프로그램 시나리오는 이렇습니다.

프로그램 시나리오

1. 샘플링을 할 원신호의 진폭과 주파수, 그리고 위상을 입력받습니다.(출력 주기는 일일이 입력하기 귀찮으니 원신호의 10주기 정도를 출력해주기로 합시다.)

2. 샘플링 주파수를 입력받습니다.

3. 사용자가 실행 버튼을 누르면 차트컨트롤1에는 원신호를, 차트컨트롤2에 샘플링 결과값을 point로 출력을 합니다.

4. 사용자가 그래프 전환 버튼을 누를 때 마다 차트컨트롤2의 chart type을 point에서 spline으로, spline에서 point로 전환해줍니다.

(뭐 이건 작은 프로그램이니 클래스 다이어그램이랑 유즈케이스 등등은 할 필요가 없을거 같군요.)


그렇다면 어떠한 변수가 필요할까요?

일단 원신호의 진폭과 주파수, 그리고 위상을 입력받을 double형 변수 amp_origin, hz_origin, phase_origin이 필요하겠군요. 또 출력주기를 저장할 double형 변수 out_period와 샘플링 주파수를 입력받을 double형 변수 hz_sample 변수가 필요할 것 같습니다. 또, 차트1에 들어갈 수열을 저장하는 Series형 변수 series1과 차트2에 들어갈 수열을 저장하는 series2변수가 필요할거 같군요. 흠... 변수는 이 정도면 된것 같습니다.

그렇다면 이젠 메쏘드를 한번 생각해보도록하죠. 일단 실행버튼을 클릭했을때 실행되는 이벤트 메쏘드와 전환 버튼을 클릭했을때 실행될 이벤트 메쏘드가 필요하겠군요. 그 다음에는 원신호의 시리즈를 만들어서 차트에 넣어주는 makeSeries가 필요하겠군요. 또 차트2의 차트타입을 바꿔줄 changeChartType 메쏘드가 필요할 것 같습니다.


필요한 변수

double amp_origin, hz_origin, phase_origin, out_period, hz_sample;

Series series1, series2;

필요한 함수

void run_Click(object sender, EventArgs e); //run 버튼을 클릭했을때 실행되는 이벤트 메쏘드

void trans_Click(object sender, EventArgs e); //trans 버튼을 클릭했을때 실행되는 이벤트 메쏘드

void makeSeries(); //차트1과 2에 넣어줄 수열을 만들어주는 메쏘드

void changeChartType(); //차트2의 차트 타입을 바꿔줄 메쏘드

흠 다 된거 같군요! 이제 프로그램을 만들어봅시다!



어플리케이션 UI




Index

Control Type

Name

Text

Tab Index 

 1

 TextBox

 tb_amp_origin

 진폭

 0

 2

 TextBox

 tb_hz_origin

 주파수

 1

 3

 TextBox

 tb_phase_origin

 위상

 2

 4

 TextBox

 tb_hz_sample

 샘플링 주파수

 3

 5

 Button

 run

 실행

 4

 6

 Button

 trans

 전환

 5

 7

 Chart

 chart1

 

 6

 8

 Chart

 chart2

 

 7

저 같은 경우는 TextBox와 Label들의 font size를 20으로 설정하였습니다.

자 그렇다면 위의 표와 그림을 참고하여 toolbox에서 드래그하여 내려놓습니다. Label도 위 그림과 같이 text를 써서 배치해 주시고요.

제 코드를 그대로 쓰실 생각이면 위 표를 보고 Properties(속성)을 설정해주세요.

자 그럼 UI도 만들었으니 코딩을 해서 기능을 넣어 볼까요?



Code

Form1.cs에 가서 코딩을 합시다.

우리가 UI를 만들던 곳은 Form1.cs[Design]입니다. Form1.cs 하위 항목의 Form1을 클릭하면 Form1.cs 파일이 열립니다.

변수 선언


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//추가해 줍니다. 나머지는 이미 추가되어 있습니다.

namespace MyApp
{
    public partial class Form1 : Form
    {
        double amp_origin, hz_origin, phase_origin, out_period, hz_sample; //클래스에 멤버들을 선언해 줍시다.
        Series series1 = new Series();
        Series series2 = new Series();//series 변수를 선언하고 변수에 객체를 할당해 줍니다.
        double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.

        public Form1()
        {
            InitializeComponent();
        }

    }
}

자 변수들을 추가해 줍시다!

이제 makeSeries 메쏘드를 만들어 볼까요?


private void makeSeries()
        {
            chart1.Series.Clear();
            chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.

            series1.Points.Clear();
            series2.Points.Clear();//series안에 있는 값들을 지워줍니다.

            series1.ChartType = SeriesChartType.Spline;
            series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.

            out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.

            for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
            {
                series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
            {
                series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            chart1.Series.Add(series1);
            chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
        }

이제 changeChartType 메쏘드를 만들어봅시다.


private void changeChartType()
        {
            if (chart2.Series[0].ChartType == SeriesChartType.Point)
                chart2.Series[0].ChartType = SeriesChartType.Spline;
            else
                chart2.Series[0].ChartType = SeriesChartType.Point;
            //chart2의 series의 차트타입이 point면 spline으로 아니면 point로 바꿔줍니다.
        }

이제 실행버튼을 클릭했을때 실행되는 이벤트 메쏘드를 만들어봅시다.

실행버튼을 더블클릭하면 자동으로 Form1.Designer.cs에 this.run.Click += new System.EventHandler(this.run_Click); 라고 이벤트 헨들러가 만들어지고 Form1.cs에 메쏘드가 만들어집니다.

직접 코드를 쓰실분은 Form1.Designer.cs에 this.run.Click += new System.EventHandler(this.run_Click);을 쓰고, Form1.cs에 메쏘드를 직접 작성하시면 됩니다.


private void run_Click(object sender, EventArgs e)
        {
            amp_origin = Double.Parse(tb_amp_origin.Text.ToString());
            hz_origin = Double.Parse(tb_hz_origin.Text.ToString());
            phase_origin = Double.Parse(tb_phase_origin.Text.ToString());
            hz_sample = Double.Parse(tb_hz_sample.Text.ToString());//text box로 입력받은 값을 멤버변수에 입력합니다.

            makeSeries();//makeSeries를 실행합니다.
        }

이제 전환버튼을 클릭했을때 실행되는 이벤트 메쏘드를 만들어봅시다.

전환버튼을 더블클릭하면 자동으로 Form1.Designer.cs에 this.trans.Click += new System.EventHandler(this.trans_Click); 라고 이벤트 헨들러가 만들어지고 Form1.cs에 메쏘드가 만들어집니다.


private void trans_Click(object sender, EventArgs e)
        {
            changeChartType();
        }

전체소스입니다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace MyApp
{
    public partial class Form1 : Form
    {
        double amp_origin, hz_origin, phase_origin, out_period, hz_sample;
        Series series1 = new Series();
        Series series2 = new Series();
        double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.

        private void trans_Click(object sender, EventArgs e)
        {
            changeChartType();
        }

        private void run_Click(object sender, EventArgs e)
        {
            amp_origin = Double.Parse(tb_amp_origin.Text.ToString());
            hz_origin = Double.Parse(tb_hz_origin.Text.ToString());
            phase_origin = Double.Parse(tb_phase_origin.Text.ToString());
            hz_sample = Double.Parse(tb_hz_sample.Text.ToString());//text box로 입력받은 값을 멤버변수에 입력합니다.

            makeSeries();//makeSeries를 실행합니다.
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void makeSeries()
        {
            chart1.Series.Clear();
            chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.

            series1.Points.Clear();
            series2.Points.Clear();//series안에 있는 값들을 지워줍니다.

            series1.ChartType = SeriesChartType.Spline;
            series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.

            out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.

            for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
            {
                series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
            {
                series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            chart1.Series.Add(series1);
            chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
        }

        private void changeChartType()
        {
            if (chart2.Series[0].ChartType == SeriesChartType.Point)
                chart2.Series[0].ChartType = SeriesChartType.Spline;
            else
                chart2.Series[0].ChartType = SeriesChartType.Point;
            //chart2의 series의 차트타입이 point면 spline으로 아니면 point로 바꿔줍니다.
        }
    }
}

자 이제 다 만들었으니 한번 실행을 해볼까요?

ctrl+F5를 눌러 실행을 해 봅시다.

정상적으로 실행이 되는 군요!


오버샘플링을 한번 해보겠습니다. 값을 입력합니다.


이제 실행을 누르면!

흠. 샘플링이 되긴 했는데, 잘 모르겠군요. 전환을 눌러 보겠습니다.

흠... 좀 다른거 같은 느낌도 들지만 자세히 보시면 같은 것을 볼 수 있습니다.

시작값과 끝값이 달라서 이런 현상이 일어나는데, 뭐 이런 현상을 해결하려면 chart1과 chart2의 x축을 설정해주면 됩니다.


            chart1.ChartAreas[0].AxisX.Maximum = out_period; //x축 max값 설정
            chart1.ChartAreas[0].AxisX.Minimum = 0; //x축 min값 설정
            chart1.ChartAreas[0].AxisX.IsStartedFromZero = true; //x축 0부터 시작

            chart2.ChartAreas[0].AxisX.Maximum = out_period;
            chart2.ChartAreas[0].AxisX.Minimum = 0;
            chart2.ChartAreas[0].AxisX.IsStartedFromZero = true;

아래는 수정된 전체 코드입니다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace MyApp
{
    public partial class Form1 : Form
    {
        double amp_origin, hz_origin, phase_origin, out_period, hz_sample;
        Series series1 = new Series();
        Series series2 = new Series();
        double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.

        private void trans_Click(object sender, EventArgs e)
        {
            changeChartType();
        }

        private void run_Click(object sender, EventArgs e)
        {
            amp_origin = Double.Parse(tb_amp_origin.Text.ToString());
            hz_origin = Double.Parse(tb_hz_origin.Text.ToString());
            phase_origin = Double.Parse(tb_phase_origin.Text.ToString());
            hz_sample = Double.Parse(tb_hz_sample.Text.ToString());//text box로 입력받은 값을 멤버변수에 입력합니다.

            makeSeries();//makeSeries를 실행합니다.
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void makeSeries()
        {
            chart1.Series.Clear();
            chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.

            series1.Points.Clear();
            series2.Points.Clear();//series안에 있는 값들을 지워줍니다.

            series1.ChartType = SeriesChartType.Spline;
            series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.

            out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.

            for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
            {
                series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
            {
                series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            chart1.ChartAreas[0].AxisX.Maximum = out_period;
            chart1.ChartAreas[0].AxisX.Minimum = 0;
            chart1.ChartAreas[0].AxisX.IsStartedFromZero = true;
            chart2.ChartAreas[0].AxisX.Maximum = out_period;
            chart2.ChartAreas[0].AxisX.Minimum = 0;
            chart2.ChartAreas[0].AxisX.IsStartedFromZero = true;

            chart1.Series.Add(series1);
            chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
        }

        private void changeChartType()
        {
            if (chart2.Series[0].ChartType == SeriesChartType.Point)
                chart2.Series[0].ChartType = SeriesChartType.Spline;
            else
                chart2.Series[0].ChartType = SeriesChartType.Point;
            //chart2의 series의 차트타입이 point면 spline으로 아니면 point로 바꿔줍니다.
        }
    }
}


AxisY의 maximum과 minimum도 같은 방식으로 설정할 수 있습니다. 뭐 차트 라이브러리의 모든 메쏘드를 설명할 순 없으니 넘어가도록 하죵 ㅎㅎ

뭐 메쏘드 이름을 보는 순간 어떤 기능을 하는 메쏘드 인지, 어떻게 사용하는지 직관적으로 알 수 있을 겁니다. ㅎㅎ

수정된 프로그램을 돌려보죠! ㅋㅋ

좀 나아졌군요ㅎㅎ

이제 다운샘플링을 한번 해보겠습니다.

한눈에 보기에도 제대로 된 신호가 안나올거 같군요. 전환을 눌러보겠습니다.

이런... 역시나 앨리어싱이 일어났군요 ㅠㅠ

앨리어싱이 일어난 신호를 분석해보면 한주기가 0.025초니까 40Hz군요. 원신호가 200Hz인데 40Hz가 되버렸군요.


이러한 현상이 일어나는 이유는 다음 그림과 같습니다.

샘플링을 하면 주파수 대역에서 신호와 샘플링 주파수가 길쌈연산(컨벌루젼)이 되죠. 그래서 오른쪽 그림과 같이 되어 40Hz가 나옵니다. 240Hz로 샘플링 해도 마찬가지로 40Hz로 나와 버리겠죠.

240Hz로 샘플링 한 그림

역시나 40Hz가 나오죠?

다음은 앨리어싱에 대한 그림입니다.

출처: Sampling Theoren url: http://cnx.org/contents/f65cf38c-c974-4a34-87b0-d4dc8863c79c@8/Sampling-Theorem

마지막으로 Series에 이름을 붙여서 사용할 수도 있습니다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace MyApp
{
    public partial class Form1 : Form
    {
        double amp_origin, hz_origin, phase_origin, out_period, hz_sample;
        Series series1 = new Series();
        Series series2 = new Series();
        double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.

        private void trans_Click(object sender, EventArgs e)
        {
            changeChartType();
        }

        private void run_Click(object sender, EventArgs e)
        {
            amp_origin = Double.Parse(tb_amp_origin.Text.ToString());
            hz_origin = Double.Parse(tb_hz_origin.Text.ToString());
            phase_origin = Double.Parse(tb_phase_origin.Text.ToString());
            hz_sample = Double.Parse(tb_hz_sample.Text.ToString());//text box로 입력받은 값을 멤버변수에 입력합니다.

            makeSeries();//makeSeries를 실행합니다.
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void makeSeries()
        {
            chart1.Series.Clear();
            chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.

            series1.Points.Clear();
            series2.Points.Clear();//series안에 있는 값들을 지워줍니다.

            series1.ChartType = SeriesChartType.Spline;
            series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.

            out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.

            for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
            {
                series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
            {
                series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
                //series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
            }

            chart1.ChartAreas[0].AxisX.Maximum = out_period;
            chart1.ChartAreas[0].AxisX.Minimum = 0;
            chart1.ChartAreas[0].AxisX.IsStartedFromZero = true;
            chart2.ChartAreas[0].AxisX.Maximum = out_period;
            chart2.ChartAreas[0].AxisX.Minimum = 0;
            chart2.ChartAreas[0].AxisX.IsStartedFromZero = true;

            series1.Name = "Original Signal";
            series2.Name = "Sampling Signal";//이름을 설정한다.

            chart1.Series.Add(series1);
            chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
        }

        private void changeChartType()
        {
            if (chart2.Series["Sampling Signal"].ChartType == SeriesChartType.Point)
                chart2.Series["Sampling Signal"].ChartType = SeriesChartType.Spline;
            else
                chart2.Series["Sampling Signal"].ChartType = SeriesChartType.Point;
            //chart2의 series의 차트타입이 point면 spline으로 아니면 point로 바꿔줍니다.
        }
    }
}

그럼 실행했을때 이렇게 되죠.

이렇게 하면 나중에 한 chart에 많은 Series를 넣을때 안 헷갈릴수 있습니다. 저 같은 경우는 어차피 시리즈가 하나니 그냥 디폴트로 하겠습니다. ㅋㅋ 나중에 시간남으시면 해보세요. 맨 처음 켰을때 시리즈 이름을 바꾼생태로 보이게 하려면 굳이 코드로 할 필요 없이 차트의 프로퍼티에서 시리즈를 클릭하여 시리즈 에디터를 실행한 다음 시리즈의 이름을 바꿔주면 됩니다.


왼쪽그림은 차트를 클릭했을때 속성 화면이고 왼쪽 화면에서 시리즈를 클릭한다음 ...버튼을 누르시면 Series Collection Editor를 볼 수 있습니다. Series Collection Editor의 왼쪽을 보시면 시리즈의 인덱스 번호와 이름이 보이죠? 바꾸고자 하는 시리즈를 클릭 한 뒤 오른쪽의 Data의 Name을 바꿔주시면 됩니다. 나중에 시간이 되면 ChartArea와 Series에 대하여 좀 더 자세히 설명하는 포스팅을 올리도록 하겠습니다.

흠 오늘 포스팅은 여기까지 입니다.

아래는 위의 파일들을 올려놓은 link 입니다.

첫번째 링크는 이 프로젝트를 올려놓은 Github링크고요.

두번째 링크는 이 프로젝트를 릴리즈한 실행파일과 프로젝트 압축파일을 올려놓은 구글 드라이브 링크입니다.

코딩을 안해보고 어플리케이션을 그냥 사용만 해보실 분은 구글드라이브 링크를 클릭하셔서 exe파일만 다운받아 실행해 보시면 될 거 같습니다.

아마 실행할때 exe파일을 윈도우 디펜더가 인증받은 앱이 아니라고 경고할 수도 있어요. 그럴땐 그냥 추가사항 누른다음 실행 누르시면 됩니다.(위험한 어플 아니니까요 ㅎㅎ)

주의! .NET Framwork 3.5이상이어야함!

뭐 윈도우 업데이트를 꺼놓으시지만 않으면 거의다 .NET Framework 3.5이상일 겁니다.

혹시 모르니 링크걸어 놓을게요.

.NET Framework

DSP_Ex_1 Github

San's Google Drive_DSP_Ex

'Electronics > DSP Experiment' 카테고리의 다른 글

Eigen c++ library를 이용한 Matrix연산  (0) 2015.09.22

이미 아시는 분들도 굉장히 많으시겠지만 혹시나 도움이 될까하여 올립니다. 
아마 visual studio 2010에서는 c코드들이 잘 컴파일이 되고 실행이 되나, visual studio 2012, 2013, 2015에서는 scanf나 fopen등이 컴파일이 되지 않아 고생하시는 분들도 있을거라 생각됩니다.

일단 그 이유부터 말씀드리자면 개발자들이 함수의 오류가 발견되면, 그 함수를 deprecate해버리고 보안버젼의 새로운 함수를 추가해 버리기 때문입니다. 
(ISO에서 c++11 표준을 정하니 ms에서 visual studio 2012부터 바꾼거죠) 
예를들어 scanf의 경우 보안상의 문제로 c++ 98에서 c++11으로 넘어가는 과정에서 deprecated되버리고 scanf_s이라는 보안버젼 함수가 새로 생겨버렸습니다. 
visual studio 2010에서는 잘 컴파일이 되는데 visual studio 2012이상 버젼에서는 정상적으로 컴파일이 안되는 이유는 visual studio 2010에서는 default로 c++98을 사용하고, visual studio 2012이상 버전부터는 c++ 11을 사용하기 때문입니다. 

즉, visual studio 2012 이상 버젼부터는 scanf를 사용하면 warning이 뜨면서 컴파일이 안됩니다. 

scanf는 최근 몇년동안 보안 문제로 말이 있었죠. 뭐 이유를 알아보면 여러가지가 있지만 콘솔 io의 경우 input size를 정해주지 않으면 command를 같이 입력하는 방식으로 공격이 가능하다는데, 제가 보안쪽을 잘 몰라서 뭐라고 말을 더 못하겠군요. 뭐 이부분에 대해선 다른 블로거 님들에게 패스를 하고, 본론으로 다시 돌아가도록 하겠습니다.


이렇게 코드를 쓰고 컴파일을 하면 보통 에러가 나며 컴파일이 안됩니다.

#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이렇게 하고 start하면 아래 그림과 같은 메세지가 뜨면서 컴파일이 되지 않죠.


에러 내용을 해석해보면, scanf라는 함수가 안전하지 않으며, scanf대신 scanf_s라는 새로운 함수를 사용하는 것을 고려해 보라고 나옵니다. 또, deprecation된 함수를 사용하려면 _CRT_SECURE_NO_WARNINGS를 사용하라고 나오네요.(deprecation을 사용하지 않으려면)


그러면 지금부터 이러한 문제를 해결하는 방법을 몇가지 말씀 드리려고 합니다.

1. _CRT_SECURE_NO_WARNINGS를 사용하여 warning을 무시한다.


#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이렇게 코드의 가장 윗부분에 _CRT_SECURE_NO_WARNINGS를 디파인 해주면 정상적으로 컴파일이 되는 것을 확인할 수 있습니다.

또는 에러 코드를 보면 C4996이라고 나오죠. 이 에러코드를 직접 무시하는 코드를 헤더 윗쪽에 추가 해서 정상적으로 컴파일을 하게 만드는 방법도 있습니다. 좀 자세히 말하자면 우리가 프로젝트를 만들때 SDL 체크를 사용하면 컴파일시 추가적으로 여러가지 보안 검사를 실시 하는데, 검사중에 경고 코드 C4996(사용되지 않는 함수 사용 경고)기능을 사용하지 않도록 설정하는 것입니다.


warning에 대한 링크 : warning-msdn

#pragma warning(disable: 4996)
#include <stdio.h>
int main()
{
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);

	return 0;
}

이런식으로요 ㅎㅎ.

pragma에 대해선 나중에 #if, #ifdef등과 함께 나중에 한번 포스팅 하도록 하겠습니다.

2. 대세에따라 secure 버젼의 새로운 함수를 사용한다.

fopen의 경우 주소를 리턴하는게 보안상 문제가 있다고 판명되어 주소를 리턴하지 않고 인자로 받아 설정하는 fopen_s라는 secure버젼의 함수가 새로 만들어졌습니다.

사실 어떠한 함수가 deprecated가 되었다는 것은 sw전문가들이 문제가 있다고 생각되어 deprecated를 시키고 보다 안전한 함수를 새로 만들었다는 것이기 때문에 대세를 따라 secure버젼을 사용하는 것이 좋다고 개인적으로 생각합니다.


#include <stdio.h>

int main()
{
	FILE *data;
	fopen_s(&data, "test.txt", "w");
	fprintf(data, "%s", "Hello world!");
	fclose(data);

	return 0;
}

요런식으로 씁니다. ㅎㅎ


#include <stdio.h>

int main()
{
	char s[16];
	scanf_s("%s", &s, 16);//마지막에 input data size를 적어줍니다.(이게 scanf와 비교하여 바뀐부분이죠^^)
	printf(s);

	return 0;
}

scanf_s의 경우는 이런식으로 씁니다.

뭐 이러한 정보들은 구글링하면 바로바로 나오니 그때 그때 사용법을 검색해서 쓰기 쉽습니다.

유용한 사이트

CPlusPlus Page

MSDN Page

3. Project 생성시 SDL(Security Development Lifecycle) checks 옵션을 체크 해제한다.

이걸 체크해제하고 프로젝트를 만들면 추가적인 오류검사를 수행하지 않습니다. 예를 들어 함수의 반환형을 int로 해놓고 아무것도 반환하지 않아도 컴파일이 되고 실행이 됩니다. 뭐 컴파일이야 되겠지만은 별로 권장하고 싶진 않네요.


#include <stdio.h>

int main() {
	int inputNum;
	scanf("%d", &inputNum);
	printf("%d \n", inputNum);
}

위와 같은 코드도 컴파일이 됩니다.

어쨋든 SDL chechs 해제를 한번 해보죠.

1. 프로젝트를 만들어 줍니다.


2. 다음을 클릭합니다.


3. SDL checks의 체크를 해제해 줍니다.


4. 위의 코드를 한번 실행해 봅니다.

아주 잘 실행이 되는군요 ㅎㅎ



SDL에 대한 /sdl 링크 하나 남기겠습니다.

MSDN SDL

뭐 오늘 포스팅은 여기까지입니다. 마지막 방법은 되도록 쓰지 마세요 ㅎㅎ

'Programming > C/C++' 카테고리의 다른 글

Endian(Little endian, Big endian)  (0) 2015.10.08

+ Recent posts