portaldacalheta.pt
  • หลัก
  • การทำกำไรและประสิทธิภาพ
  • การออกแบบ Ux
  • เทคโนโลยี
  • การออกแบบตราสินค้า
ส่วนหลัง

การค้นหาข้อความแบบเต็มของบทสนทนาด้วย Apache Lucene: บทช่วยสอน



Apache Lucene เป็นไลบรารี Java ที่ใช้สำหรับการค้นหาข้อความแบบเต็มของเอกสารและเป็นหัวใจหลักของเซิร์ฟเวอร์การค้นหาเช่น Solr และ ยางยืด . นอกจากนี้ยังสามารถฝังลงในแอปพลิเคชัน Java เช่นแอป Android หรือเว็บแบ็กเอนด์

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



ค้นหาข้อความแบบเต็มด้วย apache lucene



ตัวอย่างของการปรับแต่งประเภทนี้ในบทช่วยสอน Lucene นี้เราจะจัดทำดัชนีคลังข้อมูลของ โครงการ Gutenberg ซึ่งให้บริการ e-book ฟรีหลายพันเล่ม เรารู้ว่าหนังสือเหล่านี้หลายเล่มเป็นนวนิยาย สมมติว่าเราสนใจเป็นพิเศษในไฟล์ บทสนทนา ภายในนวนิยายเหล่านี้ ทั้ง Lucene, Elasticsearch และ Solr ไม่มีเครื่องมือสำเร็จรูปในการระบุเนื้อหาเป็นบทสนทนา ในความเป็นจริงพวกเขาจะทิ้งเครื่องหมายวรรคตอนในขั้นตอนแรกสุดของการวิเคราะห์ข้อความซึ่งสวนทางกับความสามารถในการระบุส่วนของข้อความที่เป็นบทสนทนา ดังนั้นจึงอยู่ในช่วงเริ่มต้นที่ต้องเริ่มการปรับแต่งของเรา



ชิ้นส่วนของท่อวิเคราะห์ Apache Lucene

การวิเคราะห์ Lucene JavaDoc ให้ภาพรวมที่ดีของชิ้นส่วนที่เคลื่อนไหวทั้งหมดในไปป์ไลน์การวิเคราะห์ข้อความ

ในระดับสูงคุณสามารถคิดว่าขั้นตอนการวิเคราะห์เป็นการใช้กระแสข้อมูลดิบของตัวละครในตอนเริ่มต้นและสร้าง 'คำศัพท์' โดยประมาณที่สอดคล้องกับคำในตอนท้าย



ไปป์ไลน์การวิเคราะห์มาตรฐานได้ เห็นภาพ เช่นนี้:

ท่อส่งการวิเคราะห์ Lucene



เราจะดูวิธีปรับแต่งไปป์ไลน์นี้เพื่อรับรู้ขอบเขตของข้อความที่มีเครื่องหมายอัญประกาศคู่ซึ่งฉันจะเรียกว่าบทสนทนาจากนั้นจะจับคู่การจับคู่ที่เกิดขึ้นเมื่อค้นหาในภูมิภาคเหล่านั้น

การอ่านอักขระ

เมื่อเอกสารถูกเพิ่มลงในดัชนีเริ่มแรกอักขระจะถูกอ่านจาก Java InputStream และสามารถมาจากไฟล์ฐานข้อมูลการเรียกใช้บริการเว็บ ฯลฯ ในการสร้างดัชนีสำหรับ Project Gutenberg เราดาวน์โหลด e-book และสร้างแอปพลิเคชันขนาดเล็กเพื่ออ่านไฟล์เหล่านี้และเขียนลงในดัชนี การสร้างดัชนี Lucene และไฟล์การอ่านเป็นเส้นทางที่เดินทางได้ดีดังนั้นเราจะไม่สำรวจมากนัก รหัสสำคัญในการสร้างดัชนีคือ:



IndexWriter writer = ...; BufferedReader reader = new BufferedReader(new InputStreamReader(... fileInputStream ...)); Document document = new Document(); document.add(new StringField('title', fileName, Store.YES)); document.add(new TextField('body', reader)); writer.addDocument(document);

เราจะเห็นได้ว่า e-book แต่ละเล่มจะสอดคล้องกับ Lucene Document ดังนั้นในภายหลังผลการค้นหาของเราจะเป็นรายการหนังสือที่ตรงกัน Store.YES แสดงว่าเราจัดเก็บไฟล์ หัวข้อ ซึ่งเป็นเพียงชื่อไฟล์ เราไม่ต้องการจัดเก็บไฟล์ ร่างกาย อย่างไรก็ตามเนื่องจากไม่จำเป็นในการค้นหาและจะทำให้เสียเนื้อที่ดิสก์เท่านั้น

การอ่านสตรีมที่แท้จริงเริ่มต้นด้วย addDocument IndexWriter ดึงโทเค็นจากปลายท่อ การดึงนี้จะย้อนกลับไปตามท่อจนถึงขั้นตอนแรก Tokenizer อ่านจาก InputStream



