Thứ ba, 12/05/2020 | 00:00 GMT+7

Cách xây dựng mạng neural để dịch ngôn ngữ ký hiệu sang tiếng Anh

Thị giác máy tính là một lĩnh vực con của khoa học máy tính nhằm mục đích rút ra hiểu biết cấp cao hơn từ hình ảnh và video. Điều này hỗ trợ các công nghệ như bộ lọc trò chuyện video vui nhộn, trình xác thực khuôn mặt trên thiết bị di động của bạn và ô tô tự lái.

Trong hướng dẫn này, bạn sẽ sử dụng thị giác máy tính để xây dựng trình dịch Ngôn ngữ ký hiệu USA cho webcam của bạn . Khi làm qua hướng dẫn này, bạn sẽ sử dụng OpenCV , một thư viện thị giác máy tính, PyTorch để xây dựng một mạng thần kinh sâu và onnx để xuất mạng thần kinh của bạn. Bạn cũng sẽ áp dụng các khái niệm sau khi xây dựng ứng dụng máy tính nhìn ra:

  • Bạn sẽ sử dụng phương pháp ba bước tương tự như được sử dụng trong Hướng dẫn cách áp dụng thị giác máy tính để xây dựng bộ lọc chó dựa trên cảm xúc : xử lý trước tập dữ liệu, huấn luyện mô hình và đánh giá mô hình.
  • Bạn cũng sẽ mở rộng từng bước sau: sử dụng tăng dữ liệu để giải quyết các bàn tay xoay hoặc không ở giữa, thay đổi lịch trình tốc độ học để cải thiện độ chính xác của mô hình và xuất mô hình để có tốc độ suy luận nhanh hơn.
  • Đồng thời, bạn cũng sẽ khám phá các khái niệm liên quan trong học máy .

Đến cuối hướng dẫn này, bạn sẽ có cả người phiên dịch Ngôn ngữ ký hiệu Mỹ và bí quyết học sâu cơ bản. Bạn cũng có thể truy cập mã nguồn hoàn chỉnh cho dự án này.

Yêu cầu

Để hoàn thành hướng dẫn này, bạn cần những thứ sau:

Bước 1 - Tạo dự án và cài đặt phụ thuộc

Hãy tạo không gian làm việc cho dự án này và cài đặt các phụ thuộc mà ta cần.

Trên các bản phân phối Linux, hãy bắt đầu bằng cách chuẩn bị trình quản lý gói hệ thống của bạn và cài đặt gói Python3 virtualenv. Sử dụng:

  • apt-get update
  • apt-get upgrade
  • apt-get install python3-venv

Ta sẽ gọi không gian làm việc của ta là SignLanguage :

  • mkdir ~/SignLanguage

Điều hướng đến folder SignLanguage :

  • cd ~/SignLanguage

Sau đó, tạo một môi trường ảo mới cho dự án:

  • python3 -m venv signlanguage

Kích hoạt môi trường của bạn:

  • source signlanguage/bin/activate

Sau đó, cài đặt PyTorch , một khuôn khổ học sâu cho Python mà ta sẽ sử dụng trong hướng dẫn này.

Trên macOS, cài đặt Pytorch bằng lệnh sau:

  • python -m pip install torch==1.2.0 torchvision==0.4.0

Trên Linux và Windows, sử dụng các lệnh sau cho bản dựng chỉ dành cho CPU:

  • pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
  • pip install torchvision

Bây giờ hãy cài đặt các file binary đóng gói sẵn cho OpenCV , numpyonnx , đây là các thư viện dành cho thị giác máy tính, đại số tuyến tính, xuất mô hình AI và thực thi mô hình AI, tương ứng. OpenCV cung cấp các tiện ích như quay hình ảnh và numpy cung cấp các tiện ích đại số tuyến tính như đảo ngược ma trận:

  • python -m pip install opencv-python==3.4.3.18 numpy==1.14.5 onnx==1.6.0 onnxruntime==1.0.0

Trên các bản phân phối Linux, bạn cần cài đặt libSM.so :

  • apt-get install libsm6 libxext6 libxrender-dev

Với các phần phụ thuộc được cài đặt, ta hãy xây dựng version đầu tiên của trình dịch ngôn ngữ ký hiệu của ta : trình phân loại ngôn ngữ ký hiệu.

Bước 2 - Chuẩn bị tập dữ liệu phân loại ngôn ngữ ký hiệu

Trong ba phần tiếp theo này, bạn sẽ xây dựng một bộ phân loại ngôn ngữ ký hiệu bằng cách sử dụng mạng nơ-ron. Mục tiêu của bạn là tạo ra một mô hình chấp nhận hình ảnh bàn tay làm đầu vào và kết quả một chữ cái.

Ba bước sau là bắt buộc để xây dựng mô hình phân loại học máy:

  1. Xử lý trước dữ liệu: Áp dụng mã hóa một lần cho các nhãn của bạn và bọc dữ liệu trong PyTorch Tensors. Đào tạo mô hình của bạn về dữ liệu tăng cường để chuẩn bị cho mô hình đầu vào "bất thường", chẳng hạn như bàn tay lệch tâm hoặc xoay.
  2. Chỉ định và đào tạo mô hình: Cài đặt mạng nơ-ron bằng PyTorch. Xác định các siêu tham số huấn luyện chẳng hạn như thời gian huấn luyện — và chạy giảm độ dốc ngẫu nhiên. Bạn cũng sẽ thay đổi một siêu tham số đào tạo cụ thể, đó là lịch tốc độ học tập. Những điều này sẽ tăng độ chính xác của mô hình.
  3. Chạy dự đoán bằng mô hình: Đánh giá mạng nơ-ron trên dữ liệu xác thực của bạn để hiểu độ chính xác của nó. Sau đó, xuất mô hình sang định dạng gọi là ONNX để có tốc độ suy luận nhanh hơn.

