4.4. Lựa Chọn Mô Hình, Dưới Khớp và Quá Khớp

Là những nhà khoa học học máy, mục tiêu của chúng ta là khám phá ra các khuôn mẫu. Nhưng làm sao có thể chắc chắn rằng chúng ta đã thực sự khám phá ra một khuôn mẫu khái quát chứ không chỉ đơn giản là ghi nhớ dữ liệu. Ví dụ, thử tưởng tượng rằng chúng ta muốn săn lùng các khuôn mẫu liên kết các dấu hiệu di truyền của bệnh nhân và tình trạng mất trí của họ, với nhãn được trích ra từ tập {mất trí nhớ, suy giảm nhận thức mức độ nhẹ, khỏe mạnh}. Bởi vì các gen của mỗi người định dạng họ theo cách độc nhất vô nhị (bỏ qua các cặp song sinh giống hệt nhau), nên việc ghi nhớ toàn bộ tập dữ liệu là hoàn toàn khả thi.

Chúng ta không muốn mô hình của mình nói rằng “Bob kìa! Tôi nhớ anh ta! Anh ta bị mất trí nhớ! Lý do tại sao rất đơn giản. Khi triển khai mô hình trong tương lai, chúng ta sẽ gặp các bệnh nhân mà mô hình chưa bao giờ gặp trước đó. Các dự đoán sẽ chỉ có ích khi mô hình của chúng ta thực sự khám phá ra một khuôn mẫu khái quát.

Để tóm tắt một cách chính thức hơn, mục tiêu của chúng ta là khám phá các khuôn mẫu mà chúng mô tả được các quy tắc trong tập dữ liệu mà từ đó tập huấn luyện đã được trích ra. Nếu thành công trong nỗ lực này, thì chúng ta có thể đánh giá thành công rủi ro ngay cả đối với các cá nhân mà chúng ta chưa bao giờ gặp phải trước đây. Vấn đề này—làm cách nào để khám phá ra các mẫu mà khái quát hóa—là vấn đề nền tảng của học máy.

Nguy hiểm là khi huấn luyện các mô hình, chúng ta chỉ truy cập một tập dữ liệu nhỏ. Các tập dữ liệu hình ảnh công khai lớn nhất chứa khoảng một triệu ảnh. Thường thì chúng ta phải học chỉ từ vài ngàn hoặc vài chục ngàn điểm dữ liệu. Trong một hệ thống bệnh viện lớn, chúng ta có thể truy cập hàng trăm ngàn hồ sơ y tế. Khi làm việc với các tập mẫu hữu hạn, chúng ta gặp phải rủi ro sẽ khám phá ra các mối liên kết rõ ràng mà hóa ra lại không đúng khi thu thập thêm dữ liệu.

Hiện tượng mô hình khớp với dữ liệu huấn luyện chính xác hơn nhiều so với phân phối thực được gọi là quá khớp (overfitting), và kỹ thuật sử dụng để chống lại quá khớp được gọi là điều chuẩn (regularization). Trong các phần trước, bạn có thể đã quan sát hiệu ứng này khi thử nghiệm với tập dữ liệu Fashion-MNIST. Nếu bạn đã sửa đổi cấu trúc mô hình hoặc siêu tham số trong quá trình thử nghiệm, bạn có thể đã nhận ra rằng với đủ các nút, các tầng, và các epoch huấn luyện, mô hình ấy có thể cuối cùng cũng đạt đến sự chính xác hoàn hảo trên tập huấn luyện, ngay cả khi độ chính xác trên dữ liệu kiểm tra giảm đi.

4.4.1. Lỗi huấn luyện và Lỗi khái quát

Để thảo luận hiện tượng này một cách chuyên sâu hơn, ta cần phân biệt giữa lỗi huấn luyện (training error) và lỗi khái quát (generalization error). Lỗi huấn luyện là lỗi của mô hình được tính toán trên tập huấn luyện, trong khi đó lỗi khái quát là lỗi kỳ vọng của mô hình khi áp dụng nó cho một luồng vô hạn các điểm dữ liệu mới được lấy từ cùng một phân phối dữ liệu với các mẫu ban đầu.

Vấn đề là chúng ta không bao giờ có thể tính toán chính xác lỗi khái quát vì luồng vô hạn dữ liệu chỉ có trong tưởng tượng. Trên thực tế, ta phải ước tính lỗi khái quát bằng cách áp dụng mô hình vào một tập kiểm tra độc lập bao gồm các điểm dữ liệu ngẫu nhiên ngoài tập huấn luyện.

Ba thí nghiệm sau sẽ giúp minh họa tình huống này tốt hơn. Hãy xem xét một sinh viên đại học đang cố gắng chuẩn bị cho kỳ thi cuối cùng của mình. Một sinh viên chăm chỉ sẽ cố gắng luyện tập tốt và kiểm tra khả năng của cô ấy bằng việc luyện tập những bài kiểm tra của các năm trước. Tuy nhiên, làm tốt các bài kiểm tra trước đây không đảm bảo rằng cô ấy sẽ làm tốt bài kiểm tra thật. Ví dụ, sinh viên có thể cố gắng chuẩn bị bằng cách học tủ các câu trả lời cho các câu hỏi. Điều này đòi hỏi sinh viên phải ghi nhớ rất nhiều thứ. Cô ấy có lẽ còn ghi nhớ đáp án cho các bài kiểm tra cũ một cách hoàn hảo. Một học sinh khác có thể chuẩn bị bằng việc cố gắng hiểu lý do mà một số đáp án nhất định được đưa ra. Trong hầu hết các trường hợp, sinh viên sau sẽ làm tốt hơn nhiều.

Tương tự như vậy, hãy xem xét một mô hình đơn giản chỉ sử dụng một bảng tra cứu để trả lời các câu hỏi. Nếu tập hợp các đầu vào cho phép là rời rạc và đủ nhỏ, thì có lẽ sau khi xem nhiều ví dụ huấn luyện, phương pháp này sẽ hoạt động tốt. Tuy nhiên mô hình này không có khả năng thể hiện tốt hơn so với việc đoán ngẫu nhiên khi phải đối mặt với các ví dụ chưa từng gặp trước đây. Trong thực tế, không gian đầu vào là quá lớn để có thể ghi nhớ mọi đáp án tương ứng của từng đầu vào khả dĩ. Ví dụ, hãy xem xét các ảnh \(28\times28\) đen trắng. Nếu mỗi điểm ảnh có thể lấy một trong số các giá trị xám trong thang \(256\), thì có thể có \(256^{784}\) ảnh khác nhau. Điều đó nghĩa là số lượng ảnh độ phân giải thấp còn lớn hơn nhiều so với số lượng nguyên tử trong vũ trụ. Thậm chí nếu có thể xem qua toàn bộ điểm dữ liệu, ta cũng không thể lưu trữ chúng trong bảng tra cứu.

Cuối cùng, hãy xem xét bài toán phân loại kết quả của việc tung đồng xu (lớp 0: ngửa, lớp 1: xấp) dựa trên một số đặc trưng theo ngữ cảnh sẵn có. Bất kể thuật toán nào được đưa ra, lỗi khái quát sẽ luôn là \(\frac{1}{2}\). Tuy nhiên, đối với hầu hết các thuật toán, lỗi huấn luyện sẽ thấp hơn đáng kể, tùy thuộc vào sự may mắn của ta khi lấy dữ liệu, ngay cả khi ta không có bất kỳ đặc trưng nào! Hãy xem xét tập dữ liệu {0, 1, 1, 1, 0, 1}. Việc không có đặc trưng có thể khiến ta luôn dự đoán lớp chiếm đa số, đối với ví dụ này thì đó là 1. Trong trường hợp này, mô hình luôn dự đoán lớp 1 sẽ có lỗi huấn luyện là \(\frac{1}{3}\), tốt hơn đáng kể so với lỗi khái quát. Khi ta tăng lượng dữ liệu, xác suất nhận được mặt ngửa sẽ dần tiến về \(\frac{1}{2}\) và lỗi huấn luyện sẽ tiến đến lỗi khái quát.

4.4.1.1. Lý thuyết Học Thống kê

Bởi khái quát hóa là một vấn đề nền tảng trong học máy, không quá ngạc nhiên khi nhiều nhà toán học và nhà lý thuyết học dành cả cuộc đời để phát triển các lý thuyết hình thức mô tả vấn đề này. Trong định lý cùng tên, Glivenko và Cantelli đã tìm ra tốc độ học mà tại đó lỗi huấn luyện sẽ hội tụ về lỗi khái quát. Trong chuỗi các bài báo đầu ngành, Vapnik và Chervonenkis đã mở rộng lý thuyết này cho nhiều lớp hàm tổng quát hơn. Công trình này là nền tảng của ngành Lý thuyết học thống kê.

Trong một thiết lập chuẩn cho học có giám sát – chủ đề lớn nhất xuyên suốt cuốn sách, chúng ta giả sử rằng cả dữ liệu huấn luyện và dữ liệu kiểm tra đều được lấy mẫu độc lập từ các phân phối giống hệt nhau (independent & identically distributed, thường gọi là giả thiết i.i.d.). Điều này có nghĩa là quá trình lấy mẫu dữ liệu không hề có sự ghi nhớ. Mẫu lấy ra thứ hai cũng không tương quan với mẫu thứ ba hơn so với mẫu thứ hai triệu.

Trở thành một nhà khoa học học máy giỏi yêu cầu tư duy phản biện, và có lẽ bạn đã có thể “bóc mẽ” được giả thiết này, có thể đưa ra các tình huống thường gặp mà giả thiết này không thỏa mãn. Điều gì sẽ xảy ra nếu chúng ta huấn luyện một mô hình dự đoán tỉ lệ tử vong trên bộ dữ thu thập từ các bệnh nhân tại UCSF, và áp dụng nó trên các bệnh nhân tại Bệnh viện Đa khoa Massachusetts. Các phân phối này đơn giản là không giống nhau. Hơn nữa, việc lấy mẫu có thể có tương quan về mặt thời gian. Sẽ ra sao nếu chúng ta thực hiện phân loại chủ đề cho các bài Tweet. Vòng đời của các tin tức sẽ tạo nên sự phụ thuộc về mặt thời gian giữa các chủ đề được đề cập, vi phạm mọi giả định độc lập thống kê.

Đôi khi, chúng ta có thể bỏ qua một vài vi phạm nhỏ trong giả thiết i.i.d. mà mô hình vẫn có thể làm việc rất tốt. Nhìn chung, gần như tất cả các ứng dụng thực tế đều vi phạm một vài giả thiết i.i.d. nhỏ, nhưng đổi lại ta có được các công cụ rất hữu dụng như nhận dạng khuôn mặt, nhận dạng tiếng nói, dịch ngôn ngữ, v.v.

Các vi phạm khác thì chắc chắn dẫn tới rắc rối. Cùng hình dung ở ví dụ này, ta thử huấn luyện một hệ thống nhận dạng khuôn mặt sử dụng hoàn toàn dữ liệu của các sinh viên đại học và đem đi triển khai như một công cụ giám sát trong viện dưỡng lão. Cách này gần như không khả thi vì ngoại hình giữa hai độ tuổi quá khác biệt.

Trong các mục và chương kế tiếp, chúng ta sẽ đề cập tới các vấn đề gặp phải khi vi phạm giả thiết i.i.d. Hiện tại khi giả thiết i.i.d. thậm chí được đảm bảo, hiểu được sự khái quát hóa cũng là một vấn đề nan giải. Hơn nữa, việc làm sáng tỏ nền tảng lý thuyết để giải thích tại sao các mạng nơ-ron sâu có thể khái quát hóa tốt như vậy vẫn tiếp tục làm đau đầu những bộ óc vĩ đại nhất trong lý thuyết học.

Khi huấn luyện mô hình, ta đang cố gắng tìm kiếm một hàm số khớp với dữ liệu huấn luyện nhất có thể. Nếu hàm số này quá linh hoạt để có thể khớp với các khuôn mẫu giả cũng dễ như với các xu hướng thật trong dữ liệu, thì nó có thể quá khớp để có thể tạo ra một mô hình có tính khái quát hóa cao trên dữ liệu chưa nhìn thấy. Đây chính xác là những gì chúng ta muốn tránh (hay ít nhất là kiểm soát được). Rất nhiều kỹ thuật trong học sâu là các phương pháp dựa trên thực nghiệm và thủ thuật để chống lại vấn đề quá khớp.

4.4.1.2. Độ Phức tạp của Mô hình

Khi có các mô hình đơn giản và dữ liệu dồi dào, ta kỳ vọng lỗi khái quát sẽ giống với lỗi huấn luyện. Khi làm việc với mô hình phức tạp hơn và ít mẫu huấn luyện hơn, ta kỳ vọng các lỗi huấn luyện giảm xuống nhưng khoảng cách khái quát tăng. Việc chỉ ra chính xác điều gì cấu thành nên độ phức tạp của mô hình là một vấn đề nan giải. Có rất nhiều yếu tố ảnh hưởng đến việc một mô hình có khái quát hóa tốt hay không. Ví dụ một mô hình với nhiều tham số hơn sẽ được xem là phức tạp hơn. Một mô hình mà các tham số có miền giá trị rộng hơn thì được xem là phức tạp hơn. Thông thường với các mạng nơ-ron, ta nghĩ đến một mô hình có nhiều bước huấn luyện là mô hình phức tạp hơn, và mô hình dừng sớm là ít phức tạp hơn.

Rất khó để có thể so sánh sự phức tạp giữa các thành viên trong các lớp mô hình khác hẳn nhau (ví như cây quyết định so với mạng nơ-ron). Hiện tại, có một quy tắc đơn giản khá hữu ích sau: Một mô hình có thể giải thích các sự kiện bất kỳ thì được các nhà thống kê xem là phức tạp, trong khi một mô hình với năng lực biểu diễn giới hạn nhưng vẫn có thể giải thích tốt được dữ liệu thì hầu như chắc chắn là đúng đắn hơn. Trong triết học, điều này gần với tiêu chí của Popper về khả năng phủ định của một lý thuyết khoa học: một lý thuyết tốt nếu nó khớp dữ liệu và nếu có các kiểm định cụ thể có thể dùng để phản chứng nó. Điều này quan trọng bởi vì tất cả các ước lượng thống kê là post hoc, tức là ta đánh giá giả thuyết sau khi quan sát các sự thật, do đó dễ bị tác động bởi lỗi ngụy biện cùng tên. Từ bây giờ, ta sẽ đặt triết lý qua một bên và tập trung hơn vào các vấn đề hữu hình.

Trong phần này, để có cái nhìn trực quan, chúng ta sẽ tập trung vào một vài yếu tố có xu hướng ảnh hưởng đến tính khái quát của một lớp mô hình:

  1. Số lượng các tham số có thể điều chỉnh. Khi số lượng các tham số có thể điều chỉnh (đôi khi được gọi là bậc tự do) lớn thì mô hình sẽ dễ bị quá khớp hơn.
  2. Các giá trị được nhận bởi các tham số. Khi các trọng số có miền giá trị rộng hơn, các mô hình dễ bị quá khớp hơn.
  3. Số lượng các mẫu huấn luyện. Việc quá khớp một tập dữ liệu chứa chỉ một hoặc hai mẫu rất dễ dàng, kể cả khi mô hình đơn giản. Nhưng quá khớp một tập dữ liệu với vài triệu mẫu đòi hỏi mô hình phải cực kỳ linh hoạt.

4.4.2. Lựa chọn Mô hình

Trong học máy, ta thường lựa chọn mô hình cuối cùng sau khi cân nhắc nhiều mô hình ứng viên. Quá trình này được gọi là lựa chọn mô hình. Đôi khi các mô hình được đem ra so sánh khác nhau cơ bản về mặt bản chất (ví như, cây quyết định với các mô hình tuyến tính). Khi khác, ta lại so sánh các thành viên của cùng một lớp mô hình được huấn luyện với các cài đặt siêu tham số khác nhau.

Lấy perceptron đa tầng làm ví dụ, ta mong muốn so sánh các mô hình với số lượng tầng ẩn khác nhau, số lượng nút ẩn khác nhau, và các lựa chọn hàm kích hoạt khác nhau áp dụng vào từng tầng ẩn. Để xác định được mô hình tốt nhất trong các mô hình ứng viên, ta thường sử dụng một tập kiểm định.

4.4.2.1. Tập Dữ liệu Kiểm định

Về nguyên tắc, ta không nên sử dụng tập kiểm tra cho đến khi chọn xong tất cả các siêu tham số. Nếu sử dụng dữ liệu kiểm tra trong quá trình lựa chọn mô hình, có một rủi ro là ta có thể quá khớp dữ liệu kiểm tra, và khi đó ta sẽ gặp rắc rối lớn. Nếu quá khớp dữ liệu huấn luyện, ta luôn có thể đánh giá mô hình trên tập kiểm tra để đảm bảo mình “trung thực”. Nhưng nếu quá khớp trên dữ liệu kiểm tra, làm sao chúng ta có thể biết được?

Vì vậy, ta không bao giờ nên dựa vào dữ liệu kiểm tra để lựa chọn mô hình. Tuy nhiên, không thể chỉ dựa vào dữ liệu huấn luyện để lựa chọn mô hình vì ta không thể ước tính lỗi khái quát trên chính dữ liệu được sử dụng để huấn luyện mô hình.

Phương pháp phổ biến để giải quyết vấn đề này là phân chia dữ liệu thành ba phần, thêm một tập kiểm định ngoài các tập huấn luyện và kiểm tra.

Trong các ứng dụng thực tế, bức tranh trở nên mập mờ hơn. Mặc dù tốt nhất ta chỉ nên động đến dữ liệu kiểm tra đúng một lần, để đánh giá mô hình tốt nhất hoặc so sánh một số lượng nhỏ các mô hình với nhau, dữ liệu kiểm tra trong thế giới thực hiếm khi bị vứt bỏ chỉ sau một lần sử dụng. Ta hiếm khi có được một tập kiểm tra mới sau mỗi vòng thử nghiệm.

Kết quả là một thực tiễn âm u trong đó ranh giới giữa dữ liệu kiểm định và kiểm tra mơ hồ theo cách đáng lo ngại. Trừ khi có quy định rõ ràng thì, trong các thí nghiệm trong cuốn sách này, ta thật sự đang làm việc với cái được gọi là dữ liệu huấn luyện và dữ liệu kiểm định chứ không có tập kiểm tra thật. Do đó, độ chính xác được báo cáo trong mỗi thử nghiệm thật ra là độ chính xác kiểm định và không phải là độ chính xác của tập kiểm tra thật. Tin tốt là ta không cần quá nhiều dữ liệu trong tập kiểm định. Ta có thể chứng minh rằng sự bất định trong các ước tính thuộc bậc \(\mathcal{O}(n^{-\frac{1}{2}})\).

4.4.2.2. Kiểm định chéo gập \(K\)-lần

Khi khan hiếm dữ liệu huấn luyện, có lẽ ta sẽ không thể dành ra đủ dữ liệu để tạo một tập kiểm định phù hợp. Một giải pháp phổ biến để giải quyết vấn đề này là kiểm định chéo gập \(K\)-lần. Ở phương pháp này, tập dữ liệu huấn luyện ban đầu được chia thành \(K\) tập con không chồng lên nhau. Sau đó việc huấn luyện và kiểm định mô hình được thực thi \(K\) lần, mỗi lần huấn luyện trên \(K-1\) tập con và kiểm định trên tập con còn lại (tập không được sử dụng để huấn luyện trong lần đó). Cuối cùng, lỗi huấn luyện và lỗi kiểm định được ước lượng bằng cách tính trung bình các kết quả thu được từ \(K\) thí nghiệm.

4.4.3. Dưới khớp hay Quá khớp?

Khi so sánh lỗi huấn luyện và lỗi kiểm định, ta cần lưu ý hai trường hợp thường gặp sau: Đầu tiên, ta sẽ muốn chú ý trường hợp lỗi huấn luyện và lỗi kiểm định đều lớn nhưng khoảng cách giữa chúng lại nhỏ. Nếu mô hình không thể giảm thiểu lỗi huấn luyện, điều này có nghĩa là mô hình quá đơn giản (tức không đủ khả năng biểu diễn) để có thể xác định được khuôn mẫu mà ta đang cố mô hình hóa. Hơn nữa, do khoảng cách khái quát giữa lỗi huấn luyện và lỗi kiểm định nhỏ, ta có lý do để tin rằng phương án giải quyết là một mô hình phức tạp hơn. Hiện tượng này được gọi là dưới khớp (underfitting).

Mặt khác, như ta đã thảo luận ở phía trên, ta cũng muốn chú ý tới trường hợp lỗi huấn luyện thấp hơn lỗi kiểm định một cách đáng kể, một biểu hiện của sự quá khớp nặng. Lưu ý rằng quá khớp không phải luôn là điều xấu. Đặc biệt là với học sâu, ta đều biết rằng mô hình dự đoán tốt nhất thường đạt chất lượng tốt hơn hẳn trên dữ liệu huấn luyện so với dữ liệu kiểm định. Sau cùng, ta thường quan tâm đến lỗi kiểm định hơn khoảng cách giữa lỗi huấn luyện và lỗi kiểm định.

Việc ta đang quá khớp hay dưới khớp có thể phụ thuộc vào cả độ phức tạp của mô hình lẫn kích thước của tập dữ liệu huấn luyện có sẵn, và hai vấn đề này sẽ được thảo luận ngay sau đây.

4.4.3.1. Độ phức tạp Mô hình

Để có thể hình dung một cách trực quan hơn về mối quan hệ giữa quá khớp và độ phức tạp mô hình, ta sẽ đưa ra một ví dụ sử dụng đa thức. Cho một tập dữ liệu huấn luyện có một đặc trưng duy nhất \(x\) và nhãn \(y\) tương ứng có giá trị thực, ta thử tìm bậc \(d\) của đa thức

(4.4.1)\[\hat{y}= \sum_{i=0}^d x^i w_i\]

để ước tính nhãn \(y\). Đây đơn giản là một bài toán hồi quy tuyến tính trong đó các đặc trưng được tính bằng cách lấy mũ của \(x\), \(w_i\) là trọng số của mô hình, vì \(x^0=1\) với mọi \(x\) nên \(w_0\) là hệ số điều chỉnh. Vì đây là bài toán hồi quy tuyến tính, ta có thể sử dụng bình phương sai số làm hàm mất mát.

Hàm đa thức bậc cao phức tạp hơn hàm đa thức bậc thấp, vì đa thức bậc cao có nhiều tham số hơn và miền lựa chọn hàm số cũng rộng hơn. Nếu giữ nguyên tập dữ liệu huấn luyện, các hàm đa thức bậc cao hơn sẽ luôn đạt được lỗi huấn luyện thấp hơn (ít nhất là bằng) so với đa thức bậc thấp hơn. Trong thực tế, nếu mọi điểm dữ liệu có các giá trị \(x\) riêng biệt, một hàm đa thức có bậc bằng với số điểm dữ liệu đều có thể khớp một cách hoàn hảo với tập huấn luyện. Mối quan hệ giữa bậc của đa thức với hai hiện tượng dưới khớp và quá khớp được biểu diễn trong Fig. 4.4.1.

../_images/capacity_vs_error.svg

Fig. 4.4.1 Ảnh hưởng của Độ phức tạp Mô hình tới Dưới khớp và Quá khớp

4.4.3.2. Kích thước Tập dữ liệu

Một lưu ý quan trọng khác cần ghi nhớ là kích thước tập dữ liệu. Với một mô hình cố định, tập dữ liệu càng ít mẫu thì càng có nhiều khả năng gặp phải tình trạng quá khớp với mức độ nghiêm trọng hơn. Khi số lượng dữ liệu tăng lên, lỗi khái quát sẽ có xu hướng giảm. Hơn nữa, trong hầu hết các trường hợp, nhiều dữ liệu không bao giờ là thừa. Trong một tác vụ với một phân phối dữ liệu cố định, ta có thể quan sát được mối quan hệ giữa độ phức tạp của mô hình và kích thước tập dữ liệu. Khi có nhiều dữ liệu, thử khớp một mô hình phức tạp hơn thường sẽ mang lợi nhiều lợi ích. Khi dữ liệu không quá nhiều, mô hình đơn giản sẽ là lựa chọn tốt hơn. Đối với nhiều tác vụ, học sâu chỉ tốt hơn các mô hình tuyến tính khi có sẵn hàng ngàn mẫu huấn luyện. Sự thành công hiện nay của học sâu phần nào dựa vào sự phong phú của các tập dữ liệu khổng lồ từ các công ty hoạt động trên internet, từ các thiết bị lưu trữ giá rẻ, các thiết bị được nối mạng và rộng hơn là việc số hóa nền kinh tế.

4.4.4. Hồi quy Đa thức

Bây giờ ta có thể khám phá một cách tương tác những khái niệm này bằng cách khớp đa thức với dữ liệu. Để bắt đầu ta sẽ nhập các gói thư viện thường dùng.

from d2l import mxnet as d2l
from mxnet import gluon, np, npx
from mxnet.gluon import nn
npx.set_np()

4.4.4.1. Tạo ra Tập dữ liệu

Đầu tiên ta cần dữ liệu. Cho \(x\), ta sẽ sử dụng đa thức bậc ba ở dưới đây để tạo nhãn cho tập dữ liệu huấn luyện và tập kiểm tra:

(4.4.2)\[y = 5 + 1.2x - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} + \epsilon \text{ với } \epsilon \sim \mathcal{N}(0, 0.1).\]

Số hạng nhiễu \(\epsilon\) tuân theo phân phối chuẩn (phân phối Gauss) với giá trị trung bình bằng 0 và độ lệch chuẩn bằng 0.1. Ta sẽ tạo 100 mẫu cho mỗi tập huấn luyện và tập kiểm tra.

maxdegree = 20  # Maximum degree of the polynomial
n_train, n_test = 100, 100  # Training and test dataset sizes
true_w = np.zeros(maxdegree)  # Allocate lots of empty space
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

features = np.random.normal(size=(n_train + n_test, 1))
features = np.random.shuffle(features)
poly_features = np.power(features, np.arange(maxdegree).reshape(1, -1))
poly_features = poly_features / (
    npx.gamma(np.arange(maxdegree) + 1).reshape(1, -1))
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)

Khi tối ưu hóa, ta thường muốn tránh các giá trị rất lớn của gradient, mất mát, v.v. Đây là lý do tại sao các đơn thức lưu trong poly_features được chuyển đổi giá trị từ \(x^i\) thành \(\frac{1}{i!} x^i\). Nó cho phép ta tránh các giá trị quá lớn với số mũ bậc cao \(i\). Phép tính giai thừa được lập trình trong Gluon bằng hàm Gamma, với \(n! = \Gamma(n+1)\).

Hãy xét hai mẫu đầu tiên trong tập dữ liệu được tạo. Về mặt kỹ thuật giá trị 1 là một đặc trưng, cụ thể là đặc trưng không đổi tương ứng với hệ số điều chỉnh.

features[:2], poly_features[:2], labels[:2]
(array([[-0.03716067],
        [-1.1468065 ]]),
 array([[ 1.0000000e+00, -3.7160669e-02,  6.9045764e-04, -8.5526226e-06,
          7.9455290e-08, -5.9052230e-10,  3.6573674e-12, -1.9415738e-14,
          9.0187774e-17, -3.7238198e-19,  1.3837955e-21, -4.6747992e-24,
          1.4476548e-26, -4.1381331e-29,  1.0983988e-31, -2.7211553e-34,
          6.3199964e-37, -1.3815009e-39,  2.8516424e-42, -5.6051939e-45],
        [ 1.0000000e+00, -1.1468065e+00,  6.5758252e-01, -2.5137332e-01,
          7.2069131e-02, -1.6529869e-02,  3.1594266e-03, -5.1760708e-04,
          7.4199437e-05, -9.4547104e-06,  1.0842717e-06, -1.1304095e-07,
          1.0803002e-08, -9.5299479e-10,  7.8064347e-11, -5.9683270e-12,
          4.2778224e-13, -2.8857840e-14,  1.8385725e-15, -1.1097342e-16]]),
 array([ 5.1432443 , -0.06415121]))

4.4.4.2. Huấn luyện và Kiểm tra Mô hình

Trước tiên ta lập trình hàm để tính giá trị mất mát của dữ liệu cho trước.

# Saved in the d2l package for later use
def evaluate_loss(net, data_iter, loss):
    """Evaluate the loss of a model on the given dataset."""
    metric = d2l.Accumulator(2)  # sum_loss, num_examples
    for X, y in data_iter:
        metric.add(loss(net(X), y).sum(), y.size)
    return metric[0] / metric[1]

Giờ ta định nghĩa hàm huấn luyện.

def train(train_features, test_features, train_labels, test_labels,
          num_epochs=1000):
    loss = gluon.loss.L2Loss()
    net = nn.Sequential()
    # Switch off the bias since we already catered for it in the polynomial
    # features
    net.add(nn.Dense(1, use_bias=False))
    net.initialize()
    batch_size = min(10, train_labels.shape[0])
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    test_iter = d2l.load_array((test_features, test_labels), batch_size,
                               is_train=False)
    trainer = gluon.Trainer(net.collect_params(), 'sgd',
                            {'learning_rate': 0.01})
    animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, num_epochs], ylim=[1e-3, 1e2],
                            legend=['train', 'test'])
    for epoch in range(1, num_epochs+1):
        d2l.train_epoch_ch3(net, train_iter, loss, trainer)
        if epoch % 50 == 0:
            animator.add(epoch, (evaluate_loss(net, train_iter, loss),
                                 evaluate_loss(net, test_iter, loss)))
    print('weight:', net[0].weight.data().asnumpy())

4.4.4.3. Khớp Hàm Đa thức Bậc Ba (dạng chuẩn)

Ta sẽ bắt đầu với việc sử dụng hàm đa thức bậc ba, cùng bậc với hàm tạo dữ liệu. Kết quả cho thấy cả lỗi huấn luyện và lỗi kiểm tra của mô hình đều thấp. Các tham số của mô hình được huấn luyện cũng gần với giá trị thật \(w = [5, 1.2, -3.4, 5.6]\).

# Pick the first four dimensions, i.e., 1, x, x^2, x^3 from the polynomial
# features
train(poly_features[:n_train, 0:4], poly_features[n_train:, 0:4],
      labels[:n_train], labels[n_train:])
weight: [[ 5.015699   1.1957837 -3.4175875  5.619841 ]]
../_images/output_underfit-overfit_vn_553d16_11_1.svg

4.4.4.4. Khớp hàm tuyến tính (Dưới khớp)

Hãy xem lại việc khớp hàm tuyến tính. Sau sự sụt giảm ở những epoch đầu, việc giảm thêm lỗi huấn luyện của mô hình đã trở nên khó khăn. Sau khi epoch cuối cùng kết thúc, lỗi huấn luyện vẫn còn cao. Khi được sử dụng để khớp các khuôn mẫu phi tuyến (như hàm đa thức bậc ba trong trường hợp này), các mô hình tuyến tính dễ bị dưới khớp.

# Pick the first four dimensions, i.e., 1, x from the polynomial features
train(poly_features[:n_train, 0:3], poly_features[n_train:, 0:3],
      labels[:n_train], labels[n_train:])
weight: [[ 5.2660666  4.024163  -3.9857814]]
../_images/output_underfit-overfit_vn_553d16_13_1.svg

4.4.4.5. Thiếu dữ liệu huấn luyện (Quá khớp)

Bây giờ, hãy thử huấn luyện mô hình sử dụng một đa thức với bậc rất cao. Trong trường hợp này, mô hình không có đủ dữ liệu để học được rằng các hệ số bậc cao nên có giá trị gần với không. Vì vậy, mô hình quá phức tạp của ta sẽ dễ bị ảnh hưởng bởi nhiễu ở trong dữ liệu huấn luyện. Dĩ nhiên, lỗi huấn luyện trong trường hợp này sẽ thấp (thậm chí còn thấp hơn cả khi chúng ta có được mô hình thích hợp!) nhưng lỗi kiểm tra sẽ cao.

Thử nghiệm với các độ phức tạp của mô hình (n_degree) và các kích thước của tập huấn luyện (n_subset) khác nhau để thấy được một cách trực quan điều gì đang diễn ra.

n_subset = 100  # Subset of data to train on
n_degree = 20  # Degree of polynomials
train(poly_features[1:n_subset, 0:n_degree],
      poly_features[n_train:, 0:n_degree], labels[1:n_subset],
      labels[n_train:])
weight: [[ 4.9481597   1.3327131  -3.2095444   5.042951   -0.42211843  1.3476641
   0.07500532  0.19182673 -0.01919946  0.01772289 -0.05095179 -0.02382807
  -0.01497361 -0.04940026  0.06389722 -0.0476184  -0.04380194 -0.05188226
   0.05655775  0.01104914]]
../_images/output_underfit-overfit_vn_553d16_15_1.svg

Ở các chương sau, chúng ta sẽ tiếp tục thảo luận về các vấn đề quá khớp và các phương pháp đối phó, ví dụ như suy giảm trọng số hay dropout.

4.4.5. Tóm tắt

  • Bởi vì lỗi khái quát không thể được ước lượng dựa trên lỗi huấn luyện, nên việc chỉ đơn thuần cực tiểu hóa lỗi huấn luyện sẽ không nhất thiết đồng nghĩa với việc cực tiểu hóa lỗi khái quát. Các mô hình học máy cần phải được bảo vệ khỏi việc quá khớp để giảm thiểu lỗi khái quát.
  • Một tập kiểm định có thể được sử dụng cho việc lựa chọn mô hình (với điều kiện là tập này không được sử dụng quá nhiều).
  • Dưới khớp có nghĩa là mô hình không có khả năng giảm lỗi huấn luyện, còn quá khớp là kết quả của việc lỗi huấn luyện của mô hình thấp hơn nhiều so với lỗi kiểm tra.
  • Chúng ta nên chọn một mô hình phức tạp vừa phải và tránh việc sử dụng tập huấn luyện không có đủ số số mẫu.

4.4.6. Bài tập

  1. Bạn có thể giải bài toán hồi quy đa thức một cách chính xác không? Gợi ý: sử dụng đại số tuyến tính.
  2. Lựa chọn mô hình cho các đa thức
    • Vẽ đồ thị biểu diễn lỗi huấn luyện và độ phức tạp của mô hình (bậc của đa thức). Bạn quan sát được gì?
    • Vẽ đồ thị biểu diễn lỗi kiểm tra trong trường hợp này.
    • Tạo một đồ thị tương tự nhưng với hàm của lượng dữ liệu.
  3. Điều gì sẽ xảy ra nếu bạn bỏ qua việc chuẩn hóa các đặc trưng đa thức \(x^i\) với \(1/i!\). Bạn có thể sửa chữa vấn đề này bằng cách nào khác không?
  4. Bậc mấy của đa thức giảm được tỉ lệ lỗi huấn luyện về 0?
  5. Bạn có bao giờ kỳ vọng thấy được lỗi khái quát bằng 0?

4.4.7. Thảo luận

4.4.8. Những người thực hiện

Bản dịch trong trang này được thực hiện bởi:

  • Đoàn Võ Duy Thanh
  • Trần Yến Thy
  • Lê Khắc Hồng Phúc
  • Phạm Minh Đức
  • Nguyễn Văn Tâm
  • Vũ Hữu Tiệp
  • Phạm Hồng Vinh
  • Bùi Nhật Quân
  • Lý Phi Long
  • Nguyễn Duy Du