Khóa học

Học sâu là một nhánh của học máy, tập hợp các thuật toán lấy cảm hứng từ cấu trúc và chức năng của não bộ.
TensorFlow là khung học máy thứ hai mà Google tạo ra và dùng để thiết kế, xây dựng và huấn luyện các mô hình học sâu. Bạn có thể dùng thư viện TensorFlow để thực hiện các phép tính số; bản thân điều này nghe có vẻ không quá đặc biệt, nhưng các phép tính này được thực hiện bằng đồ thị luồng dữ liệu. Trong các đồ thị này, các nút đại diện cho phép toán học, còn các cạnh đại diện cho dữ liệu, thường là các mảng dữ liệu đa chiều hoặc tensor, được truyền giữa các cạnh.
Thấy chưa? Tên gọi “TensorFlow” bắt nguồn từ các phép toán mà mạng nơ-ron thực hiện trên các mảng dữ liệu đa chiều hay tensor! Nghĩa đen là dòng chảy của các tensor. Tạm thời, bấy nhiêu là đủ về tensor, bạn sẽ đi sâu hơn ở các phần tiếp theo!
Hướng dẫn TensorFlow cho người mới bắt đầu hôm nay sẽ giới thiệu bạn cách thực hiện học sâu một cách tương tác:
Tải xuống notebook của hướng dẫn này tại đây.
Bạn cũng có thể quan tâm đến khóa học Deep Learning in Python, hướng dẫn Keras của DataCamp hoặc hướng dẫn keras với R.
Giới thiệu về Tensor
Để hiểu rõ tensor, bạn nên có một chút kiến thức về đại số tuyến tính và giải tích vectơ. Ở phần mở đầu, bạn đã đọc rằng tensor được hiện thực trong TensorFlow như các mảng dữ liệu đa chiều, nhưng có lẽ cần thêm phần dẫn nhập để nắm trọn vẹn về tensor và cách dùng chúng trong học máy.
Vectơ Phẳng
Trước khi vào vectơ phẳng, bạn nên điểm lại ngắn gọn khái niệm “vectơ”; Vectơ là dạng đặc biệt của ma trận, vốn là các mảng số hình chữ nhật. Vì vectơ là tập hợp có thứ tự của các số, chúng thường được xem như ma trận cột: chỉ có một cột và một số hàng nhất định. Nói cách khác, bạn cũng có thể coi vectơ là các đại lượng vô hướng được gán một hướng.
Ghi nhớ: ví dụ về vô hướng là “5 mét” hoặc “60 m/giây”, còn vectơ là, chẳng hạn, “5 mét về phía bắc” hoặc “60 m/giây về phía Đông”. Khác biệt rõ ràng là vectơ có hướng. Tuy nhiên, những ví dụ vừa thấy có vẻ xa lạ với các vectơ bạn gặp khi làm bài toán học máy. Điều này là bình thường; Độ dài của một vectơ toán học là một con số thuần túy: tuyệt đối. Ngược lại, hướng là tương đối: được đo so với một hướng tham chiếu và có đơn vị radian hoặc độ. Thường giả định hướng là dương và quay ngược chiều kim đồng hồ từ hướng tham chiếu.

