Bài toán 1: Nhận diện chữ số viết tay

Một phần của tài liệu HD3 trần ngọc tiến nghiên cứu một số ứng dụng của mạng thần kinh nhân tạo trong nhận dạng số viết tay và phát hiện nguy cơ mắc bệnh (Trang 72 - 95)

Nhận diện từ ảnh được chụp

Dựa trên cơ sở lí thuyết ở chương 2 ta xây dựng một chương trình code để tính toán với các bước cụ thể sau:

1. Đọc dữ liệu trên tập tranning và khởi tạo các mảng đầu vào, label tương ứng

import numpy as np import pandas as pd import cv2

import imutils

# đọc file excel bằng thư viện pandas và lưu vào biến data

72

data = pd.read_csv('/Users/huy/Desktop/do an CDT/train.csv') # tạo một mảng dữ liệu từ bảng excel trên

data = np.array(data)

# gán biến m, n tương ứng với kich thước hàng và cột của mảng data m = hàng, n = cột

m, n = data.shape

# xáo trộn vị trí các hàng một cách ngẫu nhiên np.random.shuffle(data)

# khởi tạo mảng data_train là chuyển vị của data data_train = data[:m].T

# khởi tạo mảng Y_label Y_train = data_train[0]

#khởi tạo mảng x đầu vào có kích thước 784 x m X_train = data_train[1:n]

# chia tất cả các gtri tromng mảng cho 255 để đầu vào nhỏ , tránh bão hòa khi qua các hàm kích hoạt

X_train = X_train / 255.

# gán biến m_train = số cột của mảng X_train _,m_train = X_train.shape

2. Khởi tạo ma trận các trọng số

def init_params():

# khởi tạo ma trận W1 có kích thước 20 x 784 với các gtri ngẫu nhiên W1 = np.random.rand(20, 784) - 0.5

# khởi tạo ma trận b1 có kích thước 20 x 1 với các gtri ngẫu nhiên b1 = np.random.rand(20, 1) - 0.5

# khởi tạo ma trận W2 có kích thước 10 x 20 với các gtri ngẫu nhiên W2 = np.random.rand(10, 20) - 0.5

# khởi tạo ma trận b2 có kích thước 10 x 1 với các gtri ngẫu nhiên b2 = np.random.rand(10, 1) - 0.5

# trả ra gtri của hàm là các gtri W1, b1, W2, b2 return W1, b1, W2, b2

3. Khởi tạo các hàm kích hoạt và đạo hàm( Relu, Softmax)

73

# so sánh và trả ra gtri của ham là một mangr các phần tử lớn hơn 0 return np.maximum(Z, 0)

# khởi tạo hàm kích hoạt Softmax biến đầu vào Z def softmax(Z):

# cong thức toán học của softmax A = np.exp(Z) / sum(np.exp(Z)) #trả ra gtri của hàm softmax return A

# đạo hàm của hàm ReLU def ReLU_deriv(Z):

# trả ra gtri của hàm là những gtri lớn hơn 0 return Z > 0

4. Lan truyền tiến

def forward_prop(W1, b1, W2, b2, X):

# nhân ma trận W1 với đầu vào X và + bias b1 rồi gán mảng kqua vào biến Z1

Z1 = W1.dot(X) + b1

# Đưa Z1 tính được làm biến đầu vào của hàm ReLU(Z) rồi gán mảng kết quả mới vào biến A1

A1 = ReLU(Z1)

# nhân ma trận W2 với mảng A1 trên + bias b2 rồi gán mảng kết quả vào biến Z2

Z2 = W2.dot(A1) + b2

# Đưa Z2 tính được làm biến đầu vào của hàm softmax(Z) rồi gán mảng kết qủa mới vào biến A2

A2 = softmax(Z2)

# trả ra giá tri của hàm forward_prop là Z1, A1, Z2, A2 return Z1, A1, Z2, A2

5. Khởi tạo ma trận one_hot_Y và thực hiện lan truyền ngược

