วิธีสร้างบอทวิเคราะห์ความคิดเห็นทางอีเมล: บทช่วยสอน NLP
เผยแพร่แล้ว: 2022-03-11เทคโนโลยีการประมวลผลภาษาธรรมชาติค่อนข้างซับซ้อนในช่วงไม่กี่ปีที่ผ่านมา ตั้งแต่ยักษ์ใหญ่ด้านเทคโนโลยีไปจนถึงมือสมัครเล่น หลายคนเร่งรีบในการสร้างอินเทอร์เฟซที่สมบูรณ์ซึ่งสามารถวิเคราะห์ ทำความเข้าใจ และตอบสนองต่อภาษาที่เป็นธรรมชาติได้ Alexa ของ Amazon, Cortana ของ Microsoft, Google Home ของ Google และ Siri ของ Apple ต่างตั้งเป้าที่จะเปลี่ยนวิธีที่เราโต้ตอบกับคอมพิวเตอร์
การวิเคราะห์ความรู้สึก ซึ่งเป็นฟิลด์ย่อยของการประมวลผลภาษาธรรมชาติ ประกอบด้วยเทคนิคที่กำหนดน้ำเสียงของข้อความหรือคำพูด ทุกวันนี้ ด้วยการเรียนรู้ของเครื่องจักรและข้อมูลจำนวนมากที่รวบรวมจากโซเชียลมีเดียและไซต์ตรวจสอบ เราสามารถฝึกแบบจำลองเพื่อระบุความรู้สึกของข้อความภาษาที่เป็นธรรมชาติด้วยความแม่นยำที่ยุติธรรม
ในบทช่วยสอนนี้ คุณจะได้เรียนรู้วิธีสร้างบอทที่สามารถวิเคราะห์ความรู้สึกของอีเมลที่ได้รับและแจ้งให้คุณทราบเกี่ยวกับอีเมลที่อาจต้องให้ความสนใจทันที
การวิเคราะห์ความรู้สึกในอีเมล
บอทจะถูกสร้างขึ้นโดยใช้การผสมผสานระหว่างการพัฒนา Java และ Python กระบวนการทั้งสองจะสื่อสารกันโดยใช้ Thrift หากคุณไม่คุ้นเคยกับภาษาใดภาษาหนึ่งหรือทั้งสองภาษานี้ คุณยังสามารถอ่านต่อได้เนื่องจากแนวคิดพื้นฐานของบทความนี้จะใช้กับภาษาอื่นๆ ด้วยเช่นกัน
ในการพิจารณาว่าอีเมลต้องการความสนใจจากคุณหรือไม่ บอทจะแยกวิเคราะห์และพิจารณาว่ามีน้ำเสียงเชิงลบที่ชัดเจนหรือไม่ จากนั้นจะส่งข้อความแจ้งเตือนหากจำเป็น
เราจะใช้ Sendgrid เพื่อเชื่อมต่อกับกล่องจดหมายของเรา และ Twilio เพื่อส่งข้อความแจ้งเตือน
การวิเคราะห์ความเชื่อมั่น: ปัญหาง่ายๆ ที่หลอกลวง
มีคำบางคำที่เราเชื่อมโยงกับอารมณ์เชิงบวก เช่น ความรัก ความปิติยินดี และความสุข และมีคำที่เราเชื่อมโยงกับอารมณ์เชิงลบ เช่น ความเกลียดชัง ความเศร้า และความเจ็บปวด ทำไมไม่ฝึกแบบจำลองให้จำคำเหล่านี้และนับความถี่สัมพัทธ์และความแรงของคำที่เป็นบวกและลบแต่ละคำ
มีปัญหาสองสามอย่างในเรื่องนี้
ประการแรก มีปัญหาเรื่องการปฏิเสธ ตัวอย่างเช่น ประโยคเช่น “ลูกพีชไม่เลว” หมายถึงอารมณ์เชิงบวกโดยใช้คำที่เรามักเชื่อมโยงกับการเป็นแง่ลบ โมเดลคำศัพท์ง่ายๆ จะไม่สามารถรับรู้ถึงการปฏิเสธในประโยคนี้ได้
นอกจากนี้ ความรู้สึกผสมยังเป็นปัญหาอีกประการหนึ่งของการวิเคราะห์ความรู้สึกที่ไร้เดียงสา ตัวอย่างเช่น ประโยคเช่น "ลูกพีชไม่ได้แย่ แต่แอปเปิ้ลแย่มาก" มีความรู้สึกผสมของความเข้มผสมที่มีปฏิสัมพันธ์ซึ่งกันและกัน วิธีการง่ายๆ จะไม่สามารถแก้ไขความรู้สึกที่รวมกัน ความเข้มข้นที่แตกต่างกัน หรือปฏิสัมพันธ์ระหว่างความรู้สึกได้
การวิเคราะห์ความรู้สึกโดยใช้ Recursive Neural Tensor Network
ห้องสมุด Stanford Natural Language Processing สำหรับการวิเคราะห์ความรู้สึกสามารถแก้ไขปัญหาเหล่านี้ได้โดยใช้ Recursive Neural Tensor Network (RNTN)
อัลกอริทึม RNTN แบ่งประโยคออกเป็นคำแต่ละคำก่อน จากนั้นจะสร้างโครงข่ายประสาทเทียมโดยที่โหนดเป็นคำแต่ละคำ สุดท้าย เพิ่มเลเยอร์เทนเซอร์เพื่อให้โมเดลสามารถปรับการโต้ตอบระหว่างคำและวลีได้อย่างเหมาะสม
คุณสามารถดูการสาธิตอัลกอริธึมแบบเห็นภาพได้จากเว็บไซต์ทางการ
กลุ่ม Stanford NLP ฝึกฝน Recursive Neural Tensor Network โดยใช้บทวิจารณ์ภาพยนตร์ IMDB ที่ติดแท็กด้วยตนเอง และพบว่าโมเดลของพวกเขาสามารถทำนายความรู้สึกได้อย่างแม่นยำมาก
บอทที่รับอีเมล
สิ่งแรกที่คุณต้องทำคือตั้งค่าการรวมอีเมลเพื่อให้สามารถส่งข้อมูลไปยังบอทของคุณได้
มีหลายวิธีในการทำสิ่งนี้ให้สำเร็จ แต่เพื่อความเรียบง่าย เรามาตั้งค่าเว็บเซิร์ฟเวอร์อย่างง่ายและใช้เบ็ดแยกวิเคราะห์ขาเข้าของ Sendgrid เพื่อไปป์อีเมลไปยังเซิร์ฟเวอร์ เราสามารถส่งต่ออีเมลไปยังที่อยู่แยกวิเคราะห์ขาเข้าของ Sendgrid จากนั้น Sendgrid จะส่งคำขอ POST ไปยังเว็บเซิร์ฟเวอร์ของเรา จากนั้นเราจะสามารถประมวลผลข้อมูลผ่านเซิร์ฟเวอร์ของเราได้
ในการสร้างเซิร์ฟเวอร์ เราจะใช้ Flask ซึ่งเป็นเว็บเฟรมเวิร์กอย่างง่ายสำหรับ Python
นอกจากการสร้างเว็บเซิร์ฟเวอร์แล้ว เรายังต้องการเชื่อมต่อบริการเว็บกับโดเมนอีกด้วย เพื่อความกระชับ เราจะข้ามการเขียนเกี่ยวกับเรื่องนี้ในบทความไป อย่างไรก็ตาม คุณสามารถอ่านเพิ่มเติมได้ที่นี่
การสร้างเว็บเซิร์ฟเวอร์ใน Flask นั้นง่ายอย่างไม่น่าเชื่อ
เพียงสร้าง app.py
และเพิ่มลงในไฟล์:
from flask import Flask, request import datetime app = Flask(__name__) @app.route('/analyze', methods=['POST']) def analyze(): with open('logfile.txt', 'a') as fp_log: fp_log.write('endpoint hit %s \n' % datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return "Got it" app.run(host='0.0.0.0')
หากเราปรับใช้แอปนี้หลังชื่อโดเมนและกดจุดปลายทาง “/analyze” คุณจะเห็นสิ่งนี้:
> >> requests.post('http://sentiments.shanglunwang.com:5000/analyze').text 'Got it'
ต่อไป เราต้องการส่งอีเมลไปยังปลายทางนี้
คุณสามารถค้นหาเอกสารเพิ่มเติมได้ที่นี่ แต่โดยพื้นฐานแล้ว คุณต้องการตั้งค่า Sendgrid ให้เป็นตัวประมวลผลอีเมลของคุณและให้ Sendgrid ส่งต่ออีเมลไปยังเว็บเซิร์ฟเวอร์ของเรา
นี่คือการตั้งค่าของฉันใน Sendgrid การดำเนินการนี้จะส่งอีเมลไปที่ @sentibot.shanglunwang.com
ตามคำร้องขอ POST ไปที่ “http://sentiments.shanglunwang.com/analyze”:
คุณสามารถใช้บริการอื่นๆ ที่รองรับการส่งอีเมลขาเข้าผ่านเว็บฮุคได้
หลังจากตั้งค่าทุกอย่างแล้ว ให้ลองส่งอีเมลไปยังที่อยู่ Sendgrid ของคุณ คุณควรเห็นสิ่งนี้ในบันทึก:
endpoint hit 2017-05-25 14:35:46
ที่ที่ดี! ตอนนี้คุณมีบอทที่สามารถรับอีเมลได้ นั่นคือครึ่งหนึ่งของสิ่งที่เราพยายามทำ
ตอนนี้ คุณต้องการให้บอทนี้สามารถวิเคราะห์ความรู้สึกในอีเมลได้
วิเคราะห์ความคิดเห็นทางอีเมลกับ Stanford NLP
เนื่องจากไลบรารี Stanford NLP เขียนด้วย Java เราจึงต้องการสร้างเอ็นจิ้นการวิเคราะห์ใน Java
เริ่มต้นด้วยการดาวน์โหลดไลบรารีและโมเดล Stanford NLP ใน Maven สร้างโปรเจ็กต์ Java ใหม่ เพิ่มสิ่งต่อไปนี้ในการพึ่งพา Maven ของคุณและนำเข้า:
<dependency> <groupId>edu.stanford.nlp</groupId> <artifactId>stanford-corenlp</artifactId> <version>3.6.0</version> </dependency>
เข้าถึงเครื่องมือวิเคราะห์ความเชื่อมั่นของ Stanford NLP ได้โดยการระบุหมายเหตุประกอบความคิดเห็นในโค้ดการเริ่มต้นไปป์ไลน์ จากนั้นสามารถเรียกคำอธิบายประกอบเป็นโครงสร้างแบบต้นไม้ได้
สำหรับจุดประสงค์ของบทช่วยสอนนี้ เราแค่ต้องการทราบความรู้สึกทั่วไปของประโยค เราจึงไม่จำเป็นต้องแยกวิเคราะห์ต้นไม้ เราแค่ต้องดูที่โหนดฐาน
ทำให้รหัสหลักค่อนข้างง่าย:
package seanwang; import edu.stanford.nlp.pipeline.*; import edu.stanford.nlp.util.CoreMap; import edu.stanford.nlp.ling.CoreAnnotations; import edu.stanford.nlp.sentiment.SentimentCoreAnnotations; import java.util.*; public class App { public static void main( String[] args ) { Properties pipelineProps = new Properties(); Properties tokenizerProps = new Properties(); pipelineProps.setProperty("annotators", "parse, sentiment"); pipelineProps.setProperty("parse.binaryTrees", "true"); pipelineProps.setProperty("enforceRequirements", "false"); tokenizerProps.setProperty("annotators", "tokenize ssplit"); StanfordCoreNLP tokenizer = new StanfordCoreNLP(tokenizerProps); StanfordCoreNLP pipeline = new StanfordCoreNLP(pipelineProps); String line = "Amazingly grateful beautiful friends are fulfilling an incredibly joyful accomplishment. What an truly terrible idea."; Annotation annotation = tokenizer.process(line); pipeline.annotate(annotation); // normal output for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) { String output = sentence.get(SentimentCoreAnnotations.SentimentClass.class); System.out.println(output); } } }
ลองใช้ประโยคและคุณควรเห็นคำอธิบายประกอบที่เหมาะสม การรันเอาต์พุตโค้ดตัวอย่าง:
Very Positive Negative
การผสานรวมบอทและเครื่องมือวิเคราะห์
ดังนั้นเราจึงมีโปรแกรมวิเคราะห์ความรู้สึกที่เขียนด้วยภาษา Java และบอทอีเมลที่เขียนด้วยภาษา Python เราจะทำให้พวกเขาคุยกันได้อย่างไร?
มีวิธีแก้ปัญหาที่เป็นไปได้มากมายสำหรับปัญหานี้ แต่ที่นี่เราจะใช้ Thrift เราจะสร้าง Sentiment Analyzer เป็นเซิร์ฟเวอร์ Thrift และบอทอีเมลเป็นไคลเอ็นต์ Thrift
Thrift เป็นเครื่องกำเนิดโค้ดและโปรโตคอลที่ใช้เพื่อเปิดใช้งานสองแอปพลิเคชัน ซึ่งมักเขียนด้วยภาษาต่างกัน เพื่อให้สามารถสื่อสารกันได้โดยใช้โปรโตคอลที่กำหนดไว้ ทีมงาน Polyglot ใช้ Thrift เพื่อสร้างเครือข่ายไมโครเซอร์วิสเพื่อใช้ประโยชน์จากภาษาแต่ละภาษาที่ดีที่สุด
ในการใช้ Thrift เราจำเป็นต้องมีสองสิ่ง: ไฟล์ .thrift
เพื่อกำหนดปลายทางของบริการ และโค้ดที่สร้างเพื่อใช้โปรโตคอลที่กำหนดไว้ในไฟล์ . .proto
สำหรับบริการตัววิเคราะห์ sentiment.thrift
จะมีลักษณะดังนี้:
namespace java sentiment namespace py sentiment service SentimentAnalysisService { string sentimentAnalyze(1:string sentence), }
เราสามารถสร้างรหัสไคลเอนต์และเซิร์ฟเวอร์โดยใช้ไฟล์ .thrift นี้ วิ่ง:
thrift-0.10.0.exe --gen py sentiment.thrift thrift-0.10.0.exe --gen java sentiment.thrift
หมายเหตุ: ฉันสร้างรหัสบนเครื่อง Windows คุณจะต้องใช้เส้นทางที่เหมาะสมไปยังไฟล์เรียกทำงาน Thrift ในสภาพแวดล้อมของคุณ

ตอนนี้ มาทำการเปลี่ยนแปลงที่เหมาะสมกับเอ็นจินการวิเคราะห์เพื่อสร้างเซิร์ฟเวอร์ โปรแกรม Java ของคุณควรมีลักษณะดังนี้:
SentimentHandler.java
package seanwang; public class SentimentHandler implements SentimentAnalysisService.Iface { SentimentAnalyzer analyzer; SentimentHandler() { analyzer = new SentimentAnalyzer(); } public String sentimentAnalyze(String sentence) { System.out.println("got: " + sentence); return analyzer.analyze(sentence); } }
ตัวจัดการนี้เป็นที่ที่เราได้รับคำขอการวิเคราะห์ผ่านโปรโตคอล Thrift
SentimentAnalyzer.java
package seanwang; // ... public class SentimentAnalyzer { StanfordCoreNLP tokenizer; StanfordCoreNLP pipeline; public SentimentAnalyzer() { Properties pipelineProps = new Properties(); Properties tokenizerProps = new Properties(); pipelineProps.setProperty("annotators", "parse, sentiment"); pipelineProps.setProperty("parse.binaryTrees", "true"); pipelineProps.setProperty("enforceRequirements", "false"); tokenizerProps.setProperty("annotators", "tokenize ssplit"); tokenizer = new StanfordCoreNLP(tokenizerProps); pipeline = new StanfordCoreNLP(pipelineProps); } public String analyze(String line) { Annotation annotation = tokenizer.process(line); pipeline.annotate(annotation); String output = ""; for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) { output += sentence.get(SentimentCoreAnnotations.SentimentClass.class); output += "\n"; } return output; } }
ตัววิเคราะห์ใช้ไลบรารี Stanford NLP เพื่อกำหนดความรู้สึกของข้อความและสร้างสตริงที่มีคำอธิบายประกอบความคิดเห็นสำหรับแต่ละประโยคในข้อความ
SentimentServer.java
package seanwang; // ... public class SentimentServer { public static SentimentHandler handler; public static SentimentAnalysisService.Processor processor; public static void main(String [] args) { try { handler = new SentimentHandler(); processor = new SentimentAnalysisService.Processor(handler); Runnable simple = new Runnable() { public void run() { simple(processor); } }; new Thread(simple).start(); } catch (Exception x) { x.printStackTrace(); } } public static void simple(SentimentAnalysisService.Processor processor) { try { TServerTransport serverTransport = new TServerSocket(9090); TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); System.out.println("Starting the simple server..."); server.serve(); } catch (Exception e) { e.printStackTrace(); } } }
โปรดทราบว่าฉันไม่ได้รวมไฟล์ SentimentAnalysisService.java
ไว้ที่นี่ เนื่องจากเป็นไฟล์ที่สร้างขึ้น คุณจะต้องวางรหัสที่สร้างขึ้นในที่ที่รหัสที่เหลือของคุณสามารถเข้าถึงได้
ตอนนี้เรามีเซิร์ฟเวอร์แล้ว มาเขียนไคลเอนต์ Python เพื่อใช้เซิร์ฟเวอร์กัน
client.py
from sentiment import SentimentAnalysisService from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol class SentimentClient: def __init__(self, server='localhost', socket=9090): transport = TSocket.TSocket(server, socket) transport = TTransport.TBufferedTransport(transport) protocol = TBinaryProtocol.TBinaryProtocol(transport) self.transport = transport self.client = SentimentAnalysisService.Client(protocol) self.transport.open() def __del__(self): self.transport.close() def analyze(self, sentence): return self.client.sentimentAnalyze(sentence) if __name__ == '__main__': client = SentimentClient() print(client.analyze('An amazingly wonderful sentence'))
เรียกใช้สิ่งนี้และคุณควรเห็น:
Very Positive
ยอดเยี่ยม! ตอนนี้เรามีเซิร์ฟเวอร์ที่ทำงานและพูดคุยกับลูกค้าแล้ว มารวมเข้ากับบอทอีเมลโดยสร้างอินสแตนซ์ไคลเอนต์และไพพ์อีเมลเข้าไป
import client # ... @app.route('/analyze', methods=['POST']) def analyze(): sentiment_client = client.SentimentClient() with open('logfile.txt', 'a') as fp_log: fp_log.write(str(request.form.get('text'))) fp_log.write(request.form.get('text')) fp_log.write(sentiment_client.analyze(request.form.get('text'))) return "Got it"
ตอนนี้ ปรับใช้บริการ Java ของคุณกับเครื่องเดียวกันกับที่คุณใช้เว็บเซิร์ฟเวอร์ เริ่มบริการ และรีสตาร์ทแอป ส่งอีเมลถึงบอทด้วยประโยคทดสอบ และคุณควรเห็นสิ่งนี้ในล็อกไฟล์:
Amazingly wonderfully positive and beautiful sentence. Very Positive
กำลังวิเคราะห์อีเมล
ไม่เป็นไร! ตอนนี้เรามีบอทอีเมลที่สามารถวิเคราะห์ความคิดเห็นได้แล้ว! เราสามารถส่งอีเมลและรับแท็กความรู้สึกสำหรับแต่ละประโยคที่เราส่ง ตอนนี้ มาสำรวจว่าเราจะทำให้ข่าวกรองสามารถดำเนินการได้อย่างไร
เพื่อให้ง่ายขึ้น ให้เน้นที่อีเมลที่มีประโยคเชิงลบและเชิงลบมาก ลองใช้ระบบการให้คะแนนแบบง่ายๆ และบอกว่าถ้าอีเมลมีประโยคแสดงความรู้สึกเชิงลบมากกว่า 75% เราจะทำเครื่องหมายว่าเป็นอีเมลแจ้งเตือนที่อาจต้องมีการตอบกลับทันที ลองใช้ตรรกะการให้คะแนนในเส้นทางการวิเคราะห์:
@app.route('/analyze', methods=['POST']) def analyze(): text = str(request.form.get('text')) sentiment_client = client.SentimentClient() text.replace('\n', '') # remove all new lines sentences = text.rstrip('.').split('.') # remove the last period before splitting negative_sentences = [ sentence for sentence in sentences if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very negative'] # remove newline char ] urgent = len(negative_sentences) / len(sentences) > 0.75 with open('logfile.txt', 'a') as fp_log: fp_log.write("Received: %s" % (request.form.get('text'))) fp_log.write("urgent = %s" % (str(urgent))) return "Got it"
โค้ดด้านบนมีสมมติฐานบางประการ แต่จะใช้งานได้เพื่อการสาธิต ส่งอีเมลสองสามฉบับไปยังบอทของคุณและคุณควรเห็นการวิเคราะห์อีเมลในบันทึก:
Received: Here is a test for the system. This is supposed to be a non-urgent request. It's very good! For the most part this is positive or neutral. Great things are happening! urgent = False Received: This is an urgent request. Everything is truly awful. This is a disaster. People hate this tasteless mail. urgent = True
ส่งการแจ้งเตือน
เกือบเสร็จแล้ว!
เราได้สร้างบอทอีเมลที่สามารถรับอีเมล วิเคราะห์ความคิดเห็น และพิจารณาว่าอีเมลต้องการการดูแลทันทีหรือไม่ ตอนนี้ เราแค่ต้องส่งข้อความแจ้งเตือนเมื่ออีเมลเป็นลบโดยเฉพาะ
เราจะใช้ Twilio เพื่อส่งข้อความแจ้งเตือน Python API ของพวกเขาซึ่งมีการบันทึกไว้ที่นี่ค่อนข้างตรงไปตรงมา มาแก้ไขเส้นทางการวิเคราะห์เพื่อส่งคำขอเมื่อได้รับคำขอเร่งด่วน
def send_message(body): twilio_client.messages.create( to=on_call, from_=os.getenv('TWILIO_PHONE_NUMBER'), body=body ) app = Flask(__name__) @app.route('/analyze', methods=['POST']) def analyze(): text = str(request.form.get('text')) sentiment_client = client.SentimentClient() text.replace('\n', '') # remove all new lines sentences = text.rstrip('.').split('.') # remove the last period before splitting negative_sentences = [ sentence for sentence in sentences if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very negative'] # remove newline char ] urgent = len(negative_sentences) / len(sentences) > 0.75 if urgent: send_message('Highly negative email received. Please take action') with open('logfile.txt', 'a') as fp_log: fp_log.write("Received: " % request.form.get('text')) fp_log.write("urgent = %s" % (str(urgent))) fp_log.write("\n") return "Got it"
คุณจะต้องตั้งค่าตัวแปรสภาพแวดล้อมของคุณให้เป็นข้อมูลประจำตัวของบัญชี Twilio และตั้งค่าหมายเลขโทรเป็นโทรศัพท์ที่คุณสามารถตรวจสอบได้ เมื่อคุณทำเสร็จแล้ว ให้ส่งอีเมลไปที่ปลายทางการวิเคราะห์ และคุณควรเห็นข้อความที่ส่งไปยังหมายเลขโทรศัพท์ที่เป็นปัญหา
และเสร็จแล้ว!
การประมวลผลภาษาธรรมชาติทำได้ง่ายด้วย Stanford NLP
ในบทความนี้ คุณได้เรียนรู้วิธีสร้างบอทวิเคราะห์ความคิดเห็นทางอีเมลโดยใช้ไลบรารี Stanford NLP ไลบรารีช่วยสรุปรายละเอียดที่สำคัญทั้งหมดของการประมวลผลภาษาธรรมชาติ และให้คุณใช้เป็นส่วนประกอบสำคัญสำหรับแอปพลิเคชัน NLP ของคุณ
ฉันหวังว่าโพสต์นี้จะแสดงให้เห็นหนึ่งในการประยุกต์ใช้การวิเคราะห์ความเชื่อมั่นที่มีศักยภาพอันน่าทึ่ง และสิ่งนี้เป็นแรงบันดาลใจให้คุณสร้างแอปพลิเคชัน NLP ของคุณเอง
คุณสามารถค้นหาโค้ดสำหรับบอทวิเคราะห์ความคิดเห็นทางอีเมลได้จากบทช่วยสอน NLP นี้ใน GitHub