Về mặt trực quan, bạn biểu diễn vectơ bằng các mũi tên, như trong hình trên. Điều này có nghĩa bạn cũng có thể coi vectơ là các mũi tên có hướng và độ dài. Hướng thể hiện bằng đầu mũi tên, còn độ dài thể hiện bằng chiều dài mũi tên.
Vậy vectơ phẳng là gì?
Vectơ phẳng là thiết lập trực quan nhất của tensor. Chúng khá giống các vectơ thông thường như trên, với khác biệt duy nhất là chúng nằm trong một không gian vectơ. Để hiểu rõ hơn, hãy bắt đầu với ví dụ: bạn có một vectơ kích thước 2 X 1. Điều này có nghĩa vectơ thuộc tập số thực đi theo cặp hai phần tử một lần. Hay nói cách khác, chúng thuộc không gian hai chiều. Trong các trường hợp như vậy, bạn có thể biểu diễn vectơ trên mặt phẳng tọa độ (x,y) bằng mũi tên hay tia.
Làm việc từ mặt phẳng tọa độ ở vị trí chuẩn, nơi vectơ có điểm cuối tại gốc (0,0), bạn có thể suy ra tọa độ x bằng cách nhìn vào hàng đầu tiên của vectơ, còn tọa độ y ở hàng thứ hai. Tất nhiên, vị trí chuẩn này không phải lúc nào cũng cần giữ nguyên: vectơ có thể tịnh tiến song song với chính nó trên mặt phẳng mà không thay đổi.
Lưu ý tương tự, với vectơ kích thước 3 X 1, bạn nói về không gian ba chiều. Bạn có thể biểu diễn vectơ như một hình ba chiều với mũi tên chỉ đến các vị trí trong không gian vectơ: được vẽ trên các trục chuẩn x, y và z.
Thật tuyệt khi có các vectơ và biểu diễn chúng trên mặt phẳng tọa độ, nhưng về bản chất, bạn có các vectơ để thực hiện các phép toán trên chúng và một điều có thể giúp ích là biểu diễn vectơ thành các cơ sở hoặc vectơ đơn vị.
Vectơ đơn vị là vectơ có độ lớn bằng một. Bạn thường nhận ra vectơ đơn vị bằng một chữ cái thường có dấu mũ. Vectơ đơn vị hữu dụng khi bạn muốn biểu diễn một vectơ 2-D hoặc 3-D như tổng của hai hoặc ba thành phần trực giao, chẳng hạn các trục x− và y−, hoặc trục z−.
Và khi bạn nói về việc biểu diễn một vectơ, chẳng hạn, như tổng các thành phần, bạn sẽ thấy mình đang nói về vectơ thành phần, tức là hai hoặc nhiều vectơ có tổng bằng vectơ đã cho.
Mẹo: xem video này, giải thích tensor bằng các vật dụng gia đình đơn giản!
Tensor
Bên cạnh vectơ phẳng, còn có đồng vectơ (covector) và toán tử tuyến tính là hai trường hợp khác mà cả ba có điểm chung: đều là các trường hợp cụ thể của tensor. Bạn còn nhớ cách đặc trưng một vectơ ở phần trước là các đại lượng vô hướng được gán một hướng. Vậy tensor là biểu diễn toán học của một thực thể vật lý có thể đặc trưng bởi độ lớn và nhiều hướng.
Và, giống như bạn biểu diễn một vô hướng bằng một số duy nhất và một vectơ bằng một dãy ba số trong không gian 3 chiều chẳng hạn, một tensor có thể được biểu diễn bằng một mảng 3^R số trong không gian 3 chiều.
Chữ “R” trong ký hiệu này là hạng (rank) của tensor: nghĩa là trong không gian 3 chiều, một tensor hạng hai có thể được biểu diễn bằng 3 mũ 2 tức 9 số. Trong không gian N chiều, vô hướng vẫn chỉ cần một số, vectơ cần N số, và tensor cần N^R số. Điều này giải thích vì sao bạn thường nghe vô hướng là tensor hạng 0: vì chúng không có hướng, có thể biểu diễn bằng một số.
Với điều này, việc nhận biết vô hướng, vectơ và tensor và phân biệt chúng trở nên khá dễ: vô hướng biểu diễn bằng một số, vectơ bằng một tập hợp có thứ tự các số, và tensor bằng một mảng số.
Điểm làm tensor trở nên độc đáo là sự kết hợp giữa các thành phần và các vectơ cơ sở: các vectơ cơ sở biến đổi theo một cách giữa các hệ quy chiếu và các thành phần biến đổi theo cách sao cho giữ nguyên sự kết hợp giữa thành phần và vectơ cơ sở.
Cài đặt TensorFlow
Giờ bạn đã biết thêm về TensorFlow, đã đến lúc bắt đầu và cài đặt thư viện. Ở đây, cần biết rằng TensorFlow cung cấp API cho Python, C++, Haskell, Java, Go, Rust, và còn có gói bên thứ ba cho R tên là tensorflow.
Mẹo: nếu bạn muốn biết thêm về các gói học sâu trong R, hãy xem keras: Hướng dẫn Deep Learning trong R của DataCamp.
Trong hướng dẫn này, bạn sẽ tải một phiên bản TensorFlow cho phép bạn viết mã cho dự án học sâu bằng Python. Trên trang cài đặt TensorFlow, bạn sẽ thấy một số cách phổ biến và hướng dẫn mới nhất để cài đặt TensorFlow bằng virtualenv, pip, Docker và cuối cùng là một số cách khác để cài đặt TensorFlow trên máy cá nhân.
Lưu ý Bạn cũng có thể cài TensorFlow bằng Conda nếu bạn dùng Windows. Tuy nhiên, vì cài đặt TensorFlow theo cách này dựa vào cộng đồng hỗ trợ, tốt nhất nên kiểm tra hướng dẫn cài đặt chính thức.
Bây giờ bạn đã hoàn tất quá trình cài đặt, hãy kiểm tra lại rằng bạn đã cài đặt đúng TensorFlow bằng cách import nó vào không gian làm việc với bí danh tf:
import tensorflow as tf
Lưu ý rằng bí danh bạn dùng trong dòng mã trên là một dạng quy ước - Nó giúp bạn đồng nhất với các nhà phát triển khác đang dùng TensorFlow trong các dự án khoa học dữ liệu, và với các dự án mã nguồn mở của TensorFlow.
Bắt đầu với TensorFlow: Cơ bản
Bạn thường sẽ viết các chương trình TensorFlow và chạy chúng theo khối; Thoạt nhìn điều này có vẻ mâu thuẫn khi bạn làm việc với Python. Tuy nhiên, nếu muốn, bạn cũng có thể dùng Interactive Session của TensorFlow để làm việc tương tác hơn với thư viện. Điều này đặc biệt tiện nếu bạn quen làm việc với IPython.
Trong hướng dẫn này, bạn sẽ tập trung vào phương án thứ hai: điều này sẽ giúp bạn khởi động nhanh với học sâu trong TensorFlow. Nhưng trước khi đi sâu hơn, hãy thử vài thứ nhỏ trước khi “nâng tạ nặng”.
Đầu tiên, import thư viện tensorflow với bí danh tf, như bạn đã thấy ở phần trước. Sau đó khởi tạo hai biến thực chất là hằng số. Truyền một mảng gồm bốn số vào hàm constant().
Lưu ý rằng bạn cũng có thể truyền một số nguyên, nhưng thường thì bạn sẽ làm việc với mảng. Như đã thấy ở phần mở đầu, tensor là các mảng! Vậy nên hãy truyền vào một mảng :) Tiếp theo, bạn có thể dùng multiply() để nhân hai biến. Lưu kết quả vào biến result. Cuối cùng, in result bằng hàm print().
Lưu ý rằng bạn đã định nghĩa các hằng trong khối mã DataCamp Light ở trên. Tuy vậy, còn hai kiểu giá trị khác mà bạn có thể dùng, là placeholder, tức là các giá trị chưa gán và sẽ được khởi tạo bởi phiên khi bạn chạy. Đúng như tên gọi, nó chỉ là chỗ giữ chỗ cho một tensor sẽ luôn được cung cấp khi phiên chạy; Ngoài ra còn có Variable, là các giá trị có thể thay đổi. Các hằng số, như bạn có thể đoán, là các giá trị không đổi.
Kết quả của các dòng mã là một tensor trừu tượng trong đồ thị tính toán. Tuy nhiên, trái với mong đợi, result thực sự chưa được tính. Nó chỉ định nghĩa mô hình, nhưng chưa có tiến trình nào chạy để tính kết quả. Bạn có thể thấy điều này ở phần in ra: không có kết quả thực sự (cụ thể là 30). Điều này có nghĩa TensorFlow đánh giá lười!
Tuy nhiên, nếu bạn muốn thấy kết quả, bạn phải chạy mã này trong một phiên tương tác. Bạn có thể làm điều này theo vài cách, như minh họa trong các khối mã DataCamp Light bên dưới:
Lưu ý rằng bạn cũng có thể dùng các dòng mã sau để khởi động một Session tương tác, chạy result và tự động đóng Session sau khi in output:
Trong các khối mã trên bạn chỉ định nghĩa một Session mặc định, nhưng cũng nên biết rằng bạn có thể truyền các tùy chọn. Ví dụ, bạn có thể chỉ định đối số config rồi dùng bộ đệm giao thức ConfigProto để thêm tùy chọn cấu hình cho phiên.
Ví dụ, nếu bạn thêm
config=tf.ConfigProto(log_device_placement=True)
vào Session, bạn sẽ ghi log thiết bị GPU hoặc CPU được gán cho một phép toán. Bạn sẽ nhận được thông tin về thiết bị được dùng trong phiên cho từng phép toán. Bạn cũng có thể dùng cấu hình sau, chẳng hạn khi bạn dùng ràng buộc mềm cho việc gán thiết bị:
config=tf.ConfigProto(allow_soft_placement=True)
Giờ bạn đã cài đặt và import TensorFlow vào không gian làm việc và đi qua phần cơ bản khi làm việc với gói này, đã đến lúc tạm gác lại và tập trung vào dữ liệu. Như thường lệ, trước khi bắt đầu xây dựng mạng nơ-ron, bạn sẽ dành thời gian khám phá và hiểu dữ liệu.
Biển Báo Giao Thông Bỉ: Bối cảnh
Mặc dù giao thông là chủ đề quen thuộc với hầu hết mọi người, cũng không thừa khi điểm lại nhanh các quan sát có trong bộ dữ liệu này để đảm bảo bạn hiểu mọi thứ trước khi bắt đầu. Về bản chất, ở phần này, bạn sẽ nắm kiến thức miền cần có để tiếp tục với hướng dẫn.
Tất nhiên, vì tôi là người Bỉ, tôi sẽ kể thêm vài giai thoại :)
- Biển báo giao thông Bỉ thường bằng tiếng Hà Lan và tiếng Pháp. Điều này đáng biết, nhưng với bộ dữ liệu bạn sẽ làm việc thì không quá quan trọng!
- Có sáu nhóm biển báo ở Bỉ: biển cảnh báo, biển ưu tiên, biển cấm, biển hiệu lệnh, biển liên quan đến đỗ/dừng trên đường và cuối cùng là biển chỉ dẫn.
- Ngày 1/1/2017, hơn 30.000 biển báo đã được gỡ khỏi đường ở Bỉ. Tất cả là biển cấm liên quan đến tốc độ.
- Nói về việc gỡ bỏ, sự hiện diện dày đặc của biển báo là chủ đề tranh luận kéo dài ở Bỉ (và rộng ra là toàn Liên minh châu Âu).
Nạp và Khám phá Dữ liệu
Giờ bạn đã có thêm thông tin nền, hãy tải bộ dữ liệu tại đây. Bạn nên lấy hai tệp zip liệt kê bên cạnh "BelgiumTS for Classification (cropped images)", có tên "BelgiumTSC_Training" và "BelgiumTSC_Testing".
Mẹo: nếu bạn đã tải tệp hoặc sẽ làm sau khi hoàn tất hướng dẫn này, hãy xem cấu trúc thư mục của dữ liệu đã tải! Bạn sẽ thấy thư mục kiểm thử cũng như huấn luyện chứa 61 thư mục con, là 62 loại biển báo mà bạn sẽ dùng để phân loại trong hướng dẫn này. Thêm nữa, bạn sẽ thấy các tệp có phần mở rộng .ppm hay Portable Pixmap Format. Bạn đã tải ảnh các biển báo!
Bắt đầu import dữ liệu vào không gian làm việc. Hãy bắt đầu với các dòng mã bên dưới Hàm do người dùng định nghĩa (UDF) load_data():
- Đầu tiên, đặt
ROOT_PATH. Đây là đường dẫn nơi bạn tạo thư mục chứa dữ liệu huấn luyện và kiểm thử. - Tiếp theo, bạn có thể thêm các đường dẫn cụ thể vào
ROOT_PATHvới hàmjoin(). Bạn lưu hai đường dẫn cụ thể này vàotrain_data_directoryvàtest_data_directory. - Bạn thấy sau đó có thể gọi hàm
load_data()và truyền vàotrain_data_directory. - Bây giờ, bản thân hàm
load_data()bắt đầu bằng cách gom tất cả các thư mục con có trongtrain_data_directory; Thực hiện bằng list comprehension, một cách tự nhiên để tạo danh sách - về cơ bản nói rằng, nếu bạn thấy một thứ gì đó trongtrain_data_directory, bạn sẽ kiểm tra lại xem đó có phải là thư mục không, và nếu đúng, bạn sẽ thêm vào danh sách. Ghi nhớ rằng mỗi thư mục con đại diện cho một nhãn. - Tiếp theo, bạn cần lặp qua các thư mục con. Bạn khởi tạo hai danh sách
labelsvàimages. Rồi bạn gom đường dẫn thư mục con và tên tệp ảnh lưu trong đó. Sau đó, bạn thu thập dữ liệu vào hai danh sách bằngappend().
def load_data(data_directory):
directories = [d for d in os.listdir(data_directory)
if os.path.isdir(os.path.join(data_directory, d))]
labels = []
images = []
for d in directories:
label_directory = os.path.join(data_directory, d)
file_names = [os.path.join(label_directory, f)
for f in os.listdir(label_directory)
if f.endswith(".ppm")]
for f in file_names:
images.append(skimage.data.imread(f))
labels.append(int(d))
return images, labels
ROOT_PATH = "/your/root/path"
train_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Training")
test_data_directory = os.path.join(ROOT_PATH, "TrafficSigns/Testing")
images, labels = load_data(train_data_directory)
Lưu ý rằng trong khối mã trên, dữ liệu huấn luyện và kiểm thử nằm trong các thư mục tên "Training" và "Testing", đều là thư mục con của thư mục khác "TrafficSigns". Trên máy cục bộ, có thể trông như "/Users/Name/Downloads/TrafficSigns", rồi có hai thư mục con "Training" và "Testing".
Mẹo: ôn lại cách viết hàm trong Python với Hướng dẫn Hàm Python của DataCamp.
Thống kê Biển báo
Khi dữ liệu đã nạp, đến lúc kiểm tra! Bạn có thể bắt đầu với một phân tích khá đơn giản bằng các thuộc tính ndim và size của mảng images:
Lưu ý rằng biến images và labels là danh sách, nên có thể bạn cần dùng np.array() để chuyển thành mảng trong không gian làm việc của bạn. Ở đây đã làm sẵn cho bạn!
Lưu ý rằng images[0] mà bạn in ra thực chất là một ảnh đơn lẻ được biểu diễn bằng các mảng lồng nhau! Điều này lúc đầu có thể gây khó hiểu, nhưng bạn sẽ quen khi làm việc với ảnh trong các ứng dụng học máy hay học sâu.
Tiếp theo, bạn cũng có thể xem nhanh labels, nhưng lúc này có lẽ không có nhiều bất ngờ:
Những con số này đã cho bạn một vài hiểu biết về mức độ thành công của việc import và kích thước chính xác của dữ liệu. Thoạt nhìn, mọi thứ chạy như mong đợi, và bạn thấy kích thước mảng là đáng kể nếu tính đến việc bạn đang làm việc với các mảng lồng nhau.
Mẹo thử thêm các thuộc tính sau vào mảng để biết thêm về bố cục bộ nhớ, độ dài một phần tử mảng theo byte và tổng số byte tiêu thụ bởi các phần tử mảng với thuộc tính flags, itemsize và nbytes. Bạn có thể thử trong bảng điều khiển IPython ở khối DataCamp Light phía trên!
Tiếp theo, bạn cũng có thể xem phân phối các biển báo:
Tuyệt vời! Giờ hãy xem kỹ hơn biểu đồ histogram bạn vừa tạo!

