2019년 12월 22일 일요일

우분투 sources.list 복구

우분투 apt-get update 시 Error 문제가 발생할 경우, 우선 sources.list 를 복구할 필요가 있다.
다음 링크는 이와 관련된 것이다.

2019년 12월 11일 수요일

NVIDIA jetson nano 기반 LiDAR 만들기

이 글은 저렴한 RpLiDAR A1과 엔비디아 젯슨 나노를 이용해 이동형 스캐너를 개발하는 방법을 간단히 정리한다. RpLiDAR는 저렴한 2D 스캐너로 인터넷에서 99달러에 구매할 수 있다.

개발환경은 우분투 18.04와 ROS(설치)이다. 개발환경은 나노 개발자 설치 설명 사이트를 따라 다운로드하고 설치하면 된다(아래 링크 참고). 
참고로, 라즈베리파이나 아두이노를 이용한 방법은 아래 링크를 참고바란다.
  • RpLiDAR based on RaspberryPi and arduino
장치 연결
RpLiDAR를 엔비디아 나노의 USB포트와 연결한다. 

그리고, 다음과 같이 연결된 라이다를 확인해 본다. 
$ usb-devices

다음과 같은 부분을 발견하면, 엔비디아 나노와 제대로 연결된 것이다.
Product=CP2102 USB to UART Bridge Controller
If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=cp210x


설치
RpLiDAR SDK를 다음과 같이 다운로드하고 메이크한다.
$ git clone https://github.com/Slamtec/rplidar_sdk
$ cd rplidar_sdk/sdk
$ make

다음과 같이 ttyUSB 읽기 권한을 획득한다. 
sudo gedit 50-usb-serial.rules
ls -l /dev | grep ttyUSB

이제 빌드된 rp_lidar/output/Linux/Release 폴더 밑의 RpLidar 예제를 다음과 같이 실행해 본다.
$ sudo ./ultra_simple

다음 화면은 실행된 스캔 데이터 결과이다.

ROS node 소스를 다음과 같이 다운로드하여 빌드한다.
$ git clone https://github.com/Slamtec/rplidar_ros
$ cd ..
$ catkin_make

빌드가 정상적으로 진행되면, 터미널에서 roscore를 실행한 후, 다음과 같이 roslaunch를 실행한다. 
$ roslaunch rplidar_ros view_rplidar.launch

그럼, 다음과 같이 시각화된 스캔 데이터를 확인할 수 있다.

실행화면

레퍼런스

2019년 12월 4일 수요일

LiPO 배터리 충전 에러 문제

LiPO 베터리 충전시 BATTERY INVALID ERROR 란 에러가 발생하는 경우가 있다. 베터리는 여러개의 셀로 연결되어 있은 데, 이 경우, 특정 셀이 충전되기 어려울 만큼 과전압이거나 저전압일 때 발생한다. 

이 경우에는 다른 배터리 충전기를 사용해 보고, 안되면, 다음 방법을 사용해 본다.

2019년 12월 3일 화요일

NVIDIA JETSON NANO 배터리 전압 문제 솔류션

이 글은 NVIDIA JETSON NANO 배터리 파워 문제 솔류션과 관련된 내용이다.

엔비디아 젯슨 나노는 매우 저렴한 가격(10만원)으로 모바일 가능한 작은 크기의 딥러닝 솔류션을 지원한다. 전력 소모량이 적어 5V, 3-4A에서도 동작한다. 연결할 센서 등 장치가 많은 전류를 소모하면 4A이상 필요하다. 하지만, 나노는 정확한 전압 및 전류량에 동작하도록 설계되는 바람에 배터리 연결 시 제대로 부팅조차 되지 않는 등 문제가 있어, 많은 유저가 시행착오를 하고 있다(엔비디아 위키 페이지에도 정확한 솔류션은 나와있지 않다).

나노 파워 설정
우선 나노를 사용할 때 파워를 최대로 할 것인지, 5W만 사용할 것인지 설정해야 한다. 이 내용은 다음 영상에 잘나와 있다.
좀더 상세한 내용은 아래 링크 참고한다.
컨버터 사용 방법
배터리를 통해 나노를 동작시키고, 나노에 전력소모량이 많은 센서나 장치를 연결하려면, 최소 5V, 4A가 필요하다. 이 전압과 전류를 처리하기 위해 다음과 같이 DC-DC 전압강하(STEP DOWN) 컨버터(CONVERTER)가 필요하다.
DC-DC XL4016 STEP DOWN CONVERTER

DC-DC 회로는 큰 입력 전압(40-10V)를 다른 전압으로 변환해 주는 회로이다. 이 회로는 POWER REGULATOR가 포함되어 있다. 전압은 맞춰줄 수 있지만, 나노에 필요한 전류는 나노에서 소모하는 양에 따라 달라지므로, 시행착오가 필요하다.

이 회로는 입력 전압, 출력 전압 핀이 두개 양쪽에 있다. 오른쪽 하단에 작은 출력전압 나사 조절기가 있고, 이 나사를 조정해 실제 전압을 맞춰줘야 한다. 전압은 눈으로 확인할 수 없으므로, 전압과 전류를 측정할 수 있는 디지털 미터기가 있으면 편리하다. 본인의 경우, 전압은 5.63V, 전류는 0.024A 수준으로 설정하였다.
전압 조정
전류 조정

이를 이용해, 5V보다 약간 높은 전압(5.5-5.7V)를 맞춘 후, 배터리를 입력단자에 연결하고, 출력단자를 나노 POWER 입력에 연결한다. 만약 과전압이나 저전압을 나노에 제공하면, 부팅이 안될 것이다. 다시 조정하는 과정을 거쳐 적절한 전압을 찾는다.
배터리, 전압 변환 회로 및 나노 연결

이렇게 회로 출력전압 나사를 조정해 나노의 동작전압을 찾아낸다. 다음은 이렇게 출력전압을 조정한 후 나노에 RGBD 카메라를 연결해 동작시킨 모습이다. 배터리만 연결한 상태에서 3시간 이상 나노와 센서가 동작되는 것을 확인하였다.
나노 및 센서 정상 동작 확인

휴대용 배터리 사용 방법
휴대용 배터리는 5V, 4A 스펙을 찾기가 쉽지 않다. 3A수준이면 NANO자체로는 큰 문제 없이 동작하지만, 전력소모가 많은 장치를 구동하기에는 부족한 수준이다. 다음은 휴대용 배터리를 사용해 구동 테스트한 모습이다. 배터리 스펙은 5V, 3A출력이다.
 배터리 장착 모습
 ROS와 ZED 3D CAMERA 구동 모습
ZED 3D DEPTH MAP 구동 모습

ZED 3D 카메라는 전력소모가 많은 장치이다. 이 장치를 나노와 연결하고 테스트해보았을 때, 1시간 정도 지속적으로 나노가 구동되는 것을 확인할 수 있었다. 1시간 이후 재부팅현상이 발생하였는데, 나노에서 특정 상황이 발생하면 인터럽트가 걸리는 것으로 보여졌다.

2차 테스트는 나노 파워 설정을 5W로 맞추고 진행하였다. 이 경우, 배터리 용량까지 큰 문제없이 3~4시간 정도 나노가 실행되었다.

마무리
나노는 가성비가 높은 임베디드 보드이다. 하지만, 이와 같이 까다로운 요구조건이 있어 처음 사용시 고생을 한다. 참고로, 다음에 이외 해결 방법에 대한 글을 링크한다.

2019년 12월 2일 월요일

아나콘다 완전 삭제 방법

아나콘다는 손쉬운 패키지 설치 및 설정으로 많이 활용되고 있다. 하지만, 버전이 엉키면, 패키지를 빌드하고 설치할 때 의존성 문제가 발생하여 불편한 경우가 발생한다.

이 경우, 아나콘다를 완전히 삭제하고 재설치해야 한다. 다음은 이를 위한 방법이다.

rm -rf ~/anaconda3             # removes the entire anaconda directory
rm -rf ~/.anaconda_backup
sudo gedit ./bashrc             # PATH에서 anaconda 폴더 삭제

다른 방법은 다음과 같은 명령을 사용할 수도 있다.
conda install anaconda-clean
anaconda-clean --yes

상세 내용은 여기를 참고한다. 

Keras 를 이용한 대중교통 사용 데이터 이상 탐지

이 글은 Keras 를 이용한 대중교통 사용 데이터 이상 탐지 방법을 간략히 소개한다. 좀 더 상세한 내용은 레퍼런스를 참고한다.

딥러닝 도구는 Google CoLAB을 사용하겠다. 데이터는 다음 링크에서 다운로드 한다.
이상 데이터 패턴은 다음과 같다. 

데이터를 다운로드 받고, CoLAB에 업로드 한다.
from google.colab import files
uploaded = files.upload()
for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))

소스 코드는 다음과 같다.

1. pands를 이용해 데이터 프레임을 생성한다.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tqdm

from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.metrics import mean_squared_log_error, mean_squared_error
import re
import datetime

from keras.models import *
from keras.layers import *
from keras.layers.core import Lambda
from keras import backend as K

### READ DATA AND CREATE FEATURES FOR year, month, day, hour ###
df = pd.read_csv('nyc_taxi.csv')

date = pd.DataFrame(np.vstack(df.timestamp.apply(lambda x: re.sub(r':|-' ,' ', x).split()))[:,:4].astype(int))

df = pd.concat([df, date], axis=1)
df.columns = ['timestamp','value','yr','mt','d','H']
df.head()
print(df.shape)
df.head()

### PLOT SAMPLE OF DATA ###
#df.iloc[4000:4000+7*48,:].plot(y='value',x='timestamp',figsize=(8,6))
df.iloc[8400:8400+7*48,:].plot(y='value',x='timestamp',figsize=(8,6))
plt.xticks(rotation=70)



2. LSTM을 위한 학습용 데이터를 생성한다.
### WEEKLY AUTOCORR PLOT (10 WEEKS DEPTH) ###
timeLags = np.arange(1,10*48*7)
autoCorr = [df.value.autocorr(lag=dt) for dt in timeLags]

plt.figure(figsize=(19,8))
plt.plot(1.0/(48*7)*timeLags, autoCorr);
plt.xlabel('time lag [weeks]'); plt.ylabel('correlation coeff', fontsize=12);

### CREATE WEEKDAY FEATURE AND COMPUTE THE MEAN FOR WEEKDAYS AT EVERY HOURS ###
weekday = df[['yr', 'mt', 'd']].apply(lambda x: datetime.datetime(x['yr'], x['mt'], x['d']).weekday(),axis=1).values
# weekend = weekday.copy()
# weekend[np.logical_and(weekend != 5, weekend != 6)] = 0
# weekend[weekend != 0] = 1

df['weekday'] = weekday
df['weekday_hour'] = df.weekday.astype(str) +' '+ df.H.astype(str)
df['m_weekday'] = df.weekday_hour.replace(df[:5000].groupby('weekday_hour')['value'].mean().to_dict())

### CREATE GENERATOR FOR LSTM ###
sequence_length = 48

def gen_index(id_df, seq_length, seq_cols):
    data_matrix =  id_df[seq_cols]
    num_elements = data_matrix.shape[0]
    for start, stop in zip(range(0, num_elements-seq_length, 1), range(seq_length, num_elements, 1)):
        yield data_matrix[stop-sequence_length:stop].values.reshape((-1,len(seq_cols)))

### CREATE AND STANDARDIZE DATA FOR LSTM ### 
cnt, mean = [], []
for sequence in gen_index(df, sequence_length, ['value']):
    cnt.append(sequence)

for sequence in gen_index(df, sequence_length, ['m_weekday']):
    mean.append(sequence)

cnt, mean = np.log(cnt), np.log(mean)
cnt = cnt - mean
cnt.shape

### CREATE AND STANDARDIZE LABEL FOR LSTM ###
init = df.m_weekday[sequence_length:].apply(np.log).values
label = df.value[sequence_length:].apply(np.log).values - init
label.shape

### DEFINE QUANTILE LOSS ###
def q_loss(q,y,f):
    e = (y-f)
    return K.mean(K.maximum(q*e, (q-1)*e), axis=-1)

### TRAIN TEST SPLIT ###
X_train, X_test = cnt[:5000], cnt[5000:]
y_train, y_test = label[:5000], label[5000:]

3. LSTM 모델을 생성한다. ### CREATE MODEL ###
losses = [lambda y,f: q_loss(0.1,y,f), lambda y,f: q_loss(0.5,y,f), lambda y,f: q_loss(0.9,y,f)]

inputs = Input(shape=(X_train.shape[1], X_train.shape[2]))
lstm = Bidirectional(LSTM(64, return_sequences=True, dropout=0.3))(inputs, training = True)
lstm = Bidirectional(LSTM(16, return_sequences=False, dropout=0.3))(lstm, training = True)
dense = Dense(50)(lstm)
out10 = Dense(1)(dense)
out50 = Dense(1)(dense)
out90 = Dense(1)(dense)

model = Model(inputs, [out10,out50,out90])
model.compile(loss=losses, optimizer='adam', loss_weights = [0.3,0.3,0.3])



4. 데이터를 학습한다.
history = model.fit(X_train, [y_train,y_train,y_train], epochs=50, batch_size=128, verbose=2, shuffle=True)


5. 각 주기별(10, 50, 90) 데이터 예측치 확인. keras backend function을 이용해 값 예측.
### QUANTILEs BOOTSTRAPPING ###
pred_10, pred_50, pred_90 = [], [], []
NN = K.function([model.layers[0].input, K.learning_phase()], 
                [model.layers[-3].output,model.layers[-2].output,model.layers[-1].output])

for i in tqdm.tqdm(range(0,100)):
    predd = NN([X_test, 0.5])
    pred_10.append(predd[0])
    pred_50.append(predd[1])
    pred_90.append(predd[2])

