머신러닝

In [3]:
# !pip install scikit-learn
In [4]:
# !pip install scipy

XOR 연산 학습하기

  • 두 값의 각 자릿수를 비교해,값이 0으로 같으면 0, 값이 1로 같으면 0, 다르면 1을 계산 image.png
In [5]:
from sklearn import svm
In [6]:
# 1. XOR의 계산 결과 데이터
xor_data = [
    [0,0,0],
    [0,1,1],
    [1,0,1],
    [1,1,0]
]
In [7]:
# 2. 학습을 위해 데이터와 레이블 분리하기
# 학습시키기 fit() 매개변수에 필요

data = []  # 훈련데이터
label = []  # 답

for row in xor_data:
    p = row[0]
    q = row[1]
    r = row[2]
    data.append([p, q])
    label.append(r)
In [10]:
# input 데이터 확인
print(data)

# 답 확인
print(label)
[[0, 0], [0, 1], [1, 0], [1, 1]]
[0, 1, 1, 0]
In [11]:
# 3. 데이터 학습시키기 : fit()
# SVM 알고리즘 사용
clf = svm.SVC()

# 모델 훈련
clf.fit(data, label)
Out[11]:
SVC()
In [15]:
# 4. 데이터 예측하기 : predict()
pre = clf.predict(data)
print('예측결과 : ', pre)
예측결과 :  [0 1 1 0]
In [16]:
# 5. 결과 확인 하기
ok = 0
total = 0

# enumerate : 반복문 사용시 몇 번째 반복문인지 확인할 수 있음
for idx, answer in enumerate(label):
    p = pre[idx]
    if p == answer:
        ok += 1
    total += 1

print('정답률 : ', ok, '/', total, '=', ok/total)
정답률 :  4 / 4 = 1.0
In [17]:
data
Out[17]:
[[0, 0], [0, 1], [1, 0], [1, 1]]
In [18]:
label
Out[18]:
[0, 1, 1, 0]
In [19]:
pre
Out[19]:
array([0, 1, 1, 0])

프레임워크로 작성하기

  • 판다스로 데이터와 레이블 나누기
  • scikit-learn 정답률 계산 기능 등이 내장되어 있음
In [20]:
import pandas as pd
from sklearn import svm, metrics
In [21]:
# 데이터 복사
xor_input = xor_data[:]
In [22]:
xor_input
Out[22]:
[[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
In [24]:
# 1. 입력을 학습 전용 데이터와 테스트 전용 데이터로 분류하기
xor_df = pd.DataFrame(xor_input)
xor_data = xor_df.loc[:,0:1]  # 데이터
xor_label = xor_df.loc[:,2]   # 레이블
In [25]:
# 2. 데이터 학습과 예측하기
clf = svm.SVC()
clf.fit(xor_data, xor_label)
pre = clf.predict(xor_data)
In [27]:
# 3. 정답률 구하기
ac_score = metrics.accuracy_score(xor_label, pre)
print('정답률 = ', ac_score)
정답률 =  1.0
In [28]:
xor_label
Out[28]:
0    0
1    1
2    1
3    0
Name: 2, dtype: int64
In [29]:
pre
Out[29]:
array([0, 1, 1, 0], dtype=int64)

붓꽃의 품종 분류하기

  • 외떡잎식물 백합목 붓꽃과의 여러해살이풀 '붓꽃' 분류
  • 머신러닝을 사용해 꽃잎과 꽃받침의 크기 기반 분류

붓꽃 데이터 구하기

  • https://wikibook.co.kr/pyml-rev/ 수업자료 다운로드
  • Fisher의 붓꽃 데이터 internet에 공개되어 있음
  • iris.csv 검색
  • 판다스의 데이터로도 포함되어 있음

데이터 설명

  • 붓꽃 종류 3가지 Iris-setosa, Iris-versicolor, Iris-virginica
  • SepalLength : 꽃받침의 길이
  • SepalWidth : 꽃받침의 폭
  • PetalLength : 꽃잎의 길이
  • PetalWidth : 꽃잎의 너비

머신러닝으로 붓꽃 품종 분류하기

  • 150개의 데이터 / 100개 훈련용 / 50개 테스트용
In [30]:
from sklearn import svm, metrics
import random, re
# re 정규표현식 사용하도록
In [32]:
# 1. 붓꽃의 csv 데이터 읽어들이기

csv = []
with open('pyml_rev_examples_20191204/ch4/iris.csv', 'r', encoding='utf-8') as fp:
    for line in fp:
        line = line.strip() # 줄바꿈 없애기
        cols = line.split(',') # 콤마로 구분
        
        # 문자열 데이터를 숫자로 변환하기
        fn = lambda n : float(n) if re.match(r'^[0-9\.]+$', n) else n # n이 숫자가 아니면 숫자로 바꿔
        cols = list(map(fn, cols))
        csv.append(cols)
        
# 가장 앞 줄의 헤더 제거
del csv[0]
In [33]:
csv[:3]
Out[33]:
[[5.1, 3.5, 1.4, 0.2, 'Iris-setosa'],
 [4.9, 3.0, 1.4, 0.2, 'Iris-setosa'],
 [4.7, 3.2, 1.3, 0.2, 'Iris-setosa']]
In [34]:
data = pd.read_csv('pyml_rev_examples_20191204/ch4/iris.csv')
data.head()
Out[34]:
SepalLength SepalWidth PetalLength PetalWidth Name
0 5.1 3.5 1.4 0.2 Iris-setosa
1 4.9 3.0 1.4 0.2 Iris-setosa
2 4.7 3.2 1.3 0.2 Iris-setosa
3 4.6 3.1 1.5 0.2 Iris-setosa
4 5.0 3.6 1.4 0.2 Iris-setosa
In [35]:
# 2. 데이터 셔플하기(섞기)
random.shuffle(csv)
In [37]:
# 3. 학습 전용 데이터와 테스트 전용 데이터 분할하기 (2:1 비율)
total_len = len(csv)    # 150개
train_len = int(total_len * 2 / 3) # 2:1 비율로 분할할거니까   # 100개

# 학습용
train_data = []
train_label = []

# 테스트용
test_data = []
test_label = []

for i in range(total_len):
    data = csv[i][0:4] # i번째 행의 0~4번째 인덱스 열 앞 까지(SepalLength/SepalWidth/PetalLength/PetalWidth)
    label = csv[i][4] # i번째 행의 4 번째 인덱스 열 (Name)
    
    # 100번째 이하면 - 학습용 데이터로 분류
    if i < train_len:
        train_data.append(data)
        train_label.append(label)
    
    # 100번째 이상이면 - 테스트 데이터로 분류
    else:
        test_data.append(data)
        test_label.append(label)
In [40]:
train_data[:3]
Out[40]:
[[4.6, 3.1, 1.5, 0.2], [6.1, 2.8, 4.0, 1.3], [5.7, 4.4, 1.5, 0.4]]
In [41]:
train_label[:3]
Out[41]:
['Iris-setosa', 'Iris-versicolor', 'Iris-setosa']
In [42]:
test_data[:3]
Out[42]:
[[5.0, 3.2, 1.2, 0.2], [6.0, 2.2, 4.0, 1.0], [7.1, 3.0, 5.9, 2.1]]
In [43]:
test_label[:3]
Out[43]:
['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
In [44]:
# 4. 데이터를 학습시키고 예측하기
clf = svm.SVC()
clf.fit(train_data, train_label)
pre = clf.predict(test_data)
In [45]:
# 5. 정답률 구하기
ac_score = metrics.accuracy_score(test_label, pre)
print('정답률 = ', ac_score)
정답률 =  1.0

훈련용 / 테스트 데이터 분할 메서드

  • model_selection 모듈의 train_test_split()
In [50]:
import pandas as pd
from sklearn import svm, metrics
from sklearn.model_selection import train_test_split
In [51]:
# 1. 붓꽃의 CSV 데이터 읽어들이기
csv = pd.read_csv('pyml_rev_examples_20191204/ch4/iris.csv')
In [52]:
# 2. 컬럼명으로 필요한 열 추출하기

csv_data = csv[["SepalLength","SepalWidth","PetalLength","PetalWidth"]]

csv_label = csv["Name"]
In [53]:
# 3. 학습 전용 데이터와 테스트 전용 데이터로 나누기
train_data, test_data, train_label, test_label = train_test_split(csv_data, csv_label)
In [54]:
train_data[:3]
len(train_data)
Out[54]:
112
In [55]:
test_data[:3]
len(test_data)
Out[55]:
38
In [56]:
train_label[:3]
# 셔플이라 순서가 잘 섞여있음
Out[56]:
55     Iris-versicolor
122     Iris-virginica
48         Iris-setosa
Name: Name, dtype: object
In [57]:
test_label[:3]
Out[57]:
105     Iris-virginica
107     Iris-virginica
77     Iris-versicolor
Name: Name, dtype: object
In [58]:
# 4. 데이터 학습시키고 예측하기
clf = svm.SVC()
clf.fit(train_data, train_label)
pre = clf.predict(test_data)
In [59]:
# 5. 정답률 구하기
ac_score = metrics.accuracy_score(test_label, pre)
print('정답률 = ', ac_score)
정답률 =  0.8947368421052632

이미지 내부의 문자 인식

손글씨 숫자 인식

데이터 수집 MNIST : THE MINIST DATABASE

  • THE MINIST DATABASE of handwritten digits
  • http://yann.lecun.com/exdb/mnist
  • 위 사이트에서 자료 다운
  • 학습전용 6만개 / 테스트 전용 1만개
  • MNIST 자체 데이터베이스 형식
  • train-images-idx3-ubyte.gz: training set images (9912422 bytes) 다운
  • train-labels-idx1-ubyte.gz: training set labels (28881 bytes) 다운
  • t10k-images-idx3-ubyte.gz: test set images (1648877 bytes) 다운
  • t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes) 다운

데이터 구조

  • 사이트 확인: FILE FORMATS FOR THE MNIST DATABASE
  • 이미지데이터는 각 픽셀을 그레이스케일 256단계
  • 왼쪽 위부터 오른쪽 아래로 차례차례 픽셀이 나열된 형태
  • 0:흰색, 1~255 숫자가 클수록 짙은 부분
  • 바이너리 데이터 => CSV로 변환
  • <레이블>, <28x28의 픽셀 데이터>
  • 각파일은 32bit(매빅넘버)+32bit(이미지갯수)+이미지(8byte씩)
In [60]:
# binary 파일 변환을 위한 모듈 import
import struct
In [101]:
def to_csv(name, maxdata):
    # 레이블 파일과 이미지 파일 열기 (파일 경로)
    lbl_f = open('MNIST/'+name+'-labels.idx1-ubyte', 'rb') # 읽기모드
    img_f = open('MNIST/'+name+'-images.idx3-ubyte', 'rb')    
    csv_f = open('MNIST/'+name+'.csv', 'w', encoding='utf-8')  # 쓰기모드(저장)
    
    # 1. 헤더 정보 읽기
    # unpack() : 지정된 Format에 따라 String을 Unpack, 결과는 Tuple로 리턴 (원하는 바이너리 수만큼 읽고 변환)
    # > Little-endian 
    # II 4byte 4byte
    # https://suspected.tistory.com/155
    # PGM : 글자파일로 이미지 생성 가능
    mag, lbl_count = struct.unpack('>II', lbl_f.read(8))
    mag, img_count = struct.unpack('>II', img_f.read(8))
    rows, cols = struct.unpack('>II', img_f.read(8))
    pixels = rows * cols
    
    # 2. 이미지 데이터를 읽고 csv로 저장하기
    res = []
    for idx in range(lbl_count):
        if idx > maxdata: break
        label = struct.unpack('B', lbl_f.read(1))[0]
        bdata = img_f.read(pixels)
        sdata = list(map(lambda n: str(n), bdata))
        csv_f.write(str(label) + ',')
        csv_f.write(','.join(sdata) + '\r\n')
        
        # 3. 잘 저장됐는지 이미지 파일로 저장해서 테스트하기
        if idx < 10:
            s = 'P2 28 28 255\n'    # 28*28 사이즈 이미지에서 흰색칸은 0, 검은칸은 255이 있다
            s += ' '.join(sdata)
            iname = './MNIST/{0}-{1}-{2}.pgm'.format(name,idx,label)
            with open(iname, 'w', encoding='utf-8') as f:
                f.write(s)
    csv_f.close()
    lbl_f.close()
    img_f.close()    
In [102]:
# 4. 결과를 파일로 출력하기
# 6만개 처리 시간이 많이 걸려 학습 1000개 / 테스트 500개
to_csv("train", 1000)
to_csv("t10k", 500)
폴더에 png 파일 생성됨

image.png

pgm view 에 생성된 파일 넣으면 이미지 볼 수 있음

image.png

이미지 데이터 학습하기

  • 손글씨 숫자 데이터를 벡터로 변환
In [73]:
from sklearn import model_selection, svm, metrics
In [106]:
# 1. CSV 파일 읽어들이고 가공하기
def load_csv(fname):
    labels = []
    images = []
    with open(fname, 'r') as f:
        for line in f:
            cols = line.split(',')
            if len(cols) < 2: 
                continue
            labels.append(int(cols.pop(0)))
            
            # 0 <= vals < 1
            vals = list(map(lambda n: int(n) / 256, cols))
            images.append(vals)

    return {'labels':labels, 'images':images}

data = load_csv('MNIST/train.csv')
test = load_csv('MNIST/t10k.csv')
In [107]:
# 2. 학습하기
clf = svm.SVC()
clf.fit(data['images'], data['labels'])
Out[107]:
SVC()
In [108]:
# 3. 예측하기
predict = clf.predict(test['images'])
In [109]:
# 4. 결과 확인하기
ac_score = metrics.accuracy_score(test['labels'], predict)
cl_report = metrics.classification_report(test['labels'], predict)

print('정답률 = ', ac_score)
print('리포트 = ')
print(cl_report)
정답률 =  0.8842315369261478
리포트 = 
              precision    recall  f1-score   support

           0       0.87      0.98      0.92        42
           1       0.99      1.00      0.99        67
           2       0.91      0.89      0.90        55
           3       0.94      0.72      0.81        46
           4       0.86      0.93      0.89        55
           5       0.75      0.82      0.78        50
           6       0.95      0.81      0.88        43
           7       0.79      0.94      0.86        49
           8       0.94      0.82      0.88        40
           9       0.89      0.87      0.88        54

    accuracy                           0.88       501
   macro avg       0.89      0.88      0.88       501
weighted avg       0.89      0.88      0.88       501

외국어 문장 판별하기

  • 외국어 글자를 읽어 들이고 어떤 언어인지 판정하는 프로그램 만들기
  • 알파벳을 사용하는 언어라도 프랑스어, 타갈로어(필리핀), 인도네시아어 등

판정 방법

  • 글자를 곧바로 학습기에 넣을 수 없음
  • 글자를 나타내는 백터로 변경
  • 언어가 다르면 알파벳 출현 빈도가 다름(언어학)
  • a 부터 z 까지의 출현 빈도를 확인해서 이를 특징으로 사용

샘플 데이터 수집

  • 각 언어별로 풍부한 데이터가 있는 위키피디아 글자 사용
  • 영어(en), 프랑스어(fr), 인도네시아(id), 타갈로어(tl) 테스트
  • 학습데이터 20개 / 테스트 8개

언어판별

In [110]:
from sklearn import svm, metrics
import glob, os.path, re, json
In [119]:
# 1. 텍스트를 읽어들이고 출현 빈도 조사하기
def check_freq(fname):
    name = os.path.basename(fname)
    
    # 파일명 앞의 두 문자가 언어코드
    lang = re.match(r'^[a-z]{2,}', name).group()
#     print(name)
#     print(lang)
    
    with open(fname, 'r', encoding='utf-8') as f:
        text = f.read()
    text = text.lower()   # 소문자 변환
    
    # 숫자 세기 변수(cnt) 초기화하기
    cnt = [0 for n in range(0, 26)]   # 알파벳 26자
    
    # ord() 함수 : 특정한 한 문자를 아스키 코드 값으로 변환해주는 함수
    code_a = ord('a')
    code_z = ord('z')
#     print(code_a)
    
    # 2. 알파벳 출현 횟수 구하기
    for ch in text:
        n = ord(ch)
#         print(n)
        
        if code_a <= n <= code_z:   # a~z 사이에 있을 때, 알파벳만 처리
            cnt[n - code_a] += 1
    
    # 3. 정규화하기 (각 텍스트파일의 글자수가 다르므로 정규화 필요)
    total = sum(cnt)
    freq = list(map(lambda n: n / total, cnt))
    
    return(freq, lang)    
In [120]:
# 각 파일 처리하기
def load_files(path):
    freqs = []
    labels = []
    
    # glob() : 특정 파일만 출력하기
    # https://wikidocs.net/3746
    file_list = glob.glob(path)
    for fname in file_list:
        r = check_freq(fname)
        freqs.append(r[0])
        labels.append(r[1])
    return {'freqs':freqs, 'labels':labels}

data = load_files('pyml_rev_examples_20191204/ch4/lang/train/*.txt')
test = load_files('pyml_rev_examples_20191204/ch4/lang/test/*.txt')
In [121]:
# 위 데이터 JSON으로 결과 저장해두기 - 이미지 출력용
with open('pyml_rev_examples_20191204/ch4/lang/freq.json', 'w', encoding='utf-8') as fp:
    json.dump([data, test], fp)
In [122]:
# 4. 학습하기
clf = svm.SVC()
clf.fit(data['freqs'], data['labels'])
Out[122]:
SVC()
In [123]:
# 5. 예측하기
predict = clf.predict(test['freqs'])
In [124]:
# 6. 결과 테스트하기
ac_score = metrics.accuracy_score(test['labels'], predict)
cl_report = metrics.classification_report(test['labels'], predict)

print('정답률 = ', ac_score)
print('리포트 = ')
print(cl_report)
정답률 =  1.0
리포트 = 
              precision    recall  f1-score   support

          en       1.00      1.00      1.00         2
          fr       1.00      1.00      1.00         2
          id       1.00      1.00      1.00         2
          tl       1.00      1.00      1.00         2

    accuracy                           1.00         8
   macro avg       1.00      1.00      1.00         8
weighted avg       1.00      1.00      1.00         8

데이터별 분포를 그래프로 확인

  • 알파벳의 빈도가 어떻게 다른지 시각적으로 확인
In [125]:
import matplotlib.pyplot as plt
import pandas as pd
import json
In [126]:
# 1. 알파벳 출현 빈도 데이터 읽어들이기
with open('pyml_rev_examples_20191204/ch4/lang/freq.json', 'r', encoding='utf-8') as fp:
    freq = json.load(fp)
In [128]:
# 2. 언어마다 계산하기

lang_dic = {}

for i, lbl in enumerate(freq[0]['labels']):
    fq = freq[0]['freqs'][i]
    
    if not (lbl in lang_dic):
        lang_dic[lbl] = fq
        continue
    
    for idx, v in enumerate(fq):
        lang_dic[lbl][idx] = (lang_dic[lbl][idx] + v) / 2
In [129]:
# 3. Pandas의 DataFrame에 데이터 넣기
asclist = [[chr(n) for n in range(97,97+26)]]
df = pd.DataFrame(lang_dic, index=asclist)
In [130]:
df.head()
Out[130]:
en fr id tl
a 0.073792 0.076364 0.174445 0.200662
b 0.021171 0.013063 0.025219 0.022221
c 0.032744 0.036548 0.006991 0.016142
d 0.038673 0.050288 0.040997 0.028151
e 0.132650 0.148401 0.080607 0.056933
In [133]:
# 4. 그래프 그리기
df.plot(kind='bar', subplots=True, ylim=(0,0.15)) # subplots=True 그래프 각각 그려
plt.savefig('lang-plot.png')
In [134]:
# 다른 그래프로 확인
df.plot(kind='line')
C:\Users\205\.conda\envs\pydata\lib\site-packages\pandas\plotting\_matplotlib\core.py:1192: UserWarning: FixedFormatter should only be used together with FixedLocator
  ax.set_xticklabels(xticklabels)
Out[134]:
<AxesSubplot:>

Web 인터페이스 추가

  • 언어판정 모델을 활용해서 언어 판정 웹 서비스 개발
In [135]:
# pip install joblib
Requirement already satisfied: joblib in c:\users\205\.conda\envs\pydata\lib\site-packages (0.16.0)
Note: you may need to restart the kernel to use updated packages.
In [138]:
# 언어를 판정할때마다 데이터학습할 필요 없으니 학습 모델 저장
from sklearn import svm
# from sklearn.externals import joblib
import joblib
In [139]:
# 각 언어의 출현 빈도 데이터(JSON) 읽어들이기
with open('pyml_rev_examples_20191204/ch4/lang/freq.json','r', encoding='utf-8') as fp:
    d = json.load(fp)
    data = d[0]
In [141]:
# 데이터 학습하기
clf = svm.SVC()
clf.fit(data['freqs'], data['labels'])
Out[141]:
SVC()
In [142]:
# 학습 데이터 저장하기(pickle로)
# 학습한 모델을 저장할 수 있는 sklearn의 joblib
# https://minwook-shin.github.io/python-disk-caching-parallel-computing-using-joblib/
joblib.dump(clf, 'pyml_rev_examples_20191204/ch4/lang/freq.pkl')
print('ok')
ok

cgi program 구성

  • 파이썬 내장 웹서버 구성
  • cgi-bin 폴더 내부에 프로그램 배치, 폴더 상위가 root 가 됨
  • root/cgi-bin/웹프로그램.py
  • root 에서 명령어 실행(anaconda 콘솔에서 실행)
    • python -m http.server --cgi 포트
  • 웹프로그램 실행
In [ ]:
#!/usr/bin/env python3
import cgi, os.path
from sklearn.externals import joblib

# 학습 데이터 읽어 들이기
pklfile = os.path.dirname(__file__) + "/freq.pkl"
clf = joblib.load(pklfile)

# 텍스트 입력 양식 출력하기
def show_form(text, msg=""):
    print("Content-Type: text/html; charset=utf-8")
    print("")
    print("""
        <html><body><form>
        <textarea name="text" rows="8" cols="40">{0}</textarea>
        <p><input type="submit" value="판정"></p>
        <p>{1}</p>
        </form></body></html>
    """.format(cgi.escape(text), msg))

# 판정하기
def detect_lang(text):
    # 알파벳 출현 빈도 구하기
    text = text.lower() 
    code_a, code_z = (ord("a"), ord("z"))
    cnt = [0 for i in range(26)]
    for ch in text:
        n = ord(ch) - code_a
        if 0 <= n < 26: cnt[n] += 1
    total = sum(cnt)
    if total == 0: return "입력이 없습니다"
    freq = list(map(lambda n: n/total, cnt))
    # 언어 예측하기
    res = clf.predict([freq])
    # 언어 코드를 한국어로 변환하기
    lang_dic = {"en":"영어","fr":"프랑스어",
        "id":"인도네시아어", "tl":"타갈로그어"}
    return lang_dic[res[0]]

# 입력 양식의 값 읽어 들이기
form = cgi.FieldStorage()
text = form.getvalue("text", default="")
msg = ""
if text != "":
    lang = detect_lang(text)
    msg = "판정 결과:" + lang

# 입력 양식 출력
show_form(text, msg)
In [ ]:
# 위 코드를 메모장 등의 파일로 저장한 뒤, Anaconda prompt를 이용,
# python -m http.server --cgi 명령어로 웹으로 연결
# http://localhost:8080/cgi-bin/lang-Webapp.py (혹은 지정한 포트로) 웹 프로그램 실행