Thoạt nhìn, bạn thấy có những nhãn xuất hiện dày đặc hơn: các nhãn 22, 32, 38 và 61 nổi bật. Ở thời điểm này, hãy ghi nhớ điều này, và bạn sẽ đào sâu hơn ở phần tiếp theo!
Trực quan hóa Biển báo
Những phân tích/kiểm tra nhỏ trước đó đã giúp bạn có phần nào hình dung về dữ liệu đang làm việc, nhưng khi dữ liệu chủ yếu là ảnh, bước bạn nên làm để khám phá là trực quan hóa.
Hãy xem một vài biển báo ngẫu nhiên:
- Trước hết, đảm bảo bạn import mô-đun
pyplotcủa góimatplotlibvới bí danh quen thuộcplt. - Tiếp đến, bạn sẽ tạo một danh sách gồm 4 số ngẫu nhiên. Chúng sẽ được dùng để chọn biển báo từ mảng
imagesmà bạn vừa kiểm tra ở phần trước. Ở đây, bạn chọn300,2250,3650và4000. - Sau đó, bạn sẽ nói rằng với mỗi phần tử trong độ dài danh sách đó, tức từ 0 đến 4, bạn sẽ tạo các subplot không có trục (để chúng không chiếm hết sự chú ý và bạn chỉ tập trung vào ảnh!). Trong các subplot này, bạn sẽ hiển thị một ảnh cụ thể từ mảng
imagestương ứng với số tại chỉ mụci. Ở vòng lặp đầu, bạn truyền300vàoimages[], vòng hai là2250, v.v. Cuối cùng, điều chỉnh các subplot để có đủ khoảng cách giữa chúng. - Việc cuối cùng là hiển thị biểu đồ bằng hàm
show()!
Đây là mã:
# Import the `pyplot` module of `matplotlib`
import matplotlib.pyplot as plt
# Determine the (random) indexes of the images that you want to see
traffic_signs = [300, 2250, 3650, 4000]
# Fill out the subplots with the random images that you defined
for i in range(len(traffic_signs)):
plt.subplot(1, 4, i+1)
plt.axis('off')
plt.imshow(images[traffic_signs[i]])
plt.subplots_adjust(wspace=0.5)
plt.show()
Như bạn đoán qua 62 nhãn có trong bộ dữ liệu này, các biển báo khác nhau.
Nhưng bạn còn nhận thấy gì nữa? Hãy nhìn kỹ các ảnh dưới đây:

Bốn ảnh này không cùng kích thước!
Bạn có thể thử nghịch các con số trong danh sách traffic_signs và theo dõi kỹ quan sát này hơn, nhưng dù sao đây là nhận xét quan trọng bạn cần tính đến khi bắt đầu thao tác dữ liệu để đưa vào mạng nơ-ron.
Hãy kiểm chứng giả thuyết về kích thước khác nhau bằng cách in ra shape, giá trị nhỏ nhất và lớn nhất của các ảnh cụ thể bạn đã đưa vào subplot.
Đoạn mã dưới đây rất giống đoạn bạn dùng để vẽ biểu đồ ở trên, nhưng khác ở chỗ ở đây bạn xen kẽ kích thước và ảnh thay vì chỉ vẽ các ảnh cạnh nhau:
# Import `matplotlib`
import matplotlib.pyplot as plt
# Determine the (random) indexes of the images
traffic_signs = [300, 2250, 3650, 4000]
# Fill out the subplots with the random images and add shape, min and max values
for i in range(len(traffic_signs)):
plt.subplot(1, 4, i+1)
plt.axis('off')
plt.imshow(images[traffic_signs[i]])
plt.subplots_adjust(wspace=0.5)
plt.show()
print("shape: {0}, min: {1}, max: {2}".format(images[traffic_signs[i]].shape,
images[traffic_signs[i]].min(),
images[traffic_signs[i]].max()))
Lưu ý cách bạn dùng phương thức format() trên chuỗi "shape: {0}, min: {1}, max: {2}" để điền các đối số {0}, {1} và {2} đã định nghĩa.

Giờ bạn đã xem từng ảnh rời, bạn cũng có thể quay lại biểu đồ histogram đã in ở những bước đầu khám phá dữ liệu; Bạn có thể dễ dàng làm điều này bằng cách vẽ tổng quan cả 62 lớp và một ảnh thuộc mỗi lớp:
# Import the `pyplot` module as `plt`
import matplotlib.pyplot as plt
# Get the unique labels
unique_labels = set(labels)
# Initialize the figure
plt.figure(figsize=(15, 15))
# Set a counter
i = 1
# For each unique label,
for label in unique_labels:
# You pick the first image for each label
image = images[labels.index(label)]
# Define 64 subplots
plt.subplot(8, 8, i)
# Don't include axes
plt.axis('off')
# Add a title to each subplot
plt.title("Label {0} ({1})".format(label, labels.count(label)))
# Add 1 to the counter
i += 1
# And you plot this first image
plt.imshow(image)
# Show the plot
plt.show()
Lưu ý dù bạn định nghĩa 64 subplot, không phải tất cả đều có ảnh (vì chỉ có 62 nhãn!). Cũng lưu ý rằng bạn tiếp tục không hiển thị trục để đảm bảo người đọc không bị phân tán khỏi chủ đề chính: các biển báo!

Như bạn đã đoán từ histogram ở trên, có nhiều biển báo hơn đáng kể với các nhãn 22, 32, 38 và 61. Giả thuyết này giờ được xác nhận trong biểu đồ: bạn thấy có 375 mẫu với nhãn 22, 316 mẫu với nhãn 32, 285 mẫu với nhãn 38 và cuối cùng 282 mẫu với nhãn 61.
Một câu hỏi thú vị là liệu có mối liên hệ nào giữa tất cả các mẫu này không - có thể tất cả đều là biển chỉ dẫn?
Nhìn kỹ hơn: bạn thấy nhãn 22 và 32 là biển cấm, còn nhãn 38 và 61 lần lượt là biển chỉ dẫn và biển ưu tiên. Điều này có nghĩa không có liên hệ trực tiếp giữa bốn nhãn này, ngoại trừ việc một nửa số biển có mặt nhiều trong bộ dữ liệu là loại biển cấm.

Trích xuất Đặc trưng
Giờ bạn đã khám phá kỹ dữ liệu, đến lúc “xắn tay áo”! Hãy tóm tắt nhanh những gì bạn phát hiện để đảm bảo không quên bước nào khi thao tác dữ liệu:
- Kích thước ảnh không đồng nhất;
- Có 62 nhãn hoặc giá trị mục tiêu (vì nhãn bắt đầu từ 0 và kết thúc ở 61);
- Phân phối các giá trị biển báo khá không đồng đều; Không có mối liên hệ rõ ràng giữa các biển xuất hiện dày đặc.
Giờ bạn đã nắm rõ cần cải thiện gì, bạn có thể bắt đầu thao tác dữ liệu sao cho sẵn sàng đưa vào mạng nơ-ron hay bất kỳ mô hình nào khác. Hãy bắt đầu với việc trích xuất một số đặc trưng - bạn sẽ co giãn ảnh, và chuyển các ảnh trong mảng images sang thang xám. Bạn làm chuyển đổi màu chủ yếu vì màu sắc kém quan trọng hơn trong các bài toán phân loại như bài bạn đang làm. Tuy nhiên, với bài toán phát hiện (detection), màu sắc đóng vai trò lớn! Khi đó, không cần thực hiện chuyển đổi này!
Co giãn Ảnh
Để xử lý sự khác nhau về kích thước ảnh, bạn sẽ co giãn ảnh; Bạn có thể làm dễ dàng với thư viện skimage hay Scikit-Image, bộ sưu tập thuật toán xử lý ảnh.
Trong trường hợp này, mô-đun transform sẽ hữu ích, vì nó cung cấp hàm resize(); Bạn sẽ thấy mình dùng list comprehension (lần nữa!) để co giãn mỗi ảnh về 28 x 28 pixel. Một lần nữa, bạn thấy cách tạo danh sách: với mỗi ảnh trong mảng images, bạn thực hiện phép biến đổi từ thư viện skimage. Cuối cùng, lưu kết quả vào biến images28:
# Import the `transform` module from `skimage`
from skimage import transform
# Rescale the images in the `images` array
images28 = [transform.resize(image, (28, 28)) for image in images]
Khá dễ phải không?
Lưu ý các ảnh giờ có bốn chiều: nếu bạn chuyển images28 thành mảng và nối thuộc tính shape vào, bạn sẽ thấy in ra kích thước (4575, 28, 28, 3). Các ảnh có 784 chiều (vì ảnh 28 x 28 pixel).
Bạn có thể kiểm tra kết quả co giãn bằng cách dùng lại mã trên để vẽ 4 ảnh ngẫu nhiên với biến traffic_signs. Chỉ đừng quên đổi tất cả tham chiếu từ images sang images28.
Xem kết quả tại đây:

Lưu ý vì bạn đã co giãn, các giá trị min và max cũng thay đổi; Chúng dường như đều nằm trong cùng khoảng, điều này rất tốt vì khi đó bạn không nhất thiết phải chuẩn hóa dữ liệu!
Chuyển ảnh sang thang xám
Như đã nói ở phần mở đầu mục này, màu trong ảnh ít quan trọng hơn khi bạn giải quyết bài toán phân loại. Đó là lý do bạn cũng sẽ chuyển ảnh sang thang xám.
Lưu ý, tuy vậy, bạn cũng có thể tự thử xem điều gì xảy ra với kết quả cuối của mô hình nếu bạn không thực hiện bước này.
Giống như với co giãn, bạn lại có thể trông cậy vào Scikit-Image; Lần này là mô-đun color với hàm rgb2gray() để đạt mục tiêu.
Việc này sẽ đơn giản và gọn nhẹ!
Tuy nhiên, đừng quên chuyển biến images28 về mảng, vì hàm rgb2gray() kỳ vọng một mảng làm đối số.
# Import `rgb2gray` from `skimage.color`
from skimage.color import rgb2gray
# Convert `images28` to an array
images28 = np.array(images28)
# Convert `images28` to grayscale
images28 = rgb2gray(images28)
Kiểm tra lại kết quả chuyển thang xám bằng cách vẽ một số ảnh; Ở đây, bạn có thể dùng lại và điều chỉnh nhẹ mã để hiển thị các ảnh đã điều chỉnh:
import matplotlib.pyplot as plt
traffic_signs = [300, 2250, 3650, 4000]
for i in range(len(traffic_signs)):
plt.subplot(1, 4, i+1)
plt.axis('off')
plt.imshow(images28[traffic_signs[i]], cmap="gray")
plt.subplots_adjust(wspace=0.5)
# Show the plot
plt.show()
Lưu ý rằng bạn thực sự cần chỉ định bản đồ màu hay cmap và đặt thành "gray" để vẽ ảnh bằng thang xám. Đó là vì imshow() mặc định dùng một bản đồ màu kiểu heatmap. Đọc thêm tại đây.

Mẹo: vì bạn đã dùng lại hàm này khá nhiều trong hướng dẫn, bạn có thể cân nhắc đóng gói thành một hàm riêng :)
Hai bước này rất cơ bản; Các thao tác khác bạn có thể thử trên dữ liệu gồm tăng cường dữ liệu (xoay, làm mờ, tịnh tiến, thay đổi độ sáng,…). Nếu muốn, bạn cũng có thể thiết lập một pipeline đầy đủ các thao tác biến đổi dữ liệu để đưa ảnh qua.
Học sâu với TensorFlow
Giờ bạn đã khám phá và thao tác dữ liệu, đến lúc xây dựng kiến trúc mạng nơ-ron với gói TensorFlow!
Mô hình hóa Mạng nơ-ron
Giống như khi làm với Keras, đã đến lúc xây dựng mạng nơ-ron theo từng lớp.
Nếu chưa làm, hãy import tensorflow với bí danh quen tf. Sau đó, bạn có thể khởi tạo Graph với Graph(). Bạn dùng hàm này để định nghĩa tính toán. Lưu ý với Graph, bạn chưa tính toán gì, vì nó không giữ giá trị nào. Nó chỉ định nghĩa các phép toán bạn muốn chạy sau này.
Trong trường hợp này, bạn thiết lập ngữ cảnh mặc định với as_default(), trả về một context manager khiến Graph cụ thể này thành đồ thị mặc định. Bạn dùng phương thức này nếu muốn tạo nhiều đồ thị trong cùng một tiến trình: với hàm này, bạn có một đồ thị mặc định toàn cục nơi mọi phép toán sẽ được thêm vào nếu bạn không tạo đồ thị mới.
Tiếp theo, bạn sẵn sàng thêm phép toán vào đồ thị. Như bạn có thể nhớ từ Keras, bạn xây dựng mô hình rồi khi biên dịch, bạn định nghĩa hàm mất mát, bộ tối ưu và chỉ số. Điều này giờ diễn ra trong một bước khi làm với TensorFlow:
- Trước tiên, bạn định nghĩa placeholder cho đầu vào và nhãn vì bạn chưa đưa dữ liệu “thật” vào. Ghi nhớ rằng placeholder là các giá trị chưa gán và sẽ được khởi tạo khi phiên chạy. Vậy khi bạn chạy phiên, các placeholder này sẽ nhận giá trị từ bộ dữ liệu bạn truyền vào hàm
run()! - Sau đó, bạn xây dựng mạng. Trước hết là làm phẳng đầu vào bằng hàm
flatten(), sẽ cho bạn một mảng có shape[None, 784]thay vì[None, 28, 28], là shape của ảnh thang xám. - Sau khi làm phẳng đầu vào, bạn xây một lớp kết nối đầy đủ sinh ra logits kích thước
[None, 62]. Logits là hàm hoạt động trên đầu ra chưa được chuẩn hóa của các lớp trước, dùng thang đo tương đối để hiểu các đơn vị là tuyến tính. - Với perceptron nhiều lớp đã dựng xong, bạn có thể định nghĩa hàm mất mát. Việc chọn hàm mất mát phụ thuộc vào bài toán: ở đây, bạn dùng
sparse_softmax_cross_entropy_with_logits()
- Hàm này tính entropy chéo softmax dạng thưa giữa logits và nhãn. Nói cách khác, nó đo sai số xác suất trong các bài toán phân loại rời rạc nơi các lớp loại trừ lẫn nhau. Điều này nghĩa là mỗi mẫu chỉ thuộc đúng một lớp. Ở đây, một biển báo chỉ có một nhãn. Ghi nhớ rằng, trong khi hồi quy dùng để dự đoán giá trị liên tục, phân loại dùng để dự đoán giá trị rời rạc hay lớp của điểm dữ liệu. Bạn bọc hàm này bằng
reduce_mean(), tính trung bình các phần tử trên các chiều của một tensor. - Bạn cũng muốn định nghĩa một bộ tối ưu hóa huấn luyện; Một số thuật toán tối ưu phổ biến là Stochastic Gradient Descent (SGD), ADAM và RMSprop. Tùy thuật toán bạn chọn, bạn sẽ cần tinh chỉnh một số tham số như tốc độ học hay momentum. Ở đây, bạn chọn bộ tối ưu ADAM với tốc độ học
0.001. - Cuối cùng, bạn khởi tạo các phép toán để thực thi trước khi bước vào huấn luyện.
# Import `tensorflow`
import tensorflow as tf
# Initialize placeholders
x = tf.placeholder(dtype = tf.float32, shape = [None, 28, 28])
y = tf.placeholder(dtype = tf.int32, shape = [None])
# Flatten the input data
images_flat = tf.contrib.layers.flatten(x)
# Fully connected layer
logits = tf.contrib.layers.fully_connected(images_flat, 62, tf.nn.relu)
# Define a loss function
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y,
logits = logits))
# Define an optimizer
train_op = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)
# Convert logits to label indexes
correct_pred = tf.argmax(logits, 1)
# Define an accuracy metric
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
Bạn đã tạo thành công mạng nơ-ron đầu tiên với TensorFlow!
Nếu muốn, bạn cũng có thể in ra giá trị của (hầu hết) các biến để điểm lại nhanh những gì vừa viết:
print("images_flat: ", images_flat)
print("logits: ", logits)
print("loss: ", loss)
print("predicted_labels: ", correct_pred)
Mẹo: nếu bạn thấy lỗi như “module 'pandas' has no attribute 'computation'”, hãy cân nhắc nâng cấp gói dask bằng cách chạy pip install --upgrade dask trong dòng lệnh. Xem thêm bài viết StackOverflow này.
Chạy Mạng nơ-ron
Giờ bạn đã xây dựng mô hình theo từng lớp, đến lúc chạy nó! Để làm vậy, trước tiên bạn cần khởi tạo một phiên với Session() mà bạn có thể truyền graph đã định nghĩa ở phần trước. Tiếp theo, bạn có thể chạy phiên với run(), truyền các phép toán đã khởi tạo dưới dạng biến init mà bạn cũng đã định nghĩa ở phần trước.
Sau đó, bạn có thể dùng phiên đã khởi tạo này để bắt đầu các epoch hay vòng lặp huấn luyện. Ở đây, bạn chọn 201 vì bạn muốn ghi nhận loss_value cuối; Trong vòng lặp, bạn chạy phiên với bộ tối ưu hóa huấn luyện và chỉ số mất mát (hoặc độ chính xác) đã định nghĩa ở phần trước. Bạn cũng truyền đối số feed_dict, qua đó bạn cấp dữ liệu cho mô hình. Sau mỗi 10 epoch, bạn sẽ nhận một bản ghi cho bạn biết thêm về mất mát hay chi phí của mô hình.
Như đã thấy ở phần cơ bản TensorFlow, không cần đóng phiên thủ công; điều này được làm giúp bạn. Tuy nhiên, nếu muốn thử một thiết lập khác, bạn có thể cần làm bằng sess.close() nếu bạn đặt tên phiên là sess, như trong khối mã dưới:
tf.set_random_seed(1234)
sess = tf.Session()
sess.run(tf.global_variables_initializer())
for i in range(201):
print('EPOCH', i)
_, accuracy_val = sess.run([train_op, accuracy], feed_dict={x: images28, y: labels})
if i % 10 == 0:
print("Loss: ", loss)
print('DONE WITH EPOCH')
Ghi nhớ rằng bạn cũng có thể chạy đoạn mã sau, nhưng nó sẽ đóng phiên ngay sau đó, giống như bạn đã thấy ở phần mở đầu hướng dẫn:
tf.set_random_seed(1234)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(201):
_, loss_value = sess.run([train_op, loss], feed_dict={x: images28, y: labels})
if i % 10 == 0:
print("Loss: ", loss)
Lưu ý rằng bạn dùng global_variables_initializer() vì hàm initialize_all_variables() đã lỗi thời.
Bạn vừa huấn luyện thành công mô hình! Không quá khó, đúng không?
Đánh giá Mạng nơ-ron
Bạn vẫn chưa xong; Bạn còn cần đánh giá mạng nơ-ron. Ở đây, bạn có thể thử nhìn lướt qua mức độ hoạt động của mô hình bằng cách chọn 10 ảnh ngẫu nhiên và so sánh nhãn dự đoán với nhãn thực.
Bạn có thể in chúng ra, nhưng tại sao không dùng matplotlib để vẽ chính các biển báo và so sánh trực quan?
# Import `matplotlib`
import matplotlib.pyplot as plt
import random
# Pick 10 random images
sample_indexes = random.sample(range(len(images28)), 10)
sample_images = [images28[i] for i in sample_indexes]
sample_labels = [labels[i] for i in sample_indexes]
# Run the "correct_pred" operation
predicted = sess.run([correct_pred], feed_dict={x: sample_images})[0]
# Print the real and predicted labels
print(sample_labels)
print(predicted)
# Display the predictions and the ground truth visually.
fig = plt.figure(figsize=(10, 10))
for i in range(len(sample_images)):
truth = sample_labels[i]
prediction = predicted[i]
plt.subplot(5, 2,1+i)
plt.axis('off')
color='green' if truth == prediction else 'red'
plt.text(40, 10, "Truth: {0}\nPrediction: {1}".format(truth, prediction),
fontsize=12, color=color)
plt.imshow(sample_images[i], cmap="gray")
plt.show()

Tuy nhiên, chỉ nhìn các ảnh ngẫu nhiên không cho bạn nhiều hiểu biết về hiệu năng thực sự của mô hình. Đó là lý do bạn sẽ nạp dữ liệu kiểm thử.
Lưu ý rằng bạn dùng lại hàm load_data(), hàm bạn đã định nghĩa ở đầu hướng dẫn.
# Import `skimage`
from skimage import transform
# Load the test data
test_images, test_labels = load_data(test_data_directory)
# Transform the images to 28 by 28 pixels
test_images28 = [transform.resize(image, (28, 28)) for image in test_images]
# Convert to grayscale
from skimage.color import rgb2gray
test_images28 = rgb2gray(np.array(test_images28))
# Run predictions against the full test set.
predicted = sess.run([correct_pred], feed_dict={x: test_images28})[0]
# Calculate correct matches
match_count = sum([int(y == y_) for y, y_ in zip(test_labels, predicted)])
# Calculate the accuracy
accuracy = match_count / len(test_labels)
# Print the accuracy
print("Accuracy: {:.3f}".format(accuracy))
Ghi nhớ đóng phiên bằng sess.close() nếu bạn không dùng with tf.Session() as sess: để bắt đầu phiên TensorFlow.
Tiếp theo Là Gì?
Nếu bạn muốn tiếp tục làm việc với bộ dữ liệu này và mô hình bạn đã ghép lại trong hướng dẫn, hãy thử các việc sau:
- Áp dụng LDA có điều chuẩn lên dữ liệu trước khi đưa vào mô hình. Đây là gợi ý từ một trong các bài báo gốc của nhóm nghiên cứu thu thập và phân tích bộ dữ liệu này.
- Bạn cũng có thể, như đã nói trong hướng dẫn, xem xét một số thao tác tăng cường dữ liệu khác có thể thực hiện trên ảnh biển báo. Ngoài ra, bạn có thể thử tinh chỉnh thêm mạng; Mạng bạn vừa tạo khá đơn giản.
- Dừng sớm: Theo dõi lỗi huấn luyện và kiểm thử trong khi bạn huấn luyện mạng nơ-ron. Dừng huấn luyện khi cả hai lỗi giảm rồi đột ngột tăng trở lại - đây là dấu hiệu mạng bắt đầu quá khớp dữ liệu huấn luyện.
- Thử nghiệm với các bộ tối ưu.
Nhớ xem cuốn Machine Learning With TensorFlow của Nishant Shukla.
Mẹo hãy xem thêm TensorFlow Playground và TensorBoard.
Nếu bạn muốn tiếp tục làm việc với ảnh, nhất định xem hướng dẫn scikit-learn của DataCamp, đề cập bộ dữ liệu MNIST với PCA, K-Means và Máy Vector Hỗ trợ (SVM). Hoặc xem các hướng dẫn khác như hướng dẫn này dùng bộ dữ liệu biển báo Bỉ.