def one_hot(Y):

# khởi tạo một mảng với các gtri = 0 có (Y.max() = 9 + 1) = 10 hàng tương ứng từ 0-9 và số cột bằng số cột A2

one_hot_Y = np.zeros((Y.size, Y.max() + 1))

# lần lượt xét từng hàng, với label thực tế là i thi gtri tại hàng i+1 = 1 one_hot_Y[np.arange(Y.size), Y] = 1

# lấy ma trận chuyển vị one_hot_Y = one_hot_Y.T

# trả ra gtri của hàm là ma trânj chuyển vị vừa tim được của hàm one_hot() return one_hot_Y

74

# lan truyền ngược, đạo hàm các trọng số theo hàm loss function để đánh giá mức độ ảnh hưởng của chúng tới hàm mấy mát

def backward_prop(Z1, A1, Z2, A2, W1, W2, X, Y): # thực hiện các phép toán nhân chia cộng trừ ma trận one_hot_Y = one_hot(Y) dZ2 = A2 - one_hot_Y dW2 = 1 / m * dZ2.dot(A1.T) db2 = 1 / m * np.sum(dZ2) dZ1 = W2.T.dot(dZ2) * ReLU_deriv(Z1) dW1 = 1 / m * dZ1.dot(X.T) db1 = 1 / m * np.sum(dZ1)

# trả ra các gtri dao hàm dW1, db1, dW2, db2 của hàm backward_prop() return dW1, db1, dW2, db2

6. Cập nhật lại bộ trọng số bằng thuật toán Gradient descent

def update_params(W1, b1, W2, b2, dW1, db1, dW2, db2, alpha):

# thực hiện thuật toán gradient descent với 4 trọng số W1, b1, W2, b2 để tìm ra được một bộ trọng

# số cho hàm lossfunction gần với cực tiểu nhất W1 = W1 - alpha * dW1

b1 = b1 - alpha * db1 W2 = W2 - alpha * dW2 b2 = b2 - alpha * db2

# trả ra các gtri W1, b1, W2, b2 cuối cùng của hàm update_params() return W1, b1, W2, b2

7. Tìm ra lớp có xác suất cao nhất trong 10 lớp ( 0 – 9 ) và tìm độ chính xác của giá trị dự đoán so với label thực

def get_predictions(A2):

# mảng đầu ra A2 có kích thước 10 x m

# axis = 0 :tìm ra vị trí của hàng có phân phối xác xuất cao nhất trong số 10 hàng của A2 và xét theo cột.

# trả ra một mảng mới có kích thước 1 x m ứng với các gtri dự đoán cho từng label thực tế Y_train

return np.argmax(A2, 0)

# khởi tạo hàm tính đô chính xác của dự đoán def get_accuracy(predictions, Y):

# in ra màn hình gtri dự đoán là một mảng A2 kích thước 1xm và Y_train print(predictions, Y)

# trả ra gtri của hàm get_accuracy() là tổng số các gtri trùng khớp tại từng cột tương ứng / tổng số các phần tử của Y_train

75

return np.sum(predictions == Y) / Y.size

8. Thực hiện lặp lại thuật toán Gradient descent nhiều lần tới khi tìm được bộ trọng số phù hợp ( hàm mất mát nằm trong mức chấp nhận được ) và lưu vào các biến [1] [2] [1] [2]

W , W ,b ,b

def gradient_descent(X, Y, alpha, iterations):

# gán các biến W1, b1, W2, b2 bằng với đầu ra của hàm init_params() đã khởi tạo ở trên

W1, b1, W2, b2 = init_params()

# tạo một vòng lặp từ 0 - iterations lần for i in range(iterations):

# gán các biến Z1, A1, Z2, A2 bằng gtri đầu ra của hàm forward_prop() đã khởi tao ở trên

Z1, A1, Z2, A2 = forward_prop(W1, b1, W2, b2, X)

