# japhrase

**辞書もLLMも使わずに、テキストからフレーズを見つける。**

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![PyPI](https://img.shields.io/pypi/v/japhrase)](https://pypi.org/project/japhrase/)

```bash
pip install japhrase
```

日本語テキストの中から、繰り返し出現するフレーズを統計だけで見つける。MeCab も辞書ファイルも外部 AI も要らない。テキストを入れれば、そこに何度も現れているフレーズが出てくる — 既知語でも新語でも造語でも関係ない。

---

## 最初の一歩

```python
from japhrase import PhraseExtractor

text = """ChatGPTの登場により生成AIが注目を集めている。
生成AIはテキストだけでなく画像生成にも使われる。
大規模言語モデルは生成AIの中核技術である。
生成AIの倫理的課題について議論が活発化している。
企業における生成AIの導入事例が増加している。
生成AIの品質向上が求められている。
生成AIと人間の協調が進んでいる。
生成AIの応用範囲は広がり続けている。
生成AIによるコスト削減が期待されている。
生成AIの安全性が議論されている。""" * 3

pe = PhraseExtractor(verbose=0)
df = pe.extract(text)
print(df[["seqchar", "freq"]].head())
```

```
               seqchar  freq
0                生成AI  30.0
2   ChatGPTの登場により生成AI   3.0
12  生成AIはテキストだけでなく画像生   3.0
20  大規模言語モデルは生成AIの中核技   3.0
25  生成AIの倫理的課題について議論が   3.0
```

テキストを渡すだけ。引数もファイルも不要。「生成AI」が30回出現 — 辞書なしで見つかった。

---

## デモ（2行で全機能体験）

```python
import japhrase
japhrase.run_demo()
```

内蔵の例文で、フレーズ抽出・語彙多様性・テキスト複雑度・分布比較・コロケーション分析・汚染検出・品質チェックが順に実行される。何も用意しなくていい。

個別のデモもある:

```python
from japhrase import PhraseExtractor
PhraseExtractor.demo()
```

---

## 仕組み — N-gram + PMI + 分岐エントロピー

### N-gram（頻度で拾う）

テキストを「連続する N 文字」に分解し、出現頻度を数える。

```python
from japhrase import PhraseExtractor

text = "ChatGPTの登場により生成AIが注目を集めている。生成AIはテキスト生成にも使われる。大規模言語モデルは生成AIの中核技術である。生成AIの導入事例が増加している。生成AIの品質向上が求められている。生成AIの安全性が議論されている。" * 5
pe = PhraseExtractor(verbose=0)
df = pe.extract(text)
print(df[["seqchar", "freq"]].head())
```

「生成AI」は見つかる。しかし「ている」「である」のような、どこにでも現れる無意味なフレーズも一緒に出てくる。

### PMI（意味のある結合だけ残す）

PMI（自己相互情報量）は「偶然一緒に現れる確率」と「実際の共起頻度」の比を測る。

```python
from japhrase import PhraseExtractor

text = "ChatGPTの登場により生成AIが注目を集めている。生成AIはテキスト生成にも使われる。大規模言語モデルは生成AIの中核技術である。生成AIの導入事例が増加している。生成AIの品質向上が求められている。生成AIの安全性が議論されている。" * 5
pe = PhraseExtractor(use_pmi=True, verbose=0)
df = pe.extract(text)
print(df[["seqchar", "freq"]].head())
```

「生成」と「AI」がこれほど一緒に出現するのは偶然ではない — PMI が高い。「である」は PMI が低い。

### 分岐エントロピー（切れ目を見つける）

「あるフレーズの次にどんな文字が来るか」の多様性を測り、急に上がる位置をフレーズの境界とする。

```python
from japhrase import PhraseExtractor

text = "ChatGPTの登場により生成AIが注目を集めている。生成AIはテキスト生成にも使われる。大規模言語モデルは生成AIの中核技術である。生成AIの導入事例が増加している。生成AIの品質向上が求められている。生成AIの安全性が議論されている。" * 5
pe = PhraseExtractor(use_pmi=True, use_branching_entropy=True, verbose=0)
df = pe.extract(text)
print(df[["seqchar", "freq"]].head())
```

N-gram が広く拾い → PMI が意味のある結合だけ残し → 分岐エントロピーが切れ目を正確にする。この3層が japhrase のコア。

---

## プリセット

テキスト種別ごとに最適化済みのパラメータセット。

```python
from japhrase import PhraseExtractor

pe = PhraseExtractor.preset('sns')     # SNS/Twitter
pe = PhraseExtractor.preset('news')    # ニュース
pe = PhraseExtractor.preset('novel')   # 小説
pe = PhraseExtractor.preset('report')  # 論文/レポート
```

## パラメータの自動最適化

プリセットで足りないとき、あなたのテキストに合わせて自動チューニングできる。

```python
from japhrase import PhraseExtractor

text = "生成AIが注目を集めている。生成AIはテキスト生成にも使われる。大規模言語モデルは生成AIの中核技術である。" * 10
pe = PhraseExtractor(verbose=0)

# フラグ1個でチューニング付き抽出
df = pe.extract(text, auto_tune=True)

# チューニング結果を確認
pe.show_params()

# 保存・復元
pe.save_params("my_params.json")
pe2 = PhraseExtractor.load_params("my_params.json")
```

`auto_tune=True` を付けるだけ。Optunaがあればベイズ最適化、なければヒューリスティック推定。最適化されたパラメータは JSON に保存・復元できる。

---

## 分析機能

フレーズ抽出エンジンの上に積み上げた統計分析。すべて `pip install japhrase` だけで使える。

### 語彙の豊かさを測る

```python
from japhrase import StylometryAnalyzer

stylo = StylometryAnalyzer()
rich = stylo.analyze_advanced_diversity("朝靄の中を歩いていると、足元で小さな花が揺れた。名前は知らない。淡い紫色の花弁が露を含んで光っている。")
poor = stylo.analyze_advanced_diversity("男は歩いた。男は止まった。男はまた歩いた。男は見た。男は聞いた。")
print(f"豊かな文: Hapax比={rich['hapax_ratio']:.2f}")
print(f"単調な文: Hapax比={poor['hapax_ratio']:.2f}")
```

| 指標 | 何を測るか |
|------|----------|
| Hapax比 | 一度しか使わなかった語の割合（高い = 多様） |
| MATTR | テキスト長に依存しない語彙多様性 |
| Simpson's D | 同じ語に2回当たる確率の逆数（1に近い = 多様） |
| Brunet's W / Honoré's R | 長文でも崩壊しない多様性指標 |

### 2つのテキストの語彙分布を比較

```python
from japhrase import DistributionComparator
from collections import Counter

comp = DistributionComparator()
result = comp.compare(
    Counter({"騎士": 10, "剣": 8, "王城": 5}),
    Counter({"研究": 12, "実験": 9, "データ": 7}),
)
print(f"JS距離: {result.jsd:.4f}  (0=同一, 1=完全に別)")
```

### 語の結びつきの強さを測る

```python
from japhrase import CollocationScorer

text = "生成AIが注目されている。生成AIの技術が進んでいる。" * 50
scorer = CollocationScorer()
cs = scorer.score_phrase("生成AI", 45, text)
print(f"PMI={cs.pmi:.1f}  t-score={cs.t_score:.1f}  Log-Dice={cs.log_dice:.1f}")
```

| 指標 | 何に強いか |
|------|----------|
| PMI | レアだが意味のある結合（低頻度に敏感） |
| t-score | 高頻度で安定した結合 |
| Log-Dice | コーパスサイズに依存しない（最も安定） |

### テキストの複雑度を測る

```python
from japhrase import ComplexityAnalyzer

cx = ComplexityAnalyzer()
r1 = cx.analyze("朝靄の中を歩いていると、足元で小さな花が揺れた。名前は知らない。淡い紫色の花弁が露を含んで光っている。")
r2 = cx.analyze("男は歩いた。" * 20)
print(f"多様な文章: 圧縮率={r1['compression_ratio']:.3f}")
print(f"繰り返し:   圧縮率={r2['compression_ratio']:.3f}")
```

圧縮率が低い = 同じパターンの繰り返しが多い。

### テキストの汚染を検出

文字化け・コピペ重複・括弧の閉じ忘れ等を8軸で検出する。

```python
from japhrase.contamination import scan

profile = scan("正常な文章。\nâ€™文字化け。\n「閉じない台詞")
print(f"汚染スコア: {profile.overall}/100")
print(profile.explain())
```

一括チェック:

```python
from japhrase.contamination import batch_scan

result = batch_scan({
    "1章": "正常なテキスト。問題のない文章が続きます。特に異常はありません。",
    "2章": "â€™文字化け。「閉じない括弧。重複文。重複文。重複文。重複文。重複文。",
    "3章": "正常なテキスト。こちらも問題ありません。普通の文章です。",
})
for key, p in result.profiles.items():
    print(f"{key}: スコア={p.overall}/100")
```

### テキスト類似度

```python
from japhrase import SimilarityAnalyzer

texts = [
    "生成AIが注目を集めている。大規模言語モデルの進化が著しい。",
    "生成AIの技術が注目されている。言語モデルが急速に進化している。",
    "今日は天気がよい。散歩に出かけた。公園で花を見た。",
]
analyzer = SimilarityAnalyzer(method='jaccard')
matrix = analyzer.compare_texts(texts)
print(matrix)
```

### 時系列で語彙の変化を追跡

```python
from japhrase import TemporalAnalyzer

ch1 = "騎士が剣を抜いた。王城の門が開いた。騎士は走った。剣が光った。" * 10
ch2 = "研究室でデータを確認した。実験の結果を記録した。論文を読んだ。" * 10
ch3 = "騎士は剣を収めた。王城に平和が戻った。研究が進んだ。" * 10

ta = TemporalAnalyzer()
result = ta.analyze_series([ch1, ch2, ch3], labels=["1章", "2章", "3章"])
print(f"語彙飽和度: {result['vocab_saturation']:.2f}")
```

### その他

| 機能 | 説明 |
|------|------|
| **TextVariantDetector** | 表記ゆれ検出（サーバー/サーバ、出来る/できる） |
| **WritingHabitDetector** | 書き癖検出（高頻度×低PMI = 無意識の繰り返し） |
| **CooccurrenceAnalyzer** | 共起語分析（キーワード周辺の特異語） |
| **Summarizer** | 統計的要約（LLM不要・ハルシネーションなし） |
| **japhrase.applied** | 公開前品質ゲート・話数間推移・キャラ文体指紋 |

---

## ファイルから読み込み

```python
from japhrase import PhraseExtractor

df = PhraseExtractor.from_file("input.txt")                  # エンコーディング自動検出
df = PhraseExtractor.from_file("data.csv", column="text")    # CSV（列指定）
```

## パラメータ一覧

```python
from japhrase import PhraseExtractor

pe = PhraseExtractor(
    min_count=6,                # 最小出現回数
    max_length=16,              # フレーズの最大文字数
    min_length=4,               # フレーズの最小文字数
    threshold_originality=0.5,  # 類似フレーズ除去の閾値
    use_pmi=True,               # PMI で意味的結合を評価
    use_branching_entropy=True, # 分岐エントロピーで境界検出
    knowns=["既知語1"],         # 優先的に抽出したい語
    verbose=1,                  # 進捗表示（0:非表示）
)
```

## CLI

```bash
japhrase --help                     # コマンド一覧
japhrase extract --help             # フレーズ抽出
japhrase check --help               # 文書品質チェック
japhrase detect-habits --help       # 書き癖検出
```

## help() で調べる

```python
from japhrase import PhraseExtractor
help(PhraseExtractor.extract)

from japhrase.contamination import scan
help(scan)
```

## テスト

```bash
pytest
```

## English Summary

**japhrase** finds phrases in Japanese text without a dictionary or LLM. It scans for recurring N-gram patterns, then uses PMI (pointwise mutual information) to separate meaningful collocations from noise — "生成AI" ranks high because its components co-occur far more than chance predicts, while "である" ranks low. Branching entropy further refines phrase boundaries. On top of this core: text similarity, distribution comparison (JSD, G², keyness), collocation scoring (6 metrics), vocabulary richness (7 metrics), text complexity, temporal analysis, contamination detection, and adaptive parameter tuning. Pure math, no external AI, numpy/scipy only.

## ライセンス

MIT License — Takeshi SHIMIZU
