You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
686 lines
50 KiB
686 lines
50 KiB
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## สร้างโมเดล Logistic Regression - บทเรียนที่ 4\n",
|
|
"\n",
|
|
"\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",
|
|
"\n",
|
|
"\n",
|
|
"### การจัดประเภทอื่นๆ\n",
|
|
"\n",
|
|
"ยังมีการถดถอยโลจิสติกประเภทอื่นๆ เช่น มัลติโนเมียลและออร์ดินัล:\n",
|
|
"\n",
|
|
"- **มัลติโนเมียล** ซึ่งเกี่ยวข้องกับการมีมากกว่าหนึ่งหมวดหมู่ - \"สีส้ม, สีขาว, และลาย\"\n",
|
|
"\n",
|
|
"- **ออร์ดินัล** ซึ่งเกี่ยวข้องกับหมวดหมู่ที่มีลำดับ เหมาะสมหากเราต้องการจัดลำดับผลลัพธ์อย่างมีตรรกะ เช่น ฟักทองของเราที่จัดลำดับตามขนาดที่มีจำนวนจำกัด (เล็กมาก, เล็ก, กลาง, ใหญ่, ใหญ่มาก, ใหญ่ที่สุด)\n",
|
|
"\n",
|
|
"\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
|
|
} |