# gán các biến dW1, db1, dW2, db2 bằng với gtri đầu ra của hàm backward_prop() đã khởi tao ở trên

dW1, db1, dW2, db2 = backward_prop(Z1, A1, Z2, A2, W1, W2, X, Y) # cập nhấtj lại bộ trọng số W1, b1, W2, b2 bằng gtri đầu ra của hàm update_params() đã khởi tao ở trên

W1, b1, W2, b2 = update_params(W1, b1, W2, b2, dW1, db1, dW2, db2, alpha)

# nếu lần chạy thứ i mà i chia hết cho 10 thì in ra màn hình i if i % 10 == 0:

print("Iteration: ", i)

# gán đầu ra của hàm get_predictions(A2) vào biến predictions predictions = get_predictions(A2)

# in ra màn hình mảng dự đoán predictions và mảng label thưc tế print(get_accuracy(predictions, Y))

# trả ra gtri của hàm gradient_descent() là các trọng số cuối cùng W1, b1, W2, b2

return W1, b1, W2, b2

# gán các biến trọng số cuối cùng bằng với đầu ra của hàm gradient_descent()

W1, b1, W2, b2 = gradient_descent(X_train, Y_train, 1, 250)

9. Khởi tạo hàm để tính toán với đầu vào tùy chọn và bộ trọng số thích hợp đã tìm được ở trên:

def make_predictions(X, W1, b1, W2, b2):

# gán gtri của đầu ra A2 của hàm forward_prop(W1, b1, W2, b2, X) vào biến A2

76

_, _, _, A2 = forward_prop(W1, b1, W2, b2, X)

# gán biến predictions bằng với đầu ra hàm get_predictions() predictions = get_predictions(A2)

# trả ra gtri của hàm make_predictions() là giá trị dự đoán predictions return predictions

10. Xử lí ảnh để đưa tới hàm tính toán

Chọn một ảnh đầu vào chứa các chữ số cần nhận diện nằm trong các khung cho trước: Trước tiên ta cần làm một số bước tiền xử lí ảnh để convert ảnh chụp màu sáng nhị phân binary để làm đầu vào tính toán. Thứ tự thực hiện là :

Ảnh màu → Ảnh xám→Làm mờ(Lọc nhiễu)→Ảnh nhị phân

label = cv2.imread("so24_viettay.png") #convert ảnh màu thành ảnh xám

img_gray = cv2.cvtColor(label, cv2.COLOR_BGR2GRAY) #làm mờ

blur = cv2.GaussianBlur(img_gray,(7, 7), 0)

#convert ảnh sau khi làm mờ sang dạng ảnh nhị phân binary đen trắng

77

img_binary=cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUS SIAN_C, cv2.THRESH_BINARY_INV ,21,20)

Bây giờ ta thu được một ảnh nhị phân binary với 2 màu đen trắng và kích thước ban đầu. Mỗi ảnh sẽ nằm trong một contour hình vuông khép kín và sẽ được cắt thành các ảnh riêng biệt bằng đoạn code tìm và cắt contour như sau:

contours=cv2.findContours(img_binary,cv2.RETR_LIST,cv2.CHAIN_AP POX_ SIMPLE)

# lấy một giá tri tuple thích hợp

contours = imutils.grab_contours(contours)

# sắp xếp contours theo danh sách kích thước từ nhỏ tới lớn

contours = sorted(contours, key=cv2.contourArea, reverse=False) # khởi tạo một biến number = 0

number = 0

78

# khởi tạo vòng lặp để cắt và lưu ảnh cho lớp đầu vào tính toán for contour in contours:

# xác định chi vi contours là đường bao khép kín = True hoặc chỉ là một đường cong

perimeter = cv2.arcLength(contour, True) # điều chỉnh độ dày của đường viền

approx = cv2.approxPolyDP(contour, 0.01 * perimeter, True) # khởi taoj các gtri tương ứng các cạnh của contours ( box ) x, y, w, h = cv2.boundingRect(contour)