Trong phần này của hướng dẫn, bạn sẽ hoàn thành bước 1 của 3. Bạn sẽ download dữ liệu, tạo một đối tượng Dataset để lặp qua dữ liệu của bạn và cuối cùng là áp dụng tăng dữ liệu . Ở cuối bước này, bạn sẽ có một cách lập trình để truy cập hình ảnh và nhãn trong tập dữ liệu của bạn để cung cấp cho mô hình của bạn.

Trước tiên, tải tập dữ liệu xuống folder làm việc hiện tại của bạn:

Lưu ý : Trên macOS, wget không khả dụng theo mặc định. Để làm như vậy, hãy cài đặt Homebrew theo hướng dẫn DigitalOcean này . Sau đó, chạy brew install wget .

  • wget https://assets.digitalocean.com/articles/signlanguage_data/sign-language-mnist.tar.gz

Extract file zip chứa data/ folder :

  • tar -xzf sign-language-mnist.tar.gz

Tạo một file mới, có tên step_2_dataset.py :

  • nano step_2_dataset.py

Như trước đây, hãy nhập các tiện ích cần thiết và tạo lớp sẽ lưu giữ dữ liệu . Để xử lý dữ liệu ở đây, bạn sẽ tạo tập dữ liệu huấn luyện và kiểm tra. Bạn sẽ triển khai giao diện Dataset của PyTorch, cho phép bạn tải và sử dụng đường dẫn dữ liệu tích hợp sẵn của PyTorch cho tập dữ liệu phân loại ngôn ngữ ký hiệu của bạn:

step_2_dataset.py
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import numpy as np import torch  import csv   class SignLanguageMNIST(Dataset):     """Sign Language classification dataset.      Utility for loading Sign Language dataset into PyTorch. Dataset posted on     Kaggle in 2017, by an unnamed author with username `tecperson`:     https://www.kaggle.com/datamunge/sign-language-mnist      Each sample is 1 x 1 x 28 x 28, and each label is a scalar.     """     pass 

Xóa trình giữ chỗ pass trong lớp SignLanguageMNIST . Thay vào đó, hãy thêm một phương thức để tạo ánh xạ nhãn:

step_2_dataset.py
    @staticmethod     def get_label_mapping():         """         We map all labels to [0, 23]. This mapping from dataset labels [0, 23]         to letter indices [0, 25] is returned below.         """         mapping = list(range(25))         mapping.pop(9)         return mapping 

Các nhãn nằm trong repository ảng từ 0 đến 25. Tuy nhiên, các chữ cái J (9) và Z (25) bị loại trừ. Điều này nghĩa là chỉ có 24 giá trị nhãn hợp lệ. Để tập hợp tất cả các giá trị nhãn bắt đầu từ 0 là liền nhau, ta ánh xạ tất cả các nhãn thành [0, 23]. get_label_mapping xạ này từ nhãn tập dữ liệu [0, 23] đến chỉ số chữ cái [0, 25] được cung cấp bởi phương pháp get_label_mapping này.

Tiếp theo, thêm một phương pháp để extract nhãn và mẫu từ file CSV. Phần sau giả định mỗi dòng bắt đầu bằng label và sau đó là các giá trị 784 pixel theo sau. Các giá trị 784 pixel này đại diện cho hình ảnh 28x28 :

step_2_dataset.py
    @staticmethod     def read_label_samples_from_csv(path: str):         """         Assumes first column in CSV is the label and subsequent 28^2 values         are image pixel values 0-255.         """         mapping = SignLanguageMNIST.get_label_mapping()         labels, samples = [], []         with open(path) as f:             _ = next(f)  # skip header             for line in csv.reader(f):                 label = int(line[0])                 labels.append(mapping.index(label))                 samples.append(list(map(int, line[1:])))         return labels, samples 

Để biết giải thích về cách các giá trị 784 này đại diện cho hình ảnh, hãy xem Xây dựng bộ lọc chó dựa trên cảm xúc, Bước 4 .

Lưu ý mỗi dòng trong csv.reader lặp lại là một danh sách các chuỗi; các lời gọi intmap(int, ...) truyền tất cả các chuỗi thành số nguyên. Ngay bên dưới phương thức static của ta , hãy thêm một hàm sẽ khởi tạo trình giữ dữ liệu của ta :

step_2_dataset.py
    def __init__(self,             path: str="data/sign_mnist_train.csv",             mean: List[float]=[0.485],             std: List[float]=[0.229]):         """         Args:             path: Path to `.csv` file containing `label`, `pixel0`, `pixel1`...         """         labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)         self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))         self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))          self._mean = mean         self._std = std 

Chức năng này bắt đầu bằng cách tải các mẫu và nhãn. Sau đó, nó bao bọc dữ liệu trong các mảng NumPy. Thông tin trung bình và độ lệch chuẩn sẽ được giải thích ngay sau đây, trong phần __getitem__ sau đây.

Ngay sau hàm __init__ , thêm một hàm __len__ . Dataset yêu cầu phương pháp này để xác định thời điểm ngừng lặp lại dữ liệu:

step_2_dataset.py
...     def __len__(self):         return len(self._labels) 

Cuối cùng, thêm một phương thức __getitem__ , phương thức này sẽ trả về một từ điển chứa mẫu và nhãn:

