feat: 风险关键词红色标记 + 点击显示权重贡献
- 后端: _extract_reason_tokens 返回 [{token, weight}] 格式
- 前端: detect/batch 页面风险关键词使用红色标签样式
- 点击关键词弹窗显示权重值及判定倾向
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -119,7 +119,7 @@ class NaiveBayesSpamClassifier:
|
|||||||
"trained_at": self.metadata.get("trained_at"),
|
"trained_at": self.metadata.get("trained_at"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_reason_tokens(self, text: str, classes: list[str], x_row) -> list[str]:
|
def _extract_reason_tokens(self, text: str, classes: list[str], x_row) -> list[dict]:
|
||||||
try:
|
try:
|
||||||
vocab = self.vectorizer.vocabulary_
|
vocab = self.vectorizer.vocabulary_
|
||||||
feature_names = self.vectorizer.get_feature_names_out()
|
feature_names = self.vectorizer.get_feature_names_out()
|
||||||
@@ -138,12 +138,12 @@ class NaiveBayesSpamClassifier:
|
|||||||
if idx is None:
|
if idx is None:
|
||||||
continue
|
continue
|
||||||
delta = class_log_prob[spam_idx][idx] - class_log_prob[ham_idx][idx]
|
delta = class_log_prob[spam_idx][idx] - class_log_prob[ham_idx][idx]
|
||||||
scored.append((token, delta))
|
scored.append({"token": token, "weight": round(delta, 4)})
|
||||||
|
|
||||||
scored.sort(key=lambda row: abs(row[1]), reverse=True)
|
scored.sort(key=lambda row: abs(row["weight"]), reverse=True)
|
||||||
return [token for token, _ in scored[:5]]
|
return scored[:5]
|
||||||
except Exception:
|
except Exception:
|
||||||
return list(text[:5])
|
return [{"token": ch, "weight": 0.0} for ch in list(text[:5])]
|
||||||
|
|
||||||
def model_info(self) -> dict:
|
def model_info(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -393,6 +393,12 @@ button.btn::after {
|
|||||||
color: #ffd487;
|
color: #ffd487;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-danger {
|
||||||
|
background: rgba(255, 91, 111, 0.18);
|
||||||
|
border: 1rpx solid rgba(255, 108, 128, 0.42);
|
||||||
|
color: #ffdce1;
|
||||||
|
}
|
||||||
|
|
||||||
.chip-group {
|
.chip-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -66,5 +66,18 @@ Page({
|
|||||||
'测试报告我已经同步到项目群。'
|
'测试报告我已经同步到项目群。'
|
||||||
].join('\n')
|
].join('\n')
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
showTokenWeight(e) {
|
||||||
|
const token = e.currentTarget.dataset.token
|
||||||
|
const weight = e.currentTarget.dataset.weight
|
||||||
|
const weightNum = Number(weight || 0)
|
||||||
|
const direction = weightNum >= 0 ? '倾向垃圾判定' : '倾向正常判定'
|
||||||
|
wx.showModal({
|
||||||
|
title: '关键词权重',
|
||||||
|
content: `关键词"${token}"\n权重贡献:${weightNum >= 0 ? '+' : ''}${weightNum.toFixed(4)}\n(${direction})`,
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '关闭'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,6 +69,12 @@
|
|||||||
<view class="progress-track">
|
<view class="progress-track">
|
||||||
<view class="{{item.prediction === 'spam' ? 'progress-fill' : 'progress-fill-safe'}}" style="width: {{item.confidence_text}};"></view>
|
<view class="{{item.prediction === 'spam' ? 'progress-fill' : 'progress-fill-safe'}}" style="width: {{item.confidence_text}};"></view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="field" wx:if="{{item.reason_tokens && item.reason_tokens.length}}">
|
||||||
|
<text class="field-label">风险关键词</text>
|
||||||
|
<view class="chip-group">
|
||||||
|
<text class="tag tag-danger" wx:for="{{item.reason_tokens}}" wx:for-item="tokenItem" wx:key="token" data-token="{{tokenItem.token}}" data-weight="{{tokenItem.weight}}" bindtap="showTokenWeight">{{tokenItem.token}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -98,5 +98,18 @@ Page({
|
|||||||
|
|
||||||
gotoHistory() {
|
gotoHistory() {
|
||||||
wx.navigateTo({ url: '/pages/history/index' })
|
wx.navigateTo({ url: '/pages/history/index' })
|
||||||
|
},
|
||||||
|
|
||||||
|
showTokenWeight(e) {
|
||||||
|
const token = e.currentTarget.dataset.token
|
||||||
|
const weight = e.currentTarget.dataset.weight
|
||||||
|
const weightNum = Number(weight || 0)
|
||||||
|
const direction = weightNum >= 0 ? '倾向垃圾判定' : '倾向正常判定'
|
||||||
|
wx.showModal({
|
||||||
|
title: '关键词权重',
|
||||||
|
content: `关键词"${token}"\n权重贡献:${weightNum >= 0 ? '+' : ''}${weightNum.toFixed(4)}\n(${direction})`,
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '关闭'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<view class="field" wx:if="{{result.detect.reason_tokens && result.detect.reason_tokens.length}}">
|
<view class="field" wx:if="{{result.detect.reason_tokens && result.detect.reason_tokens.length}}">
|
||||||
<text class="field-label">风险关键词</text>
|
<text class="field-label">风险关键词</text>
|
||||||
<view class="chip-group">
|
<view class="chip-group">
|
||||||
<text class="tag" wx:for="{{result.detect.reason_tokens}}" wx:key="*this">{{item}}</text>
|
<text class="tag tag-danger" wx:for="{{result.detect.reason_tokens}}" wx:key="token" data-token="{{item.token}}" data-weight="{{item.weight}}" bindtap="showTokenWeight">{{item.token}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user