Version Control and Git From Zero to Hero : Part 4 (การใช้งาน Git ร่วมกับ Jupyter Notebook)

บทความโดย ผศ.ดร.ณัฐโชติ พรหมฤทธิ์
ภาควิชาคอมพิวเตอร์
คณะวิทยาศาสตร์
มหาวิทยาลัยศิลปากร

Jupyter Notebook เป็นเครื่องมือที่เหมาะสำหรับการทดสอบ Idea การรันงาน Data Science และงานด้าน AI (Artificial Intelligence) อย่างมาก เนื่องจากผู้ใช้สามารถลองผิดลองถูก เขียน Code สลับไปมาระหว่าง Cell โดยไม่ต้องเขียนโปรแกรมให้เสร็จสมบูรณ์ก่อน

เราสามารถกด Ctrl + Enter ทดลองรัน Code ดูผลลัพธ์ทีละส่วน และไล่หา Error/Bug ไปเรื่อยๆ แล้วนำ Code ที่ใช้งานได้ ไปใส่ใน Cell ที่เตรียมไว้

ด้วยความสะดวกดังกล่าว Jupyter Notebook จึงกลายเป็นเครื่องมือที่ได้รับความนิยมเป็นอันดับต้นๆ

อย่างไรก็ตามการทำ Version Control กับ Jupyter Notebook นั้นไม่ใช่เรื่องง่าย เพราะการที่มันเก็บ Source Code และผลลัพธ์จากการรันเป็น JSON และ Binary Format ทำให้อาจเกิดความสับสนในการเปรียบเทียบความเปลี่ยนแปลงของ Source Code โดยเฉพาะเมื่อมีการแชร์ Code ให้คนอื่นๆ ในทีม

เพื่อแก้ปัญหาดังกล่าวเราจึงต้องมีการปรับแต่ง Jupyter Notebook ให้จัดเก็บ Source Code ใน Format ที่เรียบง่ายขึ้น

ใน Part 4 นี้เราจะใช้คำสั่ง git diff เปรียบเทียบ Code ในแต่ละ Version ที่เขียนโดย Editor ยอดนิยม เช่น Visual Studio Code และ Jupyter Notebook รวมทั้งอธิบายการแก้ปัญหาการการทำงานร่วมกันของ Jupyter Notebook กับ Git โดยเราจะ Clone Project จาก URL ของ Remote Repository เดิมที่ได้สร้างไว้ใน Part 3 มาแก้ไข

Clone Project

ก่อนจะ Clone ให้ลบ Folder เดิม (git101) ออก แล้วใช้คำสั่ง git clone ดังภาพด้านล่าง

git clone http://gitlab.cpsudevops.com/nuttachot/git101.git

จากภาพ Git จะ Copy Source Code ทุก Version จาก Remote Project และสร้าง Folder ใหม่ (git101) บน Local Host

เราสามารถใช้คำสั่ง git log --oneline ดู History ของ Branch ปัจจุบัน (Master Branch) ที่กำลัง Check-Out ได้

Show Changes

จากนั้นให้เปิดไฟล์ hello.py ใน Folder git101 โดยใช้ Visual Studio Code แล้วเพิ่ม Comment ที่บรรทัดแรก และคำสั่ง print ในบรรทัดที่ 2 แล้วกด Save

ขณะนี้เรามีไฟล์ hello.py อยู่ 2 Version โดย Version หนึ่งถูก Check-In ด้วย Commit ID 0ed543c อีก Version เป็น Code ที่มีการแก้ไขจาก Commit เดิม ซึ่งทำให้ไฟล์ hello.py มีในสถานะเป็น Modified

ใช้คำสั่ง git status เพื่อดูสถานะของไฟล์

เราจะใช้คำสั่ง git diff เปรียบเทียบ Source Code ของไฟล์ 2 Version บรรทัดต่อบรรทัด โดย Git จะแสดงเครื่องหมาย  "-" และตัวอักษรสีแดงในบรรทัดเดิมก่อนถูกแก้ไขหรือก่อนถูกลบ และแสดงเครื่องหมาย "+" และ Code ใหม่ที่ถูกแก้ไขหรือเพิ่มใหม่ด้วยตัวอักษรสีเขียว ดังภาพด้านล่าง

ให้ Check-In เพื่อจัดเก็บ Source Code อย่างถาวรโดยใช้คำสั่ง git add hello.py และ git commit -m 'Edit hello.py'  แล้วดู History ของ Source Code ด้วยคำสั่ง git log --oneline

ซึ่งจะเห็น History ของเรามี Commit เพิ่มอีก 1 Commit คือ Commit ID 18d761e รวมทั้ง Local Head Pointer ขยับมาชี้ที่ Commit ใหม่ แต่ Remote Head Pointer ยังคงอยู่ที่ Commit ID เดิมเหมือนตอนที่ Clone Project มาใหม่

Problems with Jupyter Notebook and Git

