wolf4032 commited on
Commit
e40efc2
·
verified ·
1 Parent(s): eed9b71

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +985 -0
app.py ADDED
@@ -0,0 +1,985 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, Tuple, Any
2
+ from __future__ import annotations
3
+ from functools import reduce
4
+ import operator
5
+ import pandas as pd
6
+
7
+ import gradio as gr
8
+
9
+ from utility import load_json_obj
10
+ from pandas_utility import read_csv_df
11
+ from pipeline import NaturalLanguageProcessing
12
+ from my_gradio import GrBlocks, GrLayout, GrComponent, GrListener
13
+
14
+ class App(GrBlocks):
15
+ """
16
+ アプリのクラス
17
+ """
18
+ @staticmethod
19
+ def _create_children_and_listeners(
20
+ model_dir: str, cuisine_df_path: str, unify_dics_path: str
21
+ ) -> Tuple[Dict[str, Any] | List[Any], List[Any]]:
22
+ """
23
+ 子要素とイベントリスナーの作成
24
+
25
+ Parameters
26
+ ----------
27
+ model_dir : str
28
+ ファインチューニング済みモデルが保存されているディレクトリ
29
+ cuisine_df_path : str
30
+ 料理のデータフレームが保存されているパス
31
+ unify_dics_path : str
32
+ 表記ゆれ統一用辞書が保存されているパス
33
+
34
+ Returns
35
+ -------
36
+ Tuple[Dict[str, Any] | List[Any], List[Any]]
37
+ 子要素とイベントリスナーのタプル
38
+ """
39
+ cuisine_infos_num = 10
40
+ label_info_dics: Dict[str, str | List[str]] = {
41
+ 'AREA': {
42
+ 'jp': '都道府県/地方',
43
+ 'color': 'red',
44
+ 'df_cols': ['Prefecture', 'Areas']
45
+ },
46
+ 'TYPE': {
47
+ 'jp': '種類',
48
+ 'color': 'green',
49
+ 'df_cols': ['Types']
50
+
51
+ },
52
+ 'SZN': {
53
+ 'jp': '季節',
54
+ 'color': 'blue',
55
+ 'df_cols': ['Seasons']
56
+ },
57
+ 'INGR': {
58
+ 'jp': '食材',
59
+ 'color': 'yellow',
60
+ 'df_cols': ['Ingredients list']
61
+ }
62
+ }
63
+
64
+ input = InputTextbox(
65
+ model_dir, label_info_dics, cuisine_df_path, unify_dics_path,
66
+ cuisine_infos_num
67
+ )
68
+ input_samples = InputSamplesDataset()
69
+ extracted_words = ExtractedWordsHighlightedText(label_info_dics)
70
+ cuisine_infos = CuisineInfos(cuisine_infos_num)
71
+
72
+ input_submitted = GrListener(
73
+ trigger=input.comp.submit,
74
+ fn=input.submitted,
75
+ inputs=input,
76
+ outputs=[extracted_words, cuisine_infos],
77
+ scroll_to_output=True
78
+ )
79
+
80
+ input_samples_selected = GrListener(
81
+ trigger=input_samples.comp.select,
82
+ fn=InputSamplesDataset.selected,
83
+ outputs=input,
84
+ thens=input_submitted
85
+ )
86
+
87
+ children = [input, input_samples, extracted_words, cuisine_infos]
88
+ listeners = [input_submitted, input_samples_selected]
89
+
90
+ return children, listeners
91
+
92
+
93
+ class InputTextbox(GrComponent):
94
+ """
95
+ 入力欄のクラス
96
+
97
+ Attributes
98
+ ----------
99
+ _nlp : NaturalLanguageProcessing
100
+ 固有表現を抽出するオブジェクト
101
+ _jp_label_dic : Dict[str, str]
102
+ 固有表現のラベルとその日本語訳の辞書
103
+ _cuisine_info_dics_maker : CuisineInfoDictionariesMaker
104
+ 検索結果の料理の情報の辞書のリストを作成するオブジェクト
105
+ """
106
+ def __init__(
107
+ self,
108
+ model_dir: str,
109
+ label_info_dics: Dict[str, str | List[str]],
110
+ cuisine_df_path: str,
111
+ unify_dics_path: str,
112
+ cuisine_infos_num: int
113
+ ):
114
+ """
115
+ コンストラクタ
116
+
117
+ Parameters
118
+ ----------
119
+ model_dir : str
120
+ ファインチューニング済みモデルが保存されているディレクトリ
121
+ label_info_dics : Dict[str, str | List[str]]
122
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
123
+ cuisine_df_path : str
124
+ 料理のデータフレームが保存されているパス
125
+ unify_dics_path : str
126
+ 表記ゆれ統一用辞書が保存されているパス
127
+ cuisine_infos_num : int
128
+ 表示する料理検索結果の最大数
129
+ """
130
+ self._nlp = NaturalLanguageProcessing(model_dir)
131
+ self._jp_label_dic: Dict[str, str] = {
132
+ label: dic['jp'] for label, dic in label_info_dics.items()
133
+ }
134
+
135
+ self._cuisine_info_dics_maker = CuisineInfoDictionariesMaker(
136
+ cuisine_df_path, unify_dics_path, label_info_dics, cuisine_infos_num
137
+ )
138
+
139
+ super().__init__()
140
+
141
+ def _create(self) -> gr.Textbox:
142
+ """
143
+ コンポーネントの作成
144
+
145
+ Returns
146
+ -------
147
+ gr.Textbox
148
+ 入力欄のコンポーネント
149
+ """
150
+ label = self._create_label()
151
+ placeholder = 'どんな料理をお探しでしょうか?'
152
+ comp = gr.Textbox(placeholder=placeholder, label=label)
153
+
154
+ return comp
155
+
156
+ def _create_label(self) -> str:
157
+ """
158
+ ラベルの作成
159
+
160
+ Returns
161
+ -------
162
+ str
163
+ コンポーネントのラベル
164
+ """
165
+ categories = [f'"{jp}"' for jp in self._jp_label_dic.values()]
166
+ label = f'入力文から、料理の{"、".join(categories)}を示す語彙を検出します'
167
+
168
+ return label
169
+
170
+ def submitted(
171
+ self, classifying_text: str
172
+ ) -> List[List[Tuple[str, str]] | List[gr.Textbox | gr.Button]]:
173
+ """
174
+ submitイベントリスナーの関数
175
+
176
+ Parameters
177
+ ----------
178
+ classifying_text : str
179
+ 固有表現抽出対象
180
+
181
+ Returns
182
+ -------
183
+ List[List[Tuple[str, str]] | List[gr.Textbox | gr.Button]]
184
+ 抽出結果のリストと、料理検索結果に応じた
185
+ テキストボックスとボタンのリストのリスト
186
+ """
187
+ classified_words: Dict[str, List[str]] = self._nlp.classify(classifying_text)
188
+
189
+ pos_tokens = [
190
+ (word, self._jp_label_dic[label])
191
+ for label, words in classified_words.items()
192
+ for word in words
193
+ ]
194
+
195
+ cuisine_info_dics = self._cuisine_info_dics_maker.create(classified_words)
196
+ cuisine_infos = CuisineInfos.update(cuisine_info_dics)
197
+
198
+ return [pos_tokens] + cuisine_infos
199
+
200
+
201
+ class InputSamplesDataset(GrComponent):
202
+ """
203
+ 入力例のクラス
204
+ """
205
+ def _create(self) -> gr.Dataset:
206
+ """
207
+ コンポーネントの作成
208
+
209
+ Returns
210
+ -------
211
+ gr.Dataset
212
+ 入力例のコンポーネント
213
+ """
214
+ label = 'こんな風に聞いてみてください'
215
+ input_samples = [
216
+ 'オオカミとムカデを使った肉料理を教えてください',
217
+ '野菜料理で仙豆を使用したものはありますか?',
218
+ 'オールマイトの髪の毛を使った料理は?',
219
+ '仙台の、宿儺の指を使った、夏に食べられる肉料理',
220
+ '呪胎九相図が使われている料理を探しています'
221
+ ]
222
+
223
+ comp = gr.Dataset(
224
+ label=label,
225
+ components=[gr.Textbox()],
226
+ samples=[[sample] for sample in input_samples]
227
+ )
228
+
229
+ return comp
230
+
231
+ @staticmethod
232
+ def selected(input: gr.SelectData) -> str:
233
+ """
234
+ selectイベントリスナーの関数
235
+
236
+ Parameters
237
+ ----------
238
+ input : gr.SelectData
239
+ _description_
240
+
241
+ Returns
242
+ -------
243
+ str
244
+ 選択した入力例
245
+ """
246
+ return input.value[0]
247
+
248
+
249
+ class ExtractedWordsHighlightedText(GrComponent):
250
+ """
251
+ 抽出結果のクラス
252
+ """
253
+ def __init__(self, label_info_dics: Dict[str, str | List[str]]):
254
+ """
255
+ コンストラクタ
256
+
257
+ Parameters
258
+ ----------
259
+ label_info_dics : Dict[str, str | List[str]]
260
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
261
+ """
262
+ super().__init__(label_info_dics)
263
+
264
+ def _create(
265
+ self, label_info_dics: Dict[str, str | List[str]]
266
+ ) -> gr.HighlightedText:
267
+ """
268
+ コンポーネントの作成
269
+
270
+ Parameters
271
+ ----------
272
+ label_info_dics : Dict[str, str | List[str]]
273
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
274
+
275
+ Returns
276
+ -------
277
+ gr.HighlightedText
278
+ 抽出結果のコンポーネント
279
+ """
280
+ color_map: Dict[str, str] = {
281
+ dic['jp']: dic['color'] for dic in label_info_dics.values()
282
+ }
283
+
284
+ comp = gr.HighlightedText(
285
+ color_map=color_map,
286
+ combine_adjacent=True,
287
+ adjacent_separator='、',
288
+ label='検出語彙一覧'
289
+ )
290
+
291
+ return comp
292
+
293
+
294
+ class CuisineInfos(GrLayout):
295
+ """
296
+ 全検索結果のクラス
297
+
298
+ Attributes
299
+ ----------
300
+ layout_type : gr.Column
301
+ GradioのColumn
302
+ """
303
+ layout_type = gr.Column
304
+
305
+ def _create(self, cuisine_infos_num: int) -> List[CuisineInfo]:
306
+ """
307
+ 子要素の作成
308
+
309
+ Parameters
310
+ ----------
311
+ cuisine_infos_num : int
312
+ 表示する料理検索結果の最大数
313
+
314
+ Returns
315
+ -------
316
+ List[CuisineInfo]
317
+ 全検索結果
318
+ """
319
+ children = [CuisineInfo() for _ in range(cuisine_infos_num)]
320
+
321
+ return children
322
+
323
+ @staticmethod
324
+ def update(
325
+ cuisine_info_dics: List[Dict[str, str]]
326
+ ) -> List[gr.Textbox | gr.Button]:
327
+ """
328
+ 全検索結果の更新
329
+
330
+ Parameters
331
+ ----------
332
+ cuisine_info_dics : List[Dict[str, str]]
333
+ 検索で見つかった料理の情報を持つ辞書のリスト
334
+
335
+ Returns
336
+ -------
337
+ List[gr.Textbox | gr.Button]
338
+ 全料理の情���のテキストボックスと、詳細ページへのボタンのリスト
339
+ """
340
+ cuisine_infos: List[gr.Textbox | gr.Button] = []
341
+
342
+ for cuisine_info_dic in cuisine_info_dics:
343
+ cuisine_info = CuisineInfo.update(cuisine_info_dic)
344
+
345
+ cuisine_infos.extend(cuisine_info)
346
+
347
+ return cuisine_infos
348
+
349
+
350
+ class CuisineInfo(GrLayout):
351
+ """
352
+ 料理の情報とURLボタンのクラス
353
+
354
+ Attributes
355
+ ----------
356
+ layout_type : gr.Row
357
+ GradioのRow
358
+ """
359
+ layout_type = gr.Row
360
+
361
+ def _create(self) -> List[InfoTextbox | UrlButton]:
362
+ """
363
+ 子要素の作成
364
+
365
+ Returns
366
+ -------
367
+ List[InfoTextbox | UrlButton]
368
+ 料理の情報のテキストボックスと、詳細ページへのボタンのリスト
369
+ """
370
+ info_textbox = InfoTextbox()
371
+ url_btn = UrlButton()
372
+
373
+ children = [info_textbox, url_btn]
374
+
375
+ return children
376
+
377
+ @staticmethod
378
+ def update(cuisine_info_dic: Dict[str, str]) -> List[gr.Textbox | gr.Button]:
379
+ """
380
+ 料理の情報とURLボタンの更新
381
+
382
+ Parameters
383
+ ----------
384
+ cuisine_info_dic : Dict[str, str]
385
+ 検索で見つかった料理の情報を持つ辞書
386
+
387
+ Returns
388
+ -------
389
+ List[gr.Textbox | gr.Button]
390
+ 料理の情報のテキストボックスと、詳細ページへのボタンのリスト
391
+ """
392
+ if cuisine_info_dic:
393
+ textbox, btn = CuisineInfo._reset(cuisine_info_dic)
394
+
395
+ else:
396
+ textbox, btn = CuisineInfo._hide()
397
+
398
+ return [textbox, btn]
399
+
400
+ @staticmethod
401
+ def _reset(cuisine_info_dic: Dict[str, str]) -> Tuple[gr.Textbox, gr.Button]:
402
+ """
403
+ 料理の変更
404
+
405
+ Parameters
406
+ ----------
407
+ cuisine_info_dic : Dict[str, str]
408
+ 検索で見つかった料理の情報を持つ辞書
409
+
410
+ Returns
411
+ -------
412
+ Tuple[gr.Textbox, gr.Button]
413
+ 料理の情報のテキストボックスと、詳細ページへのボタンのタプル
414
+ """
415
+ cuisine_name = cuisine_info_dic['name']
416
+ cuisine_info = cuisine_info_dic['info']
417
+ cuisine_url = cuisine_info_dic['url']
418
+
419
+ textbox = InfoTextbox.reset(cuisine_name, cuisine_info)
420
+ btn = UrlButton.reset(cuisine_name, cuisine_url)
421
+
422
+ return textbox, btn
423
+
424
+ @staticmethod
425
+ def _hide() -> Tuple[gr.Textbox, gr.Button]:
426
+ """
427
+ 料理の非表示
428
+
429
+ Returns
430
+ -------
431
+ Tuple[gr.Textbox, gr.Button]
432
+ 料理の情報のテキストボックスと、詳細ページへのボタンのタプル
433
+ """
434
+ textbox = InfoTextbox.hide()
435
+ btn = UrlButton.hide()
436
+
437
+ return textbox, btn
438
+
439
+
440
+ class InfoTextbox(GrComponent):
441
+ """
442
+ 料理の情報のクラス
443
+ """
444
+ def _create(self) -> gr.Textbox:
445
+ """
446
+ コンポーネントの作成
447
+
448
+ Returns
449
+ -------
450
+ gr.Textbox
451
+ 料理の情報のコンポーネント
452
+ """
453
+ comp = gr.Textbox(scale=9, visible=False)
454
+
455
+ return comp
456
+
457
+ @staticmethod
458
+ def reset(cuisine_name: str, cuisine_info: str) -> gr.Textbox:
459
+ """
460
+ 料理の情報の変更
461
+
462
+ Parameters
463
+ ----------
464
+ cuisine_name : str
465
+ 料理名
466
+ cuisine_info : str
467
+ 料理の情報
468
+
469
+ Returns
470
+ -------
471
+ gr.Textbox
472
+ 料理の情報のコンポーネント
473
+ """
474
+ comp = gr.Textbox(value=cuisine_info, label=cuisine_name, visible=True)
475
+
476
+ return comp
477
+
478
+ @staticmethod
479
+ def hide() -> gr.Textbox:
480
+ """
481
+ 料理の情報の非表示
482
+
483
+ Returns
484
+ -------
485
+ gr.Textbox
486
+ 非表示になった料理の情報のコンポーネント
487
+ """
488
+ comp = gr.Textbox(visible=False)
489
+
490
+ return comp
491
+
492
+
493
+ class UrlButton(GrComponent):
494
+ """
495
+ URLボタンのクラス
496
+ """
497
+ def _create(self) -> gr.Button:
498
+ """
499
+ コンポーネントの作成
500
+
501
+ Returns
502
+ -------
503
+ gr.Button
504
+ 詳細ページへのボタンのコンポーネント
505
+ """
506
+ comp = gr.Button(scale=1, visible=False)
507
+
508
+ return comp
509
+
510
+ @staticmethod
511
+ def reset(cuisine_name: str, cuisine_url: str) -> gr.Button:
512
+ """
513
+ 料理のボタンの更新
514
+
515
+ Parameters
516
+ ----------
517
+ cuisine_name : str
518
+ 料理名
519
+ cuisine_url : str
520
+ 料理の詳細ページへのURL
521
+
522
+ Returns
523
+ -------
524
+ gr.Button
525
+ 詳細ページへのボタンのコンポーネント
526
+ """
527
+ value = cuisine_name + '\n詳細ページ'
528
+ comp = gr.Button(value=value, link=cuisine_url, visible=True)
529
+
530
+ return comp
531
+
532
+ @staticmethod
533
+ def hide() -> gr.Button:
534
+ """
535
+ 料理のボタンの非表示
536
+
537
+ Returns
538
+ -------
539
+ gr.Button
540
+ 非表示になった詳細ページへのボタンのコンポーネント
541
+ """
542
+ comp = gr.Button(visible=False)
543
+
544
+ return comp
545
+
546
+
547
+ class CuisineInfoDictionariesMaker:
548
+ """
549
+ 料理検索結果の辞書のリスト作成用クラス
550
+
551
+ Attributes
552
+ ----------
553
+ _cuisine_searcher : CuisineSearcher
554
+ 料理を検索するオブジェクト
555
+ _word_unifier : WordUnifier
556
+ 抽出結果の表記ゆれを統一するオブジェクト
557
+ """
558
+ def __init__(
559
+ self,
560
+ cuisine_df_path: str,
561
+ unify_dics_path: str,
562
+ label_info_dics: Dict[str, str | List[str]],
563
+ cuisine_infos_num: int
564
+ ):
565
+ """
566
+ コンストラクタ
567
+
568
+ Parameters
569
+ ----------
570
+ cuisine_df_path : str
571
+ 料理のデータフレームが保存されているパス
572
+ unify_dics_path : str
573
+ 表記ゆれ統一用辞書が保存されているパス
574
+ label_info_dics : Dict[str, str | List[str]]
575
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
576
+ cuisine_infos_num : int
577
+ 表示する料理検索結果の最大数
578
+ """
579
+ self._cuisine_searcher = CuisineSearcher(
580
+ cuisine_df_path, label_info_dics, cuisine_infos_num
581
+ )
582
+ self._word_unifier = WordUnifier(unify_dics_path)
583
+
584
+ def create(
585
+ self, classified_words: Dict[str, List[str]]
586
+ ) -> List[Dict[str, str]]:
587
+ """
588
+ 料理検索結果の辞書の作成
589
+
590
+ Parameters
591
+ ----------
592
+ classified_words : Dict[str, List[str]]
593
+ ラベルと、そのラベルに分類された固有表現の辞書
594
+
595
+ Returns
596
+ -------
597
+ List[Dict[str, str]]
598
+ 料理検索結果の辞書のリスト
599
+ """
600
+ unified_words = self._word_unifier.unify(classified_words)
601
+ cuisine_info_dics = self._cuisine_searcher.search(unified_words)
602
+
603
+ return cuisine_info_dics
604
+
605
+
606
+ class CuisineSearcher:
607
+ """
608
+ 料理検索用のクラス
609
+
610
+ Attributes
611
+ ----------
612
+ _search_infos : List[str]
613
+ 料理のどの情報を取ってくるか示したリスト
614
+ _df : pd.DataFrame
615
+ 料理のデータフレーム
616
+ _label_to_col : Dict[str, List[str]]
617
+ 固有表現のラベルに対して、検索するデータフレームの列のリストの辞書
618
+ _words_dic : Dict[str, List[str]]
619
+ データフレームの列と、列に含まれる全ての要素の辞書
620
+ _cuisine_infos_num : int
621
+ 表示する料理検索結果の最大数
622
+ """
623
+ _search_infos = [
624
+ 'Name', 'Prefecture', 'Types', 'Seasons', 'Ingredients', 'Detail URL'
625
+ ]
626
+
627
+ def __init__(
628
+ self,
629
+ cuisine_df_path: str,
630
+ label_info_dics: Dict[str, str | List[str]],
631
+ cuisine_infos_num: int
632
+ ):
633
+ """
634
+ コンストラクタ
635
+
636
+ Parameters
637
+ ----------
638
+ cuisine_df_path : str
639
+ 料理のデータフレームが保存されているパス
640
+ label_info_dics : Dict[str, str | List[str]]
641
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
642
+ cuisine_infos_num : int
643
+ 表示する料理検索結果の最大数
644
+ """
645
+ self._df = read_csv_df(cuisine_df_path)
646
+ self._label_to_col = self._create_label_to_col(label_info_dics)
647
+ self._words_dic = {
648
+ col: self._find_words(col)
649
+ for cols in self._label_to_col.values() for col in cols
650
+ }
651
+ self._cuisine_infos_num = cuisine_infos_num
652
+
653
+ def _create_label_to_col(
654
+ self, label_info_dics: Dict[str, str | List[str]]
655
+ ) -> Dict[str, List[str]]:
656
+ """
657
+ label_to_colの作成
658
+
659
+ 固有表現のラベルに対応したデータフレームの列を
660
+ 特定するための辞書を作成する
661
+
662
+ Parameters
663
+ ----------
664
+ label_info_dics : Dict[str, str | List[str]]
665
+ 固有表現のラベルとラベルに対する各種設定情報の辞書
666
+
667
+ Returns
668
+ -------
669
+ Dict[str, List[str]]
670
+ 固有表現のラベルに対して、検索するデータフレームの列のリストの辞書
671
+
672
+ Raises
673
+ ------
674
+ ValueError
675
+ label_info_dicsに、データフレームに存在しない列名が含まれている場合
676
+ """
677
+ label_to_col: Dict[str, List[str]] = {
678
+ label: dic['df_cols'] for label, dic in label_info_dics.items()
679
+ }
680
+
681
+ df_cols = self._df.columns.tolist()
682
+ for cols in label_to_col.values():
683
+ for col in cols:
684
+ if col not in df_cols:
685
+ raise ValueError(f'"{col}"という列名は存在しません')
686
+
687
+ return label_to_col
688
+
689
+ def _find_words(self, col: str) -> List[str]:
690
+ """
691
+ 列に含まれる全要素の取得
692
+
693
+ Parameters
694
+ ----------
695
+ col : str
696
+ 列名
697
+
698
+ Returns
699
+ -------
700
+ List[str]
701
+ 列に含まれる全ての要素のリスト
702
+ """
703
+ words: List[str, List[str]] = self._df[col].value_counts().index.tolist()
704
+
705
+ if isinstance(words[0], list):
706
+ words_lst = words
707
+ unique_words: List[str] = []
708
+
709
+ for words in words_lst:
710
+ for word in words:
711
+ if word not in unique_words:
712
+ unique_words.append(word)
713
+
714
+ return unique_words
715
+
716
+ return words
717
+
718
+ def search(self, unified_words: Dict[str, List[str]]) -> List[Dict[str, str]]:
719
+ """
720
+ 料理の検索
721
+
722
+ Parameters
723
+ ----------
724
+ unified_words : Dict[str, List[str]]
725
+ 表記ゆれが統一された固有表現の辞書
726
+
727
+ Returns
728
+ -------
729
+ List[Dict[str, str]]
730
+ 検索結果の料理の情報を持つ辞書のリスト
731
+ """
732
+ on_df_words_dic = self._create_on_df_words_dic(unified_words)
733
+
734
+ if not on_df_words_dic:
735
+ gr.Info('いずれの語彙もデータに存在しませんでした')
736
+
737
+ return self._create_empty_dics()
738
+
739
+ cuisine_info_dics = self._create_cuisine_info_dics(on_df_words_dic)
740
+
741
+ return cuisine_info_dics
742
+
743
+ def _create_on_df_words_dic(
744
+ self, unified_words: Dict[str, List[str]]
745
+ ) -> Dict[str, List[str]]:
746
+ """
747
+ データフレームに存在する固有表現だけの辞書の作成
748
+
749
+ Parameters
750
+ ----------
751
+ unified_words : Dict[str, List[str]]
752
+ 表記ゆれが統一された固有表現の辞書
753
+
754
+ Returns
755
+ -------
756
+ Dict[str, List[str]]
757
+ データフレームに存在する表記ゆれが統一された固有表現の辞書
758
+ """
759
+ on_df_words_dic = {col: [] for col in self._words_dic}
760
+ not_on_df_words: List[str] = []
761
+
762
+ for label, words in unified_words.items():
763
+ search_cols = self._label_to_col[label]
764
+
765
+ for word in words:
766
+ not_on_df = True
767
+
768
+ for col in search_cols:
769
+ if word in self._words_dic[col]:
770
+ on_df_words_dic[col].append(word)
771
+
772
+ not_on_df = False
773
+
774
+ break
775
+
776
+ if not_on_df:
777
+ not_on_df_words.append(word)
778
+
779
+ if not_on_df_words:
780
+ CuisineSearcher._show_not_on_df_words(not_on_df_words)
781
+
782
+ on_df_words_dic = {
783
+ col: words for col, words in on_df_words_dic.items() if words
784
+ }
785
+
786
+ return on_df_words_dic
787
+
788
+ @staticmethod
789
+ def _show_not_on_df_words(not_on_df_words: List[str]) -> None:
790
+ """
791
+ データフレームに存在しなかった固有表現の表示
792
+
793
+ Parameters
794
+ ----------
795
+ not_on_df_words : List[str]
796
+ データフレームに存在しなかった固有表現のリスト
797
+ """
798
+ words = '、'.join(not_on_df_words)
799
+ message = f'無効な語彙: {words}'
800
+
801
+ gr.Info(message)
802
+
803
+ def _create_empty_dics(self) -> List[Dict[Any, Any]]:
804
+ """
805
+ 空の辞書のリストの作成
806
+
807
+ 検索結果に該当する料理がなかった場合は、CuisineInfosを非表示にする
808
+ CuisineInfo.update()に空の辞書を渡すと、
809
+ InfoTextboxとUrlButtonが非表示になる
810
+
811
+ Returns
812
+ -------
813
+ List[Dict[Any, Any]]
814
+ 空の辞書のリスト
815
+ """
816
+ return [{} for _ in range(self._cuisine_infos_num)]
817
+
818
+ def _create_cuisine_info_dics(
819
+ self, words_dic: Dict[str, List[str]]
820
+ ) -> List[Dict[str, str]]:
821
+ """
822
+ 料理の情報を持つ辞書の作成
823
+
824
+ Parameters
825
+ ----------
826
+ words_dic : Dict[str, List[str]]
827
+ 検索ワードのリストを持つ辞書
828
+
829
+ Returns
830
+ -------
831
+ List[Dict[str, str]]
832
+ 料理の情報を持つ辞書のリスト
833
+ """
834
+ condition_lst: List[pd.Series] = []
835
+
836
+ for col, words in words_dic.items():
837
+ condition = self._create_condition(col, words)
838
+ condition_lst.append(condition)
839
+
840
+ conditions = reduce(operator.and_, condition_lst)
841
+
842
+ cuisine_infos_lst = self._df.loc[conditions, self._search_infos].values.tolist()
843
+
844
+ if len(cuisine_infos_lst) > self._cuisine_infos_num:
845
+ cuisine_infos_lst = cuisine_infos_lst[:self._cuisine_infos_num]
846
+
847
+ if not cuisine_infos_lst:
848
+ gr.Info('検索条件が厳しすぎて、該当料理が見つかりませんでした')
849
+
850
+ return self._create_empty_dics()
851
+
852
+ cuisine_info_dics = self._lst_to_dics(cuisine_infos_lst)
853
+
854
+ return cuisine_info_dics
855
+
856
+ def _create_condition(self, col: str, words: List[str]) -> pd.Series:
857
+ """
858
+ 検索条件の作成
859
+
860
+ Parameters
861
+ ----------
862
+ col : str
863
+ 絞り込み対象列
864
+ words : List[str]
865
+ 検索ワード
866
+
867
+ Returns
868
+ -------
869
+ pd.Series
870
+ 該当料理の行がTrueになったboolのシリーズ
871
+ """
872
+ value_type = type(self._df.at[0, col])
873
+
874
+ if value_type is list:
875
+ condition = self._df[col].apply(
876
+ lambda values: any(word in values for word in words)
877
+ )
878
+
879
+ else:
880
+ conditions = [self._df[col] == word for word in words]
881
+ condition = reduce(operator.or_, conditions)
882
+
883
+ return condition
884
+
885
+ def _lst_to_dics(
886
+ self, infos_lst: List[List[str | List[str]]]
887
+ ) -> List[Dict[str, str]]:
888
+ """
889
+ リストから辞書への変換
890
+
891
+ 料理の情報のリストのリストを、料理の情報の辞書のリストに変換する
892
+
893
+ Parameters
894
+ ----------
895
+ infos_lst : List[List[str | List[str]]]
896
+ 料理の情報のリストのリスト
897
+
898
+ Returns
899
+ -------
900
+ List[Dict[str, str]]
901
+ 料理の情報の辞書のリスト
902
+ """
903
+ dics: List[Dict[str, str]] = []
904
+
905
+ for infos in infos_lst:
906
+ infos = [
907
+ '、'.join(info) if isinstance(info, list) else info
908
+ for info in infos
909
+ ]
910
+
911
+ name = infos[0]
912
+ info = ' | '.join(infos[1:-1])
913
+ url = infos[-1]
914
+
915
+ dic = {'name': name, 'info': info, 'url': url}
916
+ dics.append(dic)
917
+
918
+ dics_len = len(dics)
919
+
920
+ if dics_len < self._cuisine_infos_num:
921
+ dics = dics + [{} for _ in range(self._cuisine_infos_num - dics_len)]
922
+
923
+ return dics
924
+
925
+
926
+ class WordUnifier:
927
+ """
928
+ 表記ゆれ統一用のクラス
929
+
930
+ Attributes
931
+ ----------
932
+ _not_unify_labels : List[str]
933
+ 表記ゆれ統一対象ではない固有表現のラベルのリスト
934
+ _unify_dics : Dict[str, Dict[str, str]]
935
+ ラベルと、そのラベルの固有表現の表記ゆれ統一用の辞書の辞書
936
+ """
937
+ _not_unify_labels = ['SZN']
938
+
939
+ def __init__(self, unify_dics_path: str):
940
+ """
941
+ コンストラクタ
942
+
943
+ Parameters
944
+ ----------
945
+ unify_dics_path : str
946
+ 表記ゆれ統一用辞書が保存されているパス
947
+ """
948
+ self._unify_dics: Dict[str, Dict[str, str]] = load_json_obj(unify_dics_path)
949
+
950
+ def unify(
951
+ self, classified_words: Dict[str, List[str]]
952
+ ) -> Dict[str, List[str]]:
953
+ """
954
+ 表記ゆれの統一
955
+
956
+ Parameters
957
+ ----------
958
+ classified_words : Dict[str, List[str]]
959
+ ラベルと、そのラベルに分類された固有表現の辞書
960
+
961
+ Returns
962
+ -------
963
+ Dict[str, List[str]]
964
+ 表記ゆれが統一された固有表現の辞書
965
+ """
966
+ for label, words in classified_words.items():
967
+ if label in self._not_unify_labels:
968
+ continue
969
+
970
+ unify_dic = self._unify_dics[label]
971
+
972
+ unified_words = [
973
+ unify_dic[word] if word in unify_dic else word for word in words
974
+ ]
975
+
976
+ classified_words[label] = unified_words
977
+
978
+ return classified_words
979
+
980
+
981
+ model_name = 'wolf4032/bert-japanese-token-classification-search-local-cuisine'
982
+ cuisine_df_path = 'local_cuisine_dataframe.csv'
983
+ unify_dics_path = 'unifying_dictionaries.json'
984
+
985
+ app = App.create_and_launch(model_name, cuisine_df_path, unify_dics_path)