portaldacalheta.pt
  • Основен
  • Дизайн На Марката
  • Тенденции
  • Инструменти И Уроци
  • Технология
Back-End

Пълнотекстово търсене на диалози с Apache Lucene: Урок



Apache Lucene е библиотека на Java, използвана за пълнотекстово търсене на документи и е в основата на сървърите за търсене като Solr и Elasticsearch . Той може също да бъде вграден в Java приложения, като приложения за Android или уеб бекендове.

Въпреки че опциите за конфигурация на Lucene са обширни, те са предназначени за използване от разработчици на бази данни върху общ корпус от текст. Ако документите ви имат специфична структура или тип съдържание, можете да се възползвате от двете, за да подобрите качеството на търсене и възможностите за заявки.



търсене на пълен текст с apache lucene



Като пример за този вид персонализация, в този урок по Lucene ще индексираме корпуса на Проект Гутенберг , която предлага хиляди безплатни електронни книги. Знаем, че много от тези книги са романи. Да предположим, че се интересуваме особено от диалог в рамките на тези романи. Нито Lucene, Elasticsearch, нито Solr предлагат готови инструменти за идентифициране на съдържанието като диалог. Всъщност те ще изхвърлят пунктуацията на най-ранните етапи от анализа на текста, което противоречи на възможността да се идентифицират части от текста, които са диалог. Следователно в тези ранни етапи трябва да започне нашето персонализиране.



Парчета от тръбопровода за анализ на луцен Apache

The Анализ на луцен JavaDoc осигурява добър преглед на всички движещи се части в конвейера за анализ на текст.

На високо ниво можете да мислите за конвейера за анализ като за консумиране на суров поток от символи в началото и създаване на „термини“, приблизително съответстващи на думи, в края.



Стандартният тръбопровод за анализ може да бъде визуализиран като такъв:

Тръбопровод за анализ на луцен



Ще видим как да персонализираме този конвейер, за да разпознава региони от текст, маркирани с двойни кавички, които ще нарека диалог, и след това ще извадим съвпадения, които се появяват при търсене в тези региони.

Четене на герои

Когато документите първоначално се добавят към индекса, символите се четат от Java InputStream , и така те могат да идват от файлове, бази данни, обаждания към уеб услуги и др. За да създадем индекс за Project Gutenberg, изтегляме електронните книги и създаваме малко приложение, което да чете тези файлове и да ги записва в индекса. Създаването на индекс на 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);

Виждаме, че всяка електронна книга ще съответства на един Луцен Document така че по-късно резултатите от търсенето ни ще бъдат списък със съответстващи книги. Store.YES показва, че съхраняваме заглавие поле, което е само името на файла. Не искаме да съхраняваме тяло на електронната книга, тъй като тя не е необходима при търсене и само би загубила дисково пространство.

Действителното отчитане на потока започва с addDocument. IndexWriter изтегля жетони от края на тръбопровода. Това изтегляне продължава през тръбата до първия етап, Tokenizer, чете от InputStream.



Също така имайте предвид, че не затваряме потока, тъй като Lucene се справя с това вместо нас.

Токенизиране на герои

Луценът StandardTokenizer изхвърля пунктуацията и така нашата персонализация ще започне тук, тъй като трябва да запазим кавичките.



Документацията за StandardTokenizer ви кани да копирате изходния код и да го съобразите с вашите нужди, но това решение би било ненужно сложно. Вместо това ще разширим CharTokenizer, което ви позволява да посочите символи за 'приемане', при което тези, които не са 'приети', ще бъдат третирани като разделители между жетоните и изхвърлени. Тъй като се интересуваме от думи и цитати около тях, нашият персонализиран токенизатор е просто:

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 ще излъчва този допълнителен знак. Луценът всъщност осигурява двойка методи, 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 на false, за да се избегне този клон при следващото повикване и след това се върне true, което показва, че е наличен друг маркер.

@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 е false), сме готови и просто се връщаме.

  2. Ако имаме знак от повече от един знак и един от тези символи е кавичка, ние разделяме символа.

    tdd и bdd в agile
  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 за тази цел. Полезните товари са байтови масиви, които се съхраняват заедно с термини в индекса и могат да бъдат прочетени по-късно по време на търсене. Това означава, че нашият флаг ще заема разточително цял байт, така че допълнителни полезни товари могат да бъдат внедрени като битови флагове, за да се спести място.

По-долу има нов филтър, 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]

Обвързване на токенизатори и филтри заедно

An Analyzer е отговорен за сглобяването на тръбопровода за анализ, обикновено чрез комбиниране на Tokenizer с поредица от TokenFilter s. Analyzer s могат също да определят как този тръбопровод се използва повторно между анализите. Не е нужно да се притесняваме за това, тъй като нашите компоненти не изискват нищо освен повикване към reset() между употребите, което Lucene винаги ще прави. Просто трябва да направим сглобяването, като приложим Analyzer#createComponents(String):

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 за да се избегне загубата на време за инжектиране на полезния товар на диалога стоп думи които в крайна сметка ще бъдат премахнати.

Ето визуализация на новия ни тръбопровод в действие (минус онези части от стандартния тръбопровод, които сме премахнали или вече сме виждали):

Нова визуализация на тръбопровода в апаче луцен

raspberry pi като сървър

DialogueAnalyzer вече може да се използва като всеки друг запас Analyzer ще бъде и сега можем да изградим индекса и да преминем към търсене.

Пълнотекстово търсене на диалог

Ако искахме само да търсим диалог, можехме просто да изхвърлим всички символи извън офертата и щяхме да свършим. Вместо това, като оставихме всички оригинални символи непокътнати, ние си дадохме гъвкавостта или да изпълняваме заявки, които отчитат диалога, или да третираме диалога като всяка друга част от текста.

Основите на заявките за индекс на Луцен са добре документирани . За нашите цели е достатъчно да знаем, че заявките са съставени от 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 съдържащ полезния товар: трябва да проверим байта при offset, тъй като не можем да приемем, че масивът от байтове е същият полезен товар, който сме съхранявали по-рано. Когато чете индекса, Lucene няма да губи памет, заделяйки отделен байтов масив само за извикване към scorePayload, така че получаваме препратка към съществуващ байтов масив. Когато кодирате срещу API на Lucene, струва си да имате предвид, че производителността е приоритет, много преди удобството на разработчика.

Сега, когато имаме нашите нови Similarity изпълнението, след това трябва да бъде зададено на IndexSearcher използвани за изпълнение на заявки:

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

Запитвания и условия

Сега, когато нашите IndexSearcher можем да вкараме полезен товар, ние също трябва да изградим заявка, която да е наясно с полезния товар. 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 директно в нашите приложения или като част от сървър, ние можем да извършваме търсене на пълен текст в реално време над гигабайта съдържание. Освен това, чрез персонализиран анализ и оценка, можем да се възползваме от специфичните за домейна функции в нашите документи, за да подобрим уместността на резултатите или персонализираните заявки.

кое от следните е добре запознат с html5, javascript и css?

Пълните списъци с кодове за този урок по Lucene са на разположение на GitHub . Репото съдържа две приложения: LuceneIndexerApp за изграждане на индекса и LuceneQueryApp за изпълнение на заявки.

Корпусът на проект Гутенберг, който може да бъде получен като образ на диск чрез BitTorrent , съдържа много книги, които си струва да се прочетат (или с луцен, или просто по старомодния начин).

Честито индексиране!

Тъмни потребителски интерфейси. Добрите и лошите. Какво да правим и какво не.

Ux Дизайн

Тъмни потребителски интерфейси. Добрите и лошите. Какво да правим и какво не.
Front-end Frameworks: Решения или раздути проблеми?

Front-end Frameworks: Решения или раздути проблеми?

Технология

Популярни Публикации
Fastlane: Автоматизация на iOS в круиз контрол
Fastlane: Автоматизация на iOS в круиз контрол
Ефективни комуникационни стратегии за дизайнери
Ефективни комуникационни стратегии за дизайнери
Изследване на контролирани алгоритми за машинно обучение
Изследване на контролирани алгоритми за машинно обучение
10-те UX доставки, които топ дизайнерите използват
10-те UX доставки, които топ дизайнерите използват
Тест за използваемост за преобразуване: Спрете да следвате тенденциите. Започнете с данните
Тест за използваемост за преобразуване: Спрете да следвате тенденциите. Започнете с данните
 
Кеширане през пролетта с EhCache анотации
Кеширане през пролетта с EhCache анотации
Разбиване на топ 5 мита за отдалечените работници
Разбиване на топ 5 мита за отдалечените работници
Неформално въведение в DOCX
Неформално въведение в DOCX
Проектиране за интерактивна среда и интелигентни пространства
Проектиране за интерактивна среда и интелигентни пространства
Най-добрите UX инструменти (с инфографика)
Най-добрите UX инструменти (с инфографика)
Популярни Публикации
  • настройка на производителността в sql server 2012 стъпка по стъпка
  • corp c срещу corp s
  • Пример за изпращане на ъглова 4 форма
  • Как да спам за кредитни карти pdf
  • превърнете raspberry pi в уеб сървър
  • кой е един от начините, по които отрицателното търговско салдо може да бъде преобразувано в платежен баланс от din_perelink?
Категории
  • Дизайн На Марката
  • Тенденции
  • Инструменти И Уроци
  • Технология
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt