[데이터분석] AI 예측 및 이상 탐지를위한 시계열 데이터 전처리

AI 예측 및 이상 탐지를위한 시계열 데이터 전처리

세르지오 비라 혼다

나를 평가:

5.00 / 5 (4 투표)

2021 년 2 월 25 일CPOL

이 기사에서는 머신 러닝 (ML) 및 딥 러닝 (DL) 모델에 제공 할 시계열 데이터를 준비하는 방법을 알아 봅니다.

여기서는 비트 코인의 과거 가격을 기반으로 예측 및 이상 징후 탐지 작업을위한 시계열 데이터의 전처리에 대해 설명합니다.

[다운로드] AnomalyDetection-main(2).zip

소개

이 일련의 기사는 AI를 사용하여 완전한 기능의 시계열 예측기 및 이상 탐지기 애플리케이션을 개발하는 데 필요한 단계를 안내합니다. 우리의 예측 자 / 탐지기는 특히 비트 코인으로 암호 화폐 데이터를 다룰 것 입니다. 그러나이 시리즈를 따라 가면 학습 한 개념과 접근 방식을 유사한 성격의 모든 데이터 유형에 적용 할 수 있습니다.

이 시리즈를 최대한 활용하려면 Python , Machine Learning 및 Keras 기술 이 있어야 합니다. 전체 프로젝트는 내 "GitHub 저장소 에서 사용할 수 있습니다. 여기 와 여기 에서 완전한 대화 형 노트북을 확인할 수도 있습니다 .

이 시리즈 의 이전 기사 에서 시계열 데이터의 특성과 중요성에 대해 논의했습니다. 여기에서는 데이터를 신경망 예측 모델과 이상 탐지기를 훈련하는 데 사용할 수있는 형식으로 변환하는 방법을 배우게됩니다.

우리는 대부분의 시간 동안 비트 코인 가격 으로 작업 할 것입니다 . Kaggle 에서 완전한 데이터 세트를 찾았습니다 . 몇 가지 개념과 아이디어를 명확히하기 위해 NOAA 웹 사이트 에서 얻은 날씨 데이터 세트를 사용할 것 입니다. 계속해서이 데이터 세트를 다운로드하여 따라 해보세요. 이 프로젝트에서는 Kaggle 노트북 을 사용할 것입니다.하지만 다른 클라우드 노트북이나 로컬 컴퓨터를 사용할 수도 있습니다.

비트 코인 데이터 세트 검사, 재 포맷 및 정리

검사에 들어가기 전에이 프로젝트에서 사용할 모든 라이브러리를 가져 오겠습니다. 노트북이 이들 중 하나를 가져 오지 못하면 터미널에서 pip 명령을 사용하여 노트북을 설치할 수 있습니다.

 

Copy Code

import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import os import plotly.graph_objects as go from sklearn.preprocessing import MinMaxScaler import gc import joblib from tensorflow.keras.models import Sequential from tensorflow.keras import layers from tensorflow.keras.callbacks import ModelCheckpoint from tensorflow.keras.models import load_model from sklearn.ensemble import IsolationForest from sklearn.cluster import KMeans import json import urllib from datetime import datetime, timedelta,timezone import requests

이제 2011 년 12 월부터 2020 년 12 월까지의 비트 코인 기록이 포함 된 .csv 파일을로드하고 (분별 분리 – 약 400 만 개의 샘플) 첫 번째 행을 표시해 보겠습니다. 파일 경로는 / kaggle / input / bitcoin-historical-data / bitstampUSD_1-min_data_2012-01-01_to_2020-1 입니다.

 

Copy Code

btc = pd.read_csv('/kaggle/input/bitcoin-historical-data/bitstampUSD_1-min_data_2012-01-01_to_2020-12-31.csv') btc.head()

이제 먼저 살펴볼 수 있습니다.

 

날짜 형식으로 표시되어야하는 "Timestamp"열은 현재 Unix 형식으로 표시됩니다. 올바른 것으로 전환하려면 다음을 수행하십시오.

 

Copy Code

btc['Timestamp'] = pd.to_datetime(btc.Timestamp, unit='s') btc.head()

그러면 열 형식이 올바른 형식으로 변경됩니다.

 

대부분의 열에서 많은 NaN 값 (null 필드)을 볼 수 있습니다. Weighted_Price단순하게 유지하기 위해 일 변량 접근 방식에 초점을 맞추기 위해 " ``를 선택하겠습니다 . 관측 값을 다시 샘플링하고 점을 1 분 단위가 아닌 1 시간 단위로 균등하게 분리 할 것입니다. 짧은 시간에 큰 변동이있을 것으로 예상되기 때문입니다. 따라서 엄청난 양의 중복 데이터가있을 것입니다.

 

Copy Code

btc = btc[['Timestamp','Weighted_Price']] btc = btc.resample('H', on='Timestamp')[['Weighted_Price']].mean()

이제 전체 그림을 살펴보고 데이터 불연속성을 감지하고 의미없는 데이터를 제거하기 위해 세트의 어떤 부분을 제거해야하는지 시각적으로 이해해 보겠습니다.

 

Copy Code

pano = btc.copy() #We're going to use this later fig = go.Figure() fig.add_trace(go.Scatter(x=pano.index, y=pano['Weighted_Price'],name='Full history BTC price')) fig.update_layout(showlegend=True,title="BTC price history",xaxis_title="Time",yaxis_title="Prices",font=dict(family="Courier New, monospace")) fig.show()

 

위의 차트에서 2012 년부터 2013 년 중반까지 몇 가지 중단이 있음을 알 수 있습니다. 또한 2017 년 중반까지의 데이터 추세는 비트 코인 가격이 0에 가까운 값으로 돌아갈 가능성이 매우 낮기 때문에 데이터 추세는 쓸모가 없습니다. 관련없는 데이터로 미래 모델을 공급하고 싶지 않으므로 데이터 세트를 자르고 의미있는 값만 포함하고 null 값을 null이 아닌 다음 인접 항목으로 대체하겠습니다.

 

Copy Code

btc = btc.iloc[51000:] btc.fillna(method ='bfill', inplace = True) print('NaN values: ',btc.isna().sum())

이제 null 값을 포함하지 않는 데이터 세트가 있습니다. 데이터 포인트는 2017-10-25 07:00:00부터 시작되며 새 세트에는 27906 값이 있습니다. 데이터 세트를 다시 플로팅하면 다음과 같은 결과를 얻어야합니다.

 

비트 코인 데이터 세트 분할

아시다시피 데이터 세트의 한 부분을 모델 학습에 사용하고 다른 부분을 테스트에 사용하는 것이 일반적인 관행입니다. 이 특별한 경우, 우리는 연속적인 부분을 취해야합니다. 이것이 제가 값의 처음 20 %를 테스트 세트로, 마지막 80 %를 훈련 세트로 취하는 이유입니다. 이렇게하려면 다음을 실행하십시오.

 

Copy Code

data_for_us = btc.copy() #To be used later on Unsupervised Learning training_start = int(len(btc) * 0.2) train = btc.iloc[training_start:] test = btc.iloc[:training_start]

이제 훈련 세트와 테스트 세트 모양은 각각 (22325, 1) 및 (5581, 1)입니다.

비트 코인 데이터 세트 확장

이 단계는 머신 러닝 / 딥 러닝 모델 (ML / DL)을 구현하기 전에 중요합니다. 이것이 없으면 모델 수렴 속도가 느려집니다. 아마도 모델은 훈련 후에도 좋은 결과를 얻지 못할 것입니다. 일반적으로 이것은 이기종 값에 맞아야하는 매개 변수 조정이 리소스를 많이 소모하기 때문에 발생합니다. 이 단계는 모든 ML / DL 모델에서 수렴을 더 쉽게 만드는 작은 값 사이의 데이터 범위를 지정하는 것으로 정의 할 수 있습니다.

먼저 다음을 실행하여 스케일러를 맞 춥니 다.

 

Copy Code

scaler = MinMaxScaler().fit(train[['Weighted_Price']])

Scikit-Learn 의 MinMaxScaler 방법을 사용하고 있습니다. 공식 문서 웹 사이트에서 설명을 가져옵니다. " 이 추정기는 각 기능을 개별적으로 확장하고 번역하여 트레이닝 세트의 주어진 범위 (예 : 0과 1 사이)에 있도록합니다. "사람들이 스케일러로 작업 할 때 종종 저지르는 실수 사용 가능한 전체 데이터에 맞추는 것입니다. 이는 적절한 접근 방식이 아닙니다. 스케일러는 훈련 세트에만 맞아야합니다. 향후 사용을 위해 스케일러를 저장하고 훈련 및 테스트 세트를 확장하려면 다음 명령을 실행하십시오.

 

Copy Code

joblib.dump(scaler, 'scaler.gz') scaler = joblib.load('scaler.gz') def scale_samples(data,column_name,scaler): data[column_name] = scaler.transform(data[[column_name]]) return data train = scale_samples(train.copy(),train.columns[0],scaler) test = scale_samples(test,test.columns[0],scaler)

이제 테이블을 살펴보면 모든 값이 0과 1 사이임을 알 수 있습니다. 그게 우리가 원했던 것입니다. 맞죠?

시퀀스 생성 및 데이터 셋 생성

이전 기사 에서 언급했듯이 우리는 현재와 미래의 비트 코인 가격의 이상을 감지하고 싶습니다. 명확히합시다. 우선 과거 및 현재 데이터를 사용하여 미래 가격 가치를 예측 한 다음 전체 데이터 그림에서 이상을 확인해야합니다. 이것을 그래픽으로 더 명확히하겠습니다.

이것은 일반적으로 시퀀스 데이터 세트에있는 것입니다.

 

t = n 이 현재 날짜 및 시간 이라고 가정합니다 . 우리가 원하는 것은 예를 들어 t = 0 에서 t = n 까지의 데이터가 주어지면 t = n + 1에 대한 가격 값을 예측하고 전체 창 (t = 0 ~ t = n + 1) 에서 이상을 감지하는 것 입니다. 이렇게 :

 

이를 위해서는 전체 데이터 세트를 모델에 전달할 수있는 작은 시퀀스 청크로 분할해야합니다. 이 경우, 우리는 지난 24 시간의 비트 코인 가격을 모델에 전달하고 다음 시간에 대한 가격을 예측하려고합니다. 이렇게하려면,의는 정의 할 수 전환 확인 기간이 지난 창 단계로 lookforward 미래 창의 단계로를 :

 

코드에서 함수는 다음과 같습니다.

 

Copy Code

def shift_samples(data,column_name,lookback=24): data_x = [] data_y = [] for i in range(len(data) - int(lookback)): x_floats = np.array(data.iloc[i:i+lookback]) y_floats = np.array(data.iloc[i+lookback]) data_x.append(x_floats) data_y.append(y_floats) return np.array(data_x), np.array(data_y)

이렇게하면 두 개의 NumPy 배열 이 반환됩니다 . 첫 번째는 룩백 청크 용이고 두 번째는 룩 포워드 청크 용입니다. 이제이 함수를 호출하고 테스트 및 학습 세트를 전달하여 사용할 데이터 세트를 만듭니다.

 

Copy Code

X_train, y_train = shift_samples(train[['Weighted_Price']],train.columns[0]) X_test, y_test = shift_samples(test[['Weighted_Price']], test.columns[0])

마지막으로 세트의 최종 모양을 이해하려면 다음 명령을 실행하십시오.

 

Copy Code

print("Final datasets' shapes:") print('X_train: '+str(X_train.shape)+', y_train: '+str(y_train.shape)) print('X_test: '+str(X_test.shape)+', y_train: '+str(y_test.shape))

다음을 반환합니다.

 

Copy Code

Final datasets' shapes: X_train: (22301, 24, 1), y_train: (22301, 1) X_test: (5557, 24, 1), y_train: (5557, 1)

검색 단계를 늘리려면 함수를 약간 수정해야합니다.

 

Copy Code

def shift_samples(data,column_name,lookback=30,lookforward=2): data_x = [] data_y = [] for i in range(len(data) - int(lookback)-int(lookforward)): x_floats = np.array(data.iloc[i:i+lookback]) y_floats = np.array(data.iloc[i+lookback:i+lookback+lookforward]) data_x.append(x_floats) data_y.append(y_floats) return np.array(data_x), np.array(data_y)

shift_sample 함수를 수정하고 검색 단계를 늘리면 들어오는 모델도 수정해야합니다. 그런 접근 방식을 개발 하는 노트북을 만들었습니다 . 전체 노트북 상호 작용을 얻으려면 Kaggle 에서 사용할 수있는 노트북을 확인하십시오 .

 

[출처] https://www.codeproject.com/Articles/5295162/Preprocessing-Time-Series-Data-for-AI-Forecasting

Preprocessing Time Series Data for AI Forecasting and Anomaly Detection

Sergio Virahonda

Rate me:

5.00/5 (4 votes)

25 Feb 2021CPOL

In this article, we learn how to prepare time series data to be fed to machine learning (ML) and deep learning (DL) models.

Here we’ll discuss pre-processing of the time series data for forecasting and anomaly detection tasks based on Bitcoin’s historical price.

Introduction

This series of articles will guide you through the steps necessary to develop a fully functional time series forecaster and anomaly detector application with AI. Our forecaster/detector will deal with the cryptocurrency data, specifically with Bitcoin. However, after following along with this series, you’ll be able to apply the concepts and approaches you’ve learned to any data type of similar nature.

To fully benefit from this series, you should have some Python, Machine Learning, and Keras skills. The entire project is available in my "GitHub repository. You can also check out the fully interactive notebooks here and here.

In the previous article of this series, we discussed the nature and importance of time series data. In this one, you’re going to learn how to transform the data into the form that can be used to train neural network forecasting models, as well as anomaly detectors.

We are going to work with Bitcoin prices most of the time. I’ve found a complete dataset at Kaggle. To clarify some concepts and ideas, we’ll use a weather dataset that I’ve obtained from the NOAA website. Go ahead and download these datasets to follow along. In this project, I’ll be using Kaggle Notebooks – but you can use any other Cloud notebook, or even your local machine.

Inspecting, Reformatting, and Cleaning the Bitcoin Dataset

Before diving into inspection, let’s import all the libraries that we’ll use in this project. If your notebook fails to import any of them, remember that you can install them with the pip command from the terminal.

 

Copy Code

import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import os import plotly.graph_objects as go from sklearn.preprocessing import MinMaxScaler import gc import joblib from tensorflow.keras.models import Sequential from tensorflow.keras import layers from tensorflow.keras.callbacks import ModelCheckpoint from tensorflow.keras.models import load_model from sklearn.ensemble import IsolationForest from sklearn.cluster import KMeans import json import urllib from datetime import datetime, timedelta,timezone import requests

Now let’s load the .csv file that contains the Bitcoin history from December 2011 to December 2020 (minutely separated – around 4 million samples) and show the first rows. The file path is /kaggle/input/bitcoin-historical-data/bitstampUSD_1-min_data_2012-01-01_to_2020-1.

 

Copy Code

btc = pd.read_csv('/kaggle/input/bitcoin-historical-data/bitstampUSD_1-min_data_2012-01-01_to_2020-12-31.csv') btc.head()

You’ll now be able to take the first look:

 

Notice that the "Timestamp" column, which should be expressed as date format, is currently expressed as Unix format. To switch it to the right one, let’s do this:

 

Copy Code

btc['Timestamp'] = pd.to_datetime(btc.Timestamp, unit='s') btc.head()

This will change the column format to the right one:

 

You may see a lot of NaN values (null fields) in most columns. Let’s select the "Weighted_Price'' to focus on an univariate approach, to keep things simple. We’re going to resample the observations and get points equally separated by 1 hour rather than by 1 minute because you wouldn’ expect to notice big variations in short periods of time and therefore you’d have tons of redundant data.

 

Copy Code

btc = btc[['Timestamp','Weighted_Price']] btc = btc.resample('H', on='Timestamp')[['Weighted_Price']].mean()

Let’s now take a look at the entire picture to visually understand what portion of the set must be removed to detect data discontinuity and to get rid of meaningless data:

 

Copy Code

pano = btc.copy() #We're going to use this later fig = go.Figure() fig.add_trace(go.Scatter(x=pano.index, y=pano['Weighted_Price'],name='Full history BTC price')) fig.update_layout(showlegend=True,title="BTC price history",xaxis_title="Time",yaxis_title="Prices",font=dict(family="Courier New, monospace")) fig.show()

 

In the above chart, you’ll notice some discontinuities from 2012 to mid-2013. Also, the data trend until mid-2017 the data trend is useless because it’s very unlikely that the Bitcoin price gets back to values close to zero. You don’t want to feed your future models with irrelevant data, so let’s truncate our dataset and include only the meaningful values, and replace the null ones with their next not-null neighbors’:

 

Copy Code

btc = btc.iloc[51000:] btc.fillna(method ='bfill', inplace = True) print('NaN values: ',btc.isna().sum())

Now you have a dataset that doesn’t contain any null values. The data points start from 2017-10-25 07:00:00, and your new set has 27906 values. If you plot your dataset again, you must get something like this:

 

Splitting the Bitcoin Dataset

As you probably know, it’s a common practice to take one portion of the dataset to train a model and another one to test it. In this particular case, we need to take continuous portions, that’s why I’m going to take the first 20% of values as a test set and the last 80% as a training set. To do so, issue the following:

 

Copy Code

data_for_us = btc.copy() #To be used later on Unsupervised Learning training_start = int(len(btc) * 0.2) train = btc.iloc[training_start:] test = btc.iloc[:training_start]

Now, your training set and test set shapes are (22325, 1) and (5581, 1), respectively.

Scaling the Bitcoin Dataset

This step is crucial before implementing any machine learning/deep learning model (ML/DL). Without it, the model convergence speed will be low; possibly, the model will never even reach a good result after training. Typically, this happens because the parameter adjustments that need to fit heterogeneous values are very resource-consuming. We can define this step as ranging the data between small values that make convergence easier for any ML/DL model.

Let’s first fit the scaler by issuing:

 

Copy Code

scaler = MinMaxScaler().fit(train[['Weighted_Price']])

Note that we’re using the MinMaxScaler method from Scikit-Learn. I’ll take its description from the official documentation website: "This estimator scales and translates each feature individually such that it is in the given range on the training set, e.g. between zero and one." A mistake people often make when working with scalers is fitting it on the entire available data. That’s not an appropriate approach; you must fit the scaler only on the training set. To save the scaler for future use and scale the training and test sets, issue these commands:

 

Copy Code

joblib.dump(scaler, 'scaler.gz') scaler = joblib.load('scaler.gz') def scale_samples(data,column_name,scaler): data[column_name] = scaler.transform(data[[column_name]]) return data train = scale_samples(train.copy(),train.columns[0],scaler) test = scale_samples(test,test.columns[0],scaler)

Now if you inspect the tables, you’ll notice that all values are between 0 and 1. That’s what we wanted, right?

Generating Sequence and Creating the Dataset

As I’ve mentioned in the previous article, we want to detect anomalies in the current and future Bitcoin prices. Let’s clarify. First of all, we need to use the past and current data to predict what’s going to be the future price value, to then determine anomalies in the entire data picture. Let me further clarify this graphically.

This is what you’d typically have in a sequence dataset:

 

Suppose that t = n is the current date and time. What we want is to predict the price values for, for example, t = n+1 given the data from t = 0 to t = n, and detect anomalies in the entire window (t = 0 to t = n+1). Like this:

 

To achieve this, you need to segment the entire dataset into small chunks of sequences that can be passed to your model. In this case, we want to pass to our model the last 24 hours of Bitcoin prices and predict the one for the next hour. To do so, let’s define a lookback as the steps of the past window and lookforward as the steps of the future window:

 

In code, the function would be:

 

Copy Code

def shift_samples(data,column_name,lookback=24): data_x = [] data_y = [] for i in range(len(data) - int(lookback)): x_floats = np.array(data.iloc[i:i+lookback]) y_floats = np.array(data.iloc[i+lookback]) data_x.append(x_floats) data_y.append(y_floats) return np.array(data_x), np.array(data_y)

This will return two NumPy arrays, the first one for the lookback chunk and the second one for the lookforward chunk. Now call this function and pass the test and training sets to create the dataset that we’re going to use:

 

Copy Code

X_train, y_train = shift_samples(train[['Weighted_Price']],train.columns[0]) X_test, y_test = shift_samples(test[['Weighted_Price']], test.columns[0])

Lastly, to understand the final shape of the sets, issue these commands:

 

Copy Code

print("Final datasets' shapes:") print('X_train: '+str(X_train.shape)+', y_train: '+str(y_train.shape)) print('X_test: '+str(X_test.shape)+', y_train: '+str(y_test.shape))

This will return:

 

Copy Code

Final datasets' shapes: X_train: (22301, 24, 1), y_train: (22301, 1) X_test: (5557, 24, 1), y_train: (5557, 1)

If you want to increase the lookforward steps, you’ll need to slightly modify the function:

 

Copy Code

def shift_samples(data,column_name,lookback=30,lookforward=2): data_x = [] data_y = [] for i in range(len(data) - int(lookback)-int(lookforward)): x_floats = np.array(data.iloc[i:i+lookback]) y_floats = np.array(data.iloc[i+lookback:i+lookback+lookforward]) data_x.append(x_floats) data_y.append(y_floats) return np.array(data_x), np.array(data_y)

Keep in mind that if you modify the shift_sample function and increase the lookforward steps, you’ll also need to modify the incoming models. I’ve created a notebook where I develop such an approach. To get the full notebook interactivity, please check the one available at Kaggle.

 

 

 

[데이터분석] bitcoin analysis 비트 코인 시계열 데이터에 대한 AI 이상 탐지

세르지오 비라 혼다

 

2021 년 2 월 26 일CPOL

이 기사에서는 시계열 데이터에 대한 이상 탐지에 대해 설명합니다.

여기에서는 시계열 데이터에 대한 이상 탐지에 대해 자세히 살펴보고이 작업을 수행 할 수있는 모델을 구축하는 방법을 살펴 보겠습니다.

[다운로드] AnomalyDetection-main.zip

소개

이 일련의 기사는 AI를 사용하여 완전한 기능의 시계열 예측기 및 이상 탐지기 애플리케이션을 개발하는 데 필요한 단계를 안내합니다. 우리의 예측 자 / 탐지기는 특히 비트 코인으로 암호 화폐 데이터를 다룰 것 입니다. 그러나이 시리즈를 따라 가면 학습 한 개념과 접근 방식을 유사한 성격의 모든 데이터 유형에 적용 할 수 있습니다.

이 시리즈를 최대한 활용하려면 Python , Machine Learning 및 Keras 기술 이 있어야 합니다. 전체 프로젝트는 내 "GitHub 저장소 에서 사용할 수 있습니다. 여기 와 여기 에서 완전한 대화 형 노트북을 확인할 수도 있습니다 .

에서 이전 기사 , 당신은 기계 학습 (ML)과 깊은 학습 (DL) 모델에 공급되는 시계열 데이터를 준비하는 방법을 배웠습니다. 이 기사에서는 이러한 종류의 데이터에서 이상을 감지하는 방법을 설명합니다.

이상 징후 이해

이상이 무엇인지 궁금 할 수 있습니까? 어떻게 감지 할 수 있습니까? 아직 실행되지 않은 시나리오에서 이상을 감지하는 것이 실제로 가능합니까? 이상 현상 은 불규칙한 것을 가리키는 인기있는 용어입니다. 통계에서 이상 치는 데이터 수집에서 드물거나 예상치 못한 이벤트 인 이상 값 이라고도합니다 . 데이터 세트의 분포가 거의 정상이면 평균에서 표준 편차가 2 인 모든 데이터 포인트가 이상 징후가됩니다.

이상 현상의 개념을 설명하기 위해 자동차 엔진의 온도는 섭씨 90도 미만이어야합니다. 그렇지 않으면 과열되어 고장납니다. 엔진의 냉동 시스템은 온도를 안전한 범위로 유지합니다. 실패하면 정말 높은 온도 값을 알 수 있습니다. 수학적으로는 오랜 기간 동안 90 미만의 값을 가지게되며 엔진이 붕괴 될 때까지 값이 갑자기 최대 150까지 올라갑니다. 시각적으로 이것이 의미하는 바입니다.

섭씨 70 ~ 90도 사이의 데이터 포인트가 표시되지만 차트 끝에 갑자기 다른 값이 패턴을 깨뜨립니다 (강조 표시된 값). 이러한 후자의 관측은 이상치이므로 이상입니다. 위의 그래프는 이상을 설명하는 패턴을 보여줍니다.

AI 관점에서 이상 감지

이제 이상이 무엇인지 알았습니다. 어떻게 감지합니까? 이상 탐지는 데이터 수집에서 예상치 못한 항목이나 이벤트 (매우 드물게 발생하는 항목)를 식별하는 프로세스입니다. 이상 탐지는 두 가지 유형으로 제공됩니다. 하나는 단일 공간 (단일 변수)의 값 분포를 위해 예상치 못한 데이터 포인트를 식별하는 프로세스 인 일 변량 이상 탐지입니다. 다른 하나는 다변량 이상 탐지로, 이상 값은 최소 두 변수의 비정상 점수 조합입니다.

이 시리즈는 일 변량 이상 탐지에 초점을 맞출 것입니다. 그러나 동일한 접근 방식이 다 변수 컨텍스트에서 이상을 감지하도록 설계된 더 복잡한 모델의 기준으로 작동 할 수 있습니다.

K- 평균 클러스터링으로 이상 징후 감지

권장 데이터 세트를 이미 확인했다면 레이블이 전혀 지정되어 있지 않다는 것을 알았을 것입니다. 즉, 무엇이 이상인지 아닌지 아직 명확하지 않다는 것을 의미합니다. 이것은 실제 사례에서 처리해야하는 일반적인 시나리오입니다. 이상 감지 문제에 직면하면 소스에서 직접 데이터를 수집하고 특정 기술을 적용하여 이상을 발견해야합니다. 이것이 바로 비지도 학습의 도움으로 우리가 할 일입니다. 비지도 학습은 패턴을 발견하기 위해 모델이 라벨이없는 데이터에 대해 학습되는 ML 유형입니다. 이론적으로 데이터를 그룹화하기 위해 사람의 개입이 필요하지 않으므로 요구 사항 인 경우 이상을 식별 할 수 있습니다.

K- 평균 클러스터링은 오늘날 가장 단순한 것 중 하나 인 매우 간단하지만 얼마나 유용한 지 알게 될 것입니다. K-Means Clustering 은 유사한 데이터 포인트를 그룹화하고 육안으로는 분명하지 않은 기본 패턴을 찾는 알고리즘입니다. K – 중심 수 – 주어진 데이터 세트에서 식별해야하는 그룹 또는 클러스터 수를 정의합니다. 유사성이있는 데이터 포인트 모음으로서의 클러스터입니다. 중심은 수학적으로 클러스터의 중심입니다.

알고리즘은 모든 클러스터의 시작점 인 K 중심을 무작위로 선택하여 데이터 처리를 시작한 다음 반복적으로 계산을 수행하여 중심 위치를 최적화합니다. 군집이 식별되면 알고리즘은 모든 단일 데이터 포인트를 가장 가까운 군집에 할당하고 해당 레이블을 모든 관측치에 추가합니다.

기본적인 예를 들어이를 시각화 해 보겠습니다. 다음과 같은 X 개의 임의 값이 있다고 가정합니다.

두 개의 클러스터를 쉽게 식별 할 수 있습니다.

이것이 바로 K-Means Clustering 알고리즘이하는 일입니다. 이제이 알고리즘이 데이터 세트에서 어떻게 작동하는지 살펴 보겠습니다. 다음 명령을 실행하십시오.

# 모델에 전달할 데이터 준비 outliers_k_means = pano.copy () [ 51000 :] outliers_k_means.fillna (method = ' bfill' , inplace = True ) kmeans = KMeans (n_clusters = 2, random_state = 0) .fit (outliers_k_means [ ' Weighted_Price' ] .values.reshape ( -1 , 1 )) outlier_k_means = kmeans.predict (outliers_k_means [ ' Weighted_Price' ] .values.reshape ( -1 , 1 )) outliers_k_means [ ' outlier' ] = outlier_k_means outliers_k_means.head ()

결과 데이터 프레임의 처음 5 개 행을 얻게됩니다.

위의 표에서 "outlier"열에 0이있는 모든 행은 정상을 나타내고 해당 열에 1이있는 모든 행은 예외입니다. 모델이 이러한 값을 식별하는 방법을 확인하기 위해 전체 데이터 세트를 플로팅 해 보겠습니다.

a = outliers_k_means.loc [outliers_k_means [ ' outlier' ] == 1 ] # 이상치 무화과 = go.Figure () fig.add_trace (go.Scatter (x = outliers_k_means [ ' Weighted_Price' ] .index, y = outliers_k_means [ ' Weighted_Price' ] .values, mode = ' lines' , name = ' BTC Price' )) fig.add_trace (go.Scatter (x = a.index, y = a [ ' Weighted_Price' ] .values, mode = ' markers' , name = ' Anomaly' , marker_symbol = ' x' , marker_size = 2)) fig.update_layout (showlegend = True, title = " BTC 가격 이상-KMeans" , xaxis_title = " 시간" , yaxis_title = " 가격" , font = dict (family = " Courier New, monospace" )) fig.show ()

위의 차트는 지난 몇 주 동안 가격이 변칙적 이었기 때문에 실제 비트 코인 가격 현실과 다소 잘 맞습니다. 알고리즘은 ~ $ 13,000 이상의 값을 하나의 클래스로 클러스터링하고이 임계 값 미만의 값을 다른 클래스로 클러스터링합니다.

비트 코인 과거 가격에서 이상 징후를 감지하기위한 신경망 및 오토 인코더 구현

이제 Neural Networks (NN)의 도움을 받아 비지도 학습 기법을 사용해 봅시다 . 이 방법은 이상 감지에 훨씬 더 유연하고 정확한 것으로 알려져 있습니다.

오늘날 NN은 놀라운 힘과 훌륭한 결과로 인해 새로운 표준이되었습니다. 우리는 특별한 방법을 사용합니다 : 우리가 활용할 수 있습니다 LSTM (긴 단기 기억) 자율 학습 모델을 구축하기 위해 NN과 autoencoders을. 이 접근 방식의 주요 목표는 마지막 모델에서 얻은 결과를 개선하는 것이며, 현재 데이터 세트의 분포를 모델링하여 그 구조에 대해 더 많이 배울 계획입니다.

일반적으로 이상 탐지 문제는 데이터 세트에 레이블이 지정되었는지 여부에 따라 분류 또는 회귀로 처리 할 수 ​​있습니다. 다음으로 할 일은 데이터 세트를 회귀 문제로 모델링하는 것입니다. 여기서 네트워크의 재구성 오류를 정량화 할 것입니다. 이는 본질적으로 데이터 세트에서 시퀀스의 정상적인 동작을 재구성 할 수있는 모델을 구축하여 재구성 오류가 높은 데이터 포인트를 이상으로 정의 할 수 있음을 의미합니다. 명심해야 할 주요 아이디어는 빈번한 이벤트는 재구성하기가 매우 쉽지만 드물게 발생하는 이벤트는 그렇게 간단하지 않다는 것입니다. 따라서 후자의 재구성 오류는 더 높아질 것입니다.

재발의 NN - LSTM 포함은 - 특별히 순차적 데이터에 사용할 수 있습니다. 그들은 과거 데이터를 기억하는 데 능숙하며 그 중요한 기능으로 인해 더 나은 예측을 수행합니다. 작동 방식을 완전히 이해하려면 François Chollet의 " Deep Learning with Python "을 살펴 보는 것이 좋습니다 .

안타깝게도 LSTM 네트워크로는 목표를 달성하기에 충분하지 않으므로 아키텍처에 자동 인코더 를 추가해야합니다 . 기본 오토 인코더 아키텍처를 살펴 보겠습니다.

오토 인코더는 감독되지 않는 방식으로 효율적인 데이터 인코딩을 생성하는 데 사용되는 인공 NN입니다. 이를 통해 오토 인코더는 데이터 수집의 가장 중요한 기능을 학습 할 수 있습니다. 우리의 경우 재구성 기능을 사용하여 비정상적인 항목과 그렇지 않은 항목을 결정합니다. 특정 관측치를 재구성하는 데 어려움을 겪는다면 이것이 이상 현상이라고 추론 할 수 있습니다.

그건 그렇고, 나는 데이터 포인트의 재구성이 얼마나 잘못된지를 측정하는 수단으로 재구성 손실을 사용할 것입니다. 당신이 다음 볼 것은의 도움과 LSTM의 autoencoder의 창조 Keras :

# 모델 생성을위한 중요한 매개 변수 정의 tsteps = X_train.shape [ 1 ] nfeatures = X_train.shape [ 2 ] 검출기 = Sequential () detector.add (layers.LSTM ( 128 , input_shape = (tsteps, nfeatures), dropout = 0. 2 )) detector.add (layers.Dropout (rate = 0. 5 )) detector.add (layers.RepeatVector (tsteps)) detector.add (layers.LSTM ( 128 , return_sequences = True, dropout = 0. 2 )) detector.add (layers.Dropout (rate = 0. 5 )) detector.add (layers.TimeDistributed (layers.Dense (nfeatures))) detector.compile (loss = ' mae' , optimizer = ' adam' )

이제 훈련 세트에 모델을 맞 춥니 다.

checkpoint = ModelCheckpoint ( " /kaggle/working/detector.hdf5" , monitor = ' val_loss' , verbose = 1, save_best_only = True, mode = ' auto' , period = 1) history1 = detector.fit (X_train, y_train, epochs = 50, batch_size = 128, verbose = 1, validation_split = 0. 1 , callbacks = [checkpoint], shuffle = False)

지금까지 ModelCheckpoint 구현을 제외하고는 멋진 것은 없습니다 . 훈련 중에 얻은 최상의 모델을 저장합니다. 프로세스가 끝날 때 얻은 결과는 다음과 같습니다 (모든 AI 훈련주기에 무작위 요소가 포함되어 있으므로 결과가 약간 다를 수 있음을 명심하십시오).

신기원 50/50 157/157 [=============================]-ETA : 0 초-손실 : 0.0268 Epoch 00050 : val_loss가 0.11903에서 개선되지 않았습니다. 157/157 [=============================]-1 초 7ms / 단계-손실 : 0.0268-val_loss : 0.1922

얻은 최상의 모델을로드하고 평가하려면 다음 명령을 실행하십시오.

detector = load_model ( " detector.hdf5" ) detector.evaluate (X_test, y_test)

결과적으로 다음을 얻었습니다.

174/174 [=============================]-0 초 3ms / 단계-손실 : 0.0579

이것은 전혀 나쁘지 않습니다. 이제 데이터 포인트가 이상인지 여부를 정의하는 가장 간단한 방법 인 정적 임계 값을 설정할 때입니다. 이 경우이 임계 값보다 높은 오류는 이상 값으로 간주됩니다. 훈련 및 테스트 세트에 대한 MAE 손실을 얻고 적절한 임계 값이 무엇인지 시각적으로 결정할 것입니다.

X_train_pred = detector.predict (X_train) loss_mae = np.mean (np.abs (X_train_pred-X_train), axis = 1) # 이것은 MAE를 계산하는 공식입니다. sns.distplot (loss_mae, bins = 100, kde = True)

X_test_pred = detector.predict (X_test) loss_mae = np.mean (np.abs (X_test_pred-X_test), 축 = 1) sns.distplot (loss_mae, bins = 100, kde = True)

위의 차트에서 볼 수 있듯이 0.150보다 높은 관측치는 비정상적이지만 다른 시나리오에서는이 작업이 그렇게 간단하지 않습니다. 따라서 통계를 사용하여이 값을보다 정확하게 식별하는 것이 가장 좋습니다. 이 숫자를 임계 값으로 설정하고 그 위에있는 값을 결정하여 이상을 그릴 수 있습니다.

임계 값 = 0 . 15 test_df = pd.DataFrame (test [tsteps :]) test_df [ ' 손실' ] = loss_mae test_df [ ' threshold' ] = 임계 값 test_df [ ' anomaly' ] = test_df.loss> test_df.threshold test_df [ ' Weighted_Price' ] = test [tsteps :]. Weighted_Price 이상 항목 = test_df [test_df.anomaly == True ] yvals1 = scaler.inverse_transform (test [tsteps :] [[ ' Weighted_Price' ]]) yvals1 = yvals1.reshape ( -1 ) yvals2 = scaler.inverse_transform (anomalies [[ ' Weighted_Price' ]]) yvals2 = yvals2.reshape ( -1 ) 무화과 = go.Figure () fig.add_trace (go.Scatter (x = test [tsteps :]. index, y = yvals1, mode = ' lines' , name = ' BTC Price' )) fig.add_trace (go.Scatter (x = anomalies.index, y = yvals2, mode = ' markers' , name = ' Anomaly' )) fig.update_layout (showlegend = True, title = " BTC 가격 이상" , xaxis_title = " 시간" , yaxis_title = " 가격" , font = dict (family = " Courier New, monospace" )) fig.show ()

이 플롯은 테스트 세트의 이상을 보여줍니다.

이상 현상은 유망 해 보입니다. 전체 데이터 세트를 살펴보고 여기에 기존 이상을 플로팅 해 보겠습니다.

scaled_pano = test.append (train, ignore_index = False) X_shifted, y_shifted = shift_samples (scaled_pano [[ ' Weighted_Price' ]], scaled_pano.columns [ 0 ]) X_shifted_pred = detector.predict (X_shifted) loss_mae = np.mean (np.abs (X_shifted_pred-X_shifted), 축 = 1) non_scaled_pano = pano.copy () [ 51000 :] non_scaled_pano.fillna (method = ' bfill' , inplace = True ) non_scaled_pano = non_scaled_pano [: -24 ] non_scaled_pano [ ' loss_mae' ] = loss_mae non_scaled_pano [ ' threshold' ] = 임계 값 non_scaled_pano [ ' anomaly' ] = non_scaled_pano.loss_mae> non_scaled_pano.threshold pano_outliers = non_scaled_pano [non_scaled_pano [ ' anomaly' ] == True ] 무화과 = go.Figure () fig.add_trace (go.Scatter (x = non_scaled_pano.index, y = non_scaled_pano [ ' Weighted_Price' ] .values, mode = ' lines' , name = ' BTC Price' )) fig.add_trace (go.Scatter (x = pano_outliers.index, y = pano_outliers [ ' Weighted_Price' ] .values, mode = ' markers' , name = ' Anomaly' )) fig.update_layout (showlegend = True, title = " BTC 가격 이상-Autoencoder" , xaxis_title = " 시간" , yaxis_title = " 가격" , font = dict (family = " Courier New, monospace" )) fig.show ()

당신이 볼 수있는 강조 표시된 가격은 차트에 매우 희귀하고 있기 때문에, 그 결과는 우리의 기대와 일치 또한 비트 코인의 전체 가격의 역사 (확인하십시오 비트 코인 역사 가격을보고 여기에 ). 이 시리즈의 나머지 부분에서는이 모델을 이상 탐지기로 유지합니다.

다음 단계

다음 기사에서는 비트 코인 시계열에 대한 예측에 대해 논의 할 것입니다. 계속 지켜봐주세요!

 

특허

이 기사는 관련 소스 코드 및 파일과 함께 The Code Project Open License (CPOL)에 따라 사용이 허가되었습니다 .

저자에 대하여

세르지오 비라 혼다

 

 

 
미국 

Sergio Virahonda는 베네수엘라에서 자랐으며 통신 공학 학사 학위를 받았습니다. 그는 4 년 전 해외로 이주했으며 그 이후로 의미있는 데이터 과학 경력을 쌓는 데 주력해 왔습니다. 그는 현재 아르헨티나에서 프리랜서 개발자로 코드를 작성하고 있습니다.