step_2_dataset.py
    def __getitem__(self, idx):         transform = transforms.Compose([             transforms.ToPILImage(),             transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),             transforms.ToTensor(),             transforms.Normalize(mean=self._mean, std=self._std)])          return {             'image': transform(self._samples[idx]).float(),             'label': torch.from_numpy(self._labels[idx]).float()         } 

Bạn sử dụng một kỹ thuật được gọi là tăng cường dữ liệu , trong đó các mẫu bị xáo trộn trong quá trình đào tạo, để tăng độ chắc chắn của mô hình đối với những nhiễu loạn này. Đặc biệt, phóng to hình ảnh một cách ngẫu nhiên theo số lượng khác nhau và trên các vị trí khác nhau, thông qua RandomResizedCrop . Lưu ý việc phóng to không được ảnh hưởng đến lớp ngôn ngữ ký hiệu cuối cùng; do đó, nhãn không bị biến đổi. Bạn cũng chuẩn hóa các đầu vào để các giá trị hình ảnh được thay đổi tỷ lệ thành phạm vi [0, 1] theo mong đợi, thay vì [0, 255]; để thực hiện điều này, hãy sử dụng tập dữ liệu _mean_std khi chuẩn hóa.

Lớp SignLanguageMNIST đã hoàn thành của bạn sẽ trông giống như sau:

step_2_dataset.py
from torch.utils.data import Dataset from torch.autograd import Variable import torchvision.transforms as transforms import torch.nn as nn import numpy as np import torch  from typing import List  import csv   class SignLanguageMNIST(Dataset):     """Sign Language classification dataset.      Utility for loading Sign Language dataset into PyTorch. Dataset posted on     Kaggle in 2017, by an unnamed author with username `tecperson`:     https://www.kaggle.com/datamunge/sign-language-mnist      Each sample is 1 x 1 x 28 x 28, and each label is a scalar.     """      @staticmethod     def get_label_mapping():         """         We map all labels to [0, 23]. This mapping from dataset labels [0, 23]         to letter indices [0, 25] is returned below.         """         mapping = list(range(25))         mapping.pop(9)         return mapping      @staticmethod     def read_label_samples_from_csv(path: str):         """         Assumes first column in CSV is the label and subsequent 28^2 values         are image pixel values 0-255.         """         mapping = SignLanguageMNIST.get_label_mapping()         labels, samples = [], []         with open(path) as f:             _ = next(f)  # skip header             for line in csv.reader(f):                 label = int(line[0])                 labels.append(mapping.index(label))                 samples.append(list(map(int, line[1:])))         return labels, samples      def __init__(self,             path: str="data/sign_mnist_train.csv",             mean: List[float]=[0.485],             std: List[float]=[0.229]):         """         Args:             path: Path to `.csv` file containing `label`, `pixel0`, `pixel1`...         """         labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)         self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))         self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))          self._mean = mean         self._std = std      def __len__(self):         return len(self._labels)      def __getitem__(self, idx):         transform = transforms.Compose([             transforms.ToPILImage(),             transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),             transforms.ToTensor(),             transforms.Normalize(mean=self._mean, std=self._std)])          return {             'image': transform(self._samples[idx]).float(),             'label': torch.from_numpy(self._labels[idx]).float()         } 

Như trước đây, bây giờ bạn sẽ xác minh các chức năng tiện ích tập dữ liệu của ta bằng cách tải tập dữ liệu SignLanguageMNIST . Thêm mã sau vào cuối file của bạn sau lớp SignLanguageMNIST :

step_2_dataset.py
def get_train_test_loaders(batch_size=32):     trainset = SignLanguageMNIST('data/sign_mnist_train.csv')     trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)      testset = SignLanguageMNIST('data/sign_mnist_test.csv')     testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)     return trainloader, testloader 

Mã này khởi tạo tập dữ liệu bằng lớp SignLanguageMNIST . Sau đó, đối với tập hợp xác thực và đào tạo, nó bao bọc tập dữ liệu trong DataLoader . Điều này sẽ chuyển tập dữ liệu thành một tập dữ liệu có thể lặp lại để sử dụng sau này.

Đến đây bạn sẽ xác minh các tiện ích tập dữ liệu đang hoạt động. Tạo bộ tải tập dữ liệu mẫu bằng DataLoader và in phần tử đầu tiên của bộ tải đó. Thêm phần sau vào cuối file của bạn:

step_2_dataset.py
if __name__ == '__main__':     loader, _ = get_train_test_loaders(2)     print(next(iter(loader))) 

Bạn có thể kiểm tra xem file của bạn có trùng với file step_2_dataset trong ( kho lưu trữ ) này không. Thoát khỏi editor và chạy tập lệnh sau:

  • python step_2_dataset.py

Điều này tạo ra cặp tenxơ sau. Đường ống dữ liệu của ta xuất ra hai mẫu và hai nhãn. Điều này cho thấy rằng đường dẫn dữ liệu của ta đã hoàn tất và sẵn sàng hoạt động:

Output
{'image': tensor([[[[ 0.4337, 0.5022, 0.5707, ..., 0.9988, 0.9646, 0.9646], [ 0.4851, 0.5536, 0.6049, ..., 1.0502, 1.0159, 0.9988], [ 0.5364, 0.6049, 0.6392, ..., 1.0844, 1.0844, 1.0673], ..., [-0.5253, -0.4739, -0.4054, ..., 0.9474, 1.2557, 1.2385], [-0.3369, -0.3369, -0.3369, ..., 0.0569, 1.3584, 1.3242], [-0.3712, -0.3369, -0.3198, ..., 0.5364, 0.5364, 1.4783]]], [[[ 0.2111, 0.2796, 0.3481, ..., 0.2453, -0.1314, -0.2342], [ 0.2624, 0.3309, 0.3652, ..., -0.3883, -0.0629, -0.4568], [ 0.3309, 0.3823, 0.4337, ..., -0.4054, -0.0458, -1.0048], ..., [ 1.3242, 1.3584, 1.3927, ..., -0.4054, -0.4568, 0.0227], [ 1.3242, 1.3927, 1.4612, ..., -0.1657, -0.6281, -0.0287], [ 1.3242, 1.3927, 1.4440, ..., -0.4397, -0.6452, -0.2856]]]]), 'label': tensor([[24.], [11.]])}

