พื้นฐาน NLP เชิงปฏิบัติ: ตัวแยกประเภทภาษาที่ใช้งานออนไลน์ใน 3 ขั้นตอน

Tl; dr: ฉันใช้เวลาพูดคุย TED 5,000 ครั้งเพื่อสร้างตัวแยกประเภทภาษาโปรตุเกสด้วย scikit-learn จากนั้นฉันก็ปรับแต่ง Flask และ Vue.js เพื่อ "แชร์กับคุณ"

Clique aqui para o artigo em português

ใช่ ฉันเป็นสมาชิกของกลุ่มคนที่ตัดสินใจก้าวเข้าสู่กลุ่ม Data Science เมื่อเร็วๆ นี้ เรียนรู้ตามที่คุณไปผู้คนกล่าว เสนอปัญหาและหาทางแก้ไขพวกเขากล่าว อย่ากลัวที่จะทำลายสิ่งของระหว่างทางพวกเขากล่าว ฉันก็พยายามมากเหมือนกัน ในฐานะนักแปลภาษาโปแลนด์-โปรตุเกสในชีวิตก่อน ฉันมีปัญหาในโลกแห่งความเป็นจริงขึ้นมาทันที:

คุณเห็นไหมว่าโปรตุเกสเป็นภาษาที่แบ่งออกเป็นมหาสมุทรแอตแลนติก ประเทศบราซิลและโลกเก่า (ประเทศในแอฟริกาและเอเชียที่พูดโปรตุเกส + โปรตุเกส) มีความแตกต่างกันในเรื่องคำศัพท์ ไวยากรณ์ และการออกเสียง

ในขณะเดียวกัน ตลาดการแปลภาษาโปรตุเกสอิสระในโปแลนด์ค่อนข้างแคบ ฉันไม่มีความหรูหราที่จะเชี่ยวชาญและยึดติดกับพันธุ์ใดพันธุ์หนึ่งเท่านั้น เมื่อฉันสวมหมวกทั้งสองข้าง บางครั้งในสัปดาห์ทำงานเดียวกัน ฉันจึงต้องระมัดระวังในการรักษาความสม่ำเสมอในการแปลที่กำหนดเป็นสองเท่า นั่นเป็นเหตุผลว่าทำไมแนวคิดแรกๆ ของฉันสำหรับโครงการ NLP คือตัวแยกประเภทภาษาโปรตุเกส

ไม่ใช่การวิจัยที่ก้าวล้ำที่ปูทางใหม่ใน NLP หรือ AI แรงจูงใจของฉันที่นี่คล้ายกับใน "บทความขนาดกลางเกี่ยวกับ Tweetbot" ก่อนหน้าของฉัน — แสดงให้เห็นว่าขั้นตอนแรกในการประมวลผลภาษาธรรมชาติใน Python นั้นตรงไปตรงมาเพียงใด ในฐานะบุคคลที่มีพื้นฐานด้านเทคนิคเพียงเล็กน้อย ฉันพยายามนำเสนอเป็นภาษาอังกฤษธรรมดาที่สุด

บทความนี้แบ่งออกเป็นสามส่วนหลัก:

  • การเตรียมแหล่งข้อมูล (Scrapy);
  • ฝึกอบรมโมเดล (scikit-learn);
  • การปรับใช้ API ด้วยโมเดล (Flask+Vue.js)

กำลังเตรียมแหล่งข้อมูล

ฉันต้องใช้ข้อมูลอะไรบ้าง จะรวบรวมข้อมูลได้ที่ไหน และอย่างไร? ในกรณีนี้ ฉันเลือก TED.com เป็นแหล่งข้อมูลของฉัน เป็นฐานข้อมูลขนาดใหญ่พอสมควรของการบรรยายที่ถอดความได้อย่างสะดวก สิ่งที่สำคัญอย่างยิ่งในกรณีนี้คือ ทีมแปลของ TED จะคอยแบ่งแยกภาษาโปรตุเกสทั้งสองอย่างเข้มงวด (ซึ่งไม่ใช่ในกรณีของวิกิพีเดีย เป็นต้น)

