Corin1998 commited on
Commit
188f682
·
verified ·
1 Parent(s): 93a86f8

Create es_advanced_elauator.py

Browse files
Files changed (1) hide show
  1. es_advanced_elauator.py +279 -0
es_advanced_elauator.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # es_advanced_evaluator.py
2
+ from typing import List, Dict, Any, Optional
3
+ import re
4
+
5
+ from transformers import (
6
+ AutoTokenizer,
7
+ AutoModelForSequenceClassification,
8
+ pipeline,
9
+ )
10
+
11
+ # =========================
12
+ # モデル定義(Hugging Face)
13
+ # =========================
14
+
15
+ # 日本語感情分析モデル
16
+ SENTIMENT_MODEL_NAME = "koheiduck/bert-japanese-finetuned-sentiment"
17
+ # 多言語ゼロショット分類モデル(STAR分類・トーン評価用)
18
+ NLI_MODEL_NAME = "joeddav/xlm-roberta-large-xnli"
19
+
20
+ # 必要に応じて device=0 (GPU) に変更してください
21
+ sentiment_classifier = pipeline(
22
+ "sentiment-analysis",
23
+ model=SENTIMENT_MODEL_NAME,
24
+ tokenizer=SENTIMENT_MODEL_NAME,
25
+ )
26
+
27
+ zero_shot_classifier = pipeline(
28
+ "zero-shot-classification",
29
+ model=NLI_MODEL_NAME,
30
+ tokenizer=NLI_MODEL_NAME,
31
+ )
32
+
33
+
34
+ # =========================
35
+ # ユーティリティ
36
+ # =========================
37
+
38
+ _SENT_SPLIT_RE = re.compile(r"[。!?\n]+")
39
+
40
+
41
+ def split_sentences(text: str) -> List[str]:
42
+ """日本語の文をざっくり分割する簡易関数"""
43
+ sentences = [s.strip() for s in _SENT_SPLIT_RE.split(text) if s.strip()]
44
+ return sentences
45
+
46
+
47
+ # =========================
48
+ # 感情・トーン分析
49
+ # =========================
50
+
51
+ def analyze_sentiment(text: str) -> Dict[str, Any]:
52
+ """
53
+ 日本語テキストのネガポジ分析。
54
+ 出力例:
55
+ {
56
+ "label": "POSITIVE",
57
+ "score": 0.98
58
+ }
59
+ """
60
+ if not text.strip():
61
+ return {"label": "NEUTRAL", "score": 0.0}
62
+
63
+ result = sentiment_classifier(text)[0]
64
+ return {
65
+ "label": result["label"],
66
+ "score": float(result["score"]),
67
+ }
68
+
69
+
70
+ def analyze_tone(text: str) -> Dict[str, Any]:
71
+ """
72
+ ゼロショット分類で「熱意・主体性・一貫性・論理性・協調性」などをスコアリング。
73
+ multi_label=True なので、1文が複数のトーンを兼ねることも許容。
74
+ 出力例:
75
+ {
76
+ "labels": {
77
+ "熱意": 0.92,
78
+ "主体性": 0.81,
79
+ "一貫性": 0.55,
80
+ "論理性": 0.73,
81
+ "協調性": 0.40
82
+ }
83
+ }
84
+ """
85
+ if not text.strip():
86
+ return {"labels": {}}
87
+
88
+ tone_labels = ["熱意", "主体性", "一貫性", "論理性", "協調性"]
89
+
90
+ result = zero_shot_classifier(
91
+ text,
92
+ candidate_labels=tone_labels,
93
+ multi_label=True,
94
+ )
95
+
96
+ label_scores = {
97
+ label: float(score)
98
+ for label, score in zip(result["labels"], result["scores"])
99
+ }
100
+
101
+ ordered = {label: label_scores.get(label, 0.0) for label in tone_labels}
102
+
103
+ return {"labels": ordered}
104
+
105
+
106
+ # =========================
107
+ # STAR 構造分析
108
+ # =========================
109
+
110
+ STAR_LABELS_JP = [
111
+ "Situation(状況)",
112
+ "Task(課題)",
113
+ "Action(行動)",
114
+ "Result(結果)",
115
+ "Other(その他)",
116
+ ]
117
+
118
+ STAR_KEY_MAP = {
119
+ "Situation(状況)": "S",
120
+ "Task(課題)": "T",
121
+ "Action(行動)": "A",
122
+ "Result(結果)": "R",
123
+ "Other(その他)": "O",
124
+ }
125
+
126
+
127
+ def classify_sentence_star(sentence: str) -> Dict[str, Any]:
128
+ """
129
+ 1文を STAR のどれに近いかゼロショット分類する。
130
+ multi_label=False とし、最も近いラベルのみ採用。
131
+ 出力例:
132
+ {
133
+ "sentence": "...",
134
+ "star_label": "S",
135
+ "raw_label": "Situation(状況)",
136
+ "score": 0.87
137
+ }
138
+ """
139
+ if not sentence.strip():
140
+ return {
141
+ "sentence": sentence,
142
+ "star_label": "O",
143
+ "raw_label": "Other(その他)",
144
+ "score": 0.0,
145
+ }
146
+
147
+ result = zero_shot_classifier(
148
+ sentence,
149
+ candidate_labels=STAR_LABELS_JP,
150
+ multi_label=False,
151
+ )
152
+ raw_label = result["labels"][0]
153
+ score = float(result["scores"][0])
154
+ star_label = STAR_KEY_MAP.get(raw_label, "O")
155
+
156
+ return {
157
+ "sentence": sentence,
158
+ "star_label": star_label,
159
+ "raw_label": raw_label,
160
+ "score": score,
161
+ }
162
+
163
+
164
+ def analyze_star_structure(text: str) -> Dict[str, Any]:
165
+ """
166
+ 自己PRテキストの STAR 構造(S/T/A/R がどの程度含まれているか)を分析。
167
+ 出力例:
168
+ {
169
+ "coverage": {
170
+ "S": true,
171
+ "T": true,
172
+ "A": true,
173
+ "R": false
174
+ },
175
+ "star_score": 0.75,
176
+ "per_sentence": [...],
177
+ "missing_elements": ["R"],
178
+ "comment": "結果(Result)の記述が弱いため、成果をより具体的に書くと良いです。"
179
+ }
180
+ """
181
+ sentences = split_sentences(text)
182
+ if not sentences:
183
+ return {
184
+ "coverage": {k: False for k in ["S", "T", "A", "R"]},
185
+ "star_score": 0.0,
186
+ "per_sentence": [],
187
+ "missing_elements": ["S", "T", "A", "R"],
188
+ "comment": "文章が空か極端に短いため、STAR 構造の判定ができません。",
189
+ }
190
+
191
+ per_sentence_results = [
192
+ classify_sentence_star(s) for s in sentences
193
+ ]
194
+
195
+ coverage = {k: False for k in ["S", "T", "A", "R"]}
196
+ for r in per_sentence_results:
197
+ key = r["star_label"]
198
+ if key in coverage:
199
+ coverage[key] = True
200
+
201
+ missing = [k for k, v in coverage.items() if not v]
202
+ star_score = sum(1 for v in coverage.values() if v) / 4.0
203
+
204
+ # コメント生成(シンプルなヒューリスティック)
205
+ if not missing:
206
+ comment = "STAR の各要素(状況・課題・行動・結果)が一通り含まれています。構成としてバランスは良好です。"
207
+ else:
208
+ parts = []
209
+ mapping_jp = {"S": "状況(Situation)", "T": "課題(Task)", "A": "行動(Action)", "R": "結果(Result)"}
210
+ for m in missing:
211
+ parts.append(mapping_jp[m])
212
+ missing_jp = "・".join(parts)
213
+ comment = (
214
+ f"{missing_jp} の要素が弱い/不足しています。"
215
+ "不足している要素を具体的に書き足すと、より論理的で説得力のある自己PRになります。"
216
+ )
217
+
218
+ return {
219
+ "coverage": coverage,
220
+ "star_score": star_score,
221
+ "per_sentence": per_sentence_results,
222
+ "missing_elements": missing,
223
+ "comment": comment,
224
+ }
225
+
226
+
227
+ # =========================
228
+ # ES 全体の評価インターフェース
229
+ # =========================
230
+
231
+ def evaluate_entry_sheet(
232
+ self_pr: str,
233
+ motivation: Optional[str] = None,
234
+ ) -> Dict[str, Any]:
235
+ """
236
+ ES の自己PR(必須)、志望動機(任意)を入力として、
237
+ 感情・トーン・STAR 構造をまとめて評価するインターフェース。
238
+ """
239
+ texts = [self_pr]
240
+ if motivation:
241
+ texts.append(motivation)
242
+ full_text = "\n".join([t for t in texts if t])
243
+
244
+ sentiment = analyze_sentiment(full_text)
245
+ tone = analyze_tone(full_text)
246
+ star = analyze_star_structure(self_pr) # STAR は主に自己PRに対して実行
247
+
248
+ return {
249
+ "input": {
250
+ "self_pr": self_pr,
251
+ "motivation": motivation,
252
+ },
253
+ "sentiment": sentiment,
254
+ "tone": tone,
255
+ "star": star,
256
+ }
257
+
258
+
259
+ # =========================
260
+ # 単体テスト用の簡易実行
261
+ # =========================
262
+
263
+ if __name__ == "__main__":
264
+ sample_self_pr = """
265
+ 私は大学時代、サッカー部の主将としてチームをまとめました。
266
+ チームは当初、練習への参加率が低く、公式戦でも連敗が続いていました。
267
+ そこで私は、メンバー全員への個別ヒアリングを行い、参加しづらい理由や不満点を把握しました。
268
+ その上で、練習メニューの見直しや役割分担の明確化を提案し、自ら先頭に立って行動しました。
269
+ その結果、練習参加率は 50% から 85% に向上し、公式戦でも県大会ベスト8まで進出することができました。
270
+ """
271
+
272
+ sample_motivation = """
273
+ 貴社を志望する理由は、ヘルスケア領域でデータとテクノロジーを活用し、
274
+ 社会的インパクトの大きいサービスを創出している点に強く共感したためです。
275
+ """
276
+
277
+ result = evaluate_entry_sheet(sample_self_pr, sample_motivation)
278
+ import json
279
+ print(json.dumps(result, ensure_ascii=False, indent=2))