โปรดทราบว่าเราไม่ได้ปิดสตรีมเนื่องจาก Lucene จัดการเรื่องนี้ให้เรา

Tokenizing อักขระ

ลูซีน StandardTokenizer ทิ้งเครื่องหมายวรรคตอนดังนั้นการปรับแต่งของเราจะเริ่มต้นที่นี่เนื่องจากเราจำเป็นต้องรักษาเครื่องหมายคำพูดไว้



เอกสารสำหรับ StandardTokenizer เชิญให้คุณคัดลอกซอร์สโค้ดและปรับแต่งให้เข้ากับความต้องการของคุณ แต่โซลูชันนี้จะซับซ้อนโดยไม่จำเป็น แต่เราจะขยาย CharTokenizer ซึ่งช่วยให้คุณสามารถระบุอักขระที่จะ 'ยอมรับ' ได้โดยที่อักขระที่ไม่ 'ยอมรับ' จะถือว่าเป็นตัวคั่นระหว่างโทเค็นและโยนทิ้งไป เนื่องจากเราสนใจคำและใบเสนอราคาที่อยู่รอบตัว Tokenizer ที่กำหนดเองของเราจึงเป็นเพียง:

public class QuotationTokenizer extends CharTokenizer { @Override protected boolean isTokenChar(int c) return Character.isLetter(c) }

รับกระแสอินพุตของ [He said, 'Good day'.] โทเค็นที่สร้างจะเป็น [He], [said], ['Good], [day']

วิธีการสร้างหุ่นยนต์

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

การแยกโทเค็นโดยใช้ฟิลเตอร์

หลังจากโทเค็นไนเซอร์มาพร้อมกับ TokenFilter วัตถุ หมายเหตุบังเอิญว่า กรอง เป็นการเรียกชื่อผิดเล็กน้อยเป็น TokenFilter สามารถเพิ่มลบหรือแก้ไขโทเค็น

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

การล้างข้อมูลนี้จะเกี่ยวข้องกับการผลิตส่วนเสริม เริ่มใบเสนอราคา โทเค็นหากเครื่องหมายคำพูดปรากฏที่จุดเริ่มต้นของคำหรือ จบคำพูด โทเค็นหากใบเสนอราคาปรากฏในตอนท้าย เราจะละเว้นการจัดการคำที่ยกมาเพียงคำเดียวเพื่อความเรียบง่าย

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

คุณลักษณะที่เราสนใจ ณ จุดนี้ในไปป์ไลน์คือ:

  • CharTermAttribute: ประกอบด้วย char[] บัฟเฟอร์ที่ถืออักขระของโทเค็นปัจจุบัน เราจะต้องจัดการสิ่งนี้เพื่อลบใบเสนอราคาหรือสร้างโทเค็นใบเสนอราคา

  • TypeAttribute: ประกอบด้วย 'ประเภท' ของโทเค็นปัจจุบัน เนื่องจากเรากำลังเพิ่มเครื่องหมายคำพูดเริ่มต้นและสิ้นสุดลงในสตรีมโทเค็นเราจะแนะนำสองประเภทใหม่โดยใช้ตัวกรองของเรา

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

คุณอาจสงสัยว่าเหตุใด API สำหรับจัดการสตรีมโทเค็นจึงมีความซับซ้อนและโดยเฉพาะอย่างยิ่งทำไมเราไม่สามารถทำบางอย่างเช่น String#split บนโทเค็นที่เข้ามา เนื่องจาก Lucene ได้รับการออกแบบมาสำหรับการจัดทำดัชนีค่าใช้จ่ายที่มีความเร็วสูงโดยที่โทเค็นไนเซอร์และตัวกรองในตัวสามารถเคี้ยวข้อความขนาดกิกะไบต์ได้อย่างรวดเร็วในขณะที่ใช้หน่วยความจำเพียงเมกะไบต์ เพื่อให้บรรลุสิ่งนี้จะมีการจัดสรรเพียงเล็กน้อยหรือไม่มีเลยในระหว่างการสร้างโทเค็นและการกรองดังนั้น Attribute อินสแตนซ์ที่กล่าวถึงข้างต้นมีวัตถุประสงค์เพื่อจัดสรรครั้งเดียวและนำกลับมาใช้ใหม่ หากโทเค็นไนเซอร์และตัวกรองของคุณเขียนด้วยวิธีนี้และลดการจัดสรรของตัวเองให้น้อยที่สุดคุณสามารถปรับแต่ง Lucene ได้โดยไม่ทำให้ประสิทธิภาพลดลง

ด้วยเหตุนี้เรามาดูวิธีใช้ตัวกรองที่ใช้โทเค็นเช่น ['Hello] และสร้างโทเค็นทั้งสอง, ['] และ [Hello]:

