{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## สร้างโมเดล Logistic Regression - บทเรียนที่ 4\n", "\n", "![ภาพประกอบ Logistic vs. Linear Regression](../../../../../../2-Regression/4-Logistic/images/linear-vs-logistic.png)\n", "\n", "#### **[แบบทดสอบก่อนเรียน](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/15/)**\n", "\n", "#### บทนำ\n", "\n", "ในบทเรียนสุดท้ายเกี่ยวกับ Regression ซึ่งเป็นหนึ่งในเทคนิคพื้นฐานของ *คลาสสิก* ML เราจะมาดู Logistic Regression กัน คุณสามารถใช้เทคนิคนี้เพื่อค้นหารูปแบบในการทำนายหมวดหมู่แบบไบนารี ตัวอย่างเช่น ลูกอมนี้เป็นช็อกโกแลตหรือไม่? โรคนี้ติดต่อหรือไม่? ลูกค้าคนนี้จะเลือกสินค้านี้หรือไม่?\n", "\n", "ในบทเรียนนี้ คุณจะได้เรียนรู้:\n", "\n", "- เทคนิคสำหรับ Logistic Regression\n", "\n", "✅ เพิ่มความเข้าใจเกี่ยวกับการทำงานกับ Regression ประเภทนี้ใน [โมดูลการเรียนรู้](https://learn.microsoft.com/training/modules/introduction-classification-models/?WT.mc_id=academic-77952-leestott)\n", "\n", "## ความต้องการเบื้องต้น\n", "\n", "หลังจากที่เราได้ทำงานกับข้อมูลฟักทองมาแล้ว ตอนนี้เราคุ้นเคยกับมันมากพอที่จะสังเกตเห็นว่ามีหมวดหมู่แบบไบนารีที่เราสามารถทำงานด้วยได้: `Color`\n", "\n", "เรามาสร้างโมเดล Logistic Regression เพื่อทำนายว่า *ฟักทองที่กำหนดมีแนวโน้มที่จะมีสีอะไร* (สีส้ม 🎃 หรือสีขาว 👻)\n", "\n", "> ทำไมเราถึงพูดถึงการจัดหมวดหมู่แบบไบนารีในบทเรียนที่เกี่ยวกับ Regression? ก็เพื่อความสะดวกทางภาษาเท่านั้น เพราะ Logistic Regression นั้น [จริงๆ แล้วเป็นวิธีการจัดหมวดหมู่](https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression) แม้ว่าจะอิงตามเส้นตรงก็ตาม เรียนรู้วิธีอื่นๆ ในการจัดหมวดหมู่ข้อมูลในกลุ่มบทเรียนถัดไป\n", "\n", "สำหรับบทเรียนนี้ เราจะต้องใช้แพ็กเกจดังต่อไปนี้:\n", "\n", "- `tidyverse`: [tidyverse](https://www.tidyverse.org/) คือ [ชุดของแพ็กเกจ R](https://www.tidyverse.org/packages) ที่ออกแบบมาเพื่อทำให้วิทยาศาสตร์ข้อมูลเร็วขึ้น ง่ายขึ้น และสนุกขึ้น!\n", "\n", "- `tidymodels`: [tidymodels](https://www.tidymodels.org/) เป็นกรอบงานที่เป็น [ชุดของแพ็กเกจ](https://www.tidymodels.org/packages/) สำหรับการสร้างโมเดลและการเรียนรู้ของเครื่อง\n", "\n", "- `janitor`: [แพ็กเกจ janitor](https://github.com/sfirke/janitor) มีเครื่องมือเล็กๆ ที่เรียบง่ายสำหรับการตรวจสอบและทำความสะอาดข้อมูลที่ไม่สมบูรณ์\n", "\n", "- `ggbeeswarm`: [แพ็กเกจ ggbeeswarm](https://github.com/eclarke/ggbeeswarm) มีวิธีการสร้างกราฟแบบ beeswarm โดยใช้ ggplot2\n", "\n", "คุณสามารถติดตั้งแพ็กเกจเหล่านี้ได้ด้วยคำสั่ง:\n", "\n", "`install.packages(c(\"tidyverse\", \"tidymodels\", \"janitor\", \"ggbeeswarm\"))`\n", "\n", "หรือใช้สคริปต์ด้านล่างเพื่อตรวจสอบว่าคุณมีแพ็กเกจที่จำเป็นสำหรับบทเรียนนี้หรือไม่ และติดตั้งให้ในกรณีที่ยังไม่มี\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "suppressWarnings(if (!require(\"pacman\"))install.packages(\"pacman\"))\n", "\n", "pacman::p_load(tidyverse, tidymodels, janitor, ggbeeswarm)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **กำหนดคำถาม**\n", "\n", "สำหรับจุดประสงค์ของเรา เราจะกำหนดคำถามในรูปแบบไบนารี: 'สีขาว' หรือ 'ไม่ใช่สีขาว' ในชุดข้อมูลของเรายังมีหมวดหมู่ 'ลาย' อยู่ด้วย แต่มีตัวอย่างในหมวดหมู่นี้น้อยมาก ดังนั้นเราจะไม่ใช้มัน และมันจะหายไปเมื่อเราลบค่าที่เป็น null ออกจากชุดข้อมูลอยู่แล้ว\n", "\n", "> 🎃 ข้อเท็จจริงสนุกๆ บางครั้งเราจะเรียกฟักทองสีขาวว่า 'ฟักทองผี' ฟักทองเหล่านี้แกะสลักได้ยากกว่า จึงไม่ได้รับความนิยมเท่าฟักทองสีส้ม แต่ก็ดูเท่ดี! ดังนั้นเราสามารถปรับคำถามของเราใหม่ได้ว่า: 'ฟักทองผี' หรือ 'ไม่ใช่ฟักทองผี' 👻\n", "\n", "## **เกี่ยวกับการถดถอยโลจิสติก**\n", "\n", "การถดถอยโลจิสติกแตกต่างจากการถดถอยเชิงเส้นที่คุณเคยเรียนรู้มาก่อนในหลายๆ แง่มุมที่สำคัญ\n", "\n", "#### **การจัดประเภทแบบไบนารี**\n", "\n", "การถดถอยโลจิสติกไม่ได้มีคุณสมบัติเหมือนกับการถดถอยเชิงเส้น การถดถอยโลจิสติกให้การคาดการณ์เกี่ยวกับ `หมวดหมู่แบบไบนารี` (\"สีส้มหรือไม่ใช่สีส้ม\") ในขณะที่การถดถอยเชิงเส้นสามารถคาดการณ์ `ค่าต่อเนื่อง` ได้ เช่น จากแหล่งที่มาของฟักทองและเวลาที่เก็บเกี่ยว *ราคาของมันจะเพิ่มขึ้นเท่าไร*\n", "\n", "![อินโฟกราฟิกโดย Dasani Madipalli](../../../../../../2-Regression/4-Logistic/images/pumpkin-classifier.png)\n", "\n", "### การจัดประเภทอื่นๆ\n", "\n", "ยังมีการถดถอยโลจิสติกประเภทอื่นๆ เช่น มัลติโนเมียลและออร์ดินัล:\n", "\n", "- **มัลติโนเมียล** ซึ่งเกี่ยวข้องกับการมีมากกว่าหนึ่งหมวดหมู่ - \"สีส้ม, สีขาว, และลาย\"\n", "\n", "- **ออร์ดินัล** ซึ่งเกี่ยวข้องกับหมวดหมู่ที่มีลำดับ เหมาะสมหากเราต้องการจัดลำดับผลลัพธ์อย่างมีตรรกะ เช่น ฟักทองของเราที่จัดลำดับตามขนาดที่มีจำนวนจำกัด (เล็กมาก, เล็ก, กลาง, ใหญ่, ใหญ่มาก, ใหญ่ที่สุด)\n", "\n", "![การถดถอยมัลติโนเมียล vs ออร์ดินัล](../../../../../../2-Regression/4-Logistic/images/multinomial-vs-ordinal.png)\n", "\n", "#### **ตัวแปรไม่จำเป็นต้องมีความสัมพันธ์กัน**\n", "\n", "จำได้ไหมว่าการถดถอยเชิงเส้นทำงานได้ดีขึ้นเมื่อมีตัวแปรที่มีความสัมพันธ์กันมากขึ้น? การถดถอยโลจิสติกตรงกันข้าม - ตัวแปรไม่จำเป็นต้องสอดคล้องกัน ซึ่งเหมาะกับข้อมูลนี้ที่มีความสัมพันธ์ค่อนข้างอ่อน\n", "\n", "#### **คุณต้องการข้อมูลที่สะอาดและมากพอ**\n", "\n", "การถดถอยโลจิสติกจะให้ผลลัพธ์ที่แม่นยำมากขึ้นหากคุณใช้ข้อมูลจำนวนมาก ชุดข้อมูลขนาดเล็กของเราไม่เหมาะสมที่สุดสำหรับงานนี้ ดังนั้นโปรดคำนึงถึงข้อนี้\n", "\n", "✅ ลองคิดถึงประเภทของข้อมูลที่เหมาะสมกับการถดถอยโลจิสติก\n", "\n", "## แบบฝึกหัด - จัดระเบียบข้อมูล\n", "\n", "ก่อนอื่น ทำความสะอาดข้อมูลเล็กน้อย โดยลบค่าที่เป็น null และเลือกเฉพาะบางคอลัมน์:\n", "\n", "1. เพิ่มโค้ดต่อไปนี้:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Load the core tidyverse packages\n", "library(tidyverse)\n", "\n", "# Import the data and clean column names\n", "pumpkins <- read_csv(file = \"https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/2-Regression/data/US-pumpkins.csv\") %>% \n", " clean_names()\n", "\n", "# Select desired columns\n", "pumpkins_select <- pumpkins %>% \n", " select(c(city_name, package, variety, origin, item_size, color)) \n", "\n", "# Drop rows containing missing values and encode color as factor (category)\n", "pumpkins_select <- pumpkins_select %>% \n", " drop_na() %>% \n", " mutate(color = factor(color))\n", "\n", "# View the first few rows\n", "pumpkins_select %>% \n", " slice_head(n = 5)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "คุณสามารถดูข้อมูลใน dataframe ใหม่ของคุณได้เสมอ โดยใช้ฟังก์ชัน [*glimpse()*](https://pillar.r-lib.org/reference/glimpse.html) ดังตัวอย่างด้านล่าง:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "pumpkins_select %>% \n", " glimpse()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "เรามายืนยันกันว่าเรากำลังทำปัญหาการจำแนกประเภทแบบไบนารีจริงๆ:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Subset distinct observations in outcome column\n", "pumpkins_select %>% \n", " distinct(color)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### การแสดงผล - แผนภูมิประเภทหมวดหมู่\n", "ตอนนี้คุณได้โหลดข้อมูลฟักทองขึ้นมาอีกครั้งและทำการทำความสะอาดเพื่อให้ได้ชุดข้อมูลที่มีตัวแปรบางตัว รวมถึงตัวแปร Color มาลองแสดงผล dataframe ในโน้ตบุ๊กโดยใช้ไลบรารี ggplot กัน\n", "\n", "ไลบรารี ggplot มีวิธีที่น่าสนใจในการแสดงผลข้อมูลของคุณ ตัวอย่างเช่น คุณสามารถเปรียบเทียบการกระจายตัวของข้อมูลสำหรับแต่ละ Variety และ Color ในแผนภูมิประเภทหมวดหมู่ได้\n", "\n", "1. สร้างแผนภูมิประเภทนี้โดยใช้ฟังก์ชัน geombar โดยใช้ข้อมูลฟักทองของเรา และกำหนดการจับคู่สีสำหรับแต่ละหมวดหมู่ของฟักทอง (สีส้มหรือสีขาว):\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "python" } }, "outputs": [], "source": [ "# Specify colors for each value of the hue variable\n", "palette <- c(ORANGE = \"orange\", WHITE = \"wheat\")\n", "\n", "# Create the bar plot\n", "ggplot(pumpkins_select, aes(y = variety, fill = color)) +\n", " geom_bar(position = \"dodge\") +\n", " scale_fill_manual(values = palette) +\n", " labs(y = \"Variety\", fill = \"Color\") +\n", " theme_minimal()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "จากการสังเกตข้อมูล คุณสามารถเห็นได้ว่า ข้อมูลสีมีความสัมพันธ์กับชนิดพันธุ์อย่างไร\n", "\n", "✅ จากกราฟประเภทนี้ มีการสำรวจที่น่าสนใจอะไรบ้างที่คุณสามารถจินตนาการได้?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### การเตรียมข้อมูล: การเข้ารหัสคุณลักษณะ\n", "\n", "ชุดข้อมูลฟักทองของเรามีค่าที่เป็นสตริงในทุกคอลัมน์ การทำงานกับข้อมูลประเภทหมวดหมู่เป็นเรื่องง่ายสำหรับมนุษย์ แต่ไม่ใช่สำหรับเครื่องจักร อัลกอริธึมการเรียนรู้ของเครื่องทำงานได้ดีเมื่อใช้ตัวเลข นั่นเป็นเหตุผลว่าทำไมการเข้ารหัสจึงเป็นขั้นตอนสำคัญในกระบวนการเตรียมข้อมูล เนื่องจากช่วยให้เราสามารถเปลี่ยนข้อมูลประเภทหมวดหมู่ให้เป็นข้อมูลเชิงตัวเลขโดยไม่สูญเสียข้อมูล การเข้ารหัสที่ดีนำไปสู่การสร้างโมเดลที่ดี\n", "\n", "สำหรับการเข้ารหัสคุณลักษณะ มีตัวเข้ารหัสหลักสองประเภท:\n", "\n", "1. **Ordinal encoder**: เหมาะสำหรับตัวแปรประเภทลำดับ (ordinal variables) ซึ่งเป็นตัวแปรหมวดหมู่ที่ข้อมูลมีการเรียงลำดับอย่างมีเหตุผล เช่น คอลัมน์ `item_size` ในชุดข้อมูลของเรา มันจะสร้างการจับคู่ที่แต่ละหมวดหมู่ถูกแทนด้วยตัวเลข ซึ่งตัวเลขนั้นแสดงถึงลำดับของหมวดหมู่ในคอลัมน์\n", "\n", "2. **Categorical encoder**: เหมาะสำหรับตัวแปรประเภทนามธรรม (nominal variables) ซึ่งเป็นตัวแปรหมวดหมู่ที่ข้อมูลไม่มีการเรียงลำดับอย่างมีเหตุผล เช่น คุณลักษณะทั้งหมดที่แตกต่างจาก `item_size` ในชุดข้อมูลของเรา มันใช้การเข้ารหัสแบบ one-hot ซึ่งหมายความว่าแต่ละหมวดหมู่จะถูกแทนด้วยคอลัมน์ไบนารี: ตัวแปรที่ถูกเข้ารหัสจะเท่ากับ 1 หากฟักทองนั้นอยู่ใน Variety นั้น และเท่ากับ 0 หากไม่ใช่\n", "\n", "Tidymodels มีอีกหนึ่งแพ็กเกจที่น่าสนใจ: [recipes](https://recipes.tidymodels.org/) - แพ็กเกจสำหรับการเตรียมข้อมูล เราจะกำหนด `recipe` ที่ระบุว่าคอลัมน์ตัวทำนายทั้งหมดควรถูกเข้ารหัสเป็นชุดของตัวเลข จากนั้น `prep` เพื่อประมาณค่าปริมาณและสถิติที่จำเป็นสำหรับการดำเนินการใด ๆ และสุดท้าย `bake` เพื่อใช้การคำนวณกับข้อมูลใหม่\n", "\n", "> โดยปกติ recipes มักถูกใช้เป็นตัวเตรียมข้อมูลสำหรับการสร้างโมเดล ซึ่งมันจะกำหนดว่าควรมีการดำเนินการใดบ้างกับชุดข้อมูลเพื่อเตรียมให้พร้อมสำหรับการสร้างโมเดล ในกรณีนี้ **แนะนำอย่างยิ่ง** ให้คุณใช้ `workflow()` แทนการประมาณ recipe ด้วย prep และ bake ด้วยตนเอง เราจะเห็นทั้งหมดนี้ในอีกสักครู่\n", ">\n", "> อย่างไรก็ตาม สำหรับตอนนี้ เรากำลังใช้ recipes + prep + bake เพื่อระบุว่าควรมีการดำเนินการใดบ้างกับชุดข้อมูลเพื่อเตรียมให้พร้อมสำหรับการวิเคราะห์ข้อมูล และจากนั้นดึงข้อมูลที่ผ่านการเตรียมพร้อมแล้วพร้อมกับขั้นตอนที่ได้ดำเนินการ\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Preprocess and extract data to allow some data analysis\n", "baked_pumpkins <- recipe(color ~ ., data = pumpkins_select) %>%\n", " # Define ordering for item_size column\n", " step_mutate(item_size = ordered(item_size, levels = c('sml', 'med', 'med-lge', 'lge', 'xlge', 'jbo', 'exjbo'))) %>%\n", " # Convert factors to numbers using the order defined above (Ordinal encoding)\n", " step_integer(item_size, zero_based = F) %>%\n", " # Encode all other predictors using one hot encoding\n", " step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE) %>%\n", " prep(data = pumpkin_select) %>%\n", " bake(new_data = NULL)\n", "\n", "# Display the first few rows of preprocessed data\n", "baked_pumpkins %>% \n", " slice_head(n = 5)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "✅ ข้อดีของการใช้ ordinal encoder กับคอลัมน์ Item Size คืออะไร?\n", "\n", "### วิเคราะห์ความสัมพันธ์ระหว่างตัวแปร\n", "\n", "เมื่อเราได้ทำการเตรียมข้อมูลเบื้องต้นแล้ว เราสามารถวิเคราะห์ความสัมพันธ์ระหว่างฟีเจอร์และป้ายกำกับ (label) เพื่อทำความเข้าใจว่าโมเดลจะสามารถทำนายป้ายกำกับจากฟีเจอร์ได้ดีแค่ไหน วิธีที่ดีที่สุดในการวิเคราะห์ประเภทนี้คือการสร้างกราฟ \n", "เราจะใช้ฟังก์ชัน ggplot geom_boxplot_ อีกครั้ง เพื่อแสดงความสัมพันธ์ระหว่าง Item Size, Variety และ Color ในกราฟแบบหมวดหมู่ (categorical plot) เพื่อให้การแสดงผลข้อมูลดียิ่งขึ้น เราจะใช้คอลัมน์ Item Size ที่ถูกเข้ารหัสแล้ว และคอลัมน์ Variety ที่ยังไม่ได้เข้ารหัส\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Define the color palette\n", "palette <- c(ORANGE = \"orange\", WHITE = \"wheat\")\n", "\n", "# We need the encoded Item Size column to use it as the x-axis values in the plot\n", "pumpkins_select_plot<-pumpkins_select\n", "pumpkins_select_plot$item_size <- baked_pumpkins$item_size\n", "\n", "# Create the grouped box plot\n", "ggplot(pumpkins_select_plot, aes(x = `item_size`, y = color, fill = color)) +\n", " geom_boxplot() +\n", " facet_grid(variety ~ ., scales = \"free_x\") +\n", " scale_fill_manual(values = palette) +\n", " labs(x = \"Item Size\", y = \"\") +\n", " theme_minimal() +\n", " theme(strip.text = element_text(size = 12)) +\n", " theme(axis.text.x = element_text(size = 10)) +\n", " theme(axis.title.x = element_text(size = 12)) +\n", " theme(axis.title.y = element_blank()) +\n", " theme(legend.position = \"bottom\") +\n", " guides(fill = guide_legend(title = \"Color\")) +\n", " theme(panel.spacing = unit(0.5, \"lines\"))+\n", " theme(strip.text.y = element_text(size = 4, hjust = 0)) \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ใช้ swarm plot\n", "\n", "เนื่องจาก Color เป็นหมวดหมู่แบบไบนารี (สีขาวหรือไม่ใช่สีขาว) จึงต้องใช้ '[วิธีการเฉพาะทาง](https://github.com/rstudio/cheatsheets/blob/main/data-visualization.pdf)' ในการแสดงผลข้อมูล\n", "\n", "ลองใช้ `swarm plot` เพื่อแสดงการกระจายของสีในความสัมพันธ์กับ item_size\n", "\n", "เราจะใช้ [ggbeeswarm package](https://github.com/eclarke/ggbeeswarm) ซึ่งมีวิธีการสร้างกราฟแบบ beeswarm โดยใช้ ggplot2 กราฟ beeswarm เป็นวิธีการแสดงจุดข้อมูลที่ปกติจะทับซ้อนกันให้อยู่ข้างกันแทน\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Create beeswarm plots of color and item_size\n", "baked_pumpkins %>% \n", " mutate(color = factor(color)) %>% \n", " ggplot(mapping = aes(x = color, y = item_size, color = color)) +\n", " geom_quasirandom() +\n", " scale_color_brewer(palette = \"Dark2\", direction = -1) +\n", " theme(legend.position = \"none\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ตอนนี้เรามีความเข้าใจเกี่ยวกับความสัมพันธ์ระหว่างหมวดหมู่แบบทวิภาคของสีและกลุ่มขนาดที่ใหญ่ขึ้นแล้ว ลองมาสำรวจการใช้โลจิสติกรีเกรสชันเพื่อกำหนดสีที่เป็นไปได้ของฟักทองแต่ละลูกกัน\n", "\n", "## สร้างโมเดลของคุณ\n", "\n", "เลือกตัวแปรที่คุณต้องการใช้ในโมเดลการจำแนกประเภท และแบ่งข้อมูลออกเป็นชุดฝึกอบรมและชุดทดสอบ [rsample](https://rsample.tidymodels.org/) ซึ่งเป็นแพ็กเกจใน Tidymodels มีโครงสร้างพื้นฐานสำหรับการแบ่งข้อมูลและการสุ่มตัวอย่างซ้ำอย่างมีประสิทธิภาพ:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Split data into 80% for training and 20% for testing\n", "set.seed(2056)\n", "pumpkins_split <- pumpkins_select %>% \n", " initial_split(prop = 0.8)\n", "\n", "# Extract the data in each split\n", "pumpkins_train <- training(pumpkins_split)\n", "pumpkins_test <- testing(pumpkins_split)\n", "\n", "# Print out the first 5 rows of the training set\n", "pumpkins_train %>% \n", " slice_head(n = 5)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "🙌 เราพร้อมแล้วที่จะฝึกโมเดลโดยการปรับคุณสมบัติการฝึกให้เข้ากับป้ายกำกับการฝึก (สี)\n", "\n", "เราจะเริ่มต้นด้วยการสร้างสูตรที่ระบุขั้นตอนการเตรียมข้อมูลที่ควรดำเนินการเพื่อเตรียมข้อมูลให้พร้อมสำหรับการสร้างโมเดล เช่น การเข้ารหัสตัวแปรประเภทให้เป็นชุดของตัวเลข เช่นเดียวกับ `baked_pumpkins` เราสร้าง `pumpkins_recipe` แต่จะไม่ใช้ `prep` และ `bake` เนื่องจากจะถูกรวมไว้ในเวิร์กโฟลว์ ซึ่งคุณจะได้เห็นในอีกไม่กี่ขั้นตอนจากนี้\n", "\n", "มีหลายวิธีในการระบุโมเดลการถดถอยโลจิสติกใน Tidymodels ดูที่ `?logistic_reg()` สำหรับตอนนี้ เราจะระบุโมเดลการถดถอยโลจิสติกผ่านเครื่องยนต์เริ่มต้น `stats::glm()`\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Create a recipe that specifies preprocessing steps for modelling\n", "pumpkins_recipe <- recipe(color ~ ., data = pumpkins_train) %>% \n", " step_mutate(item_size = ordered(item_size, levels = c('sml', 'med', 'med-lge', 'lge', 'xlge', 'jbo', 'exjbo'))) %>%\n", " step_integer(item_size, zero_based = F) %>% \n", " step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)\n", "\n", "# Create a logistic model specification\n", "log_reg <- logistic_reg() %>% \n", " set_engine(\"glm\") %>% \n", " set_mode(\"classification\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ตอนนี้เรามีสูตรและสเปคของโมเดลแล้ว เราจำเป็นต้องหาวิธีรวมสิ่งเหล่านี้เข้าด้วยกันเป็นวัตถุหนึ่ง ที่จะช่วยจัดการการเตรียมข้อมูล (prep+bake เบื้องหลัง) ฝึกโมเดลด้วยข้อมูลที่ผ่านการเตรียมแล้ว และยังรองรับกิจกรรมการประมวลผลหลังการฝึกโมเดลได้อีกด้วย\n", "\n", "ใน Tidymodels วัตถุที่สะดวกนี้เรียกว่า [`workflow`](https://workflows.tidymodels.org/) ซึ่งช่วยจัดเก็บองค์ประกอบการสร้างโมเดลของคุณได้อย่างสะดวกสบาย\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Bundle modelling components in a workflow\n", "log_reg_wf <- workflow() %>% \n", " add_recipe(pumpkins_recipe) %>% \n", " add_model(log_reg)\n", "\n", "# Print out the workflow\n", "log_reg_wf\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "หลังจากที่กำหนด *workflow* แล้ว สามารถ `train` โมเดลได้โดยใช้ฟังก์ชัน [`fit()`](https://tidymodels.github.io/parsnip/reference/fit.html) ฟังก์ชันนี้จะช่วยประเมินสูตรและเตรียมข้อมูลก่อนการฝึกโมเดล ดังนั้นเราไม่จำเป็นต้องทำการเตรียมข้อมูลด้วย prep และ bake ด้วยตัวเอง\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Train the model\n", "wf_fit <- log_reg_wf %>% \n", " fit(data = pumpkins_train)\n", "\n", "# Print the trained workflow\n", "wf_fit\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "โมเดลจะแสดงค่าสัมประสิทธิ์ที่ได้จากการฝึกสอนระหว่างการเทรน\n", "\n", "ตอนนี้เราได้ฝึกสอนโมเดลด้วยข้อมูลการฝึกสอนเรียบร้อยแล้ว เราสามารถใช้โมเดลนี้เพื่อทำนายผลบนข้อมูลทดสอบได้โดยใช้ [parsnip::predict()](https://parsnip.tidymodels.org/reference/predict.model_fit.html) มาเริ่มต้นด้วยการใช้โมเดลเพื่อทำนายป้ายกำกับสำหรับชุดข้อมูลทดสอบ และความน่าจะเป็นของแต่ละป้ายกำกับกัน เมื่อความน่าจะเป็นมากกว่า 0.5 ป้ายกำกับที่ทำนายจะเป็น `WHITE` หากน้อยกว่านั้นจะเป็น `ORANGE`\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Make predictions for color and corresponding probabilities\n", "results <- pumpkins_test %>% select(color) %>% \n", " bind_cols(wf_fit %>% \n", " predict(new_data = pumpkins_test)) %>%\n", " bind_cols(wf_fit %>%\n", " predict(new_data = pumpkins_test, type = \"prob\"))\n", "\n", "# Compare predictions\n", "results %>% \n", " slice_head(n = 10)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "เยี่ยมมาก! นี่ช่วยให้เข้าใจการทำงานของโลจิสติกรีเกรสชันได้ลึกซึ้งยิ่งขึ้น\n", "\n", "### เข้าใจได้ดีขึ้นผ่านเมทริกซ์ความสับสน\n", "\n", "การเปรียบเทียบแต่ละการทำนายกับค่าจริงที่สอดคล้องกัน (\"ground truth\") ไม่ใช่วิธีที่มีประสิทธิภาพนักในการประเมินว่ารุ่นทำนายได้ดีแค่ไหน โชคดีที่ Tidymodels มีเครื่องมือเพิ่มเติมที่ช่วยได้: [`yardstick`](https://yardstick.tidymodels.org/) - แพ็กเกจที่ใช้วัดประสิทธิภาพของโมเดลด้วยเมตริกประสิทธิภาพ\n", "\n", "หนึ่งในเมตริกประสิทธิภาพที่เกี่ยวข้องกับปัญหาการจำแนกประเภทคือ [`confusion matrix`](https://wikipedia.org/wiki/Confusion_matrix) เมทริกซ์ความสับสนอธิบายว่ารุ่นการจำแนกประเภททำงานได้ดีเพียงใด โดยเมทริกซ์ความสับสนจะจัดทำตารางว่าตัวอย่างในแต่ละคลาสถูกจำแนกอย่างถูกต้องโดยโมเดลกี่ตัวอย่าง ในกรณีของเรา มันจะแสดงให้คุณเห็นว่าฟักทองสีส้มถูกจำแนกเป็นสีส้มกี่ลูก และฟักทองสีขาวถูกจำแนกเป็นสีขาวกี่ลูก นอกจากนี้ เมทริกซ์ความสับสนยังแสดงให้เห็นว่ามีการจำแนกไปยังหมวดหมู่ที่ **ผิด** กี่ตัวอย่างด้วย\n", "\n", "ฟังก์ชัน [**`conf_mat()`**](https://tidymodels.github.io/yardstick/reference/conf_mat.html) จาก yardstick ใช้คำนวณการจัดตารางไขว้ระหว่างคลาสที่สังเกตได้และคลาสที่ทำนาย\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Confusion matrix for prediction results\n", "conf_mat(data = results, truth = color, estimate = .pred_class)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "มาทำความเข้าใจเกี่ยวกับ confusion matrix กัน โมเดลของเราถูกขอให้จำแนกฟักทองออกเป็นสองประเภท คือประเภท `white` และประเภท `not-white`\n", "\n", "- หากโมเดลของคุณทำนายว่าฟักทองเป็นสีขาว และในความเป็นจริงมันอยู่ในประเภท 'white' เราเรียกสิ่งนี้ว่า `true positive` ซึ่งแสดงด้วยตัวเลขด้านบนซ้าย\n", "\n", "- หากโมเดลของคุณทำนายว่าฟักทองไม่ใช่สีขาว และในความเป็นจริงมันอยู่ในประเภท 'white' เราเรียกสิ่งนี้ว่า `false negative` ซึ่งแสดงด้วยตัวเลขด้านล่างซ้าย\n", "\n", "- หากโมเดลของคุณทำนายว่าฟักทองเป็นสีขาว และในความเป็นจริงมันอยู่ในประเภท 'not-white' เราเรียกสิ่งนี้ว่า `false positive` ซึ่งแสดงด้วยตัวเลขด้านบนขวา\n", "\n", "- หากโมเดลของคุณทำนายว่าฟักทองไม่ใช่สีขาว และในความเป็นจริงมันอยู่ในประเภท 'not-white' เราเรียกสิ่งนี้ว่า `true negative` ซึ่งแสดงด้วยตัวเลขด้านล่างขวา\n", "\n", "| ความจริง |\n", "|:-----:|\n", "\n", "\n", "| | | |\n", "|---------------|--------|-------|\n", "| **ทำนาย** | WHITE | ORANGE |\n", "| WHITE | TP | FP |\n", "| ORANGE | FN | TN |\n", "\n", "อย่างที่คุณอาจเดาได้ว่า เราต้องการให้มีจำนวน true positive และ true negative มากขึ้น และจำนวน false positive และ false negative น้อยลง ซึ่งหมายความว่าโมเดลทำงานได้ดีขึ้น\n", "\n", "confusion matrix มีประโยชน์เพราะมันช่วยให้เราสามารถคำนวณตัวชี้วัดอื่น ๆ ที่ช่วยประเมินประสิทธิภาพของโมเดลการจำแนกประเภทได้ดียิ่งขึ้น มาดูตัวชี้วัดเหล่านี้กัน:\n", "\n", "🎓 Precision: `TP/(TP + FP)` หมายถึงสัดส่วนของผลลัพธ์ที่ทำนายว่าเป็นบวกที่เป็นบวกจริง ๆ หรือที่เรียกว่า [positive predictive value](https://en.wikipedia.org/wiki/Positive_predictive_value \"Positive predictive value\")\n", "\n", "🎓 Recall: `TP/(TP + FN)` หมายถึงสัดส่วนของผลลัพธ์ที่เป็นบวกจากจำนวนตัวอย่างที่เป็นบวกจริง ๆ หรือที่เรียกว่า `sensitivity`\n", "\n", "🎓 Specificity: `TN/(TN + FP)` หมายถึงสัดส่วนของผลลัพธ์ที่เป็นลบจากจำนวนตัวอย่างที่เป็นลบจริง ๆ\n", "\n", "🎓 Accuracy: `TP + TN/(TP + TN + FP + FN)` เปอร์เซ็นต์ของป้ายกำกับที่ทำนายได้อย่างถูกต้องสำหรับตัวอย่าง\n", "\n", "🎓 F Measure: ค่าเฉลี่ยถ่วงน้ำหนักระหว่าง precision และ recall โดยค่าที่ดีที่สุดคือ 1 และค่าที่แย่ที่สุดคือ 0\n", "\n", "มาคำนวณตัวชี้วัดเหล่านี้กัน!\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Combine metric functions and calculate them all at once\n", "eval_metrics <- metric_set(ppv, recall, spec, f_meas, accuracy)\n", "eval_metrics(data = results, truth = color, estimate = .pred_class)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## แสดงกราฟ ROC ของโมเดลนี้\n", "\n", "มาทำการแสดงผลอีกครั้งเพื่อดูสิ่งที่เรียกว่า [`กราฟ ROC`](https://en.wikipedia.org/wiki/Receiver_operating_characteristic):\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Make a roc_curve\n", "results %>% \n", " roc_curve(color, .pred_ORANGE) %>% \n", " autoplot()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "กราฟ ROC มักถูกใช้เพื่อดูผลลัพธ์ของตัวจำแนกในแง่ของค่าบวกจริง (True Positives) เทียบกับค่าบวกเท็จ (False Positives) กราฟ ROC โดยทั่วไปจะแสดง `True Positive Rate`/Sensitivity บนแกน Y และ `False Positive Rate`/1-Specificity บนแกน X ดังนั้น ความชันของกราฟและพื้นที่ระหว่างเส้นกลางกับกราฟจึงมีความสำคัญ: คุณต้องการกราฟที่พุ่งขึ้นและข้ามเส้นไปอย่างรวดเร็ว ในกรณีของเรา มีค่าบวกเท็จในช่วงเริ่มต้น และจากนั้นเส้นก็พุ่งขึ้นและข้ามเส้นไปอย่างเหมาะสม\n", "\n", "สุดท้ายนี้ เรามาใช้ `yardstick::roc_auc()` เพื่อคำนวณค่าพื้นที่ใต้กราฟ (Area Under the Curve) วิธีหนึ่งในการตีความ AUC คือความน่าจะเป็นที่โมเดลจะจัดอันดับตัวอย่างบวกแบบสุ่มให้สูงกว่าตัวอย่างลบแบบสุ่ม\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "# Calculate area under curve\n", "results %>% \n", " roc_auc(color, .pred_ORANGE)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ผลลัพธ์อยู่ที่ประมาณ `0.975` ซึ่งเมื่อพิจารณาว่า AUC มีค่าตั้งแต่ 0 ถึง 1 คุณต้องการคะแนนที่สูง เพราะโมเดลที่ทำนายได้ถูกต้อง 100% จะมีค่า AUC เท่ากับ 1; ในกรณีนี้ โมเดลถือว่า *ค่อนข้างดี*\n", "\n", "ในบทเรียนอนาคตเกี่ยวกับการจำแนกประเภท คุณจะได้เรียนรู้วิธีปรับปรุงคะแนนของโมเดล (เช่น การจัดการกับข้อมูลที่ไม่สมดุลในกรณีนี้)\n", "\n", "## 🚀ความท้าทาย\n", "\n", "ยังมีอีกมากมายเกี่ยวกับการวิเคราะห์ Logistic Regression! แต่วิธีที่ดีที่สุดในการเรียนรู้คือการทดลอง ค้นหาชุดข้อมูลที่เหมาะสมกับการวิเคราะห์ประเภทนี้และสร้างโมเดลด้วยชุดข้อมูลนั้น คุณได้เรียนรู้อะไร? เคล็ดลับ: ลองดู [Kaggle](https://www.kaggle.com/search?q=logistic+regression+datasets) สำหรับชุดข้อมูลที่น่าสนใจ\n", "\n", "## ทบทวนและศึกษาด้วยตนเอง\n", "\n", "อ่านหน้าแรก ๆ ของ [เอกสารนี้จาก Stanford](https://web.stanford.edu/~jurafsky/slp3/5.pdf) เกี่ยวกับการใช้งาน Logistic Regression ในทางปฏิบัติ ลองคิดถึงงานที่เหมาะสมกับการวิเคราะห์แบบ Regression ประเภทต่าง ๆ ที่เราได้ศึกษาไปจนถึงตอนนี้ งานแบบไหนที่เหมาะสมที่สุด?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**ข้อจำกัดความรับผิดชอบ**: \nเอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่แม่นยำ เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ แนะนำให้ใช้บริการแปลภาษาจากผู้เชี่ยวชาญ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดซึ่งเกิดจากการใช้การแปลนี้\n" ] } ], "metadata": { "anaconda-cloud": "", "kernelspec": { "display_name": "R", "langauge": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "3.4.1" }, "coopTranslator": { "original_hash": "feaf125f481a89c468fa115bf2aed580", "translation_date": "2025-09-06T13:35:48+00:00", "source_file": "2-Regression/4-Logistic/solution/R/lesson_4-R.ipynb", "language_code": "th" } }, "nbformat": 4, "nbformat_minor": 1 }