pred_10 = np.asarray(pred_10)[:,:,0] 
pred_50 = np.asarray(pred_50)[:,:,0]
pred_90 = np.asarray(pred_90)[:,:,0]

### REVERSE TRANSFORM PREDICTIONS ###
pred_90_m = np.exp(np.quantile(pred_90,0.9,axis=0) + init[5000:])
pred_50_m = np.exp(pred_50.mean(axis=0) + init[5000:])
pred_10_m = np.exp(np.quantile(pred_10,0.1,axis=0) + init[5000:])

6. 이상 데이터 확인 ### EVALUATION METRIC ###
mean_squared_log_error(np.exp(y_test + init[5000:]), pred_50_m)

### PLOT QUANTILE PREDICTIONS ###
plt.figure(figsize=(16,8))
plt.plot(pred_90_m, color='cyan')
plt.plot(pred_50_m, color='blue')
plt.plot(pred_10_m, color='green')

### CROSSOVER CHECK ###
plt.scatter(np.where(np.logical_or(pred_50_m>pred_90_m, pred_50_m<pred_10_m))[0], 
            pred_50_m[np.logical_or(pred_50_m>pred_90_m, pred_50_m<pred_10_m)], c='red', s=50)

### PLOT UNCERTAINTY INTERVAL LENGHT WITH REAL DATA ###
plt.figure(figsize=(16,8))
plt.plot(np.exp(y_test + init[5000:]), color='red', alpha=0.4)
plt.scatter(range(len(pred_10_m)), pred_90_m - pred_10_m)

그럼 다음과 같은 결과를 얻을 수 있다. 아래에서 청색 지점은 이상이 상대적으로 많은 곳이다.
레퍼런스

LSTM을 이용한 주가 이상 탐지

이 글은 LSTM을 이용한 주가 이상 탐지 방법을 간단히 요약한다.

예측 데이터는 The S&P 500 지수이다. 이 데이터는 https://www.kaggle.com/pdquant/sp500-daily-19862018 에서 다운로드 받는다.

데이터를 우선 다음과 같이 다운로드한다.
!gdown --id 10vdMg_RazoIatwrT7azKFX4P02OebU76 --output spx.csv

소스코드는 다음과 같다.

df = pd.read_csv('spx.csv', parse_dates=['date'], index_col='date')
train_size = int(len(df) * 0.95)
test_size = len(df) - train_size
train, test = df.iloc[0:train_size], df.iloc[train_size:len(df)]
print(train.shape, test.shape)

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler = scaler.fit(train[['close']])

train['close'] = scaler.transform(train[['close']])
test['close'] = scaler.transform(test[['close']])

def create_dataset(X, y, time_steps=1):
    Xs, ys = [], []
    for i in range(len(X) - time_steps):
        v = X.iloc[i:(i + time_steps)].values
        Xs.append(v)
        ys.append(y.iloc[i + time_steps])
    return np.array(Xs), np.array(ys)

TIME_STEPS = 30

# reshape to [samples, time_steps, n_features]
X_train, y_train = create_dataset(
  train[['close']],
  train.close,
  TIME_STEPS
)

X_test, y_test = create_dataset(
  test[['close']],
  test.close,
  TIME_STEPS
)

print(X_train.shape)

model = keras.Sequential()
model.add(keras.layers.LSTM(
    units=64,
    input_shape=(X_train.shape[1], X_train.shape[2])
))

model.add(keras.layers.Dropout(rate=0.2))
model.add(keras.layers.RepeatVector(n=X_train.shape[1]))
model.add(keras.layers.LSTM(units=64, return_sequences=True))
model.add(keras.layers.Dropout(rate=0.2))
model.add(
  keras.layers.TimeDistributed(
    keras.layers.Dense(units=X_train.shape[2])
  )
)

model.compile(loss='mae', optimizer='adam')

history = model.fit(
    X_train, y_train,
    epochs=10,
    batch_size=32,
    validation_split=0.1,
    shuffle=False
)

X_train_pred = model.predict(X_train)
train_mae_loss = np.mean(np.abs(X_train_pred - X_train), axis=1)

THRESHOLD = 0.65
X_test_pred = model.predict(X_test)
test_mae_loss = np.mean(np.abs(X_test_pred - X_test), axis=1)

test_score_df = pd.DataFrame(index=test[TIME_STEPS:].index)
test_score_df['loss'] = test_mae_loss
test_score_df['threshold'] = THRESHOLD
test_score_df['anomaly'] = test_score_df.loss > test_score_df.threshold
test_score_df['close'] = test[TIME_STEPS:].close

anomalies = test_score_df[test_score_df.anomaly == True]

다음은 주식 예측에서 이상탐지된 결과이다.

레퍼런스