# キーワードブロックリストによるフィルタリング

ほとんどのユーザーはTisaneを使用してコンテキストを考慮したモデレーションを行いますが、中には別の理由から単純なキーワードフィルタリングが必要になる場合もあります。

Tisaneは、単語リストモニタリング時にありがちな落とし穴を避けるためのオプションを備えており、単語リストのモニタリングに使用することができます。

## レスポンスに単語の詳細を含める

デフォルトでは、Tisaneは `abuse`、`sentiment`、`entities_summary`セクションのみをレスポンスに出力します。しかし、Tisaneはすべての文章を構成する単語の内訳を提供することもできます。

単語を出力するには、`settings`パラメータに`"words":true`を追加します。

すべての単語はトークン化（チャンクに分割）されます。トークン化のアルゴリズムは言語によって異なります。その言語がスペースを使うか使わないか、複合語があるか（例：ドイツ語やオランダ語）、単語の間にスペースを取るか（例：英語の*kung fu*、またはスペイン語の*EE.UU.*）どうかは、ユーザーに対して透過的です。

結果は、`words`という名前の構造化された配列になります。これはレスポンス内の`sentence_list`構造内にあります。

すべての単語要素には以下のものが含まれます。

- 実際の文字列（`text`）
- スタート位置である`offset`
- ストップワード用の`stopword`フラグ
- Tisane内部IDの一部（下記オプション2を参照）
- 文法や文体などの関連する特徴


## キーワードフィルタリングの実装

Tisaneでキーワードフィルタリングを行うには2つの方法があります。

* オプション1：シンプル（非推奨）
* オプション2：強力


どちらのアプローチも、「[スカンソープ問題](https://en.wikipedia.org/wiki/Scunthorpe_problem)」として知られる偽陽性の問題を防ぐことができます。

## オプション1：シンプル（非推奨）

シンプルかつ明確なソリューションは以下の通りです。

1. `words`の配列を走査する。
2. すべての要素について、`text`属性に禁止語（または表現。トークン化はロジカルであり、*kung fu*や *power plant*は1つの単語であるため）が含まれているかどうかを確認する。


### 制限

シンプルさに伴う欠点：

- 単語が語形変化するとどうなるでしょうか？「語幹を使えばいい」と言う人もいますが、必ずしも語幹に還元できるとは限りません。
- 英語で_bought_という単語を_buy_というレンマをもとにどう捉えるのでしょうか？_b_を語幹と仮定した場合、_b_で始まる単語はすべて_buy_の活用形と仮定するのでしょうか？
- 形態論的に豊かな言語（フランス語、ドイツ語、アラビア語、ロシア語、ヒンディー語など）には、より多くの種類があり、多くの語形変化があります。
- 例えば、*USA*の代わりに*U.S.A.*、*e-mail*の代わりに*email*など、単語のスペルが微妙に違う場合はどうなるでしょうか？
- その言葉が難読化されていたり、情報セキュリティー用語で言うところの「敵対的なテキスト操作」が施されていたらどうなるでしょうか？
- *Alaska*（州）に関する言及を一切排除したいが、*Alaska Air*や*baked Alaska*は認めるとしたらどうでしょうか？（最近の実例では、キーワード照合で「ゲイ」という単語にフラグが立ったため、[2025年のペンタゴン・クリーンアップ](https://www.newsweek.com/military-remove-enola-gay-photos-dei-rules-2041029)で、[*エノラ・ゲイ*](https://en.wikipedia.org/wiki/Enola_Gay)に関する記事が削除されました。）


## オプション2：強力

オプション2は、Tisaneの内部識別子に依存します。このルートは、Tisaneのアルゴスピークと解読能力、形態素解析能力を活用することで、上記の欠点をすべて解決することができます。

単語エントリーの内部識別子は `lexeme`と`family`です。

### 語彙素IDについて

Tisaneの語彙素IDは、単語とその可能なすべての語形変化に関連付けられています。

単語が難読化されていたり（たとえば、「break」ではなく「br*k」）、スペルミスがあった場合でも、Tisaneが元の単語を認識できた場合は、元の単語の語彙素IDが提供されます。

### ファミリーIDについて

ファミリーIDは、語義に従ってフィルターをかけたい場合の別のオプションです。

例：米国で「elevator」（エレベータ）と呼ばれる装置は、英国では「lift」と呼ばれます。  用語が違うだけで、現実世界では同じ存在となります。つまり、2つとも同じファミリーIDを持っていることになります。しかし、Aerodynamic lift（空力的揚力）という意味での 「lift」は、別のファミリーIDです。

さらに、ファミリーIDは言語が違っても同じです。つまり、言語や方言に関係なく、捉えるべき「概念のリスト」を作ることができます。

パワーユーザーであれば、カテゴリー別にフィルタリングすることもできます。例：どんな飛行機でも、どんな車でも、どんな鳥でも「clay pigeon」（クレーピジョン）は無視しますが、「ハト」（pigeon）は取得します。

### 語彙素IDの使用を勧める理由

Tisaneは一般的に語感を重視していますが、語感の違いはユーザーにとって必ずしも明確ではないことがわかりました。また、キーワードフィルタリングは通常、意図的にコンテキストを無視します。そのため、当社は語彙素IDを使用することをお勧めします。

リスト内のすべての単語またはフレーズについて：

1. 語彙素IDを調べるには、サンプル文章を実行してそこから語彙IDを取得するか、言語モデル直接アクセスAPIを使用します。
2. `words`の配列を走査する。
3. すべての単語について、語彙素IDをユーザーの語彙素IDのリストと比較します。


例：

リクエスト（出力を簡略化するため、`sentiment`、`topics`、`entities`はオフ）：


```json
{
  "language":"en",
  "content":"d/l star t*k for free",
  "settings":
  {
    "snippets":true,
    "words":true,
    "sentiment":false,
    "entities":false,
    "topics":false
  }
}
```

レスポンス：


```json
{
	"text": "d/l star t*k for free",
	"sentence_list": [
		{
			"offset": 0,
			"text": "d/l star t*k for free",
			"words": [
				{
					"type": "word",
					"offset": 0,
					"text": "d/l",
					"role": "verb",
					"lexeme": 44058,
					"family": 117658,
					"grammar": [
						"VERB"
					]
				},
				{
					"type": "word",
					"offset": 4,
					"text": "star t*k",
					"role": "patient",
					"lexeme": 317071,
					"family": 152100,
					"wikidata": "Q1092",
					"grammar": [
						"PROPN"
					]
				},
				{
					"type": "word",
					"offset": 13,
					"text": "for free",
					"lexeme": 62119,
					"family": 93462,
					"grammar": [
						"ADV"
					]
				}
			],
			"corrected_text": "d/l star trek for free"
		}
	]
}
```

レスポンス内：

1. _Star Trek_という難解な言葉は、


* 1つのユニットとしてまとめられる
* 割り当てられた語彙素ID 317071
* 割り当てられたファミリーID 152100


1. *corrected_text*属性は、_d/l star trek for free_という文の難読化解除バージョンを含みます。