TED เคยดูแลรักษา API อย่างเป็นทางการ แต่เลิกผลิตไปในปี 2016 ดังนั้นฉันจึงตั้งค่า "Scrapy Spider" เพื่อดึงข้อมูล ส่วนใหญ่เป็นแบบสำเร็จรูปตาม "บทช่วยสอนแบบ Scrapy" สไปเดอร์วนดูรายการการบรรยายของ TED ในภาษาที่ระบุ และติดตามลิงก์ไปยังการบรรยายแต่ละรายการ:

def parse_front(self, response):
 
   talk_links = response.css(“a.ga-link::attr(href)”) 
   links_to_follow = talk_links.extract() 
   for url in links_to_follow: 
      url = url.replace(‘?language’, ‘/transcript?language’) 
      yield response.follow(url=url, callback=self.parse_pages)

จากนั้น วิธีแยกวิเคราะห์ที่สองจะขูดชื่อและข้อความของการบรรยายแต่ละรายการและเพิ่มลงในพจนานุกรม:

def parse_pages(self, response): 
   title = response.xpath(‘//head/title/text()’).extract_first()    
   title = title.strip() 
   title = title.split(‘:’)[0] 
   talk = response.xpath( ‘//div[@class=”Grid__cell flx-s:1 p-r:4"]/p/text()’).extract() 
   for line in talk: 
      line = line.strip() 
      line = line.replace(‘\n’,’ ‘) 
      line = line.replace(‘\t’,’ ‘) 
   talk = ‘ ‘.join(talk) 
   talk = talk.replace(‘\n’,’ ‘) 
   ted_dict[title] = talk

เมื่อเสร็จแล้ว พจนานุกรมจะถูกเทลงในไฟล์ CSV สิ่งที่ฉันต้องทำคือกำหนด xpaths ที่เหมาะสม ฉันยังตั้งค่าดีเลย์ครึ่งนาทีระหว่างการดาวน์โหลดแต่ละครั้ง (หากไม่มี ฉันเข้าใช้งานเซิร์ฟเวอร์บ่อยเกินไปและมันบล็อกคำถามของฉัน):

class TedSpiderPt(scrapy.Spider): 
   name = “ted_spider_pt” 
   download_delay = 0.5

การเตรียมข้อมูลที่เหลือและกระบวนการฝึกอบรมสามารถรวมอยู่ในสมุดบันทึก Jupyter ที่นี่ ด้านล่างนี้ ฉันอ่านผ่านๆ และเน้นช่วงเวลาสำคัญๆ

ฉันคัดลอกการพูดคุย TED ประมาณ 2,500 รายการสำหรับภาษาโปรตุเกสแต่ละเวอร์ชัน ประมาณ 12-18,000 อักขระต่อการพูดคุยแต่ละครั้ง หลังจากทำความสะอาดการจัดรูปแบบแล้ว ฉันติดป้ายกำกับการถอดเสียงเป็น "0" สำหรับ PT-PT (โปรตุเกสแบบยุโรป) และ "1" สำหรับ PT-BR จากนั้นจึงปรับให้เหมาะสมและแปลงด้วย CountVectorizer ของ scikit-learn

หากบทความนี้เป็นการแนะนำคุณเกี่ยวกับการประมวลผลภาษาธรรมชาติ: CountVectorizer แปลงชุดเอกสารข้อความเป็นจำนวนคำ ข้อมูลข้อความในรูปแบบตัวเลขนี้ช่วยเพิ่มความสามารถในการประมวลผลได้อย่างมาก

ก่อนอื่น ฉันแบ่งการบรรยายออกเป็นชุดการฝึกอบรมและการทดสอบในสัดส่วน 2:1 โมเดลจะเรียนรู้จากข้อมูลการฝึกเท่านั้น ส่วนทดสอบใช้ตรวจสอบประสิทธิภาพก่อนปล่อยออกสู่โลก

จากนั้นฉันก็จัด Vectorizer เข้ากับข้อมูลการฝึกอบรม — ในภาษาอังกฤษธรรมดา มันจะกำหนดหมายเลขที่ไม่ซ้ำกันสำหรับแต่ละคำที่พบ เพื่อสร้างคำศัพท์ เพื่อกำจัดค่าผิดปกติที่รุนแรง ฉันตั้งค่าพารามิเตอร์ min_df เป็น 2 — คำที่เกิดขึ้นเพียงครั้งเดียวจะไม่นำมาพิจารณา จากนั้นฉันก็เปลี่ยนชุดการฝึก เช่น นับจำนวนคำแต่ละคำจากคำศัพท์ หลังจาก CountVectorizing แต่ละคำ 59,061 คำจะถูกนับในการพูดคุย 3,555 คำที่ประกอบเป็นชุดการฝึกอบรม

ดูด้านล่างสำหรับภาพประกอบ:

คำว่า "eu" ("ฉัน" ในภาษาโปรตุเกส) ได้รับการจัดทำดัชนีเป็น "23892" ในคำศัพท์ ในการนับจำนวนคำสำหรับดัชนีนี้ เราจะเห็นการบรรยายรายการหนึ่งที่มีจำนวน "eu" สูงกว่ามาก เราสามารถย้อนกลับไปดูการถอดเสียงพูดคุยได้ และ... แท้จริงแล้ว ประสบการณ์ส่วนตัวมีบทบาทสำคัญใน "TED talk" ของ Chiki Sarkar

นอกเหนือจากการทำเวคเตอร์และการนับแล้ว ฉันงดเว้นการประมวลผลล่วงหน้าอื่นๆ ฉันไม่ควบคุมคำนั้น เนื่องจากฉันต้องการรักษาความแตกต่างทางไวยากรณ์ (เช่น ความแตกต่างในการผันคำกริยา) ระหว่างภาษาทั้งสองเวอร์ชัน เพื่อยกตัวอย่าง: การลด "[a] fazer" และ "fazendo" ลงไปที่ก้านจะทำให้ความแตกต่าง PT/BR ที่ฉันต้องการจับไม่ชัดเจน

การฝึกอบรมโมเดล

จากนั้น ฉันฝึกตัวแยกประเภท Multinomial Naive Bayes ในชุดการฝึก แม้ว่าแนวทางนี้จะค่อนข้างพื้นฐาน แต่ก็ทำงานได้ดีอย่างน่าประหลาดใจในแอปพลิเคชัน NLP เช่นนี้

คำอธิบายภาษาอังกฤษธรรมดาบางประการอีกครั้ง: ตัวแยกประเภท NaiveBayes โดยทั่วไปจะเปรียบเทียบความถี่ของคำที่กำหนดในชุด BR และ PT และกำหนดว่าคำนั้นแนะนำข้อความ "บราซิล" หรือ "โปรตุเกส" มากกว่ากัน ขณะทำนาย คำทั้งหมดในข้อความจะถูกชั่งน้ำหนักและเราจะได้ความน่าจะเป็นขั้นสุดท้าย

หลังจากฝึกฝนตัวแยกประเภทแล้ว ฉันจะเปลี่ยนชุดการทดสอบโดยใช้เวกเตอร์ไลเซอร์แบบเดิม มีคำศัพท์ชุดฝึกอบรมอยู่แล้ว และจะนับเฉพาะคำที่ปรากฏในนั้นเท่านั้น

จากนั้นชุดการทดสอบแบบเวกเตอร์จะถูกจำแนกประเภทโดยใช้ MultinomialNaiveBayes ในกรณีนี้ ฉันได้ปรับแต่งชุดทดสอบเล็กน้อย — ฉันต้องการทดสอบความสามารถในการจัดประเภทข้อความสั้น ดังนั้นฉันจึงแบ่งการบรรยายของชุดการทดสอบออกเป็นกลุ่มย่อยๆ ละ 200–760 อักขระ เพื่อการเปรียบเทียบ ย่อหน้าที่คุณกำลังอ่านอยู่ตอนนี้มีอักขระ 370 ตัว ผลลัพธ์?

ฉันได้รับความแม่นยำ 86.55% สำหรับข้อความสั้น โดยมีสัดส่วนที่เกือบเท่ากันของตัวอย่าง PT-PT และ PT-BR ที่จัดประเภทอย่างไม่ถูกต้อง คะแนนดีพอมั้ย? ฉันไม่ใช่ผู้ตัดสินที่เป็นกลางที่สุดที่นี่ แต่ฉันว่ามันก็ค่อนข้างดี เรามาดูตัวอย่างวลีที่จัดประเภทผิดเพื่อช่วยเราประเมินโมเดลกัน

แม้ว่าคุณจะไม่คุ้นเคยกับภาษาโปรตุเกสทั้งสองรูปแบบ ลองจินตนาการถึงประโยคบางประโยคที่แต่งขึ้นในลักษณะที่ไม่ทำให้ฟังดูเป็นภาษาอังกฤษแบบอเมริกันหรือแบบอังกฤษ นั่นเป็นปัญหาของวลีที่จัดประเภทไม่ถูกต้องหลายวลีที่นี่ ไม่ว่าในกรณีใด ฉันจะเจาะลึกถึงวิธีปรับปรุงโมเดลในส่วนต่อๆ ไปของบทความนี้ สำหรับตอนนี้ มาแบ่งปันให้โลกได้รับรู้กันเถอะ!

การปรับใช้ API กับโมเดล

หากฉันต้องการใช้โมเดลของฉันนอก Jupyter Notebook ฉันต้องการวิธีส่งออกข้อมูลดังกล่าว สำหรับงานนี้ ฉันใช้ ไลบรารีการทำให้ซีเรียลไลซ์ joblib ซึ่งให้วิธีแก้ปัญหาแบบเบา ๆ

ข้อความใหม่ทุกข้อความที่ส่งไปที่โมเดลจะต้องได้รับการแปลงโดย vectorizer แล้วจึงจัดประเภท ดังนั้นฉันจึงสรุป vectorizer และ classifier ให้เป็น "ไปป์ไลน์" ของ scikit ถ้าอย่างนั้นก็เป็นเพียงเรื่องของการถ่ายโอนข้อมูลลงในไฟล์ joblib:

pipeline = Pipeline([('vectorizer',vectorizer), 'classifier',nb_classifier)])
dump(pipeline, 'ptclassifier.joblib', protocol = 2)

“Lightweight” เป็นคำที่ใช้กับ Flask ซึ่งเป็นเฟรมเวิร์กแอปพลิเคชันเว็บแบบลีนที่ฉันใช้ในการปรับใช้ API รหัสเต็มสำหรับแอป Flask ของฉันคือ "ใน repo GitHub" โดยพื้นฐานแล้วมันจะโหลดโมเดล joblibbed และรับฟังคำขอ GET ด้วยอาร์กิวเมนต์เดียว: สตริงของข้อมูลข้อความ เมื่อได้รับแล้ว จะส่งสตริงผ่านไปป์ไลน์และส่งกลับทั้งป้ายกำกับที่คาดการณ์และค่าความเชื่อมั่นของการคาดคะเนนั้น

ฉันยังใช้แพ็คเกจ flask-cors เพื่ออนุญาตคำขอจากแหล่งอื่น ในกรณีของเรา แอปส่วนหน้าที่ฉันจะแนะนำในอีกสักครู่ซึ่งโฮสต์ไว้ที่อื่น จะต้องมีสิทธิ์เข้าถึง Flask API นี้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ CORS ที่นี่

ฉันโฮสต์แอป Flask ของฉันบน PythonAnywhere ซึ่งเป็นบริการที่ฉันอธิบายโดยละเอียดเพิ่มเติมอีกเล็กน้อยใน บทความ tweetbot หากคุณเริ่มต้นด้วยสคริปต์ Python ที่โฮสต์ออนไลน์ ฉันขอแนะนำ PyA ด้วยความเต็มใจ — ทีมสนับสนุนให้ความช่วยเหลือเสมอกับปัญหาของผู้เริ่มต้น

วิธีที่ทรมานของฉันไปสู่ส่วนหน้า ML

เอ่อ ส่วนหน้า.. ต้นตอและสาเหตุของความหงุดหงิดครั้งใหญ่ของฉัน ฉันเชื่อว่าขั้นตอนสุดท้าย การตั้งค่าหน้าที่ง่ายที่สุดด้วยกล่องข้อความและปุ่ม "ส่ง" จะค่อนข้างง่าย ฉันมั่นใจมากจนฉันตกอยู่ในวงจรอุบาทว์

ก่อนอื่น ฉันได้รับคำแนะนำว่าฉันควรใช้ Vue.js แต่เริ่มต้นด้วยหนังสือเกี่ยวกับ JavaScript เอง เช่น EloquentJS อ่า.. อย่าเข้าใจฉันผิด ฉันชอบ EJS ซึ่งเป็นเวอร์ชันออนไลน์ที่มีแซนด์บ็อกซ์สำหรับแบบฝึกหัด ซึ่งสามารถเข้าถึงได้มาก แต่หลังจากผ่านไปหนึ่งสัปดาห์กับหลักสูตรนี้ ฉันก็ตระหนักว่าเป้าหมายของฉันในขณะนี้คือการต่อส่วนหน้าพื้นฐานและกลับไปสร้างโมเดลอีกครั้ง ฉันยังคงรู้สึกไม่คล่องในภาษา Python เพียงพอที่จะเริ่มภาษาใหม่

จากนั้นฉันก็ค้นหา “เทมเพลตส่วนหน้าของ Machine Learning อย่างง่าย” และคำหลักที่คล้ายกันซึ่งมีผลลัพธ์ที่หลากหลาย กับดักที่ฉันตกเข้าไป? ความอดทนของฉันสำหรับโซลูชันที่ใช้งานได้เพียงเล็กน้อยนั้นกินเวลาน้อยกว่าเวลาที่ฉันต้องทำความเข้าใจและปรับใช้เทมเพลต

ในที่สุด ฉันพบอันที่ทำงานเร็วพอสำหรับฟิวส์สั้นของฉัน — นำเสนอโดย James Salvatore ในบล็อก Uptake Tech ฉันนำเทมเพลตของเขามาปรับแต่งเล็กน้อยตามจุดประสงค์ของฉัน เพื่อความง่าย ฉันจึงตัดแถบเครื่องมือออก เพิ่มรูปภาพที่ใช้เป็นส่วนหัว และเก็บกล่องข้อความไว้เพียงกล่องเดียว ฉันโฮสต์แอปเวอร์ชันที่ใช้งานจริงบน AlwaysData ซึ่งเป็นบริการอื่นที่ฉันสามารถแนะนำได้

ดูผลลัพธ์สุดท้าย ที่นี่ หากคุณยังไม่ได้ดำเนินการ

เล่นสนุกแม้ว่าคุณจะไม่รู้จักภาษาโปรตุเกสก็ตาม ฉันจะมีความสุขมากถ้าคุณทำลายบางสิ่งที่นั่นและบอกฉันเกี่ยวกับมัน! นอกจากนี้ การวิพากษ์วิจารณ์อย่างสร้างสรรค์ต่อ "โค้ดในพื้นที่เก็บข้อมูล" จะทำให้คุณได้รับความกตัญญูชั่วนิรันดร์

อะไรต่อไป?

บทความนี้จะมีส่วนที่ 2 ในอนาคตอันใกล้นี้ ฉันต้องการ:

  • เพิ่มฟีเจอร์ที่แสดงให้ผู้ใช้แอปส่วนหน้าของฉันเห็นว่าคำแต่ละคำมีอิทธิพลต่อการทำนายอย่างไร
  • ขูดแหล่งข้อความอื่นๆ (ข่าว บล็อก คำบรรยายภาพยนตร์) ตรวจสอบประสิทธิภาพของโมเดล
  • สำรวจข้อผิดพลาดของตัวแยกประเภทและพยายามค้นหารูปแบบ
  • ลองใช้แนวทาง LSTM ในการแก้ปัญหาและเปรียบเทียบผลลัพธ์
  • และสุดท้ายแต่ไม่ท้ายสุด หวังว่าฉันจะได้รับคำติชมจากผู้อ่านของฉันที่ควรค่าแก่การแบ่งปัน!

โบนัส (สำหรับผู้พูดภาษาโปรตุเกส)!

ฉันสะดุดกับสองตัวอย่างของผู้คนที่แก้ไขปัญหาคล้ายกันกับภาษาโปรตุเกส:

ขอขอบคุณ Piotr Migdał สำหรับคำแนะนำเกี่ยวกับเรื่องนี้และโครงการอื่นๆ และขอขอบคุณ Kamil Herba และ Piotr Kaznowski สำหรับข้อมูลเชิงลึก ทั้งในบทความและเว็บแอป