[출처] https://www.codeproject.com/Articles/5295163/AI-Anomaly-Detection-on-Bitcoin-Time-Series-Data

 

 

 

AI Anomaly Detection on Bitcoin Time Series Data

Sergio Virahonda

 

26 Feb 2021CPOL

In this article we talk about anomaly detection on time series data.

Here we’ll go deeper into anomaly detection on time-series data and see how to build models that can perform this task.

Introduction

This series of articles will guide you through the steps necessary to develop a fully functional time series forecaster and anomaly detector application with AI. Our forecaster/detector will deal with the cryptocurrency data, specifically with Bitcoin. However, after following along with this series, you’ll be able to apply the concepts and approaches you’ve learned to any data type of similar nature.

To fully benefit from this series, you should have some Python, Machine Learning, and Keras skills. The entire project is available in my "GitHub repository. You can also check out the fully interactive notebooks here and here.

In the previous article, you learned how to prepare time series data to be fed to machine learning (ML) and deep learning (DL) models. In this article, I’ll explain how to detect anomalies in this kind of data.

Understanding Anomalies

You might be wondering what is an anomaly? How could you detect it? Is it actually possible to detect anomalies in scenarios that haven’t played out yet? Anomaly is a popular term that refers to something irregular. In statistics, anomalies are often referred to as outliers – infrequent or unexpected events in a data collection. If the distribution of the dataset is approximately normal then anomalies would be all those data points that are 2 standard deviations from the mean.

To illustrate the concept of anomaly: a car engine’s temperature must be below 90 Centigrade, otherwise it will overheat and break down. The engine’s refrigeration system keeps the temperature in the safe range; if it fails, you could notice really high temperature values. Mathematically, you’d have values under 90 for a long period of time, then the values suddenly peak to150 until your engine collapses. Visually, this is what I mean:

You’d see data points between 70 and 90 Centigrade, but suddenly different values at the end of the chart break the pattern (the ones highlighted). These latter observations are outliers and, therefore, anomalies. The above graph shows a pattern that describes an anomaly.

Anomaly Detection from the AI Perspective

Now you know what an anomaly is - how do you detect it? As you may infer anomaly detection is the process of identifying the unexpected items or events in a data collection – those that occur very rarely. Anomaly detection comes in two flavors. One is the univariate anomaly detection which is the process of identifying those unexpected data points for a distribution of values in a single space (single variable). The other one is the multivariate anomaly detection, where an outlier is a combination of unusual scores of at least two variables.

I’ll focus these series on univariate anomaly detection. However, please note that the same approach can work as a baseline for more complex models, designed to detect anomalies in multivariate contexts.

Detecting Anomalies with K-Means Clustering

If you’ve checked the recommended datasets already, you’d have noticed that they aren’t labeled at all, meaning that it’s not yet clear what’s an anomaly and what’s not. This is the typical scenario you’ll have to deal with in real-world cases. When you face an anomaly detection challenge, you’ll have to gather the data coming directly from the source and apply certain techniques to discover anomalies. This is exactly what we’re going to do with some help from unsupervised learning. Unsupervised learning is a type of ML where the model is trained on unlabeled data in order to discover patterns. In theory, it doesn’t require human intervention to group the data and, therefore, identify anomalies — if that’s the requirement.

Keep in mind that K- Means Clustering is extremely simple – one of the simplest ones nowadays – but you’ll see how useful it can be. K-Means Clustering is an algorithm that groups similar data points and finds out underlying patterns that are not obvious to a naked eye. – the number of centroids –defines how many groups or clusters must be identified in a given dataset. A cluster as a collection of data points with similarities. A centroid is, mathematically, the center of a cluster.

The algorithm starts the data processing by randomly selecting K centroids, which are the starting points for every cluster, and then iteratively makes calculations to optimize the centroid positions. Once the clusters have been identified, the algorithm allocates every single data point to the closest cluster, and adds the corresponding label to every observation.

Let’s visualize this with a basic example. TSuppose that you have X random values that plot like this:

You can easily identify two clusters:

And that’s what the K-Means Clustering algorithm does, in a nutshell. Now let’s see how this algorithm works on our dataset. Issue the next commands:

# Preparing data to be passed to the model outliers_k_means = pano.copy()[51000:] outliers_k_means.fillna(method ='bfill', inplace = True) kmeans = KMeans(n_clusters=2, random_state=0).fit(outliers_k_means['Weighted_Price'].values.reshape(-1, 1)) outlier_k_means = kmeans.predict(outliers_k_means['Weighted_Price'].values.reshape(-1, 1)) outliers_k_means['outlier'] = outlier_k_means outliers_k_means.head()

You’ll get the first five rows of the resulting dataframe:

In the above table, every row with 0 in the "outlier" column indicates a normal, and every one with 1 in that column - an anomaly. Let’s plot the entire dataset to see how the model identifies these values:

a = outliers_k_means.loc[outliers_k_means['outlier'] == 1] #anomaly fig = go.Figure() fig.add_trace(go.Scatter(x=outliers_k_means['Weighted_Price'].index, y=outliers_k_means['Weighted_Price'].values,mode='lines',name='BTC Price')) fig.add_trace(go.Scatter(x=a.index, y=a['Weighted_Price'].values,mode='markers',name='Anomaly',marker_symbol='x',marker_size=2)) fig.update_layout(showlegend=True,title="BTC price anomalies - KMeans",xaxis_title="Time",yaxis_title="Prices",font=dict(family="Courier New, monospace")) fig.show()

The above chart fits rather well with the actual Bitcoin price reality because the last few weeks the price has been an anomaly. The algorithm clusters values above ~$13,000 as one class, and the values below this threshold – as another one.

Implementing Neural Networks and Autoencoders to Detect Anomalies on Bitcoin historical Price

Now let’s use an unsupervised learning technique with some help from Neural Networks (NN). This way is known to be much more flexible and accurate for anomaly detection.

Nowadays, NNs are the new norm because of their amazing power and great results. We’ll use them in an unusual way: we’ll utilize an LSTM (long short-term memory) NN and autoencoders to build an unsupervised learning model. The main goal of this approach is to improve the results obtained with the last model, and I plan to do so by modeling the distribution of our current dataset so as to learn more about its structure.

Generally speaking, anomaly detection problems can be addressed as classification or regression, depending on whether the dataset is labeled. What we’re going to do next is to model our dataset as a regression problem, where we’re going to quantify the reconstruction error of our network. This essentially means that we’re going to build a model that can reconstruct the normal behavior of the sequences in our dataset so a data point that has a high reconstruction error can be defined as an anomaly. The main idea to keep in mind is that frequent events are quite easy to reconstruct but the infrequent ones are not that simple. Therefore, the reconstruction error for the latter will be higher.

The Recurrent NNs – including LSTM – are specially designed to work with sequential data. They are great at remembering past data and, due to that important feature, accomplish better predictions. If you want to fully understand how they work, I recommend that you have a look at "Deep Learning with Python" by François Chollet.

Unfortunately, the LSTM network is not enough to achieve our goal, so we’ll need to add an autoencoder to our architecture. Let’s take a look at the basic autoencoder architecture:

An autoencoder is an artificial NN used to produce efficient data encodings in an unsupervised way. By doing this, the autoencoder can learn the most important features of the data collection. In our case, we’ll use its reconstruction capabilities to determine what’s an anomaly and what’s not. If it struggles with reconstructing certain observations, we can infer that these are anomalies.

By the way, I’m going to use the reconstruction loss as a means to measure how wrong a data point’s reconstruction is. What you’ll see next is the creation of an LSTM autoencoder with some help from Keras:

#Defining important parameters for the model creation tsteps = X_train.shape[1] nfeatures = X_train.shape[2] detector = Sequential() detector.add(layers.LSTM(128, input_shape=(tsteps, nfeatures),dropout=0.2)) detector.add(layers.Dropout(rate=0.5)) detector.add(layers.RepeatVector(tsteps)) detector.add(layers.LSTM(128, return_sequences=True,dropout=0.2)) detector.add(layers.Dropout(rate=0.5)) detector.add(layers.TimeDistributed(layers.Dense(nfeatures))) detector.compile(loss='mae', optimizer='adam')

Now, let’s fit the model on the training set:

checkpoint = ModelCheckpoint("/kaggle/working/detector.hdf5", monitor='val_loss', verbose=1,save_best_only=True, mode='auto', period=1) history1 = detector.fit(X_train,y_train,epochs=50,batch_size=128,verbose=1,validation_split=0.1,callbacks=[checkpoint],shuffle=False)

Nothing fancy so far except the ModelCheckpoint implementation. It saves the best model obtained during training. At the end of the process, this is what I’ve obtained (keep in mind that your results may vary slightly from these since every AI training cycle contains an element of randomness):

Epoch 50/50 157/157 [==============================] - ETA: 0s - loss: 0.0268 Epoch 00050: val_loss did not improve from 0.11903 157/157 [==============================] - 1s 7ms/step - loss: 0.0268 - val_loss: 0.1922

To load the best model obtained and evaluate it, issue these commands:

detector = load_model("detector.hdf5") detector.evaluate(X_test, y_test)

As a result, I’ve obtained:

174/174 [==============================] - 0s 3ms/step - loss: 0.0579

This is not bad at all. Now it’s time to set a static threshold, which is the simplest way to define whether or not a data point is an anomaly. In our case, any error higher than this threshold will be considered an outlier. We’re going to get the MAE loss for the training and test sets and visually determine what would be the appropriate threshold:

X_train_pred = detector.predict(X_train) loss_mae = np.mean(np.abs(X_train_pred - X_train), axis=1) #This is the formula to calculate MAE sns.distplot(loss_mae, bins=100, kde=True)

X_test_pred = detector.predict(X_test) loss_mae = np.mean(np.abs(X_test_pred - X_test), axis=1) sns.distplot(loss_mae, bins=100, kde=True)

As you can see in the above charts, observations higher than 0.150 become unusual but in some other scenarios this task is not as simple. For that reason, it's often best to use statistics to identify this value with more precision. Let's set that number as the threshold and determine what value is above it, so that we can plot the anomalies:

threshold = 0.15 test_df = pd.DataFrame(test[tsteps:]) test_df['loss'] = loss_mae test_df['threshold'] = threshold test_df['anomaly'] = test_df.loss > test_df.threshold test_df['Weighted_Price'] = test[tsteps:].Weighted_Price anomalies = test_df[test_df.anomaly == True] yvals1 = scaler.inverse_transform(test[tsteps:][['Weighted_Price']]) yvals1 = yvals1.reshape(-1) yvals2 = scaler.inverse_transform(anomalies[['Weighted_Price']]) yvals2 = yvals2.reshape(-1) fig = go.Figure() fig.add_trace(go.Scatter(x=test[tsteps:].index, y=yvals1,mode='lines',name='BTC Price')) fig.add_trace(go.Scatter(x=anomalies.index, y=yvals2,mode='markers',name='Anomaly')) fig.update_layout(showlegend=True,title="BTC price anomalies",xaxis_title="Time",yaxis_title="Prices",font=dict(family="Courier New, monospace")) fig.show()

This plot shows the anomalies in the test set:

The anomalies look promising. Let’s take a look at the entire dataset and plot the existing anomalies on it:

scaled_pano = test.append(train, ignore_index=False) X_shifted, y_shifted = shift_samples(scaled_pano[['Weighted_Price']], scaled_pano.columns[0]) X_shifted_pred = detector.predict(X_shifted) loss_mae = np.mean(np.abs(X_shifted_pred - X_shifted), axis=1) non_scaled_pano = pano.copy()[51000:] non_scaled_pano.fillna(method ='bfill', inplace = True) non_scaled_pano = non_scaled_pano[:-24] non_scaled_pano['loss_mae'] = loss_mae non_scaled_pano['threshold'] = threshold non_scaled_pano['anomaly'] = non_scaled_pano.loss_mae > non_scaled_pano.threshold pano_outliers = non_scaled_pano[non_scaled_pano['anomaly'] == True] fig = go.Figure() fig.add_trace(go.Scatter(x=non_scaled_pano.index, y=non_scaled_pano['Weighted_Price'].values,mode='lines',name='BTC Price')) fig.add_trace(go.Scatter(x=pano_outliers.index, y=pano_outliers['Weighted_Price'].values,mode='markers',name='Anomaly')) fig.update_layout(showlegend=True,title="BTC price anomalies - Autoencoder",xaxis_title="Time",yaxis_title="Prices",font=dict(family="Courier New, monospace")) fig.show()

And as you can see, the results match our expectations because the highlighted prices are very rare in the chart and also Bitcoin's entire price history (to see the Bitcoin historical price please check here). We'll keep this model as our anomaly detector for the rest of this series.

Next Step

In the next article, we are going to discuss forecasting on Bitcoin time series. Stay tuned!

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sergio Virahonda

 

 

 
United States 

Sergio Virahonda grew up in Venezuela where obtained a bachelor's degree in Telecommunications Engineering. He moved abroad 4 years ago and since then has been focused on building meaningful data science career. He's currently living in Argentina writing code as a freelance developer.

 

 

 

 

 

 

 

 

 

머신 딥러닝 관련 개발 프로세스

1. 딥러닝 학습 흐름도

 

2. 지도 학습 흐름도

 

3. 비지도 학습 흐름도

 

[Reference] : 졸리운_곰, 「SQL학습 및 DB설계 - 머신 딥러닝 관련 개발 프로세스」 https://www.stechstar.com/user/zbxe/?mid=study_SQL&document_srl=75356.

[자격소개] 국가기술 빅데이터 분석기사 

국가기술 빅데이터 분석기사

국가기술자격

 

관련 근거

국가기술자격법 및 동법 시행령

빅데이터분석기사 정의

빅데이터 이해를 기반으로 빅데이터 분석 기획, 빅데이터 수집·저장·처리, 빅데이터 분석 및 시각화를 수행하는 실무자를 말한다.

빅데이터분석기사의 필요성

전 세계적으로 빅데이터가 미래성장동력으로 인식돼, 각국 정부에서는 관련 기업투자를 끌어내는 등 국가·기업의 주요 전략분야로 부상하고 있다.

국가와 기업의 경쟁력 확보를 위해 빅데이터 분석 전문가의 수요는 증가하고 있으나, 수요 대비 공급 부족으로 인력 확보에 어려움이 높은 실정이다.

이에 정부차원에서 빅데이터 분석 전문가 양성과 함께 체계적으로 역량을 검증할 수 있는 국가기술자격 수요가 높은 편이다.

빅데이터분석기사의 직무

대용량의 데이터 집합으로부터 유용한 정보를 찾고 결과를 예측하기 위해 목적에 따라 분석기술과 방법론을 기반으로 정형/비정형 대용량 데이터를 구축, 탐색, 분석하고 시각화를 수행하는 업무를 수행한다.

 

과목 및 내용

필기

필기과목명주요항목세부항목세세항목빅데이터 분석 기획빅데이터의 이해데이터분석 계획데이터 수집 및 저장 계획빅데이터 탐색데이터 전처리데이터 탐색통계기법 이해빅데이터 모델링분석모형 설계분석기법 적용빅데이터 결과해석분석모형 평가 및 개선분석결과 해석 및 활용

빅데이터 개요 및 활용
  • 빅데이터의 특징
  • 빅데이터의 가치
  • 데이터 산업의 이해
  • 빅데이터 조직 및 인력
빅데이터 기술 및 제도
  • 빅데이터 플랫폼
  • 빅데이터와 인공지능
  • 개인정보 법·제도
  • 개인정보 활용
분석방안수립
  • 분석 로드맵 설정
  • 분석 문제 정의
  • 데이터 분석 방안
분석 작업 계획
  • 데이터 확보 계획
  • 분석 절차 및 작업 계획
데이터 수집 및 전환
  • 데이터 수집
  • 데이터 유형 및 속성 파악
  • 데이터 변환
  • 데이터 비식별화
  • 데이터 품질 검증
데이터 적재 및 저장
  • 데이터 적재
  • 데이터 저장
데이터 정제
  • 데이터 정제
  • 데이터 결측값 처리
  • 데이터 이상값 처리
분석 변수 처리
  • 변수 선택
  • 차원축소
  • 파생변수 생성
  • 변수 변환
  • 불균형 데이터 처리
데이터 탐색 기초
  • 데이터 탐색 개요
  • 상관관계 분석
  • 기초통계량 추출 및 이해
  • 시각적 데이터 탐색
고급 데이터 탐색
  • 시공간 데이터 탐색
  • 다변량 데이터 탐색
  • 비정형 데이터 탐색
기술통계
  • 데이터요약
  • 표본추출
  • 확률분포
  • 표본분표
추론통계
  • 점추정
  • 구간추정
  • 가설검정
분석 절차 수립
  • 분석모형 선정
  • 분석모형 정의
  • 분석모형 구축 절차
분석 환경 구축
  • 분석 도구 선정
  • 데이터 분할
분석기법
  • 회귀분석
  • 로지스틱 회귀분석
  • 의사결정나무
  • 인공신경망
  • 서포트벡터머신
  • 연관성분석
  • 군집분석
고급 분석기법
  • 범주형 자료 분석
  • 다변량 분석
  • 시계열 분석
  • 베이지안 기법
  • 딥러닝 분석
  • 비정형 데이터 분석
  • 앙상블 분석
  • 비모수 통계
분석모형 평가
  • 평가 지표
  • 분석모형 진단
  • 교차 검증
  • 모수 유의성 검정
  • 적합도 검정
분석모형 개선
  • 과대적합 방지
  • 매개변수 최적화
  • 분석모형 융합
  • 최종모형 선정
분석결과 해석
  • 분석모형 해석
  • 비즈니스 기여도 평가
분석결과 시각화
  • 시공간 시각화
  • 관계 시각화
  • 비교 시각화
  • 인포그래픽
분석결과 활용
  • 분석모형 전개
  • 분석결과 활용 시나리오 개발
  • 분석모형 모니터링
  • 분석모형 리모델링

실기

실기과목명주요항목세부항목세세항목빅데이터 분석 실무데이터 수집 작업데이터 전처리 작업데이터 모형 구축 작업데이터 모형 평가 작업

데이터 수집하기
  • 정형, 반정형, 비정형 등 다양한 형태의 데이터를 읽을 수 있다.
  • 필요시 공개 데이터를 수집할 수 있다.
데이터 정제하기
  • 정제가 필요한 결측값, 이상값 등이 무엇인지 파악할 수 있다.
  • 결측값와 이상값에 대한 처리 기준을 정하고 제거 또는 임의의 값으로 대체할 수 있다.
데이터 변환하기
  • 데이터의 유형을 원하는 형태로 변환할 수 있다.
  • 데이터의 범위를 표준화 또는 정규화를 통해 일치시킬 수 있다.
  • 기존 변수를 이용하여 의미 있는 새로운 변수를 생성하거나 변수를 선택할 수 있다.
분석모형 선택하기
  • 다양한 분석모형을 이해할 수 있다.
  • 주어진 데이터와 분석 목적에 맞는 분석모형을 선택할 수 있다.
  • 선정모형에 필요한 가정 등을 이해할 수 있다.
분석모형 구축하기
  • 모형 구축에 부합하는 변수를 지정할 수 있다.
  • 모형 구축에 적합한 형태로 데이터를 조작할 수 있다.
  • 모형 구축에 적절한 매개변수를 지정할 수 있다.
구축된 모형 평가하기
  • 최종 모형을 선정하기 위해 필요한 모형 평가 지표들을 잘 사용할 수 있다.
  • 선택한 평가지표를 이용하여 구축된 여러 모형을 비교하고 선택할 수 있다.
  • 성능 향상을 위해 구축된 여러 모형을 적절하게 결합할 수 있다.
분석결과 활용하기
  • 최종모형 또는 분석결과를 해석할 수 있다.
  • 최종모형 또는 분석결과를 저장할 수 있다.

 

출제문항수

출제기준(필기)

직무분야중직무분야자격종목필기검정방법문제수시험시간

정보통신 정보기술 빅데이터 분석기사
  • ○ 직무내용
    대용량의 데이터 집합으로부터 유용한 정보를 찾고 결과를 예측하기 위해 목적에 따라 분석기술과 방법론을 기반으로 정형/비정형 대용량 데이터를 구축, 탐색, 분석하고 시각화를 수행하는 업무를 수행한다.
객관식 80 120분

과목별 주요 항목

필기과목명문제수주요항목빅데이터 분석기획빅데이터 탐색빅데이터 모델링빅데이터 결과 해석

20
  • 빅데이터의 이해
  • 데이터 분석 계획
  • 데이터 수집 및 저장 계획
20
  • 데이터 전처리
  • 데이터 탐색
  • 통계기법 이해
20
  • 분석모형 설계
  • 분석기법 적용
20
  • 분석모형 평가 및 개선
  • 분석결과 해석 및 활용

출제기준(실기)

직무분야중직무분야자격종목실기검정방법시험시간

정보통신 정보기술 빅데이터 분석기사
  • ○ 직무내용
    대용량의 데이터 집합으로부터 유용한 정보를 찾고 결과를 예측하기 위해 목적에 따라 분석기술과 방법론을 기반으로 정형/비정형 대용량 데이터를 구축, 탐색, 분석하고 시각화를 수행하는 업무를 수행한다.
통합형(필답형, 작업형) 180분

과목별 주요 항목

실기과목명주요항목빅데이터 분석실무

  • 데이터 수집 작업
  • 데이터 전처리 작업
  • 데이터 모형 구축 작업
  • 데이터 모형 평가 작업

응시자격

응시자격

  • 다음 중 하나에 해당하는 사람
    1. 대학졸업자등 또는 졸업예정자 (전공 무관)
    2. 기사 등급 이상의 자격을 취득한 사람 (종목 무관)
    3. 3년제 전문대학 졸업자등으로서 졸업 후 1년 이상 직장경력이 있는 사람 (전공, 직무분야 무관)
    4. 2년제 전문대학 졸업자등으로서 졸업 후 2년 이상 직장경력이 있는 사람 (전공, 직무분야 무관)
    5. 기사 수준 기술훈련과정 이수자 또는 그 이수예정자 (종목 무관)
    6. 산업기사 수준 기술훈련과정 이수자로서 이수 후 2년 이상 직장경력이 있는 사람 (종목, 직무분야 무관)
    7. 4년 이상 직장경력이 있는 사람 (직무분야 무관)
    ※ 졸업증명서 및 경력증명서 제출 필요
  •  

비고

1. 대학 및 대학원 수료자로서 학위를 취득하지 못한 사람은 "대학졸업자등", 전 과정의 2분의 1 이상을 마친 사람은 "2년제 전문대학졸업자등"

2. "졸업예정자"란 필기시험일 기준으로 최종 학년에 재학 중인 사람

3. 최종 학년이 아닌 경우, 106학점 이상 인정받은 사람은 “대학졸업예정자”, 81학점 이상을 인정받은 사람은 “3년제 대학졸업예정자”, 41학점 이상을 인정받은 사람은 “2년제 대학졸업예정자” (이때 대학 재학으로 취득한 학점 이외의 자격증 취득 등 기타의 방식으로 18학점 이상 포함 필수)

4. 전공심화과정의 학사학위를 취득한 사람은 “대학졸업자”, 그 졸업예정자는 “대학졸업예정자”

5. "이수자"란 기사 수준 기술훈련과정 또는 산업기사 수준 기술훈련과정을 마친 사람

6. "이수예정자"란 국가기술자격 검정의 필기시험일 또는 최초 시험일 현재 기사 수준 기술훈련과정 또는 산업기사 수준 기술훈련과정에서 각 과정의 2분의 1을 초과하여 교육훈련을 받고 있는 사람

합격기준

필기시험 합격기준실기시험 합격기준

과목당 100점을 만점으로
1. 전 과목 40점 이상
2. 전 과목 평균 60점 이상
100점을 만점으로 60점이상
(시험의 일부 과정을 응시하지 않은 경우 득점에 관계없이 불합격)

응시자격 증빙서류

구분내용

제출서류 경력증명서 또는 재직증명서
최종학력증명서
자격증 사본

 

 

 

[출처] https://www.dataq.or.kr/www/sub/a_07.do

 

[Reference] : 졸리운_곰, 「SQL학습 및 DB설계 - [자격소개] 국가기술 빅데이터 분석기사」 https://www.stechstar.com/user/zbxe/?mid=study_SQL&document_srl=75407.

한림대 의대 자랑스러운 한림인 위촉 추천

 

 

 

 

"김현철 정신과의사, 만나면 모텔가기 바빴다"…환자 성관계 논란

[중앙일보] 입력 2019.05.29 06:30   수정 2019.05.29 20:57

[PD수첩]

배우 유아인을 상담도 하지 않고 '경조증'이라 공개 진단해 논란을 일으켰던 정신과 의사가 환자들과 성관계를 맺는 등 '그루밍 성폭력'을 가했다는 의혹이 불거졌다. MBC 'PD수첩'은 '스타 의사'로 알려진 김현철 정신건강의학과의원 원장에 관한 의혹을 제기했다.
 
김 원장은 2018년 이전까지 각종 매체에 출연하며 전국 각지의 환자들을 상담했다. 김 원장을 찾는 환자는 하루 100명에 육박했다. 
 
그런 김 원장에게 피해를 입었다는 여성 환자 2명이 PD수첩을 통해 목소리를 냈다. 
 

"김 원장, 성폭행 후 성관계 제안" 

[PD 수첩]

환자 A씨는 "김 원장이 갑작스레 일본 여행을 제의했다"며 "일본에서 성폭력을 당했고 그 이후로 여러 차례 성관계 제안을 거부하지 못했다"고 주장했다.
 
A씨는 "눈을 떠보니 김현철이 옆에 누워서 안고 몸을 만지고 있었다"며 "어떤 액션을 취해야 할지도 모르겠고 내가 여기서 싫다고 하거나 '왜 이러세요?'라고 하면 되게 이상해질 것 같고 나중에 치료에도 영향을 줄 것 같았다"고 설명했다. 또, "만나면 모텔로 가기 바빴다"며 "그가 '너는 나를 잠자리 대상으로만 생각하니'라고 물어 고민하기도 했다"고 털어놨다.
 
환자 B씨도 자신이 김 원장에게 호감을 표시하자 김 원장이 바로 성관계를 제안했다고 밝혔다. B씨는 "치료 기간 중 다섯 차례 이상 성관계를 가졌다"며 "성관계를 거부하지 못했다"고 주장했다.
 
정신과 의사는 취약한 상태의 환자와 특별한 관계를 맺어서는 안 된다. 환자가 자신을 치료하는 의사에게 특별한 감정을 느끼는 '전이'로 인해 의사를 크게 신뢰하거나 연인 같은 감정을 느끼는 일이 치료 과정 중 일어날 수 있기 때문이다. 
 

[PD 수첩]

이런 '전이' 현상을 악용해서는 안 된다는 것은 정신과 의사들의 불문율이다. 해외에서는 정신과 의사와 환자와의 성접촉을 성범죄로 규정하고 엄격하게 금지하고 있다. 이 때문에 전문가들은 김 원장의 이런 행위는 연애가 아닌 '정신적 갈취'라고 지적했다.
 
김 원장은 'PD수첩' 제작진에게 '내가 성폭행을 한 게 아니라 당했다'는 취지로 해명했다. 김 원장은 "여자분이 당할 수도 있지만 그 반대일 수도 있다"며 '두 분이 달라붙어 자신이 당했다'고 주장했다. 그는 '5회 성관계를 맺은 여성과도 모두 원치 않은 상태에서 이루어졌냐'는 질문을 받고는 "진짜 당연하다"고 거듭 주장했다.
 

"김 원장, 매사가 음담패설…마약류 과다 처방도"  

이날 방송에서는 김 원장이 병원 직원들에게도 성적인 발언을 일삼았다는 폭로가 나왔다. 그의 병원에서 일했던 직원은 "매사에 하는 말들이 음담패설"이라며 "저한테 시계 같은 것을 보여 주면서, 자기의 성기가 이렇게 굵고 크다고 했다"고 폭로했다. 또 다른 전 직원은 "(김 원장이) '옷을 야하게 입고 왔다'는 말을 하더라"고 전했다.
 
병원에 근무했던 직원에 따르면 그는 국민건강보험공단에 수천만 원에 달하는 급여를 허위 청구하기도 했다. 식약처가 2~3주 내 단기처방을 권고한 마약류 의약품을 한 번에 6개월 치 가량 처방했다는 증언도 나왔다.
 

[PD 수첩]

김 원장은 지난 2017년 배우 유아인이 페미니즘 논쟁을 벌일 당시 유아인과 상담도 하지 않고 SNS로 공개 진단해 논란을 일으켰다. 당시 김 원장은 유아인이 일주일 째 트위터로 논쟁을 벌이는 것에 대해 "지금 급성 경조증을 유발할 가능성이 있다"며 "사고 비약 및 과대 사고 같은 보상 기전이 보인다. 유아인님의 경우 이론상 내년 2월이 가장 위험하다"는 소견을 공개했다.

 
대한신경정신의학회 윤리위원회는 김 원장을 불러 이러한 사안을 조사했고, 지난해 3월 말 학회 설립 이래 최초로 회원을 제명했다.
 
정은혜 기자 jeong.eunhye1@joongang.co.kr

 

[원본] https://news.joins.com/article/23482477#none

거룩한 한림대 의예과 2022 수시 정시

춘천한림대성심병원

다시 한림대를 위대하게 위대하게 한림대

돈버는 이익사업으로 아픈사람에게 갑질로 갈취하는

이익추구 병(신)원 : 매출은 순위에도 없지만 이익은 국내 5위권에 듬

 

 

[돌팔이 한림대 의대생 필독] 한림대 김현철 정신과 의사 :

너희들도 역시 다르지 않다.

 

한림대 의예과 수시 정시 2022

 

 

김현철 정신과의사 한림대 교수 위촉

김현철 정신과의사 그는 누구인가?

 

 

위대한 한림대 의예과의 진실-김현철정신과의사와 함께하는 의과대학의 진실-정신과의사의 동료들 정시 수시 군기 입시, QS 세계대학평가,  성심병원, 대학병원, 한림대병원, 한림대 영웅상, 커트라인, 한눈에 보기

 

한림대 의예과 수시 정시 2022

 

 

교육목적 및 교육목표

의예과/의학과

한림대학교 의과대학 의예과 및 의학과는풍부한 인간성과 창조적 지성을 지닌 좋은 의사를 양성함을 교육목적으로 한다.

[좋은 의사란, 환자를 보살피는 일을 최고의 관심사로 여기고, 최신의 지식과 술기를 익히고 행할 수 있으며, 환자는 물론 동료와 좋은 관계를 유지하고, 정직과 신뢰를 바탕으로 성실하게 행동하는 의사를 말한다.]

 

교육목적을 달성하기 위하여 다음과 같은 의사를 양성함을 교육목표로 한다.

1. 환자중심의 진료 및 소통능력을 갖춘 의사

2. 올바른 윤리관을 갖추고 전문가적 능력을 지속 개발하는 의사

3. 사회, 국가, 인류에 공헌하는 의사

4. 국제사회 활동 능력을 갖춘 의사

 

[출처]https://www.hallym.ac.kr/hallym_univ/sub01/cP7.html

 

 

진실을 왜곡하고 욕설과 비방으로 일관하는 한림대의 본성을 알아보자.

김현철 정신과의사의 사례

 

 

 

돌파리 명문 한림대 정신과의사 교수 강의 왈 (曰) 가로사대 "위대하게 미쳤다고 낙인찍구 그렇게 부르고 조롱하면 진짜 미친다는데"

 

똥먹은 것처럼 강의하는 한림대 정신과 교수 강의 이런 강의 맞아?

 

원래 한림대 정신과 교수라는게 정신 이상한 게 똥먹으면 하는거야?

 

그래서 한림대 의대생. 의사 들도 정신이 이상한거야?

 

그 결과중의 하나가 한림대 김현철 정신과 의사래!

 

정신이 이상한 강의하고 시험보고 하니 한림대 의대, 의사, 교수의 정신이 정상인지 비정상인지 입증부터 해봐라!

 

위대하게 잘난 한림대학교 중긴/기말/졸업시험은 4지 나 5지 선다인 객관식에 절대 평가라며? 중딩, 고딩들이냐?

 

무고죄 고소장

[   ]

요약 무고죄로 손해를 준 사람에 대한 심판을 법원, 검찰 등 수사 기관에 요청하는 양식

이미지 크게보기

서식 구성항목

고소인 명, 고소인 인적 사항, 피고소인 명, 피고소인 인적 사항, 고소 취지, 고소 사실, 고소일자 명

무고죄의 피해자, 피해자의 법정대리인이나 고소권을 가진 수사기관이 피해를 본 사실을 신고하여 심판을 신청하는 양식이다. 고소는 수사를 촉구하는 행위로 기소와는 의미가 다르며, 양식이나 구술로 검사 또는 경찰관 혹은 대리인을 통해서 가능하다.

피고소인의 기본 인적사항이나 정보 사항을 분명하게 알지 못하는 경우 피고소인의 성별, 외모의 특징, 인상착의 등을 구체적으로 기재해야 한다. 피해 사실은 일시, 장소, 내용, 결과 등을 구체적으로 기재해야 한다.

작성팁

• 피해 사실은 일시, 장소, 내용, 결과 등을 구체적으로 기재해야 한다.
• 고소이유에는 피고소인의 범행 경위 및 정황, 고소를 하게 된 동기와 사유 등 범죄사실을 뒷받침하는 내용을 간략, 명료하게 기재한다.
• 제출할 증거의 세부내역은 별지를 작성하여 첨부한다.
• 고소장 제출일을 기재해야 하며, 고소인 란에는 고소인이 직접 자필로 서명해야 한다.

관련서식

고소장, 고발장, 고소취하서, 고소장 취하서

관련이미지

무고죄 고소장 서식 구성항목출처: 예스폼 서식사전 (촬영: )

[네이버 지식백과] 무고죄 고소장 [誣告罪 告訴狀] (비즈폼 서식사전)

Expert Systems in Prolog [프롤로그에서의 전문가 시스템 구현]

Expert Systems in Prolog

Introduction

An expert system emulates the decision-making ability of a human expert.

Prolog is very well suited for implementing expert systems due to several reasons:

  • Prolog itself can be regarded as a simple inference engine or theorem prover that derives conclusions from known rules. Very simple expert systems can be implemented by relying on Prolog's built-in search and backtracking mechanisms.
  • Prolog data structures let us flexibly and conveniently represent rule-based systems that need additional functionality such as probabilistic reasoning.
  • We can easily write meta-interpreters in Prolog to implement custom evaluation strategies of rules.

Example: Animal identification

Our aim is to write an expert system that helps us identify animals.

Suppose we have already obtained the following knowledge about animals, which are rules of inference:

  • If it has a fur and says woof, then the animal is a dog.
  • If it has a fur and says meow, then the animal is a cat.
  • If it has feathers and says quack, then the animal is a duck.

These rules are not exhaustive, but they serve as a running example to illustrate a few points about expert systems.

The key idea of an expert system is to derive useful new information based on user-provided input. In the following, we see several ways to do this in Prolog.

Direct Prolog implementation

We now consider an implementation that uses Prolog rules directly to implement the mentioned inference rules.

This is straight-forward, using is_true/1 to emit a question and only proceeding with the current clause if the user input is the atom yes:

animal(dog) :- is_true('has fur'), is_true('says woof'). animal(cat) :- is_true('has fur'), is_true('says meow'). animal(duck) :- is_true('has feathers'), is_true('says quack'). is_true(Q) :- format("~w?\n", [Q]), read(yes).

There is a clear drawback of this approach, which is shown in the following sample interaction:

?- animal(A). has fur? |: yes. says woof? |: no. has fur? |: yes. says meow? |: yes. A = cat .

The system has asked a question redundantly: Ideally, the fact that the animal does have a fur would have to be stated at most once by the user.

How can we best implement this? It is tempting to mess with the global database somehow to store user input over backtracking. However, changing a global state destroys many elementary properties we expect from pure logical relations and is generally a very bad idea, so we don't do it this way.

 

Using a domain-specific language

To solve the shortcoming explained above, we will now change the representation of our rules from Prolog clauses to a custom language that we write and interpret a bit differently than plain Prolog. A language that is tailored for a specific application domain is aptly called a domain-specific language (DSL).

We shall use the following representation to represent the knowledge:

animals([animal(dog, [is_true('has fur'), is_true('says woof')]), animal(cat, [is_true('has fur'), is_true('says meow')]), animal(duck, [is_true('has feathers'), is_true('says quack')])]).

The inference rules are now represented by terms of the form animal(A, Conditions), by which we mean that A is identified if all Conditions are true. Note especially that using a list is a clean representation of conditions.

It is a straight-forward exercise to implement an interpreter for this new representation. For example, the following snippet behaves like the expert system we saw in the previous section, assuming is_true/1 is defined as before:

animal(A) :- animals(As), member(animal(A,Cs), As), maplist(call, Cs).

Notably, this of course also shares the mentioned disadvantage:

?- animal(A). has fur? |: yes. says woof? |: no. has fur?

Now the point: We can interpret these rules differently by simply changing the interpreter, while leaving the rules unchanged. For example, let us equip this expert system with a memory that records the facts that are already known because they were already entered by the user at some point during the interaction.

We implement this memory in a pure way, by threading through additional arguments that describe the relation between states of the memory before and after the user is queried for additional facts. For convenience, we are using DCG notation to carry around the state implicitly.

Here is an implementation that does this:

animal(A) :- animals(Animals), Known0 = [], phrase(any_animal(Animals, A), [Known0], _). any_animal([Animal|Animals], A) --> any_animal_(Animal, Animals, A). any_animal_(animal(A0, []), Animals, A) --> ( { A0 = A } ; any_animal(Animals, A) ). any_animal_(animal(A0, [C|Cs]), Animals, A) --> state0_state(Known0, Known), { condition_truth(C, T, Known0, Known) }, next_animal(T, animal(A0,Cs), Animals, A). next_animal(yes, Animal, Animals, A) --> any_animal([Animal|Animals], A). next_animal(no, _, Animals, A) --> any_animal(Animals, A). state0_state(S0, S), [S] --> [S0].

It is only left to define condition_truth/4: Depending on what is already known, this predicate either uses the existing knowledge or queries the user for more information.

To distinguish these two cases in pure way, we use the meta-predicate if_/3:

condition_truth(is_true(Q), Answer, Known0, Known) :- if_(known_(Q,Answer,Known0), Known0 = Known, ( format("~w?\n", [Q]), read(Answer), Known = [known(Q,Answer)|Known0])). known_(What, Answer, Known, Truth) :- if_(memberd_t(known(What,yes), Known), ( Answer = yes, Truth = true ), if_(memberd_t(known(What,no), Known), ( Answer = no, Truth = true), Truth = false)).

And thus, at last, the question no longer appears redundantly:

?- animal(A). has fur? |: yes. says woof? |: no. says meow? |: yes. A = cat .

Separating the knowledge base from the way it is interpreted has allowed us to add features while leaving the inference rules unchanged.

 

Using a different DSL

Consider now yet another way to solve the exact same problem. Let us view the animal identification task as interpreting the following decision diagram, where dotted lines indicate no, and plain lines indicate yes:

 

In this case, the diagram is in fact a full binary tree which can be represented naturally using Prolog terms. For example, let us represent the decision tree as follows, using a term of the form if_then_else/3 for each inner node, and animal/1 and false/0 for leaves:

tree(if_then_else('has fur', if_then_else('says woof', animal(dog), if_then_else('says meow', animal(cat), false)), if_then_else('has feathers', if_then_else('says quack', animal(duck), false), false))).

Other kinds of decision diagrams can also be represented efficiently with Prolog terms.

Such trees can be interpreted in a straight-forward way, using again the definition of is_true/1 to query the user:

animal(A) :- tree(T), tree_animal(T, A). tree_animal(animal(A), A). tree_animal(if_then_else(Cond,Then,Else), A) :- ( is_true(Cond) -> tree_animal(Then, A) ; tree_animal(Else, A) ).

Note: This fragment uses the impure if-then-else construct. This is logically sound only if the condition is sufficiently instantiated, so that its truth can be safely determined without prematurely committing to one branch.



Since each question appears at most once on every path from the root to a leaf, it is not necessary to keep track of which questions have already been answered:

?- animal(A). has fur? |: yes. says woof? |: no. says meow? |: yes. A = cat.

Comparison of approaches

We have now seen three different ways to implement an expert system in Prolog:

  • direct Prolog implementation
  • devising and interpreting a domain-specific language
  • using a completely different domain-specific language.

Each of these approaches was rather easy to implement in Prolog, and there are several other DSLs that would also be suitable. The question thus arises: Which DSL, if any, should we choose to implement expert systems in Prolog? Let us briefly consider the main points we have seen:

  1. Using Prolog directly is straight-forward. However, a naive implementation has a few drawbacks. In our case, the same question was unnecessarily asked repeatedly.
  2. Using a domain-specific language lets us cleanly separate the main logic of the expert system from additional features, such as keeping track of already answered questions.
  3. A DSL based on decision diagrams is very easy to interpret and automatically avoids redundant questions.

From these points alone, option (3) seems very attractive. However, it also raises a few important questions: First, how was the decision diagram even obtained, and does it faithfully model the conditions we want to express? It is rather easy to do it by hand in this example, but how would you do it in more complex cases? Second, how costly is the transformation from a rather straight-forward fact base as in option (2) to using decision diagrams instead? Third, is this really a good diagram, and what do we even mean by good? Are there orderings of nodes that let us reduce the number of questions? In the worst case, on average, in the best case? Fourth, how extensible is the language of decision diagrams? For example, can all animal identification tasks be modeled in this way? etc.

These questions show that the best choice depends on many factors.

 

[출처] https://www.metalevel.at/prolog/expertsystems

 

 

[C/C++] (고전 기호 처리 인공지능)  전문가 시스템

전문가 시스템

(Expert System)

 

C 인공지능 프로그래밍 : Herbert Schildt 지음, 신경숙.류성렬 옮김, 세웅, 1991 (원서 : Artificial Intelligence using C, McGraw-Hill, 1987), page 93~153

 

1. 전문가시스템 이란 무엇인가? (WHAT IS AN EXPERT SYSTEMS?)

    (1) 전문가시스템의 장점은 무엇인가? (What Are the Advantages of an Expert Systems ?)

    (2) 상업적 전문가시스템 몇가지 예 (Some Examples of Commercial Expert Systems)

2. 전문가시스템은 어떻게 동작하는가? (HOW EXPERT SYSTEMS WORK?)

     (1) 지식베이스 (The Knowledge Base)

     (2) 추론기관 (The Inference Engine)    

3. 만능 전문가시스템 만들기 (CREATING A GENERAL-PURPOSE EXPERT SYSTEM)

  3.1. 연산의 본질적 요소 (The Essential of Operation)

  3.2. 지식베이스 구조화 (Structuring the Knowledge Base)

  3.3. 지식베이스 로드 (Loading the Knowledge Base)

  3.4. 추론기관 구현 (Implementing the Inference Engine)

  3.5. 시범 수행 (A Sample Run)

  3.6. 복수의 해 찾기 (Finding Multiple Solutions)

4. 더 복잡한 버전 (A MORE SOPHISTICATED VERSION)

5. 지식 엔지니어링 (KNOWLEDGE ENGINEERING)

  5.1. 지식베이스 구조 (Knowledge Base Organization)

  5.2. 전문가 찾기 (Finding the Expert)

  5.3. 지식베이스 확증 (Verifying the Knowledge Base)

 

전문가시스템은 주로 두 가지 이유 때문에 흥미롭다 : 먼저, 현실의 요구를 수행하는, 일반적으로 유용하고 실용적인 프로그램이다. 두 번째로, 실현할 수가 있다. 이것이 바로 전문가시스템이 AI 의 상업적 성공의 대부분을 차지하는 이유이다. 이 장의 첫 부분은 전문가시스템이 무엇인가 하는 것과 수행할 수 있는 여러 가지 방법을 보여줄 것이다. 이 장의 두 번째 부분은 완전하고, 일반적인 목적으로 사용되는 전문가시스템을 하나 개발한다.

1. 전문가시스템 이란 무엇인가? (WHAT IS AN EXPERT SYSTEMS?)

 

제 1 장에서 언급했듯이, 전문가시스템은 사람 전문가의 행위를 흉내내는 프로그램이다. 사용자가 어떤 주제에 관한 견해를 표현하기 위하여 제공하는 정보를 사용한다. 그러므로, 전문가시스템은 대답과 일치하는 대상을 알아낼 수 있을 때까지 질문을 한다. 전문가시스템이 무엇인지 이해하기 위하여, 과일 전문가와 충고를 구하는 어떤 사람 사이의 다음 대화를 생각해 보자.

컴퓨터화된 과일 전문가시스템의 목표는 이 대화를 재생산하는 것이다. 더 일반적으로, 전문가시스템은 전문기술의 주제에 관해 사용자에게 충고를 하려고 시도한다.

(1) 전문가시스템의 장점은 무엇인가? (What Are the Advantages of an Expert Systems ?)

전문가시스템의 바람직함은 주로 유용성과 편의성에 기초한다. 잠을 자고, 먹고, 피로를 풀고, 쉬고 하는 등의 일을 해야 하는 사람 전문가와는 달리, 전문가시스템은 하루 24 시간, 연중 매일 사용하기에 유용하다. 또한, 사람 전문가의 수는 제한 될 수 있는 반면, 많은 전문가시스템은 만들어질 수 있다. 더욱이, 사람과는 달리, 컴퓨터화된 전문가는 절대 죽지 않고 지식을 취한다. 전문가시스템에 있는 지식은, 쉽게 복사되고 저장될 수 있으므로, 전문 지식의 영구적인 손실이 거의 드물다.

사람 전문가에 비해 전문가시스템의 또 다른 장점은, 컴퓨터화된 전문가는 항상 성능이 최고하는 것이다. 사람 전문가가 지칠 때, 전문가 충고의 신뢰성은 사라질런지도 모른다. 그러나 컴퓨터화된 전문가는 항상 최상의 견해 - 자기의 지식의 제한 범위 안에서 - 를 생성할 것이다.

전문가시스템의 덜 중요한 장점은 개성이 부족하다는 것이다. 아마도 알겠지만, 개성은 항상 적합한 것은 아니다. 전문가와 친하게 지내지 않는다면, 전문가의 지식을 이용하는 것이 마음 내키지 않을 수도 있다. 반대 상황도 생길수 있다 : 좋아하지 않는 사람 전문가는 신뢰할 만한 정보를 표현할 수 없을 지도 모른다. 그러나 컴퓨터화된 전문가는 개성을 갖지 않는다. 따라서 이러한 문제들은 제거된다.

전문가시스템의 마지막 장점은, 컴퓨터화된 전문가가 존재한 후, 단순히 한 기계에서 다른 기계로 프로그램을 복사함으로써 새로운 전문가를 만들어 낼 수 있다. 사람은 어떤 분야에서 전문가가 되기 위하여 오랜 기간을 필요로 하는데, 이것 때문에 사람 전문가를 얻기가 힘들다.

(2) 상업적 전문가시스템 몇가지 예 (Some Examples of Commercial Expert Systems)

MYCIN 이라는 전문가시스템이 없다면, 전문가시스템은 AI 연구실에 머물고 외부로 진출하지 못했을런지도 모른다. AI 의 가장 커다란 관념 문제들 (image problems) 중의 하나는, 다른 프로그래머를 포함해서 많은 사람들이 AI 기법은 엄밀한 규칙들과 가정을 요구하는 문제에 대해서만 작동한다고 믿는 것이었다. 이 사람들은 AI 는 결코 어려운 문제를 해결하기 위하여 사용될 수 없다고 믿었다. MYCIN 은 그 모두를 변화시켰다.

MYCIN 은 세계에서 최초로 성공한 전문가시스템이다. 1970 년대 중반에 스탠포드 대학에서 개발되었는데, 의사가 어떤 세균성 질병을 진단하는 것을 돕기 위하여 설계하였다. 병을 진단하는 것은 본질적으로 환자가 제시하는 증상과 병의 징후 사이에 일치가 발견될 때까지 그 둘을 비교하는 일이다. 문제는, 의사가 존재하는 모든 병을 빠르고 자신있게 진단하는 것은 어렵다는 것이다. MYCIN 은 진단을 확실히 해줌으로써 이 요구를 만족시켰다.

상업적으로 생존가능한 전문가시스템의 또 다른 예는, 1978 년 리차드 듀다, 피터, 하드, 그리고 레네 레보우가 만든 PROSPECTOR 이다. PROSPECTOR 는 지질학에서의 전문가이다 : 어떤 광상이 어떤 특별한 지역에서 발견될수 있는 가능성을 예측한다. 석유, 천연가스, 헬륨의 발견을 예측하는 프로그램을 포함하여, 이 프로그램을 변형한 것이 여럿 있다.

1980 년대 초에는 세금 상담, 보험 충고, 법적인 도움을 줄 수 있는 전용 전문가시스템들이 도입되었다. 많은 프로그래머들은 1980 년대 말까지, 집이나 사무실에서 사용될 수 있는 "개인의" 전문가시스템의 커다란 시장이 생길 것이라고 믿는다. 이 시스템들은 원예 에서부터 자동 수리까지 많은 분야에서 전문가가 될 것이다. 실상, 전문가시스템은 개인용 컴퓨터에서 수행되는 가장 흔한 유형의 프로그램임을 증명할 수도 있다.

2. 전문가시스템은 어떻게 동작하는가? (HOW EXPERT SYSTEMS WORK?)

모든 전문가시스템은 지식베이스와 추론기관의 두 부분을 갖는다. 이 절에서는 전문가시스템이 두 부분을 모두 구현할 수 있는 여러 가지 서로 다른 방법들을 설명한다.

(1) 지식베이스 (The Knowledge Base)

지식베이스는 어떤 주제에 대하여 특정한 정보와 규칙을 갖는 데이터베이스이다. 이 설명을 위해, 알아야 하는 두 가지 용어가 있다 :

  • 대상 (Object) : 관련된 규칙으로 정의한 결론
  • 속성 (Attribute) : 규칙과 함께 대상을 정의하는 것을 돕는 특정한 성질

그러므로, 지식베이스를 대상 사이의 관련 규칙과 속성을 갖는 일련의 대상으로 생각해보자. 가장 간단한 의미에서 (그리고, 많은 응용을 위하여), 속성에 적용되는 규칙은, 대상이 그 속성을 "갖는다 (has)" 또는 "갖지 않는다 (has not)" 라는 것을 말해준다. 그러므로, 대상이 소유하거나 소유하지 않는 일련의 속성을 사용하여 대상을 정의할 수 있다. 예를 들어, 여러 가지 유형의 과일을 나타내주는 전문가시스템은 다음과 같은 지식베이스를 가질 것이다.

대상

규칙

속성

사과

갖는다

갖는다

갖지 않는다

갖는다

나무에서 자란다

둥근 모양이다

아주 남쪽에서 자란다

빨간색이나 노란색이다

포도 

갖는다

갖는다

갖는다

갖지 않는다

갖는다

덩굴에서 자란다

작은 크기이다

자주색이다

가시있는 덩굴이다       

포도주를 만들 수 있다

갖는다

갖는다

갖지 않는다

갖는다

나무에서 자란다

둥근 모양이다

북부에서 자란다

오렌지색이다

이 지식베이스에 대하여 생각해 보면, 간단해질 수 있다는 것을 알 수 있다. 단 한가지 규칙 - "갖는다" - 만 사용할 수 있고, "갖지 않는다" 관계를 설정해야 한다면 그 속성의 부정형을 사용할 수 있다. 그러므로, 규칙은 단순히 "소유한다 (possesses)" 가 되고, 간단해진 지식베이스는 다음과 같다 :

대상

소유

사과

나무에서 자란다

둥근 모양이다

아주 남부에서 자라지 않는다

빨간색이나 노란색이다

포도

덩굴에서 자란다

작은 크기이다

색이 가지각색이다

덩굴에는 가시가 없다

포도주를 만들기 위해 사용될수 있다.

나무에서 자란다

둥근 모양이다

북부에서 자랄수 있다

오렌지색이다

비록 어떤 복잡한 전문가시스템은 단순히 "소유한다" 보다 더 복잡한 규칙을 필요로 할지도 모르지만, 이 규칙은 많은 상황에 대하여 충분하고 지식베이스를 크게 간략화시킨다. 이 책의 나머지에서는 지식베이스가 대상과 속성으로만 이루어졌다고 가정한다.

(2) 추론기관 (The Inference Engine)

추론기관은 일치하는 대상을 찾기 위하여 제공하는 정보를 사용하려고 시도하는 전문가시스템의 일부분이다. 추론기관의 두가지 넓은 부류가 있다 ; 결정적 (deterministic) 과 확률적 (probabilistic). 이 두 부류 사이의 차이점을 이해하기 위해서, 두 전문가를 생각해보자 - 하나는 화학에서 다른 하나는 사회학에서. 화학자는 문제의 원자가 두 개의 전자 (electron) 을 가지면 헬륨 원자라고 확실히 (certainty) 보고할 수 있다. 전자의 수가 원소의 유형을 결정하기 때문에 원자의 명명에 대해서는 의심이 없다. 그러나, 사회학자에게, 학생들이 학교를 그만두는 것을 막는 최선의 방법이 무엇이냐고 물으면, 사회학자는 단지 있을 수 있는 것으로, 또는 어떤 성공률을 갖는 것으로 제한된 대답을 할 것이다. 그러므로, 대답은 가능성은 있지만 불확실하다.

대부분은 결정적이 아니고 오히려 어느 정도 확률적이다. 그러나, 이 중 많은 수에 대하여, 그것들을 결정적인 상황으로 다룰 수 있기 때문에 불확실성 요인이 통계적으로 중요하지 않다. 이 장의 나머지 부분에서는 결정적 전문가시스템의 논리가 더 명확하므로 그것만을 다룬다 (그러나, 제 8 장에서는 확률과 불확실성을 다룬다.)

확실성과 불확실성이라는 두 가지 넓은 부류 외에, 추론기관을 구성하는 세가지 기본 방법이 있다 : 전진추론, 후진추론, 규칙값. 이 방법들의 차이점은 추론이 목표에 도달하려고 시도하는 방법에 관계된다.

전진연쇄는 데이터 지향 (data-driven) 이라고도 부른다. 왜냐하면 추론기관이 대상물 (object) 인 종단 지점에 도달할 때까지 논리 AND 와 OR 들의 네트워크를 통해 이동하도록 사용자가 제공하는 정보를 추론기관이 사용하기 때문이다. 만약 추론기관이, 존재하는 정보를 사용하여 대상을 발견할 수 없다면, 더 많은 정보를 요구한다. 대상을 정의하는 속성은 대상으로 유도하는 경로를 만든다 : 대상에 도달하는 유일한 방법은 모든 규칙을 만족하는 것이다. 그러므로, 전진연쇄 추론기관은 어떤 정보를 가지고 시작하여 그 정보에 맞는 대상을 발견하려고 한다.

전진연쇄가 어떻게 작동하는지 이해하기 위하여, 차가 작동하지 않는 문제에 대한 견해를 듣기 위하여 이 경우의 전문가인 기능공에게 전화를 건다고 생각해 보자. 기능공은 무엇이 잘못되었는지 설명해 줄 것을 요구한다. 차에 전력손실과 노킹 (knocking) 이 있다는 것과, 때때로 점화 장치를 끈 후에도 계속 운전했으며, 여러 달 동안 엔진을 조정해 주지 않았다는 것을 설명한다. 이 정보를 이용하여 기능공은 차는 엔진 조정을 절실히 필요로 할 가능성이 가장 높다고 말한다.

만약 앞에서 설명된 과일 지식베이스로 되돌아가 생각한다면 그림 1 에서처럼 적절한 속성이 주어질 때 전진연쇄 추론기관이, 대상인 사과에 어떻게 도달하는가를 보이는 다이아그램을 만들 수 있다. 보는 바와 같이, 전진연쇄 시스템은 기본적으로 잎 노드로부터 루트 노드로 트리를 만든다.

그림 1  대상물 사과 (apple) 를 향한 전진연쇄 방법

후진연쇄는 전진연쇄의 반대이다. 후진연쇄 추론기관은 가설 (hypothesis) 을 갖고 시작하여 그것을 확신하거나 부정하기 위한 정보를 요구한다. 후진연쇄는, 전문가시스템이 한 대상을 갖고 시작하여 그것을 확증하려고 하기 때문에, 때때로 목적 지향 (object-driven) 이라고 부른다.

후진 연쇄가 어떻게 작동하는지 이해하기 위해서, 컴퓨터가 갑자기 작동을 멈추었다고 생각해보자. 첫 번째 가설은 전력이 나갔다는 것이다. 이를 체크하기 위해, 환풍기 (fan) 소리를 들어본다. 환풍기가 돌아가는 소리가 들리면 이 가설을 거절하고 다른 것으로 진행한다. 두 번째 가설은 잘못된 소프트웨어 때문에 컴퓨터가 고장났다는 것이다. 이 가능성을 확인하거나 거절하기 위하여, 컴퓨터가 성공적으로 다시 부트될 것인지 알기 위하여 리셋시킨다. 다행히도, 컴퓨터는 재부트된다 ; 두 번째 가설은 참이 된다.

문제의 과일이 사과이면, 과일 지식베이스에 후진연쇄 추론을 적용하여 그림 2 에 있는 도표를 만들어낸다. 도표가 보여주듯이, 후진연쇄는 트리를 자른다. 이것은 트리를 구성하는, 전진연쇄의 반대 과정이다.

그림 2  대상물 사과 (apple) 를 향한 후진연쇄 방법

규칙값 추론기관은 시스템의 현재 상태에 따라 가장 큰 중요성을 갖는 정보를 요구하기 때문에 전진연쇄나 후진연쇄 시스템보다 이론상 우수하다. 규칙값 추론기관은 실제로 개선된 후진연쇄 기관이다. 일반적인 작동 원리는 이렇다. 즉, 시스템으로부터 불확실성이 가장 큰 것을 제거할 (remove the most uncertainty from the system) 정보를 다음 정보로서 시스템이 요구한다.

규칙값 접근 방식을 이해하기 위해, 아이가 아파서 의사를 불렀다고 생각해보자. 의사는 먼저 아이가 열이 있는지를 묻는데, 이것은 이 질문에 대한 많은 가능성의 수를 줄이기 때문이다. 당신이 "예" 하고 대답하면, 의사는 당신에게 아이가 구토를 느끼는 지를 묻는다. 첫 번째 질문에서처럼, 의사는 현재 상태가 주어져 있을 때 그 대답이 진단에 가장 큰 영향을 미치기 때문에 다른 질문들보다 이 질문을 한다. 이 과정은 의사가 진단을 할수 있을 때 까지 계속된다. 이 예에서, 중요한 점은 의사가 결론까지 가장 빠르게 이를 질문들을 선택한다는 것이다.

규칙값 시스템의 문제점은 구현하기가 어렵다는 것이다. 여기에는 두가지 이유가 있다 : 우선, 실생활에서, 지식베이스는 종종 너무 커서 가능한 조합의 수는 시스템 용량을 초과한다. 그러므로, 시스템은 어떤 주어진 상태에 대하여 어느 정보가 가장 많은 불확실성을 제거하는지 알 수 없다. 두 번째, 규칙값 시스템은 지식베이스로 하여금 표준 대상-속성 정보뿐만 아니라 값 한정사 (Quantifier) 를 포함하도록 요구하는데 이것은 지식베이스 구성하는 일을 더 어렵게 한다. 그러나, 자신을 다른 것들보다 규칙값 추론으로 유도하는 어떤 상황이 있다. 또한, 구현될 때, 규칙값 시스템은 일반적으로 다른 두 방법보다 더 나은 일을 한다.

어떤 규칙값 전문가시스템은 시스템의 여러 면을 기록하기 위하여 통계 모듈을 갖는 전진연쇄 또는 후진연쇄로 시작했다. 후에, 그런 유형의 전문가시스템이 잠깐 사용된 뒤에, 이 통계 정보는 규칙값 접근 방식을 구현하기 위하여 사용될 수 있다.

이 시점에서, 추론기관의 세가지 유형 중 어느 것이 사용하기에 가장 좋은지 알고 싶어할 것이다. 대답은, 세가지 모두가 그 일을 할수 있기 때문에 다분히 각각의 성능에 따른다. 앞에서 언급했듯이, 규칙값 시스템이 구현하기가 다소 어렵다는 것을 알수 있다 : 따라서, 전문가시스템을 구축하는데에 전문가가 될 때 까지 아마도 이 방법을 피해야 할 것이다.

전진연쇄 방법은 트리를 구성하기 때문에 지식베이스로부터 가장 많은 양의 정보를 이끌어내는 과정을 다소 쉽게 한다. 전형적인 전진연쇄 시스템은 속성과 일치하는 가능한 모든 대상들을 발견하다. 후진연쇄 방법의 장점은 대상을 발견하기에 충분한 정보만들 요구한다는 것이다. 그러므로, 후진연쇄 시스템은 목표위주 (goal-driven) 이기 때문에 적절한 정보만이 시스템에 입력되게 한다. 후진연쇄 시스템은 단 하나의 대상만을 원할 때 좋다 - 비록 다른 대상들도 그 속성을 만족한다고 하더라도 좋다. 복수해를 발견하는 후진연쇄 전문가시스템을 만들 수 있다. 하지만 전진연쇄 전문가시스템을 구성하는 것보다 약간 더 많은 일을 요구한다.

최종적인 분석에서, 사용하려는 실제 접근방식은 각자에게 달려있다. 그러나, 모든 것이 같으면, 후진연쇄 방법은 구현하기가 더 쉬우므로, 기대할 것으로 생각하는 방법인 듯한 전문가시스템을 생성한다. 이러한 이유 때문에, 이 장에서는 후진연쇄를 사용하는 전문가시스템을 개발한다.

3. 만능 전문가시스템 만들기 (CREATING A GENERAL-PURPOSE EXPERT SYSTEM)

이제 전문가시스템에 필요한 배경을 갖고 있으므로, 실제 전문가시스템이 어떤가를 볼 준비가 되어있다. 이 절에서는 후진 연쇄를 사용하는 만능 전문가시스템을 만든다. 여러 가지 다른 지식베이스들을 가지고 같은 추론 기관을 사용할수 있게 하기 때문에 만능이라고 부른다. 또한 지식베이스를 만드는데 필요한 루틴들을 포함한다. 그러나 시작하기 전에, 전문가시스템이 구현되는 방법에 대하여 가질 수 있는 몇 가지 선입견을 버리는 것이 중요하다.

불행하지만 또 이해가 가는 사실은, 사실상 AI 에 관한 모든 소개 책들은 간단한 동물 전문가시스템을 예로 사용한다는 것이다. 때때로, 소개적인 책은 그것을 다른 것으로 부를 수도 있지만, 거의 같은 방법으로 구성되어 있다. 비록 이 간단한 전문가시스템이 전문가시스템의 여러 면을 설명하는 (그리고 상상력을 자극하는) 역할을 하지만, 전문가시스템을 구현하는 데에 옳지 못한 방법을 제시한다. 추론기관에 대상과 속성이 혼합되어 있다 : 이것은 지식베이스가 프로그램으로 코드화되기 어렵다는 것을 의미한다. 이것은 여러 가지 이유 때문에 나쁜 프로그래밍 기법이다. 가장 나쁜 이유 중의 하나는, 지식베이스를 변경하고 질을 높이거나 확장하는 것은 프로그램 코드로의 변경-그리고 재컴파일을 의미하는 것이다. 이 변경은 우발적인 컴파일 에러에서부터 익숙치 못한 프로그래머에 의한 실제 코드의 다변성까지의 범위에 이르는 모든 종류의 골치거리로 노출된다. 지식베이스를 프로그램으로 어렵게 코딩하는 것이 나쁜 프로그래밍 기법이라는 또다른 이유는, 지식베이스로의 실지 변화는 사용자가 프로그램을 변화시킬 것을 요구할 것인데 이것은 자존심 있는 어떤 프로그래머도 원하지 않는 일일 것이다. 또, 이렇게 되면 사용자가 원시 코드를 필요로 한다는 의미인데 이것은 어떤 상업 소프트웨어 개발자도 허용하지 않을 것이다.

이 설명에서 보여주듯이, 지식베이스는 추론기관과 분리되어야 한다. 이런 방식으로 만이 현명한 소프트웨어 개발자들은 진실로 작동할 수 있는 시스템을 만들 수 있다. 이것이 바로 모든 상업적 전문가시스템의 구조이고 이 장에 있는 전문가시스템의 구조이다.

(1) 연산의 본질적 요소 (The Essential of Operation)

전문가시스템 프로그램 작성하는 방법을 보기 전에, 이 절에서는 추론기관을 만들기 위한 것들을 실제 용어로 정의한다. 이 절에서는 지식베이스가 단지 대상과 그 속성들로만 구성될 것이라고 가정한다. 추론기관이 무엇을 할수 있어야 하는지 이해하기 위해서, 다음의 작은 지식베이스를 사용해보자 :

대상

속성

1

2

3

4

A, B, C

A, B, Y

B, X

A, B, D

가장 원시적인 수준에서, 추론기관은 대상 1 이 목표라고 가정하여 시작하고 목표가 대상 1 의 속성을 가지고 있는지 물어 이를 확인하려고 한다. 만약 그렇다면, 추론기관은 대상 1 이 답이라고 보고한다. 그렇지 않으면, 추론기관은 대상 2 로 진행하고 대상 2 의 속성에 관하여 질문한다. 이 과정은 추론 기관이 적당한 대상을 발견하거나 대상이 더 이상 없을 때 까지 반복된다. 이것이 추론기관이 따라가는 단계이고 대상 4 가 목표이면 다음 대화가 생긴다.

  • 전문가   A 를 갖는가?
  • 사용자   그렇다.
  • 전문가   B 를 갖는가?
  • 사용자   그렇다.
  • 전문가   C 를 갖는다?
  • 사용자   아니다.                /* 거절 1 */
  • 전문가   A 를 갖는가?         /* 군더더기 (redundant) */
  • 사용자   그렇다.
  • 전문가   B 를 갖는가?         /* 군더더기 */
  • 사용자   그렇다.
  • 전문가   Y 를 갖는가?
  • 사용자   아니다.                /* 거절 2 */
  • 전문가   B 를 갖는가?         /* 군더더기 */
  • 사용자   그렇다.
  • 전문가   X 를 갖는가?         /* 불필요함 */
  • 사용자   아니다.                /* 거절 3 */
  • 전문가   A 를 갖는가?         /* 군더더기 */
  • 사용자   그렇다.
  • 전문가   B 를 갖는가?         /* 군더더기 */
  • 사용자   그렇다.
  • 전문가   D 를 갖는가?         /* 발견했음 */
  • 사용자   그렇다.
  • 전문가   4 이다.

이렇게 작동하는 전문가시스템은 사용하기가 지루할 뿐 아니라 시스템에 대상이 좀 더 많을 때는 거의 가치가 없다. 이런 시스템에는 두가지 단점이 있다. 첫째, 같은 속성에 대하여 여러번 묻는다. 둘째, 대상 3 을 조사할 때 불필요한 질문을 한다. 시스템은 이전의 질문으로부터 문제의 대상이 속성 A 를 갖는다는 것을 알았어야 했다 : 대상 3 은 속성 A 를 갖지 않기 때문에 더 시도하지 않고 대상 3 을 거절했어야 했다. 속성 X 에 대한 지식이 유용했을 수도 (could) 있지만, 추론기관은 현재 상태와 맞지 않는 모든 대상들을 건너뛴다면 평균적으로 더 효율적일 것이다. 그러므로, 요구되는 것은, 목표와 같은 대상 4 를 갖는 지식베이스가 주어졌을 때 다음 대화를 생성하는 추론기관이다.

  • 전문가   A 를 갖는가?
  • 사용자   그렇다.
  • 전문가   B 를 갖는가?
  • 사용자   그렇다.
  • 전문가   C 를 갖는다?
  • 사용자   아니다.                /* 거절 1 */
  • 전문가   Y 를 갖는가?
  • 사용자   아니다.                /* 거절 2 */
  •                                      /* 거절 3 */
  • 전문가   D 를 갖는가?         /* 발견했음 */
  • 사용자   그렇다.
  • 전문가   4 이다.

이런 유형의 추론기관을 목표로 하면 추론기관이 갖추어야 할 시방은 다음과 같다 :

1. 전문가시스템은 같은 속성에 대하여 두 번 질문을 해서는 안된다.

2. 전문가시스템은, 즉시 거절해야 하고, 이미 알려진 필요한 속성들중 하나 이상 갖지 않는, 또는 이미 거절된 속성을 갖는 모든 대상을 그냥 넘어가야 한다.

3. 명령에 대하여, 전문가시스템은 왜 그 추론을 따르고 있는지 보고할 수 있어야 한다.

세 번째 제한조건은 전문가시스템이 올바로 동작하고 있다는 것을 증명하는 방법일 뿐 아니라 사용자를 교육시키는 방법이기도 하다.

(2) 지식베이스 구조화 (Structuring the Knowledge Base)

전문가시스템을 만들 때, 첫 단계는 지식베이스의 구조를 정의하는 것이다. 지식베이스에 있는 각 엔트리는 대상의 이름과 일련의 속성들로 구성되어야 한다. 대상을 포함하는 부분을 구성하는 가장 쉬운 방법 중의 하나는 다음 구조를 갖는 배열을 사용하는 것이다 (연결 리스트나 트리를 사용할 수도 있지만).

struct object {

} ob;

struct object k_base[MAX];    /* holds the knowledge base */

  • char name[80];

    struct attribute *alist;    /* pointer to list of attribute */

속성의 수를 모르기 때문에, 속성 리스트는 다음 구조의 단일 연결 리스트이다.

struct attribute {

} at;

  • char attrib[80];

    struct attribute *next;    /* use linked list */

알게 되겠지만, 추론기관은 목표에 속하는 모든 속성과 속하지 않는 속성을 추적해 나가야 한다. 이를 하는 가장 좋은 방법은 구조 유형 attribute 의 두 연결 리스트를 사용하는 것이다 : 두 포인터 yes 와 no 는 이 리스트들의 처음을 갖게 된다. 총괄변수 l_pos 는 현재 지식베이스에 있는 대상들의 수를 가지고 있다. 전문가시스템의 전체 데이터베이스 선언 부분이 다음에 있다 :

# define MAX 100    /* arbitrary, could be much larger */

struct attribute {

} at;

struct object {

} ob;

struct object k_base[MAX];    /* holds the knowledge base */

int l_pos=-1;       /* location of top of k base */

struct attribute *yes, *no;       /* used for yes and no lists */

struct attribute *yesnext, *nonext;

  • char attrib[80];

    struct attribute *next;    /* use linked list */

    char name[80];

    struct attribute *alist;    /* pointer to list of attribute */

(3) 지식베이스 로드 (Loading the Knowledge Base)

실제로 추론기관을 개발하기 전에, 지식베이스에 정보를 넣을 루틴을 만들어야 한다. 보통 상업적 전문가시스템에서처럼 별도의 프로그램을 작성할 수 있지만, 간단히 하기 위해, 사용자로 하여금 시스템에 정보를 입력하게 하는 루틴들은, 여기서 개발된 전문가시스템에 구축된 선택 사항일 것이다. 다음에서 보듯이, enter() 는 대상의 이름과 속성 리스트를 읽는다. 이 과정은 사용자가 빈 줄을 타이핑할 때 까지 반복된다. 그리고 나서, enter() 는 또 다른 대상을 위하여 프롬프트를 내보낸다 : 다른 대상의 이름과 속성을 입력하거나, 엔트리 과정을 건너뛰기 위하여 빈 줄을 입력하게 하였다.

/* input an object and its list of attributes */

enter( )

{

    int t;

    struct attribute *p, *oldp;

    

    for (; ;)  {

        t=get_net( );    /*get the index of next available object in k_base */

        if (t == -1) {

            printf("Out of list space. \n");

            return ;

        }

        printf("object name : ");

        gets(k_base[t].name) {

        if (!*_base[t].name) {

            l_pos--;

            break;

        }

        p=(struct attribute *) malloc(sizeof(at));

        if (p=='\0') {

            printf("Out of memory. \n");

            return;

        }

        k_base[t].alist=p;

        printf("Enter attributes (RETURN to quit) \n");

        for (; ;) {

            printf(": ");

            gets(p->attrib);

            if(!p->attrib[0]) break;

            oldp=p;

            p->next=struct attribute *) malloc(sizeof(at));

            p=p->next;

            p->next'\0' '

            if (p == '\0') {

                printf("Out of memory. \n");

                return;

            }

        }

        oldp->next='\0';

    }

}

(4) 추론기관 구현 (Implementing the Inference Engine)

이제 정보를 지식베이스에 로드할 수 있게 되었으므로, 추론기관은 도전해볼 준비가 되어있다. 추론기관은 전문가시스템의 추진력이다. 비록 추상적인 의미에서는 아주 간단하지만, 추론기관을 구현하는 것은 어려울 수도 있다. 여기서 개발된 추론기관은 아주 간단하지만, 실제로 많은 업무에 유용하다. 상업적 전문가시스템은 일반적으로 이 전문가시스템에 부과하는 것보다 더 많은 제약조건과 요구사항을 갖는다는 것을 기억해야 한다. 그러나, 여기에서 개발한 추론기관은 더 발전된 시스템을 위한 시작점 역할은 충분히 할 수 있다.

추론기관의 맨 윗 수준 함수는 다음에 있는 query() 이다.

/* inquire information from the expert */

query( )

{

    int t;

    char ch;

    struct attribute *p;

 

    for (t=0 ; t<=1_pos;t++) {

        p=k_base[t].alist;

        if (try(p,k_base[t].name)) {

            printf("the object is %s\n", k_base[t].name);

            return;

        }

    }

    printf("No object found\n");

}

query() 함수는 주요 루틴, try() 함수에 대한 드라이버로서의 역할만 하기 때문에 매우 간단하다. 본질적으로, query() 는 try() 가 일치 (match) 를 발견하거나 지식베이스에 더이상 할 것이 없을 때까지 한 번에 한 대상 씩을 try() 에게 보낸다. try() 함수는 다음에 있는 것처럼, 추론기관의 지능 (intelligence) 을 포함한다.

/* try an object */

try(p, ob)

struct attribute *p;

char *ob;

{

    char answer;

    struct attribute *a, *t;

    

    if (!trailno(p)) return 0 ;

 

    if (!trailyes(p)) return 0 ;

while(p) {

    /* if already been asked then move on to next

       attribute

    */

    if (ask(p->attrib)) {

       printf("is/does/has if %s? ", p->attrib);

       answer=tolower(getche());

       printf("\n");

 

       a=(struct attribute *) malloc(sizeof(at));

       if (!a) {

           printf("out of memory\n");

           return ;

       }

}

return 1;

}

  • a->next='\0';

    switch(answer) {

    }

    else p=p->next;

    • case 'n':

      case 'y':

      case 'w' :    /* why? */

      }

      • strcpy(a->attrib,p->attrib);

        if (!no) {

           no=a;

           nonext=no;

        }

        else {

           nonext->next=a;

           nonext=a;

        }

        return 0;

        strcpy(a->attrib,p->attrib);

        if (!yes) {

           yes=a;

           yesnext=yes;

        }

        else {

           yesnext->next=a;

           yesnext=a;

        }

        p=p->next;

        break;

        printf("trying %s\n", ob);

        if(yes)

           printf("it is/has/does:\", ob);

        t=yes;

        while(t) {

           printf("%s\n", t->attrib);

           t=t->next;

        }

        if (no) printf("and is/has/does not :\n");

        t=no;

        while(t) {

           printf("%s\n", t->attrib);

           t=t->next;

        }

        break;

try() 함수는 다음과 같이 작동한다. 첫째, trailyes() 와 trailno() 루틴은 건네진 각 대상의 속성 리스트를 체크한다. trailyes() 와 trailno() 루틴은 시스템의 현재 상태를 만족시키지 않는 대상을 스크린으로 내보낸다. 실제로, trailyes() 는 대상의 속성이 그 대상이 가져야 한다고 사용자가 지정한 모든 속성을 갖는다는 것을 체크하고, trailno() 는 그 대상이 거절된 대상을 갖지 않는다는 것을 확인하기 위하여 체크한다. 이 단계는 시방의 첫 번째 제약조건을 만족시킨다.

다음, 대상이 이미 확인된 속성을 포함하면, try() 는 리스트에서 다음 속성으로 진행한다 - 그리하여 질문이 두 번 생기는 것을 막는다. ask() 함수는 이 결정을 하고 시방의 두 번째 제약조건을 만족시킨다.

다음, try() 는 사용자에게 속성에 대하여 묻는다. 각각의 긍정적인 대답은 try() 로 하여금 yes 속성 리스트에 그 속성을 더하게 한다. 그리고 나서 try() 는 다음 속성을 시도한다. 모든 속성들이 일치하면, 대상이 발견되었고 try() 는 참 (true) 을 리턴한다. 각각의 부정적인 대답은 try() 로 하여금 no 리스트에 그 속성을 더하게 하고 거짓 (false) 을 리턴한다.

마지막으로, 사용자는 또한 "Why" 의 W 를 타이프할 수 있다. 이것은 시스템으로 하여금 현재의 추론과정을 정당화 (justify) 하게 한다. 이 단계는 시방의 세 번째 제약조건을 만족시킨다. 지원 함수 trailyes(), trailno(), ask() 가 다음에 있다.

/* see if it has any attributes known not
    to be part of the object by checking the no list */

trailno(p)

struct attribute *p;

{

}

 

/* see if it has all attributes known

   to be part of the object by checking the yes list */

trailyes(p)

struct attribute *p;

{

  • struct attribute *a, *t;

     

    a=no;

    while(a) {

    }

    return 1;

    • t=p;

      while(t) {

      }

      a=a->next;

      • if (!strcmp(t->atrib, a->attrib))

        t=t->next;

        • return 0;    /* does have a negative attribute */

    struct attribute *a, *t;

    char ok;

    a=yes;

    •  

    while(a) {

    }

    • ok=0

      t=p;

      while(t) {

      }

      • if (!strcmp(t->attrib, a->attrib))

        t=t->next;

        • ok=1;    /* does have a needed attribute */

      if (!ok) return 0;

      a=a->next;

    return 1;

}

 

/* see if attribute already asked*/

ask(attrib)

char *attrib;

{

 

}

  • struct attribute *p;

     

    p=yes;

    whiple(p && strcmp(attrib, p->attrib))

    if (!p) return 1;    /*false if end of list */

    else return 0;

    • p=->next;

       

사용자는 지식베이스에 정보를 한 번 넣기만 하면 되기 때문에, save() 와 load() 함수는 디스크 파일 expert.dat 로부터 지식베이스를 저장하고 로드하기 위하여 사용된다 (그러나, 사용자로 하여금 파일의 이름을 지정하는 것이 쉬울 것이다. 이러한 개선을 직접 하기 원할지도 모른다). save() 와 load() 루틴들이 아래에 나와 있다.

/* save the knowledge base */

save()

{

}

 

/* load the knowledge base */

load( )

{

FILE *fp;

 

if ((fp=fopen("expert.dat", "r"))==0) {

}

printf("loading knowledge base\n");

 

/* free any old lists */

clear_kbase();

 

for (t=0;t<MAX;++t) {

}

  • int t, x;

    stuct attribut *p;

    FILE *fp;

     

    if ((fp=fopen("expert.dat", "w")) ==0) {

    }

    printf("saving knowledge base\n");

     

    for(t=0;t<=1_pos;++t) {

    }

    putc('\0', fp);

    fclose(fp);

    • printf("cannt open file\n"

      return;

      for (x=0;x<sizeof(k_base[t].name);x++)

      p=k_base[t].alist;

      while(p) {

      }

      /* end of list marker */

      for (x=0;x<sizeof(p->attrib);x++) putc('\0', fp);

      • putc(k_base[t].name[x], );

        for (x=0;x<sizeof(p->atrib);x++)

        p=p->next;

        • putc(p->attrib[x], fp);

    int t, x;

    struct attribute *p, *oldp;

    printf("cannot open file\n");

    return;

    if ((k_base[t].name[0]=getc(fp))==0) break;

    for (x=1;x<sizeof(k_base[t].name);x++)

    k_base[t].alist=(struct attribute *) malloc(sizeof(at));

    if(!k_base[t].alist) {

    }

     

    k_base[t].alist=(struct attribute *) malloc(sizeof(at));

    p=k_base[t].alist;

    if(!p) {

    }

    for (;;) {

    }

    fclose(fp);

    l_pos=t-1;

    • k_base[t].name[x]=getc(fp);

      printf("Out of memory\n");

      break;

      printf("Out of memory\n");

      return;

      for (x=0;x<sizeof(p->attrib);x++)

      • p->attrib[x]=getc(fp);

         

      if (!p->attrib[0]) {

      }

      p->next=(struct attribute *) malloc(sizeof(at));

      }

      • oldp->next='\0';

        • break;     /* end of list */

        if (!p->next)

        }

        oldp=p;

        p=p->next;

        • printf("Out of memory\n");

          break;

전체 전문가시스템 프로그램이 다음에 있다. 이제 시범 수행을 할 수 있도록 컴퓨터에 입력해야 한다.

/* A simple expert system */

 

#include <stdio.h>

#include <malloc.h>

 

#define MAX 100    /* arbitrary, could be much larger */

 

struct attribute {

} at;

 

struct object {

} ob;

 

struct object k_base[MAX];    /* holds the knowledge base */

int l_pos=-1;    /* location of top of k base */

 

struct attribute *yes, *no;    /* used for yes and no lists */

struct attribute *uesnext, *nonext;

main()

{

}

 

free_trails()

{

}

 

/* input an object and its list of attributes */

enter()

{

 

for (;;) {

}

}

 

/* These routines make up the inference engine */

 

/* inquire information from the expert */

query()

{

  • char attrib[80];

    struct attribute *next;    /* use linked list */

    char name[80];

    struct attribute *alist;    /* pointer to list of attribute */

    char ch;

     

    no-yes='\0';

    do {

     

    } while (ch != 'x');

    • free_trails();

      ch=menu();

      switch(ch) {

      }

      • case 'e': enter();

        case 'q': query();

        case 's': save();

        case 'l': load();

        • break;

          break;

          break;

    struct attribute *p;

     

    while(yes) {

    }

     

    while(no) {

    }

    • p=yes->next;

      free(yes);

      yes=p;

      p=no->next;

      free(no);

      no=p;

    int t,i;

    struct attribute *p, *oldp;

    t=get_next();    /* get the index of the next
                             available object in k_base */

    if (t == -1) {

    }

    printf("Object name: ");

    gets(k_base[t].name);

     

    if (!*k_base[t].name) {

    }

    p=(struct attribute *) malloc(sizeof(at));

    if (p=='\0') {

    }

    k_base[t].alist=p;

    for (i=0;i<sizeof(p->attrib);i++) p->attrib[i]=' ';

    printf("Enter attributes (RETURN to quit)\n");

    for (;;) {

    }

    oldp->next = '\0';

    • printf("Out of list space. \n");

      return;

      l_pos--;

      break;

      printf("Out of memory.\n");

      return;

      printf(": ");

      gets(p->attrib);

      if(!p->attrib[0]) break;

      oldp=p;

      p->nest=(struct attribute *) malloc(sizeof(at));

      if (p->next == '\0') {

      }

      p=p->next;

      p->next='\0';

      for (i=0;i<sizeof(p->attrib);i++) p->attrib[i]=' ';

      • printf("Out of memory.\n");

        return;

    int t;

    char ch;

    struct attribute *p;

     

    for (t=0;t<=l_pos;t++) {

    }

    printf("No object found\n");

    • p=k_base[t].alist;

      if (try(p, k_base[t].name)) {

      }

      • printf("the object is %s\n", k_base[t].name);

        return;

}

 

/* try an object */

try(p, ob)

struct attribute *p;

char *ob;

{

 

 

}

 

/* see if it has any attributes known not
    to be part of the object by checking the no list */

trailno(p)

struct attribute *p;

{

  • char answer;

    struct attribute *a, *t;

    if (!trailno(p)) return 0;

     

    if (!trailyes(p)) return 0;

    while(p) {

    }

    return 1;

    • /* if already been asked then move on to next
          attribute
      */

      if (ask(p->attrib)) {

      printf("is/does/has it %s? ", p->attrib);

      answer=tolower(getche());

      printf("\n");

       

      a=(struct attribute *) malloc(sizeof(at));

      if (!a) {

      }

      a->next='\0';

      switch(answer) {

      }

      else p=p->next;

      • printf("out of memory\n");

        return ;

        case 'n':

        case 'y' :

        case 'w':    /* why? */

        }

        • strcpy(a->attrib, p->attrib);

          if (!no) {

          }

          else {

          }

          return 0;

          • no=a;

            nonext=no;

            nonext->next=a;

            nonext=a;

          strcpy(a->attrib, p->attrib);

          if (!yes) {

          }

          else {

          }

          p=p->next;

          break;

          • yes=a;

            yesnext=yes;

            yesnext->next=a;

            yesnext=a;

          printf("Trying %s\n", ob);

          if(yes)

          t=yes;

          while(t) {

          }

          if (no) printf("and is/has/does not :\n");

          t=no;

          while(t) {

          }

          break;

          • printf("it is/has/does:\n", ob);

            printf("%s\n", t->attrib);

            t=t->next;

            printf("%s\n", t->attrib);

            t=t->next;

    struct attribute *a, *t;

    a=no;

    while(a) {

     

    • t=p;

      while(t) {

      }

      • if (!strcmp(t->attrib, a->attrib))

        t=t->next;

        • return 0;    /* does have a negative attribute */

      a=a->next;

    }

    return 1;

}

 

/* see if it has all attributes known

   to be part of the object by checking the yes list */

trailyes(p)

struct attribute *p;

{

 

}

 

/* see if attribute already asked */

ask(attrib)

char *attrib;

{

}

 

/* support routines */

/* get next free index in k_base array */

get_next()

{

}

 

menu()

{

}

 

/* save the knowledge base */

save()

{

}

 

/* load the knowledge base */

load()

{

  • struct attribute *a, *t;

    char ok;

     

    a=yes;

    while(a) {

    }

    return 1;

    • ok=0;

      t=p;

      while(t) {

      }

      if (!ok) return 0;

      a=a->next;

      • if (!strcmp(t->attrib, a->attrib))

        t=t->next;

        • ok=1;    /* does have a needed attribute */

    struct attribute *p;

     

    p=yes;

    while(p && strcmp(attrib, p->attrib))

    if (!p) return 1;    /* false if end of list */

    else return 0;

    • p=p->next;

       

    l_pos++;

    if (l_pos<MAX) return l_pos;

    else return -1;

    char ch;

    printf("(E)nter, (Q)uery, (S)save, (L)oad, e(X)it\n");

    do {

    } while (!is_in(ch, "eqslx"));

    printf("\n");

    return ch;

    • printf("chooose one:");

      ch=tolower(getche());

    int t, x;

    struct attribute *p;

    FILE *fp;

     

    if ((fp=fopen("expert.dat", "w"))==0) {

    }

    printf("saving knowledge base\n");

     

    for (t=0;t<=lpos;++t) {

    }

    putc('\0',fp);

    fclose(fp);

    • printf("cannot open file\n");

      return;

      for (x=0;x<sizeof(k_base[T].name);x++)

      p=k_base[t].alist;

      while(p) {

      }

      /* end of list marker */

      for (x=0;x<sizeof(p->attrib);x++)putc('\0',fp);

      • putc(k_base[t].name[x],fp);

        for (x=0;x<sizeof(p->attrib);x++)

        p=p->next;

        • putc(p->attrib[x],fp);

    int t, x;

    struct attribute *p, *oldp;

    FILE *fp;

     

    if ((fp=fopen("exper.dat", "r"))==0) {

    }

    priintf("loading knowledge base\n")'

     

    /* free any old lists */

    clear_kbase();

     

    for (t=0;t<MAX;++t) {

     

    • printf("cannot open file\n");

      return;

      if ((k_basd[t].name[0]=getc(fp))==0) break;

      for (x=1;x<sizeof(k_base[t].name);x++)

      • k_base[t].name[x]=getc(fp);

      k_base[t].alist=(struct attribute *) malloc(sizeof(at));

      p=k_base[t].alist;

      if(!p) {

      }

      for (;;) {

      }

      • printf("Out of memory\n");

        return;

        for (x=0;x<sizeof(p->attri);x++)

         

        if (!p->attrib[0]) {

        }

        p->next=(struct attribute *) malloc(sizeof(at));

        if (!p->next) {

        }

        oldp=p;

        p=p->next;

        • p->attrib[x]=getc(fp);

          oldp->next='\0';

          • break;    /* end of list */

          printf("out of memory\n");

          break;

    }

    fclose(fp);

    • l_os=t-1;

}

  •  

/* reset the k base */

clear_kbase()

{

}

is_in(ch, s)

char ch, *s;

{

}

  • int t;

    struct attribute *p, *p2;

     

    for (t=0;t<=l_pos;t++) {

    }

    • p=k_base[t].alist;

      while(p) {

      • p2=p;

        free(p);

        p=p2->next;

       

      }

    while(*s)

    return 0;

    • if (ch==*s++) return 1;

(5) 시범 수행 (A Sample Run)

이제 전문가시스템을 컴퓨터에 넣었으므로, 그것을 수행하고 다음 정보를 지식베이스에 넣는다.

     대            상

속                    성

사과 (Apple)

귤 (Orange)

포도 (Grape)

나무딸기 (Raspberry)

배 (Pear)

수박 (Watermelon)

체리 (Cherry)

탄제린 (Tangerine)

둥글고, 빨갛거나 노랗고, 나무에서 자람

둥글고, 나무에서 자라고, 오렌지색

가시가 없고, 넝쿨에서 자라고, 자주색

가시가 있고, 빨갛고, 줄기에서 자람

둥글지 않고, 빨갛거나 녹색이고, 나무에서 자람

크고, 녹색이고, 가시가 없고, 넝쿨에서 자람

작고, 나무에서 자라고, 빨갛거나 노란색

오렌지 나무에서 자라고, 둥글고, 표면이 매끄러움

지식이 준비된 시스템을 가지고, 다른 유형의 과일을 알아내기 위하여 사용한다. 다음에 오는 세가지 대화가 이 시스템을 사용하여 산출되었다.

is/has/does it round y

is/has/does it red or yellow n

is/has/does it grow on trees y

is/has/does it orange y

it is orange

  • 첫 번째 대화
     

is/has/does it round n

is/has/does it not thorns y

is/has/does it grow on vines y

is/has/does it purple n

is/has/does it large y

is/has/does it green y

it is watermelon

  • 두 번째 대화
     

is/has/does it round n

is/has/does it no thorns y

is/has/does it grow on vines y

is/has/does it purple n

is/has/does it large y

is/has/does it green w

 

Trying watermelon:

it is/has/does:

grow on vines

no thorns

it is/has/does not:

round

purple

 

is/has/does it green y

it is watermelon

  • 세 번째 대화
     

세 번째 대화에서 why 명령 사용을 눈여겨 보기 바란다. 시스템을 가지고 실험해보기 바란다 ; 작동을 이해하고 확신되면 곧 다음 절로 진행해야 한다.

(6) 복수의 해 찾기 (Finding Multiple Solutions)

지식베이스를 검토해보면, 탄제르가 표면이 부드럽다는 부가적인 속성을 갖는 것을 제외하고 오렌지와 탄제르의 정의가 동일한 것을 알게 될 것이다. 그러므로, 사용자에게 비록 탄제르가 있지만, 주어진 것과 같은 전문가시스템은 항상 오렌지를 보고할 것이다. 왜냐하면 탄제르의 처음 세가지 속성과 일치하기 때문이다. 이 답은 어떤 상황에서는 받아들일 수 있지만, 대부분 완전한 해를 찾는 것이 중요할 것이다.

이제 모든 해를 찾도록 이 시스템을 변경할 방법을 연구해 보자. 시스템이 복수해를 찾을 수 있도록 query() 함수만을 약간 변형하면 된다. 새 버전이 다음에 있다.

/* inquire information from the expert */

query()

{

}

  • int t;

    char ch;

    struct attribute *p;

     

    for (t=0;t<=1_pos;t++) {

    }

    printf("No (more) object(s) found\n");

    • p=k_base[t].alist;

      if (try((p, k_basee[t].name)) {

      }

      • printf("%s fits current description\n", k_base[t].name);

        printf("continue? (Y/N): ");

        ch=tolower(getche());

        printf("\n");

        if (ch=='n') return;

이 버전에서, 시스템이 현재의 사실과 맞는 대상을 발견할 때마다 query() 는 그 이름을 출력하고 사용자에게 계속할 것인지를 묻는다. 사용자가 y 를 타이프하면, 탐색은 계속된다. 다음 대화에 변화가 나타나 있다.

is/has/does it round y

is/has/does it red or yellow n

is/has/does it grow on trees y

orange fits the current description

continue (Y/N)? y

is/has/does it soft skinned y

tangerine fits the current description

contine (Y/N)? n

비슷한 정의를 갖는 새로운 과일을 가지고 실험하려고 시도하기 원할지도 모른다. 이와같이, 프로그램은 때때로 아주 영리해 보인다.

4. 더 복잡한 버전 (A MORE SOPHISTICATED VERSION)

좀 더 노력하면, 시스템이 why 명령에 대한 반응을 향상 시킬 수 있다. 프로그램이 현재 특정한 대상에서 작동하고 있는 이유를 알려주고는 있지만 다른 대상들을 왜 거절했는지는 알려주지 않는다. 이를 변경하는 것은 새로운 데이터베이스, 두 개의 새로운 함수, 그리고 프로그램의 다른 여러 부분의 변화를 요구한다.

새로운 데이터베이스는 거절당한 대상의 이름과 거절의 원인이 된 속성을 갖게 된다. 다음 구조 배열은 이 정보를 유지하기 위하여 사용된다.

struct rejected_object {

char name[80];

char attrib[80];    /* attribute that caused rejection */

char condition;    /*  should it or shouldn't it

                             have been found ? */

} rj;

struct rejected_object r_base[MAX];

condition 필드는 대상에 어떤 속성이 없는지, 또는 대상이 이미 거절된 속성을 갖는지를 말해준다. 한 대상이 거절될 때마다 - try() 에서의 no 대답에 의해서든 trailyes() 나 trailno() 에 의해서든지, reject() 는 거절된 데이터베이스에 그 대상과 속성을 넣는다. reject() 함수가 다음에 있다.

/* place rejected object into database */

reject(ob, at, cond)

char *ob, *at, cond;

{

}

  • r_pos++;

     

    strcpy(r_base[r_pos].name, ob);

    strcpy(r_base[r_pos].attrib, at);

    r_base[r_pos.condition=cond;

시스템이 why 명령을 내보낼 때마다, 시스템은 새로운 함수 reasoning() 을 호출하는데, 이것은 시스템의 현재 상태를 보일 뿐 아니라 어떤 대상이 왜 거절되었는지를 보여준다. reasoning() 함수가 다음에 있다.

/* show why a line of reasoning is being followed */

reasonig(ob)

char *ob;

{

 

}

  • struct attribute *t;

    int i;

    printf("Trying %s\n", ob);

    if (yes)

    t=yes;

    while(t) {

    }

    if (no) printf("it is/has/does not:\n");

    t=no;

    while(t) {

    }

     

    for (i=0;i<=r_pos;i++) {

    }

    • printf("it is/has/does:\n");

      printf("%s\n", t->attrib);

      t=t->next;

      printf("%s\n", t->attrib);

      t=t->next;

      printf("%s is rejected because ", r_base[i].name);

      if (r_base[i].condition=='n')

      else

      • printf("$s is not an attribute.\n", r_base[i].attrib);

        printf("%s is not an attribute.\n", r_base[i].attrib);

이 변화와 첨가를 지원하려면, 프로그램 전체에 약간의 변화를 많은 부분에서 요구한다. 따라서 편의상, 개선된 전문가시스템 전체를 다음에 제시한다 :

/* An improve expert system that finds multiple

   goals and displays reasoning */

 

#include "stdio.h"

#include <malloc.h>

#define MAX 100

struct attribute {

} at;

 

struct object [

} ob;

 

struct rejected_object {

} rj;

struct rejected_object r_base[MAX];    /* holds the knowledge base */

int l_pos=-1;    /* location of top of k base */

int r_pos=-1;   /* location of top of reject base */

 

struct attribute *yes, *no;    /* used foor yes and no llists */

struct attribute *yesnext, *nonext;

 

main()

{

 

  • char attrib[80];

    struct attribute *next;    /* use linked list */

    char name[80];

    struct attribute *alist;    /* pointer to list of attributes */

    char name[80];

    char attrib[80];    /* attribute that caused rejection */

    char condition;    /* should it or shouldn't it

                                have been found ? */

    char ch;

    no=yes='\0';

    do {

    }

    } while (ch != 'x');

    • free_trails();

      ch=menu();

      switch(ch) {

      • case 'e' : enter();

        case 'q': query();

        case 's': save();

        case 'l': load();

        • break;

          break;

          break;

}

  •  

free_trails()

{

  • struct attribute *p;

    while(yes) {

    }

    while(no) {

    }

    •  

      p=yes->next;

      free(yes);

      yes=p;

       

      p=no->next;

      free(no);

      no=p;

    r_pos=-1;

}

/* input an object and its list of attributes */

enter()

{

 

}

  • int t, i;

    struct attribute *p, *oldp;

     

    for (;;) {

    • t=get_next();    /* get the index of the next

                               availabel object in k_base */

      if (t == -1) {

      }

      printf("Object name: ");

      gets(k_base[t].name);

      • printf("Out of list space.\n");

        return;

    • if (!*k_base[t].name) {

      • l_pos--;

        break;

      }

     

    • p=(struct attribute *) malloc(sizeof(at));

      if (p=='\0') {

      • printf ("out of memory.\n");

        return;

      }

      k_base[t].alist=p;

      for (i=0;i<sizeof(p->attrib);i++) p->attrib[i]=' ';

      printf("Enter attributes (RETURN to  quit)\n");

      for (;;) {

      • printf(": ");

        gets(p->attrib):

        if(!p->attrib[0]) break;

        oldp=p;

        p->next=(struct attribute *) malloc(sizeof(at));

        if (p->next == '\0') {

        • printf("Out of memory.\n");

          return;

        }

        p=p->next;

        p->next-'\0';

        for (i=0;i<sizeof(p->attrib);i++) p->attrib[i]=' ';

      }

      oldp->next = '\0';

    }

     

/* inquire information from the expert */

query()

{

}

 

/* see if it has any attributes known not

    to be part of the object by checking the no list */

trailno(p, ob)

struct attribute *p;

char *ob;

{

 

}

 

/* see if it has all attributes known

    to be part of the object by checking the yes list */

trailyes(p, ob)

struct attribute *p;

char *ob;

{

  • int t;

    char ch;

    struct attribute *p;

    for (t=0;t<=1_pos;t++) {

    }

    printf("No (more) object(s) found\n");

    }

     

    /* try an object */

    try(p, ob)

    struct attribute *p;

    char *ob;

    {

    char answer;

    struct attribute *a, *t;

     

    if (!trailno(p, ob)) return 0;

     

    if (!trailyes(p, ob)) return 0;

     

    while(p) {

        /* if already been asked then move on to next

           attribute

    •  

      p=k_base[t].alist;

      if (try(p, k_base[t].name)) {

      }

      • printf("%s fits current description\n", k_base[t].name);

        printf("continue? (Y/N): ");

        ch=tolower(getche());

        printf("\n");

        if (ch=='n') return;

        */

        if (ask(p->attrib)) {

    }

    return 1;

    • printf("is/does/has it %s? ",  p->attrib);

      answer=tolower(getche());

      printf("\n");

       

      a=(struct attribute *) malloc(sizeof(at));

      if (!a) {

      }

      a->next='\0';

      switch(answer) {

      }

      else p=p->next;

      • printf("out memory\n");

        return 0;

        case 'n':

        case 'y':

        case 'w':    /* why? */

        }

        • strcpy(a->attrib, p->attrib);

          if (!no) {

          }

          else {

          }

          reject(ob, p->attrib, 'n');

          return 0;

          • no=a;

            nonext=no;

            nonext->next=a;

            nonext=a;

          strcpy(a->attrib, p->attrib);

          if (!yes) {

          }

          else {

          }

          p=p->next;

          break;

          • yes=a;

            yesnext=yes;

            yesnext->next=a;

            yesnext=a;

          reasonig(ob);

          break;

    struct attribute *a, *t;

    a=no;

    while(a) {

    }

    return 1;

    • t=p;

      while(t) {

      }

      a=a->next;

      • if (!strcmp(t->attrib, a->attrib)) {

        }

        t=t->next;

        • reject(ob, t->attrib, 'n');

          return 0;    /* does have a negative attribute */

    struct attribute *a, *t;

    char ok;

    a=yes;

    while(a) {

    • ok=0;

      t=p;

      while(t) {

      }

      if (!ok) {

      }

      a=a->next;

      • if (!strcmp(t->attrib, a->attrib)) {

        • ok=1;    /* does have a needed attribute */

        }

        t=t->next;

        reject (ob, a->attrib, 'y');

        return 0;

    }

    return 1;

}

  •  

/* see if attribute already asked */

ask(attrib)

char *attrib;

{

  • struct attribute *p;

    •  

    p=yes;

    while(p && strcmp(attrib, p->attrib))

    • p=p->next;

       

    if (!p) return 1;    /* false if end of list */

    else return 0;

}

 

/* show why a line of reasoning is being followed */

reasoning(ob)

char *ob;

{

 

}

 

/* place rejected object into databese */

reject(ob, at, cond)

char *ob, *at, cond;

{

}

 

/* get next free index in k_base array */

get_next()

{

l_pos++;

if(l_pos<MAX) return l_pos;

else ruturn -1;

}

 

menu()

{

char ch;

printf("(E)mneter, (Q)uery, (S)save, (L)oad, e(X)it\n");

do {

printf("choose one:");

ch=toloower(getche());

} while (!is_in(ch, "eqslx"));

printf("\n");

return ch;

}

 

save()

{

 

 

}

 

load()

{

}

  • stuct attribute *t;

    int i;

     

    printf("Trying %s\n", ob);

    if (yes)

    t=yes;

    while(t) {

    }

    if (no) printf("it is/has/does noot:\n");

    t=no;

    while(t) {

    }

    • printf("it is/has/does:\n");

      printf("%s\n", t->attrib);

      t=t->next;

      printf("%s\n", t->attrib);

      t=t->next;

    for (i=0;i<=r_pos;i++) {

    }

    • printf("%s is rejected because ", r_base[i].name);

      if (r_base[i].condition=='n')

      else

      • printf("%s is not an attribute.\n", r_base[i].attrib);

        printf("%s is a required attribute.\n", r_base[i].attrib);

    r_pos++;

     

    strcpy(r_base[r_pos].name, ob);

    strcpy(r_base[r_pos].attrib, at);

    r_base[r_pos].condition=cond;

    int t, x;

    struct attribute *p;

    FILE *fp;

    if ((fp=fopen("expert.dat", "w"))==0) {

    }

    printf("saving knowledge base\n");

    • printf("cannot open file\n");

      return;

    for (t=0;t<=1_pos;++t) {

    }

    putc(0, fp);

    fclose(fp);

    • for (x=0;x<sizeof(k_base[t].name);x++)

      p=k_base[t].alist;

      while(p) {

      }

      /* end of list marker */

      for (x=0;x<sizeof(p->attrib);x++) putc('\0', fp);

      • putc(k_base[t].name[x], fp);

        for (x=0;x<sizeof(p->attrib);x++)

        p=p->next;

        • putc(p->attrib[x], fp);

    int t, x;

    struct attribute *p, *oldp;

    FILE *fp;

     

    if((fp=fopen("expert.dat", "r"))==0) {

    }

    printf("loading knowledge base\n");

     

    /* free any old lists */

    clear_kbase();

    for(t=0;t<MAX;++t) {

     

    }

    fclose(fp);

    l_poos=t-1;

    • printf("cannot open file\n");

      return;

      if((k_base[t].name[0]=getc(fp))==0) break;

      for (x=1;x<sizeof(k_base[t].name);x++)

         k_base[t].name[x]=getc(fp);

      k_base[t].alist=(struct attribute *) malloc(sizeof(at));

      p=k_base[t].alist;

      if(!p) {

      }

      for(;;) {

      }

      • printf("Out of memory\n");

        return;

        for(x=0;x<sizeof(p->attrib);x++)

        if(!p->attrib[0]) {

        }

        p->next=(struct attribute *) malloc(sizeof(at));

        if(!p->next) {

        }

        oldp=p;

        p=p->next;

        • p->attrib[x]=getc(fp);

          oldp->next='\0';

          break;    /* end of list */

          printf("out of memory\n");

          break;

     

clear_kbase()

{

  • int t;

    struct attribute *p, *p2;

    •  

    for(t=0;t<=l_pos;t++) {

    • p=k_base[t].alist;

      while(p) {

      }

      • p2=p->next;

        free(p);

        p=p2;

    }

}

  •  

is_in(ch, s)

char ch, *s;

{

 

  • while(*s)

    • if(ch==*s++) return 1;

      return 0;

    }

     

시범 수행에서 과일 데이터베이스를 사용한 향상된 출력 예가 다음에 나와 있다.

is/has/does it round? y

is/has/does it red or yellow? n

is/has/does it grow on trees? w

 

Trying orange

it is/has/does:

round

it is/has/does not:

red or yellow

 

apple is rejected because red or yellow is not an attribute

보는 바와 같이, 이 대화는 시스템이 여러 대상들을 왜 거절했는지 설명해 주기 때문에 사용자에게 상당히 많은 정보를 전달해 준다. 큰 지식베이스를 가지고, 많은 거절 (시스템의 현재 상태와 관계없는 많은 것들) 을 디스플레이 하는 것을 막을 방법을 찾는 것이 필요할 수도 있다. 이런 개선은 스스로 해보기 바란다 ; 그러나, 힌트로서, 지식베이스는 또한 시스템이 현재의 가설에 관련된 거절된 대상들만 디스플레이하도록 어떤 관계를 동반할 것을 요구할 것이다.

5. 지식 엔지니어링 (KNOWLEDGE ENGINEERING)

이 장에서 개발된 전문가시스템은 지식베이스에 있는 첫 번째 엔트리를 갖고 탐색을 시작한다. 그리고 단순히 대상들의 리스트를 따라 순서대로 진행한다. 이 과정은 대상이 적은 경우에는 좋지만, 더 큰 지식베이스를 사용할 때 문제를 일으킬 수도 있다. 예를 들어, 묘사하는 것이 숫자 999 로 표현한 천개의 대상이 있고 공통된 속성이 있으면, 찾는데에 오랜 시간이 걸릴 수도 있다. 다른 문제도 있겠지만 이런 종류의 문제를 해결하려고 하면 지식공학이라는 분야가 필요하다.

지식공학은 지식베이스가 조직되고, 구성되고, 확증되는 방법을 다루는 분야이다. 이 주제에 대하여 충분히 설명하는 것은 이 책의 범위를 넘어서지만, 지식베이스를 구축하려면 여러 가지 어려움에 봉착한다는 점은 알아야 한다. 지식공학 분야는 역사가 아주 짧고, 연구될 것이 많다는 것을 명심하자.

(1) 지식베이스 구조 (Knowledge Base Organization)

앞에서 말했듯이, 이 장에서 개발된 전문가시스템은 지식베이스의 기초에서 시작하여, 전체를 순차적으로 진행하였다. 휴리스틱만 추가되면, 이것은 추론기관에서 기대하는 가장 좋은 결과가 될 것이다. 그러나, 지식베이스를 만드는 사람으로서, 지식베이스의 구성도 제어할 수 있다. 이렇게 하면 더 좋아지기도 더 나빠지기도 할 것이다.

어떤 경우에는, 정보를 구성하는 좋은 방법으로 가장 가능성이 많은 대상들을 맨 위에 놓고, 가장 가능성이 적은 대상들을 아래 부분에 놓는다. 문제는 어느 대상이 가능성이 높고 어느 것이 가능성이 적은지를 결정하는 것이다. 사람 전문가조차도 어느 해가 가장 가능성이 높은지 모를 수 있다. 때때로, 지식베이스를 임의로 만들어서, 시스템이 각 대상을 선택하는 횟수를 기록하는 동안의 짧은 기간만 사용하는 수도 있다. 이런 빈도수를 사용하면 지식베이스를 다시 배열할 수 있다.

지식베이스를 구성하는 또 다른 방법은 지식베이스의 윗부분에 "트리" 의 가장 큰 부분이 잘리게 하는 그런 속성들을 놓는 것이다. 큰 지식베이스에 대해서, 어는 속성이 가장 큰 효과를 갖는지 결정하는 것은 어렵다는 것을 알 수 있다.

(2) 전문가 찾기 (Finding the Expert)

이 장 전체에서 사람 전문가를 찾을 수 있고, 그 전문가의 지식을 쉽게 빼낼 수 있다고 가정했다. 불행히도, 그런 경우는 거의 없다. 먼저, 누가 진짜 전문가이고 아닌지 결정하는 것은 꽤나 어렵다. 특히 그 주제에 대하여 거의 모를 때 문제가 된다. 두 번째로, 같은 대상에 대하여 두 명의 다른 전문가가 종종 의견이 틀리다 - 이것은 어느 것을 사용할지 아는 것을 어렵게 한다.

전문가들의 의견이 다를 때, 일반적으로 세가지 중에서 선택을 할 수 있다. 첫째, 한 전문가를 선택하고 다른 것을 무시한다. 이것은 분명 가장 쉬운 선택이다. 하지만 지식베이스가 어떤 잘못된 정보를 포함할 수 있다는 것을 의미한다 (그러나 옳지 못한 정보의 양은 사람 전문가가 제공할 수 있는 것뿐이다). 둘째, 정보를 평균화할 수 있다 : 즉, 두 전문가 사이에 공통된 정보만 사용한다. 이러한 선택이 첫 번째 것만큼 쉽지는 않지만 그리 큰 문제없이 이 과정을 종종 사용할 수 있다. 단점은 지식베이스의 내용이 완화된다는 것과, 어느 전문가의 지식도 충분히 반영되지 못한다는 것이다. 세 번째, 두 전문가의 지식을 모든 시스템에 포함해서 사용자가 결정하게 할 수도 있다. 각 항목에 대하여 확률 인자 (probability factor) 를 부가할 수 있다. 이 선택이 비록 어떤 상황에서는 받아들여 질 수 있지만, 많은 응용에 대하여 전문가시스템이 권위가 있기를 원할 것이다 - 즉, 사용자가 결정하기를 원하지 않는다.

또 다른 어려운 점은 대부분의 사람 전문가가 그들이 무엇을 아는지를 모른다는 것이다 : 사람은 컴퓨터가 할 수 있는 것과 같은 방법으로 "메모리 덤프" 를 할 수 없다. 그러므로, 필요한 정보를 모두 뽑아내는 것이 어려울 수 있다. 또한, 어떤 전문가들은 그들이 알고 있는 모든 것을 말해주기 위하여 시간이 걸리는 것을 자진해서 하려고 하지 않을 것이다.

(3) 지식베이스 확증 (Verifying the Knowledge Base)

비록 협조적이고, 자신의 전문지식에 대하여 완벽하게 설명해 줄 전문가를 찾았다고 하더라도, 지식을 컴퓨터에 정확하게 베껴 넣었다는 것을 확증하는 문제에 여전히 부딪치게 된다. 본질적으로, 지식베이스를 시험해 봐야 한다. 문제는 이렇다. 지식베이스를 어떻게 테스트할 것인가?

제 1 장에서 언급했듯이, 소모적인 탐색은 폭발적 탐색이 되기 때문에, 아주 작은 데이터 집합 이외의 어떤 것에도 적용이 불가능하다. 그러므로, 대부분의 실세계 전문가시스템에 대하여, 지식베이스가 정확하다는 것을 완벽하게 증명할 방법은 없다. 가장 좋은 해결은 지식베이스의 정확성에 대하여 아주 자신할 수 있도록 충분한 테스트를 하는 것이다. 통계적 표본 기법을 사용하여, 요구하는 어떤 신뢰수준 (confidence level) 이든 산출할 일련의 테스트를 고안할 수 있다.

또다른 접근 방식으로, 일관성을 위하여 지식베이스에 있는 모든 정보가 그 자체와 일치하는지 알기 위하여 시스템으로 하여금 직접 체크하게 할 수도 있다. 이것은 확실히 모든 문제를 발견하지는 않겠지만, 몇 개는 발견할 것이다. 그러나 이 자기체크 (self-check) 를 어떻게 구현하는 지에 따라서, 어떤 커다란 지식베이스에서는 폭발적인 '벽 (brick wall)" 에 부딪칠 수도 있다. 그래서 이 방법을 사용하는 것이 불가능할지도 모른다.

그러므로, 전문가시스템을 더 많이 사용하게 됨에 따라, 지식베이스 확증 (verification) 은 가장 중요한 연구분야 중의 하나가 될 것이다.

[출처] http://www.aistudy.co.kr/program/expert_schildt.htm

‘책임 회피’ ‘그럴만한 이유가 있었다’ 적반하장, 어떻게 행동하면 좋을까

흔히 가해자가 피해자에게 되려 ‘왜 이렇게 예민하게 구냐’거나 ‘네가 원만히 넘어갔으면 괜찮았을 일’이라며 더 화를 내는 경우나, 해당 사건과 전혀 상관이 없는 피해자의 평소 인성을 지적하는 등 적반하장으로 구는 경우를 목격하곤 한다.


인간의 본성에 대한 의심까지 들게 하는 이런 현상들에 대해 한 가지 설명을 보여주는 연구가 있어서 소개한다(Sullivan at al., 2012).


집단적 피해의식(Competitive victimhood)은 ‘우리 집단도 당한 게 많다!’ 라는 말 그대로 집단 단위의 피해의식을 이야기 한다. 그런데 집단의 도덕적 정체성(moral identity)이 위험에 처하면 이런 피해의식이 높아진다고 한다.


예컨대 남성이 여성을 폭행한 사건에 대한 이야기를 들은 남성들은 그렇지 않은 남성들에 비해, 본인이 잘못을 한 건 아니지만 그래도 같은 남성으로서 문제의 심각성에 좀 더 주목한다던가 문제 해결에 적극적으로 나서는 등의 적응적인 모습을 보여줄 것 같지만, 실제로는 ‘요즘은 남성이 더 차별받는다! 남성이야말로 피해자다!’같은 인식을 비교적 강하게 보이는 현상이 나타난다고 한다.


비슷하게 학부생을 타겟으로 학부생이 잘못한 사건을 얘기 하면 이번엔 ‘학부생들이 대학원생들에 비해 차별받고 있다! 우리도 피해자다!’같은 이야기가 나온다고 한다.


집단 정체성에 의해 자신도 왠지 가해자가 된 것 같은 알 수 없는 죄책감 같은 게 생기게 될 텐데 그런 ‘불편한 죄책감’을 떨치기 위해서 이런 방어적인 행동을 보이는 것일 수 있겠다.


또한 집단 정체성은 우리 자신의 정체성과도 결국 연결된 부분이라 (우리는 자신을 규정할 때 스스로의 고유한 모습-나는 뭘 잘 하고 뭘 좋아한다-뿐 아니라 어디 학생, 어디 직원, 어디 국민 같이 소속된 집단을 통해서도 자신을 규정한다)집단의 정체성 훼손은 결국 내 정체성에 상처가 가는 것이기도 하다. 따라서 이를 막으려 본능적으로 대응하는 것이기도 할 테다.

 

 

pixabay 제공

 


여하튼 잘못에 대해 ‘사과와 반성. 재발 방지’로 대응하는 게 ‘당연’하다고들 여기지만. 현실은 책임 회피와 ‘그럴만한 이유가 있었다’ ‘걔네도 나쁘다’가 좀 더 디폴트 모드인 듯 보일 때가 많다. 그래서 이렇게 적반하장들이 횡행한 것일까.


관련해서 사람들에게 게임을 하게 해서 승자와 패자가 나뉘게 한 다음 승자의 행동을 관찰하니까 왠지 미안한 마음에 패자에게 친절하게 대해주기는커녕 되려 이 기회에 확실히 밟아버리겠다는 것 마냥 승자가 패자에게 (패자가 승자에게 보이는 것보다) ‘더 높은 공격성’을 보이며 게임이 끝난 후에도 끝가지 패자를 괴롭히더라는 연구가 있었다. 승자의 입지를 굳히게끔 하는 나름 합리적인 행동일지 모르겠으나 역시 그 ‘정도’에 대해 유의할 필요가 있을 것이다.


우리의 마음 시스템이 원래 생겨 먹은 대로 굴러가다 보면 좋은 결과들을 낼 때도 많지만 얘기치 못한 부조리한 결과들을 낼 때도 많기 때문에 감독의 마음으로 항상 의식하고 때로 너무 많이 나갔다 싶을 경우 STOP!을 외쳐주는 게 많이 필요한 듯 보인다.


대부분의 정신적 프로세스가 의식 바깥에서 일어나고 의식은 일이 다 치뤄진 후 최종 보고를 받는 정도에 지나지 않는다고 보는 학자들도 있는데, 어쨋든 ‘최종 보고’를 받을 수 있다는 것이 중요하지 않나 싶다. 보고를 받고 잘못된 것 같으면 최대한 시정하고자 하는 힘이 최종 결재자에게 있으니 말이다.


이런 의미에서 바우마이스터 등의 학자들은 의지, 자아(self)의 역할을 CEO나 조종사에 비유하기도 한다. 우리 마음은 비행기의 자동항법장치처럼 우리가 일일이 조종하지 않아도(그렇게 하지도 못하고) ‘알아서’ 흘러가지만 비행기도 때로 필요할 때, 중요한 상황에서는 핸들을 잡고 수동으로 조종하는 것처럼 자아가 그런 대장 역할을 하지 않냐는 것이다.


잘못된 행동을 하고서 도덕적 정체성을 지켜보겠다고 적반하장을 하고 있는 것은 아닌지, 약자에게 불필요하게 잔인하게 행동하고 있는 것은 아닌지 늘 자신을 감독하는 것이 필요하겠다.

 

[출처] http://dongascience.donga.com/news/view/10364

 

+ Recent posts