Đến đây bạn đã xác minh đường dẫn dữ liệu hoạt động. Điều này kết thúc bước đầu tiên — xử lý trước dữ liệu — hiện đã bao gồm tăng cường dữ liệu để tăng độ mạnh của mô hình. Tiếp theo, bạn sẽ xác định mạng thần kinh và trình tối ưu hóa.

Bước 3 - Xây dựng và đào tạo Bộ phân loại ngôn ngữ ký hiệu bằng cách sử dụng học sâu

Với một đường ống dẫn dữ liệu đang hoạt động, bây giờ bạn sẽ xác định một mô hình và đào tạo nó trên dữ liệu. Đặc biệt, bạn sẽ xây dựng một mạng nơ-ron với sáu lớp, xác định tổn thất, trình tối ưu hóa và cuối cùng, tối ưu hóa chức năng tổn thất cho các dự đoán mạng nơ-ron của bạn. Vào cuối bước này, bạn sẽ có một bộ phân loại ngôn ngữ ký hiệu hoạt động.

Tạo một file mới có tên là step_3_train.py :

  • nano step_3_train.py

Nhập các tiện ích cần thiết:

step_3_train.py
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch  from step_2_dataset import get_train_test_loaders 

Xác định mạng nơ-ron PyTorch bao gồm ba lớp chập, tiếp theo là ba lớp được kết nối đầy đủ. Thêm cái này vào cuối tập lệnh hiện có của bạn:

step_3_train.py
class Net(nn.Module):     def __init__(self):         super(Net, self).__init__()         self.conv1 = nn.Conv2d(1, 6, 3)         self.pool = nn.MaxPool2d(2, 2)         self.conv2 = nn.Conv2d(6, 6, 3)         self.conv3 = nn.Conv2d(6, 16, 3)         self.fc1 = nn.Linear(16 * 5 * 5, 120)         self.fc2 = nn.Linear(120, 48)         self.fc3 = nn.Linear(48, 24)      def forward(self, x):         x = F.relu(self.conv1(x))         x = self.pool(F.relu(self.conv2(x)))         x = self.pool(F.relu(self.conv3(x)))         x = x.view(-1, 16 * 5 * 5)         x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)         return x 

Bây giờ hãy khởi tạo mạng nơ-ron, xác định một hàm mất mát và xác định các siêu tham số tối ưu hóa bằng cách thêm đoạn mã sau vào cuối tập lệnh:

step_3_train.py
def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) 

Cuối cùng, bạn sẽ luyện tập trong hai kỷ nguyên :

step_3_train.py
def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)      trainloader, _ = get_train_test_loaders()     for epoch in range(2):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)     torch.save(net.state_dict(), "checkpoint.pth") 

Bạn định nghĩa một kỷ nguyên là sự lặp lại của quá trình đào tạo trong đó mọi mẫu đào tạo đã được sử dụng chính xác một lần. Khi kết thúc chức năng chính, các thông số của mô hình sẽ được lưu vào một file có tên là "checkpoint.pth" .

Thêm mã sau vào cuối tập lệnh của bạn để extract imagelabel từ bộ tải tập dữ liệu và sau đó bọc mỗi thứ trong một Variable PyTorch:

step_3_train.py
def train(net, criterion, optimizer, trainloader, epoch):     running_loss = 0.0     for i, data in enumerate(trainloader, 0):         inputs = Variable(data['image'].float())         labels = Variable(data['label'].long())         optimizer.zero_grad()          # forward + backward + optimize         outputs = net(inputs)         loss = criterion(outputs, labels[:, 0])         loss.backward()         optimizer.step()          # print statistics         running_loss += loss.item()         if i % 100 == 0:             print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1))) 

Mã này cũng sẽ chạy chuyển tiếp phía trước và sau đó sao chép ngược thông qua mạng mất mát và thần kinh.

Ở cuối file của bạn, thêm phần sau để gọi hàm main :

step_3_train.py
if __name__ == '__main__':     main() 

Kiểm tra kỹ xem file của bạn có trùng với file sau đây không:

step_3_train.py
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch  from step_2_dataset import get_train_test_loaders   class Net(nn.Module):     def __init__(self):         super(Net, self).__init__()         self.conv1 = nn.Conv2d(1, 6, 3)         self.pool = nn.MaxPool2d(2, 2)         self.conv2 = nn.Conv2d(6, 6, 3)         self.conv3 = nn.Conv2d(6, 16, 3)         self.fc1 = nn.Linear(16 * 5 * 5, 120)         self.fc2 = nn.Linear(120, 48)         self.fc3 = nn.Linear(48, 25)      def forward(self, x):         x = F.relu(self.conv1(x))         x = self.pool(F.relu(self.conv2(x)))         x = self.pool(F.relu(self.conv3(x)))         x = x.view(-1, 16 * 5 * 5)         x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)         return x   def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)      trainloader, _ = get_train_test_loaders()     for epoch in range(2):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)     torch.save(net.state_dict(), "checkpoint.pth")   def train(net, criterion, optimizer, trainloader, epoch):     running_loss = 0.0     for i, data in enumerate(trainloader, 0):         inputs = Variable(data['image'].float())         labels = Variable(data['label'].long())         optimizer.zero_grad()          # forward + backward + optimize         outputs = net(inputs)         loss = criterion(outputs, labels[:, 0])         loss.backward()         optimizer.step()          # print statistics         running_loss += loss.item()         if i % 100 == 0:             print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1)))   if __name__ == '__main__':     main() 

