Xây dựng Tự động hoàn thành dựa trên AI trong Trình duyệt bằng Vue.js, FastAPI và WebSockets

Mar 22 2022
Tự động hoàn thành thông minh tương tự như Google sử dụng mạng nơ-ron lặp lại
Giới thiệu Tôi yêu Google Tìm kiếm. Bằng cách nào đó, nó đọc được suy nghĩ của chúng ta và biết được chúng ta sẽ gõ gì tiếp theo.
Những gì chúng tôi sẽ xây dựng

Giới thiệu

Tôi yêu Google Tìm kiếm. Bằng cách nào đó, nó đọc được suy nghĩ của chúng ta và biết được chúng ta sẽ gõ gì tiếp theo. Tôi phải thừa nhận rằng, tôi không biết nó hoạt động như thế nào, nhưng trong bài viết này, chúng ta hãy cố gắng xây dựng một cái gì đó tương tự bằng cách sử dụng học sâu và mạng nơ-ron tuần hoàn.

Trực giác tổng thể

Hình ảnh của Tác giả

Ý tưởng là ngay sau khi người dùng bắt đầu nhập vào thanh tìm kiếm, chúng tôi lấy văn bản đầu vào và đưa nó vào một ký tự mạng thần kinh lặp lại theo từng ký tự để tạo dự đoán tự động hoàn thành.

Hướng dẫn được chia thành 3 phần:

  • Xây dựng và đào tạo mô hình RNN nhân vật
  • Xây dựng giao diện người dùng và phụ trợ
  • Thiết lập giao tiếp thông qua WebSockets

Chuẩn bị mô hình

Đầu tiên, hãy xây dựng mô hình charRNN là xương sống của ứng dụng.

Sổ ghi chép trên chứa mã để xây dựng mô hình. Hãy xem qua nó từng bước:

Tập dữ liệu

Bây giờ, chúng tôi muốn đào tạo mô hình charRNN để dự đoán câu hỏi mà người dùng sẽ hỏi - vì vậy hãy đào tạo mô hình trên tập dữ liệu câu hỏi.

Tôi đã tìm thấy tập dữ liệu dưới đây từ Kaggle (vui lòng xem lời xác nhận ở cuối về việc sử dụng tập dữ liệu):

Tập dữ liệu này được thiết kế đặc biệt cho các nhiệm vụ liên quan đến trả lời câu hỏi, tuy nhiên, chúng tôi chỉ có thể lọc ra các câu hỏi.

>> questions_all[10:15]
>> array(['Did Lincoln start his political career in 1832?',
       'Did Lincoln ever represent Alton & Sangamon Railroad?',
       'Which county was Lincoln born in?',
       'When did Lincoln first serve as President?',
       'Who assassinated Lincoln?'], dtype=object)

Xử lý trước dữ liệu

Bây giờ chúng ta đã lọc ra các câu hỏi, bước tiếp theo là làm sạch chúng và thực hiện xử lý trước bao gồm chuyển văn bản thành chữ thường; và xóa dấu chấm câu, giá trị rỗng, tab, dòng mới và nhiều khoảng trắng.

Đoạn mã trên thực hiện xử lý trước.

Thống kê dữ liệu

Chúng tôi vẫn chưa xử lý trước xong. Hãy xem biểu đồ phân phối độ dài của các câu hỏi:

Biểu đồ phân bố độ dài câu hỏi

Từ cốt truyện trên, chúng ta có thể thấy rằng một câu hỏi trung bình có ~ 50 ký tự, nhưng cũng có những câu hỏi dài hơn 200 ký tự. Nếu bạn nghĩ về điều đó, một người dùng hiếm khi tìm kiếm những câu hỏi dài như vậy, vì vậy hãy xóa hoàn toàn chúng đi và thật an toàn khi làm như vậy vì câu hỏi trung bình chỉ dài 50 ký tự. Bây giờ, chúng ta có thể giữ các câu hỏi nằm trong một độ lệch chuẩn so với giá trị trung bình để bao gồm phần lớn tập dữ liệu và loại bỏ mọi thứ khác.

Trong đoạn mã trên, chúng tôi tính toán giá trị trung bình và độ lệch chuẩn của phân phối và thêm chúng để có được độ dài tối ưu là độ dài cách giá trị trung bình một độ lệch chuẩn. Cuối cùng, chúng tôi loại bỏ các câu hỏi dài hơn độ dài tối ưu.

Chuẩn bị tập dữ liệu

Bây giờ chúng ta đã có các câu hỏi sẵn sàng, chúng ta phải mã hóa và chuẩn bị đưa chúng vào mô hình.

Hình ảnh động dưới đây cung cấp một cái nhìn tổng quan về quá trình mã hóa mà chúng ta sẽ thảo luận chi tiết.

Hoạt hình của tác giả

Trong Pytorch, chúng tôi thường tạo một lớp tập dữ liệu tùy chỉnh kế thừa từ lớp của Pytorch Datasetvà đây là một vài lợi ích của việc làm theo cách này:

  • Chúng tôi sẽ có nhiều quyền kiểm soát hơn đối với dữ liệu.
  • Nó giúp giữ cho mã theo mô-đun.
  • Chúng ta có thể tạo một Pytorch Dataloadertừ phiên bản tập dữ liệu này, nó sẽ tự động xử lý việc phân phối, xáo trộn và lấy mẫu dữ liệu như chúng ta sẽ thấy sau.
  • Đầu tiên, trong __init__phương pháp này, chúng tôi tải các câu hỏi và từ vựng được chuẩn bị bằng cách lấy các ký tự duy nhất từ ​​tập dữ liệu. Chúng tôi cũng thêm một mã thông báo bắt đầu và kết thúc bổ sung vì bất cứ khi nào chúng tôi xử lý các chuỗi hữu hạn, mô hình sẽ biết khi nào bắt đầu và kết thúc câu.
  • Phương encode_questionspháp này được sử dụng để mã hóa một câu hỏi duy nhất. Ở đây, trước tiên, chúng tôi mã hóa các ký tự trong câu hỏi dưới dạng chỉ số bằng cách sử dụng từ điển từ vựng mà chúng tôi đã tạo trong __init__phương pháp, sau đó chúng tôi thực hiện mã hóa một lần. Lưu ý rằng chúng tôi nối mã thông báo bắt đầu và kết thúc tương ứng ở đầu và cuối câu trước khi mã hóa một nóng. Bạn cũng có thể sử dụng phương pháp nhúng thay vì mã hóa một nóng, nhưng vì từ vựng ở đây ít, chúng ta có thể tiến hành mã hóa một nóng.
  • Quay lại __init__phương pháp này, chúng tôi mã hóa tất cả các câu hỏi và đặt chúng bằng các số không để tất cả chúng có cùng độ dài. Padding cho phép chúng tôi sắp xếp hàng loạt các câu hỏi có thể cải thiện thời gian đào tạo mà không ảnh hưởng đến hiệu suất của mô hình.
  • Có hai phương pháp bổ sung mà chúng tôi phải xác định: __len__phương thức sẽ trả về tổng số điểm dữ liệu trong tập dữ liệu và __getitem__phương thức sẽ trả về một điểm dữ liệu dựa trên chỉ mục. Các phương pháp này được Pytorch's sử dụng Dataloaderđể xử lý hàng loạt và xáo trộn dữ liệu như chúng ta sẽ thấy ở phần tiếp theo.
Mã hóa một câu hỏi

Thực hiện Chuyến tàu, Phân tách xác thực

Tiếp theo, hãy chia dữ liệu của chúng tôi thành các bộ đào tạo và xác nhận và tạo bộ dữ liệu.

Trong đoạn mã trên, trước tiên chúng ta tạo từ vựng bằng cách lấy các ký tự duy nhất từ ​​tất cả các câu hỏi, xác định kích thước lô và mã thông báo bắt đầu và kết thúc có thể là bất kỳ ký tự duy nhất nào không có trong từ vựng.

Tiếp theo, chúng tôi thực hiện phân tách xác thực-đào tạo theo tỷ lệ 9: 1 và tạo tập dữ liệu bằng cách sử dụng QuestionsDatasetlớp mà chúng tôi đã xác định trước đó. Phương thức khởi tạo của Pytorch Dataloadercho phép chúng ta tạo bộ lưu dữ liệu từ các thể hiện tập dữ liệu này, có thể tự động nhập hàng loạt, lấy mẫu và xáo trộn dữ liệu.

Cuối cùng, chúng tôi đã chuẩn bị dữ liệu để đưa vào mô hình. Hãy xây dựng mô hình tiếp theo.

Xây dựng mô hình charRNN

Mô hình charRNN nhận một ký tự đầu vào tại mỗi bước thời gian và xuất ra một phân phối xác suất cho ký tự thích hợp tiếp theo như được minh họa bên dưới:

Minh họa của tác giả

Ý tưởng là chúng ta chuyển một câu hỏi được mã hóa đến mạng nơ-ron tuần hoàn, mạng này tạo ra trạng thái ẩn ở mọi bước thời gian. Tiếp theo, chúng tôi chuyển từng trạng thái ẩn này thông qua một mạng được kết nối đầy đủ để lấy nhật ký. Sau đó, chúng tôi tính toán Mất Entropy Chéo từ logits và các mục tiêu. Chúng tôi tính toán tổn thất ở mỗi bước thời gian và cộng chúng trên tất cả các bước thời gian để có được tổng số tổn thất sẽ được sử dụng cho việc nhân giống ngược thông qua toàn bộ mạng.

Đây là mã cho mô hình:

Lưu ý rằng chúng tôi đang làm phẳng đầu ra trước khi chuyển nó qua mạng được kết nối đầy đủ. Đó là bởi vì bất cứ khi nào chúng tôi tính toán lỗ, chúng tôi làm điều đó cho tất cả các lô cùng nhau thay vì hàng loạt. Vì vậy, chúng tôi làm phẳng dọc theo kích thước lô để kết hợp tất cả các lô như hình dưới đây:

làm phẳng đầu ra

Đào tạo người mẫu

Được rồi, hãy đào tạo mô hình của chúng tôi:

Ở đây tôi đã xác định một mạng LSTM ba lớp với các lớp xếp chồng lên nhau với trạng thái ẩn có kích thước 512 và xác suất bỏ mạng là 0,4.

Tôi đã sử dụng trình tối ưu hóa Adam với tỷ lệ học mặc định là 1e-3 và Cross Entropy làm hàm mất mát. Vòng lặp đào tạo và xác thực được chạy trong 100 kỷ nguyên và mô hình được lưu trong mỗi 10 kỷ nguyên. Đây là những siêu thông số có thể được điều chỉnh dựa trên hiệu suất của mô hình.

Ngoài ra, có một số điều cần lưu ý ở đây:

  • Chúng tôi đang chuyển các trạng thái ẩn qua các lô, có nghĩa là trạng thái cuối cùng của một lô sẽ là trạng thái ban đầu của lô tiếp theo.
  • Chúng tôi đang cắt bớt các gradient trước khi cập nhật các thông số mô hình vì RNNs gặp phải sự cố bùng nổ các gradient do sự lan truyền ngược theo thời gian. Vì vậy, chúng tôi "cắt" các chuyển sắc có cường độ lớn đến một ngưỡng cụ thể.

Thoạt nhìn, có vẻ như một cốt truyện hay, nhưng nếu quan sát kỹ, việc mất chuyến tàu và mất xác thực nằm gần nhau - đó là một dấu hiệu không tốt. Nó thường có nghĩa là mô hình không thể nắm bắt các biểu diễn trong dữ liệu. Nó có ý nghĩa vì chúng tôi đang sử dụng một mô hình charRNN đơn giản. Chúng tôi có thể cải thiện hiệu suất nếu chúng tôi sử dụng các mô hình dựa trên máy biến áp, nhưng chúng ta hãy tiếp tục với mô hình này ngay bây giờ.

Tạo câu hỏi

Bây giờ mô hình của chúng tôi đã được đào tạo, chúng ta hãy hiểu cách thực hiện suy luận, trong trường hợp của chúng tôi là dự đoán câu hỏi dựa trên đầu vào của người dùng.

Ý tưởng được đưa ra một ký tự đầu vào, chúng tôi mã hóa nó và chuyển qua mạng để lấy các bản ghi làm đầu ra. Sau đó, chúng tôi áp dụng hàm softmax trên logits để có được phân phối xác suất của ký tự tiếp theo. Bây giờ, chúng tôi có thể trực tiếp chọn nhân vật hàng đầu từ bản phân phối này, nhưng điều này sẽ dẫn đến việc trang bị quá nhiều, vì vậy chúng tôi chọn k nhân vật hàng đầu từ bản phân phối và chọn ngẫu nhiên một trong số họ làm nhân vật tiếp theo.

Quá trình này được minh họa trong predict_next_charphương thức trong GenerateTextlớp trong đoạn mã bên dưới.

Phương predict_next_charthức này nhận một ký tự đầu vào và trạng thái ẩn để dự đoán ký tự thích hợp tiếp theo.

Bây giờ, mục đích của chúng tôi ở đây là dự đoán câu hỏi dựa trên ngữ cảnh từ người dùng. Bối cảnh, còn được gọi là nguyên tố, là một tập hợp các ký tự đầu tiên. Ý tưởng là đưa từng ký tự ban đầu này vào mô hình từng cái một và xây dựng trạng thái ẩn. Tiếp theo, sử dụng trạng thái ẩn và ký tự cuối cùng trong ngữ cảnh, chúng tôi dự đoán ký tự tiếp theo bằng cách sử dụng predict_next_charphương pháp, sau đó chúng tôi lấy ký tự được dự đoán này và sử dụng nó làm đầu vào để dự đoán ký tự tiếp theo. Chúng tôi lặp lại quá trình này cho đến khi chúng tôi đạt được mã thông báo kết thúc, điều này cho thấy rằng chúng tôi đã đến cuối câu hỏi.

Quá trình này được minh họa trong generate_textphương pháp.

Hãy xem một số ví dụ dưới đây:

Chuẩn bị giao diện người dùng

Giao diện người dùng của chúng tôi sẽ đơn giản, nó chỉ là một thanh tìm kiếm với đầu vào của người dùng được hiển thị ở phía trước và tự động hoàn thành hiển thị phía sau nó với độ mờ giảm như được hiển thị trong hình dưới đây:

Để có được hiệu ứng này, ý tưởng là đặt hai spanphần tử chồng lên nhau trong cùng một vùng divchứa, điều này có thể được thực hiện bằng cách đặt divvị trí của vùng chứa thành tương đối và spanvị trí của các phần tử thành tuyệt đối. Về cơ bản, chúng tôi đang đặt các spanphần tử liên quan đến vùng divchứa để chúng có thể được đặt ở cùng một vị trí.

Đoạn mã được hiển thị bên dưới:

Giờ đây, bạn có thể spanchỉnh sửa một phần tử ngay trong trình duyệt bằng cách đặt thuộc tính của nó contenteditablethành true. Ngoài ra, vì chúng tôi muốn tự động hoàn thành sau đầu vào của người dùng, nên chỉ số z của nó được đặt thành -1. Cuối cùng, chúng tôi bổ sung một số CSS để có được một thanh tìm kiếm tự động hoàn thành tuyệt đẹp.

Giao tiếp thông qua WebSockets

Ý tưởng là ngay sau khi người dùng bắt đầu nhập vào trình duyệt, văn bản cần được gửi làm đầu vào cho mô hình charRNN của chúng tôi tại phần phụ trợ để tạo các dự đoán. Các gợi ý sau đó sẽ được gửi trở lại giao diện người dùng và hiển thị trong trình giữ chỗ tự động hoàn thành phía sau thông tin người dùng nhập vào thanh tìm kiếm.

Vì chúng tôi muốn thiết lập giao tiếp hai chiều thời gian thực giữa giao diện người dùng và phụ trợ, nên WebSockets sẽ là lựa chọn lý tưởng.

Hãy xây dựng giao diện người dùng, phần phụ trợ và thiết lập giao tiếp bằng cách sử dụng WebSockets.

Xây dựng chương trình phụ trợ

Đối với phần phụ trợ, chúng tôi chỉ cần tạo một trình bao bọc API xung quanh mô hình charRNN của chúng tôi để phục vụ các yêu cầu. Tôi đang sử dụng FastAPI ở đây để xác định API WebSocket.

Hãy xem qua đoạn mã:

Đầu tiên, chúng tôi xác định và tải mô hình charRNN được đào tạo trước và đặt nó ở chế độ đánh giá. Tiếp theo, chúng tôi xác định một tuyến sử dụng giao thức WebSocket và một hàm predict_questionđược gọi bất cứ khi nào một yêu cầu truy cập vào tuyến này.

Hàm predict_questionlà một hàm không đồng bộ, về cơ bản có nghĩa là thay vì đợi một tác vụ cụ thể kết thúc, nó thực thi các phần khác và quay lại khi tác vụ kết thúc. Từ asynckhóa được sử dụng để xác định một hàm không đồng bộ trong khi awaittừ khóa cho biết không đợi hoàn thành một tác vụ cụ thể bên trong hàm.

Trong chức năng này, đầu tiên chúng tôi chấp nhận bắt tay từ máy chủ giao diện người dùng và thiết lập giao tiếp. Sau đó, ngay khi chúng tôi nhận được văn bản đầu vào từ giao diện người dùng, chúng tôi chạy văn bản đó thông qua mô hình charRNN để tạo dự đoán tự động hoàn thành và gửi lại văn bản đầu vào bằng giao thức WebSocket.

Tiếp theo, hãy thêm cơ chế giao tiếp WebSocket trong giao diện người dùng.

Xây dựng giao diện người dùng

Trong giao diện người dùng, chúng tôi cần một cơ chế để phát hiện đầu vào của người dùng để chúng tôi có thể gửi văn bản đến phần phụ trợ ngay khi người dùng bắt đầu nhập. Chúng ta có thể sử dụng chỉ thị Vue và trình xử lý sự kiện cho mục đích này. Chỉ v-onthị (cũng được biểu thị bằng @ký hiệu) lắng nghe các sự kiện cụ thể trên một phần tử và gọi hàm xử lý khi sự kiện được kích hoạt.

hãy xem qua đoạn mã:

Đầu tiên, chúng ta tạo đối tượng WebSocket trong mountedmóc vòng đời. Các móc vòng đời về cơ bản là các hàm được gọi trong các giai đoạn tạo thành phần khác nhau. Vòng mountedđời hook được gọi khi thành phần được kết xuất vào DOM và đó là một giai đoạn tốt để khởi tạo đối tượng WebSocket và thiết lập giao tiếp với phần phụ trợ.

Tiếp theo, trong phần tử thanh tìm kiếm, chúng tôi thêm dòng @input="sendText”để lắng nghe sự kiện đầu vào và kích hoạt chức năng sendTextlấy văn bản trong thanh tìm kiếm và gửi nó đến phần phụ trợ. Khi chúng tôi nhận được dự đoán tự động hoàn thành từ chương trình phụ trợ, chức năng receiveTextđược kích hoạt thông qua onmessagephương thức gọi lại của đối tượng WebSocket và chức năng này sẽ điền vào autoCompletetrình giữ chỗ được hiển thị đằng sau thông tin người dùng nhập vào thanh tìm kiếm.

Lưu ý dòng @keypress=”preventInput”trong phần tử thanh tìm kiếm. Đây là một trình xử lý sự kiện ngăn người dùng nhập các giá trị số hoặc dấu chấm câu vào thanh tìm kiếm vì hãy nhớ trong bước xử lý trước của mô hình charRNN của chúng tôi, chúng tôi đã xóa mọi thứ chỉ giữ lại các ký tự trong bảng chữ cái. Vì vậy, để ngăn đầu vào không được công nhận đi vào mô hình charRNN của chúng tôi, chúng tôi sử dụng trình xử lý sự kiện này.

Đó là nó. Chúng tôi đã hoàn tất ứng dụng của mình. Hãy xem nó trong hành động.

Chạy ứng dụng

Bạn có thể tìm thấy mã trong kho lưu trữ GitHub này . Để thiết lập môi trường, hãy sao chép kho lưu trữ này vào không gian làm việc của bạn:

git clone https://github.com/wingedrasengan927/deep-autocomplete.git
cd deep-autocomplete

kubectl apply -f ./backend/backend-deployment.yaml
kubectl apply -f ./backend/backend-service.yaml
kubectl apply -f ./frontend/frontend-deployment.yaml
kubectl apply -f ./frontend/frontend-service.yaml

Các ứng dụng

Mặc dù đây là một dự án thú vị, thân thiện với người mới bắt đầu, nhưng một ứng dụng chính mà tôi có thể nghĩ đến là xây dựng các thanh tìm kiếm được cá nhân hóa sử dụng một số loại thuật toán so khớp mờ để tìm kiếm kết quả mong muốn. Tuy nhiên, chúng tôi có thể muốn sử dụng các mô hình dựa trên Máy biến áp để có hiệu suất tốt hơn.

Tôi hy vọng bạn thích bài viết. Hãy kết nối trên LinkedIn và Twitter .

Sự nhìn nhận:

Bộ dữ liệu được sử dụng ở đây được thu thập bởi Noah Smith, Michael Heilman, Rebecca Hwa, Shay Cohen, Kevin Gimpel, và nhiều sinh viên tại Đại học Carnegie Mellon và Đại học Pittsburgh từ năm 2008 đến năm 2010. Nó được sử dụng ở đây theo CC BY_SA 3.0. Vui lòng trích dẫn bài báo này nếu bạn viết bất kỳ bài báo nào liên quan đến việc sử dụng dữ liệu ở trên:

Smith, NA, Heilman, M., & Hwa, R. (2008, tháng 9). Tạo câu hỏi như một dự án khóa học đại học cạnh tranh. Trong Kỷ yếu của Hội thảo NSF về Thử thách Đánh giá và Nhiệm vụ được Chia sẻ Tạo Câu hỏi.

© Copyright 2021 - 2023 | vngogo.com | All Rights Reserved