# chỉ xét với các box chữ nhật if len(approx) == 4:

contour_with_license_plate = approx

# với mỗi khung hình chữ nhật tìm đươc, biến number tăng lên 1 number += 1

# cắt anh ban đầu thành một ảnh phu hợp để đưa tới biến đầu vào license_plate = label[(y+4) :(y + h-4), (x+4) :(x + w-4)]

#lưu ảnh

cv2.imwrite("anh{}.png".format(number), license_plate) # gán số lượng ảnh vào biến so_chu_so

so_chu_so = number

Như vậy với n chữ số viết trong n khung thì sẽ cắt ra được n ảnh và lưu vào thư viện của máy.

79 Kết quả thu được 10 ảnh với thứ tự lần lượt :

11. Xử lí từng ảnh một và xuất ra kết quả dự đoán

# khởi tạo một mảng trắng để lưu dữ liệu dữ đoán thành một mảng riêng predict = []

# i bắt đầu từ 0 tới number-1 nên ta sẽ cộng thêm một vào số lượng ảnh va bắt đầu vòng lăp từ i = 1 tương ứng ảnh 1

for i in range(number+1):

# điều kiện i > 0 thì mới thực hiên ctrinh phía sau if i > 0:

# đọc ảnh " anh{i}.png " được lưu ở trên img = cv2.imread("anh{}.png".format(i)) # chuyển sang thang màu xám

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # chuyển ảnh về thang màu đen tráng nhị phân

img_binary=cv2.adaptiveThreshold(img_gray,255,cv2.ADAPTIVE_THRESH_ GAUSSIAN_C, cv2.THRESH_BINARY_INV ,21,20)

Resize các ảnh về kích thước 28x28 để sử dụng tập trainning của bộ dữ liệu MNIST. Cần resize để ảnh giữ nguyên được tỉ lệ khung hình, không bị méo mó và lệch lạc dẫn tới sai lệch gía trị của các pixel so với ban đầu.

def get_square(image,square_size):

80

#khởi tạo một biến differ bằng cạnh dài của ảnh cao,rong=image.shape if(cao > rong): differ=cao else: differ=rong differ+=1

# khởi tạo một ảnh đen có kích thước lớn hơn ảnh cần resize mask = np.zeros((differ, differ), dtype="uint8")

x_pos = int((differ-rong)/2) y_pos = int((differ-cao)/2)

#đưa ảnh thật vào tâm của ảnh đen vừa khởi tạo

mask[y_pos:y_pos+cao, x_pos:x_pos+rong] = image[0:cao, 0:rong] #resize ảnh đen về kích thước mong muốn

mask = cv2.resize(mask, (square_size, square_size), interpolation=cv2.INTER_AREA)

#trả ra kết quả của hàm là một ảnh đã đc scale về kích thước tùy chọn return mask

Ý tưởng thực hiện là ta sẽ tạo ra một ảnh đen với kich thước lớn hơn kích thước ảnh cần resize. Sau đó ta đưa ảnh thật vào chính giữa ảnh đen và resize ảnh đen thì đồng thời ảnh đầu vào cũng sẽ được resize đều 4 cạnh nên giữ nguyên được tỉ lệ khung hình ban đầu. tuy nhiên giá trị các pixel sau khi resize bị giảm nên ta sẽ tăng cường độ sáng tại cái điểm đó lên để kết quá tính toán đạt độ chính xác cao hơn bằng đoạn code sau:

81

set1,last=cv2.threshold(get_square(img_binary, 28), 10, 255, cv2.THRESH_BINARY)

Kết quả sau khi resize và tăng cường độ sáng:

Cuối cùng ta sẽ lưu giá trị các pixel của ảnh vào một mảng a làm đầu vào tính toán. Vòng lặp for cho phép thực hiện dự đoán n lần với n tương ứng là số chữ số cần nhận diện và kết quả mỗi lần sẽ được cập nhật vào một mảng trắng khai báo trước đó và in mảng đó ra màn hình:

cv2.imwrite("binary{}.png".format(i), last)

# lưu gtri các pixel vào một mảng a chính là đầu vào của model dự đoán a = np.array(last).reshape(784, 1)

# chia tất cả các gtri trong mảng cho 255 a = a/255.

# khởi tạo hàm dự đoán

def test_prediction(X, W1, b1, W2, b2):

# gán giá trị đầu ra của hàm make_predictions() ở trên vào biến prediction

prediction = make_predictions(X, W1, b1, W2, b2) # trả ra gtri của hàm test_prediction()

82

return prediction

# gán gtri đầu ra của hàm test_prediction() vào biến a với đầu vào a, W1, b1, W2, b2

a = test_prediction(a, W1, b1, W2, b2)

# lưu gtri vừa dự đoán vào mảng trắng đã tạo trước đó predict.append(a)

12. In mảng đó ra màn hình dãy số dự đoán

# reshape lại kích thước của mảng hiện thị dãy các số dự đoán (1xnumber) predict = np.array(predict, dtype=int).reshape(number,)

#in dãy dự đoán ra màn hình for i in range(len(predict)+1):

if i>0:

print("anh{}".format(i)+" "+"predict:", predict[i-1]) cv2.destroyAllWindows()

Như vậy ta đã xây dựng được một mô hình tính toán và nhận diện chữ số viết tay thông qua ảnh chụp. lần chạy : 250 kết quả thu được

83

Model đã dự đoán chính xác với độ chính xác cao 92.27% trên tập trainning. Số ảnh được dự đoán chính xác là 9/10. Ở một vài lần chạy sau tý lệ chính xác nằm trong khoảng 7-9/10. Trên thực tế do k sử dụng các công cụ tiền xử lí ảnh mạnh như mạng noron tích chập CNN ,.. kết quả nhận diện cũng bị ảnh hưởng khá nhiều do chât lượng ảnh chụp.

Nhận diện số viết tay trên giao diện do nhóm thiết kế

Về nguyên lí và tính toán không có sự thay đổi, phần mở rộng thêm đó là một giao diện tương tự như PAINT trên window do nhóm thiết kế bằng tkinter và cái widget nó cung cấp cụ thể như sau:

1. Khai báo các thư viện cần dùng và khởi tạo giao diện

import cv2 import tkinter

from tkinter import* import win32gui import time

from PIL import ImageGrab

# thiết lập giao diện bằng thư viện tkinter root = Tk()

root.title("PHẦN MỀM NHẬN DIỆN SỐ VIẾT TAY") root.geometry("500x500") width = 8 color = "black" new_width = DoubleVar(root) 2. Khởi tạo hàm vẽ def paint(event):

# toa đô tưc thời trên giao diện canvas

84

x, y = event.x, event.y

# tạo ra hình oval tại mỗi tọa độ trên

canvas.create_oval(x-width, y-width, x+width, y+width, fill=color, width=0)

def paint1(event): x, y = event.x, event.y

canvas3.create_oval(x-width, y-width, x+width, y+width, fill=color, width=0)

# khởi tạo hàm thay đổi màu sắc của đường vẽ def setcolor(newcolor):

global color color = newcolor

3. Khởi tạo các màu để lựa chọn

def colorset():

#khởi tạo các ô vuông màu tương ứng với màu chọn trên vùng canvas2 id = canvas2.create_rectangle(10, 10, 30, 30, fill="red", width=1) # .blind bắt sự kiên và thực thi sư kiên bằng click chuột

canvas2.tag_bind(id, "<Button-1>", lambda x: setcolor("red")) id = canvas2.create_rectangle(40, 10, 60, 30, fill="black", width=1) canvas2.tag_bind(id, "<Button-1>", lambda x: setcolor("black")) id = canvas2.create_rectangle(70, 10, 90, 30, fill="blue", width=1) canvas2.tag_bind(id, "<Button-1>", lambda x: setcolor("blue"))