Lưu và thoát. Sau đó, chạy chương trình đào tạo bằng chứng về khái niệm của ta bằng lệnh:

  • python step_3_train.py

Bạn sẽ thấy kết quả tương tự như sau khi mạng nơron đào tạo:

Output
[0, 0] loss: 3.208171 [0, 100] loss: 3.211070 [0, 200] loss: 3.192235 [0, 300] loss: 2.943867 [0, 400] loss: 2.569440 [0, 500] loss: 2.243283 [0, 600] loss: 1.986425 [0, 700] loss: 1.768090 [0, 800] loss: 1.587308 [1, 0] loss: 0.254097 [1, 100] loss: 0.208116 [1, 200] loss: 0.196270 [1, 300] loss: 0.183676 [1, 400] loss: 0.169824 [1, 500] loss: 0.157704 [1, 600] loss: 0.151408 [1, 700] loss: 0.136470 [1, 800] loss: 0.123326

Để có được tổn thất thấp hơn, bạn có thể tăng số kỷ nguyên lên 5, 10 hoặc thậm chí là 20. Tuy nhiên, sau một khoảng thời gian huấn luyện nhất định, tổn thất mạng sẽ không còn giảm khi thời gian huấn luyện tăng lên. Để tránh vấn đề này, khi thời gian đào tạo tăng lên, bạn sẽ đưa ra một lịch trình tốc độ học tập, làm giảm tốc độ học tập theo thời gian. Để hiểu tại sao điều này hoạt động, hãy xem hình dung của Distill tại “Tại sao Momentum thực sự hoạt động” .

Sửa đổi chức năng main của bạn với hai dòng sau, xác định một bộ lập scheduler và gọi lệnh scheduler.step . Hơn nữa, hãy thay đổi số kỷ nguyên thành 12 :

step_3_train.py
def main():     net = Net().float()     criterion = nn.CrossEntropyLoss()     optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)     scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)      trainloader, _ = get_train_test_loaders()     for epoch in range(12):  # loop over the dataset multiple times         train(net, criterion, optimizer, trainloader, epoch)         scheduler.step()     torch.save(net.state_dict(), "checkpoint.pth") 

Kiểm tra xem file của bạn có trùng với file bước 3 trong kho lưu trữ này không. Quá trình đào tạo sẽ diễn ra trong repository ảng 5 phút. Đầu ra của bạn sẽ giống như sau:

Output
[0, 0] loss: 3.208171 [0, 100] loss: 3.211070 [0, 200] loss: 3.192235 [0, 300] loss: 2.943867 [0, 400] loss: 2.569440 [0, 500] loss: 2.243283 [0, 600] loss: 1.986425 [0, 700] loss: 1.768090 [0, 800] loss: 1.587308 ... [11, 0] loss: 0.000302 [11, 100] loss: 0.007548 [11, 200] loss: 0.009005 [11, 300] loss: 0.008193 [11, 400] loss: 0.007694 [11, 500] loss: 0.008509 [11, 600] loss: 0.008039 [11, 700] loss: 0.007524 [11, 800] loss: 0.007608

Khoản lỗ cuối cùng thu được là 0.007608 , nhỏ hơn 3 bậc so với mức lỗ ban đầu 3.20 . Điều này kết thúc bước thứ hai trong quy trình làm việc của ta , nơi ta cài đặt và đào tạo mạng nơ-ron. Như đã nói, giá trị tổn thất nhỏ như thế này, nó có rất ít ý nghĩa. Để xem xét hiệu suất của mô hình, ta sẽ tính toán độ chính xác của nó — phần trăm hình ảnh mà mô hình đã phân loại chính xác.

Bước 4 - Đánh giá công cụ phân loại ngôn ngữ ký hiệu

Bây giờ, bạn sẽ đánh giá trình phân loại ngôn ngữ ký hiệu của bạn bằng cách tính toán độ chính xác của nó trên tập hợp xác thực , một tập hợp các hình ảnh mà mô hình không thấy trong quá trình đào tạo. Điều này sẽ mang lại cảm giác tốt hơn về hiệu suất của mô hình so với giá trị tổn thất cuối cùng đã làm. Hơn nữa, bạn sẽ thêm các tiện ích để lưu mô hình được đào tạo của ta khi kết thúc đào tạo và tải mô hình được đào tạo trước của ta khi thực hiện suy luận.

Tạo một file mới, được gọi là step_4_evaluate.py .

  • nano step_4_evaluate.py

Nhập các tiện ích cần thiết:

step_4_evaluate.py
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np  import onnx import onnxruntime as ort  from step_2_dataset import get_train_test_loaders from step_3_train import Net 

Tiếp theo, xác định một tiện ích để đánh giá hiệu suất của mạng nơ-ron. Hàm sau đây so sánh chữ cái được dự đoán của mạng nơ-ron với chữ cái thật, cho một hình ảnh:

step_4_evaluate.py
def evaluate(outputs: Variable, labels: Variable) -> float:     """Evaluate neural network outputs against non-one-hotted labels."""     Y = labels.numpy()     Yhat = np.argmax(outputs, axis=1)     return float(np.sum(Yhat == Y)) 

