كيفية البحث عن ملف المستنقعات في 104 سطور من التعليمات البرمجية في بيثون

استمرارًا لموضوع النصوص القصيرة المفيدة ، أود أن أطلع القراء على إمكانية بناء بحث عن طريق محتوى الملفات والصور في 104 سطراً. بالتأكيد لن يكون حلاً محيرًا للعقل - ولكنه سيعمل على تلبية الاحتياجات البسيطة. أيضًا ، لن يخترع المقال أي شيء - كل الحزم مفتوحة المصدر.



ونعم - يتم أيضًا حساب الأسطر الفارغة في الكود. يتم تقديم عرض صغير للعمل في نهاية المقال.



نحتاج إلى python3 ، الذي تم تنزيله بواسطة Tesseract 5 ، ونموذج distiluse-base-multilingual-cased من حزمة Sentence-Transformers . أولئك الذين يفهمون بالفعل ما سيحدث بعد ذلك لن يكونوا مثيرين للاهتمام.



في غضون ذلك ، سيبدو كل ما نحتاجه كما يلي:



أول 18 سطرًا
import numpy as np
import os, sys, glob

os.environ['PATH'] += os.pathsep + os.path.join(os.getcwd(), 'Tesseract-OCR')
extensions = [
    '.xlsx', '.docx', '.pptx',
    '.pdf', '.txt', '.md', '.htm', 'html',
    '.jpg', '.jpeg', '.png', '.gif'
]

import warnings; warnings.filterwarnings('ignore')
import torch, textract, pdfplumber
from cleantext import clean
from razdel import sentenize
from sklearn.neighbors import NearestNeighbors
from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer('./distillUSE')





ستكون هناك حاجة ، كما ترون ، بشكل لائق ، ويبدو أن كل شيء جاهز ، لكن لا يمكنك الاستغناء عن ملف. على وجه الخصوص ، لا يعمل textract (ليس من Amazon ، الذي يتم دفعه) بطريقة ما بشكل جيد مع ملفات pdf الروسية ، حيث يمكنك استخدام pdfplumber . علاوة على ذلك ، يعد تقسيم النص إلى جمل مهمة صعبة ، ويقوم razdel بعمل ممتاز مع اللغة الروسية في هذه الحالة .



أولئك الذين لم نسمع عن scikit تعلم - I الحسد أنه في باختصار، فإن NearestNeighbors الخوارزمية في أنه يتذكر النواقل ويعطي مهلة أقرب منها. بدلا من scikit التعلم، يمكنك استخدام faiss أو مزعجة، أو حتى elasticsearch على سبيل المثال .



الشيء الرئيسي هو تحويل نص (أي) ملف فعليًا إلى متجه ، وهذا ما يفعلونه:



36 سطرًا من التعليمات البرمجية التالية
def processor(path, embedder):
    try:
        if path.lower().endswith('.pdf'):
            with pdfplumber.open(path) as pdf:
                if len(pdf.pages):
                    text = ' '.join([
                        page.extract_text() or '' for page in pdf.pages if page
                    ])
        elif path.lower().endswith('.md') or path.lower().endswith('.txt'):
            with open(path, 'r', encoding='UTF-8') as fd:
                text = fd.read()
        else:
            text = textract.process(path, language='rus+eng').decode('UTF-8')
        if path.lower()[-4:] in ['.jpg', 'jpeg', '.gif', '.png']:
            text = clean(
                text,
                fix_unicode=False, lang='ru', to_ascii=False, lower=False,
                no_line_breaks=True
            )
        else:
            text = clean(
                text,
                lang='ru', to_ascii=False, lower=False, no_line_breaks=True
            )
        sentences = list(map(lambda substring: substring.text, sentenize(text)))
    except Exception as exception:
        return None
    if not len(sentences):
        return None
    return {
        'filepath': [path] * len(sentences),
        'sentences': sentences,
        'vectors': [vector.astype(float).tolist() for vector in embedder.encode(
            sentences
        )]
    }





حسنًا ، تظل المسألة تقنية - لتصفح جميع الملفات واستخراج المتجهات والعثور على أقرب نقطة إلى الاستعلام من خلال مسافة جيب التمام.



الكود المتبقي
def indexer(files, embedder):
    for file in files:
        processed = processor(file, embedder)
        if processed is not None:
            yield processed

def counter(path):
    if not os.path.exists(path):
        return None
    for file in glob.iglob(path + '/**', recursive=True):
        extension = os.path.splitext(file)[1].lower()
        if extension in extensions:
            yield file

def search(engine, text, sentences, files):
    indices = engine.kneighbors(
        embedder.encode([text])[0].astype(float).reshape(1, -1),
        return_distance=True
    )

    distance = indices[0][0][0]
    position = indices[1][0][0]

    print(
        ' "%.3f' % (1 - distance / 2),
        ': "%s",  "%s"' % (sentences[position], files[position])
    )

print('  "%s"' % sys.argv[1])
paths = list(counter(sys.argv[1]))

print(' "%s"' % sys.argv[1])
db = list(indexer(paths, embedder))

sentences, files, vectors = [], [], []
for item in db:
    sentences += item['sentences']
    files += item['filepath']
    vectors += item['vectors']

engine = NearestNeighbors(n_neighbors=1, metric='cosine').fit(
    np.array(vectors).reshape(len(vectors), -1)
)

query = input(' : ')
while query:
    search(engine, query, sentences, files)
    query = input(' : ')





يمكنك تشغيل كل الكود مثل هذا:



python3 app.py /path/to/your/files/


هذا هو الحال مع الكود.



وها هو العرض التوضيحي الموعود.



أخذت خبرين من "Lenta.ru" ، ووضعت أحدهما في ملف gif من خلال الطلاء سيئ السمعة ، والآخر في ملف نصي.



First.gif




ملف .txt الثاني
, . .



, - . , , , . . , .



, , , . . .



, - - .



, №71 , , , . 10 , . — .



وهنا رسم متحرك gif يوضح كيف يعمل. مع وحدة معالجة الرسومات ، بالطبع ، كل شيء يعمل بشكل أكثر بهجة.



مظاهرة ، من الأفضل النقر على الصورة






شكرا للقراءة! ما زلت آمل أن تكون هذه الطريقة مفيدة لشخص ما.



All Articles