جستجوی معنایی

این راهنما شما را با نحوه پیاده‌سازی جستجوی معنایی در دستگاه آشنا می‌کند، تکنیک جستجویی که محتوای مرتبط را براساس معنی پُرسمان پیدا می‌کند، نه صرفاً براساس کلیدواژه‌ها. جستجوی معنایی نیاز به ساختن و نگهداری دستی جدول‌های مترادف سفارشی یا طبقه‌بندی‌های برچسب‌گذاری را ازبین می‌برد. برای پیاده‌سازی جستجوی معنایی در دستگاه، از کتابخانه‌های زیر استفاده کنید:

  • کیت توسعه نرم‌افزار ML Kit، به‌ویژه Embedding API، برای تولید جاسازی‌های برداری
  • AndroidX AppSearch برای ذخیره کردن جاسازی‌های برداری و جستجوی داده‌های ساختاریافته بااستفاده از سنجه‌های فاصله کسینوسی برای پیدا کردن محتوا براساس معناشناسی آن‌ها

پیش‌نیازها

برای استفاده از جستجوی معنایی درون‌دستگاهی، باید شرایط لازم دستگاه و حساب زیر را داشته باشید:

  • دستگاهتان از Android AICore پشتیبانی می‌کند. اکثر دستگاه‌های پشتیبانی‌شده AICore از مدل‌های شتاب‌یافته NPU استفاده می‌کنند، درحالی‌که بقیه دستگاه‌های پشتیبانی‌شده به مدل CPU (با شتاب‌دهی XNNPACK) برمی‌گردند.
  • حساب Google در دستگاهتان بخشی از برنامه پیش‌نمای توسعه‌دهندگان نرم‌افزار AICore است.
  • دستگاهتان جدیدترین نسخه AICore Developer Preview APK را داشته باشد: در دستگاهتان، صفحه Android AICore در «فروشگاه Play» را باز کنید و برای نصب جدیدترین نسخه APK، روی به‌روزرسانی تک‌ضرب بزنید. یا به تنظیمات دستگاه بروید، سرویس‌های سیستم یا سرویس‌های Google را جستجو کنید، روی Android AICore (نسخه بتا) تک‌ضرب بزنید، و اگر به‌روزرسانی دردسترس است، روی به‌روزرسانی تک‌ضرب بزنید.
  • دستگاهتان جدیدترین نسخه برنامه «خدمات رایانش خصوصی» را دارد: در دستگاهتان، صفحه «خدمات رایانش خصوصی» در «فروشگاه Play» را باز کنید و برای نصب جدیدترین نسخه برنامه، روی به‌روزرسانی تک‌ضرب بزنید. یا به فروشگاه Play > مدیریت برنامه‌ها و دستگاه > مدیریت > خدمات رایانش خصوصی بروید و اگر به‌روزرسانی دردسترس است، روی به‌روزرسانی تک‌ضرب بزنید.

مدل‌ها و پیکربندی‌های پشتیبانی‌شده

‫ML Kit Embedding API از خانواده مدل EmbeddingGemma متعلق به Google استفاده می‌کند که توسط Android AICore مدیریت می‌شود. به‌طور خاص، Embedding API از EmbeddingGemma 2 (embedding_gemma_v2) برای تولید جاسازی‌ها برای ورودی‌های نوشتاری و تصویری استفاده می‌کند.

مشخصات و محدودیت‌های ورودی در اینجا آمده است:

خصوصیت ورودی EmbeddingGemma 2 یادداشت‌های استفاده
روش‌های پشتیبانی‌شده نوشتار و تصویر (Modality.TEXT، Modality.IMAGE) برای تأیید اینکه ورودی تصویر پشتیبانی می‌شود، در زمان اجرا بررسی کنید که model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) مقدار true را برمی‌گرداند.
حد داده‌واحد ‫۲۵۶ کد بااستفاده از روش model.getTokenLimit() بازرسی کنید. هر تصویر به ۷۰ نشانه تبدیل می‌شود و ۱۸۶ نشانه برای نوشتار همراه باقی می‌ماند.
وضوح تصویر تا ۷۶۸ پیکسل در ضلع کوتاه‌تر سازنده کلاس ImagePart انواع شناسه Bitmap، Uri، یا ByteArray را می‌پذیرد. تصاویری که بعد کوتاه‌تر آن‌ها بیشتر از ۷۶۸ پیکسل باشد، به‌طور خودکار توسط کیت توسعه نرم‌افزار کوچک می‌شوند و نسبت ابعادی آن‌ها حفظ می‌شود. برای عملکرد و استفاده از حافظه بهینه، از نسبت‌های ابعادی خیلی بزرگ یا خیلی کوچک استفاده نکنید.

بُعد بردار برونداد ۷۶۸ است (یک شیء FloatArray از اعداد اعشاری ۳۲ بیتی). می‌توانید بُعد بردار برونداد را در زمان اجرا بااستفاده از روش model.getMetadata()?.dimension بررسی کنید.

شروع کنید

برای پیکربندی پروژه‌تان جهت استفاده از جستجوی معنایی، وابستگی‌های زیر را به فایل build.gradle.kts (یا build.gradle) سطح ماژول خود اضافه کنید.

Kotlin

dependencies {
    // ...
    implementation("com.google.mlkit:genai-embedding:1.0.0-alpha1")
    implementation("androidx.appsearch:appsearch:1.2.0-alpha01")
    implementation("androidx.appsearch:appsearch-local-storage:1.2.0-alpha01")
    implementation("androidx.concurrent:concurrent-futures-ktx:1.3.0")
    // ...
}

Groovy

dependencies {
    // ...
    implementation 'com.google.mlkit:genai-embedding:1.0.0-alpha1'
    implementation 'androidx.appsearch:appsearch:1.2.0-alpha01'
    implementation 'androidx.appsearch:appsearch-local-storage:1.2.0-alpha01'
    implementation 'androidx.concurrent:concurrent-futures-ktx:1.3.0'
    // ...
}

برای بازیابی مدل‌های دردسترس، از روش EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()) استفاده کنید.

تعریف کردن طرح‌واره AppSearch

برای فعال کردن جستجوی شباهت معنایی، باید طرح‌واره AppSearch را تعریف کنید که در آن ویژگی بردار به‌طور صریح برای نمایه‌سازی شباهت پیکربندی شده باشد.

// Define the document schema
val documentSchema = AppSearchSchema.Builder("my_schema_type")
    .addProperty(
        AppSearchSchema.StringPropertyConfig.Builder("text")
            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
            .build()
    )
    .addProperty(
        AppSearchSchema.StringPropertyConfig.Builder("imageUri")
            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
            .build()
    )
    .addProperty(
        AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding")
            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
            .setIndexingType(
                AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_SIMILARITY)
            .setQuantizationType(
                AppSearchSchema.EmbeddingPropertyConfig.QUANTIZATION_TYPE_8_BIT)
            .build()
    )
    .build()

نمایه‌سازی محتوای سند

برای نمایه‌سازی محتوای سند، باید برای هر شیء Content یک جاسازی برداری تولید کنید، آن را در یک نمونه GenericDocument قرار دهید، و آن را در پایگاه داده AppSearch خود ذخیره کنید.

هنگام ساختن شیء EmbeddingVector، باید مقدار فعال modelSignature را نیز ارسال کنید. AppSearch از این رشته modelSignature برای جلوگیری از پرسمان‌های جستجوی ناسازگار استفاده می‌کند.

suspend fun syncDatabase(documents: List<Pair<String, Uri?>>) {

    val modelSignature = model?.getModelSignature() ?: return
    val docsWithEmbedding = mutableListOf<GenericDocument>()

    for ((index, item) in documents.withIndex()) {
        val (content, imageUri) = item
        // 1. Generate embedding (text and/or image)
        val parts = mutableListOf<Part>()
        if (content.isNotBlank()) {
            parts.add(TextPart(content))
        }
        var imagePart: ImagePart? = null
        val vector = try {
            if (imageUri != null && isImageSupported) {
                imagePart = ImagePart(imageUri)
                parts.add(imagePart)
            }
            if (parts.isEmpty()) continue
            val request = EmbeddingRequest(
                Content(parts)
            )
            model?.generateEmbedding(request)?.embedding?.vector
                ?: continue
        } finally {
            imagePart?.close()
        }

        // 2. Build the AppSearch document
        val docBuilder = GenericDocument.Builder<GenericDocument.Builder<*>>(
            "my_namespace",
            "doc_$index",
            "my_schema_type"
        )
        .setPropertyString("text", content)
        // Associate the vector with the signature that generated it
        .setPropertyEmbedding("embedding", EmbeddingVector(vector, modelSignature))

        if (imageUri != null) {
            docBuilder.setPropertyString("imageUri", imageUri.toString())
        }

        docsWithEmbedding.add(docBuilder.build())
    }

    // 3. Clear the old database by force-overriding the schema
    val clearRequest = SetSchemaRequest.Builder().setForceOverride(true).build()
    session?.setSchemaAsync(clearRequest)?.await()

    val schemaRequest = SetSchemaRequest.Builder().addSchemas(documentSchema).build()
    session?.setSchemaAsync(schemaRequest)?.await()

    // 4. Index documents
    val putRequest = PutDocumentsRequest.Builder().addGenericDocuments(docsWithEmbedding).build()
    session?.putAsync(putRequest)?.await()
}

برای انجام پُرسمان معنایی، پُرسمان جستجو (نوشتار و/یا تصویر) را به بردار جاسازی تبدیل کنید، پارامترهای جستجوی برداری را پیکربندی کنید، و پایگاه داده AppSearch خود را پُرسمان کنید.

توصیه‌ها:

  • سنجه پیش‌فرض را روی ثابت SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE تنظیم کنید.
  • نتایج را براساس فرمول "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))" رتبه‌بندی کنید.
  • از رشته منطقی "semanticSearch(getEmbeddingParameter(0), $threshold)" برای فیلتر کردن نتایج استفاده کنید، جایی که شباهت با مقدار آستانه شما مطابقت دارد (معمولاً ۰٫۵ یا بالاتر).
suspend fun search(
    queryText: String,
    imageUri: Uri? = null,
    threshold: Double = 0.5
): List<Triple<String, Uri?, Double>> {
    val modelSignature = model?.getModelSignature() ?: return emptyList()

    // 1. Generate query embedding (text and/or image)
    val parts = mutableListOf<Part>()
    if (queryText.isNotBlank()) {
        parts.add(TextPart(queryText))
    }
    var imagePart: ImagePart? = null
    val queryVector = try {
        if (imageUri != null && isImageSupported) {
            imagePart = ImagePart(imageUri)
            parts.add(imagePart)
        }
        if (parts.isEmpty()) return emptyList()
        val request = EmbeddingRequest(
            content = Content(parts)
        )
        model?.generateEmbedding(request)?.embedding?.vector
            ?: return emptyList()
    } finally {
        imagePart?.close()
    }

    // 2. Configure search specifications
    val searchSpec = SearchSpec.Builder()
        .addFilterSchemas("my_schema_type")
        .setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
        .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
        .setListFilterQueryLanguageEnabled(true)
        .setResultCountPerPage(5)
        // Add the query vector as parameter 0
        .addEmbeddingParameters(EmbeddingVector(queryVector, modelSignature))
        .build()

    // 3. Execute search
    val queryExpression = "semanticSearch(getEmbeddingParameter(0), $threshold)"
    val results = session?.search(queryExpression, searchSpec) ?: return emptyList()

    return try {
        val page = results.nextPageAsync.await()
        page.map {
            val text = it.genericDocument.getPropertyString("text") ?: ""
            val matchedImageUri = it.genericDocument.getPropertyString("imageUri")?.let(Uri::parse)
            // rankingSignal contains the computed similarity score
            Triple(text, matchedImageUri, it.rankingSignal)
        }
    } finally {
        results.close()
    }
}

استفاده از نمایه جستجوی ترکیبی

برای داشتن بهترین تجربه کاربری، توصیه می‌کنیم از استراتژی ترکیبی شامل جستجوی معنایی و جستجوی کلیدواژه استفاده کنید. وقتی کاربر درحال تایپ کردن در نوار جستجو است، از جستجوی کلیدواژه برای نشان دادن پیشنهادهای فوری پیش‌تایپ استفاده کنید؛ وقتی پُرسمان کامل را وارد می‌کند، یا حتی در وقفه‌های بین کلمات، از جستجوی معنایی برای خواندن، درک کردن، و واکشی بهترین مطابقت‌های مفهومی استفاده کنید.

نمایه‌سازی مجدد پایگاه داده برداری

جاسازی‌های تولیدشده توسط نسخه‌های مختلف مدل در فضاهای برداری ناسازگار قرار دارند و نمی‌توان آن‌ها را مستقیماً مقایسه کرد. هرزمان مدل جاسازی را به‌روز می‌کنید، باید پایگاه داده AppSearch را دوباره نمایه‌سازی کنید.

توصیه می‌کنیم به‌محض اینکه مدلی که استفاده می‌کنید وارد دوره منسوخ شدن شد، بازنمایه‌های برداری را دوباره نمایه‌سازی کنید. می‌توانید از Embedding API برای بررسی زمان‌بندی منسوخ شدن مدل استفاده کنید. برای بازیابی شیء EmbeddingMetadata و بررسی این ویژگی‌های تاریخ چرخه حیات (که به‌عنوان اشیاء LocalDate در ساعت هماهنگ جهانی برگردانده می‌شوند)، روش model.getMetadata() را فراخوانی کنید:

  • مقدار deprecationDate نشان‌دهنده شروع دوره منسوخ شدن مدل است. باید بازنمایه‌سازی را درحالی‌که مدل همچنان کار می‌کند شروع کنید و بازنمایه‌سازی را قبل‌از خاموش شدن رسمی آن به‌پایان برسانید.
  • مقدار shutdownDate زمانی است که مدل ازکار انداخته می‌شود و دیگر ارائه نخواهد شد. پس‌از این تاریخ، مدل برای استنتاج غیرقابل‌دسترس می‌شود و جاسازی‌های موجود این مدل مجبور به بازنمایه‌سازی با جدیدترین مدل دردسترس می‌شوند.

همچنین منسوخ شدن مدل‌های آتی، زمان‌بندی‌های انتقال، و تاریخ‌های هدف برای توقف را ازطریق یادداشت‌های انتشار ML Kit و به‌روزرسانی‌های مستندات توسعه‌دهنده اطلاع‌رسانی می‌کنیم.

روال‌های مطلوب

برای ارائه تجربه کاربری خوب، هنگام ادغام کردن جاسازی‌های برداری در برنامه Android خود، این روال‌ها را دنبال کنید:

  • زمان‌بندی حجم‌های کاری نمایه‌سازی: همیشه حجم‌های کاری نمایه‌سازی پس‌زمینه را بااستفاده از کتابخانه WorkManager زمان‌بندی کنید. تکلیف‌های پس‌زمینه را به‌گونه‌ای پیکربندی کنید که فقط زمانی اجرا شوند که دستگاه درحال شارژ شدن و آزاد است. «کیت یادگیری ماشین» محدودیت‌های سهمیه‌ای برای استفاده از مدل اعمال می‌کند، مشابه میاناهای برنامه‌سازی کاربردی LLM، با محدودیت‌های آزادتر زمانی که دستگاه درحال شارژ است. در حالت «پیش‌نمایش توسعه‌دهندگان نرم‌افزار»، می‌توانید برای اهداف آزمایشی محدودیت‌های سهمیه را دور بزنید.
  • بهینه‌سازی فضای ذخیره‌سازی و پُرسمان: بااستفاده از کوانتیزاسیون و کوتاه کردن ابعاد، ردپای پایگاه داده و تأخیر پُرسمان را بهینه‌سازی کنید:
    • طرح‌واره AppSearchSchema را برای استفاده از ثابت QUANTIZATION_TYPE_8_BIT پیکربندی کنید، همان‌طور که در تعریف طرح‌واره AppSearch نشان داده شده است. این کار با کاهش دقت هر مختصات به عدد صحیح ۸ بیتی، مصرف حافظه را با کمترین تأثیر بر کیفیت کاهش می‌دهد.
    • از Matryoshka Representation Learning (MRL) که توسط مدل EmbeddingGemma 2 پشتیبانی می‌شود برای کوتاه کردن بردار جاسازی استفاده کنید. برای کسب اطلاعات بیشتر، EmbeddingGemma 2 را در HuggingFace ببینید.
  • سازگاری امضا: فضای برداری را بااستفاده از امضای نامطابق پُرسمان نکنید. اگر نسخه مدل جاسازی را ارتقا دهید، باید پایگاه داده AppSearch را پاک کنید و جاسازی‌ها را برای همه اسناد بااستفاده از امضای مدل جدید بازتولید کنید.
  • ایمنی و تعدیل محتوا: شما مسئول پیاده‌سازی تعدیل محتوا و فیلتر ایمنی در برنامه‌تان هستید. برخلاف میاناهای برنامه‌سازی کاربردی نوشتار یا تصویر زایا، میانای برنامه‌سازی کاربردی «جاسازی ML Kit» فیلترهای ایمنی داخلی ندارد. چون مدل به‌جای تولید نوشتار یا رسانه جدید، نمایش‌های برداری عددی (مختصات FloatArray) از ورودی ارائه‌شده توسط تماس‌گیرنده را برونداد می‌کند، بردارهای عددی ذاتاً خطرات ایمنی محتوای زایا را ندارند. حذف فیلتر ایمنی درون‌دستگاهی همچنین با جلوگیری از رقابت واحد پردازش مرکزی درطول نمایه‌سازی سند با توان گذرداد بالا و پُرسمان‌های هم‌زمان، عملکرد جستجو و نمایه‌سازی با تأخیر کم را حفظ می‌کند. برای بهبود ایمنی، این دستورالعمل‌ها را دنبال کنید:
    • ورودی‌های غیرقابل‌اعتماد و محتوای نمایه‌شده را تعدیل کنید: اگر برنامه‌تان محتوای تولیدشده توسط کاربر غیرقابل‌اعتماد یا داده‌های خارجی را نمایه‌سازی یا جستجو می‌کند، قبل‌از نمایه‌سازی آن‌ها یا نمایش نتایج جستجو، تعدیل محتوا و فیلترهای خط‌مشی را روی نوشتار یا تصاویر منبع اعمال کنید.
    • محافظت از خطوط لوله هوش مصنوعی زایای پایین‌دستی: اگر اسناد یا بخش‌های بازیابی‌شده به‌عنوان بافت به یک مدل هوش مصنوعی زایای پایین‌دستی منتقل می‌شوند، بررسی کنید که فیلترهای ایمنی و خط‌مشی‌های محتوا در آن مدل پایین‌دستی و خروجی کاربر-محور آن فعال باشند.

منابع بیشتر