outputs là một danh sách các xác suất của lớp cho mỗi mẫu. Ví dụ, outputs cho một mẫu đơn có thể là [0.1, 0.3, 0.4, 0.2] . labels là danh sách các lớp nhãn. Ví dụ, lớp nhãn có thể là 3 .

Y = ... chuyển đổi các nhãn thành một mảng NumPy. Tiếp theo, Yhat = np.argmax(...) chuyển đổi xác suất của lớp outputs thành các lớp được dự đoán. Ví dụ: danh sách xác suất của lớp [0.1, 0.3, 0.4, 0.2] sẽ mang lại lớp 2 dự đoán, vì giá trị chỉ số 2 là 0,4 là giá trị lớn nhất.

Vì cả YYhat đều là các lớp nên bạn có thể so sánh chúng. Yhat == Y kiểm tra xem lớp được dự đoán có trùng với lớp nhãn hay không và np.sum(...) là một thủ thuật tính toán số lượng giá trị np.sum(...) -y. Nói cách khác, np.sum sẽ xuất ra số lượng mẫu đã được phân loại chính xác.

Thêm hàm thứ hai batch_evaluate , áp dụng hàm evaluate đầu tiên cho tất cả các hình ảnh:

step_4_evaluate.py
def batch_evaluate(         net: Net,         dataloader: torch.utils.data.DataLoader) -> float:     """Evaluate neural network in batches, if dataset is too large."""     score = n = 0.0     for batch in dataloader:         n += len(batch['image'])         outputs = net(batch['image'])         if isinstance(outputs, torch.Tensor):             outputs = outputs.detach().numpy()         score += evaluate(outputs, batch['label'][:, 0])     return score / n 

batch là một group hình ảnh được lưu trữ dưới dạng một tensor duy nhất. Đầu tiên, bạn tăng tổng số hình ảnh bạn đang đánh giá ( n ) với số hình ảnh trong lô này. Tiếp theo, bạn chạy suy luận trên mạng nơ-ron với lô hình ảnh này, outputs = net(...) . Kiểu kiểm tra if isinstance(...) chuyển đổi kết quả kết quả trong một mảng NumPy nếu cần. Cuối cùng, bạn sử dụng evaluate để tính số lượng mẫu được phân loại chính xác. Khi kết thúc hàm, bạn tính toán phần trăm mẫu mà bạn đã phân loại chính xác, score / n .

Cuối cùng, thêm tập lệnh sau để tận dụng các tiện ích trước đó:

step_4_evaluate.py
def validate():     trainloader, testloader = get_train_test_loaders()     net = Net().float()      pretrained_model = torch.load("checkpoint.pth")     net.load_state_dict(pretrained_model)      print('=' * 10, 'PyTorch', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)   if __name__ == '__main__':     validate() 

Điều này tải một mạng nơ-ron được đào tạo trước và đánh giá hiệu suất của nó trên tập dữ liệu ngôn ngữ ký hiệu được cung cấp. Cụ thể, tập lệnh ở đây đưa ra độ chính xác trên các hình ảnh bạn đã sử dụng để đào tạo và một tập hợp hình ảnh riêng biệt mà bạn đặt sang một bên cho mục đích thử nghiệm, được gọi là bộ xác thực .

Tiếp theo, bạn sẽ xuất mô hình PyTorch sang file binary ONNX. Sau đó, file binary này được dùng trong production để chạy suy luận với mô hình của bạn. Quan trọng nhất, mã chạy mã binary này không cần bản sao của định nghĩa mạng root . Ở cuối hàm validate , hãy thêm phần sau:

step_4_evaluate.py
    trainloader, testloader = get_train_test_loaders(1)      # export to onnx     fname = "signlanguage.onnx"     dummy = torch.randn(1, 1, 28, 28)     torch.onnx.export(net, dummy, fname, input_names=['input'])      # check exported model     model = onnx.load(fname)     onnx.checker.check_model(model)  # check model is well-formed      # create runnable session with exported model     ort_session = ort.InferenceSession(fname)     net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]      print('=' * 10, 'ONNX', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc) 

Thao tác này xuất mô hình ONNX, kiểm tra mô hình đã xuất và sau đó chạy suy luận với mô hình đã xuất. Kiểm tra kỹ xem file của bạn có trùng với file bước 4 trong kho lưu trữ này không:

step_4_evaluate.py
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np  import onnx import onnxruntime as ort  from step_2_dataset import get_train_test_loaders from step_3_train import Net   def evaluate(outputs: Variable, labels: Variable) -> float:     """Evaluate neural network outputs against non-one-hotted labels."""     Y = labels.numpy()     Yhat = np.argmax(outputs, axis=1)     return float(np.sum(Yhat == Y))   def batch_evaluate(         net: Net,         dataloader: torch.utils.data.DataLoader) -> float:     """Evaluate neural network in batches, if dataset is too large."""     score = n = 0.0     for batch in dataloader:         n += len(batch['image'])         outputs = net(batch['image'])         if isinstance(outputs, torch.Tensor):             outputs = outputs.detach().numpy()         score += evaluate(outputs, batch['label'][:, 0])     return score / n   def validate():     trainloader, testloader = get_train_test_loaders()     net = Net().float().eval()      pretrained_model = torch.load("checkpoint.pth")     net.load_state_dict(pretrained_model)      print('=' * 10, 'PyTorch', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)      trainloader, testloader = get_train_test_loaders(1)      # export to onnx     fname = "signlanguage.onnx"     dummy = torch.randn(1, 1, 28, 28)     torch.onnx.export(net, dummy, fname, input_names=['input'])      # check exported model     model = onnx.load(fname)     onnx.checker.check_model(model)  # check model is well-formed      # create runnable session with exported model     ort_session = ort.InferenceSession(fname)     net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]      print('=' * 10, 'ONNX', '=' * 10)     train_acc = batch_evaluate(net, trainloader) * 100.     print('Training accuracy: %.1f' % train_acc)     test_acc = batch_evaluate(net, testloader) * 100.     print('Validation accuracy: %.1f' % test_acc)   if __name__ == '__main__':     validate() 

Để sử dụng và đánh giá điểm kiểm tra từ bước cuối cùng, hãy chạy như sau:

  • python step_4_evaluate.py

Điều này sẽ mang lại kết quả tương tự như sau, khẳng định rằng mô hình đã xuất của bạn không chỉ hoạt động mà còn đồng ý với mô hình PyTorch ban đầu của bạn:

Output
========== PyTorch ========== Training accuracy: 99.9 Validation accuracy: 97.4 ========== ONNX ========== Training accuracy: 99.9 Validation accuracy: 97.4

Mạng nơ-ron của bạn đạt độ chính xác của chuyến tàu là 99,9% và độ chính xác xác thực là 97,4%. Khoảng cách giữa độ chính xác của quá trình đào tạo và xác nhận này cho thấy mô hình của bạn đang quá mức . Điều này nghĩa là thay vì học các mẫu tổng quát, mô hình của bạn đã ghi nhớ dữ liệu đào tạo. Để hiểu ý nghĩa và nguyên nhân của việc trang bị quá nhiều, hãy xem Tìm hiểu về sự cân bằng giữa phương sai lệch .

Đến đây, ta đã hoàn thành bộ phân loại ngôn ngữ ký hiệu. Về bản chất, mô hình của ta có thể phân biệt chính xác giữa các biển báo một cách chính xác hầu như mọi lúc. Đây là một mô hình hợp lý tốt, vì vậy ta chuyển sang giai đoạn cuối cùng của ứng dụng của ta . Ta sẽ sử dụng bộ phân loại ngôn ngữ ký hiệu này trong ứng dụng webcam thời gian thực.

Bước 5 - Liên kết nguồn cấp máy ảnh

Mục tiêu tiếp theo của bạn là liên kết camera của máy tính với trình phân loại ngôn ngữ ký hiệu của bạn. Bạn sẽ thu thập thông tin đầu vào của camera, phân loại ngôn ngữ ký hiệu được hiển thị và sau đó báo cáo lại ký hiệu đã phân loại cho user .

Bây giờ, hãy tạo một tập lệnh Python cho bộ phát hiện khuôn mặt. Tạo file step_6_camera.py bằng nano hoặc editor yêu thích của bạn:

  • nano step_5_camera.py

Thêm mã sau vào file :

step_5_camera.py
"""Test for sign language classification""" import cv2 import numpy as np import onnxruntime as ort  def main():     pass  if __name__ == '__main__':     main() 

Mã này nhập OpenCV, chứa các tiện ích hình ảnh của bạn và thời gian chạy ONNX, là tất cả những gì bạn cần để chạy suy luận với mô hình của bạn . Phần còn lại của mã là bản soạn sẵn chương trình Python điển hình.

Bây giờ, hãy thay thế pass trong hàm main bằng đoạn mã sau, mã này khởi tạo bộ phân loại ngôn ngữ ký hiệu bằng cách sử dụng các tham số bạn đã đào tạo trước đó. Ngoài ra, thêm ánh xạ từ các chỉ số đến các chữ cái và thống kê hình ảnh:

step_5_camera.py
def main():     # constants     index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')     mean = 0.485 * 255.     std = 0.229 * 255.      # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx") 

Bạn sẽ sử dụng các phần tử của tập lệnh thử nghiệm này từ tài liệu OpenCV chính thức. Cụ thể, bạn sẽ cập nhật nội dung của hàm main . Bắt đầu bằng cách khởi tạo đối tượng VideoCapture được cài đặt để chụp nguồn cấp dữ liệu trực tiếp từ máy ảnh của máy tính của bạn. Đặt cái này ở cuối hàm main :

step_5_camera.py
def main():     ...     # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx")      cap = cv2.VideoCapture(0) 

Sau đó, thêm một vòng lặp while , vòng lặp này sẽ đọc từ máy ảnh ở mọi bước:

step_5_camera.py
def main():     ...     cap = cv2.VideoCapture(0)     while True:         # Capture frame-by-frame         ret, frame = cap.read() 

Viết một hàm tiện ích để lấy nét trung tâm cho khung máy ảnh. Đặt hàm này trước hàm main :

step_5_camera.py
def center_crop(frame):     h, w, _ = frame.shape     start = abs(h - w) // 2     if h > w:         frame = frame[start: start + w]     else:         frame = frame[:, start: start + h]     return frame 

Tiếp theo, lấy phần giữa của khung máy ảnh, chuyển đổi sang thang độ xám, chuẩn hóa và thay đổi kích thước thành 28x28 . Đặt cái này bên while vòng lặp while trong hàm main :

step_5_camera.py
def main():     ...     while True:         # Capture frame-by-frame         ret, frame = cap.read()          # preprocess data         frame = center_crop(frame)         frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)         x = cv2.resize(frame, (28, 28))         x = (frame - mean) / std 

Tuy nhiên trong while vòng lặp, chạy suy luận với thời gian chạy ONNX. Chuyển đổi kết quả kết quả thành index lớp, sau đó thành chữ cái:

step_5_camera.py
        ...         x = (frame - mean) / std          x = x.reshape(1, 1, 28, 28).astype(np.float32)         y = ort_session.run(None, {'input': x})[0]          index = np.argmax(y, axis=1)         letter = index_to_letter[int(index)] 

Hiển thị chữ cái được dự đoán bên trong khung và hiển thị lại khung cho user :

step_5_camera.py
        ...         letter = index_to_letter[int(index)]          cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)         cv2.imshow("Sign Language Translator", frame) 

Vào cuối những while vòng lặp, thêm mã này để kiểm tra xem user chạm vào q nhân vật, và nếu như vậy, thoát khỏi ứng dụng. Dòng này tạm dừng chương trình trong 1 mili giây. Thêm những điều sau:

step_5_camera.py
        ...         cv2.imshow("Sign Language Translator", frame)          if cv2.waitKey(1) & 0xFF == ord('q'):             break 

Cuối cùng, giải phóng chụp và đóng tất cả các cửa sổ. Đặt bên ngoài này của while vòng lặp để chấm dứt main chức năng.

step_5_camera.py
...      while True:         ...         if cv2.waitKey(1) & 0xFF == ord('q'):             break       cap.release()     cv2.destroyAllWindows() 

Kiểm tra kỹ file của bạn trùng với file sau hoặckho lưu trữ này:

step_5_camera.py
import cv2 import numpy as np import onnxruntime as ort   def center_crop(frame):     h, w, _ = frame.shape     start = abs(h - w) // 2     if h > w:         return frame[start: start + w]     return frame[:, start: start + h]   def main():     # constants     index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')     mean = 0.485 * 255.     std = 0.229 * 255.      # create runnable session with exported model     ort_session = ort.InferenceSession("signlanguage.onnx")      cap = cv2.VideoCapture(0)     while True:         # Capture frame-by-frame         ret, frame = cap.read()          # preprocess data         frame = center_crop(frame)         frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)         x = cv2.resize(frame, (28, 28))         x = (x - mean) / std          x = x.reshape(1, 1, 28, 28).astype(np.float32)         y = ort_session.run(None, {'input': x})[0]          index = np.argmax(y, axis=1)         letter = index_to_letter[int(index)]          cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)         cv2.imshow("Sign Language Translator", frame)          if cv2.waitKey(1) & 0xFF == ord('q'):             break      cap.release()     cv2.destroyAllWindows()  if __name__ == '__main__':     main() 

Thoát khỏi file của bạn và chạy tập lệnh.

  • python step_5_camera.py

Khi tập lệnh được chạy, một cửa sổ sẽ bật lên với nguồn cấp dữ liệu webcam trực tiếp của bạn. Ký tự ngôn ngữ ký hiệu dự đoán sẽ được hiển thị ở trên cùng bên trái. Hãy giơ tay lên và làm dấu hiệu yêu thích của bạn để xem bộ phân loại của bạn đang hoạt động. Dưới đây là một số kết quả mẫu cho thấy chữ LD.

Ảnh chụp màn hình chương trình OpenCV mẫu của bạn, cho ngôn ngữ ký hiệu 'L'.
Ảnh chụp màn hình của chương trình OpenCV mẫu của bạn, cho ngôn ngữ ký hiệu 'D'

Trong khi thử nghiệm, hãy lưu ý nền cần phải khá rõ ràng để trình dịch này hoạt động. Đây là một hậu quả đáng tiếc về độ sạch của tập dữ liệu. Nếu tập dữ liệu bao gồm hình ảnh các dấu hiệu bàn tay với các hình nền khác nhau, mạng sẽ mạnh mẽ đối với các hình nền ồn ào. Tuy nhiên, tập dữ liệu có nền trống và các bàn tay được căn giữa một cách độc đáo. Do đó, trình dịch webcam này hoạt động tốt nhất khi bàn tay của bạn cũng được căn giữa và đặt trên nền trống.

Điều này kết thúc ứng dụng phiên dịch ngôn ngữ ký hiệu.

Kết luận

Trong hướng dẫn này, bạn đã xây dựng một trình dịch Ngôn ngữ ký hiệu Mỹ bằng cách sử dụng thị giác máy tính và mô hình học máy. Đặc biệt, bạn đã thấy các khía cạnh mới của việc đào tạo mô hình học máy — cụ thể là tăng cường dữ liệu cho độ bền của mô hình, lịch trình tốc độ học tập để giảm tổn thất và xuất mô hình AI bằng ONNX để sử dụng trong production . Sau đó, điều này đạt đến đỉnh điểm trong một ứng dụng thị giác máy tính thời gian thực, dịch ngôn ngữ ký hiệu thành các chữ cái bằng cách sử dụng một đường ống dẫn bạn xây dựng. Cần lưu ý việc chống lại tính giòn của bộ phân loại cuối cùng có thể được giải quyết bằng bất kỳ hoặc tất cả các phương pháp sau. Để khám phá thêm, hãy thử các chủ đề sau để cải thiện ứng dụng của bạn:

  • Tổng quát hóa: Đây không phải là một chủ đề phụ trong tầm nhìn máy tính, đúng hơn, nó là một vấn đề liên tục trong suốt quá trình học máy. Xem phần Hiểu biết về sự cân bằng phương sai lệch .
  • Thích ứng domain : Giả sử mô hình của bạn được đào tạo trong domain A (ví dụ: môi trường nắng). Bạn có thể điều chỉnh mô hình sang domain B (ví dụ: môi trường nhiều mây) một cách nhanh chóng không?
  • Ví dụ về đối thủ: Giả sử đối thủ đang cố ý thiết kế hình ảnh để đánh lừa mô hình của bạn. Làm thế nào bạn có thể thiết kế những hình ảnh như vậy? Làm thế nào bạn có thể chống lại những hình ảnh như vậy?

Tags:

Các tin liên quan