85

canvas2.tag_bind(id, "<Button-1>", lambda x: setcolor("green")) id = canvas2.create_rectangle(130, 10, 150, 30, fill="purple", width=1) canvas2.tag_bind(id, "<Button-1>", lambda x: setcolor("purple"))

4. Khởi tạo hàm chức năng của các nút bấm

def clearall():

# xóa hết các đường trên giao diện canvas canvas.delete("all")

canvas3.delete("all")

# thay đổi giá trị của label = 1 khoảng trắng lbl.configure(text=" ", bg="white") # set giá trị thanh tiến trình về 0

bar["value"] = 0

# set giá trị phần trăm tiến trình = 0 percent.set(str(int(0)) + '%')

# khởi tạo hàm thực thi button " save " def saveimage():

# chon ví trí của ảnh lưu là vùng canvas id = canvas.winfo_id()

# khởi tạo vùng lưu ảnh

rect = win32gui.GetWindowRect(id) # lưu ảnh theo thời gian thực

currentTime1 = time.strftime("ATest") # lưu lại ảnh dưới dạng .png

ImageGrab.grab(rect).save(f"{currentTime1}.png")

id = canvas3.winfo_id()

86

currentTime2 = time.strftime("BTest")

ImageGrab.grab(rect).save(f"{currentTime2}.png")

# trả ra kết quả của hàm saveimage() là 2 ảnh tương ứng 2 canvas để vẽ return currentTime1, currentTime2

5. Khởi tạo hàm xét độ dày đường vẽ trên canvas

def setwidth(event): global width

width = new_width.get()

6. Đưa tất cả chương trình xử lí vào một hàm

def xu_li_anh():

data = pd.read_csv('/Users/huy/Desktop/do an CDT/train.csv') data = np.array(data) m, n = data.shape np.random.shuffle(data) data_train = data[:m].T Y_train = data_train[0] X_train = data_train[1:n] X_train = X_train / 255. _, m_train = X_train.shape def init_params(): W1 = np.random.rand(30, 784) - 0.5 b1 = np.random.rand(30, 1) - 0.5 W2 = np.random.rand(10, 30) - 0.5 b2 = np.random.rand(10, 1) - 0.5 return W1, b1, W2, b2 def ReLU(Z): return np.maximum(Z, 0) def softmax(Z): A = np.exp(Z) / sum(np.exp(Z))

87 return A def forward_prop(W1, b1, W2, b2, X): Z1 = W1.dot(X) + b1 A1 = ReLU(Z1) Z2 = W2.dot(A1) + b2 A2 = softmax(Z2) return Z1, A1, Z2, A2 def ReLU_deriv(Z): return Z > 0 def one_hot(Y):

one_hot_Y = np.zeros((Y.size, Y.max() + 1)) one_hot_Y[np.arange(Y.size), Y]= 1 one_hot_Y = one_hot_Y.T

return one_hot_Y

def backward_prop(Z1, A2, Z2, A2, W1, W2, X, Y): one_hot_Y = one_hot(Y) dZ2 = A2 - one_hot_Y dW2 = 1 / m * dZ2.dot(A1.T) db2 = 1 / m * np.sum(dZ2) dZ1 = W2.T.dot(dZ2) * ReLU_deriv(Z1) dW1 = 1 / m * dZ1.dot(X.T) db1 = 1 / m * np.sum(dZ1) return dW1, db1, dW2, db2

def update_params(W1, b1, W2, b2, dW1, db1, dW2, db2, alpha): W1 = W1 - alpha * dW1

b1 = b1 - alpha * db1 W2 = W2 - alpha * dW2

Một phần của tài liệu HD3 trần ngọc tiến nghiên cứu một số ứng dụng của mạng thần kinh nhân tạo trong nhận dạng số viết tay và phát hiện nguy cơ mắc bệnh (Trang 72 - 95)