public class QuotationTokenFilter extends TokenFilter { private static final char QUOTE = '''; public static final String QUOTE_START_TYPE = 'start_quote'; public static final String QUOTE_END_TYPE = 'end_quote'; private final OffsetAttribute offsetAttr = addAttribute(OffsetAttribute.class); private final TypeAttribute typeAttr = addAttribute(TypeAttribute.class); private final CharTermAttribute termBufferAttr = addAttribute(CharTermAttribute.class);

เราเริ่มต้นด้วยการได้รับการอ้างอิงถึงคุณลักษณะบางอย่างที่เราเห็นก่อนหน้านี้ เราต่อท้ายชื่อฟิลด์ด้วย“ Attr” ดังนั้นจึงจะชัดเจนในภายหลังเมื่อเราอ้างถึง เป็นไปได้ว่าบาง Tokenizer การใช้งานไม่ได้ให้คุณสมบัติเหล่านี้ดังนั้นเราจึงใช้ addAttribute เพื่อรับข้อมูลอ้างอิงของเรา addAttribute จะสร้างอินสแตนซ์แอ็ตทริบิวต์หากไม่มีหรือดึงการอ้างอิงที่แชร์ไปยังแอ็ตทริบิวต์ประเภทนั้น โปรดทราบว่า Lucene ไม่อนุญาตให้ใช้แอตทริบิวต์ประเภทเดียวกันหลายอินสแตนซ์พร้อมกัน

private boolean emitExtraToken; private int extraTokenStartOffset, extraTokenEndOffset; private String extraTokenType;

เนื่องจากตัวกรองของเราจะแนะนำโทเค็นใหม่ที่ไม่มีอยู่ในสตรีมเดิมเราจึงจำเป็นต้องมีที่สำหรับบันทึกสถานะของโทเค็นนั้นระหว่างการโทรไปยัง incrementToken เนื่องจากเรากำลังแยกโทเค็นที่มีอยู่ออกเป็นสองรายการจึงเพียงพอที่จะทราบเพียงออฟเซ็ตและประเภทของโทเค็นใหม่ นอกจากนี้เรายังมีธงที่บอกเราว่าจะเรียกต่อไปที่ incrementToken หรือไม่ จะแสดงโทเค็นพิเศษนี้ Lucene มีวิธีการสองวิธีคือ captureState และ restoreState ซึ่งจะทำเพื่อคุณ แต่วิธีการเหล่านี้เกี่ยวข้องกับการจัดสรร State และอาจเป็นเรื่องยุ่งยากกว่าการจัดการสถานะนั้นด้วยตัวเองดังนั้นเราจะหลีกเลี่ยงการใช้

@Override public void reset() throws IOException { emitExtraToken = false; extraTokenStartOffset = -1; extraTokenEndOffset = -1; extraTokenType = null; super.reset(); }

เนื่องจากเป็นส่วนหนึ่งของการหลีกเลี่ยงการจัดสรรในเชิงรุก Lucene สามารถใช้อินสแตนซ์ตัวกรองซ้ำได้ ในสถานการณ์นี้คาดว่าจะมีการเรียกไปที่ reset จะทำให้ตัวกรองกลับสู่สถานะเริ่มต้น ดังนั้นที่นี่เราเพียงแค่รีเซ็ตฟิลด์โทเค็นพิเศษของเรา

@Override public boolean incrementToken() throws IOException { if (emitExtraToken) { advanceToExtraToken(); emitExtraToken = false; return true; } ...

ตอนนี้เรามาถึงส่วนที่น่าสนใจ เมื่อเราดำเนินการ incrementToken เรียกว่าเรามีโอกาสที่จะ ไม่ โทร incrementToken ในขั้นตอนก่อนหน้าของท่อ ด้วยการทำเช่นนั้นเราจึงแนะนำโทเค็นใหม่อย่างมีประสิทธิภาพเนื่องจากเราไม่ได้ดึงโทเค็นจาก Tokenizer

แต่เราเรียก advanceToExtraToken ในการตั้งค่าแอตทริบิวต์สำหรับโทเค็นพิเศษของเราให้ตั้งค่า emitExtraToken เป็นเท็จเพื่อหลีกเลี่ยงสาขานี้ในการเรียกครั้งถัดไปจากนั้นส่งกลับ true ซึ่งระบุว่าโทเค็นอื่นพร้อมใช้งาน

แนวทางปฏิบัติที่ดีที่สุดสำหรับการออกแบบฐานข้อมูล mysql
@Override public boolean incrementToken() throws IOException { ... (emit extra token) ... boolean hasNext = input.incrementToken(); if (hasNext) { char[] buffer = termBufferAttr.buffer(); if (termBuffer.length() > 1) { if (buffer[0] == QUOTE) { splitTermQuoteFirst(); } else if (buffer[termBuffer.length() - 1] == QUOTE) { splitTermWordFirst(); } } else if (termBuffer.length() == 1) { if (buffer[0] == QUOTE) { typeAttr.setType(QUOTE_END_TYPE); } } } return hasNext; }

ส่วนที่เหลือของ incrementToken จะทำหนึ่งในสามสิ่งที่แตกต่างกัน จำได้ว่า termBufferAttr ใช้เพื่อตรวจสอบเนื้อหาของโทเค็นที่มาทางท่อ:

  1. หากเรามาถึงจุดสิ้นสุดของสตรีมโทเค็น (เช่น hasNext เป็นเท็จ) เราก็ดำเนินการเสร็จสิ้นและกลับมา

  2. หากเรามีโทเค็นมากกว่าหนึ่งอักขระและหนึ่งในอักขระเหล่านั้นเป็นเครื่องหมายคำพูดเราจะแยกโทเค็น

  3. หากโทเค็นเป็นเครื่องหมายคำพูดเดี่ยวเราถือว่าเป็นเครื่องหมายคำพูดปิดท้าย เพื่อให้เข้าใจว่าเหตุใดให้สังเกตว่าเครื่องหมายคำพูดเริ่มต้นจะปรากฏทางด้านซ้ายของคำเสมอ (กล่าวคือไม่มีเครื่องหมายวรรคตอนกลาง) ในขณะที่คำพูดลงท้ายสามารถใช้เครื่องหมายวรรคตอน (เช่นในประโยค [He told us to 'go back the way we came.']) ในกรณีเหล่านี้ใบเสนอราคาสิ้นสุดจะเป็นโทเค็นแยกกันอยู่แล้วดังนั้นเราจึงจำเป็นต้องตั้งค่าประเภทเท่านั้น

splitTermQuoteFirst และ splitTermWordFirst จะตั้งค่าแอตทริบิวต์เพื่อสร้างโทเค็นปัจจุบันเป็นคำหรือใบเสนอราคาและตั้งค่าช่อง 'พิเศษ' เพื่ออนุญาตให้ใช้อีกครึ่งหนึ่งในภายหลัง ทั้งสองวิธีมีความคล้ายคลึงกันดังนั้นเราจะดูแค่ splitTermQuoteFirst:

private void splitTermQuoteFirst() { int origStart = offsetAttr.startOffset(); int origEnd = offsetAttr.endOffset(); offsetAttr.setOffset(origStart, origStart + 1); typeAttr.setType(QUOTE_START_TYPE); termBufferAttr.setLength(1); prepareExtraTerm(origStart + 1, origEnd, TypeAttribute.DEFAULT_TYPE); }

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

prepareExtraTerm จะตั้งค่า extra* ฟิลด์และตั้งค่า emitExtraToken เป็นจริง มันถูกเรียกโดยออฟเซ็ตที่ชี้ไปที่โทเค็น“ พิเศษ” (เช่นคำที่ตามหลังเครื่องหมายคำพูด)

ทั้งหมดของ QuotationTokenFilter คือ พร้อมใช้งานบน GitHub .

นอกเหนือจากนั้นในขณะที่ตัวกรองนี้สร้างโทเค็นพิเศษเพียงหนึ่งโทเค็น แต่สามารถขยายวิธีการนี้เพื่อแนะนำโทเค็นพิเศษจำนวนหนึ่งโดยพลการ เพียงแค่แทนที่ extra* ฟิลด์ที่มีคอลเลกชันหรือดีกว่าคืออาร์เรย์ที่มีความยาวคงที่หากมีการ จำกัด จำนวนโทเค็นพิเศษที่สามารถสร้างได้ ดู SynonymFilter และ PendingInput ชั้นในสำหรับตัวอย่างนี้

การบริโภคโทเค็นใบเสนอราคาและการทำเครื่องหมายบทสนทนา

ตอนนี้เราได้พยายามเพิ่มเครื่องหมายคำพูดเหล่านั้นลงในสตรีมโทเค็นแล้วเราสามารถใช้คำพูดเหล่านั้นเพื่อคั่นส่วนของบทสนทนาในข้อความได้

เนื่องจากเป้าหมายสุดท้ายของเราคือการปรับผลการค้นหาโดยพิจารณาว่าคำต่างๆเป็นส่วนหนึ่งของบทสนทนาหรือไม่เราจึงต้องแนบข้อมูลเมตากับคำเหล่านั้น ลูซีนให้ PayloadAttribute เพื่อจุดประสงค์นี้. Payloads คืออาร์เรย์แบบไบต์ที่จัดเก็บควบคู่ไปกับคำศัพท์ในดัชนีและสามารถอ่านได้ในภายหลังระหว่างการค้นหา ซึ่งหมายความว่าแฟล็กของเราจะใช้ไบต์ทั้งหมดอย่างสิ้นเปลืองดังนั้นจึงสามารถใช้เพย์โหลดเพิ่มเติมเป็นแฟล็กบิตเพื่อประหยัดพื้นที่

ด้านล่างนี้คือตัวกรองใหม่ DialoguePayloadTokenFilter ซึ่งเพิ่มไว้ที่ส่วนท้ายสุดของท่อวิเคราะห์ มันแนบเพย์โหลดที่ระบุว่าโทเค็นเป็นส่วนหนึ่งของบทสนทนาหรือไม่

public class DialoguePayloadTokenFilter extends TokenFilter { private final TypeAttribute typeAttr = getAttribute(TypeAttribute.class); private final PayloadAttribute payloadAttr = addAttribute(PayloadAttribute.class); private static final BytesRef PAYLOAD_DIALOGUE = new BytesRef(new byte[] { 1 }); private static final BytesRef PAYLOAD_NOT_DIALOGUE = new BytesRef(new byte[] { 0 }); private boolean withinDialogue; protected DialoguePayloadTokenFilter(TokenStream input) { super(input); } @Override public void reset() throws IOException { this.withinDialogue = false; super.reset(); } @Override public boolean incrementToken() throws IOException { boolean hasNext = input.incrementToken(); while(hasNext) { boolean isStartQuote = QuotationTokenFilter .QUOTE_START_TYPE.equals(typeAttr.type()); boolean isEndQuote = QuotationTokenFilter .QUOTE_END_TYPE.equals(typeAttr.type()); if (isStartQuote) { withinDialogue = true; hasNext = input.incrementToken(); } else if (isEndQuote) { withinDialogue = false; hasNext = input.incrementToken(); } else { break; } } if (hasNext) { payloadAttr.setPayload(withinDialogue ? PAYLOAD_DIALOGUE : PAYLOAD_NOT_DIALOGUE); } return hasNext; } }

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

ตัวอย่างเช่น DialoguePayloadTokenFilter จะเปลี่ยนกระแสโทเค็น:

[the], [program], [printed], ['], [hello], [world], [']`

ในสตรีมใหม่นี้:

[the][0], [program][0], [printed][0], [hello][1], [world][1]

การผูกโทเค็นและฟิลเตอร์เข้าด้วยกัน

อัน Analyzer มีหน้าที่ในการประกอบไปป์ไลน์การวิเคราะห์โดยทั่วไปจะรวม Tokenizer ด้วยชุดของ TokenFilter s Analyzer s ยังสามารถกำหนดวิธีที่จะใช้ไปป์ไลน์ซ้ำระหว่างการวิเคราะห์ เราไม่จำเป็นต้องกังวลเกี่ยวกับเรื่องนี้เนื่องจากส่วนประกอบของเราไม่ต้องการอะไรเลยนอกจากการโทรไปที่ reset() ระหว่างการใช้งานซึ่ง Lucene มักจะทำ เราต้องทำการประกอบโดยใช้ Analyzer#createComponents(String):

python รับคุณสมบัติของวัตถุ
public class DialogueAnalyzer extends Analyzer { @Override protected TokenStreamComponents createComponents(String fieldName) { QuotationTokenizer tokenizer = new QuotationTokenizer(); TokenFilter filter = new QuotationTokenFilter(tokenizer); filter = new LowerCaseFilter(filter); filter = new StopFilter(filter, StopAnalyzer.ENGLISH_STOP_WORDS_SET); filter = new DialoguePayloadTokenFilter(filter); return new TokenStreamComponents(tokenizer, filter); } }

ดังที่เราเห็นก่อนหน้านี้ตัวกรองมีการอ้างอิงย้อนกลับไปยังขั้นตอนก่อนหน้าในไปป์ไลน์นั่นคือวิธีที่เราสร้างอินสแตนซ์ นอกจากนี้เรายังเลื่อนตัวกรองบางส่วนจาก StandardAnalyzer: LowerCaseFilter และ StopFilter. สองคนนี้ต้องตามมา QuotationTokenFilter เพื่อให้แน่ใจว่ามีการแยกคำพูดใด ๆ เราสามารถยืดหยุ่นได้มากขึ้นในการจัดวาง DialoguePayloadTokenFilter เนื่องจากทุกที่หลังจาก QuotationTokenFilter จะทำ. เราใส่ไว้หลัง StopFilter เพื่อหลีกเลี่ยงการเสียเวลาในการอัดข้อมูลบทสนทนาเข้าไป หยุดคำ ที่จะถูกลบในที่สุด

นี่คือการแสดงภาพของไปป์ไลน์ใหม่ของเราที่กำลังดำเนินการอยู่ (ลบส่วนเหล่านั้นของไปป์ไลน์มาตรฐานที่เราได้นำออกหรือเห็นไปแล้ว):

การแสดงภาพไปป์ไลน์ใหม่ใน apache lucene

DialogueAnalyzer สามารถใช้เป็นสต็อกอื่น ๆ ได้แล้ว Analyzer จะเป็นและตอนนี้เราสามารถสร้างดัชนีและดำเนินการค้นหาต่อไป

การค้นหาข้อความแบบเต็มของบทสนทนา

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

พื้นฐานของการสืบค้นดัชนี Lucene คือ เอกสารอย่างดี . สำหรับวัตถุประสงค์ของเราก็เพียงพอแล้วที่จะทราบว่าข้อความค้นหาประกอบด้วย Term วัตถุติดกันด้วยตัวดำเนินการเช่น MUST หรือ SHOULD พร้อมกับเอกสารการจับคู่ตามข้อกำหนดเหล่านั้น จากนั้นเอกสารการจับคู่จะได้คะแนนตาม Similarity ที่กำหนดค่าได้ วัตถุและผลลัพธ์เหล่านั้นสามารถเรียงลำดับตามคะแนนกรองหรือ จำกัด ตัวอย่างเช่น Lucene ช่วยให้เราสามารถค้นหาเอกสารสิบอันดับแรกที่ต้องมีทั้งสองคำ [hello] และ [world].

การปรับแต่งผลการค้นหาตามบทสนทนาสามารถทำได้โดยปรับคะแนนของเอกสารตามเพย์โหลด จุดขยายแรกสำหรับสิ่งนี้จะอยู่ใน Similarity ซึ่งมีหน้าที่ชั่งน้ำหนักและให้คะแนนคำที่ตรงกัน

ความเหมือนและการให้คะแนน

โดยค่าเริ่มต้นการสืบค้นจะใช้ DefaultSimilarity ซึ่งจะให้น้ำหนักคำศัพท์ตามความถี่ที่เกิดขึ้นในเอกสาร เป็นจุดขยายที่ดีสำหรับการปรับน้ำหนักดังนั้นเราจึงขยายไปสู่การให้คะแนนเอกสารตามน้ำหนักบรรทุกด้วย วิธีการ DefaultSimilarity#scorePayload มีไว้เพื่อวัตถุประสงค์นี้:

public final class DialogueAwareSimilarity extends DefaultSimilarity { @Override public float scorePayload(int doc, int start, int end, BytesRef payload) { if (payload.bytes[payload.offset] == 0) { return 0.0f; } return 1.0f; } }

DialogueAwareSimilarity เพียงแค่ให้คะแนนเพย์โหลดที่ไม่ใช่บทสนทนาเป็นศูนย์ เป็นละ Term สามารถจับคู่ได้หลายครั้งซึ่งอาจมีคะแนนน้ำหนักบรรทุกหลายรายการ การตีความคะแนนเหล่านี้ขึ้นอยู่กับ Query การนำไปใช้งาน

ให้ความสำคัญกับ BytesRef ที่มี payload: เราต้องตรวจสอบไบต์ที่ offset เนื่องจากเราไม่สามารถสรุปได้ว่าอาร์เรย์ไบต์เป็น payload เดียวกับที่เราเก็บไว้ก่อนหน้านี้ เมื่ออ่านดัชนี Lucene จะไม่เสียหน่วยความจำในการจัดสรรอาร์เรย์ไบต์แยกต่างหากสำหรับการเรียกไปที่ scorePayload ดังนั้นเราจึงได้รับการอ้างอิงในอาร์เรย์ไบต์ที่มีอยู่ เมื่อเข้ารหัสกับ Lucene API จะต้องคำนึงว่าประสิทธิภาพเป็นสิ่งสำคัญเหนือความสะดวกของนักพัฒนา

ตอนนี้เรามี Similarity ใหม่ของเราแล้ว จากนั้นจะต้องตั้งค่าบน IndexSearcher ใช้เพื่อดำเนินการค้นหา:

IndexSearcher searcher = new IndexSearcher(... reader for index ...); searcher.setSimilarity(new DialogueAwareSimilarity());

แบบสอบถามและข้อกำหนด

ตอนนี้ IndexSearcher ของเรา สามารถให้คะแนน payloads ได้เราต้องสร้างแบบสอบถามที่ payload-ทราบด้วย PayloadTermQuery สามารถใช้เพื่อจับคู่ Term ในขณะเดียวกันก็ตรวจสอบเพย์โหลดของการแข่งขันเหล่านั้นด้วย:

PayloadTermQuery helloQuery = new PayloadTermQuery(new Term('body', 'hello'), new AveragePayloadFunction());

คำค้นหานี้ตรงกับคำว่า [hello] ภายใน ร่างกาย ฟิลด์ (โปรดจำไว้ว่านี่คือที่ที่เราใส่เนื้อหาของเอกสาร) นอกจากนี้เรายังต้องจัดเตรียมฟังก์ชันเพื่อคำนวณคะแนนน้ำหนักบรรทุกขั้นสุดท้ายจากการแข่งขันทุกเทอมดังนั้นเราจึงเสียบ AveragePayloadFunction ซึ่งเป็นค่าเฉลี่ยของคะแนนน้ำหนักบรรทุกทั้งหมด ตัวอย่างเช่นถ้าคำว่า [hello] เกิดขึ้นภายในบทสนทนาสองครั้งและบทสนทนาภายนอกหนึ่งครั้งคะแนนเพย์โหลดสุดท้ายจะเท่ากับ ²⁄₃ คะแนนน้ำหนักบรรทุกสุดท้ายนี้จะถูกคูณด้วยคะแนนที่จัดทำโดย DefaultSimilarity สำหรับเอกสารทั้งหมด

เราใช้ค่าเฉลี่ยเนื่องจากเราต้องการไม่เน้นย้ำผลการค้นหาที่มีคำจำนวนมากปรากฏนอกบทสนทนาและเพื่อให้คะแนนเป็นศูนย์สำหรับเอกสารโดยไม่มีคำใด ๆ ในบทสนทนาเลย

นอกจากนี้เรายังสามารถแต่งหลาย ๆ PayloadTermQuery วัตถุโดยใช้ BooleanQuery หากเราต้องการค้นหาคำศัพท์หลายคำที่มีอยู่ในบทสนทนา (โปรดทราบว่าลำดับของคำศัพท์นั้นไม่เกี่ยวข้องในคำค้นหานี้แม้ว่าประเภทการสืบค้นอื่น ๆ จะมีการระบุตำแหน่งก็ตาม):

PayloadTermQuery worldQuery = new PayloadTermQuery(new Term('body', 'world'), new AveragePayloadFunction()); BooleanQuery query = new BooleanQuery(); query.add(helloQuery, Occur.MUST); query.add(worldQuery, Occur.MUST);

เมื่อเรียกใช้แบบสอบถามนี้เราจะเห็นว่าโครงสร้างแบบสอบถามและการใช้งานความคล้ายคลึงกันทำงานร่วมกันอย่างไร:

ไปป์ไลน์การวิเคราะห์บทสนทนาของลูซีน

การดำเนินการสืบค้นและคำอธิบาย

ในการดำเนินการค้นหาเราส่งต่อไปที่ IndexSearcher:

TopScoreDocCollector collector = TopScoreDocCollector.create(10); searcher.search(query, new PositiveScoresOnlyCollector(collector)); TopDocs topDocs = collector.topDocs();

Collector ออบเจ็กต์ถูกใช้เพื่อเตรียมการรวบรวมเอกสารที่ตรงกัน

นักสะสมสามารถประกอบขึ้นเพื่อให้เกิดการรวมกันของการเรียงลำดับการ จำกัด และการกรอง ตัวอย่างเช่นเพื่อให้ได้เอกสารการให้คะแนนสิบอันดับแรกที่มีอย่างน้อยหนึ่งคำในบทสนทนาเราจะรวม TopScoreDocCollector และ PositiveScoresOnlyCollector. การรับเฉพาะคะแนนที่เป็นบวกทำให้มั่นใจได้ว่าคะแนนเป็นศูนย์ที่ตรงกัน (กล่าวคือผู้ที่ไม่มีข้อกำหนดในบทสนทนา) จะถูกกรองออก

หากต้องการดูการทำงานของแบบสอบถามนี้เราสามารถดำเนินการได้จากนั้นใช้ IndexSearcher#explain เพื่อดูว่าเอกสารแต่ละฉบับได้รับคะแนนอย่างไร:

for (ScoreDoc result : topDocs.scoreDocs) { Document doc = searcher.doc(result.doc, Collections.singleton('title')); System.out.println('--- document ' + doc.getField('title').stringValue() + ' ---'); System.out.println(this.searcher.explain(query, result.doc)); }

ที่นี่เราทำซ้ำรหัสเอกสารใน TopDocs ได้มาจากการค้นหา เรายังใช้ IndexSearcher#doc เพื่อดึงฟิลด์หัวเรื่องสำหรับการแสดงผล สำหรับคำถามของเราเกี่ยวกับ 'hello' ผลลัพธ์นี้เป็น:

--- Document whelv10.txt --- 0.072256625 = (MATCH) btq, product of: 0.072256625 = weight(body:hello in 7336) [DialogueAwareSimilarity], result of: 0.072256625 = fieldWeight in 7336, product of: 2.345208 = tf(freq=5.5), with freq of: 5.5 = phraseFreq=5.5 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.009765625 = fieldNorm(doc=7336) 1.0 = AveragePayloadFunction.docScore() --- Document daved10.txt --- 0.061311778 = (MATCH) btq, product of: 0.061311778 = weight(body:hello in 6873) [DialogueAwareSimilarity], result of: 0.061311778 = fieldWeight in 6873, product of: 3.3166249 = tf(freq=11.0), with freq of: 11.0 = phraseFreq=11.0 3.1549776 = idf(docFreq=2873, maxDocs=24796) 0.005859375 = fieldNorm(doc=6873) 1.0 = AveragePayloadFunction.docScore() ...

แม้ว่าผลลัพธ์จะเต็มไปด้วยศัพท์แสง แต่เราสามารถดูได้ว่าประเพณีของเรา Similarity การใช้งานถูกใช้ในการให้คะแนนและวิธีการที่ MaxPayloadFunction สร้างตัวคูณเป็น 1.0 สำหรับการแข่งขันเหล่านี้ นี่หมายความว่าน้ำหนักบรรทุกถูกโหลดและทำประตูและการแข่งขันทั้งหมดของ 'Hello' เกิดขึ้นในบทสนทนาดังนั้นผลลัพธ์เหล่านี้จึงอยู่ในอันดับต้น ๆ ที่เราคาดหวังไว้

นอกจากนี้ยังควรชี้ให้เห็นว่าดัชนีสำหรับ Project Gutenberg ที่มีน้ำหนักบรรทุกมีขนาดเกือบสี่กิกะไบต์ แต่ในเครื่องพัฒนาที่เรียบง่ายของฉันการสืบค้นจะเกิดขึ้นทันที เราไม่ได้เสียสละความเร็วใด ๆ เพื่อให้บรรลุเป้าหมายการค้นหาของเรา

ห่อ

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

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

วิธีสร้างปลั๊กอินเวิร์ดเพรส

รายการรหัสทั้งหมดสำหรับบทช่วยสอน Lucene นี้คือ พร้อมใช้งานบน GitHub . repo มีสองแอปพลิเคชั่น: LuceneIndexerApp สำหรับการสร้างดัชนีและ LuceneQueryApp สำหรับการดำเนินการค้นหา

คลังข้อมูลของ Project Gutenberg ซึ่งสามารถรับได้ เป็นภาพดิสก์ผ่าน BitTorrent มีหนังสือน่าอ่านมากมาย (ไม่ว่าจะกับ Lucene หรือแบบเก่า ๆ )

สร้างดัชนีอย่างมีความสุข!

UX Research Methods และ Path to User Empathy

การออกแบบ Ux

UX Research Methods และ Path to User Empathy
หลีกเลี่ยงการปฏิบัติที่ไม่ดีในการออกแบบ iOS และ Android

หลีกเลี่ยงการปฏิบัติที่ไม่ดีในการออกแบบ iOS และ Android

มือถือ

โพสต์ยอดนิยม
Ractive.js - เว็บแอพที่ทำได้ง่าย
Ractive.js - เว็บแอพที่ทำได้ง่าย
การสอน Mirror API: Google Glass สำหรับนักพัฒนาเว็บ
การสอน Mirror API: Google Glass สำหรับนักพัฒนาเว็บ
ตลาดล้านดอลลาร์ดีกว่าตลาดพันล้านดอลลาร์หรือไม่?
ตลาดล้านดอลลาร์ดีกว่าตลาดพันล้านดอลลาร์หรือไม่?
ทำไมฉันต้องใช้ Node.js การสอนเป็นกรณี ๆ ไป
ทำไมฉันต้องใช้ Node.js การสอนเป็นกรณี ๆ ไป
รองประธานองค์กร
รองประธานองค์กร
 
สงครามเย็นแห่งเทคโนโลยี: ยังคงอยู่ที่นี่และยังคงถูกใช้
สงครามเย็นแห่งเทคโนโลยี: ยังคงอยู่ที่นี่และยังคงถูกใช้
ปรับขนาดด้วยความเร็ว: อธิบายเครือข่ายสายฟ้าของ Bitcoin
ปรับขนาดด้วยความเร็ว: อธิบายเครือข่ายสายฟ้าของ Bitcoin
ส่วนประกอบของปฏิกิริยาที่มีประสิทธิภาพ: คำแนะนำในการเพิ่มประสิทธิภาพการตอบสนอง
ส่วนประกอบของปฏิกิริยาที่มีประสิทธิภาพ: คำแนะนำในการเพิ่มประสิทธิภาพการตอบสนอง
คำแนะนำสำหรับนักพัฒนา Android เกี่ยวกับรูปแบบการเรียกดูตัวอย่างข้อมูล
คำแนะนำสำหรับนักพัฒนา Android เกี่ยวกับรูปแบบการเรียกดูตัวอย่างข้อมูล
การรับรองและการประกันการรับประกัน: เครื่องมือการควบรวมกิจการที่ผู้ขายทุกคนควรทราบ
การรับรองและการประกันการรับประกัน: เครื่องมือการควบรวมกิจการที่ผู้ขายทุกคนควรทราบ
โพสต์ยอดนิยม
  • ทำไมการพัฒนา Android ถึงซับซ้อน
  • นักพัฒนาซอฟต์แวร์ไม่ใช้ภาษาโปรแกรมเชิงวัตถุอีกต่อไป
  • อิออน 2 และเชิงมุม 2
  • คู่มือสไตล์การออกแบบวัสดุของ Google
  • aws โซลูชั่น สถาปนิก การเตรียมการรับรอง
หมวดหมู่
  • การทำกำไรและประสิทธิภาพ
  • การออกแบบ Ux
  • เทคโนโลยี
  • การออกแบบตราสินค้า
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt