From da45878c545dd46419ce40f7a6f18230e0da4c58 Mon Sep 17 00:00:00 2001 From: liwenhao2 Date: Tue, 15 Sep 2026 09:57:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=A4=8D=E8=AF=BB=E6=B2=BB?= =?UTF-8?q?=E7=90=86:=20=E5=88=9D=E8=AF=91=E6=89=B9=E6=AC=A14=E2=86=922?= =?UTF-8?q?=E3=80=81=E9=80=90=E6=9D=A1isEcho=E5=A4=8D=E8=AF=BB=E6=A3=80?= =?UTF-8?q?=E6=B5=8B(=E5=BD=92=E4=B8=80=E5=8C=96+=E4=BA=8C=E5=85=83?= =?UTF-8?q?=E7=BB=84Jaccard)=E6=8B=92=E5=86=99=E6=8A=84=E5=8E=9F=E6=96=87?= =?UTF-8?q?=E3=80=81=E4=BA=8C=E6=AC=A1=E7=BF=BB=E8=AF=91=E5=90=8C=E8=A7=84?= =?UTF-8?q?=E5=88=99=E3=80=81=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=98=BE=E5=BC=8F?= =?UTF-8?q?=E7=A6=81=E6=8A=84=E5=B9=B6=E5=85=81=E8=AE=B8=E7=A9=BA=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/k2fsa/sherpa/onnx/MainActivity.kt | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt index 11c0fed..7541d08 100644 --- a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt +++ b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt @@ -309,7 +309,7 @@ class MainActivity : AppCompatActivity() { // KV 污染 workaround(2026-09-14,不改 .so):nativeGenerate 不清 KV, // 同引擎连续 generate 会复读上一条 → 每"批"generate 前重建引擎。 // 初译从逐句改为小批量编号翻译(凑 4 句或 2.5s 定时封批)。 - private val initialBatchMax = 4 + private val initialBatchMax = 2 // 4 句批后期句常退化成抄原文;2 句批明显更稳 private val initialBatchDelayMs = 2_500L private val initialPending = ArrayList>() private var initialBatchTimer: Runnable? = null @@ -1935,7 +1935,9 @@ class MainActivity : AppCompatActivity() { uiHandler.post { val touched = HashSet() refs.forEach { s2 -> - map[s2.id.toString()]?.takeIf { it.isNotBlank() }?.let { tr -> + map[s2.id.toString()]?.takeIf { + it.isNotBlank() && !isEcho(s2.text, it) + }?.let { tr -> if (s2.trans != tr) { s2.trans = tr s2.line?.let { touched.add(it) } @@ -1995,8 +1997,10 @@ class MainActivity : AppCompatActivity() { if (map == null || !plausibleTranslations(map.values)) return@post uiHandler.post { val touched = HashSet() - batch.forEach { (sid, _) -> - map[sid.toString()]?.takeIf { it.isNotBlank() }?.let { tr -> + batch.forEach { (sid, src) -> + map[sid.toString()]?.takeIf { + it.isNotBlank() && !isEcho(src, it) + }?.let { tr -> findSentence(sid)?.let { s2 -> if (s2.trans != tr) { s2.trans = tr @@ -2028,7 +2032,9 @@ class MainActivity : AppCompatActivity() { systemPrompt = "你是专业翻译。逐句翻译下面编号句子为" + targetName + "。各句相互独立,必须逐句对应翻译,不得合并、不得重复、不得解释。" + "只输出JSON对象,键是编号字符串,值是译文," + - "例如 {\"1\":\"…\",\"2\":\"…\"}。不得增删编号。", + "例如 {\"1\":\"…\",\"2\":\"…\"}。不得增删编号。" + + "严禁把原句原样作为译文输出;确实无法翻译的句子," + + "值输出空字符串。", userPrompt = "【句子】\n$numbered", maxNewTokens = (160 * items.size).coerceAtMost(900), temperature = 0f, @@ -2050,6 +2056,30 @@ class MainActivity : AppCompatActivity() { return v.size < 2 || v.distinct().size * 2 >= v.size } + // 复读检测:译文与原文归一化后全等/长串包含/字符二元组 Jaccard≥0.72 + // → 判为"把原文抄进译文",该条丢弃(维持原状),不写入字幕 + private fun normalizeForEcho(x: String): String = + x.lowercase().filter { it.isLetterOrDigit() || it.code >= 0x3040 } + + private fun isEcho(src: String, tr: String): Boolean { + val a = normalizeForEcho(src) + val b = normalizeForEcho(tr) + if (a.isEmpty() || b.isEmpty()) return false + if (a == b) return true + if (a.length >= 8 && (a.contains(b) || b.contains(a))) return true + if (a.length < 6 || b.length < 6) return false + fun bigrams(x: String): HashSet { + val set = HashSet() + for (i in 0 until x.length - 1) set.add(x.substring(i, i + 2)) + return set + } + val ba = bigrams(a); val bb = bigrams(b) + if (ba.isEmpty() || bb.isEmpty()) return false + val inter = bb.count { ba.contains(it) } + val union = ba.size + bb.size - inter + return union > 0 && inter.toDouble() / union >= 0.72 + } + private val unloadRunnable = Runnable { val e = translateEngine ?: return@Runnable if (initialPending.isEmpty()) {