เพื่อให้เห็นถึงปัญหาของการใช้ Git ร่วมกับ Jupyter Notebook เรามาลองทำตามตัวอย่างต่อไปนี้

ที่ Folder git101 ให้เปิด Jupyter Notebook แล้วสร้างไฟล์ Python 3 ตั้งชื่อ 'hello_version_control' พิมพ์ Code ในไฟล์ใหม่ กด Shift + Enter เพื่อรันโปรแกรมพร้อมสร้าง Cell ใหม่ แล้วกด Save จะได้ผลรันดังภาพด้านล่าง

Jupyter Notebook จะบันทึกนามสกุลของไฟล์ เป็น .ipynb ซึ่งเมื่อเปิดไฟล์ hello_version_control.ipynb ด้วย Visual Studio Code เราจะเห็นว่ามีการจัดเก็บ Source Code และ Output ดังภาพ

ให้ Check-In โดยใช้คำสั่ง git add hello_version_control.ipynb และ git commit -m 'Create hello_version_control.ipynb'  แล้วดู History ของ Source Code ด้วยคำสั่ง git log --oneline เราจะได้ Commit ID ใหม่ดังนี้

แก้ไขไฟล์ hello_version_control.ipynb โดยเปลี่ยนค่าของ b เป็น 8 ใน Cell ที่ 1 และกำหนดค่า X = range(12) ใน Cell ที่ 2

เมื่อเปรียบเทียบ Source Code โดยใช้คำสั่ง git diff จะพบว่าทั้งสอง Version มีความแตกต่างกันในส่วนที่ไม่เกี่ยวข้องกับ Source Code โดยตรงอยู่หลายบรรทัด ซึ่งอาจทำให้เกิดความสับสนในการ Review Code รวมทั้งการ Merge History ของ Git โดยอัตโนมัติ

Solution

วิธีหนึ่งในการแก้ปัญหาดังกล่าว คือการปรับแต่ง Jupyter Notebook ให้บันทึก Source Code แบบ Plain Text ด้วย Jupytext Library โดยเราจะต้อง Shutdown Jupyter Notebook และติดตั้ง Jupytext แล้วตั้งค่าในไฟล์ jupyter_notebook_config.py ด้วยคำสั่งดังต่อไปนี้

pip install jupytext --upgrade
jupyter notebook --generate-config

ให้แก้ไขไฟล์ jupyter_notebook_config.py ตาม Path ของตัวเอง โดยเพิ่มคำสั่งด้านล่าง แล้ว Restart Jupyter Notebook ครับ

c.NotebookApp.contents_manager_class="jupytext.TextFileContentsManager"
c.ContentsManager.default_jupytext_formats = ".ipynb,.Rmd"

หลังจากนั้นให้ปิดการบันทึกอัตโนมัติ โดยรันคำสั่ง %autosave 0 ใน Cell แรกของไฟล์แล้วกด Save, Jupyter Notebook จะสร้างไฟล์ใหม่นามสกุล .Rmd แต่มีชื่อเดียวกับไฟล์เดิม

เราสามารถแก้ไข Code ได้ทั้งที่ไฟล์ hello_version_control.Rmd และ hello_version_control.ipynb เมื่อแก้ไข Code แล้วกด Save, Jupyter Notebook จะ Sync ข้อมูลให้เหมือนกันทั้ง 2 ไฟล์โดยอัตโนมัติ

อย่างไรก็ตามเมื่อเปิดไฟล์จาก Visual Studio Code เราจะเห็นว่า hello_version_control.Rmd มี Format การเก็บข้อมูลที่เรียบง่ายกว่า

เราสามารถ Check-In ที่ Local Host ได้ทั้ง 2 ไฟล์ครับ แต่เพื่อไม่ให้เกิดความสับสนในภายหลัง เราจะ Check-In Source Code ลง Project เฉพาะไฟล์ *.Rmd

ดังนั้นจึงต้องตั้งค่า Git ไม่ให้ Track ไฟล์นามสกุล ipynb โดยการเพิ่ม *.ipynb ในไฟล์ .gitignore ดังภาพด้านล่าง

ให้ Check-In ทั้งไฟล์ hello_version_control.Rmd และ .gitignore โดยใช้คำสั่ง git add . และ git commit -m 'Create first rmd'  แล้วดู History ของ Source Code ด้วยคำสั่ง git log --oneline

จากนั้นเปลี่ยนค่าของ b เป็น 6 ใน Cell ที่ 1 และกำหนดค่า X = range(14) ใน Cell ที่ 2 แล้วเปรียบเทียบ Source Code ทั้งสอง Version โดยใช้คำสั่ง git diff hello_version_control.Rmd

การเปรียบเทียบ Version ของไฟล์ Jupyter Notebook ก็จะดูไม่สับสนอีกต่อไปครับ

ขอขอบคุณ Nipa.Cloud ที่ให้การสนับสนุน Environment ในการเรียนการสอน
รายวิชา Dev-Ops and Cloud Engineering 101